Browse Source

refactor: extract script and string methods

tophf 1 month ago
parent
commit
6000510ce3
3 changed files with 201 additions and 194 deletions
  1. 3 194
      src/common/index.js
  2. 90 0
      src/common/script.js
  3. 108 0
      src/common/string.js

+ 3 - 194
src/common/index.js

@@ -1,10 +1,11 @@
 // SAFETY WARNING! Exports used by `injected` must make ::safe() calls and use __proto__:null
 // SAFETY WARNING! Exports used by `injected` must make ::safe() calls and use __proto__:null
 
 
-import { browser, HOMEPAGE_URL, INFERRED, RUN_AT_RE, SUPPORT_URL } from './consts';
+import { browser } from './consts';
 import { deepCopy } from './object';
 import { deepCopy } from './object';
-import { blob2base64, i18n, isDataUri, tryUrl } from './util';
 
 
 export { normalizeKeys } from './object';
 export { normalizeKeys } from './object';
+export * from './script';
+export * from './string';
 export * from './util';
 export * from './util';
 
 
 if (process.env.DEV && process.env.IS_INJECTED !== 'injected-web') {
 if (process.env.DEV && process.env.IS_INJECTED !== 'injected-web') {
@@ -21,11 +22,6 @@ export const ignoreChromeErrors = () => chrome.runtime.lastError;
 export const browserWindows = !process.env.IS_INJECTED && browser.windows;
 export const browserWindows = !process.env.IS_INJECTED && browser.windows;
 export const defaultImage = !process.env.IS_INJECTED && `${ICON_PREFIX}128.png`;
 export const defaultImage = !process.env.IS_INJECTED && `${ICON_PREFIX}128.png`;
 /** @return {'0' | '1' | ''} treating source as abstract truthy/falsy to ensure consistent result */
 /** @return {'0' | '1' | ''} treating source as abstract truthy/falsy to ensure consistent result */
-export const nullBool2string = v => v ? '1' : v == null ? '' : '0';
-/** Will be encoded to avoid splitting the URL in devtools UI */
-const BAD_URL_CHAR = /[#/?]/g;
-/** Fullwidth range starts at 0xFF00, normal range starts at space char code 0x20 */
-const replaceWithFullWidthForm = s => String.fromCharCode(s.charCodeAt(0) - 0x20 + 0xFF00);
 const PORT_ERROR_RE = /(Receiving end does not exist)|The message port closed before|moved into back\/forward cache|$/;
 const PORT_ERROR_RE = /(Receiving end does not exist)|The message port closed before|moved into back\/forward cache|$/;
 
 
 export function initHooks() {
 export function initHooks() {
@@ -144,150 +140,6 @@ export function ignoreNoReceiver(err) {
   }
   }
 }
 }
 
 
-export function leftpad(input, length, pad = '0') {
-  let num = input.toString();
-  while (num.length < length) num = `${pad}${num}`;
-  return num;
-}
-
-/**
- * @param {string} browserLang  Language tags from RFC5646 (`[lang]-[script]-[region]-[variant]`, all parts are optional)
- * @param {string} locale  `<lang>`, `<lang>-<region>`
- */
-function localeMatch(browserLang, metaLocale) {
-  const bParts = browserLang.toLowerCase().split('-');
-  const mParts = metaLocale.toLowerCase().split('-');
-  let bi = 0;
-  let mi = 0;
-  while (bi < bParts.length && mi < mParts.length) {
-    if (bParts[bi] === mParts[mi]) mi += 1;
-    bi += 1;
-  }
-  return mi === mParts.length;
-}
-
-/**
- * Get locale attributes such as `@name:zh-CN`
- */
-export function getLocaleString(meta, key, languages = navigator.languages) {
-  // zh, zh-cn, zh-tw
-  const mls = Object.keys(meta)
-    .filter(metaKey => metaKey.startsWith(key + ':'))
-    .map(metaKey => metaKey.slice(key.length + 1))
-    .sort((a, b) => b.length - a.length);
-  let bestLocale;
-  for (const lang of languages) {
-    bestLocale = mls.find(ml => localeMatch(lang, ml));
-    if (bestLocale) break;
-  }
-  return meta[bestLocale ? `${key}:${bestLocale}` : key] || '';
-}
-
-/**
- * @param {VMScript} script
- * @returns {string | undefined}
- */
-export function getScriptHome(script) {
-  let custom, meta;
-  return (custom = script.custom)[HOMEPAGE_URL]
-    || (meta = script.meta)[HOMEPAGE_URL]
-    || script[INFERRED]?.[HOMEPAGE_URL]
-    || meta.homepage
-    || meta.website
-    || meta.source
-    || custom.from;
-}
-
-/**
- * @param {VMScript} script
- * @returns {string | undefined}
- */
-export function getScriptSupportUrl(script) {
-  return script.meta[SUPPORT_URL] || script[INFERRED]?.[SUPPORT_URL];
-}
-
-/**
- * @param {VMScript} script
- * @returns {string}
- */
-export function getScriptIcon(script) {
-  return script.custom.icon || script.meta.icon;
-}
-
-/**
- * @param {VMScript} script
- * @returns {string}
- */
-export function getScriptName(script) {
-  return script.custom.name || getLocaleString(script.meta, 'name')
-    || `#${script.props.id ?? i18n('labelNoName')}`;
-}
-
-/** @returns {VMInjection.RunAt} without "document-" */
-export function getScriptRunAt(script) {
-  return `${script.custom[RUN_AT] || script.meta[RUN_AT] || ''}`.match(RUN_AT_RE)?.[1] || 'end';
-}
-
-/** URL that shows the name of the script and opens in devtools sources or in our editor */
-export function getScriptPrettyUrl(script, displayName) {
-  return `${
-    extensionRoot
-  }${
-    // When called from prepareScript, adding a space to group scripts in one block visually
-    displayName && IS_FIREFOX ? '%20' : ''
-  }${
-    encodeURIComponent((displayName || getScriptName(script))
-    .replace(BAD_URL_CHAR, replaceWithFullWidthForm))
-  }.user.js#${
-    script.props.id
-  }`;
-}
-
-/**
- * @param {VMScript} script
- * @param {Object} [opts]
- * @param {boolean} [opts.all] - to return all two urls [checkUrl, downloadUrl]
- * @param {boolean} [opts.allowedOnly] - check shouldUpdate
- * @param {boolean} [opts.enabledOnly]
- * @return {string[] | string}
- */
-export function getScriptUpdateUrl(script, { all, allowedOnly, enabledOnly } = {}) {
-  if ((!allowedOnly || script.config.shouldUpdate)
-  && (!enabledOnly || script.config.enabled)) {
-    const { custom, meta } = script;
-    /* URL in meta may be set to an invalid value to enforce disabling of the automatic updates
-     * e.g. GreasyFork sets it to `none` when the user installs an old version.
-     * We'll show such script as non-updatable. */
-    const downloadURL = tryUrl(custom.downloadURL || meta.downloadURL || custom.lastInstallURL);
-    const updateURL = tryUrl(custom.updateURL || meta.updateURL || downloadURL);
-    const url = downloadURL || updateURL;
-    if (url) return all ? [downloadURL, updateURL] : url;
-  }
-}
-
-export function getFullUrl(url, base) {
-  let obj;
-  try {
-    obj = new URL(url, base);
-  } catch (e) {
-    return `data:,${e.message} ${url}`;
-  }
-  return obj.href;
-}
-
-export function encodeFilename(name) {
-  // `escape` generated URI has % in it
-  return name.replace(/[-\\/:*?"<>|%\s]/g, (m) => {
-    let code = m.charCodeAt(0).toString(16);
-    if (code.length < 2) code = `0${code}`;
-    return `-${code}`;
-  });
-}
-
-export function decodeFilename(filename) {
-  return filename.replace(/-([0-9a-f]{2})/g, (_m, g) => String.fromCharCode(parseInt(g, 16)));
-}
-
 export async function getActiveTab(windowId = -2 /*chrome.windows.WINDOW_ID_CURRENT*/) {
 export async function getActiveTab(windowId = -2 /*chrome.windows.WINDOW_ID_CURRENT*/) {
   return (
   return (
     await browser.tabs.query({
     await browser.tabs.query({
@@ -308,46 +160,3 @@ export function makePause(ms) {
     ? Promise.resolve()
     ? Promise.resolve()
     : new Promise(resolve => setTimeout(resolve, ms));
     : new Promise(resolve => setTimeout(resolve, ms));
 }
 }
-
-export function trueJoin(separator) {
-  return this.filter(Boolean).join(separator);
-}
-
-/**
- * @param {string} raw - raw value in storage.cache
- * @param {string} [url]
- * @returns {?string}
- */
-export function makeDataUri(raw, url) {
-  if (isDataUri(url)) return url;
-  if (/^(i,|image\/)/.test(raw)) { // workaround for bugs in old VM, see 2e135cf7
-    const i = raw.lastIndexOf(',');
-    const type = raw.startsWith('image/') ? raw.slice(0, i) : 'image/png';
-    return `data:${type};base64,${raw.slice(i + 1)}`;
-  }
-  return raw;
-}
-
-/**
- * @param {VMReq.Response} response
- * @returns {Promise<string>}
- */
-export async function makeRaw(response) {
-  const type = (response.headers.get('content-type') || '').split(';')[0] || '';
-  const body = await blob2base64(response.data);
-  return `${type},${body}`;
-}
-
-export function loadQuery(string) {
-  const res = {};
-  if (string) {
-    new URLSearchParams(string).forEach((val, key) => {
-      res[key] = val;
-    });
-  }
-  return res;
-}
-
-export function dumpQuery(dict) {
-  return `${new URLSearchParams(dict)}`;
-}

+ 90 - 0
src/common/script.js

@@ -0,0 +1,90 @@
+import { HOMEPAGE_URL, INFERRED, RUN_AT_RE, SUPPORT_URL } from './consts';
+import { getLocaleString } from './string';
+import { i18n, tryUrl } from './util';
+
+/** Will be encoded to avoid splitting the URL in devtools UI */
+const BAD_URL_CHAR = /[#/?]/g;
+/** Fullwidth range starts at 0xFF00, normal range starts at space char code 0x20 */
+const replaceWithFullWidthForm = s => String.fromCharCode(s.charCodeAt(0) - 0x20 + 0xFF00);
+
+/**
+ * @param {VMScript} script
+ * @returns {string | undefined}
+ */
+export function getScriptHome(script) {
+  let custom, meta;
+  return (custom = script.custom)[HOMEPAGE_URL]
+    || (meta = script.meta)[HOMEPAGE_URL]
+    || script[INFERRED]?.[HOMEPAGE_URL]
+    || meta.homepage
+    || meta.website
+    || meta.source
+    || custom.from;
+}
+
+/**
+ * @param {VMScript} script
+ * @returns {string | undefined}
+ */
+export function getScriptSupportUrl(script) {
+  return script.meta[SUPPORT_URL] || script[INFERRED]?.[SUPPORT_URL];
+}
+
+/**
+ * @param {VMScript} script
+ * @returns {string}
+ */
+export function getScriptIcon(script) {
+  return script.custom.icon || script.meta.icon;
+}
+
+/**
+ * @param {VMScript} script
+ * @returns {string}
+ */
+export function getScriptName(script) {
+  return script.custom.name || getLocaleString(script.meta, 'name')
+    || `#${script.props.id ?? i18n('labelNoName')}`;
+}
+
+/** @returns {VMInjection.RunAt} without "document-" */
+export function getScriptRunAt(script) {
+  return `${script.custom[RUN_AT] || script.meta[RUN_AT] || ''}`.match(RUN_AT_RE)?.[1] || 'end';
+}
+
+/** URL that shows the name of the script and opens in devtools sources or in our editor */
+export function getScriptPrettyUrl(script, displayName) {
+  return `${
+    extensionRoot
+  }${
+    // When called from prepareScript, adding a space to group scripts in one block visually
+    displayName && IS_FIREFOX ? '%20' : ''
+  }${
+    encodeURIComponent((displayName || getScriptName(script))
+    .replace(BAD_URL_CHAR, replaceWithFullWidthForm))
+  }.user.js#${
+    script.props.id
+  }`;
+}
+
+/**
+ * @param {VMScript} script
+ * @param {Object} [opts]
+ * @param {boolean} [opts.all] - to return all two urls [checkUrl, downloadUrl]
+ * @param {boolean} [opts.allowedOnly] - check shouldUpdate
+ * @param {boolean} [opts.enabledOnly]
+ * @return {string[] | string}
+ */
+export function getScriptUpdateUrl(script, { all, allowedOnly, enabledOnly } = {}) {
+  if ((!allowedOnly || script.config.shouldUpdate)
+  && (!enabledOnly || script.config.enabled)) {
+    const { custom, meta } = script;
+    /* URL in meta may be set to an invalid value to enforce disabling of the automatic updates
+     * e.g. GreasyFork sets it to `none` when the user installs an old version.
+     * We'll show such script as non-updatable. */
+    const downloadURL = tryUrl(custom.downloadURL || meta.downloadURL || custom.lastInstallURL);
+    const updateURL = tryUrl(custom.updateURL || meta.updateURL || downloadURL);
+    const url = downloadURL || updateURL;
+    if (url) return all ? [downloadURL, updateURL] : url;
+  }
+}

+ 108 - 0
src/common/string.js

@@ -0,0 +1,108 @@
+import { blob2base64, isDataUri } from './util';
+
+export const nullBool2string = v => v ? '1' : v == null ? '' : '0';
+
+export function leftpad(input, length, pad = '0') {
+  let num = input.toString();
+  while (num.length < length) num = `${pad}${num}`;
+  return num;
+}
+
+/**
+ * @param {string} browserLang  Language tags from RFC5646 (`[lang]-[script]-[region]-[variant]`, all parts are optional)
+ * @param {string} locale  `<lang>`, `<lang>-<region>`
+ */
+function localeMatch(browserLang, metaLocale) {
+  const bParts = browserLang.toLowerCase().split('-');
+  const mParts = metaLocale.toLowerCase().split('-');
+  let bi = 0;
+  let mi = 0;
+  while (bi < bParts.length && mi < mParts.length) {
+    if (bParts[bi] === mParts[mi]) mi += 1;
+    bi += 1;
+  }
+  return mi === mParts.length;
+}
+
+/**
+ * Get locale attributes such as `@name:zh-CN`
+ */
+export function getLocaleString(meta, key, languages = navigator.languages) {
+  // zh, zh-cn, zh-tw
+  const mls = Object.keys(meta)
+    .filter(metaKey => metaKey.startsWith(key + ':'))
+    .map(metaKey => metaKey.slice(key.length + 1))
+    .sort((a, b) => b.length - a.length);
+  let bestLocale;
+  for (const lang of languages) {
+    bestLocale = mls.find(ml => localeMatch(lang, ml));
+    if (bestLocale) break;
+  }
+  return meta[bestLocale ? `${key}:${bestLocale}` : key] || '';
+}
+
+export function getFullUrl(url, base) {
+  let obj;
+  try {
+    obj = new URL(url, base);
+  } catch (e) {
+    return `data:,${e.message} ${url}`;
+  }
+  return obj.href;
+}
+
+export function encodeFilename(name) {
+  // `escape` generated URI has % in it
+  return name.replace(/[-\\/:*?"<>|%\s]/g, (m) => {
+    let code = m.charCodeAt(0).toString(16);
+    if (code.length < 2) code = `0${code}`;
+    return `-${code}`;
+  });
+}
+
+export function decodeFilename(filename) {
+  return filename.replace(/-([0-9a-f]{2})/g, (_m, g) => String.fromCharCode(parseInt(g, 16)));
+}
+
+export function trueJoin(separator) {
+  return this.filter(Boolean).join(separator);
+}
+
+/**
+ * @param {string} raw - raw value in storage.cache
+ * @param {string} [url]
+ * @returns {?string}
+ */
+export function makeDataUri(raw, url) {
+  if (isDataUri(url)) return url;
+  if (/^(i,|image\/)/.test(raw)) { // workaround for bugs in old VM, see 2e135cf7
+    const i = raw.lastIndexOf(',');
+    const type = raw.startsWith('image/') ? raw.slice(0, i) : 'image/png';
+    return `data:${type};base64,${raw.slice(i + 1)}`;
+  }
+  return raw;
+}
+
+/**
+ * @param {VMReq.Response} response
+ * @returns {Promise<string>}
+ */
+export async function makeRaw(response) {
+  const type = (response.headers.get('content-type') || '').split(';')[0] || '';
+  const body = await blob2base64(response.data);
+  return `${type},${body}`;
+}
+
+export function loadQuery(string) {
+  const res = {};
+  if (string) {
+    new URLSearchParams(string).forEach((val, key) => {
+      res[key] = val;
+    });
+  }
+  return res;
+}
+
+export function dumpQuery(dict) {
+  return `${new URLSearchParams(dict)}`;
+}