diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dec8bba..7c6aaab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,39 +6,29 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 - - name: Setup node - uses: actions/setup-node@v1 + uses: actions/checkout@v3 with: - node-version: 16.x - - name: Cache node modules - uses: actions/cache@v1 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - - name: Install & Build - run: > - rm -rf .gitignore - - echo "node_modules" >> .gitignore - - echo "doc/node_modules" >> .gitignore + fetch-depth: 0 - npm i -D + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 7.16.1 - npm install -D webpack-cli webpack + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: "18" + cache: "pnpm" - npm run build + - name: Install dependencies + run: pnpm install --prefer-offline + - name: Install & Build + run: | + pnpm run build echo "clientworker.js.org" > ./dist/CNAME - - cd doc - - npm add --dev vitepress vue - - npm run "docs:build" - - + pnpm docs:build - name: Deploy to GithubPage uses: peaceiris/actions-gh-pages@v3 @@ -49,4 +39,4 @@ jobs: - name: Publish to NPM uses: JS-DevTools/npm-publish@v1 with: - token: ${{ secrets.NPM }} \ No newline at end of file + token: ${{ secrets.NPM }} diff --git a/.gitignore b/.gitignore index 2f43ada..f06235c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ node_modules -package-lock.json -doc/node_modules -dist/ \ No newline at end of file +dist diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3662b37 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} \ No newline at end of file diff --git a/build.ts b/build.ts new file mode 100644 index 0000000..974bbe0 --- /dev/null +++ b/build.ts @@ -0,0 +1,23 @@ +import { build } from "esbuild"; +import copyStaticFiles from "esbuild-copy-static-files"; +import { writeFile } from "fs/promises"; +import { configSchema } from "./types/configType"; + +(async () => { + await build({ + entryPoints: ["./main/entry.js"], + entryNames: "[dir]/cw", + bundle: true, + outdir: "dist", + minify: true, + sourcemap: true, + target: ["es2015"], + plugins: [copyStaticFiles({ src: "static", dest: "dist" })], + }); + await writeFile( + "dist/config.schema.json", + JSON.stringify(configSchema, null, 2) + ); +})(); + +export {}; diff --git a/doc/.gitignore b/doc/.gitignore deleted file mode 100644 index b512c09..0000000 --- a/doc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules \ No newline at end of file diff --git a/doc/package.json b/doc/package.json index 870735c..1c972a5 100644 --- a/doc/package.json +++ b/doc/package.json @@ -14,5 +14,9 @@ }, "author": "", "license": "ISC", - "description": "" + "description": "", + "dependencies": { + "vitepress": "^1.0.0-alpha.4", + "vue": "^3.2.37" + } } diff --git a/main/entry.js b/main/entry.js deleted file mode 100644 index be116e5..0000000 --- a/main/entry.js +++ /dev/null @@ -1,44 +0,0 @@ -import { } from './handle/main.js' -import pkgjson from '../package.json' -import cons from './utils/cons.js' -import CacheDB from '@chenyfan/cache-db' - -cons.s(`ClientWorker${pkgjson.version} Started!`) -const db = new CacheDB() -db.read('hotpatch').then(script => { - if (!!script) { - cons.s('Hotpatch Loaded!') - eval(script) - } else { - cons.w('Hotpatch Not Found!') - } -}) -db.read('config').then(config => { - config = JSON.parse(config) || {} - setInterval(() => { - cons.s(`ClientWorker@${pkgjson.version} Start to Clean Expired Cache!`) - caches.open("ClientWorker_ResponseCache").then(cache => { - cache.keys().then(keys => { - keys.forEach(key => { - cache.match(key).then(res => { - if (Number(res.headers.get('ClientWorker_ExpireTime')) <= new Date().getTime()) { - cache.delete(key) - } - }) - }) - }) - }) - }, eval(config.cleaninterval) || 60*1000); -}) -addEventListener('fetch', event => { - event.respondWith(self.clientworkerhandle(event.request)) -}) -addEventListener('install', function () { - cons.s(`ClientWorker@${pkgjson.version} Installed!`) - self.skipWaiting(); -}); -addEventListener('activate', function () { - cons.s(`ClientWorker@${pkgjson.version} Activated!`) - self.clients.claim(); -}) - diff --git a/main/entry.ts b/main/entry.ts new file mode 100644 index 0000000..85738ca --- /dev/null +++ b/main/entry.ts @@ -0,0 +1,54 @@ +import CacheDB from "@chenyfan/cache-db"; +import pkgjson from "../package.json"; +import clientworkerhandle from "./handle/main"; +import cons from "./utils/cons"; + +// For compatibility + +self.clientworkerhandle = clientworkerhandle; + +cons.s(`ClientWorker${pkgjson.version} Started!`); +const db = new CacheDB(); +db.read("hotpatch").then((script) => { + if (!!script) { + cons.s("Hotpatch Loaded!"); + eval(script as string); + } else { + cons.w("Hotpatch Not Found!"); + } +}); + +db.read("config").then((config) => { + const cfg = JSON.parse(config) || {}; + config = cfg; + setInterval(() => { + cons.s(`ClientWorker@${pkgjson.version} Start to Clean Expired Cache!`); + caches.open("ClientWorker_ResponseCache").then((cache) => { + cache.keys().then((keys) => { + keys.forEach((key) => { + cache.match(key).then((res) => { + if ( + Number(res!.headers.get("ClientWorker_ExpireTime")) <= + new Date().getTime() + ) { + cache.delete(key); + } + }); + }); + }); + }); + }, eval(cfg.cleaninterval) || 60 * 1000); +}); +addEventListener("fetch", (event) => { + (event as FetchEvent).respondWith( + self.clientworkerhandle((event as FetchEvent).request) + ); +}); +addEventListener("install", function () { + cons.s(`ClientWorker@${pkgjson.version} Installed!`); + self.skipWaiting(); +}); +addEventListener("activate", function () { + cons.s(`ClientWorker@${pkgjson.version} Activated!`); + self.clients.claim(); +}); diff --git a/main/handle/cgi.js b/main/handle/cgi.js deleted file mode 100644 index 426cbc4..0000000 --- a/main/handle/cgi.js +++ /dev/null @@ -1,80 +0,0 @@ -import yaml from 'js-yaml' -import CacheDB from '@chenyfan/cache-db' -import FetchEngine from '../utils/engine.js' -import pkgjson from '../../package.json' -const router_cgi = async (request) => { - const db = new CacheDB() - const urlStr = request.url.toString() - const urlObj = new URL(urlStr) - const pathname = urlObj.pathname - const q = (s) => { return urlObj.searchParams.get(s) } - let config - switch (pathname.split('/')[2]) { - case 'hello': - return new Response('Hello ClientWorker!') - case 'info': - return new Response(JSON.stringify({ - version: pkgjson.version - }),{ - headers:{ - 'Content-Type':'application/json' - } - }) - case 'page': - switch (q('type')) { - case 'install': - return fetch('/404') - default: - return new Response('Error, page type not found') - } - case 'api': - - switch (q('type')) { - case 'config': - return fetch(q('url') || '/config.yaml') - .then(res => res.text()) - .then(text => yaml.load(text)) - .then(async config => { - await db.write('config', JSON.stringify(config), { type: "json" }) - return new Response('ok') - }) - .catch(async err => { - await db.write('config', '') - return new Response(err) - }) - case 'clear': - return caches.open('ClientWorker_ResponseCache').then(async cache => { - return cache.keys().then(async keys => { - await Promise.all(keys.map(key => { - cache.delete(key) - })) - return new Response('ok') - }) - }) - case 'hotpatch': - config =JSON.parse(await db.read('config')) - if(typeof config.hotpatch !== 'object') return new Response('Error, config.hotpatch not found') - const hotpatch = config.hotpatch - await FetchEngine.parallel(hotpatch) - .then(t=>t.text()) - .then(async script=>{ - await db.write('hotpatch', script, { type: "text" }) - eval(script) - }) - return new Response('ok') - case 'hotconfig': - config = JSON.parse(await db.read('config')) - if(typeof config.hotconfig !== 'object') return new Response('Error, config.hotconfig not found') - const hotconfig = config.hotconfig - const nConfig = await FetchEngine.parallel(hotconfig).then(t=>t.text()).then(t=>yaml.load(t)).then(t=>JSON.stringify(t)).catch(t=>{return ''}) - if(nConfig)await db.write('config',nConfig) - return new Response('ok') - - default: - return new Response('Error, api type not found') - } - default: - return new Response('Not Found!, Client Worker!') - } -} -export default router_cgi \ No newline at end of file diff --git a/main/handle/cgi.ts b/main/handle/cgi.ts new file mode 100644 index 0000000..a695633 --- /dev/null +++ b/main/handle/cgi.ts @@ -0,0 +1,106 @@ +import yaml from "js-yaml"; +import CacheDB from "@chenyfan/cache-db"; +import FetchEngine from "../utils/engine"; +import pkgjson from "../../package.json"; +import { ConfigType } from "../../types/configType"; + +const router_cgi = async (request: Request) => { + const db = new CacheDB(); + const urlStr = request.url.toString(); + const urlObj = new URL(urlStr); + const pathname = urlObj.pathname; + const q = (s: string) => { + return urlObj.searchParams.get(s); + }; + let config: ConfigType; + switch (pathname.split("/")[2]) { + case "hello": + return new Response("Hello ClientWorker!"); + case "info": + return new Response( + JSON.stringify({ + version: pkgjson.version, + }), + { + headers: { + "Content-Type": "application/json", + }, + } + ); + case "page": + switch (q("type")) { + case "install": + return fetch("/404"); + default: + return new Response("Error, page type not found"); + } + case "api": + switch (q("type")) { + case "config": + return fetch(q("url") || "/config.yaml") + .then((res) => res.text()) + .then((text) => yaml.load(text)) + .then(async (config) => { + await db.write("config", JSON.stringify(config), { + type: "json", + }); + return new Response("ok"); + }) + .catch(async (err) => { + await db.write("config", ""); + return new Response(err); + }); + case "clear": + return caches + .open("ClientWorker_ResponseCache") + .then(async (cache) => { + return cache.keys().then(async (keys) => { + await Promise.all( + keys.map((key) => { + cache.delete(key); + }) + ); + return new Response("ok"); + }); + }); + case "hotpatch": + config = JSON.parse(await db.read("config")); + if (typeof config.hotpatch !== "object") + return new Response("Error, config.hotpatch not found"); + const hotpatch = config.hotpatch; + await FetchEngine.parallel( + hotpatch.map((hpc) => new Request(hpc)), + {} + ) + .then((t) => t.text()) + .then(async (script) => { + await db.write("hotpatch", script, { type: "text" }); + eval(script); + }); + return new Response("ok"); + case "hotconfig": + config = JSON.parse(await db.read("config")); + if (typeof config.hotconfig !== "object") + return new Response("Error, config.hotconfig not found"); + const hotconfig = config.hotconfig; + const nConfig = await FetchEngine.parallel( + hotconfig.map((cfg) => new Request(cfg)), + {} + ) + .then((t) => t.text()) + .then((t) => yaml.load(t)) + .then((t) => JSON.stringify(t)) + .catch((t) => { + return ""; + }); + if (nConfig) await db.write("config", nConfig); + return new Response("ok"); + + default: + return new Response("Error, api type not found"); + } + default: + return new Response("Not Found!, Client Worker!"); + } +}; +export default router_cgi; diff --git a/main/handle/main.js b/main/handle/main.js deleted file mode 100644 index 8879f81..0000000 --- a/main/handle/main.js +++ /dev/null @@ -1,283 +0,0 @@ -import router_cgi from './cgi.js' -import CacheDB from '@chenyfan/cache-db' -import cons from './../utils/cons.js' -import FetchEngine from '../utils/engine.js' -import rebuild from '../utils/rebuild.js' -self.clientworkerhandle = async (request) => { - //当前域 new Request('').url - const domain = new URL(new Request('').url).host - const db = new CacheDB() - - let tReq = request - const urlStr = tReq.url.toString() - const urlObj = new URL(urlStr) - const pathname = urlObj.pathname - if (pathname.split('/')[1] === 'cw-cgi') { - return router_cgi(request) - } - const config = await db.read('config', { type: "json" }) - if (!config) return fetch(request) - - let tFetched = false - let EngineFetch = false - let fetchConfig = {} - let EngineFetchList = [] - let tRes = new Response() - for (let catch_rule of config.catch_rules) { - if (catch_rule.rule === '_') catch_rule.rule = domain - if (!tReq.url.match(new RegExp(catch_rule.rule))) continue; - - for (let transform_rule of catch_rule.transform_rules) { - let tSearched = false - - if (transform_rule.search === '_') transform_rule.search = catch_rule.rule - switch (transform_rule.searchin || "url") { - case 'url': - if (tReq.url.match(new RegExp(transform_rule.search, transform_rule.searchflags))) tSearched = true; - break - case 'header': - if (tReq.headers.get(transform_rule.searchkey).match(new RegExp(transform_rule.search, transform_rule.searchflags))) tSearched = true; - break; - case 'status': - if (!tFetched) { cons.w(`${tReq.url} is not fetched yet,the status rule are ignored`); break } - if (String(tRes.status).match(new RegExp(transform_rule.search, transform_rule.searchflags))) tSearched = true; - break - case 'statusText': - if (!tFetched) { cons.w(`${tReq.url} is not fetched yet,the statusText rule are ignored`); break } - if (tRes.statusText.match(new RegExp(transform_rule.search, transform_rule.searchflags))) tSearched = true; - break - case 'body': - if (!tFetched) { cons.w(`${tReq.url} is not fetched yet,the body rule are ignored`); break } - if ((await tRes.clone().text()).match(new RegExp(transform_rule.search, transform_rule.searchflags))) tSearched = true; - break; - default: - cons.e(`${tReq.url} the ${transform_rule.searchin} search rule are not supported`); - break - - } - - switch (transform_rule.replacein || 'url') { - case 'url': - if (tFetched && tSearched) { cons.w(`${tReq.url} is already fetched,the url transform rule:${transform_rule.search} are ignored`); break } - if (typeof transform_rule.replace !== 'undefined' && tSearched) { - if (typeof transform_rule.replace === 'string') { - if (EngineFetch) cons.w(`EngineFetch Disabled for ${tReq.url},the request will downgrade to normal fetch`) - tReq = rebuild.request(tReq, { url: tReq.url.replace(new RegExp(transform_rule.replacekey || transform_rule.search, transform_rule.replaceflags), transform_rule.replace) }) - EngineFetch = false - } else { - if (EngineFetch) { cons.w(`Replacement cannot be used for ${tReq.url},the request is already powered by fetch-engine `); break } - transform_rule.replace.forEach(replacement => { - if (replacement === '_') { - EngineFetchList.push(tReq) - return; - } - EngineFetchList.push( - rebuild.request(tReq, { url: tReq.url.replace(new RegExp(transform_rule.replacekey || transform_rule.search, transform_rule.replaceflags), replacement) }) - ) - }); - - EngineFetch = true - } - } - break - case 'body': - if (tSearched) { - if (tFetched) { - tRes = rebuild.response(tRes, { body: (await tRes.clone().text()).replace(new RegExp(transform_rule.replacekey || transform_rule.search, transform_rule.replaceflags), transform_rule.replace) }) - - } else { - tReq = rebuild.request(tReq, { body: (await tReq.clone().text()).replace(new RegExp(transform_rule.replacekey || transform_rule.search, transform_rule.replaceflags), transform_rule.replace) }) - } - } - break; - - case 'status': - if (typeof transform_rule.replace === 'string' && tSearched) tRes = rebuild.response(tRes, { status: tRes.status.replace(new RegExp(transform_rule.replacekey || transform_rule.search, transform_rule.replaceflags), transform_rule.replace) }) - break; - case 'statusText': - if (typeof transform_rule.replace === 'string' && tSearched) tRes = rebuild.response(tRes, { statusText: tRes.statusText.replace(new RegExp(transform_rule.replacekey || transform_rule.search, transform_rule.replaceflags), transform_rule.replace) }) - break; - default: - cons.e(`${tReq.url} the ${transform_rule.replacein} replace rule are not supported`); - } - if (!tSearched) continue - if (typeof transform_rule.header === 'object') { - for (var header in transform_rule.header) { - if (tFetched) { - tRes = rebuild.response(tRes, { headers: { [header]: transform_rule.header[header] } }) - } else { - tReq = rebuild.request(tReq, { headers: { [header]: transform_rule.header[header] } }) - } - } - } - - if (typeof transform_rule.action !== 'undefined') { - switch (transform_rule.action) { - case 'skip': - return fetch(request) - case 'fetch': - if (tFetched) { cons.w(`${tReq.url} is already fetched,the fetch action are ignored`); break } - if (typeof transform_rule.fetch === 'undefined') { cons.e(`Fetch Config is not defined for ${tReq.url}`); break } - - fetchConfig = { - status: transform_rule.fetch.status, - mode: transform_rule.fetch.mode, - credentials: transform_rule.fetch.credentials, - redirect: transform_rule.fetch.redirect, - timeout: transform_rule.fetch.timeout, - threads: transform_rule.fetch.threads, - limit: transform_rule.fetch.limit - } - if (!transform_rule.fetch.preflight) { - tReq = new Request(tReq.url, { - method: ((method) => { - if (method === "GET" || method === "HEAD" || method === "POST") return method; - return "GET" - })(tReq.method), - body: ((body) => { - if (tReq.method === "POST") return body; - return null - })(tReq.body) - }) //https://segmentfault.com/a/1190000006095018 - delete fetchConfig.credentials - //fetchConfig.mode = "cors" - for (var eReq in EngineFetchList) { - EngineFetchList[eReq] = new Request(EngineFetchList[eReq].url, tReq) - } - } - - tRes = await new Promise(async (res, rej) => { - const EngineFetcher = async () => { - let cRes - return new Promise(async (resolve, reject) => { - if (!EngineFetch) { - switch (transform_rule.fetch.engine || 'fetch') { - case 'fetch': - cRes = await FetchEngine.fetch(tReq, fetchConfig) - break - case 'crazy': - cRes = await FetchEngine.crazy(tReq, fetchConfig) - break - default: - cons.e(`${tReq.url} the ${transform_rule.fetch.engine} engine are not supported`); - break - } - } else { - switch (transform_rule.fetch.engine || 'parallel') { - case 'classic': - cRes = await FetchEngine.classic(EngineFetchList, fetchConfig) - break; - case 'parallel': - cRes = await FetchEngine.parallel(EngineFetchList, fetchConfig) - break; - case 'KFCThursdayVW50': - if (new Date().getDay() === 4) cons.e(`VW50! The Best Fetch Engine in the World Said!`) - cRes = await FetchEngine.KFCThursdayVW50(EngineFetchList, fetchConfig) - break; - default: - cons.e(`Fetch Engine ${transform_rule.fetch.engine} is not supported`) - break; - } - - } - if (typeof transform_rule.fetch.cache === "object" && cRes.status === (transform_rule.fetch.status || 200)) { - cRes = rebuild.response(cRes, { headers: { "ClientWorker_ExpireTime": new Date().getTime() + Number(eval(transform_rule.fetch.cache.expire || '0')) } }) - caches.open("ClientWorker_ResponseCache").then(cache => { - cache.put(tReq, cRes.clone()) - .then(() => { resolve(cRes) }) - }) - } - else { resolve(cRes) } - }) - } - if (typeof transform_rule.fetch.cache === "object") { - caches.open("ClientWorker_ResponseCache").then(cache => { - cache.match(tReq).then(cRes => { - if (!!cRes) { - if (Number(cRes.headers.get('ClientWorker_ExpireTime')) > new Date().getTime()) { - cons.s(`${tReq.url} is fetched from cache`) - res(cRes) - return - } else { - cons.w(`${tReq.url} is expired.`) - res(Promise.any([ - EngineFetcher(), - new Promise(async (resolve, reject) => { - setTimeout(() => { - cons.e(`${tReq.url} is too late to fetch,even though the cache has expired,so return by cache`) - resolve(cRes) - return; - }, transform_rule.fetch.cache.delay || 3000); - }) - ])) - } - - } else { - cons.w(`${tReq.url} is not cached!And it is too late to fetch!`) - res(EngineFetcher()) - } - }) - }) - } else { res(EngineFetcher()) } - }) - tFetched = true - break - case 'redirect': - if (typeof transform_rule.redirect === 'undefined') { - cons.e(`Redirect Config is not defined for ${tReq.url}`); - break; - } - if (typeof transform_rule.redirect.url === 'string') return Response.redirect(transform_rule.redirect.url, transform_rule.redirect.status || 301) - return Response.redirect( - tReq.url.replace(new RegExp(transform_rule.search), transform_rule.redirect.to), - transform_rule.redirect.status || 301 - ) - case 'return': - if (typeof transform_rule.return === 'undefined') transform_rule.return = {} - return new Response(transform_rule.return.body || "Error!", { - status: transform_rule.return.status || 503, - headers: transform_rule.return.headers || {} - }) - case 'script': - if (typeof transform_rule.script === 'undefined') { - cons.e(`Script Config is not defined for ${tReq.url}`); - break; - } - if (typeof transform_rule.script.function === 'string') { - const ClientWorkerAnonymousFunctionName = `ClientWorker_AnonymousFunction_${new Date().getTime()}` - self[ClientWorkerAnonymousFunctionName] = eval(transform_rule.script.function) - transform_rule.script.name = ClientWorkerAnonymousFunctionName - } - const ScriptAns = await (Function('return (' + transform_rule.script.name + ')')())({ - fetched: tFetched, - request: tReq, - response: tRes - }) - - if (ScriptAns.fetched) { - if (transform_rule.script.skip || false) { - return ScriptAns.response - } - tFetched = true - tRes = ScriptAns.response - } else { - tReq = ScriptAns.request - } - break; - default: - cons.w(`This Action:${transform_rule.action} is not supported yet`) - break - } - } - } - - - } - if (!tFetched) { - //3.0.0 默认改为skip - return fetch(request) - } - - return tRes -} -export default {} \ No newline at end of file diff --git a/main/handle/main.ts b/main/handle/main.ts new file mode 100644 index 0000000..57c2873 --- /dev/null +++ b/main/handle/main.ts @@ -0,0 +1,477 @@ +import CacheDB from "@chenyfan/cache-db"; +import { ConfigType } from "../../types/configType"; +import cons from "../utils/cons"; +import FetchEngine from "../utils/engine"; +import rebuild from "../utils/rebuild"; +import router_cgi from "./cgi"; + +const clientworkerhandle = async (request: Request) => { + //当前域 new Request('').url + const domain = new URL(new Request("").url).host; + const db = new CacheDB(); + + let tReq = request; + const urlStr = tReq.url.toString(); + const urlObj = new URL(urlStr); + const pathname = urlObj.pathname; + if (pathname.split("/")[1] === "cw-cgi") { + return router_cgi(request); + } + const config = await db.read("config", { + type: "json", + }); + if (!config) return fetch(request); + + let tFetched = false; + let EngineFetch = false; + let fetchConfig: + | ConfigType["catch_rules"][number]["transform_rules"][number]["fetch"] + | undefined = undefined; + let EngineFetchList: Request[] = []; + let tRes = new Response(); + for (let catch_rule of config.catch_rules) { + if (catch_rule.rule === "_") catch_rule.rule = domain; + if (!tReq.url.match(new RegExp(catch_rule.rule))) continue; + + for (let transform_rule of catch_rule.transform_rules) { + let tSearched = false; + + if (transform_rule.search === "_") + transform_rule.search = catch_rule.rule; + switch (transform_rule.searchin || "url") { + case "url": + if ( + tReq.url.match( + new RegExp(transform_rule.search, transform_rule.searchflags) + ) + ) + tSearched = true; + break; + case "header": + if ( + tReq.headers + .get(transform_rule.searchkey!)! + .match( + new RegExp(transform_rule.search, transform_rule.searchflags) + ) + ) + tSearched = true; + break; + case "status": + if (!tFetched) { + cons.w( + `${tReq.url} is not fetched yet,the status rule are ignored` + ); + break; + } + if ( + String(tRes.status).match( + new RegExp(transform_rule.search, transform_rule.searchflags) + ) + ) + tSearched = true; + break; + case "statusText": + if (!tFetched) { + cons.w( + `${tReq.url} is not fetched yet,the statusText rule are ignored` + ); + break; + } + if ( + tRes.statusText.match( + new RegExp(transform_rule.search, transform_rule.searchflags) + ) + ) + tSearched = true; + break; + case "body": + if (!tFetched) { + cons.w(`${tReq.url} is not fetched yet,the body rule are ignored`); + break; + } + if ( + (await tRes.clone().text()).match( + new RegExp(transform_rule.search, transform_rule.searchflags) + ) + ) + tSearched = true; + break; + default: + cons.e( + `${tReq.url} the ${transform_rule.searchin} search rule are not supported` + ); + break; + } + + switch (transform_rule.replacein || "url") { + case "url": + if (tFetched && tSearched) { + cons.w( + `${tReq.url} is already fetched,the url transform rule:${transform_rule.search} are ignored` + ); + break; + } + if (typeof transform_rule.replace !== "undefined" && tSearched) { + if (typeof transform_rule.replace === "string") { + if (EngineFetch) + cons.w( + `EngineFetch Disabled for ${tReq.url},the request will downgrade to normal fetch` + ); + tReq = rebuild.request(tReq, { + url: tReq.url.replace( + new RegExp( + transform_rule.replacekey || transform_rule.search, + transform_rule.replaceflags + ), + transform_rule.replace + ), + }); + EngineFetch = false; + } else { + if (EngineFetch) { + cons.w( + `Replacement cannot be used for ${tReq.url},the request is already powered by fetch-engine ` + ); + break; + } + transform_rule.replace.forEach((replacement) => { + if (replacement === "_") { + EngineFetchList.push(tReq); + return; + } + EngineFetchList.push( + rebuild.request(tReq, { + url: tReq.url.replace( + new RegExp( + transform_rule.replacekey || transform_rule.search, + transform_rule.replaceflags + ), + replacement + ), + }) + ); + }); + + EngineFetch = true; + } + } + break; + case "body": + if (tSearched) { + if (tFetched) { + tRes = rebuild.response(tRes, { + body: (await tRes.clone().text()).replace( + new RegExp( + transform_rule.replacekey || transform_rule.search, + transform_rule.replaceflags + ), + transform_rule.replace as string + ), + }); + } else { + tReq = rebuild.request(tReq, { + body: (await tReq.clone().text()).replace( + new RegExp( + transform_rule.replacekey || transform_rule.search, + transform_rule.replaceflags + ), + transform_rule.replace as string + ), + }); + } + } + break; + + case "status": + if (typeof transform_rule.replace === "string" && tSearched) + tRes = rebuild.response(tRes, { + status: Number( + tRes.status + .toString() + .replace( + new RegExp( + transform_rule.replacekey || transform_rule.search, + transform_rule.replaceflags + ), + transform_rule.replace + ) + ), + }); + break; + case "statusText": + if (typeof transform_rule.replace === "string" && tSearched) + tRes = rebuild.response(tRes, { + statusText: tRes.statusText.replace( + new RegExp( + transform_rule.replacekey || transform_rule.search, + transform_rule.replaceflags + ), + transform_rule.replace + ), + }); + break; + default: + cons.e( + `${tReq.url} the ${transform_rule.replacein} replace rule are not supported` + ); + } + if (!tSearched) continue; + if (typeof transform_rule.header === "object") { + Object.entries(transform_rule.header).forEach(([header, val]) => { + if (tFetched) { + tRes = rebuild.response(tRes, { + headers: { [header]: val }, + }); + } else { + tReq = rebuild.request(tReq, { + headers: { [header]: val }, + }); + } + }); + } + + if (typeof transform_rule.action !== "undefined") { + switch (transform_rule.action) { + case "skip": + return fetch(request); + case "fetch": + if (tFetched) { + cons.w( + `${tReq.url} is already fetched,the fetch action are ignored` + ); + break; + } + if (typeof transform_rule.fetch === "undefined") { + cons.e(`Fetch Config is not defined for ${tReq.url}`); + break; + } + + fetchConfig = { + status: transform_rule.fetch.status, + mode: transform_rule.fetch.mode as RequestMode | undefined, + credentials: transform_rule.fetch.credentials as + | RequestCredentials + | undefined, + redirect: transform_rule.fetch.redirect as + | RequestRedirect + | undefined, + timeout: transform_rule.fetch.timeout, + threads: transform_rule.fetch.threads, + trylimit: transform_rule.fetch.trylimit, + engine: transform_rule.fetch.engine, + preflight: transform_rule.fetch.preflight, + }; + if (!transform_rule.fetch.preflight) { + tReq = new Request(tReq.url, { + method: ((method) => { + if ( + method === "GET" || + method === "HEAD" || + method === "POST" + ) + return method; + return "GET"; + })(tReq.method), + body: ((body) => { + if (tReq.method === "POST") return body; + return null; + })(tReq.body), + }); //https://segmentfault.com/a/1190000006095018 + delete fetchConfig!.credentials; + //fetchConfig.mode = "cors" + for (var eReq in EngineFetchList) { + EngineFetchList[eReq] = new Request( + EngineFetchList[eReq].url, + tReq + ); + } + } + + tRes = await new Promise(async (res, rej) => { + const EngineFetcher = async (): Promise => { + let cRes: Response; + return new Promise(async (resolve, reject) => { + if (!EngineFetch) { + switch (transform_rule.fetch?.engine || "fetch") { + case "fetch": + cRes = await FetchEngine.fetch(tReq, fetchConfig!); + break; + case "crazy": + cRes = await FetchEngine.crazy(tReq, fetchConfig!); + break; + default: + cons.e( + `${tReq.url} the ${transform_rule.fetch?.engine} engine are not supported` + ); + break; + } + } else { + switch (transform_rule.fetch?.engine || "parallel") { + case "classic": + cRes = await FetchEngine.classic( + EngineFetchList, + fetchConfig! + ); + break; + case "parallel": + cRes = await FetchEngine.parallel( + EngineFetchList, + fetchConfig! + ); + break; + case "KFCThursdayVW50": + if (new Date().getDay() === 4) + cons.e( + `VW50! The Best Fetch Engine in the World Said!` + ); + cRes = await FetchEngine.KFCThursdayVW50( + EngineFetchList, + fetchConfig! + ); + break; + default: + cons.e( + `Fetch Engine ${transform_rule.fetch?.engine} is not supported` + ); + break; + } + } + if ( + typeof transform_rule.fetch?.cache === "object" && + cRes.status === (transform_rule.fetch?.status || 200) + ) { + cRes = rebuild.response(cRes, { + headers: { + ClientWorker_ExpireTime: ( + new Date().getTime() + + Number(eval(transform_rule.fetch.cache.expire || "0")) + ).toString(), + }, + }); + caches.open("ClientWorker_ResponseCache").then((cache) => { + cache.put(tReq, cRes.clone()).then(() => { + resolve(cRes); + }); + }); + } else { + resolve(cRes); + } + }); + }; + if (typeof transform_rule.fetch?.cache === "object") { + caches.open("ClientWorker_ResponseCache").then((cache) => { + cache.match(tReq).then((cRes) => { + if (!!cRes) { + if ( + Number(cRes.headers.get("ClientWorker_ExpireTime")) > + new Date().getTime() + ) { + cons.s(`${tReq.url} is fetched from cache`); + res(cRes); + return; + } else { + cons.w(`${tReq.url} is expired.`); + res( + Promise.any([ + EngineFetcher(), + new Promise(async (resolve, reject) => { + setTimeout(() => { + cons.e( + `${tReq.url} is too late to fetch,even though the cache has expired,so return by cache` + ); + resolve(cRes); + return; + }, transform_rule.fetch?.cache?.delay || 3000); + }), + ]) + ); + } + } else { + cons.w( + `${tReq.url} is not cached!And it is too late to fetch!` + ); + res(EngineFetcher()); + } + }); + }); + } else { + res(EngineFetcher()); + } + }); + tFetched = true; + break; + case "redirect": + if (typeof transform_rule.redirect === "undefined") { + cons.e(`Redirect Config is not defined for ${tReq.url}`); + break; + } + if (typeof transform_rule.redirect.url === "string") + return Response.redirect( + transform_rule.redirect.url, + transform_rule.redirect.status || 301 + ); + return Response.redirect( + tReq.url.replace( + new RegExp(transform_rule.search), + transform_rule.redirect.to! + ), + transform_rule.redirect.status || 301 + ); + case "return": + if (typeof transform_rule.return === "undefined") + transform_rule.return = { + body: "Error!", + status: 503, + header: { ServerProvide: "", "content-type": "text/plain" }, + }; + return new Response(transform_rule.return.body || "Error!", { + status: transform_rule.return.status || 503, + headers: transform_rule.return.header || {}, + }); + case "script": + if (typeof transform_rule.script === "undefined") { + cons.e(`Script Config is not defined for ${tReq.url}`); + break; + } + if (typeof transform_rule.script.function === "string") { + const ClientWorkerAnonymousFunctionName = `ClientWorker_AnonymousFunction_${new Date().getTime()}`; + // @ts-ignore + self[ClientWorkerAnonymousFunctionName] = eval( + transform_rule.script.function + ); + transform_rule.script.name = ClientWorkerAnonymousFunctionName; + } + const ScriptAns = await Function( + "return (" + transform_rule.script.name + ")" + )()({ + fetched: tFetched, + request: tReq, + response: tRes, + }); + + if (ScriptAns.fetched) { + if (transform_rule.script.skip || false) { + return ScriptAns.response; + } + tFetched = true; + tRes = ScriptAns.response; + } else { + tReq = ScriptAns.request; + } + break; + default: + cons.w(`This Action:${transform_rule.action} is not supported yet`); + break; + } + } + } + } + if (!tFetched) { + //3.0.0 默认改为skip + return fetch(request); + } + + return tRes; +}; +export default clientworkerhandle; diff --git a/main/utils/cons.js b/main/utils/cons.js deleted file mode 100644 index f65f11b..0000000 --- a/main/utils/cons.js +++ /dev/null @@ -1,18 +0,0 @@ -const cons = { - s: (m) => { - console.log(`%c[SUCCESS]%c ${m}`, 'color:white;background:green;', '') - }, - w: (m) => { - console.log(`%c[WARNING]%c ${m}`, 'color:brown;background:yellow;', '') - }, - i: (m) => { - console.log(`%c[INFO]%c ${m}`, 'color:white;background:blue;', '') - }, - e: (m) => { - console.log(`%c[ERROR]%c ${m}`, 'color:white;background:red;', '') - }, - d: (m) => { - console.log(`%c[DEBUG]%c ${m}`, 'color:white;background:black;', '') - } -} -export default cons \ No newline at end of file diff --git a/main/utils/cons.ts b/main/utils/cons.ts new file mode 100644 index 0000000..5a0b0b3 --- /dev/null +++ b/main/utils/cons.ts @@ -0,0 +1,18 @@ +const cons = { + s: (m: string) => { + console.log(`%c[SUCCESS]%c ${m}`, "color:white;background:green;", ""); + }, + w: (m: string) => { + console.log(`%c[WARNING]%c ${m}`, "color:brown;background:yellow;", ""); + }, + i: (m: string) => { + console.log(`%c[INFO]%c ${m}`, "color:white;background:blue;", ""); + }, + e: (m: string) => { + console.log(`%c[ERROR]%c ${m}`, "color:white;background:red;", ""); + }, + d: (m: string) => { + console.log(`%c[DEBUG]%c ${m}`, "color:white;background:black;", ""); + }, +}; +export default cons; diff --git a/main/utils/engine.js b/main/utils/engine.js deleted file mode 100644 index 7baa66b..0000000 --- a/main/utils/engine.js +++ /dev/null @@ -1,319 +0,0 @@ -import cons from './cons.js' -import rebuild from './rebuild.js' -if (!Promise.any) { - Promise.any = function (promises) { - return new Promise((resolve, reject) => { - promises = Array.isArray(promises) ? promises : [] - let len = promises.length - let errs = [] - if (len === 0) return reject(new AggregateError('All promises were rejected')) - promises.forEach((promise) => { - promise.then(value => { - resolve(value) - }, err => { - len-- - errs.push(err) - if (len === 0) { - reject(new AggregateError(errs)) - } - }) - }) - }) - } -} -const FetchEngine = { - fetch: async (req, config) => { - config = config || { status: 200 } - return new Promise((resolve, reject) => { - const reqtype = Object.prototype.toString.call(req) - if (reqtype !== '[object String]' && reqtype !== '[object Request]') { - reject(`FetchEngine.fetch: req must be a string or Request object,but got ${reqtype}`) - } - setTimeout(() => { - reject(new Response('504 All GateWays Failed,ClientWorker Show This Page,Engine Fetch', { status: 504, statusText: '504 All Gateways Timeout' })) - }, config.timeout || 5000); - fetch(req, { - mode: config.mode, - credentials: config.credential, - redirect: config.redirect || "follow" - }).then(res => { - resolve(res) - }).catch(err => { reject(err) }) - }) - }, - crazy: async (req, config) => { - config = config || { status: 200 } - config.threads = config.threads || 4 - config.trylimit = config.trylimit || 10 - const reqtype = Object.prototype.toString.call(req) - if (reqtype !== '[object String]' && reqtype !== '[object Request]') { - cons.e(`FetchEngine.fetch: req must be a string or Request object,but got ${reqtype}`) - return; - } - const controller = new AbortController(); - const PreFetch = await fetch(req, { - signal: controller.signal, - mode: config.mode, - credentials: config.credential, - redirect: config.redirect || "follow" - }) - const PreHeaders = PreFetch.headers - const AllSize = PreHeaders.get('Content-Length') - if (PreFetch.status.toString().match(config.status)) { - return (new Response('504 All GateWays Failed,ClientWorker Show This Page,Engine Crazy', { status: 504, statusText: '504 All Gateways Timeout' })) - } - controller.abort(); - if (!AllSize || AllSize < config.threads) { - cons.e(`FetchEngine.crazy: The Origin is not support Crazy Mode,or the size of the file is less than ${config.threads} bytes,downgrade to normal fetch`) - return FetchEngine.fetch(req, config) - } - return new Promise((resolve, reject) => { - const chunkSize = parseInt(AllSize / config.threads); - const chunks = []; - for (let i = 0; i < config.threads; i++) { - chunks.push( - new Promise(async (res, rej) => { - let trycount = 1 - const instance = async () => { - trycount += 1 - const nReq = rebuild.request(req, { - headers: { - Range: `bytes=${i * chunkSize}-${(i + 1) * chunkSize - 1}` - }, - url: req.url - }) - return fetch(nReq, { - mode: config.mode, - credentials: config.credential, - redirect: config.redirect || "follow" - }) - .then(res => res.arrayBuffer()) - .catch(err => { - if (trycount >= config.trylimit) { - reject() - return; - } - return instance() - }) - } - res(instance()); - }) - ) - } - Promise.all(chunks).then(responses => { - const resbodys = [] - for (let i = 0; i < responses.length; i++) { - resbodys.push(responses[i]); - } - resolve(new Response(new Blob(resbodys), { - headers: PreHeaders, - status: 200, - statusText: 'OK' - })); - - }) - setTimeout(() => { - reject(new Response('504 All GateWays Failed,ClientWorker Show This Page,Engine Crazy', { status: 504, statusText: '504 All Gateways Timeout' })) - }, config.timeout || 5000); - }) - }, - KFCThursdayVW50: async (reqs, config) => { - config = config || { status: 200 } - config.threads = config.threads || 4 - config.trylimit = config.trylimit || 10 - const reqtype = Object.prototype.toString.call(reqs) - if (reqtype === '[object String]' || reqtype === '[object Request]') { - cons.w(`FetchEngine.KFCThursdayVW50: reqs is a string or Request object,downgrade to crazy`) - return FetchEngine.crazy(reqs, config) - } else if (reqtype !== '[object Array]') { - cons.e(`FetchEngine.KFCThursdayVW50: reqs must be a string or Request object or an array,but got ${reqtype}`) - return Promise.reject(`FetchEngine.KFCThursdayVW50: reqs must be a string or Request object or an array,but got ${reqtype}`) - } else if (reqtype === '[object Array]') { - if (reqs.length === 0) { - cons.e(`FetchEngine.KFCThursdayVW50: reqs array is empty`) - reject() - } - if (reqs.length === 1) { - cons.w(`FetchEngine.KFCThursdayVW50: reqs array is only one,downgrade to crazy`) - return FetchEngine.crazy(reqs[0], config) - } - } - const controller = new AbortController(); - const PreFetch = await FetchEngine.parallel(reqs, { - signal: controller.signal, - mode: config.mode, - credentials: config.credential, - redirect: config.redirect || "follow", - timeout: config.timeout || 30000 - }) - - const PreHeaders = PreFetch.headers - const AllSize = PreHeaders.get('Content-Length') - if (PreFetch.status.toString().match(config.status)) { - reject(new Response('504 All GateWays Failed,ClientWorker Show This Page,Engine KFCThursdayVW50', { status: 504, statusText: '504 All Gateways Timeout' })) - } - controller.abort(); - if (!AllSize || AllSize < config.threads) { - cons.e(`FetchEngine.KFCThursdayVW50: The Origin is not support KFCThursdayVW50 Mode,or the size of the file is less than ${config.threads} bytes,downgrade to normal fetch`) - return FetchEngine.fetch(reqs, config) - } - return new Promise((resolve, reject) => { - const chunkSize = parseInt(AllSize / config.threads); - const chunks = []; - for (let i = 0; i < config.threads; i++) { - chunks.push( - new Promise(async (res, rej) => { - let trycount = 1 - const instance = async () => { - trycount += 1 - const nReqs = [] - reqs.forEach(req => { - nReqs.push(rebuild.request(req, { - headers: { - Range: `bytes=${i * chunkSize}-${(i + 1) * chunkSize - 1}` - }, - url: req.url - })) - }) - return FetchEngine.parallel(nReqs, { - mode: config.mode, - credentials: config.credential, - redirect: config.redirect || "follow", - timeout: config.timeout || 30000, - status: 206 - }) - .then(res => res.arrayBuffer()) - .catch(async err => { - cons.e(`FetchEngine.KFCThursdayVW50: ${await err.text()}`) - if (trycount >= config.trylimit) { - reject() - return; - } - return instance() - }) - } - res(instance()); - }) - ) - } - Promise.all(chunks).then(responses => { - const resbodys = [] - for (let i = 0; i < responses.length; i++) { - resbodys.push(responses[i]); - } - resolve(new Response(new Blob(resbodys), { - headers: PreHeaders, - status: 200, - statusText: 'OK' - })); - - }) - setTimeout(() => { - reject(new Response('504 All GateWays Failed,ClientWorker Show This Page,Engine KFCThursdayVW50', { status: 504, statusText: '504 All Gateways Timeout' })) - }, config.timeout || 30000); - }) - }, - classic: async (reqs, config) => { - return new Promise((resolve, reject) => { - config = config || { status: 200 } - const reqtype = Object.prototype.toString.call(reqs) - if (reqtype === '[object String]' || reqtype === '[object Request]') { - cons.w(`FetchEngine.classic: reqs should be an array,but got ${reqtype},this request will downgrade to normal fetch`) - resolve(FetchEngine.fetch(reqs, config)) - } else if (reqtype !== '[object Array]') { - cons.e(`FetchEngine.classic: reqs must be a string , Request or Array object,but got ${reqtype}`) - reject() - } else if (reqtype === '[object Array]') { - if (reqtype.length === 0) { - cons.e(`FetchEngine.classic: reqs array is empty`) - reject() - } - if (reqtype.length === 1) { - cons.w(`FetchEngine.classic: reqs array is only one element,this request will downgrade to normal fetch`) - resolve(FetchEngine.fetch(reqs[0], config)) - } - } - const controller = new AbortController(); - const PauseProgress = async (res) => { - return new Response(await (res).arrayBuffer(), { status: res.status, headers: res.headers, statusText: res.statusText }); - }; - - Promise.any(reqs.map(req => { - fetch(req, { - signal: controller.signal, - mode: config.mode, - credentials: config.credential, - redirect: config.redirect || "follow" - }) - .then(PauseProgress) - .then(res => { - if (res.status.toString().match(config.status)) { - controller.abort(); - resolve(res) - } - }).catch(err => { - if (err == 'DOMException: The user aborted a request.') console.log()//To disable the warning:DOMException: The user aborted a request. - }) - })) - - - setTimeout(() => { - reject(new Response('504 All GateWays Failed,ClientWorker Show This Page,Engine Classic', { status: 504, statusText: '504 All Gateways Timeout' })) - }, config.timeout || 5000); - - }) - }, - parallel: async (reqs, config) => { - return new Promise((resolve, reject) => { - config = config || { status: 200 } - const reqtype = Object.prototype.toString.call(reqs) - if (reqtype === '[object String]' || reqtype === '[object Request]') { - cons.w(`FetchEngine.parallel: reqs should be an array,but got ${reqtype},this request will downgrade to normal fetch`) - resolve(FetchEngine.fetch(reqs, config)) - } else if (reqtype !== '[object Array]') { - cons.e(`FetchEngine.parallel: reqs must be a string , Request or Array object,but got ${reqtype}`) - reject() - } else if (reqtype === '[object Array]') { - if (reqtype.length === 0) { - cons.e(`FetchEngine.parallel: reqs array is empty`) - reject() - } - if (reqtype.length === 1) { - cons.w(`FetchEngine.parallel: reqs array is only one element,this request will downgrade to normal fetch`) - resolve(FetchEngine.fetch(reqs[0], config)) - } - } - const abortEvent = new Event("abortOtherInstance") - const eventTarget = new EventTarget(); - Promise.any(reqs.map(async req => { - let controller = new AbortController(), tagged = false; - eventTarget.addEventListener(abortEvent.type, () => { - if (!tagged) controller.abort() - }) - fetch(req, { - signal: controller.signal, - mode: config.mode, - credentials: config.credential, - redirect: config.redirect || "follow" - }).then(res => { - if (res.status.toString().match(config.status)) { - tagged = true; - eventTarget.dispatchEvent(abortEvent) - resolve(rebuild.response(res,{})) - } - }).catch(err => { - if (err == 'DOMException: The user aborted a request.') console.log()//To disable the warning:DOMException: The user aborted a request. - }) - })) - - setTimeout(() => { - reject(new Response('504 All GateWays Failed,ClientWorker Show This Page,Engine Parallel', { status: 504, statusText: '504 All Gateways Timeout' })) - }, config.timeout || 5000); - - - - }) - } -} - -export default FetchEngine \ No newline at end of file diff --git a/main/utils/engine.ts b/main/utils/engine.ts new file mode 100644 index 0000000..62b2bed --- /dev/null +++ b/main/utils/engine.ts @@ -0,0 +1,439 @@ +import cons from "./cons"; +import rebuild from "./rebuild"; + +if (!Promise.any) { + Promise.any = function (promises: Promise[]) { + return new Promise((resolve, reject) => { + promises = Array.isArray(promises) ? promises : []; + let len = promises.length; + let errs: unknown[] = []; + if (len === 0) + return reject(new AggregateError("All promises were rejected")); + promises.forEach((promise) => { + promise.then( + (value) => { + resolve(value); + }, + (err) => { + len--; + errs.push(err); + if (len === 0) { + reject(new AggregateError(errs)); + } + } + ); + }); + }); + }; +} + +export type FetchEngineConfig = Partial<{ + mode: RequestMode; + credentials: RequestCredentials; + timeout: number; + redirect: RequestRedirect; + threads: number; + trylimit: number; + status: number; + signal: AbortSignal; +}>; + +type FetchEngineFunction = ( + req: Request, + config: FetchEngineConfig +) => Promise; + +const FetchEngine: Record<"fetch" | "crazy", FetchEngineFunction> & + Record< + "KFCThursdayVW50" | "parallel" | "classic", + (reqs: Request[] | Request, config: FetchEngineConfig) => Promise + > = { + fetch: async (req, config) => { + config = config || { status: 200 }; + return new Promise((resolve, reject) => { + const reqtype = Object.prototype.toString.call(req); + if (reqtype !== "[object String]" && reqtype !== "[object Request]") { + reject( + `FetchEngine.fetch: req must be a string or Request object,but got ${reqtype}` + ); + } + setTimeout(() => { + reject( + new Response( + "504 All GateWays Failed,ClientWorker Show This Page,Engine Fetch", + { status: 504, statusText: "504 All Gateways Timeout" } + ) + ); + }, config.timeout || 5000); + fetch(req, { + mode: config.mode, + credentials: config.credentials, + redirect: config.redirect || "follow", + }) + .then((res) => { + resolve(res); + }) + .catch((err) => { + reject(err); + }); + }); + }, + + crazy: async (req, config) => { + config = config || { status: 200 }; + config.threads = config.threads || 4; + config.trylimit = config.trylimit || 10; + const reqtype = Object.prototype.toString.call(req); + if (reqtype !== "[object String]" && reqtype !== "[object Request]") { + cons.e( + `FetchEngine.fetch: req must be a string or Request object,but got ${reqtype}` + ); + throw new Error( + `FetchEngine.fetch: req must be a string or Request object,but got ${reqtype}` + ); + } + const controller = new AbortController(); + const PreFetch = await fetch(req, { + signal: controller.signal, + mode: config.mode, + credentials: config.credentials, + redirect: config.redirect || "follow", + }); + const PreHeaders = PreFetch.headers; + const AllSize = PreHeaders.get("Content-Length"); + if ( + config.status && + PreFetch.status.toString().match(config.status.toString()) + ) { + return new Response( + "504 All GateWays Failed,ClientWorker Show This Page,Engine Crazy", + { status: 504, statusText: "504 All Gateways Timeout" } + ); + } + controller.abort(); + if (!AllSize || Number(AllSize) < config.threads) { + cons.e( + `FetchEngine.crazy: The Origin is not support Crazy Mode,or the size of the file is less than ${config.threads} bytes,downgrade to normal fetch` + ); + return FetchEngine.fetch(req, config); + } + return new Promise((resolve, reject) => { + const chunkSize = Math.floor(Number(AllSize) / config.threads!); + const chunks: Promise[] = []; + for (let i = 0; i < config.threads!; i++) { + chunks.push( + new Promise(async (res, rej) => { + let trycount = 1; + const instance = async (): Promise => { + trycount += 1; + const nReq = rebuild.request(req, { + headers: { + Range: `bytes=${i * chunkSize}-${(i + 1) * chunkSize - 1}`, + }, + url: req.url, + }); + return fetch(nReq, { + mode: config.mode, + credentials: config.credentials, + redirect: config.redirect || "follow", + }) + .then((res) => res.arrayBuffer()) + .catch((err) => { + if (trycount >= config.trylimit!) { + reject(); + } + return instance(); + }); + }; + res(instance()); + }) + ); + } + Promise.all(chunks).then((responses) => { + const resbodys: BlobPart[] = []; + for (let i = 0; i < responses.length; i++) { + resbodys.push(responses[i]!); + } + resolve( + new Response(new Blob(resbodys), { + headers: PreHeaders, + status: 200, + statusText: "OK", + }) + ); + }); + setTimeout(() => { + reject( + new Response( + "504 All GateWays Failed,ClientWorker Show This Page,Engine Crazy", + { status: 504, statusText: "504 All Gateways Timeout" } + ) + ); + }, config.timeout || 5000); + }); + }, + + KFCThursdayVW50: async (reqs, config) => { + config = config || { status: 200 }; + config.threads = config.threads || 4; + config.trylimit = config.trylimit || 10; + const reqtype = Object.prototype.toString.call(reqs); + if (reqtype === "[object String]" || reqtype === "[object Request]") { + cons.w( + `FetchEngine.KFCThursdayVW50: reqs is a string or Request object,downgrade to crazy` + ); + return FetchEngine.crazy(reqs as Request, config); + } else if (reqtype !== "[object Array]") { + cons.e( + `FetchEngine.KFCThursdayVW50: reqs must be a string or Request object or an array,but got ${reqtype}` + ); + return Promise.reject( + `FetchEngine.KFCThursdayVW50: reqs must be a string or Request object or an array,but got ${reqtype}` + ); + } else if (reqtype === "[object Array]") { + if ((reqs as Request[]).length === 0) { + cons.e(`FetchEngine.KFCThursdayVW50: reqs array is empty`); + throw new Error("FetchEngine.KFCThursdayVW50: reqs array is empty"); + } + if ((reqs as Request[]).length === 1) { + cons.w( + `FetchEngine.KFCThursdayVW50: reqs array is only one,downgrade to crazy` + ); + return FetchEngine.crazy((reqs as Request[])[0], config); + } + } + const controller = new AbortController(); + const PreFetch = await FetchEngine.parallel(reqs, { + signal: controller.signal, + mode: config.mode, + credentials: config.credentials, + redirect: config.redirect || "follow", + timeout: config.timeout || 30000, + }); + + const PreHeaders = PreFetch.headers; + const AllSize = PreHeaders.get("Content-Length"); + if ( + config.status && + PreFetch.status.toString().match(config.status.toString()) + ) { + return Promise.reject( + new Response( + "504 All GateWays Failed,ClientWorker Show This Page,Engine KFCThursdayVW50", + { status: 504, statusText: "504 All Gateways Timeout" } + ) + ); + } + controller.abort(); + if (!AllSize || Number(AllSize) < config.threads) { + cons.e( + `FetchEngine.KFCThursdayVW50: The Origin is not support KFCThursdayVW50 Mode,or the size of the file is less than ${config.threads} bytes,downgrade to normal fetch` + ); + return FetchEngine.fetch(reqs as Request, config); + } + return new Promise((resolve, reject) => { + const chunkSize = Math.floor(Number(AllSize) / config.threads!); + const chunks: Promise[] = []; + for (let i = 0; i < config.threads!; i++) { + chunks.push( + new Promise(async (res, rej) => { + let trycount = 1; + const instance = async (): Promise => { + trycount += 1; + const nReqs: Request[] = []; + (reqs as Request[]).forEach((req) => { + nReqs.push( + rebuild.request(req, { + headers: { + Range: `bytes=${i * chunkSize}-${ + (i + 1) * chunkSize - 1 + }`, + }, + url: req.url, + }) + ); + }); + return FetchEngine.parallel(nReqs, { + mode: config.mode, + credentials: config.credentials, + redirect: config.redirect || "follow", + timeout: config.timeout || 30000, + status: 206, + }) + .then((res) => res.arrayBuffer()) + .catch(async (err) => { + cons.e(`FetchEngine.KFCThursdayVW50: ${await err.text()}`); + if (trycount >= config.trylimit!) { + reject(); + } + return instance(); + }); + }; + res(instance()); + }) + ); + } + Promise.all(chunks).then((responses) => { + const resbodys: BlobPart[] = []; + for (let i = 0; i < responses.length; i++) { + resbodys.push(responses[i]); + } + resolve( + new Response(new Blob(resbodys), { + headers: PreHeaders, + status: 200, + statusText: "OK", + }) + ); + }); + setTimeout(() => { + reject( + new Response( + "504 All GateWays Failed,ClientWorker Show This Page,Engine KFCThursdayVW50", + { status: 504, statusText: "504 All Gateways Timeout" } + ) + ); + }, config.timeout || 30000); + }); + }, + classic: async (reqs, config) => { + return new Promise((resolve, reject) => { + config = config || { status: 200 }; + const reqtype = Object.prototype.toString.call(reqs); + if (reqtype === "[object String]" || reqtype === "[object Request]") { + cons.w( + `FetchEngine.classic: reqs should be an array,but got ${reqtype},this request will downgrade to normal fetch` + ); + resolve(FetchEngine.fetch(reqs as Request, config)); + } else if (reqtype !== "[object Array]") { + cons.e( + `FetchEngine.classic: reqs must be a string , Request or Array object,but got ${reqtype}` + ); + reject(); + } else if (reqtype === "[object Array]") { + if (reqtype.length === 0) { + cons.e(`FetchEngine.classic: reqs array is empty`); + reject(); + } + if (reqtype.length === 1) { + cons.w( + `FetchEngine.classic: reqs array is only one element,this request will downgrade to normal fetch` + ); + resolve(FetchEngine.fetch((reqs as Request[])[0], config)); + } + } + const controller = new AbortController(); + const PauseProgress = async (res: Response) => { + return new Response(await res.arrayBuffer(), { + status: res.status, + headers: res.headers, + statusText: res.statusText, + }); + }; + + Promise.any( + (reqs as Request[]).map((req) => { + fetch(req, { + signal: controller.signal, + mode: config.mode, + credentials: config.credentials, + redirect: config.redirect || "follow", + }) + .then(PauseProgress) + .then((res) => { + if ( + config.status && + res.status.toString().match(config.status.toString()) + ) { + controller.abort(); + resolve(res); + } + }) + .catch((err) => { + if (err == "DOMException: The user aborted a request.") + console.log(); //To disable the warning:DOMException: The user aborted a request. + }); + }) + ); + + setTimeout(() => { + reject( + new Response( + "504 All GateWays Failed,ClientWorker Show This Page,Engine Classic", + { status: 504, statusText: "504 All Gateways Timeout" } + ) + ); + }, config.timeout || 5000); + }); + }, + parallel: async (reqs, config) => { + return new Promise((resolve, reject) => { + config = config || { status: 200 }; + const reqtype = Object.prototype.toString.call(reqs); + if (reqtype === "[object String]" || reqtype === "[object Request]") { + cons.w( + `FetchEngine.parallel: reqs should be an array,but got ${reqtype},this request will downgrade to normal fetch` + ); + resolve(FetchEngine.fetch(reqs as Request, config)); + } else if (reqtype !== "[object Array]") { + cons.e( + `FetchEngine.parallel: reqs must be a string , Request or Array object,but got ${reqtype}` + ); + reject(); + } else if (reqtype === "[object Array]") { + if (reqtype.length === 0) { + cons.e(`FetchEngine.parallel: reqs array is empty`); + reject(); + } + if (reqtype.length === 1) { + cons.w( + `FetchEngine.parallel: reqs array is only one element,this request will downgrade to normal fetch` + ); + resolve(FetchEngine.fetch((reqs as Request[])[0], config)); + } + } + const abortEvent = new Event("abortOtherInstance"); + const eventTarget = new EventTarget(); + Promise.any( + (reqs as Request[]).map(async (req) => { + let controller = new AbortController(), + tagged = false; + eventTarget.addEventListener(abortEvent.type, () => { + if (!tagged) controller.abort(); + }); + fetch(req, { + signal: controller.signal, + mode: config.mode, + credentials: config.credentials, + redirect: config.redirect || "follow", + }) + .then((res) => { + if ( + config.status && + res.status.toString().match(config.status.toString()) + ) { + tagged = true; + eventTarget.dispatchEvent(abortEvent); + resolve(rebuild.response(res, {})); + } + }) + .catch((err) => { + if (err == "DOMException: The user aborted a request.") + console.log(); //To disable the warning:DOMException: The user aborted a request. + }); + }) + ); + + setTimeout(() => { + reject( + new Response( + "504 All GateWays Failed,ClientWorker Show This Page,Engine Parallel", + { status: 504, statusText: "504 All Gateways Timeout" } + ) + ); + }, config.timeout || 5000); + }); + }, +}; + +export default FetchEngine; diff --git a/main/utils/rebuild.js b/main/utils/rebuild.js deleted file mode 100644 index c44cafd..0000000 --- a/main/utils/rebuild.js +++ /dev/null @@ -1,46 +0,0 @@ -import cons from "./cons.js" -const rebuild = { - request:(req, init) => { - req = req.clone() - if (req.mode === 'navigate') { - cons.w(`You can't rebuild a POST method with body when it is a navigate request.ClientWorker will ignore it's body`) - } - let nReq = new Request(req, { - headers: rebuildheaders(req, init.headers), - method: init.method || req.method, - mode: req.mode === 'navigate' ? "same-origin" : (init.mode || req.mode), - credentials: init.credentials || req.credentials, - redirect: init.redirect || req.redirect - }) - if (!!init.url) nReq = new Request(init.url, nReq) - return nReq - }, - response:(res, init) => { - if(res.type === 'opaque') { - cons.e(`You can't rebuild a opaque response.ClientWorker will ignore this build`) - return res - } - let nRes = new Response(res.body, { - headers: rebuildheaders(res, init.headers), - status: init.status || res.status, - statusText: init.statusText || res.statusText - }) - return nRes - } -} - -const rebuildheaders = (re, headers) => { - if (!!headers) { - const nHeaders = new Headers(re.headers) - for (let key in headers) { - if (headers[key] !== undefined) { - nHeaders.set(key, headers[key]) - } else { - nHeaders.delete(key) - } - } - return nHeaders - } - return new Headers(re.headers) -} -export default rebuild \ No newline at end of file diff --git a/main/utils/rebuild.ts b/main/utils/rebuild.ts new file mode 100644 index 0000000..7685e4e --- /dev/null +++ b/main/utils/rebuild.ts @@ -0,0 +1,53 @@ +import cons from "./cons"; + +const rebuild = { + request: (req: Request, init: Partial) => { + req = req.clone(); + if (req.mode === "navigate") { + cons.w( + `You can't rebuild a POST method with body when it is a navigate request.ClientWorker will ignore it's body` + ); + } + let nReq = new Request(req, { + headers: rebuildheaders(req, init.headers), + method: init.method || req.method, + mode: req.mode === "navigate" ? "same-origin" : init.mode || req.mode, + credentials: init.credentials || req.credentials, + redirect: init.redirect || req.redirect, + }); + if (!!init.url) nReq = new Request(init.url, nReq); + return nReq; + }, + response: (res: Response, init: Partial) => { + if (res.type === "opaque") { + cons.e( + `You can't rebuild a opaque response.ClientWorker will ignore this build` + ); + return res; + } + let nRes = new Response(res.body, { + headers: rebuildheaders(res, init.headers), + status: init.status || res.status, + statusText: init.statusText || res.statusText, + }); + return nRes; + }, +}; + +const rebuildheaders = ( + re: Request | Response, + headers: HeadersInit | undefined +) => { + if (!!headers) { + const nHeaders = new Headers(re.headers); + Object.entries(headers).forEach(([key, value]) => { + if (!value) { + nHeaders.delete(key); + } else nHeaders.set(key, value); + }); + return nHeaders; + } + return new Headers(re.headers); +}; + +export default rebuild; diff --git a/package.json b/package.json index acc6cdc..cd5b5f8 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "A simple, globally hijacked, easily customizable, Service Worker-based front-end worker", "main": "./dist/cw.js", "scripts": { - "build": "webpack ./main/entry.js -c ./cw.config.js && cp -r static/* dist " + "build": "rimraf dist && node --loader esbuild-register/loader -r esbuild-register build.ts", + "docs:build": "pnpm --filter clientworker-doc docs:build" }, "type": "module", "repository": { @@ -23,12 +24,17 @@ "homepage": "https://github.com/ChenYFan/ClientWorker#readme", "dependencies": { "@chenyfan/cache-db": "^0.0.4", - "js-yaml": "^4.1.0", - "vitepress": "^1.0.0-alpha.4", - "vue": "^3.2.37" + "js-yaml": "^4.1.0" }, "devDependencies": { + "@types/js-yaml": "^4.0.5", + "@types/node": "^18.11.9", + "esbuild": "^0.15.14", + "esbuild-copy-static-files": "^0.1.0", + "esbuild-register": "^3.4.1", + "json-schema-to-ts": "^2.6.1", + "rimraf": "^3.0.2", "terser-webpack-plugin": "^5.3.3", - "webpack-bundle-analyzer": "^4.5.0" + "typescript": "^4.9.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..24cd2ee --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1128 @@ +lockfileVersion: 5.4 + +importers: + + .: + specifiers: + '@chenyfan/cache-db': ^0.0.4 + '@types/js-yaml': ^4.0.5 + '@types/node': ^18.11.9 + esbuild: ^0.15.14 + esbuild-copy-static-files: ^0.1.0 + esbuild-register: ^3.4.1 + js-yaml: ^4.1.0 + json-schema-to-ts: ^2.6.1 + rimraf: ^3.0.2 + terser-webpack-plugin: ^5.3.3 + typescript: ^4.9.3 + dependencies: + '@chenyfan/cache-db': 0.0.4 + js-yaml: 4.1.0 + devDependencies: + '@types/js-yaml': 4.0.5 + '@types/node': 18.11.9 + esbuild: 0.15.14 + esbuild-copy-static-files: 0.1.0 + esbuild-register: 3.4.1_esbuild@0.15.14 + json-schema-to-ts: 2.6.1 + rimraf: 3.0.2 + terser-webpack-plugin: 5.3.6_esbuild@0.15.14 + typescript: 4.9.3 + + doc: + specifiers: + vitepress: ^1.0.0-alpha.4 + vue: ^3.2.37 + dependencies: + vitepress: 1.0.0-alpha.29 + vue: 3.2.45 + +packages: + + /@algolia/autocomplete-core/1.7.2: + resolution: {integrity: sha512-eclwUDC6qfApNnEfu1uWcL/rudQsn59tjEoUYZYE2JSXZrHLRjBUGMxiCoknobU2Pva8ejb0eRxpIYDtVVqdsw==} + dependencies: + '@algolia/autocomplete-shared': 1.7.2 + dev: false + + /@algolia/autocomplete-preset-algolia/1.7.2_algoliasearch@4.14.2: + resolution: {integrity: sha512-+RYEG6B0QiGGfRb2G3MtPfyrl0dALF3cQNTWBzBX6p5o01vCCGTTinAm2UKG3tfc2CnOMAtnPLkzNZyJUpnVJw==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + dependencies: + '@algolia/autocomplete-shared': 1.7.2 + algoliasearch: 4.14.2 + dev: false + + /@algolia/autocomplete-shared/1.7.2: + resolution: {integrity: sha512-QCckjiC7xXHIUaIL3ektBtjJ0w7tTA3iqKcAE/Hjn1lZ5omp7i3Y4e09rAr9ZybqirL7AbxCLLq0Ra5DDPKeug==} + dev: false + + /@algolia/cache-browser-local-storage/4.14.2: + resolution: {integrity: sha512-FRweBkK/ywO+GKYfAWbrepewQsPTIEirhi1BdykX9mxvBPtGNKccYAxvGdDCumU1jL4r3cayio4psfzKMejBlA==} + dependencies: + '@algolia/cache-common': 4.14.2 + dev: false + + /@algolia/cache-common/4.14.2: + resolution: {integrity: sha512-SbvAlG9VqNanCErr44q6lEKD2qoK4XtFNx9Qn8FK26ePCI8I9yU7pYB+eM/cZdS9SzQCRJBbHUumVr4bsQ4uxg==} + dev: false + + /@algolia/cache-in-memory/4.14.2: + resolution: {integrity: sha512-HrOukWoop9XB/VFojPv1R5SVXowgI56T9pmezd/djh2JnVN/vXswhXV51RKy4nCpqxyHt/aGFSq2qkDvj6KiuQ==} + dependencies: + '@algolia/cache-common': 4.14.2 + dev: false + + /@algolia/client-account/4.14.2: + resolution: {integrity: sha512-WHtriQqGyibbb/Rx71YY43T0cXqyelEU0lB2QMBRXvD2X0iyeGl4qMxocgEIcbHyK7uqE7hKgjT8aBrHqhgc1w==} + dependencies: + '@algolia/client-common': 4.14.2 + '@algolia/client-search': 4.14.2 + '@algolia/transporter': 4.14.2 + dev: false + + /@algolia/client-analytics/4.14.2: + resolution: {integrity: sha512-yBvBv2mw+HX5a+aeR0dkvUbFZsiC4FKSnfqk9rrfX+QrlNOKEhCG0tJzjiOggRW4EcNqRmaTULIYvIzQVL2KYQ==} + dependencies: + '@algolia/client-common': 4.14.2 + '@algolia/client-search': 4.14.2 + '@algolia/requester-common': 4.14.2 + '@algolia/transporter': 4.14.2 + dev: false + + /@algolia/client-common/4.14.2: + resolution: {integrity: sha512-43o4fslNLcktgtDMVaT5XwlzsDPzlqvqesRi4MjQz2x4/Sxm7zYg5LRYFol1BIhG6EwxKvSUq8HcC/KxJu3J0Q==} + dependencies: + '@algolia/requester-common': 4.14.2 + '@algolia/transporter': 4.14.2 + dev: false + + /@algolia/client-personalization/4.14.2: + resolution: {integrity: sha512-ACCoLi0cL8CBZ1W/2juehSltrw2iqsQBnfiu/Rbl9W2yE6o2ZUb97+sqN/jBqYNQBS+o0ekTMKNkQjHHAcEXNw==} + dependencies: + '@algolia/client-common': 4.14.2 + '@algolia/requester-common': 4.14.2 + '@algolia/transporter': 4.14.2 + dev: false + + /@algolia/client-search/4.14.2: + resolution: {integrity: sha512-L5zScdOmcZ6NGiVbLKTvP02UbxZ0njd5Vq9nJAmPFtjffUSOGEp11BmD2oMJ5QvARgx2XbX4KzTTNS5ECYIMWw==} + dependencies: + '@algolia/client-common': 4.14.2 + '@algolia/requester-common': 4.14.2 + '@algolia/transporter': 4.14.2 + dev: false + + /@algolia/logger-common/4.14.2: + resolution: {integrity: sha512-/JGlYvdV++IcMHBnVFsqEisTiOeEr6cUJtpjz8zc0A9c31JrtLm318Njc72p14Pnkw3A/5lHHh+QxpJ6WFTmsA==} + dev: false + + /@algolia/logger-console/4.14.2: + resolution: {integrity: sha512-8S2PlpdshbkwlLCSAB5f8c91xyc84VM9Ar9EdfE9UmX+NrKNYnWR1maXXVDQQoto07G1Ol/tYFnFVhUZq0xV/g==} + dependencies: + '@algolia/logger-common': 4.14.2 + dev: false + + /@algolia/requester-browser-xhr/4.14.2: + resolution: {integrity: sha512-CEh//xYz/WfxHFh7pcMjQNWgpl4wFB85lUMRyVwaDPibNzQRVcV33YS+63fShFWc2+42YEipFGH2iPzlpszmDw==} + dependencies: + '@algolia/requester-common': 4.14.2 + dev: false + + /@algolia/requester-common/4.14.2: + resolution: {integrity: sha512-73YQsBOKa5fvVV3My7iZHu1sUqmjjfs9TteFWwPwDmnad7T0VTCopttcsM3OjLxZFtBnX61Xxl2T2gmG2O4ehg==} + dev: false + + /@algolia/requester-node-http/4.14.2: + resolution: {integrity: sha512-oDbb02kd1o5GTEld4pETlPZLY0e+gOSWjWMJHWTgDXbv9rm/o2cF7japO6Vj1ENnrqWvLBmW1OzV9g6FUFhFXg==} + dependencies: + '@algolia/requester-common': 4.14.2 + dev: false + + /@algolia/transporter/4.14.2: + resolution: {integrity: sha512-t89dfQb2T9MFQHidjHcfhh6iGMNwvuKUvojAj+JsrHAGbuSy7yE4BylhLX6R0Q1xYRoC4Vvv+O5qIw/LdnQfsQ==} + dependencies: + '@algolia/cache-common': 4.14.2 + '@algolia/logger-common': 4.14.2 + '@algolia/requester-common': 4.14.2 + dev: false + + /@babel/helper-string-parser/7.19.4: + resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-validator-identifier/7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/parser/7.20.3: + resolution: {integrity: sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.20.2 + dev: false + + /@babel/runtime/7.20.1: + resolution: {integrity: sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + dev: true + + /@babel/types/7.20.2: + resolution: {integrity: sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.19.4 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 + dev: false + + /@chenyfan/cache-db/0.0.4: + resolution: {integrity: sha512-kDOxRQ68OlrbQhXehHxc6BjTvr0sVXhCV45uucN5Jk6qKahEtRguA/orTh43ZAwnZ34LEdyj62kRXik8vq+fNg==} + dev: false + + /@docsearch/css/3.3.0: + resolution: {integrity: sha512-rODCdDtGyudLj+Va8b6w6Y85KE85bXRsps/R4Yjwt5vueXKXZQKYw0aA9knxLBT6a/bI/GMrAcmCR75KYOM6hg==} + dev: false + + /@docsearch/js/3.3.0: + resolution: {integrity: sha512-oFXWRPNvPxAzBhnFJ9UCFIYZiQNc3Yrv6912nZHw/UIGxsyzKpNRZgHq8HDk1niYmOSoLKtVFcxkccpQmYGFyg==} + dependencies: + '@docsearch/react': 3.3.0 + preact: 10.11.3 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + dev: false + + /@docsearch/react/3.3.0: + resolution: {integrity: sha512-fhS5adZkae2SSdMYEMVg6pxI5a/cE+tW16ki1V0/ur4Fdok3hBRkmN/H8VvlXnxzggkQIIRIVvYPn00JPjen3A==} + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + dependencies: + '@algolia/autocomplete-core': 1.7.2 + '@algolia/autocomplete-preset-algolia': 1.7.2_algoliasearch@4.14.2 + '@docsearch/css': 3.3.0 + algoliasearch: 4.14.2 + transitivePeerDependencies: + - '@algolia/client-search' + dev: false + + /@esbuild/android-arm/0.15.14: + resolution: {integrity: sha512-+Rb20XXxRGisNu2WmNKk+scpanb7nL5yhuI1KR9wQFiC43ddPj/V1fmNyzlFC9bKiG4mYzxW7egtoHVcynr+OA==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/linux-loong64/0.15.14: + resolution: {integrity: sha512-eQi9rosGNVQFJyJWV0HCA5WZae/qWIQME7s8/j8DMvnylfBv62Pbu+zJ2eUDqNf2O4u3WB+OEXyfkpBoe194sg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + + /@jridgewell/gen-mapping/0.3.2: + resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/trace-mapping': 0.3.17 + dev: true + + /@jridgewell/resolve-uri/3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array/1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/source-map/0.3.2: + resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==} + dependencies: + '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.17 + dev: true + + /@jridgewell/sourcemap-codec/1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + dev: true + + /@jridgewell/trace-mapping/0.3.17: + resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + + /@types/js-yaml/4.0.5: + resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==} + dev: true + + /@types/json-schema/7.0.11: + resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + dev: true + + /@types/node/18.11.9: + resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==} + dev: true + + /@types/web-bluetooth/0.0.16: + resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==} + dev: false + + /@vitejs/plugin-vue/3.2.0_vite@3.2.4+vue@3.2.45: + resolution: {integrity: sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^3.0.0 + vue: ^3.2.25 + dependencies: + vite: 3.2.4 + vue: 3.2.45 + dev: false + + /@vue/compiler-core/3.2.45: + resolution: {integrity: sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==} + dependencies: + '@babel/parser': 7.20.3 + '@vue/shared': 3.2.45 + estree-walker: 2.0.2 + source-map: 0.6.1 + dev: false + + /@vue/compiler-dom/3.2.45: + resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==} + dependencies: + '@vue/compiler-core': 3.2.45 + '@vue/shared': 3.2.45 + dev: false + + /@vue/compiler-sfc/3.2.45: + resolution: {integrity: sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==} + dependencies: + '@babel/parser': 7.20.3 + '@vue/compiler-core': 3.2.45 + '@vue/compiler-dom': 3.2.45 + '@vue/compiler-ssr': 3.2.45 + '@vue/reactivity-transform': 3.2.45 + '@vue/shared': 3.2.45 + estree-walker: 2.0.2 + magic-string: 0.25.9 + postcss: 8.4.19 + source-map: 0.6.1 + dev: false + + /@vue/compiler-ssr/3.2.45: + resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==} + dependencies: + '@vue/compiler-dom': 3.2.45 + '@vue/shared': 3.2.45 + dev: false + + /@vue/devtools-api/6.4.5: + resolution: {integrity: sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==} + dev: false + + /@vue/reactivity-transform/3.2.45: + resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==} + dependencies: + '@babel/parser': 7.20.3 + '@vue/compiler-core': 3.2.45 + '@vue/shared': 3.2.45 + estree-walker: 2.0.2 + magic-string: 0.25.9 + dev: false + + /@vue/reactivity/3.2.45: + resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==} + dependencies: + '@vue/shared': 3.2.45 + dev: false + + /@vue/runtime-core/3.2.45: + resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==} + dependencies: + '@vue/reactivity': 3.2.45 + '@vue/shared': 3.2.45 + dev: false + + /@vue/runtime-dom/3.2.45: + resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==} + dependencies: + '@vue/runtime-core': 3.2.45 + '@vue/shared': 3.2.45 + csstype: 2.6.21 + dev: false + + /@vue/server-renderer/3.2.45_vue@3.2.45: + resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==} + peerDependencies: + vue: 3.2.45 + dependencies: + '@vue/compiler-ssr': 3.2.45 + '@vue/shared': 3.2.45 + vue: 3.2.45 + dev: false + + /@vue/shared/3.2.45: + resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==} + dev: false + + /@vueuse/core/9.5.0_vue@3.2.45: + resolution: {integrity: sha512-6GsWBsJHEb3sYw15mbLrcbslAVY45pkzjJYTKYKCXv88z7srAF0VEW0q+oXKsl58tCbqooplInahXFg8Yo1m4w==} + dependencies: + '@types/web-bluetooth': 0.0.16 + '@vueuse/metadata': 9.5.0 + '@vueuse/shared': 9.5.0_vue@3.2.45 + vue-demi: 0.13.11_vue@3.2.45 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + + /@vueuse/metadata/9.5.0: + resolution: {integrity: sha512-4M1AyPZmIv41pym+K5+4wup3bKuYebbH8w8BROY1hmT7rIwcyS4tEL+UsGz0Hiu1FCOxcoBrwtAizc0YmBJjyQ==} + dev: false + + /@vueuse/shared/9.5.0_vue@3.2.45: + resolution: {integrity: sha512-HnnCWU1Vg9CVWRCcI8ohDKDRB2Sc4bTgT1XAIaoLSfVHHn+TKbrox6pd3klCSw4UDxkhDfOk8cAdcK+Z5KleCA==} + dependencies: + vue-demi: 0.13.11_vue@3.2.45 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + + /acorn/8.8.1: + resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv-keywords/3.5.2_ajv@6.12.6: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + dependencies: + ajv: 6.12.6 + dev: true + + /ajv/6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /algoliasearch/4.14.2: + resolution: {integrity: sha512-ngbEQonGEmf8dyEh5f+uOIihv4176dgbuOZspiuhmTTBRBuzWu3KCGHre6uHj5YyuC7pNvQGzB6ZNJyZi0z+Sg==} + dependencies: + '@algolia/cache-browser-local-storage': 4.14.2 + '@algolia/cache-common': 4.14.2 + '@algolia/cache-in-memory': 4.14.2 + '@algolia/client-account': 4.14.2 + '@algolia/client-analytics': 4.14.2 + '@algolia/client-common': 4.14.2 + '@algolia/client-personalization': 4.14.2 + '@algolia/client-search': 4.14.2 + '@algolia/logger-common': 4.14.2 + '@algolia/logger-console': 4.14.2 + '@algolia/requester-browser-xhr': 4.14.2 + '@algolia/requester-common': 4.14.2 + '@algolia/requester-node-http': 4.14.2 + '@algolia/transporter': 4.14.2 + dev: false + + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: false + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /body-scroll-lock/4.0.0-beta.0: + resolution: {integrity: sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==} + dev: false + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /buffer-from/1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /commander/2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + dev: true + + /concat-map/0.0.1: + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + dev: true + + /csstype/2.6.21: + resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} + dev: false + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /esbuild-android-64/0.15.14: + resolution: {integrity: sha512-HuilVIb4rk9abT4U6bcFdU35UHOzcWVGLSjEmC58OVr96q5UiRqzDtWjPlCMugjhgUGKEs8Zf4ueIvYbOStbIg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + optional: true + + /esbuild-android-arm64/0.15.14: + resolution: {integrity: sha512-/QnxRVxsR2Vtf3XottAHj7hENAMW2wCs6S+OZcAbc/8nlhbAL/bCQRCVD78VtI5mdwqWkVi3wMqM94kScQCgqg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /esbuild-copy-static-files/0.1.0: + resolution: {integrity: sha512-KlpmYqANA1t2nZavEdItfcOjJC6wbHA21v35HJWN32DddGTWKNNGDKljUzbCPojmpD+wAw8/DXr5abJ4jFCE0w==} + dev: true + + /esbuild-darwin-64/0.15.14: + resolution: {integrity: sha512-ToNuf1uifu8hhwWvoZJGCdLIX/1zpo8cOGnT0XAhDQXiKOKYaotVNx7pOVB1f+wHoWwTLInrOmh3EmA7Fd+8Vg==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /esbuild-darwin-arm64/0.15.14: + resolution: {integrity: sha512-KgGP+y77GszfYJgceO0Wi/PiRtYo5y2Xo9rhBUpxTPaBgWDJ14gqYN0+NMbu+qC2fykxXaipHxN4Scaj9tUS1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /esbuild-freebsd-64/0.15.14: + resolution: {integrity: sha512-xr0E2n5lyWw3uFSwwUXHc0EcaBDtsal/iIfLioflHdhAe10KSctV978Te7YsfnsMKzcoGeS366+tqbCXdqDHQA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + + /esbuild-freebsd-arm64/0.15.14: + resolution: {integrity: sha512-8XH96sOQ4b1LhMlO10eEWOjEngmZ2oyw3pW4o8kvBcpF6pULr56eeYVP5radtgw54g3T8nKHDHYEI5AItvskZg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + + /esbuild-linux-32/0.15.14: + resolution: {integrity: sha512-6ssnvwaTAi8AzKN8By2V0nS+WF5jTP7SfuK6sStGnDP7MCJo/4zHgM9oE1eQTS2jPmo3D673rckuCzRlig+HMA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-64/0.15.14: + resolution: {integrity: sha512-ONySx3U0wAJOJuxGUlXBWxVKFVpWv88JEv0NZ6NlHknmDd1yCbf4AEdClSgLrqKQDXYywmw4gYDvdLsS6z0hcw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-arm/0.15.14: + resolution: {integrity: sha512-D2LImAIV3QzL7lHURyCHBkycVFbKwkDb1XEUWan+2fb4qfW7qAeUtul7ZIcIwFKZgPcl+6gKZmvLgPSj26RQ2Q==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-arm64/0.15.14: + resolution: {integrity: sha512-kle2Ov6a1e5AjlHlMQl1e+c4myGTeggrRzArQFmWp6O6JoqqB9hT+B28EW4tjFWgV/NxUq46pWYpgaWXsXRPAg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-mips64le/0.15.14: + resolution: {integrity: sha512-FVdMYIzOLXUq+OE7XYKesuEAqZhmAIV6qOoYahvUp93oXy0MOVTP370ECbPfGXXUdlvc0TNgkJa3YhEwyZ6MRA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-ppc64le/0.15.14: + resolution: {integrity: sha512-2NzH+iuzMDA+jjtPjuIz/OhRDf8tzbQ1tRZJI//aT25o1HKc0reMMXxKIYq/8nSHXiJSnYV4ODzTiv45s+h73w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-riscv64/0.15.14: + resolution: {integrity: sha512-VqxvutZNlQxmUNS7Ac+aczttLEoHBJ9e3OYGqnULrfipRvG97qLrAv9EUY9iSrRKBqeEbSvS9bSfstZqwz0T4Q==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-s390x/0.15.14: + resolution: {integrity: sha512-+KVHEUshX5n6VP6Vp/AKv9fZIl5kr2ph8EUFmQUJnDpHwcfTSn2AQgYYm0HTBR2Mr4d0Wlr0FxF/Cs5pbFgiOw==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-netbsd-64/0.15.14: + resolution: {integrity: sha512-6D/dr17piEgevIm1xJfZP2SjB9Z+g8ERhNnBdlZPBWZl+KSPUKLGF13AbvC+nzGh8IxOH2TyTIdRMvKMP0nEzQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + optional: true + + /esbuild-openbsd-64/0.15.14: + resolution: {integrity: sha512-rREQBIlMibBetgr2E9Lywt2Qxv2ZdpmYahR4IUlAQ1Efv/A5gYdO0/VIN3iowDbCNTLxp0bb57Vf0LFcffD6kA==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + optional: true + + /esbuild-register/3.4.1_esbuild@0.15.14: + resolution: {integrity: sha512-iCgs88/1wA5dIRx4i65eSjbkgrQQQJGpY6Z1eD2XPlzrSjbgNtfkw2/rfSMzJ4dTtlOD8EZTxrIA3fyYp0FsMA==} + peerDependencies: + esbuild: '>=0.12 <1' + dependencies: + debug: 4.3.4 + esbuild: 0.15.14 + transitivePeerDependencies: + - supports-color + dev: true + + /esbuild-sunos-64/0.15.14: + resolution: {integrity: sha512-DNVjSp/BY4IfwtdUAvWGIDaIjJXY5KI4uD82+15v6k/w7px9dnaDaJJ2R6Mu+KCgr5oklmFc0KjBjh311Gxl9Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + optional: true + + /esbuild-windows-32/0.15.14: + resolution: {integrity: sha512-pHBWrcA+/oLgvViuG9FO3kNPO635gkoVrRQwe6ZY1S0jdET07xe2toUvQoJQ8KT3/OkxqUasIty5hpuKFLD+eg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + + /esbuild-windows-64/0.15.14: + resolution: {integrity: sha512-CszIGQVk/P8FOS5UgAH4hKc9zOaFo69fe+k1rqgBHx3CSK3Opyk5lwYriIamaWOVjBt7IwEP6NALz+tkVWdFog==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + + /esbuild-windows-arm64/0.15.14: + resolution: {integrity: sha512-KW9W4psdZceaS9A7Jsgl4WialOznSURvqX/oHZk3gOP7KbjtHLSsnmSvNdzagGJfxbAe30UVGXRe8q8nDsOSQw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /esbuild/0.15.14: + resolution: {integrity: sha512-pJN8j42fvWLFWwSMG4luuupl2Me7mxciUOsMegKvwCmhEbJ2covUdFnihxm0FMIBV+cbwbtMoHgMCCI+pj1btQ==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.15.14 + '@esbuild/linux-loong64': 0.15.14 + esbuild-android-64: 0.15.14 + esbuild-android-arm64: 0.15.14 + esbuild-darwin-64: 0.15.14 + esbuild-darwin-arm64: 0.15.14 + esbuild-freebsd-64: 0.15.14 + esbuild-freebsd-arm64: 0.15.14 + esbuild-linux-32: 0.15.14 + esbuild-linux-64: 0.15.14 + esbuild-linux-arm: 0.15.14 + esbuild-linux-arm64: 0.15.14 + esbuild-linux-mips64le: 0.15.14 + esbuild-linux-ppc64le: 0.15.14 + esbuild-linux-riscv64: 0.15.14 + esbuild-linux-s390x: 0.15.14 + esbuild-netbsd-64: 0.15.14 + esbuild-openbsd-64: 0.15.14 + esbuild-sunos-64: 0.15.14 + esbuild-windows-32: 0.15.14 + esbuild-windows-64: 0.15.14 + esbuild-windows-arm64: 0.15.14 + + /estree-walker/2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: false + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true + + /fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: false + + /glob/7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: false + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-core-module/2.11.0: + resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + dependencies: + has: 1.0.3 + dev: false + + /jest-worker/27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + dependencies: + '@types/node': 18.11.9 + merge-stream: 2.0.0 + supports-color: 8.1.1 + dev: true + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: false + + /json-schema-to-ts/2.6.1: + resolution: {integrity: sha512-SQ/L0BBDxYzd7AiF7WK5oKTF/3Y007XRH6Ss4ZQUfnBEtsbLmtG0GDiPw831eNzcF5WYqdUtbxS1NSb8hBu6Tg==} + engines: {node: ^16.10.0} + dependencies: + '@babel/runtime': 7.20.1 + '@types/json-schema': 7.0.11 + ts-algebra: 1.1.1 + ts-toolbelt: 9.6.0 + dev: true + + /json-schema-traverse/0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /jsonc-parser/3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: false + + /magic-string/0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + dependencies: + sourcemap-codec: 1.4.8 + dev: false + + /merge-stream/2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: false + + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: false + + /postcss/8.4.19: + resolution: {integrity: sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: false + + /preact/10.11.3: + resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + dev: false + + /punycode/2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + engines: {node: '>=6'} + dev: true + + /randombytes/2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /regenerator-runtime/0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: true + + /resolve/1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: false + + /rimraf/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rollup/2.79.1: + resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} + engines: {node: '>=10.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: false + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + + /schema-utils/3.1.1: + resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==} + engines: {node: '>= 10.13.0'} + dependencies: + '@types/json-schema': 7.0.11 + ajv: 6.12.6 + ajv-keywords: 3.5.2_ajv@6.12.6 + dev: true + + /serialize-javascript/6.0.0: + resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + dependencies: + randombytes: 2.1.0 + dev: true + + /shiki/0.11.1: + resolution: {integrity: sha512-EugY9VASFuDqOexOgXR18ZV+TbFrQHeCpEYaXamO+SZlsnT/2LxuLBX25GGtIrwaEVFXUAbUQ601SWE2rMwWHA==} + dependencies: + jsonc-parser: 3.2.0 + vscode-oniguruma: 1.6.2 + vscode-textmate: 6.0.0 + dev: false + + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: false + + /source-map-support/0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + /sourcemap-codec/1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + dev: false + + /supports-color/8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: false + + /terser-webpack-plugin/5.3.6_esbuild@0.15.14: + resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.17 + esbuild: 0.15.14 + jest-worker: 27.5.1 + schema-utils: 3.1.1 + serialize-javascript: 6.0.0 + terser: 5.15.1 + dev: true + + /terser/5.15.1: + resolution: {integrity: sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@jridgewell/source-map': 0.3.2 + acorn: 8.8.1 + commander: 2.20.3 + source-map-support: 0.5.21 + dev: true + + /to-fast-properties/2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: false + + /ts-algebra/1.1.1: + resolution: {integrity: sha512-W43a3/BN0Tp4SgRNERQF/QPVuY1rnHkgCr/fISLY0Ycu05P0NWPYRuViU8JFn+pFZuY6/zp9TgET1fxMzppR/Q==} + dependencies: + ts-toolbelt: 9.6.0 + dev: true + + /ts-toolbelt/9.6.0: + resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} + dev: true + + /typescript/4.9.3: + resolution: {integrity: sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.1.1 + dev: true + + /vite/3.2.4: + resolution: {integrity: sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.15.14 + postcss: 8.4.19 + resolve: 1.22.1 + rollup: 2.79.1 + optionalDependencies: + fsevents: 2.3.2 + dev: false + + /vitepress/1.0.0-alpha.29: + resolution: {integrity: sha512-oaRaeMLcN9M3Bxz97fFVF6Gzm3Aqtb0CijTt5TOW0XPzNPuKA0YpFnsmS97gdKmA+VztM6itRJ8K7JJuU0VS3g==} + hasBin: true + dependencies: + '@docsearch/css': 3.3.0 + '@docsearch/js': 3.3.0 + '@vitejs/plugin-vue': 3.2.0_vite@3.2.4+vue@3.2.45 + '@vue/devtools-api': 6.4.5 + '@vueuse/core': 9.5.0_vue@3.2.45 + body-scroll-lock: 4.0.0-beta.0 + shiki: 0.11.1 + vite: 3.2.4 + vue: 3.2.45 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - '@vue/composition-api' + - less + - react + - react-dom + - sass + - stylus + - sugarss + - terser + dev: false + + /vscode-oniguruma/1.6.2: + resolution: {integrity: sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==} + dev: false + + /vscode-textmate/6.0.0: + resolution: {integrity: sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==} + dev: false + + /vue-demi/0.13.11_vue@3.2.45: + resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vue: 3.2.45 + dev: false + + /vue/3.2.45: + resolution: {integrity: sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==} + dependencies: + '@vue/compiler-dom': 3.2.45 + '@vue/compiler-sfc': 3.2.45 + '@vue/runtime-dom': 3.2.45 + '@vue/server-renderer': 3.2.45_vue@3.2.45 + '@vue/shared': 3.2.45 + dev: false + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..38d3f0b --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - doc diff --git a/static/autoupdate.js b/static/autoupdate.js index c3810a4..1160b8a 100644 --- a/static/autoupdate.js +++ b/static/autoupdate.js @@ -1,53 +1,63 @@ -;(async (updateSWDelay, updateConfigDelay) => { - const LSDB = { - read: (key) => { - return localStorage.getItem(key); - }, - write: (key, value) => { - localStorage.setItem(key, value); - } - } - async function updateSW() { - if (navigator.serviceWorker) { - navigator.serviceWorker.getRegistrations().then(async registrations => { - for (let registration of registrations) { - await registration.unregister(); - } - console.log(`Unregistered service workers`); - }).then(() => { - //register new service worker in /cw.js - navigator.serviceWorker.register('/cw.js').then(async registration => { - console.log(`Registered service worker`); - await registration.update(); - LSDB.write('cw_time_sw', new Date().getTime()); - }) - }) - } - }; - async function updateConfig() { - await fetch('/cw-cgi/api?type=config').then(res => res.text()).then(res => { - if (res === 'ok') { - console.log(`Config updated`); - LSDB.write('cw_time_config', new Date().getTime()); - } else { - console.log(`Config update failed`); - } +(async (updateSWDelay, updateConfigDelay) => { + const LSDB = { + read: (key) => { + return localStorage.getItem(key); + }, + write: (key, value) => { + localStorage.setItem(key, value); + }, + }; + async function updateSW() { + if (navigator.serviceWorker) { + navigator.serviceWorker + .getRegistrations() + .then(async (registrations) => { + for (let registration of registrations) { + await registration.unregister(); + } + console.log(`Unregistered service workers`); }) + .then(() => { + //register new service worker in /cw.js + navigator.serviceWorker + .register("/cw.js") + .then(async (registration) => { + console.log(`Registered service worker`); + await registration.update(); + LSDB.write("cw_time_sw", new Date().getTime()); + }); + }); } + } + async function updateConfig() { + await fetch("/cw-cgi/api?type=config") + .then((res) => res.text()) + .then((res) => { + if (res === "ok") { + console.log(`Config updated`); + LSDB.write("cw_time_config", new Date().getTime()); + } else { + console.log(`Config update failed`); + } + }); + } - if (Number(LSDB.read('cw_time_sw')) < new Date().getTime() - updateSWDelay) { - await updateSW(); - await updateConfig(); - } - if (Number(LSDB.read('cw_time_config')) < new Date().getTime() - updateConfigDelay) { - await updateConfig(); - } + if (Number(LSDB.read("cw_time_sw")) < new Date().getTime() - updateSWDelay) { + await updateSW(); + await updateConfig(); + } + if ( + Number(LSDB.read("cw_time_config")) < + new Date().getTime() - updateConfigDelay + ) { + await updateConfig(); + } - setInterval(async () => { - await updateSW(); - await updateConfig(); - }, updateSWDelay); - setInterval(async () => { - await updateConfig() - }, updateConfigDelay); -})(1000 * 60 * 60 * 12, 1000 * 60); \ No newline at end of file + setInterval(async () => { + await updateSW(); + await updateConfig(); + }, updateSWDelay); + setInterval(async () => { + await updateConfig(); + }, updateConfigDelay); +})(1000 * 60 * 60 * 12, 1000 * 60); diff --git a/static/config.schema.json b/static/config.schema.json deleted file mode 100644 index bc9f3c1..0000000 --- a/static/config.schema.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-06/schema#", - "$ref": "#/definitions/ClientWorker", - "definitions": { - "ClientWorker": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "hotpatch": { - "type": "array", - "items": { - "type": "string", - "format": "uri", - "qt-uri-protocols": [ - "https" - ], - "qt-uri-extensions": [ - ".js" - ] - } - }, - "hotconfig": { - "type": "array", - "items": { - "type": "string", - "format": "uri", - "qt-uri-protocols": [ - "https" - ], - "qt-uri-extensions": [ - ".yaml" - ] - } - }, - "cleaninterval": { - "type": "string" - }, - "catch_rules": { - "type": "array", - "items": { - "$ref": "#/definitions/CatchRule" - } - } - }, - "required": [ - "catch_rules", - "name" - ], - "title": "Clientworker" - }, - "CatchRule": { - "type": "object", - "additionalProperties": false, - "properties": { - "rule": { - "type": "string" - }, - "transform_rules": { - "type": "array", - "items": { - "$ref": "#/definitions/TransformRule" - } - } - }, - "required": [ - "rule", - "transform_rules" - ], - "title": "CatchRule" - }, - "TransformRule": { - "type": "object", - "additionalProperties": false, - "properties": { - "search": { - "type": "string" - }, - "searchin": { - "type": "string" - }, - "replace": { - "$ref": "#/definitions/Replace" - }, - "action": { - "type": "string", - "enum": [ - "redirect", - "return", - "fetch", - "script" - ] - }, - "redirect": { - "$ref": "#/definitions/Redirect" - }, - "script": { - "$ref": "#/definitions/Script" - }, - "fetch": { - "$ref": "#/definitions/Fetch" - }, - "header": { - "$ref": "#/definitions/Header" - }, - "searchkey": { - "type": "string" - }, - "replacein": { - "type": "string" - }, - "replacekey": { - "type": "string" - }, - "searchflags": { - "type": "string" - }, - "replaceflags": { - "type": "string" - }, - "return": { - "$ref": "#/definitions/Return" - } - }, - "required": [ - "search" - ], - "title": "TransformRule" - }, - "Fetch": { - "type": "object", - "additionalProperties": false, - "properties": { - "status": { - "type": "integer" - }, - "engine": { - "type": "string", - "enum": [ - "fetch", - "Crazy", - "Classic", - "Parallel", - "KFCThursdayVW50" - ] - }, - "preflight": { - "type": "boolean" - }, - "timeout": { - "type": "integer" - }, - "cache": { - "$ref": "#/definitions/Cache" - }, - "threads": { - "type": "integer" - }, - "enable": { - "type": "boolean" - } - }, - "required": [ - "engine", - "preflight" - ], - "title": "Fetch" - }, - "Cache": { - "type": "object", - "additionalProperties": false, - "properties": { - "expire": { - "type": "string" - }, - "delay": { - "type": "integer" - }, - "enable": { - "type": "boolean" - } - }, - "required": [ - "expire" - ], - "title": "Cache" - }, - "Header": { - "type": "object", - "additionalProperties": false, - "properties": { - "content-type": { - "type": "string" - }, - "ServerProvide": { - "type": "string" - } - }, - "required": [ - "ServerProvide", - "content-type" - ], - "title": "Header" - }, - "Redirect": { - "type": "object", - "additionalProperties": false, - "properties": { - "to": { - "type": "string" - }, - "url": { - "type": "string", - "format": "uri", - "qt-uri-protocols": [ - "https" - ] - }, - "status": { - "type": "integer" - } - }, - "required": [], - "title": "Redirect" - }, - "Return": { - "type": "object", - "additionalProperties": false, - "properties": { - "body": { - "type": "string" - }, - "header": { - "$ref": "#/definitions/Header" - }, - "status": { - "type": "integer" - } - }, - "required": [ - "body", - "header", - "status" - ], - "title": "Return" - }, - "Script": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "skip": { - "type": "boolean" - }, - "function": { - "type": "string" - } - }, - "required": [ - "skip" - ], - "title": "Script" - }, - "Replace": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string", - "format": "uri", - "qt-uri-protocols": [ - "https" - ] - } - }, - { - "type": "string" - } - ], - "title": "Replace" - } - } -} diff --git a/static/config.yaml b/static/config.yaml index 0b194c4..ffaafc2 100644 --- a/static/config.yaml +++ b/static/config.yaml @@ -1,107 +1,100 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/ChenYFan/ClientWorker/gh-pages/config.schema.json name: ClientWorker Docs Config hotpatch: - - https://raw.githubusercontent.com/ChenYFan/ClientWorker/gh-pages/cw.js + - https://raw.githubusercontent.com/ChenYFan/ClientWorker/gh-pages/cw.js hotconfig: - - https://raw.githubusercontent.com/ChenYFan/ClientWorker/gh-pages/config.yaml + - https://raw.githubusercontent.com/ChenYFan/ClientWorker/gh-pages/config.yaml cleaninterval: 1000*20 -catch_rules: - - rule: _ - transform_rules: - - search: \#.* - searchin: url - replace: '' - - search: \?.* - replace: '' - - search: ([^\/.]+)\/index(|\.html)$ - action: redirect - redirect: - to: $1/ - - search: \/google$ - action: redirect - redirect: - url: https://google.com - status: 301 +catch_rules: + - rule: _ + transform_rules: + - search: \#.* + searchin: url + replace: "" + - search: \?.* + replace: "" + - search: ([^\/.]+)\/index(|\.html)$ + action: redirect + redirect: + to: $1/ + - search: \/google$ + action: redirect + redirect: + url: https://google.com + status: 301 - - search: \/time$ - action: script - script: - name: gettime - skip: true - - search: \/timefunction$ - action: script - script: - function: ()=>{return {fetched:true,response:new Response(new Date().toString())}} - skip: true - - - search: \/([^\/.]+)$ - action: redirect - redirect: - to: /$1.html - status: 301 - - - search: ([^\/.]+)\/$ - replace: $1/index.html - - - - - - search: _ - replace: docroot/clientworker@3.0.0-beta-4/doc/docs/.vitepress/dist + - search: \/time$ + action: script + script: + name: gettime + skip: true + - search: \/timefunction$ + action: script + script: + function: ()=>{return {fetched:true,response:new Response(new Date().toString())}} + skip: true + - search: \/([^\/.]+)$ + action: redirect + redirect: + to: /$1.html + status: 301 - - search: ^https\:\/\/docroot - replace: - - https://cdn.jsdelivr.net/npm - - https://cdn1.tianli0.top/npm - - https://jsd.onmicrosoft.cn/npm - - https://unpkg.onmicrosoft.cn - - https://unpkg.com - - https://npm.sourcegcdn.com - action: fetch - fetch: - status: 200 - engine: parallel - preflight: false - timeout: 5000 - cache: - expire: 1000*10 - delay: 3000 - - - search: \.html$ - header: - content-type: text/html;charset=utf-8 - ServerProvide: ClientWorker - - - rule: ^https\:\/\/npmm\/chenyfan\-os\@0\.0\.0\-r24\/(.*)\.jpg$ - transform_rules: - - search: image\/webp - searchin: header - searchkey: Accept - replace: .webp - replacein: url - replacekey: .jpg - - rule: ^https\:\/\/cdn\.jsdelivr\.net\/npm\/chenyfan\-happypic\@0\.0\.33\/1\.jpg$ - transform_rules: - - search: _ - action: fetch - fetch: - status: 200 - engine: crazy - preflight: false - threads: 5 - timeout: 30000 - cache: - enable: true - expire: 1000*60 + - search: ([^\/.]+)\/$ + replace: $1/index.html + - search: _ + replace: docroot/clientworker@3.0.0-beta-4/doc/docs/.vitepress/dist + - search: ^https?\:\/\/docroot + replace: + - https://cdn.jsdelivr.net/npm + - https://cdn1.tianli0.top/npm + - https://jsd.onmicrosoft.cn/npm + - https://unpkg.onmicrosoft.cn + - https://unpkg.com + - https://npm.sourcegcdn.com + action: fetch + fetch: + status: 200 + engine: parallel + preflight: false + timeout: 5000 + cache: + expire: 1000*10 + delay: 3000 + - search: \.html$ + header: + content-type: text/html;charset=utf-8 + ServerProvide: ClientWorker + - rule: ^https\:\/\/npmm\/chenyfan\-os\@0\.0\.0\-r24\/(.*)\.jpg$ + transform_rules: + - search: image\/webp + searchin: header + searchkey: Accept + replace: .webp + replacein: url + replacekey: .jpg + - rule: ^https\:\/\/cdn\.jsdelivr\.net\/npm\/chenyfan\-happypic\@0\.0\.33\/1\.jpg$ + transform_rules: + - search: _ + action: fetch + fetch: + status: 200 + engine: crazy + preflight: false + threads: 5 + timeout: 30000 + cache: + enable: true + expire: 1000*60 - - rule: ^https\:\/\/npmm\/ - transform_rules: - - search: _ - replace: + - rule: ^https\:\/\/npmm\/ + transform_rules: + - search: _ + replace: - https://npm.elemecdn.com/ - https://cdn.jsdelivr.net/npm/ - https://unpkg.com/ @@ -109,43 +102,42 @@ catch_rules: - https://jsd.onmicrosoft.cn/npm/ - https://unpkg.onmicrosoft.cn/ - https://npm.sourcegcdn.com/ - - search: _ - action: fetch - fetch: - status: 200 - engine: parallel - preflight: false - timeout: 5000 - cache: - enable: true - expire: 1000*60*60*24*7 - delay: 150 - - - rule: ^https\:\/\/npmm\/jquery\@3\.6\.0\/package\.json$ - transform_rules: - - search: jquery - searchin: body - searchflags: g - replace: fakejquery - replacein: body - replaceflags: g - - - - rule: .* - transform_rules: - - search: .* - action: fetch - fetch: + - search: _ + action: fetch + fetch: + status: 200 + engine: parallel + preflight: false + timeout: 5000 + cache: enable: true - engine: fetch - preflight: false - - - search: (^4|^5) - searchin: status - action: return - return: - body: Error! - header: - content-type: text/plain;charset=utf-8 - ServerProvide: ClientWorker - status: 503 + expire: 1000*60*60*24*7 + delay: 150 + + - rule: ^https\:\/\/npmm\/jquery\@3\.6\.0\/package\.json$ + transform_rules: + - search: jquery + searchin: body + searchflags: g + replace: fakejquery + replacein: body + replaceflags: g + + - rule: .* + transform_rules: + - search: .* + action: fetch + fetch: + enable: true + engine: fetch + preflight: false + + - search: (^4|^5) + searchin: status + action: return + return: + body: Error! + header: + content-type: text/plain;charset=utf-8 + ServerProvide: ClientWorker + status: 503 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7e76b38 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, + "strict": true, + "lib": ["esnext", "WebWorker", "WebWorker.ImportScripts"], + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "noEmit": true, + "isolatedModules": true, + "plugins": [], + "allowJs": true, + "resolveJsonModule": true + } +} diff --git a/types/cacheDB.d.ts b/types/cacheDB.d.ts new file mode 100644 index 0000000..f332fe7 --- /dev/null +++ b/types/cacheDB.d.ts @@ -0,0 +1,42 @@ +// Type definitions for @chenyfan/cache-db 0.0 +// Project: https://www.npmjs.com/package/@chenyfan/cache-db +// Definitions by: AHdark +// lixiang810 +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +declare module "@chenyfan/cache-db" { + namespace CacheDB { + interface Config { + type: "json" | "arrayBuffer" | "blob" | "text" | string; + } + type ReadMethodData = object | ArrayBuffer | Blob | string | null; + type WriteValue = + | ReadableStream + | Blob + | ArrayBufferView + | ArrayBuffer + | FormData + | URLSearchParams + | string; + } + + class CacheDB { + constructor(namespace?: string, prefix?: string); + + namespace: string; + prefix: string; + + read( + key: string, + config?: CacheDB.Config + ): Promise; + write( + key: string | number | boolean, + value: CacheDB.WriteValue, + config?: CacheDB.Config + ): Promise; + delete(key: string): Promise; + } + + export default CacheDB; +} diff --git a/types/configType.ts b/types/configType.ts new file mode 100644 index 0000000..230f451 --- /dev/null +++ b/types/configType.ts @@ -0,0 +1,261 @@ +import { FromSchema } from "json-schema-to-ts"; + +export const configSchema = { + $schema: "http://json-schema.org/draft-06/schema#", + $ref: "#/definitions/ClientWorker", + definitions: { + ClientWorker: { + type: "object", + additionalProperties: false, + properties: { + name: { + type: "string", + }, + hotpatch: { + type: "array", + items: { + type: "string", + format: "uri", + "qt-uri-protocols": ["https"], + "qt-uri-extensions": [".js"], + }, + }, + hotconfig: { + type: "array", + items: { + type: "string", + format: "uri", + "qt-uri-protocols": ["https"], + "qt-uri-extensions": [".yaml"], + }, + }, + cleaninterval: { + type: "string", + }, + catch_rules: { + type: "array", + items: { + $ref: "#/definitions/CatchRule", + }, + }, + }, + required: ["catch_rules", "name"], + title: "Clientworker", + }, + CatchRule: { + type: "object", + additionalProperties: false, + properties: { + rule: { + type: "string", + }, + transform_rules: { + type: "array", + items: { + $ref: "#/definitions/TransformRule", + }, + }, + }, + required: ["rule", "transform_rules"], + title: "CatchRule", + }, + TransformRule: { + type: "object", + additionalProperties: false, + properties: { + search: { + type: "string", + }, + searchin: { + type: "string", + }, + replace: { + $ref: "#/definitions/Replace", + }, + action: { + type: "string", + enum: ["redirect", "return", "fetch", "script", "skip"], + }, + redirect: { + $ref: "#/definitions/Redirect", + }, + script: { + $ref: "#/definitions/Script", + }, + fetch: { + $ref: "#/definitions/Fetch", + }, + header: { + $ref: "#/definitions/Header", + }, + searchkey: { + type: "string", + }, + replacein: { + type: "string", + }, + replacekey: { + type: "string", + }, + searchflags: { + type: "string", + }, + replaceflags: { + type: "string", + }, + return: { + $ref: "#/definitions/Return", + }, + }, + required: ["search"], + title: "TransformRule", + }, + Fetch: { + type: "object", + additionalProperties: false, + properties: { + status: { + type: "integer", + }, + engine: { + type: "string", + enum: ["fetch", "crazy", "classic", "parallel", "KFCThursdayVW50"], + }, + preflight: { + type: "boolean", + }, + credentials: { + type: "string", + enum: ["same-origin", "include", "omit"], + }, + trylimit: { type: "number" }, + redirect: { type: "string", enum: ["error", "follow", "manual"] }, + mode: { + type: "string", + enum: ["same-origin", "cors", "navigate", "no-cors"], + }, + timeout: { + type: "integer", + }, + cache: { + $ref: "#/definitions/Cache", + }, + threads: { + type: "integer", + }, + enable: { + type: "boolean", + }, + }, + required: ["engine", "preflight"], + title: "Fetch", + }, + Cache: { + type: "object", + additionalProperties: false, + properties: { + expire: { + type: "string", + }, + delay: { + type: "integer", + }, + enable: { + type: "boolean", + }, + }, + required: ["expire"], + title: "Cache", + }, + Header: { + type: "object", + additionalProperties: false, + properties: { + "content-type": { + type: "string", + }, + ServerProvide: { + type: "string", + }, + }, + required: ["ServerProvide", "content-type"], + title: "Header", + }, + Redirect: { + description: + "你要找的是在外部观察 url 不跳转的 URL 重写吗?redirect 可以直接返回一个跳转,无视 fetch 状态,对接下来的规则也将不执行。", + type: "object", + additionalProperties: false, + properties: { + to: { + description: "表示重定向的替换规则", + type: "string", + }, + url: { + description: "表示重定向的目标url", + type: "string", + format: "uri", + "qt-uri-protocols": ["https"], + }, + status: { + description: "你可以选择 301/302,不过这其实没有太大用处。", + type: "integer", + }, + }, + required: [], + title: "Redirect", + }, + Return: { + type: "object", + additionalProperties: false, + properties: { + body: { + type: "string", + }, + header: { + $ref: "#/definitions/Header", + }, + status: { + type: "integer", + }, + }, + required: ["body", "header", "status"], + title: "Return", + }, + Script: { + type: "object", + additionalProperties: false, + properties: { + name: { + type: "string", + }, + skip: { + type: "boolean", + }, + function: { + type: "string", + }, + }, + required: ["skip"], + title: "Script", + }, + Replace: { + anyOf: [ + { + type: "array", + items: { + type: "string", + format: "uri", + "qt-uri-protocols": ["https"], + }, + }, + { + type: "string", + }, + ], + title: "Replace", + }, + }, +} as const; + +export type ConfigType = FromSchema; diff --git a/types/esbuild-copy-static-files.d.ts b/types/esbuild-copy-static-files.d.ts new file mode 100644 index 0000000..5a002aa --- /dev/null +++ b/types/esbuild-copy-static-files.d.ts @@ -0,0 +1,12 @@ +declare module "esbuild-copy-static-files" { + export default function copyStaticFiles(param: { + src: string; + dest: string; + dereference?: boolean; + errorOnExist?: boolean; + filter?: (...param: any) => boolean; + force?: boolean; + preserveTimestamps?: boolean; + recursive?: boolean; + }): import("esbuild").Plugin; +} diff --git a/types/globalThis.d.ts b/types/globalThis.d.ts new file mode 100644 index 0000000..2c14f2e --- /dev/null +++ b/types/globalThis.d.ts @@ -0,0 +1,7 @@ +declare global { + var clientworkerhandle: (req: Request) => Response | PromiseLike; + var clients: { claim(): Promise }; + var skipWaiting: () => Promise | void; +} + +export {};