Files
KateLegend2_proj/assets/scripts/common/StorageMgr.ts
T
2026-05-06 08:17:32 +08:00

124 lines
3.8 KiB
TypeScript

/**
* Local-storage facade used by:
* - Level unlock state (req 17.1)
* - Floating control layout (req 17.2)
* - BGM / SFX volume (req 17.3, 16.4)
* - Tutorial completion flags (req 17.4)
* - Story-intro seen flag (req 17.5 / 19.5)
*
* Rationale for the thin facade:
* - The WeChat Mini Game runtime exposes `wx.setStorageSync` while the
* in-editor / browser preview exposes `sys.localStorage`. We isolate
* both behind a single `IStorageDriver` interface so that switching
* platforms is a single line.
* - On read, a failure never throws: it returns the provided default value
* (req 17.6 — "must not crash if local storage is unreadable").
* - All values go through JSON serialisation so that structured objects
* round-trip without callers having to remember to stringify.
*/
export interface IStorageDriver {
getItem(key: string): string | null;
setItem(key: string, value: string): void;
removeItem(key: string): void;
}
/** Selects the best available driver at runtime. */
function detectDriver(): IStorageDriver {
// 1. WeChat Mini Game global
const wxGlobal = (globalThis as any).wx;
if (wxGlobal && typeof wxGlobal.setStorageSync === 'function') {
return {
getItem(key) {
try {
const v = wxGlobal.getStorageSync(key);
return v === '' ? null : (v as string);
} catch {
return null;
}
},
setItem(key, value) {
try {
wxGlobal.setStorageSync(key, value);
} catch {
// swallow — req 17.6
}
},
removeItem(key) {
try {
wxGlobal.removeStorageSync(key);
} catch {
// swallow
}
},
};
}
// 2. Browser localStorage
if (typeof globalThis !== 'undefined' && (globalThis as any).localStorage) {
const ls = (globalThis as any).localStorage as Storage;
return {
getItem: (k) => ls.getItem(k),
setItem: (k, v) => ls.setItem(k, v),
removeItem: (k) => ls.removeItem(k),
};
}
// 3. In-memory fallback (Jest, Node-only unit tests).
const mem = new Map<string, string>();
return {
getItem: (k) => (mem.has(k) ? (mem.get(k) as string) : null),
setItem: (k, v) => {
mem.set(k, v);
},
removeItem: (k) => {
mem.delete(k);
},
};
}
export class StorageMgr {
private driver: IStorageDriver;
constructor(driver?: IStorageDriver) {
this.driver = driver ?? detectDriver();
}
/**
* Read a JSON-serialisable value. Returns `defaultValue` if the key is
* missing, unparseable, or the underlying driver throws (req 17.6).
*/
public get<T>(key: string, defaultValue: T): T {
try {
const raw = this.driver.getItem(key);
if (raw == null) {
return defaultValue;
}
return JSON.parse(raw) as T;
} catch {
return defaultValue;
}
}
/** Write a JSON-serialisable value. Silently ignores driver errors. */
public set<T>(key: string, value: T): void {
try {
this.driver.setItem(key, JSON.stringify(value));
} catch {
// req 17.6
}
}
public remove(key: string): void {
this.driver.removeItem(key);
}
/** Swap the driver at runtime. Used in unit tests and platform ports. */
public setDriver(driver: IStorageDriver): void {
this.driver = driver;
}
}
/** Shared project-wide storage manager. */
export const globalStorageMgr = new StorageMgr();