UNPKG

mock-violentmonkey

Version:

Mock violentmonkey's globals for testing userscripts

111 lines 4 kB
import crypto from 'node:crypto'; import { getTabId } from '../tab.js'; import { BetterMap } from '../utils/index.js'; import { VMStorage } from '../vm-storage.js'; /* See: * https://violentmonkey.github.io/api/gm/ * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/075a3b326659b486fc270491d0730979be29bc27/types/tampermonkey/index.d.ts */ /* For getValue, setValue and all related functions */ const storages = new VMStorage(() => new BetterMap()); /** Sets a key / value pair for current context to storage. */ const setValue = (key, value) => { const oldValue = getRawValue(key); const stringified = JSON.stringify(value); if (value === undefined) { storages.get(false)?.delete(key); } else { storages.get(true).set(key, stringified); } dispatchChange(key, oldValue, stringified); }; const getRawValue = (key) => storages.get(false)?.get(key); /** Retrieves a value for current context from storage. */ const getValue = (key, defaultValue) => { const rawValue = getRawValue(key); if (rawValue === undefined) { return defaultValue; } return JSON.parse(rawValue); }; /** Deletes an existing key / value pair for current context from storage. */ const deleteValue = key => { const oldValue = getRawValue(key); storages.get(false)?.delete(key); dispatchChange(key, oldValue); }; /** Returns an array of keys of all available values within this context. */ const listValues = () => [...(storages.get(false)?.keys() ?? [])]; /** * ``` * BetterMap<number, // id of the async context * BetterMap<string, // name of the value to listen for * BetterMap<string, // uuid of the callback * [tabId: number, callback: AddValueChangeListenerCallback]> * > * > * ``` */ const valueChangeCallbacksStore = new VMStorage(() => new BetterMap()); /** Adds a change listener to the storage and returns the listener ID. */ const addValueChangeListener = (key, callback) => { const currentContextCallbacks = valueChangeCallbacksStore.get(true); const namedStorageCallbacks = currentContextCallbacks.get(key, () => new BetterMap()); const listenerId = crypto.randomUUID(); namedStorageCallbacks.set(listenerId, [getTabId(), callback]); return listenerId; }; /** Removes a change listener by its ID. */ const removeValueChangeListener = listenerId => { const currentContextCallbacks = valueChangeCallbacksStore.get(false); if (!currentContextCallbacks) { return; } for (const namedStorageCallbacks of currentContextCallbacks.values()) { namedStorageCallbacks.delete(listenerId); } }; const parseValue = (value) => value === undefined ? value : JSON.parse(value); const dispatchChange = (key, oldValue, newValue) => { if (oldValue === newValue) { return; } const currentContextCallbacks = valueChangeCallbacksStore.get(false); const namedStorageCallbacks = currentContextCallbacks?.get(key); if (!namedStorageCallbacks) { return; } const currentTabId = getTabId(); for (const [callbackTabId, callback] of namedStorageCallbacks.values()) { callback(key, parseValue(oldValue), parseValue(newValue), currentTabId !== callbackTabId); } }; /** * Use these in test files */ export { setValue as GM_setValue, getValue as GM_getValue, deleteValue as GM_deleteValue, listValues as GM_listValues, addValueChangeListener as GM_addValueChangeListener, removeValueChangeListener as GM_removeValueChangeListener, }; /** * These are for in userscripts */ Object.defineProperties(globalThis, { GM_setValue: { value: setValue, }, GM_getValue: { value: getValue, }, GM_deleteValue: { value: deleteValue, }, GM_listValues: { value: listValues, }, GM_addValueChangeListener: { value: addValueChangeListener, }, GM_removeValueChangeListener: { value: removeValueChangeListener, }, }); //# sourceMappingURL=storage.js.map