Skip to content
Draft
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e1a71fb
Initial plan for issue
Copilot May 20, 2025
d1ffffc
Fix Safari SecurityError when Block All Cookies is set
Copilot May 20, 2025
c13a592
Fix PR comments on StorageHelperFuncs.ts
Copilot May 20, 2025
16f3b92
Remove newline changes from interface files per PR feedback
Copilot May 21, 2025
a488d75
Refactor storage helper functions to use single return statements per…
Copilot May 21, 2025
fd68d94
Remove generated JS files from PR per feedback
Copilot May 21, 2025
831c9b4
Remove RequestResponseHeaders.ts from PR per reviewer feedback
Copilot May 21, 2025
d472d65
Pass resolved string to _canSafelyAccessStorage per PR feedback
Copilot May 21, 2025
d2cd04d
Merge _canSafelyAccessStorage function into _getVerifiedStorageObject
Copilot May 21, 2025
93c0f44
Simplify storage access code with default-false approach
Copilot May 22, 2025
d4677e0
Merge branch 'main' into copilot/fix-2494
MSNev May 22, 2025
cdf088a
Remove unnecessary nested try-catch for direct storage access
Copilot May 22, 2025
22169a2
Merge branch 'main' into copilot/fix-2494
MSNev May 23, 2025
cee30cd
Add caching to storage verification to reduce test item creation
Copilot May 23, 2025
9e5c985
Update storage caching to use createCachedValue instead of reset
Copilot May 23, 2025
d0ee829
Remove _canUseLocalStorage and _canUseSessionStorage flags, add stora…
Copilot May 23, 2025
2274771
Don't reset cached storage on getItem and read operations
Copilot May 24, 2025
59362d4
Simplify storage wrapper and remove redundant code
Copilot May 24, 2025
b9789af
Use existing helper functions in utlCanUseLocalStorage/SessionStorage
Copilot May 24, 2025
e775471
Refactor storage helper functions to reduce code duplication and fix …
Copilot May 24, 2025
d25a325
Merge branch 'main' into copilot/fix-2494
MSNev May 27, 2025
7e32aa3
Address PR comments for StorageHelperFuncs.ts
Copilot May 27, 2025
ec81543
Refactor storage functions per PR feedback
Copilot May 28, 2025
23f5e50
Refactor utlCanUseLocalStorage and utlCanUseSessionStorage to include…
Copilot May 28, 2025
98c0e2c
Revert utility functions to original form while keeping _throwInterna…
Copilot May 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 130 additions & 114 deletions shared/AppInsightsCommon/src/StorageHelperFuncs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -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) {
try {
const uid = (new Date).toString();
const storage: Storage = getGlobalInst(storageTypeName);
const name = _storagePrefix + uid;

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 {
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;
}
};
}

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);
_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;
_verifiedSessionStorage = null;
// Force recheck of storage availability
utlCanUseLocalStorage(true);
utlCanUseSessionStorage(true);
}

/**
Expand All @@ -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);
}
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);
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 [];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just return the keys variable and remove the return from line 222

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified utlGetSessionStorageKeys to declare keys at the top and have a single return statement, removing the early return pattern. See commit 98c0e2c.

}

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);
}
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);
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);
return true;
}

return false;
}