mock-violentmonkey
Version:
Mock violentmonkey's globals for testing userscripts
111 lines • 4 kB
JavaScript
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