🐛 fix: 修复受限环境下 secure-storage 触发 sessionStorage SecurityError

- 新增 safeGetStorage 探测能力探针,捕获 sessionStorage/localStorage 读取与写入探测时抛出的 SecurityError,返回可用的 Storage 或 null
- 将顶层敏感数据迁移逻辑外层包裹 try-catch,避免 iframe/sandbox/隐私设置等受限上下文导致模块初始化阶段未捕获异常中断
- 在 migrateFromLocalStorage 中基于 safeGetStorage 动态获取 localStorage,localStorage 不可用时直接跳过迁移而非抛错
- 补充受限环境降级单元测试:模拟 sessionStorage/localStorage 属性读取被拒、以及 sessionStorage 写入失败时确保降级到 fallbackStorage 且不抛异常
This commit is contained in:
Abner
2026-05-08 18:33:59 +08:00
parent be2c4e4dfb
commit b5ec8eb18a
2 changed files with 125 additions and 16 deletions

View File

@@ -125,4 +125,84 @@ describe('SecureStorage', () => {
expect(store.getItem('bad')).toBeNull();
});
});
describe('受限环境降级', () => {
let originalSessionStorage;
let originalLocalStorage;
beforeEach(() => {
originalSessionStorage = Object.getOwnPropertyDescriptor(window, 'sessionStorage');
originalLocalStorage = Object.getOwnPropertyDescriptor(window, 'localStorage');
});
afterEach(() => {
if (originalSessionStorage) {
Object.defineProperty(window, 'sessionStorage', originalSessionStorage);
} else {
delete window.sessionStorage;
}
if (originalLocalStorage) {
Object.defineProperty(window, 'localStorage', originalLocalStorage);
} else {
delete window.localStorage;
}
});
it('应该在 sessionStorage 访问被拒时降级到内存存储', () => {
// 模拟 SecurityError属性读取抛异常
Object.defineProperty(window, 'sessionStorage', {
get() {
throw new DOMException('Access is denied', 'SecurityError');
},
configurable: true
});
// 直接构造新实例,构造函数内部调用 safeGetStorage 会捕获异常
const SecureStorageClass = store.constructor;
const restricted = new SecureStorageClass();
expect(restricted.storage).toBeNull();
restricted.setItem('key', 'value');
expect(restricted.getItem('key')).toBe('value');
expect(restricted.fallbackStorage.has('key')).toBe(true);
});
it('应该在 localStorage 访问被拒时跳过迁移', () => {
Object.defineProperty(window, 'localStorage', {
get() {
throw new DOMException('Access is denied', 'SecurityError');
},
configurable: true
});
// migrateFromLocalStorage 内部调用 safeGetStorage 会捕获异常
expect(() => store.migrateFromLocalStorage('any_key')).not.toThrow();
});
it('应该在 sessionStorage 写入失败时降级到内存', () => {
// 模拟 sessionStorage.setItem 拒绝写入
store.storage = {
getItem: jest.fn(() => null),
setItem: jest.fn(() => {
throw new DOMException('Quota exceeded', 'QuotaExceededError');
}),
removeItem: jest.fn()
};
// 迁移数据localStorage 读取成功,但 sessionStorage 写入失败
const local = {
getItem: jest.fn(() => 'old_value'),
setItem: jest.fn(),
removeItem: jest.fn()
};
Object.defineProperty(window, 'localStorage', {
get: () => local,
configurable: true
});
store.migrateFromLocalStorage('test_key');
// 数据应降级到 fallbackStorage
expect(store.fallbackStorage.has('test_key')).toBe(true);
});
});
});