Files

152 lines
5.4 KiB
JavaScript

const { scaffoldModule } = require("./scaffold-module");
const { mergeStorageState } = require("./merge-storage-state");
function normalizeStorageSeed(seed = {}) {
const merged = mergeStorageState(seed);
return {
cookie: seed.cookie || "",
cookies: merged.cookies,
localStorage: merged.localStorage,
sessionStorage: merged.sessionStorage,
};
}
function renderStoragePatch(seed) {
const serializedSeed = JSON.stringify(seed, null, 2);
return `(
function installStorageModule(globalObject) {
const seed = ${serializedSeed};
const protector = globalObject.__envNativeProtector || null;
function defineValue(target, key, value) {
Object.defineProperty(target, key, {
configurable: true,
enumerable: false,
writable: true,
value: value
});
}
function Storage() {}
defineValue(Storage.prototype, "key", function key(index) {
const keys = Object.keys(this.__store__);
return typeof keys[index] === "string" ? keys[index] : null;
});
defineValue(Storage.prototype, "getItem", function getItem(name) {
const key = String(name);
return Object.prototype.hasOwnProperty.call(this.__store__, key) ? this.__store__[key] : null;
});
defineValue(Storage.prototype, "setItem", function setItem(name, value) {
this.__store__[String(name)] = String(value);
this.length = Object.keys(this.__store__).length;
});
defineValue(Storage.prototype, "removeItem", function removeItem(name) {
delete this.__store__[String(name)];
this.length = Object.keys(this.__store__).length;
});
defineValue(Storage.prototype, "clear", function clear() {
this.__store__ = Object.create(null);
this.length = 0;
});
function createStorage(initialState) {
const storage = Object.create(Storage.prototype);
defineValue(storage, "__store__", Object.assign(Object.create(null), initialState || {}));
defineValue(storage, "length", Object.keys(storage.__store__).length);
Object.keys(storage.__store__).forEach(function defineEntry(key) {
Object.defineProperty(storage, key, {
configurable: true,
enumerable: true,
get: function getEntry() {
return storage.getItem(key);
},
set: function setEntry(value) {
storage.setItem(key, value);
}
});
});
return storage;
}
if (protector && protector.repairFunctionMeta) {
protector.repairFunctionMeta(Storage, { name: "Storage", length: 0 });
protector.repairFunctionMeta(Storage.prototype.key, { name: "key", length: 1 });
protector.repairFunctionMeta(Storage.prototype.getItem, { name: "getItem", length: 1 });
protector.repairFunctionMeta(Storage.prototype.setItem, { name: "setItem", length: 2 });
protector.repairFunctionMeta(Storage.prototype.removeItem, { name: "removeItem", length: 1 });
protector.repairFunctionMeta(Storage.prototype.clear, { name: "clear", length: 0 });
}
if (protector && protector.protectFunction) {
protector.protectFunction(Storage, "Storage");
protector.protectFunction(Storage.prototype.key, "key");
protector.protectFunction(Storage.prototype.getItem, "getItem");
protector.protectFunction(Storage.prototype.setItem, "setItem");
protector.protectFunction(Storage.prototype.removeItem, "removeItem");
protector.protectFunction(Storage.prototype.clear, "clear");
}
const localStorage = createStorage(seed.localStorage || {});
const sessionStorage = createStorage(seed.sessionStorage || {});
Object.defineProperty(globalObject, "Storage", {
configurable: true,
enumerable: false,
writable: true,
value: Storage
});
Object.defineProperty(globalObject, "localStorage", {
configurable: true,
enumerable: false,
writable: true,
value: localStorage
});
Object.defineProperty(globalObject, "sessionStorage", {
configurable: true,
enumerable: false,
writable: true,
value: sessionStorage
});
if (globalObject.document && typeof globalObject.document.cookie === "string" && seed.cookie) {
globalObject.document.cookie = seed.cookie;
}
return {
localStorage: localStorage,
sessionStorage: sessionStorage
};
})(typeof globalThis !== "undefined" ? globalThis : window);
`;
}
function buildStorageModule(seed = {}) {
const module = scaffoldModule("storage-module");
const normalizedSeed = normalizeStorageSeed(seed);
module.patchPlan = [
"构建 Storage 构造器与原型方法。",
"按真实种子预置 localStorage / sessionStorage 的 key/value 运行态。",
"把 cookie 字符串与 document.cookie 入口联动,而不是把 storage 状态和 cookie 割裂开。",
];
module.patchCode = renderStoragePatch(normalizedSeed);
module.runtimeState = normalizedSeed;
module.validation = [
"检查 localStorage.length / sessionStorage.length。",
"检查 getItem/setItem/removeItem/clear/key 的返回值。",
"检查 localStorage.device_id 这类同名属性访问是否与 getItem 一致。",
"检查 document 已存在时 cookie 字符串能否同步注入。",
];
module.residualRisk = [
"未实现配额异常、跨 tab 事件同步与 storage event。",
"如果站点依赖历史态的更新时间、写入顺序或事件广播,仍需继续扩展。",
];
return module;
}
module.exports = {
buildStorageModule,
normalizeStorageSeed,
};