|
@@ -1,632 +1,629 @@
|
|
|
-import Promise from 'sync-promise-lite';
|
|
|
-import { i18n, request, buffer2string, getFullUrl } from 'src/common';
|
|
|
-import { getNameURI, getScriptInfo, isRemote, parseMeta, newScript } from './script';
|
|
|
+import { i18n, request, buffer2string, getFullUrl, object } from 'src/common';
|
|
|
+import { getNameURI, isRemote, parseMeta, newScript } from './script';
|
|
|
import { testScript, testBlacklist } from './tester';
|
|
|
import { register } from './init';
|
|
|
|
|
|
-let db;
|
|
|
-
|
|
|
-const position = {
|
|
|
- value: 0,
|
|
|
- set(v) {
|
|
|
- position.value = +v || 0;
|
|
|
- },
|
|
|
- get() {
|
|
|
- return position.value + 1;
|
|
|
- },
|
|
|
- update(v) {
|
|
|
- if (position.value < +v) position.set(v);
|
|
|
- },
|
|
|
-};
|
|
|
-
|
|
|
-register(openDatabase().then(initPosition));
|
|
|
-
|
|
|
-function openDatabase() {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
+const patch = () => new Promise((resolve, reject) => {
|
|
|
+ console.info('Upgrade database...');
|
|
|
+ init();
|
|
|
+ function init() {
|
|
|
const req = indexedDB.open('Violentmonkey', 1);
|
|
|
req.onsuccess = () => {
|
|
|
- db = req.result;
|
|
|
- resolve();
|
|
|
+ transform(req.result);
|
|
|
};
|
|
|
- req.onerror = e => {
|
|
|
- const { error } = e.target;
|
|
|
- console.error(`IndexedDB error: ${error.message}`);
|
|
|
- reject(error);
|
|
|
+ req.onerror = reject;
|
|
|
+ req.onupgradeneeded = () => {
|
|
|
+ // No available upgradation
|
|
|
+ throw reject();
|
|
|
};
|
|
|
- req.onupgradeneeded = e => {
|
|
|
- const _db = e.currentTarget.result;
|
|
|
- // scripts: id uri custom meta enabled update code position
|
|
|
- const os = _db.createObjectStore('scripts', {
|
|
|
- keyPath: 'id',
|
|
|
- autoIncrement: true,
|
|
|
- });
|
|
|
- os.createIndex('uri', 'uri', { unique: true });
|
|
|
- os.createIndex('update', 'update', { unique: false });
|
|
|
- // position should be unique at last
|
|
|
- os.createIndex('position', 'position', { unique: false });
|
|
|
- // require: uri code
|
|
|
- _db.createObjectStore('require', { keyPath: 'uri' });
|
|
|
- // cache: uri data
|
|
|
- _db.createObjectStore('cache', { keyPath: 'uri' });
|
|
|
- // values: uri values
|
|
|
- _db.createObjectStore('values', { keyPath: 'uri' });
|
|
|
+ }
|
|
|
+ function transform(db) {
|
|
|
+ const tx = db.transaction(['scripts', 'require', 'cache', 'values']);
|
|
|
+ const updates = {};
|
|
|
+ let processing = 3;
|
|
|
+ const onCallback = () => {
|
|
|
+ processing -= 1;
|
|
|
+ if (!processing) resolve(browser.storage.local.set(updates));
|
|
|
};
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-function transformScript(script) {
|
|
|
- // XXX transform custom fields used in v2.6.1-
|
|
|
- if (script) {
|
|
|
- const { custom } = script;
|
|
|
- [
|
|
|
- ['origInclude', '_include'],
|
|
|
- ['origMatch', '_match'],
|
|
|
- ['origExclude', '_exclude'],
|
|
|
- ['origExcludeMatch', '_excludeMatch'],
|
|
|
- ].forEach(([key, oldKey]) => {
|
|
|
- if (typeof custom[key] === 'undefined') {
|
|
|
- custom[key] = custom[oldKey] !== false;
|
|
|
- delete custom[oldKey];
|
|
|
- }
|
|
|
+ getAllScripts(tx, items => {
|
|
|
+ const uriMap = {};
|
|
|
+ items.forEach(({ script, code }) => {
|
|
|
+ updates[`scr:${script.props.id}`] = script;
|
|
|
+ updates[`code:${script.props.id}`] = code;
|
|
|
+ uriMap[script.props.uri] = script.props.id;
|
|
|
+ });
|
|
|
+ getAllValues(tx, data => {
|
|
|
+ data.forEach(({ id, values }) => {
|
|
|
+ updates[`val:${id}`] = values;
|
|
|
+ });
|
|
|
+ onCallback();
|
|
|
+ }, uriMap);
|
|
|
+ });
|
|
|
+ getAllCache(tx, cache => {
|
|
|
+ cache.forEach(({ uri, data }) => {
|
|
|
+ updates[`cac:${uri}`] = data;
|
|
|
+ });
|
|
|
+ onCallback();
|
|
|
+ });
|
|
|
+ getAllRequire(tx, data => {
|
|
|
+ data.forEach(({ uri, code }) => {
|
|
|
+ updates[`req:${uri}`] = code;
|
|
|
+ });
|
|
|
+ onCallback();
|
|
|
});
|
|
|
}
|
|
|
- return script;
|
|
|
-}
|
|
|
-
|
|
|
-export function getScript(id, cTx) {
|
|
|
- const tx = cTx || db.transaction('scripts');
|
|
|
- const os = tx.objectStore('scripts');
|
|
|
- return new Promise(resolve => {
|
|
|
- os.get(id).onsuccess = e => {
|
|
|
- const { result } = e.target;
|
|
|
- result.id = id;
|
|
|
- resolve(result);
|
|
|
+ function getAllScripts(tx, callback) {
|
|
|
+ const os = tx.objectStore('scripts');
|
|
|
+ const list = [];
|
|
|
+ const req = os.openCursor();
|
|
|
+ req.onsuccess = e => {
|
|
|
+ const cursor = e.target.result;
|
|
|
+ if (cursor) {
|
|
|
+ const { value } = cursor;
|
|
|
+ list.push(transformScript(value));
|
|
|
+ cursor.continue();
|
|
|
+ } else {
|
|
|
+ callback(list);
|
|
|
+ }
|
|
|
};
|
|
|
- })
|
|
|
- .then(transformScript);
|
|
|
-}
|
|
|
-
|
|
|
-export function queryScript(id, meta, cTx) {
|
|
|
- if (id) return getScript(id, cTx);
|
|
|
- return new Promise(resolve => {
|
|
|
- const uri = getNameURI({ meta });
|
|
|
- const tx = cTx || db.transaction('scripts');
|
|
|
- tx.objectStore('scripts').index('uri').get(uri).onsuccess = e => {
|
|
|
- resolve(e.target.result);
|
|
|
+ req.onerror = reject;
|
|
|
+ }
|
|
|
+ function getAllCache(tx, callback) {
|
|
|
+ const os = tx.objectStore('cache');
|
|
|
+ const list = [];
|
|
|
+ const req = os.openCursor();
|
|
|
+ req.onsuccess = e => {
|
|
|
+ const cursor = e.target.result;
|
|
|
+ if (cursor) {
|
|
|
+ const { value: { uri, data } } = cursor;
|
|
|
+ list.push({ uri, data });
|
|
|
+ cursor.continue();
|
|
|
+ } else {
|
|
|
+ callback(list);
|
|
|
+ }
|
|
|
};
|
|
|
- })
|
|
|
- .then(transformScript);
|
|
|
-}
|
|
|
-
|
|
|
-export function getScriptData(id) {
|
|
|
- return getScript(id).then(script => {
|
|
|
- if (!script) return Promise.reject();
|
|
|
- const data = getScriptInfo(script);
|
|
|
- data.code = script.code;
|
|
|
- return data;
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-export function getScriptInfos(ids) {
|
|
|
- const tx = db.transaction('scripts');
|
|
|
- return Promise.all(ids.map(id => getScript(id, tx)))
|
|
|
- .then(scripts => scripts.filter(Boolean).map(getScriptInfo));
|
|
|
-}
|
|
|
-
|
|
|
-export function getValues(uris, cTx) {
|
|
|
- const tx = cTx || db.transaction('values');
|
|
|
- const os = tx.objectStore('values');
|
|
|
- return Promise.all(uris.map(uri => new Promise(resolve => {
|
|
|
- os.get(uri).onsuccess = e => {
|
|
|
- resolve(e.target.result);
|
|
|
+ req.onerror = reject;
|
|
|
+ }
|
|
|
+ function getAllRequire(tx, callback) {
|
|
|
+ const os = tx.objectStore('require');
|
|
|
+ const list = [];
|
|
|
+ const req = os.openCursor();
|
|
|
+ req.onsuccess = e => {
|
|
|
+ const cursor = e.target.result;
|
|
|
+ if (cursor) {
|
|
|
+ const { value: { uri, code } } = cursor;
|
|
|
+ list.push({ uri, code });
|
|
|
+ cursor.continue();
|
|
|
+ } else {
|
|
|
+ callback(list);
|
|
|
+ }
|
|
|
};
|
|
|
- })))
|
|
|
- .then(data => data.reduce((result, value, i) => {
|
|
|
- if (value) result[uris[i]] = value.values;
|
|
|
- return result;
|
|
|
- }, {}));
|
|
|
-}
|
|
|
-
|
|
|
-export function getScriptsByURL(url) {
|
|
|
- const tx = db.transaction(['scripts', 'require', 'values', 'cache']);
|
|
|
- return loadScripts()
|
|
|
- .then(data => Promise.all([
|
|
|
- loadRequires(data.require),
|
|
|
- getValues(data.uris, tx),
|
|
|
- getCacheB64(data.cache, tx),
|
|
|
- ]).then(res => ({
|
|
|
- scripts: data.scripts,
|
|
|
- require: res[0],
|
|
|
- values: res[1],
|
|
|
- cache: res[2],
|
|
|
- })));
|
|
|
-
|
|
|
- function loadScripts() {
|
|
|
- const data = {
|
|
|
- uris: [],
|
|
|
+ req.onerror = reject;
|
|
|
+ }
|
|
|
+ function getAllValues(tx, callback, uriMap) {
|
|
|
+ const os = tx.objectStore('values');
|
|
|
+ const list = [];
|
|
|
+ const req = os.openCursor();
|
|
|
+ req.onsuccess = e => {
|
|
|
+ const cursor = e.target.result;
|
|
|
+ if (cursor) {
|
|
|
+ const { value: { uri, values } } = cursor;
|
|
|
+ const id = uriMap[uri];
|
|
|
+ if (id) list.push({ id, values });
|
|
|
+ cursor.continue();
|
|
|
+ } else {
|
|
|
+ callback(list);
|
|
|
+ }
|
|
|
};
|
|
|
- const require = {};
|
|
|
- const cache = {};
|
|
|
- return (testBlacklist(url) ? Promise.resolve([]) : (
|
|
|
- getScriptsByIndex('position', null, tx, script => {
|
|
|
- if (!testScript(url, script)) return;
|
|
|
- data.uris.push(script.uri);
|
|
|
- script.meta.require.forEach(key => { require[key] = 1; });
|
|
|
- Object.keys(script.meta.resources).forEach(key => {
|
|
|
- cache[script.meta.resources[key]] = 1;
|
|
|
- });
|
|
|
- return script;
|
|
|
- })
|
|
|
- ))
|
|
|
- .then(scripts => {
|
|
|
- data.scripts = scripts.filter(Boolean);
|
|
|
- data.require = Object.keys(require);
|
|
|
- data.cache = Object.keys(cache);
|
|
|
- return data;
|
|
|
- });
|
|
|
+ req.onerror = reject;
|
|
|
}
|
|
|
- function loadRequires(uris) {
|
|
|
- const os = tx.objectStore('require');
|
|
|
- return Promise.all(uris.map(uri => new Promise(resolve => {
|
|
|
- os.get(uri).onsuccess = e => {
|
|
|
- resolve(e.target.result);
|
|
|
- };
|
|
|
- })))
|
|
|
- .then(data => data.reduce((result, value, i) => {
|
|
|
- if (value) result[uris[i]] = value.code;
|
|
|
- return result;
|
|
|
- }, {}));
|
|
|
+ function transformScript(script) {
|
|
|
+ const item = {
|
|
|
+ script: {
|
|
|
+ meta: parseMeta(script.code),
|
|
|
+ custom: script.custom,
|
|
|
+ props: {
|
|
|
+ id: script.id,
|
|
|
+ uri: script.uri,
|
|
|
+ position: script.position,
|
|
|
+ },
|
|
|
+ config: {
|
|
|
+ enabled: script.enabled,
|
|
|
+ shouldUpdate: script.update,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ code: script.code,
|
|
|
+ };
|
|
|
+ return item;
|
|
|
}
|
|
|
+})
|
|
|
+// Ignore error
|
|
|
+.catch(() => {});
|
|
|
+
|
|
|
+function cacheOrFetch(handle) {
|
|
|
+ const requests = {};
|
|
|
+ return function cachedHandle(url, ...args) {
|
|
|
+ let promise = requests[url];
|
|
|
+ if (!promise) {
|
|
|
+ promise = handle.call(this, url, ...args)
|
|
|
+ .catch(() => {
|
|
|
+ console.error(`Error fetching: ${url}`);
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ delete requests[url];
|
|
|
+ });
|
|
|
+ requests[url] = promise;
|
|
|
+ }
|
|
|
+ return promise;
|
|
|
+ };
|
|
|
+}
|
|
|
+function ensureListArgs(handle) {
|
|
|
+ return function handleList(data) {
|
|
|
+ let items = Array.isArray(data) ? data : [data];
|
|
|
+ items = items.filter(Boolean);
|
|
|
+ if (!items.length) return Promise.resolve();
|
|
|
+ return handle.call(this, items);
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
-export function getData() {
|
|
|
- const tx = db.transaction(['scripts', 'cache']);
|
|
|
- return loadScripts()
|
|
|
- .then(data => loadCache(data.cache).then(cache => ({
|
|
|
- cache,
|
|
|
- scripts: data.scripts,
|
|
|
- })));
|
|
|
-
|
|
|
- function loadScripts() {
|
|
|
- const data = {};
|
|
|
- const cache = {};
|
|
|
- return getScriptsByIndex('position', null, tx, script => {
|
|
|
- const { icon } = script.meta;
|
|
|
- if (isRemote(icon)) cache[icon] = 1;
|
|
|
- return getScriptInfo(script);
|
|
|
- })
|
|
|
- .then(scripts => {
|
|
|
- data.scripts = scripts;
|
|
|
- data.cache = Object.keys(cache);
|
|
|
- return data;
|
|
|
- });
|
|
|
- }
|
|
|
- function loadCache(uris) {
|
|
|
- return getCacheB64(uris, tx)
|
|
|
- .then(cache => {
|
|
|
- Object.keys(cache).forEach(key => {
|
|
|
- cache[key] = `data:image/png;base64,${cache[key]}`;
|
|
|
+const store = {};
|
|
|
+const storage = {
|
|
|
+ base: {
|
|
|
+ prefix: '',
|
|
|
+ getKey(id) {
|
|
|
+ return `${this.prefix}${id}`;
|
|
|
+ },
|
|
|
+ getOne(id) {
|
|
|
+ const key = this.getKey(id);
|
|
|
+ return browser.storage.local.get(key).then(data => data[key]);
|
|
|
+ },
|
|
|
+ getMulti(ids) {
|
|
|
+ return browser.storage.local.get(ids.map(id => this.getKey(id)))
|
|
|
+ .then(data => {
|
|
|
+ const result = {};
|
|
|
+ ids.forEach(id => { result[id] = data[this.getKey(id)]; });
|
|
|
+ return result;
|
|
|
});
|
|
|
- return cache;
|
|
|
+ },
|
|
|
+ dump(id, value) {
|
|
|
+ if (!id) return Promise.resolve();
|
|
|
+ return browser.storage.local.set({
|
|
|
+ [this.getKey(id)]: value,
|
|
|
+ });
|
|
|
+ },
|
|
|
+ remove(id) {
|
|
|
+ if (!id) return Promise.resolve();
|
|
|
+ return browser.storage.local.remove(this.getKey(id));
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+storage.script = Object.assign({}, storage.base, {
|
|
|
+ prefix: 'scr:',
|
|
|
+ dump: ensureListArgs(function dump(items) {
|
|
|
+ const updates = {};
|
|
|
+ items.forEach(item => {
|
|
|
+ updates[this.getKey(item.props.id)] = item;
|
|
|
+ store.scriptMap[item.props.id] = item;
|
|
|
});
|
|
|
- }
|
|
|
-}
|
|
|
+ return browser.storage.local.set(updates)
|
|
|
+ .then(() => items);
|
|
|
+ }),
|
|
|
+});
|
|
|
+storage.code = Object.assign({}, storage.base, {
|
|
|
+ prefix: 'code:',
|
|
|
+});
|
|
|
+storage.value = Object.assign({}, storage.base, {
|
|
|
+ prefix: 'val:',
|
|
|
+});
|
|
|
+storage.require = Object.assign({}, storage.base, {
|
|
|
+ prefix: 'req:',
|
|
|
+ fetch: cacheOrFetch(function fetch(uri) {
|
|
|
+ return request(uri).then(({ data }) => this.dump(uri, data));
|
|
|
+ }),
|
|
|
+});
|
|
|
+storage.cache = Object.assign({}, storage.base, {
|
|
|
+ prefix: 'cac:',
|
|
|
+ fetch: cacheOrFetch(function fetch(uri, check) {
|
|
|
+ return request(uri, { responseType: 'arraybuffer' })
|
|
|
+ .then(({ data: buffer }) => {
|
|
|
+ const data = {
|
|
|
+ buffer,
|
|
|
+ blob: options => new Blob([buffer], options),
|
|
|
+ string: () => buffer2string(buffer),
|
|
|
+ base64: () => window.btoa(data.string()),
|
|
|
+ };
|
|
|
+ return (check ? Promise.resolve(check(data)) : Promise.resolve())
|
|
|
+ .then(() => this.dump(uri, data.base64()));
|
|
|
+ });
|
|
|
+ }),
|
|
|
+});
|
|
|
|
|
|
-export function removeScript(id) {
|
|
|
- const tx = db.transaction('scripts', 'readwrite');
|
|
|
- return new Promise(resolve => {
|
|
|
- const os = tx.objectStore('scripts');
|
|
|
- os.delete(id).onsuccess = () => { resolve(); };
|
|
|
+register(initialize());
|
|
|
+
|
|
|
+function initialize() {
|
|
|
+ return browser.storage.local.get('version')
|
|
|
+ .then(({ version: lastVersion }) => {
|
|
|
+ const { version } = browser.runtime.getManifest();
|
|
|
+ return (lastVersion ? Promise.resolve() : patch())
|
|
|
+ .then(() => {
|
|
|
+ if (version !== lastVersion) return browser.storage.local.set({ version });
|
|
|
+ });
|
|
|
})
|
|
|
- .then(() => {
|
|
|
- browser.runtime.sendMessage({
|
|
|
- cmd: 'RemoveScript',
|
|
|
- data: id,
|
|
|
+ .then(() => browser.storage.local.get())
|
|
|
+ .then(data => {
|
|
|
+ const scripts = [];
|
|
|
+ const storeInfo = {
|
|
|
+ id: 0,
|
|
|
+ position: 0,
|
|
|
+ };
|
|
|
+ Object.keys(data).forEach(key => {
|
|
|
+ const value = data[key];
|
|
|
+ if (key.startsWith('scr:')) {
|
|
|
+ // {
|
|
|
+ // meta,
|
|
|
+ // custom,
|
|
|
+ // props: { id, position, uri },
|
|
|
+ // config: { enabled, shouldUpdate },
|
|
|
+ // }
|
|
|
+ scripts.push(value);
|
|
|
+ storeInfo.id = Math.max(storeInfo.id, getInt(object.get(value, 'props.id')));
|
|
|
+ storeInfo.position = Math.max(storeInfo.position, getInt(object.get(value, 'props.position')));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ scripts.sort((a, b) => {
|
|
|
+ const [pos1, pos2] = [a, b].map(item => getInt(object.get(item, 'props.position')));
|
|
|
+ return Math.sign(pos1 - pos2);
|
|
|
});
|
|
|
+ Object.assign(store, {
|
|
|
+ scripts,
|
|
|
+ storeInfo,
|
|
|
+ scriptMap: scripts.reduce((map, item) => {
|
|
|
+ map[item.props.id] = item;
|
|
|
+ return map;
|
|
|
+ }, {}),
|
|
|
+ });
|
|
|
+ if (process.env.DEBUG) {
|
|
|
+ console.log('store:', store); // eslint-disable-line no-console
|
|
|
+ }
|
|
|
+ return normalizePosition();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-export function moveScript(id, offset) {
|
|
|
- const tx = db.transaction('scripts', 'readwrite');
|
|
|
- const os = tx.objectStore('scripts');
|
|
|
- return getScript(id, tx)
|
|
|
- .then(script => {
|
|
|
- let pos = script.position;
|
|
|
- let range;
|
|
|
- let order;
|
|
|
- let number = offset;
|
|
|
- if (offset < 0) {
|
|
|
- range = IDBKeyRange.upperBound(pos, true);
|
|
|
- order = 'prev';
|
|
|
- number = -number;
|
|
|
- } else {
|
|
|
- range = IDBKeyRange.lowerBound(pos, true);
|
|
|
- order = 'next';
|
|
|
+function getInt(val) {
|
|
|
+ return +val || 0;
|
|
|
+}
|
|
|
+
|
|
|
+export function normalizePosition() {
|
|
|
+ const updates = [];
|
|
|
+ store.scripts.forEach((item, index) => {
|
|
|
+ const position = index + 1;
|
|
|
+ if (object.get(item, 'props.position') !== position) {
|
|
|
+ object.set(item, 'props.position', position);
|
|
|
+ updates.push(item);
|
|
|
}
|
|
|
- return new Promise(resolve => {
|
|
|
- os.index('position').openCursor(range, order).onsuccess = e => {
|
|
|
- const { result } = e.target;
|
|
|
- if (result) {
|
|
|
- number -= 1;
|
|
|
- const { value } = result;
|
|
|
- value.position = pos;
|
|
|
- pos = result.key;
|
|
|
- result.update(value);
|
|
|
- if (number) result.continue();
|
|
|
- else {
|
|
|
- script.position = pos;
|
|
|
- os.put(script).onsuccess = () => { resolve(); };
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
- });
|
|
|
});
|
|
|
+ store.storeInfo.position = store.scripts.length;
|
|
|
+ return storage.script.dump(updates);
|
|
|
}
|
|
|
|
|
|
-function getCacheB64(urls, cTx) {
|
|
|
- const tx = cTx || db.transaction('cache');
|
|
|
- const os = tx.objectStore('cache');
|
|
|
- return Promise.all(urls.map(url => new Promise(resolve => {
|
|
|
- os.get(url).onsuccess = e => {
|
|
|
- resolve(e.target.result);
|
|
|
- };
|
|
|
- })))
|
|
|
- .then(data => data.reduce((map, value, i) => {
|
|
|
- if (value) map[urls[i]] = value.data;
|
|
|
- return map;
|
|
|
- }, {}));
|
|
|
+export function getScript(where) {
|
|
|
+ let script;
|
|
|
+ if (where.id) {
|
|
|
+ script = store.scriptMap[where.id];
|
|
|
+ } else {
|
|
|
+ const uri = getNameURI({ meta: where.meta, id: '@@should-have-name' });
|
|
|
+ const predicate = item => uri === object.get(item, 'props.uri');
|
|
|
+ script = store.scripts.find(predicate);
|
|
|
+ }
|
|
|
+ return Promise.resolve(script);
|
|
|
}
|
|
|
|
|
|
-function saveCache(uri, data, cTx) {
|
|
|
- const tx = cTx || db.transaction('cache', 'readwrite');
|
|
|
- const os = tx.objectStore('cache');
|
|
|
- return new Promise(resolve => {
|
|
|
- os.put({ uri, data }).onsuccess = () => { resolve(); };
|
|
|
- });
|
|
|
+export function getScripts() {
|
|
|
+ return Promise.resolve(store.scripts);
|
|
|
+}
|
|
|
+
|
|
|
+export function getScriptByIds(ids) {
|
|
|
+ return Promise.all(ids.map(id => getScript({ id })))
|
|
|
+ .then(scripts => scripts.filter(Boolean));
|
|
|
+}
|
|
|
+
|
|
|
+export function getScriptCode(id) {
|
|
|
+ return storage.code.getOne(id);
|
|
|
}
|
|
|
|
|
|
-function saveRequire(uri, code, cTx) {
|
|
|
- const tx = cTx || db.transaction('require', 'readwrite');
|
|
|
- const os = tx.objectStore('require');
|
|
|
- return new Promise(resolve => {
|
|
|
- os.put({ uri, code }).onsuccess = () => { resolve(); };
|
|
|
+export function setValues(id, values) {
|
|
|
+ return storage.value.dump(id, values);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @desc Get scripts to be injected to page with specific URL.
|
|
|
+ */
|
|
|
+export function getScriptsByURL(url) {
|
|
|
+ const scripts = testBlacklist(url) ? [] : store.scripts.filter(script => testScript(url, script));
|
|
|
+ const reqKeys = {};
|
|
|
+ const cacheKeys = {};
|
|
|
+ scripts.forEach(script => {
|
|
|
+ if (object.get(script, 'config.enabled')) {
|
|
|
+ script.meta.require.forEach(key => {
|
|
|
+ reqKeys[key] = 1;
|
|
|
+ });
|
|
|
+ Object.keys(script.meta.resources).forEach(key => {
|
|
|
+ cacheKeys[script.meta.resources[key]] = 1;
|
|
|
+ });
|
|
|
+ }
|
|
|
});
|
|
|
+ const enabledScriptIds = scripts
|
|
|
+ .filter(script => script.config.enabled)
|
|
|
+ .map(script => script.props.id);
|
|
|
+ return Promise.all([
|
|
|
+ storage.require.getMulti(Object.keys(reqKeys)),
|
|
|
+ storage.cache.getMulti(Object.keys(cacheKeys)),
|
|
|
+ storage.value.getMulti(enabledScriptIds),
|
|
|
+ storage.code.getMulti(enabledScriptIds),
|
|
|
+ ])
|
|
|
+ .then(([require, cache, values, code]) => ({
|
|
|
+ scripts,
|
|
|
+ require,
|
|
|
+ cache,
|
|
|
+ values,
|
|
|
+ code,
|
|
|
+ }));
|
|
|
}
|
|
|
|
|
|
-export function saveScript(script, cTx) {
|
|
|
- script.enabled = script.enabled ? 1 : 0;
|
|
|
- script.update = script.update ? 1 : 0;
|
|
|
- if (!script.position) script.position = position.get();
|
|
|
- position.update(script.position);
|
|
|
- const tx = cTx || db.transaction('scripts', 'readwrite');
|
|
|
- const os = tx.objectStore('scripts');
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const res = os.put(script);
|
|
|
- res.onsuccess = e => {
|
|
|
- script.id = e.target.result;
|
|
|
- resolve(script);
|
|
|
- };
|
|
|
- res.onerror = () => {
|
|
|
- reject(i18n('msgNamespaceConflict'));
|
|
|
- };
|
|
|
+/**
|
|
|
+ * @desc Get data for dashboard.
|
|
|
+ */
|
|
|
+export function getData() {
|
|
|
+ const scripts = store.scripts;
|
|
|
+ const cacheKeys = {};
|
|
|
+ scripts.forEach(script => {
|
|
|
+ const icon = object.get(script, 'meta.icon');
|
|
|
+ if (isRemote(icon)) cacheKeys[icon] = 1;
|
|
|
});
|
|
|
+ return storage.cache.getMulti(Object.keys(cacheKeys))
|
|
|
+ .then(cache => {
|
|
|
+ Object.keys(cache).forEach(key => {
|
|
|
+ cache[key] = `data:image/png;base64,${cache[key]}`;
|
|
|
+ });
|
|
|
+ return cache;
|
|
|
+ })
|
|
|
+ .then(cache => ({ scripts, cache }));
|
|
|
}
|
|
|
|
|
|
-const cacheRequests = {};
|
|
|
-function fetchCache(url, check) {
|
|
|
- let promise = cacheRequests[url];
|
|
|
- if (!promise) {
|
|
|
- // DataURL cannot be loaded with `responseType=blob`
|
|
|
- // ref: https://bugs.chromium.org/p/chromium/issues/detail?id=412752
|
|
|
- promise = request(url, { responseType: 'arraybuffer' })
|
|
|
- .then(({ data: buffer }) => {
|
|
|
- const data = {
|
|
|
- buffer,
|
|
|
- blob(options) {
|
|
|
- return new Blob([buffer], options);
|
|
|
- },
|
|
|
- string() {
|
|
|
- return buffer2string(buffer);
|
|
|
- },
|
|
|
- base64() {
|
|
|
- return window.btoa(data.string());
|
|
|
- },
|
|
|
- };
|
|
|
- if (check) return Promise.resolve(check(data)).then(() => data);
|
|
|
- return data;
|
|
|
- })
|
|
|
- .then(({ base64 }) => saveCache(url, base64()))
|
|
|
- .then(() => { delete cacheRequests[url]; });
|
|
|
- cacheRequests[url] = promise;
|
|
|
+export function removeScript(id) {
|
|
|
+ const i = store.scripts.findIndex(item => id === object.get(item, 'props.id'));
|
|
|
+ if (i >= 0) {
|
|
|
+ store.scripts.splice(i, 1);
|
|
|
+ storage.script.remove(id);
|
|
|
}
|
|
|
- return promise;
|
|
|
+ return browser.runtime.sendMessage({
|
|
|
+ cmd: 'RemoveScript',
|
|
|
+ data: id,
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
-const requireRequests = {};
|
|
|
-function fetchRequire(url) {
|
|
|
- let promise = requireRequests[url];
|
|
|
- if (!promise) {
|
|
|
- promise = request(url)
|
|
|
- .then(({ data }) => saveRequire(url, data))
|
|
|
- .catch(() => { console.error(`Error fetching required script: ${url}`); })
|
|
|
- .then(() => { delete requireRequests[url]; });
|
|
|
- requireRequests[url] = promise;
|
|
|
+export function moveScript(id, offset) {
|
|
|
+ const index = store.scripts.findIndex(item => id === object.get(item, 'props.id'));
|
|
|
+ const step = offset > 0 ? 1 : -1;
|
|
|
+ const indexStart = index;
|
|
|
+ const indexEnd = index + offset;
|
|
|
+ const offsetI = Math.min(indexStart, indexEnd);
|
|
|
+ const offsetJ = Math.max(indexStart, indexEnd);
|
|
|
+ const updated = store.scripts.slice(offsetI, offsetJ + 1);
|
|
|
+ if (step > 0) {
|
|
|
+ updated.push(updated.shift());
|
|
|
+ } else {
|
|
|
+ updated.unshift(updated.pop());
|
|
|
}
|
|
|
- return promise;
|
|
|
+ store.scripts = [
|
|
|
+ ...store.scripts.slice(0, offsetI),
|
|
|
+ ...updated,
|
|
|
+ ...store.scripts.slice(offsetJ + 1),
|
|
|
+ ];
|
|
|
+ return normalizePosition();
|
|
|
}
|
|
|
|
|
|
-export function setValue(uri, values) {
|
|
|
- const os = db.transaction('values', 'readwrite').objectStore('values');
|
|
|
- return new Promise(resolve => {
|
|
|
- os.put({ uri, values }).onsuccess = () => { resolve(); };
|
|
|
- });
|
|
|
+function saveScript(script, code) {
|
|
|
+ const config = script.config || {};
|
|
|
+ config.enabled = getInt(config.enabled);
|
|
|
+ config.shouldUpdate = getInt(config.shouldUpdate);
|
|
|
+ const props = script.props || {};
|
|
|
+ let oldScript;
|
|
|
+ if (!props.id) {
|
|
|
+ store.storeInfo.id += 1;
|
|
|
+ props.id = store.storeInfo.id;
|
|
|
+ } else {
|
|
|
+ oldScript = store.scriptMap[props.id];
|
|
|
+ }
|
|
|
+ props.uri = getNameURI(script);
|
|
|
+ // Do not allow script with same name and namespace
|
|
|
+ if (store.scripts.some(item => {
|
|
|
+ const itemProps = item.props || {};
|
|
|
+ return props.id !== itemProps.id && props.uri === itemProps.uri;
|
|
|
+ })) {
|
|
|
+ throw i18n('msgNamespaceConflict');
|
|
|
+ }
|
|
|
+ if (oldScript) {
|
|
|
+ script.config = Object.assign({}, oldScript.config, config);
|
|
|
+ script.props = Object.assign({}, oldScript.props, props);
|
|
|
+ const index = store.scripts.indexOf(oldScript);
|
|
|
+ store.scripts[index] = script;
|
|
|
+ } else {
|
|
|
+ store.storeInfo.position += 1;
|
|
|
+ props.position = store.storeInfo.position;
|
|
|
+ script.config = config;
|
|
|
+ script.props = props;
|
|
|
+ store.scripts.push(script);
|
|
|
+ }
|
|
|
+ return Promise.all([
|
|
|
+ storage.script.dump(script),
|
|
|
+ storage.code.dump(props.id, code),
|
|
|
+ ]);
|
|
|
}
|
|
|
|
|
|
-export function updateScriptInfo(id, data, custom) {
|
|
|
- const tx = db.transaction('scripts', 'readwrite');
|
|
|
- const os = tx.objectStore('scripts');
|
|
|
- return getScript(id, tx)
|
|
|
- .then(script => new Promise((resolve, reject) => {
|
|
|
- if (!script) return reject();
|
|
|
- Object.keys(data).forEach(key => {
|
|
|
- if (key in script) script[key] = data[key];
|
|
|
- });
|
|
|
- Object.assign(script.custom, custom);
|
|
|
- os.put(script).onsuccess = () => {
|
|
|
- resolve(getScriptInfo(script));
|
|
|
- };
|
|
|
- }));
|
|
|
+export function updateScriptInfo(id, data) {
|
|
|
+ const script = store.scriptMap[id];
|
|
|
+ if (!script) return Promise.reject();
|
|
|
+ script.config = Object.assign({}, script.config, data.config);
|
|
|
+ script.custom = Object.assign({}, script.custom, data.custom);
|
|
|
+ return storage.script.dump(script);
|
|
|
}
|
|
|
|
|
|
export function getExportData(ids, withValues) {
|
|
|
- const tx = db.transaction(['scripts', 'values']);
|
|
|
- return loadScripts()
|
|
|
+ return Promise.all(ids.map(id => getScript({ id })))
|
|
|
.then(scripts => {
|
|
|
- const res = { scripts };
|
|
|
+ const data = { scripts };
|
|
|
if (withValues) {
|
|
|
- return getValues(scripts.map(script => script.uri), tx)
|
|
|
+ return storage.value.getMulti(ids)
|
|
|
.then(values => {
|
|
|
- res.values = values;
|
|
|
- return res;
|
|
|
+ data.values = values;
|
|
|
+ return data;
|
|
|
});
|
|
|
}
|
|
|
- return res;
|
|
|
- });
|
|
|
- function loadScripts() {
|
|
|
- const os = tx.objectStore('scripts');
|
|
|
- return Promise.all(ids.map(id => new Promise(resolve => {
|
|
|
- os.get(id).onsuccess = e => {
|
|
|
- resolve(e.target.result);
|
|
|
- };
|
|
|
- })))
|
|
|
- .then(data => data.filter(Boolean));
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-export function vacuum() {
|
|
|
- const tx = db.transaction(['scripts', 'require', 'cache', 'values'], 'readwrite');
|
|
|
- checkPosition();
|
|
|
- return loadScripts()
|
|
|
- .then(data => Promise.all([
|
|
|
- vacuumCache('require', data.require),
|
|
|
- vacuumCache('cache', data.cache),
|
|
|
- vacuumCache('values', data.values),
|
|
|
- ]).then(() => ({
|
|
|
- require: data.require,
|
|
|
- cache: data.cache,
|
|
|
- })))
|
|
|
- .then(data => Promise.all([
|
|
|
- Object.keys(data.require).map(k => data.require[k] === 1 && fetchRequire(k)),
|
|
|
- Object.keys(data.cache).map(k => data.cache[k] === 1 && fetchCache(k)),
|
|
|
- ]));
|
|
|
-
|
|
|
- function loadScripts() {
|
|
|
- const data = {
|
|
|
- require: {},
|
|
|
- cache: {},
|
|
|
- values: {},
|
|
|
- };
|
|
|
- return getScriptsByIndex('position', null, tx, script => {
|
|
|
- const base = script.custom.lastInstallURL;
|
|
|
- script.meta.require.forEach(url => {
|
|
|
- const fullUrl = getFullUrl(url, base);
|
|
|
- data.require[fullUrl] = 1;
|
|
|
- });
|
|
|
- Object.keys(script.meta.resources).forEach(key => {
|
|
|
- const url = script.meta.resources[key];
|
|
|
- const fullUrl = getFullUrl(url, base);
|
|
|
- data.cache[fullUrl] = 1;
|
|
|
- });
|
|
|
- if (isRemote(script.meta.icon)) data.cache[script.meta.icon] = 1;
|
|
|
- data.values[script.uri] = 1;
|
|
|
- })
|
|
|
- .then(() => data);
|
|
|
- }
|
|
|
- function vacuumCache(dbName, dict) {
|
|
|
- const os = tx.objectStore(dbName);
|
|
|
- const deleteCache = uri => new Promise(resolve => {
|
|
|
- if (!dict[uri]) {
|
|
|
- os.delete(uri).onsuccess = () => { resolve(); };
|
|
|
- } else {
|
|
|
- dict[uri] += 1;
|
|
|
- resolve();
|
|
|
- }
|
|
|
- });
|
|
|
- return new Promise(resolve => {
|
|
|
- os.openCursor().onsuccess = e => {
|
|
|
- const { result } = e.target;
|
|
|
- if (result) {
|
|
|
- const { value } = result;
|
|
|
- deleteCache(value.uri).then(() => result.continue());
|
|
|
- } else resolve();
|
|
|
- };
|
|
|
- });
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-export function getScriptsByIndex(index, options, cTx, mapEach) {
|
|
|
- const tx = cTx || db.transaction('scripts');
|
|
|
- return new Promise(resolve => {
|
|
|
- const os = tx.objectStore('scripts');
|
|
|
- const list = [];
|
|
|
- os.index(index).openCursor(options).onsuccess = e => {
|
|
|
- const { result } = e.target;
|
|
|
- if (result) {
|
|
|
- let { value } = result;
|
|
|
- value = transformScript(value);
|
|
|
- if (mapEach) value = mapEach(value);
|
|
|
- list.push(value);
|
|
|
- result.continue();
|
|
|
- } else resolve(list);
|
|
|
- };
|
|
|
+ return data;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-function updateProps(target, source) {
|
|
|
- if (source) {
|
|
|
- Object.keys(source).forEach(key => {
|
|
|
- if (key in target) target[key] = source[key];
|
|
|
- });
|
|
|
- }
|
|
|
- return target;
|
|
|
-}
|
|
|
-
|
|
|
export function parseScript(data) {
|
|
|
- const meta = parseMeta(data.code);
|
|
|
- if (!meta.name) return Promise.reject(i18n('msgInvalidScript'));
|
|
|
- const res = {
|
|
|
+ const { id, code, message, isNew, config, custom } = data;
|
|
|
+ const meta = parseMeta(code);
|
|
|
+ if (!meta.name) throw i18n('msgInvalidScript');
|
|
|
+ const result = {
|
|
|
cmd: 'UpdateScript',
|
|
|
data: {
|
|
|
- message: data.message == null ? i18n('msgUpdated') : data.message || '',
|
|
|
+ message: message == null ? i18n('msgUpdated') : message || '',
|
|
|
},
|
|
|
};
|
|
|
- const tx = db.transaction(['scripts', 'require'], 'readwrite');
|
|
|
- function fetchResources(base) {
|
|
|
- // @require
|
|
|
- meta.require.forEach(url => {
|
|
|
- const fullUrl = getFullUrl(url, base);
|
|
|
- const cache = data.require && data.require[fullUrl];
|
|
|
- if (cache) saveRequire(fullUrl, cache, tx);
|
|
|
- else fetchRequire(fullUrl);
|
|
|
- });
|
|
|
- // @resource
|
|
|
- Object.keys(meta.resources).forEach(k => {
|
|
|
- const url = meta.resources[k];
|
|
|
- const fullUrl = getFullUrl(url, base);
|
|
|
- const cache = data.resources && data.resources[fullUrl];
|
|
|
- if (cache) saveCache(fullUrl, cache);
|
|
|
- else fetchCache(fullUrl);
|
|
|
- });
|
|
|
- // @icon
|
|
|
- if (isRemote(meta.icon)) {
|
|
|
- fetchCache(
|
|
|
- getFullUrl(meta.icon, base),
|
|
|
- ({ blob: getBlob }) => new Promise((resolve, reject) => {
|
|
|
- const blob = getBlob({ type: 'image/png' });
|
|
|
- const url = URL.createObjectURL(blob);
|
|
|
- const image = new Image();
|
|
|
- const free = () => URL.revokeObjectURL(url);
|
|
|
- image.onload = () => {
|
|
|
- free();
|
|
|
- resolve();
|
|
|
- };
|
|
|
- image.onerror = () => {
|
|
|
- free();
|
|
|
- reject();
|
|
|
- };
|
|
|
- image.src = url;
|
|
|
- }),
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
- return queryScript(data.id, meta, tx)
|
|
|
- .then(result => {
|
|
|
+ return getScript({ id, meta })
|
|
|
+ .then(oldScript => {
|
|
|
let script;
|
|
|
- if (result) {
|
|
|
- if (data.isNew) throw i18n('msgNamespaceConflict');
|
|
|
- script = result;
|
|
|
+ if (oldScript) {
|
|
|
+ if (isNew) throw i18n('msgNamespaceConflict');
|
|
|
+ script = Object.assign({}, oldScript);
|
|
|
} else {
|
|
|
- script = newScript();
|
|
|
- script.position = position.get();
|
|
|
- res.cmd = 'AddScript';
|
|
|
- res.data.message = i18n('msgInstalled');
|
|
|
+ ({ script } = newScript());
|
|
|
+ result.cmd = 'AddScript';
|
|
|
+ result.data.message = i18n('msgInstalled');
|
|
|
}
|
|
|
- updateProps(script, data.more);
|
|
|
- Object.assign(script.custom, data.custom);
|
|
|
+ script.config = Object.assign({}, script.config, config);
|
|
|
+ script.custom = Object.assign({}, script.custom, custom);
|
|
|
script.meta = meta;
|
|
|
- script.code = data.code;
|
|
|
- script.uri = getNameURI(script);
|
|
|
- // use referer page as default homepage
|
|
|
if (!meta.homepageURL && !script.custom.homepageURL && isRemote(data.from)) {
|
|
|
script.custom.homepageURL = data.from;
|
|
|
}
|
|
|
if (isRemote(data.url)) script.custom.lastInstallURL = data.url;
|
|
|
- fetchResources(script.custom.lastInstallURL);
|
|
|
- script.custom.modified = data.modified || Date.now();
|
|
|
- return saveScript(script, tx);
|
|
|
+ object.set(script, 'props.lastModified', data.modified || Date.now());
|
|
|
+ return saveScript(script, code).then(() => script);
|
|
|
})
|
|
|
.then(script => {
|
|
|
- Object.assign(res.data, getScriptInfo(script));
|
|
|
- return res;
|
|
|
+ fetchScriptResources(script, data);
|
|
|
+ Object.assign(result.data, script);
|
|
|
+ return result;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-function initPosition() {
|
|
|
- const os = db.transaction('scripts').objectStore('scripts');
|
|
|
- return new Promise(resolve => {
|
|
|
- os.index('position').openCursor(null, 'prev').onsuccess = e => {
|
|
|
- const { result } = e.target;
|
|
|
- if (result) position.set(result.key);
|
|
|
- resolve();
|
|
|
- };
|
|
|
+function fetchScriptResources(script, cache) {
|
|
|
+ const base = object.get(script, 'custom.lastInstallURL');
|
|
|
+ const meta = script.meta;
|
|
|
+ // @require
|
|
|
+ meta.require.forEach(url => {
|
|
|
+ const fullUrl = getFullUrl(url, base);
|
|
|
+ const cached = object.get(cache, ['require', fullUrl]);
|
|
|
+ if (cached) {
|
|
|
+ storage.require.dump(fullUrl, cached);
|
|
|
+ } else {
|
|
|
+ storage.require.fetch(fullUrl);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // @resource
|
|
|
+ Object.keys(meta.resources).forEach(key => {
|
|
|
+ const url = meta.resources[key];
|
|
|
+ const fullUrl = getFullUrl(url, base);
|
|
|
+ const cached = object.get(cache, ['resources', fullUrl]);
|
|
|
+ if (cached) {
|
|
|
+ storage.cache.dump(fullUrl, cached);
|
|
|
+ } else {
|
|
|
+ storage.cache.fetch(fullUrl);
|
|
|
+ }
|
|
|
});
|
|
|
+ // @icon
|
|
|
+ if (isRemote(meta.icon)) {
|
|
|
+ const fullUrl = getFullUrl(meta.icon, base);
|
|
|
+ storage.cache.fetch(fullUrl, ({ blob: getBlob }) => new Promise((resolve, reject) => {
|
|
|
+ const blob = getBlob({ type: 'image/png' });
|
|
|
+ const url = URL.createObjectURL(blob);
|
|
|
+ const image = new Image();
|
|
|
+ const free = () => URL.revokeObjectURL(url);
|
|
|
+ image.onload = () => {
|
|
|
+ free();
|
|
|
+ resolve();
|
|
|
+ };
|
|
|
+ image.onerror = () => {
|
|
|
+ free();
|
|
|
+ reject();
|
|
|
+ };
|
|
|
+ image.src = url;
|
|
|
+ }));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-export function checkPosition(start) {
|
|
|
- let offset = Math.max(1, start || 0);
|
|
|
- const updates = [];
|
|
|
- let changed;
|
|
|
- if (!position.checking) {
|
|
|
- const tx = db.transaction('scripts', 'readwrite');
|
|
|
- const os = tx.objectStore('scripts');
|
|
|
- position.checking = new Promise(resolve => {
|
|
|
- os.index('position').openCursor(start).onsuccess = e => {
|
|
|
- const cursor = e.target.result;
|
|
|
- if (cursor) {
|
|
|
- const { value } = cursor;
|
|
|
- if (value.position !== offset) updates.push({ id: value.id, position: offset });
|
|
|
- position.update(offset);
|
|
|
- offset += 1;
|
|
|
- cursor.continue();
|
|
|
- } else {
|
|
|
- resolve();
|
|
|
- }
|
|
|
- };
|
|
|
- })
|
|
|
- .then(() => {
|
|
|
- changed = updates.length;
|
|
|
- return update();
|
|
|
- function update() {
|
|
|
- const item = updates.shift();
|
|
|
- if (item) {
|
|
|
- return new Promise(resolve => {
|
|
|
- os.get(item.id).onsuccess = e => {
|
|
|
- const { result } = e.target;
|
|
|
- result.position = item.position;
|
|
|
- os.put(result).onsuccess = () => { resolve(); };
|
|
|
- };
|
|
|
- })
|
|
|
- .then(update);
|
|
|
+export function vacuum() {
|
|
|
+ const valueKeys = {};
|
|
|
+ const cacheKeys = {};
|
|
|
+ const requireKeys = {};
|
|
|
+ const codeKeys = {};
|
|
|
+ const mappings = [
|
|
|
+ [storage.value, valueKeys],
|
|
|
+ [storage.cache, cacheKeys],
|
|
|
+ [storage.require, requireKeys],
|
|
|
+ [storage.code, codeKeys],
|
|
|
+ ];
|
|
|
+ browser.storage.get().then(data => {
|
|
|
+ Object.keys(data).forEach(key => {
|
|
|
+ mappings.some(([substore, map]) => {
|
|
|
+ const { prefix } = substore;
|
|
|
+ if (key.startsWith(prefix)) {
|
|
|
+ // -1 for untouched, 1 for touched, 2 for missing
|
|
|
+ map[key.slice(prefix.length)] = -1;
|
|
|
+ return true;
|
|
|
}
|
|
|
- }
|
|
|
- })
|
|
|
- .then(() => {
|
|
|
- browser.runtime.sendMessage({
|
|
|
- cmd: 'ScriptsUpdated',
|
|
|
});
|
|
|
- position.checking = null;
|
|
|
- })
|
|
|
- .then(() => changed);
|
|
|
- }
|
|
|
- return position.checking;
|
|
|
+ });
|
|
|
+ });
|
|
|
+ const touch = (obj, key) => {
|
|
|
+ if (obj[key] < 0) obj[key] = 1;
|
|
|
+ else if (!obj[key]) obj[key] = 2;
|
|
|
+ };
|
|
|
+ store.scripts.forEach(script => {
|
|
|
+ const { id } = script.props;
|
|
|
+ touch(codeKeys, id);
|
|
|
+ touch(valueKeys, id);
|
|
|
+ const base = script.custom.lastInstallURL;
|
|
|
+ script.meta.require.forEach(url => {
|
|
|
+ const fullUrl = getFullUrl(url, base);
|
|
|
+ touch(requireKeys, fullUrl);
|
|
|
+ });
|
|
|
+ Object.keys(script.meta.resources).forEach(key => {
|
|
|
+ const url = script.meta.resources[key];
|
|
|
+ const fullUrl = getFullUrl(url, base);
|
|
|
+ touch(cacheKeys, fullUrl);
|
|
|
+ });
|
|
|
+ const { icon } = script.meta;
|
|
|
+ if (isRemote(icon)) {
|
|
|
+ const fullUrl = getFullUrl(icon, base);
|
|
|
+ touch(cacheKeys, fullUrl);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ mappings.forEach(([substore, map]) => {
|
|
|
+ Object.keys(map).forEach(key => {
|
|
|
+ const value = map[key];
|
|
|
+ if (value < 0) {
|
|
|
+ // redundant value
|
|
|
+ substore.remove(key);
|
|
|
+ } else if (value === 2 && substore.fetch) {
|
|
|
+ // missing resource
|
|
|
+ substore.fetch(key);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
}
|