ultraviolet/client/storage.js
2022-02-19 00:10:57 -05:00

155 lines
No EOL
6 KiB
JavaScript

import EventEmitter from "./events.js";
import HookEvent from "./hook.js";
class StorageApi extends EventEmitter {
constructor(ctx) {
super();
this.ctx = ctx;
this.window = ctx.window;
this.localStorage = this.window.localStorage || null;
this.sessionStorage = this.window.sessionStorage || null;
this.Storage = this.window.Storage || {};
this.storeProto = this.Storage.prototype || {};
this.getItem = this.storeProto.getItem || null;
this.setItem = this.storeProto.setItem || null;
this.removeItem = this.storeProto.removeItem || null;
this.clear = this.storeProto.clear || null;
this.key = this.storeProto.key || null;
this.methods = ['key', 'getItem', 'setItem', 'removeItem', 'clear'];
this.wrappers = new ctx.nativeMethods.Map();
};
overrideMethods() {
this.ctx.override(this.storeProto, 'getItem', (target, that, args) => {
if (!args.length) return target.apply((this.wrappers.get(that) || that), args);
let [ name ] = args;
const event = new HookEvent({ name }, target, (this.wrappers.get(that) || that));
this.emit('getItem', event);
if (event.intercepted) return event.returnValue;
return event.target.call(event.that, event.data.name);
});
this.ctx.override(this.storeProto, 'setItem', (target, that, args) => {
if (2 > args.length) return target.apply((this.wrappers.get(that) || that), args);
let [ name, value ] = args;
const event = new HookEvent({ name, value }, target, (this.wrappers.get(that) || that));
this.emit('setItem', event);
if (event.intercepted) return event.returnValue;
return event.target.call(event.that, event.data.name, event.data.value);
});
this.ctx.override(this.storeProto, 'removeItem', (target, that, args) => {
if (!args.length) return target.apply((this.wrappers.get(that) || that), args);
let [ name ] = args;
const event = new HookEvent({ name }, target, (this.wrappers.get(that) || that));
this.emit('removeItem', event);
if (event.intercepted) return event.returnValue;
return event.target.call(event.that, event.data.name);
});
this.ctx.override(this.storeProto, 'clear', (target, that) => {
const event = new HookEvent(null, target, (this.wrappers.get(that) || that));
this.emit('clear', event);
if (event.intercepted) return event.returnValue;
return event.target.call(event.that);
});
this.ctx.override(this.storeProto, 'key', (target, that, args) => {
if (!args.length) return target.apply((this.wrappers.get(that) || that), args);
let [ index ] = args;
const event = new HookEvent({ index }, target, (this.wrappers.get(that) || that));
this.emit('key', event);
if (event.intercepted) return event.returnValue;
return event.target.call(event.that, event.data.index);
});
};
overrideLength() {
this.ctx.overrideDescriptor(this.storeProto, 'length', {
get: (target, that) => {
const event = new HookEvent({ length: target.call((this.wrappers.get(that) || that)) }, target, (this.wrappers.get(that) || that));
this.emit('length', event);
if (event.intercepted) return event.returnValue;
return event.data.length;
},
});
};
emulate(storage, obj = {}) {
this.ctx.nativeMethods.setPrototypeOf(obj, this.storeProto);
const proxy = new this.ctx.window.Proxy(obj, {
get: (target, prop) => {
if (prop in this.storeProto || typeof prop === 'symbol') return storage[prop];
const event = new HookEvent({ name: prop }, null, storage);
this.emit('get', event);
if (event.intercepted) return event.returnValue;
return storage[event.data.name];
},
set: (target, prop, value) => {
if (prop in this.storeProto || typeof prop === 'symbol') return storage[prop] = value;
const event = new HookEvent({ name: prop, value }, null, storage);
this.emit('set', event);
if (event.intercepted) return event.returnValue;
return storage[event.data.name] = event.data.value;
},
deleteProperty: (target, prop) => {
if (typeof prop === 'symbol') return delete storage[prop];
const event = new HookEvent({ name: prop }, null, storage);
this.emit('delete', event);
if (event.intercepted) return event.returnValue;
return delete storage[event.data.name];
},
});
this.wrappers.set(proxy, storage);
this.ctx.nativeMethods.setPrototypeOf(proxy, this.storeProto);
return proxy;
};
};
export default StorageApi;
class StorageWrapper {
constructor(api, storage, wrap, unwrap, origin) {
this.api = api;
this.ctx = api.ctx;
this.storage = storage;
this.wrap = wrap;
this.unwrap = unwrap;
this.origin = origin;
this.emulation = {};
};
clear() {
for (const key in this.storage) {
const data = this.unwrap(key);
if (!data || data.origin !== this.origin) continue;
this.api.removeItem.call(this.storage, key);
};
this.emulation = {};
this.ctx.nativeMethods.setPrototypeOf(this.emulation, this.api.storeProto);
};
__init() {
for (const key in this.storage) {
const data = this.unwrap(key);
if (!data || data.origin !== this.origin) continue;
this.emulation[data.name] = this.api.getItem.call(this.storage, key);
};
this.ctx.nativeMethods.setPrototypeOf(this.emulation, this.api.storeProto);
};
};