-
Notifications
You must be signed in to change notification settings - Fork 256
Fix Safari SecurityError when Block All Cookies is enabled #2539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 21 commits
e1a71fb
d1ffffc
c13a592
16f3b92
a488d75
fd68d94
831c9b4
d472d65
d2cd04d
93c0f44
d4677e0
cdf088a
22169a2
cee30cd
9e5c985
d0ee829
2274771
59362d4
b9789af
e775471
d25a325
7e32aa3
ec81543
23f5e50
98c0e2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,22 +5,24 @@ | |
| IDiagnosticLogger, _eInternalMessageId, _throwInternal, dumpObj, eLoggingSeverity, getExceptionName, getGlobal, getGlobalInst, | ||
| isNullOrUndefined, objForEachKey | ||
| } from "@microsoft/applicationinsights-core-js"; | ||
| import { ICachedValue, createCachedValue, objGetOwnPropertyDescriptor } from "@nevware21/ts-utils"; | ||
| import { StorageType } from "./Enums"; | ||
|
|
||
| let _canUseLocalStorage: boolean = undefined; | ||
| let _canUseSessionStorage: boolean = undefined; | ||
| let _storagePrefix: string = ""; | ||
|
|
||
| // Create cached values for verified storage objects to avoid repeated checks | ||
| let _verifiedLocalStorage: ICachedValue<Storage> = null; | ||
| let _verifiedSessionStorage: ICachedValue<Storage> = null; | ||
|
|
||
| /** | ||
| * Gets the localStorage object if available | ||
| * @returns {Storage} - Returns the storage object if available else returns null | ||
| */ | ||
| function _getLocalStorageObject(): Storage { | ||
| if (utlCanUseLocalStorage()) { | ||
| return _getVerifiedStorageObject(StorageType.LocalStorage); | ||
| if (!_verifiedLocalStorage) { | ||
| _verifiedLocalStorage = createCachedValue(_getVerifiedStorageObject(StorageType.LocalStorage)); | ||
| } | ||
|
|
||
| return null; | ||
| return _verifiedLocalStorage.v; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -30,56 +32,127 @@ | |
| * @returns {Storage} Returns storage object verified that it is usable | ||
| */ | ||
| function _getVerifiedStorageObject(storageType: StorageType): Storage { | ||
| let result = null; | ||
| const storageTypeName = storageType === StorageType.LocalStorage ? "localStorage" : "sessionStorage"; | ||
|
|
||
| try { | ||
| if (isNullOrUndefined(getGlobal())) { | ||
| return null; | ||
| // Default to false - assume storage is not available | ||
| let canAccessStorage = false; | ||
| const gbl = getGlobal(); | ||
|
|
||
| // Only proceed if we have a global object | ||
| if (!isNullOrUndefined(gbl)) { | ||
| // Try the safest method first (property descriptor) | ||
| try { | ||
| const descriptor = objGetOwnPropertyDescriptor(gbl, storageTypeName); | ||
| if (descriptor && descriptor.get) { | ||
| canAccessStorage = true; | ||
| } | ||
| } catch (e) { | ||
| // If descriptor check fails, try direct access | ||
| // This will be caught by the outer try-catch if it fails | ||
| canAccessStorage = !!gbl[storageTypeName]; | ||
| } | ||
| } | ||
| let uid = (new Date).toString(); | ||
| let storage: Storage = getGlobalInst(storageType === StorageType.LocalStorage ? "localStorage" : "sessionStorage"); | ||
| let name:string = _storagePrefix + uid; | ||
| storage.setItem(name, uid); | ||
| let fail = storage.getItem(name) !== uid; | ||
| storage.removeItem(name); | ||
| if (!fail) { | ||
| return storage; | ||
|
|
||
| // If we determined storage might be accessible, verify it works | ||
| if (canAccessStorage) { | ||
MSNev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| try { | ||
| const uid = (new Date).toString(); | ||
MSNev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const storage: Storage = getGlobalInst(storageTypeName); | ||
| const name = _storagePrefix + uid; | ||
MSNev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| storage.setItem(name, uid); | ||
| const success = storage.getItem(name) === uid; | ||
| storage.removeItem(name); | ||
|
|
||
| if (success) { | ||
| // Create a wrapped storage object that protects write operations | ||
| const originalStorage = storage; | ||
|
|
||
| // Helper to create storage operation methods with consistent error handling | ||
| const _createStorageOperation = function<T>(operationName: string, resetOnError: boolean, defaultValue?: T): Function { | ||
MSNev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return function(...args: any[]): T { | ||
| try { | ||
| return originalStorage[operationName].apply(originalStorage, args); | ||
| } catch (e) { | ||
| // Reset cache on error for write operations only | ||
| if (resetOnError) { | ||
| if (storageType === StorageType.LocalStorage) { | ||
| _verifiedLocalStorage = null; | ||
| } else { | ||
| _verifiedSessionStorage = null; | ||
| } | ||
| } | ||
| return defaultValue; | ||
| } | ||
| }; | ||
| } | ||
MSNev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const wrappedStorage = { | ||
| // Read operations - don't reset cache on error | ||
| getItem: _createStorageOperation<string>("getItem", false, null), | ||
| key: _createStorageOperation<string>("key", false, null), | ||
| get length(): number { | ||
| return _createStorageOperation<number>("length", false, 0)(); | ||
| }, | ||
|
|
||
| // Write operations - reset cache on error | ||
| setItem: _createStorageOperation<void>("setItem", true), | ||
| removeItem: _createStorageOperation<void>("removeItem", true), | ||
| clear: _createStorageOperation<void>("clear", true) | ||
| }; | ||
|
|
||
| result = wrappedStorage as Storage; | ||
| } | ||
| } catch (e) { | ||
| // Storage exists but can't be used (quota exceeded, etc.) | ||
| } | ||
| } | ||
| } catch (exception) { | ||
| // eslint-disable-next-line no-empty | ||
| } catch (e) { | ||
| // Catch any unexpected errors | ||
| } | ||
|
|
||
| return null; | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the sessionStorage object if available | ||
| * @returns {Storage} - Returns the storage object if available else returns null | ||
| */ | ||
| function _getSessionStorageObject(): Storage { | ||
| if (utlCanUseSessionStorage()) { | ||
| return _getVerifiedStorageObject(StorageType.SessionStorage); | ||
| if (!_verifiedSessionStorage) { | ||
| _verifiedSessionStorage = createCachedValue(_getVerifiedStorageObject(StorageType.SessionStorage)); | ||
| } | ||
|
|
||
| return null; | ||
| return _verifiedSessionStorage.v; | ||
| } | ||
|
|
||
|
|
||
|
|
||
| /** | ||
| * Disables the global SDK usage of local or session storage if available | ||
| */ | ||
| export function utlDisableStorage() { | ||
| _canUseLocalStorage = false; | ||
| _canUseSessionStorage = false; | ||
| _verifiedLocalStorage = createCachedValue(null); | ||
MSNev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| _verifiedSessionStorage = createCachedValue(null); | ||
| } | ||
|
|
||
| export function utlSetStoragePrefix(storagePrefix: string) { | ||
| _storagePrefix = storagePrefix || ""; | ||
| // Reset the cached storage instances since prefix changed | ||
| _verifiedLocalStorage = null; | ||
| _verifiedSessionStorage = null; | ||
| } | ||
|
|
||
| /** | ||
| * Re-enables the global SDK usage of local or session storage if available | ||
| */ | ||
| export function utlEnableStorage() { | ||
| _canUseLocalStorage = utlCanUseLocalStorage(true); | ||
| _canUseSessionStorage = utlCanUseSessionStorage(true); | ||
| _verifiedLocalStorage = null; | ||
MSNev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| _verifiedSessionStorage = null; | ||
| // Force recheck of storage availability | ||
| utlCanUseLocalStorage(true); | ||
| utlCanUseSessionStorage(true); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -88,142 +161,85 @@ | |
| * @param reset - Should the usage be reset and determined only based on whether LocalStorage is available | ||
| */ | ||
| export function utlCanUseLocalStorage(reset?: boolean): boolean { | ||
| if (reset || _canUseLocalStorage === undefined) { | ||
| _canUseLocalStorage = !!_getVerifiedStorageObject(StorageType.LocalStorage); | ||
| if (reset) { | ||
| _verifiedLocalStorage = null; | ||
| } | ||
|
|
||
| return _canUseLocalStorage; | ||
| return !!_getLocalStorageObject(); | ||
| } | ||
|
|
||
| export function utlGetLocalStorage(logger: IDiagnosticLogger, name: string): string { | ||
| const storage = _getLocalStorageObject(); | ||
| if (storage !== null) { | ||
| try { | ||
| return storage.getItem(name); | ||
| } catch (e) { | ||
| _canUseLocalStorage = false; | ||
|
|
||
| _throwInternal(logger, | ||
| eLoggingSeverity.WARNING, | ||
| _eInternalMessageId.BrowserCannotReadLocalStorage, | ||
| "Browser failed read of local storage. " + getExceptionName(e), | ||
| { exception: dumpObj(e) }); | ||
| } | ||
| return storage.getItem(name); | ||
MSNev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| return null; | ||
| } | ||
|
|
||
| export function utlSetLocalStorage(logger: IDiagnosticLogger, name: string, data: string): boolean { | ||
| const storage = _getLocalStorageObject(); | ||
| if (storage !== null) { | ||
| try { | ||
| storage.setItem(name, data); | ||
| return true; | ||
| } catch (e) { | ||
| _canUseLocalStorage = false; | ||
|
|
||
| _throwInternal(logger, | ||
| eLoggingSeverity.WARNING, | ||
| _eInternalMessageId.BrowserCannotWriteLocalStorage, | ||
| "Browser failed write to local storage. " + getExceptionName(e), | ||
| { exception: dumpObj(e) }); | ||
| } | ||
| storage.setItem(name, data); | ||
MSNev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| export function utlRemoveStorage(logger: IDiagnosticLogger, name: string): boolean { | ||
| const storage = _getLocalStorageObject(); | ||
| if (storage !== null) { | ||
| try { | ||
| storage.removeItem(name); | ||
| return true; | ||
| } catch (e) { | ||
| _canUseLocalStorage = false; | ||
|
|
||
| _throwInternal(logger, | ||
| eLoggingSeverity.WARNING, | ||
| _eInternalMessageId.BrowserFailedRemovalFromLocalStorage, | ||
| "Browser failed removal of local storage item. " + getExceptionName(e), | ||
| { exception: dumpObj(e) }); | ||
| } | ||
| storage.removeItem(name); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep the original _throwInternal, but no need to set the _canUseLocalStorage as this is now handled in the wrapped implementation.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reverted utlCanUseSessionStorage to the original simple form, removing the try/catch while keeping the function logic clean. The wrapped implementation now handles error scenarios. See commit 98c0e2c. |
||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| export function utlCanUseSessionStorage(reset?: boolean): boolean { | ||
| if (reset || _canUseSessionStorage === undefined) { | ||
| _canUseSessionStorage = !!_getVerifiedStorageObject(StorageType.SessionStorage); | ||
| if (reset) { | ||
| _verifiedSessionStorage = null; | ||
| } | ||
|
|
||
| return _canUseSessionStorage; | ||
| return !!_getSessionStorageObject(); | ||
| } | ||
|
|
||
| export function utlGetSessionStorageKeys(): string[] { | ||
| const keys: string[] = []; | ||
|
|
||
| if (utlCanUseSessionStorage()) { | ||
| objForEachKey(getGlobalInst<any>("sessionStorage"), (key) => { | ||
| keys.push(key); | ||
| }); | ||
| const keys: string[] = []; | ||
| try { | ||
| objForEachKey(getGlobalInst<any>("sessionStorage"), (key) => { | ||
| keys.push(key); | ||
| }); | ||
| } catch (e) { | ||
| // Invalidate session storage on any error | ||
| _verifiedSessionStorage = null; | ||
| } | ||
| return keys; | ||
| } | ||
|
|
||
| return keys; | ||
| return []; | ||
|
||
| } | ||
|
|
||
| export function utlGetSessionStorage(logger: IDiagnosticLogger, name: string): string { | ||
| const storage = _getSessionStorageObject(); | ||
| if (storage !== null) { | ||
| try { | ||
| return storage.getItem(name); | ||
| } catch (e) { | ||
| _canUseSessionStorage = false; | ||
|
|
||
| _throwInternal(logger, | ||
| eLoggingSeverity.WARNING, | ||
| _eInternalMessageId.BrowserCannotReadSessionStorage, | ||
| "Browser failed read of session storage. " + getExceptionName(e), | ||
| { exception: dumpObj(e) }); | ||
| } | ||
| return storage.getItem(name); | ||
MSNev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| return null; | ||
| } | ||
|
|
||
| export function utlSetSessionStorage(logger: IDiagnosticLogger, name: string, data: string): boolean { | ||
| const storage = _getSessionStorageObject(); | ||
| if (storage !== null) { | ||
| try { | ||
| storage.setItem(name, data); | ||
| return true; | ||
| } catch (e) { | ||
| _canUseSessionStorage = false; | ||
|
|
||
| _throwInternal(logger, | ||
| eLoggingSeverity.WARNING, | ||
| _eInternalMessageId.BrowserCannotWriteSessionStorage, | ||
| "Browser failed write to session storage. " + getExceptionName(e), | ||
| { exception: dumpObj(e) }); | ||
| } | ||
| storage.setItem(name, data); | ||
MSNev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| export function utlRemoveSessionStorage(logger: IDiagnosticLogger, name: string): boolean { | ||
| const storage = _getSessionStorageObject(); | ||
| if (storage !== null) { | ||
| try { | ||
| storage.removeItem(name); | ||
| return true; | ||
| } catch (e) { | ||
| _canUseSessionStorage = false; | ||
|
|
||
| _throwInternal(logger, | ||
| eLoggingSeverity.WARNING, | ||
| _eInternalMessageId.BrowserFailedRemovalFromSessionStorage, | ||
| "Browser failed removal of session storage item. " + getExceptionName(e), | ||
| { exception: dumpObj(e) }); | ||
| } | ||
| storage.removeItem(name); | ||
MSNev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.