diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 9477454..610e045 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,34 +1,113 @@ +/* eslint-env node */ +/** @type {import('eslint').Linter.Config} */ module.exports = { - "env": { - "browser": true, - "es6": true, - "webextensions": true + parser: "@typescript-eslint/parser", + env: { + browser: true, + commonjs: true, + es2017: true, + }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "prettier" + ], + plugins: ["@typescript-eslint"], + parserOptions: { + project: ["./tsconfig.eslint.json"], + tsconfigRootDir: __dirname, + }, + root: true, + rules: { + /* start stylistic rules */ + "@typescript-eslint/adjacent-overload-signatures": "error", + "@typescript-eslint/array-type": "error", + "@typescript-eslint/consistent-type-imports": [ + "error", + { + fixStyle: "inline-type-imports", + }, + ], + "@typescript-eslint/consistent-type-exports": "error", + "@typescript-eslint/prefer-readonly": "warn", + "@typescript-eslint/class-literal-property-style": ["warn", "getters"], + "@typescript-eslint/consistent-generic-constructors": "error", + "@typescript-eslint/consistent-type-assertions": "error", + "@typescript-eslint/no-confusing-non-null-assertion": "warn", + "@typescript-eslint/no-inferrable-types": "warn", + "@typescript-eslint/non-nullable-type-assertion-style": "warn", + "@typescript-eslint/prefer-for-of": "warn", + // "@typescript-eslint/prefer-nullish-coalescing": "warn", + "@typescript-eslint/prefer-optional-chain": "warn", + "@typescript-eslint/prefer-string-starts-ends-with": "error", + "@typescript-eslint/no-meaningless-void-operator": "error", + "@typescript-eslint/no-unnecessary-boolean-literal-compare": "warn", + "@typescript-eslint/no-unnecessary-condition": "warn", + "@typescript-eslint/no-unnecessary-qualifier": "warn", + "@typescript-eslint/no-unnecessary-type-arguments": "warn", + "@typescript-eslint/prefer-reduce-type-parameter": "warn", + "@typescript-eslint/promise-function-async": "warn", + /* end stylistic rules */ + + /* start recommended rules */ + "no-restricted-globals": [2, "event", "error"], + "@typescript-eslint/no-base-to-string": "warn", + "@typescript-eslint/no-duplicate-enum-values": "error", + "@typescript-eslint/no-duplicate-type-constituents": "warn", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-extra-non-null-assertion": "error", + "@typescript-eslint/no-floating-promises": "warn", + "@typescript-eslint/no-for-in-array": "warn", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }, + ], + "no-implied-eval": "off", + "@typescript-eslint/no-implied-eval": "error", + "no-loss-of-precision": "off", + "@typescript-eslint/no-loss-of-precision": "warn", + "@typescript-eslint/no-misused-new": "error", + "@typescript-eslint/no-misused-promises": [ + "error", + { checksVoidReturn: false }, + ], + "@typescript-eslint/no-non-null-asserted-nullish-coalescing": "error", + "@typescript-eslint/no-non-null-asserted-optional-chain": "warn", + "@typescript-eslint/no-redundant-type-constituents": "warn", + "@typescript-eslint/no-this-alias": "warn", + "@typescript-eslint/no-unnecessary-type-assertion": "warn", + "@typescript-eslint/no-unnecessary-type-constraint": "warn", + /* TODO eventually turn all these on */ + "@typescript-eslint/no-unsafe-argument": "warn", + // "@typescript-eslint/no-unsafe-assignment": "warn", + // "@typescript-eslint/no-unsafe-call": "warn", + "@typescript-eslint/no-unsafe-declaration-merging": "warn", + "@typescript-eslint/no-unsafe-enum-comparison": "warn", + // "@typescript-eslint/no-unsafe-member-access": "warn", + "@typescript-eslint/no-unsafe-return": "warn", + "@typescript-eslint/prefer-as-const": "warn", + "require-await": "off", + // "@typescript-eslint/require-await": "warn", + "@typescript-eslint/restrict-template-expressions": "warn", + "@typescript-eslint/unbound-method": "off", + "@typescript-eslint/method-signature-style": "error", + "@typescript-eslint/await-thenable": "error", + }, + reportUnusedDisableDirectives: true, + ignorePatterns: ["__generated__", "__mocks__", "dist", "static"], + overrides: [ + { + extends: ["plugin:@typescript-eslint/disable-type-checked"], + files: ["webpack.*.js", ".*.cjs"], + rules: { + "@typescript-eslint/no-var-requires": "off", + }, }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module" - }, - "rules": { - "no-restricted-globals": [ - 2, - "event", "error" - ], - "indent": [ - "error", - 2 - ], - "linebreak-style": [ - "error", - "unix" - ], - "quotes": [ - "error", - "double" - ], - "semi": [ - "error", - "always" - ] - } + ], }; diff --git a/package.json b/package.json index 2797cce..c6ae8ef 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,7 @@ { "name": "@webrecorder/awp-sw", - "browser": "dist/sw.js", - "version": "0.4.4", - "main": "index.js", + "browser": "dist/index.js", + "version": "0.5.0", "type": "module", "repository": { "type": "git", @@ -10,33 +9,47 @@ }, "license": "AGPL-3.0-or-later", "exports": { - ".": "./src/index.js" + ".": { + "types": "./dist/types/index.d.ts", + "default": "./dist/index.js" + } }, "files": [ - "src/*" + "src/*", + "dist/*" ], "dependencies": { - "@ipld/car": "^5.3.1", + "@ipld/car": "^5.3.2", "@ipld/unixfs": "^3.0.0", - "@webrecorder/wabac": "^2.18.1", + "@webrecorder/wabac": "^2.20.0", "auto-js-ipfs": "^2.3.0", "client-zip": "^2.3.0", "hash-wasm": "^4.9.0", "idb": "^7.1.1", - "p-queue": "^7.3.4", + "p-queue": "^8.0.1", "uuid": "^9.0.0", - "warcio": "^2.2.1" + "warcio": "^2.3.1" }, "scripts": { "build": "webpack --mode production", "build-dev": "webpack --mode development", "start-dev": "webpack serve --mode development --port 10001 --output-path ./dist --static ${PWD}", - "lint": "eslint ./src webpack.config.cjs" + "lint": "eslint ./src", + "format:check": "prettier --check ./src/", + "format": "prettier --write ./src/" }, "devDependencies": { - "eslint": "^8.28.0", + "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", + "eslint": "^8.56.1", + "eslint-config-prettier": "^9.1.0", + "prettier": "^3.3.3", "raw-loader": "^4.0.2", - "webpack": "^5.91.0", + "ts-loader": "^9.5.1", + "tsconfig-paths-webpack-plugin": "^4.1.0", + "typescript": "^5.5.4", + "webpack": "^5.94.0", "webpack-cli": "^5.1.4" }, "description": "This library has been factored out of [ArchiveWeb.page](https://webrecorder/archiveweb.page) and represents the core service worker implementation necessarily for high-fidelity web archiving.", diff --git a/src/api.js b/src/api.js deleted file mode 100644 index 48d31e8..0000000 --- a/src/api.js +++ /dev/null @@ -1,396 +0,0 @@ -import { API } from "@webrecorder/wabac/src/api.js"; -import { tsToDate } from "@webrecorder/wabac/src/utils.js"; - -import { Downloader } from "./downloader.js"; -import { Signer } from "./keystore.js"; -import { ipfsAdd, ipfsRemove, setAutoIPFSUrl } from "./ipfsutils.js"; -import { RecProxy } from "./recproxy.js"; - -// eslint-disable-next-line no-undef -const DEFAULT_SOFTWARE_STRING = `Webrecorder ArchiveWeb.page ${__AWP_VERSION__}, using warcio.js ${__WARCIO_VERSION__}`; - -// =========================================================================== -class ExtAPI extends API -{ - constructor(collections, {softwareString = "", replaceSoftwareString = false} = {}) { - super(collections); - this.softwareString = replaceSoftwareString ? softwareString : softwareString + DEFAULT_SOFTWARE_STRING; - - this.uploading = new Map(); - } - - get routes() { - return { - ...super.routes, - "downloadPages": "c/:coll/dl", - "upload": ["c/:coll/upload", "POST"], - "uploadStatus": "c/:coll/upload", - "uploadDelete": ["c/:coll/upload", "DELETE"], - "recPending": "c/:coll/recPending", - "pageTitle": ["c/:coll/pageTitle", "POST"], - "ipfsAdd": ["c/:coll/ipfs", "POST"], - "ipfsRemove": ["c/:coll/ipfs", "DELETE"], - "ipfsDaemonUrl": ["ipfs/daemonUrl", "POST"], - "publicKey": "publicKey", - }; - } - - downloaderOpts() { - const softwareString = this.softwareString; - - const signer = new Signer(softwareString, {cacheSig: true}); - - return {softwareString, signer}; - } - - async handleApi(request, params, event) { - switch (params._route) { - case "downloadPages": - return await this.handleDownload(params); - - case "upload": - return await this.handleUpload(params, request, event); - - case "uploadStatus": - return await this.getUploadStatus(params); - - case "uploadDelete": - return await this.deleteUpload(params); - - case "recPending": - return await this.recordingPending(params); - - case "pageTitle": - return await this.updatePageTitle(params.coll, request); - - case "publicKey": - return await this.getPublicKey(); - - case "ipfsAdd": - return await this.startIpfsAdd(event, request, params.coll); - - case "ipfsRemove": - return await this.ipfsRemove(request, params.coll); - - case "ipfsDaemonUrl": - return await this.setIPFSDaemonUrlFromBody(request); - - default: - return await super.handleApi(request, params); - } - } - - async handleDownload(params) { - const dl = await this.getDownloader(params); - return dl.download(); - } - - async getDownloader(params) { - const coll = await this.collections.loadColl(params.coll); - if (!coll) { - return {error: "collection_not_found"}; - } - - const pageQ = params._query.get("pages"); - const pageList = pageQ === "all" ? null : pageQ.split(","); - - const format = params._query.get("format") || "wacz"; - let filename = params._query.get("filename"); - - return new Downloader({...this.downloaderOpts(), coll, format, filename, pageList}); - } - - async handleUpload(params, request, event) { - const uploading = this.uploading; - - const prevUpload = uploading.get(params.coll); - - const {url, headers, abortUpload} = await request.json(); - - if (prevUpload && prevUpload.status === "uploading") { - if (abortUpload && prevUpload.abort) { - prevUpload.abort(); - return {aborted: true}; - } - return {error: "already_uploading"}; - } else if (abortUpload) { - return {error: "not_uploading"}; - } - - const dl = await this.getDownloader(params); - const dlResp = await dl.download(); - const filename = dlResp.filename; - - const abort = new AbortController(); - const signal = abort.signal; - - const counter = new CountingStream(dl.metadata.size, abort); - - const body = dlResp.body.pipeThrough(counter.transformStream()); - - try { - const urlObj = new URL(url); - urlObj.searchParams.set("filename", filename); - urlObj.searchParams.set("name", dl.metadata.title || filename); - const fetchPromise = fetch(urlObj.href, {method: "PUT", headers, duplex: "half", body, signal}); - uploading.set(params.coll, counter); - if (event.waitUntil) { - event.waitUntil(this.uploadFinished(fetchPromise, params.coll, dl.metadata, filename, counter)); - } - return {uploading: true}; - } catch (e) { - uploading.delete(params.coll); - return {error: "upload_failed", details: e.toString()}; - } - } - - async uploadFinished(fetchPromise, collId, metadata, filename, counter) { - try { - const resp = await fetchPromise; - const json = await resp.json(); - - console.log(`Upload finished for ${filename} ${collId}`); - - metadata.uploadTime = new Date().getTime(); - metadata.uploadId = json.id; - if (!metadata.mtime) { - metadata.mtime = metadata.uploadTime; - } - if (!metadata.ctime) { - metadata.ctime = metadata.uploadTime; - } - await this.collections.updateMetadata(collId, metadata); - counter.status = "done"; - - } catch (e) { - console.log(`Upload failed for ${filename} ${collId}`); - console.log(e); - counter.status = counter.aborted ? "aborted" : "failed"; - } - } - - async deleteUpload(params) { - const collId = params.coll; - - this.uploading.delete(collId); - - const coll = await this.collections.loadColl(collId); - - if (coll && coll.metadata) { - coll.metadata.uploadTime = null; - coll.metadata.uploadId = null; - await this.collections.updateMetadata(collId, coll.metadata); - return {deleted: true}; - } - - return {deleted: false}; - } - - async getUploadStatus(params) { - let result = null; - const counter = this.uploading.get(params.coll); - - if (!counter) { - result = {status: "idle"}; - } else { - const { size, totalSize, status } = counter; - result = {status, size, totalSize}; - - if (status !== "uploading") { - this.uploading.delete(params.coll); - } - } - - const coll = await this.collections.loadColl(params.coll); - - if (coll && coll.metadata) { - result.uploadTime = coll.metadata.uploadTime; - result.uploadId = coll.metadata.uploadId; - result.ctime = coll.metadata.ctime; - result.mtime = coll.metadata.mtime; - } - - return result; - } - - async recordingPending(params) { - const coll = await this.collections.loadColl(params.coll); - if (!coll) { - return {error: "collection_not_found"}; - } - - if (!(coll.store instanceof RecProxy)) { - return {error: "invalid_collection"}; - } - - const numPending = await coll.store.getCounter(); - - return { numPending }; - } - - async prepareColl(collId, request) { - const coll = await this.collections.loadColl(collId); - if (!coll) { - return {error: "collection_not_found"}; - } - - const body = await this.setIPFSDaemonUrlFromBody(request); - - return {coll, body}; - } - - async setIPFSDaemonUrlFromBody(request) { - let body; - - try { - body = await request.json(); - if (body.ipfsDaemonUrl) { - setAutoIPFSUrl(body.ipfsDaemonUrl); - } - } catch (e) { - body = {}; - } - - return body; - } - - async startIpfsAdd(event, request, collId) { - const {coll, body} = await this.prepareColl(collId, request); - - const client = await self.clients.get(event.clientId); - - const p = runIPFSAdd(collId, coll, client, this.downloaderOpts(), this.collections, body); - - if (event.waitUntil) { - event.waitUntil(p); - } - - try { - await p; - } catch (e) { - return {error: "ipfs_not_available"}; - } - - return {collId}; - } - - async ipfsRemove(request, collId) { - const {coll} = await this.prepareColl(collId, request); - - if (await ipfsRemove(coll)) { - await this.collections.updateMetadata(coll.name, coll.config.metadata); - return {removed: true}; - } - - return {removed: false}; - } - - async updatePageTitle(collId, request) { - const json = await request.json(); - let {url, ts, title} = json; - - ts = tsToDate(ts).getTime(); - - const coll = await this.collections.loadColl(collId); - if (!coll) { - return {error: "collection_not_found"}; - } - - //await coll.store.db.init(); - - const result = await coll.store.lookupUrl(url, ts); - - if (!result) { - return {error: "page_not_found"}; - } - - // drop to second precision for comparison - const roundedTs = Math.floor(result.ts / 1000) * 1000; - if (url !== result.url || ts !== roundedTs) { - return {error: "no_exact_match"}; - } - - const page = await coll.store.db.getFromIndex("pages", "url", url); - if (!page) { - return {error: "page_not_found"}; - } - page.title = title; - await coll.store.db.put("pages", page); - - return {"added": true}; - } - - async getPublicKey() { - const { signer } = this.downloaderOpts(); - const keys = await signer.loadKeys(); - if (!keys || !keys.public) { - return {}; - } else { - return {publicKey: keys.public}; - } - } -} - -// =========================================================================== -async function runIPFSAdd(collId, coll, client, opts, collections, replayOpts) { - let size = 0; - let totalSize = 0; - - const sendMessage = (type, result = null) => { - if (client) { - client.postMessage({ - type, collId, size, result, totalSize - }); - } - }; - - const {url, cid} = await ipfsAdd(coll, opts, replayOpts, (incSize, _totalSize) => { - size += incSize; - totalSize = _totalSize; - sendMessage("ipfsProgress"); - }); - - const result = {cid, ipfsURL: url}; - - sendMessage("ipfsAdd", result); - - await collections.updateMetadata(coll.name, coll.config.metadata); -} - - -// =========================================================================== -class CountingStream -{ - constructor(totalSize, abort) { - this.totalSize = totalSize || 0; - this.status = "uploading"; - this.size = 0; - this._abort = abort; - this.aborted = false; - } - - abort() { - if (this._abort) { - this._abort.abort(); - this.aborted = true; - } - } - - transformStream() { - const counterStream = this; - - return new TransformStream({ - start() { - counterStream.size = 0; - }, - - transform(chunk, controller) { - counterStream.size += chunk.length; - //console.log(`Uploaded: ${counterStream.size}`); - controller.enqueue(chunk); - } - }); - } -} - -export { ExtAPI }; diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 0000000..5745934 --- /dev/null +++ b/src/api.ts @@ -0,0 +1,483 @@ +import { API, type SWCollections, tsToDate } from "@webrecorder/wabac/swlib"; + +import { Downloader, type Metadata } from "./downloader"; +import { Signer } from "./keystore"; +import { ipfsAdd, ipfsRemove, setAutoIPFSUrl } from "./ipfsutils"; +import { RecProxy } from "./recproxy"; +import { type Collection } from "@webrecorder/wabac/swlib"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type RouteMatch = Record; + +declare let self: ServiceWorkerGlobalScope; + +const DEFAULT_SOFTWARE_STRING = `Webrecorder ArchiveWeb.page ${__AWP_VERSION__}, using warcio.js ${__WARCIO_VERSION__}`; + +// =========================================================================== +class ExtAPI extends API { + softwareString = ""; + uploading: Map = new Map(); + + constructor( + collections: SWCollections, + { softwareString = "", replaceSoftwareString = false } = {}, + ) { + super(collections); + this.softwareString = replaceSoftwareString + ? softwareString + : softwareString + DEFAULT_SOFTWARE_STRING; + } + + override get routes(): Record { + return { + ...super.routes, + downloadPages: "c/:coll/dl", + upload: ["c/:coll/upload", "POST"], + uploadStatus: "c/:coll/upload", + uploadDelete: ["c/:coll/upload", "DELETE"], + recPending: "c/:coll/recPending", + pageTitle: ["c/:coll/pageTitle", "POST"], + ipfsAdd: ["c/:coll/ipfs", "POST"], + ipfsRemove: ["c/:coll/ipfs", "DELETE"], + ipfsDaemonUrl: ["ipfs/daemonUrl", "POST"], + publicKey: "publicKey", + }; + } + + downloaderOpts() { + const softwareString = this.softwareString; + + const signer = new Signer(softwareString, { cacheSig: true }); + + return { softwareString, signer }; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + override async handleApi(request: Request, params: any, event: FetchEvent) { + switch (params._route) { + case "downloadPages": + return await this.handleDownload(params); + + case "upload": + return await this.handleUpload(params, request, event); + + case "uploadStatus": + return await this.getUploadStatus(params); + + case "uploadDelete": + return await this.deleteUpload(params); + + case "recPending": + return await this.recordingPending(params); + + case "pageTitle": + return await this.updatePageTitle(params.coll, request); + + case "publicKey": + return await this.getPublicKey(); + + case "ipfsAdd": + //return await this.startIpfsAdd(event, request, params.coll); + return {}; + + case "ipfsRemove": + //return await this.ipfsRemove(request, params.coll); + return {}; + + case "ipfsDaemonUrl": + return await this.setIPFSDaemonUrlFromBody(request); + + default: + return await super.handleApi(request, params, event); + } + } + + async handleDownload(params: RouteMatch) { + const { dl, error } = await this.getDownloader(params); + if (error) { + return error; + } + return dl.download(); + } + + async getDownloader(params: RouteMatch) { + const coll = await this.collections.loadColl(params.coll); + if (!coll) { + return { error: { error: "collection_not_found" } }; + } + + const pageQ = params["_query"].get("pages"); + const pageList = pageQ === "all" ? null : pageQ.split(","); + + const format = params["_query"].get("format") || "wacz"; + const filename = params["_query"].get("filename"); + + return { + dl: new Downloader({ + ...this.downloaderOpts(), + coll, + format, + filename, + pageList, + }), + }; + } + + async handleUpload(params: RouteMatch, request: Request, event: FetchEvent) { + const uploading = this.uploading; + + const prevUpload = uploading.get(params.coll); + + const { url, headers, abortUpload } = await request.json(); + + if (prevUpload && prevUpload.status === "uploading") { + if (abortUpload && prevUpload.abort) { + prevUpload.abort(); + return { aborted: true }; + } + return { error: "already_uploading" }; + } else if (abortUpload) { + return { error: "not_uploading" }; + } + + const { dl, error } = await this.getDownloader(params); + if (error) { + return error; + } + const dlResp = await dl.download(); + if (!(dlResp instanceof Response)) { + return dlResp; + } + const filename = dlResp.filename || ""; + + const abort = new AbortController(); + const signal = abort.signal; + + const counter = new CountingStream(dl.metadata.size, abort); + + const body = dlResp.body!.pipeThrough(counter.transformStream()); + + try { + const urlObj = new URL(url); + urlObj.searchParams.set("filename", filename || ""); + urlObj.searchParams.set("name", dl.metadata["title"] || filename || ""); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const fetchPromise = fetch(urlObj.href, { + method: "PUT", + headers, + duplex: "half", + body, + signal, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); + uploading.set(params.coll, counter); + if (event.waitUntil) { + event.waitUntil( + this.uploadFinished( + fetchPromise, + params.coll, + dl.metadata, + filename, + counter, + ), + ); + } + return { uploading: true }; + } catch (e: unknown) { + uploading.delete(params.coll); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return { error: "upload_failed", details: (e as any).toString() }; + } + } + + async uploadFinished( + fetchPromise: Promise, + collId: string, + metadata: Metadata, + filename: string, + counter: CountingStream, + ) { + try { + const resp = await fetchPromise; + const json = await resp.json(); + + console.log(`Upload finished for ${filename} ${collId}`); + + metadata.uploadTime = new Date().getTime(); + metadata.uploadId = json.id; + if (!metadata.mtime) { + metadata.mtime = metadata.uploadTime; + } + if (!metadata.ctime) { + metadata.ctime = metadata.uploadTime; + } + await this.collections.updateMetadata( + collId, + metadata as Record, + ); + counter.status = "done"; + } catch (e) { + console.log(`Upload failed for ${filename} ${collId}`); + console.log(e); + counter.status = counter.aborted ? "aborted" : "failed"; + } + } + + async deleteUpload(params: RouteMatch) { + const collId = params.coll; + + this.uploading.delete(collId); + + const coll = await this.collections.loadColl(collId); + + if (coll?.metadata) { + coll.metadata.uploadTime = null; + coll.metadata.uploadId = null; + await this.collections.updateMetadata(collId, coll.metadata); + return { deleted: true }; + } + + return { deleted: false }; + } + + async getUploadStatus(params: RouteMatch) { + let result: Metadata = {}; + const counter = this.uploading.get(params.coll); + + if (!counter) { + result = { status: "idle" }; + } else { + const { size, totalSize, status } = counter; + result = { status, size, totalSize }; + + if (status !== "uploading") { + this.uploading.delete(params.coll); + } + } + + const coll = await this.collections.loadColl(params.coll); + + if (coll?.metadata) { + result.uploadTime = coll.metadata.uploadTime; + result.uploadId = coll.metadata.uploadId; + result.ctime = coll.metadata.ctime; + result.mtime = coll.metadata.mtime; + } + + return result; + } + + async recordingPending(params: RouteMatch) { + const coll = await this.collections.loadColl(params.coll); + if (!coll) { + return { error: "collection_not_found" }; + } + + if (!(coll.store instanceof RecProxy)) { + return { error: "invalid_collection" }; + } + + const numPending = await coll.store.getCounter(); + + return { numPending }; + } + + async prepareColl(collId: string, request: Request) { + const coll = await this.collections.loadColl(collId); + if (!coll) { + return { error: "collection_not_found" }; + } + + const body = await this.setIPFSDaemonUrlFromBody(request); + + return { coll, body }; + } + + async setIPFSDaemonUrlFromBody(request: Request) { + let body; + + try { + body = await request.json(); + if (body.ipfsDaemonUrl) { + setAutoIPFSUrl(body.ipfsDaemonUrl); + } + } catch (_e: unknown) { + body = {}; + } + + return body; + } + + async startIpfsAdd(event: FetchEvent, request: Request, collId: string) { + const { coll, body } = await this.prepareColl(collId, request); + + const client = await self.clients.get(event.clientId); + + const p = runIPFSAdd( + collId, + coll, + client, + this.downloaderOpts(), + this.collections, + body, + ); + + if (event.waitUntil) { + event.waitUntil(p); + } + + try { + await p; + } catch (_e) { + return { error: "ipfs_not_available" }; + } + + return { collId }; + } + + async ipfsRemove(request: Request, collId: string) { + const { coll } = await this.prepareColl(collId, request); + + if (await ipfsRemove(coll)) { + await this.collections.updateMetadata(coll.name, coll.config.metadata); + return { removed: true }; + } + + return { removed: false }; + } + + async updatePageTitle(collId: string, request: Request) { + const json = await request.json(); + const { url, title } = json; + let { ts } = json; + + ts = tsToDate(ts).getTime(); + + const coll = await this.collections.loadColl(collId); + if (!coll) { + return { error: "collection_not_found" }; + } + + //await coll.store.db.init(); + + const result = await coll.store.lookupUrl(url, ts); + + if (!result) { + return { error: "page_not_found" }; + } + + // drop to second precision for comparison + const roundedTs = Math.floor(result.ts / 1000) * 1000; + if (url !== result.url || ts !== roundedTs) { + return { error: "no_exact_match" }; + } + + const page = await coll.store.db.getFromIndex("pages", "url", url); + if (!page) { + return { error: "page_not_found" }; + } + page.title = title; + await coll.store.db.put("pages", page); + + return { added: true }; + } + + async getPublicKey() { + const { signer } = this.downloaderOpts(); + const keys = await signer.loadKeys(); + if (!keys?.public) { + return {}; + } else { + return { publicKey: keys.public }; + } + } +} + +// =========================================================================== +async function runIPFSAdd( + collId: string, + coll: Collection, + client: Client | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + opts: any, + collections: SWCollections, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + replayOpts: any, +) { + let size = 0; + let totalSize = 0; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const sendMessage = (type: string, result: any = null) => { + if (client) { + client.postMessage({ + type, + collId, + size, + result, + totalSize, + }); + } + }; + + const { url, cid } = await ipfsAdd( + coll, + opts, + replayOpts, + (incSize: number, _totalSize: number) => { + size += incSize; + totalSize = _totalSize; + sendMessage("ipfsProgress"); + }, + ); + + const result = { cid, ipfsURL: url }; + + sendMessage("ipfsAdd", result); + + if (coll.config.metadata) { + await collections.updateMetadata(coll.name, coll.config.metadata); + } +} + +// =========================================================================== +class CountingStream { + totalSize: number; + status: string; + size = 0; + _abort?: AbortController; + aborted: boolean; + + constructor(totalSize?: number, abort?: AbortController) { + this.totalSize = totalSize || 0; + this.status = "uploading"; + this.size = 0; + this._abort = abort; + this.aborted = false; + } + + abort() { + if (this._abort) { + this._abort.abort(); + this.aborted = true; + } + } + + transformStream() { + const counterStream = this; + + return new TransformStream({ + start() { + counterStream.size = 0; + }, + + transform(chunk, controller) { + counterStream.size += chunk.length; + //console.log(`Uploaded: ${counterStream.size}`); + controller.enqueue(chunk); + }, + }); + } +} + +export { ExtAPI }; diff --git a/src/downloader.js b/src/downloader.ts similarity index 51% rename from src/downloader.js rename to src/downloader.ts index e99b859..d7e1ae3 100644 --- a/src/downloader.js +++ b/src/downloader.ts @@ -5,11 +5,136 @@ import { Deflate } from "pako"; import { v5 as uuidv5 } from "uuid"; import { createSHA256 } from "hash-wasm"; +import { type IHasher } from "hash-wasm/dist/lib/WASMInterface.js"; import { getSurt, WARCRecord, WARCSerializer } from "warcio"; -import { getTSMillis, getStatusText, digestMessage } from "@webrecorder/wabac/src/utils.js"; - +import { + getTSMillis, + getStatusText, + digestMessage, + type Collection, + type ArchiveDB, + type ResourceEntry, +} from "@webrecorder/wabac/swlib"; +import { type DataSignature, type Signer } from "./keystore"; +import { type ExtPageEntry } from "./recproxy"; + +export type SizeCallback = (size: number) => void; + +export type ResponseWithFilename = Response & { + filename?: string; +}; + +type ClientZipEntry = { + name: string; + lastModified: Date; + input: AsyncGenerator; +}; + +type FileStats = { + filename: string; + size: number; + hash?: string; +}; + +export type DownloaderOpts = { + coll: Collection; + format: string; + filename?: string; + pageList?: string[]; + signer?: Signer; + softwareString?: string; + gzip?: boolean; + uuidNamespace?: string; + markers?: Markers; +}; + +export type Markers = { + ZIP?: Uint8Array; + WARC_PAYLOAD?: Uint8Array; + WARC_GROUP?: Uint8Array; +}; + +type DLResourceEntry = ResourceEntry & { + offset?: number; + length?: number; + timestamp?: string; + skipped?: boolean; + text?: string; + + pageId: string; + digest: string; +}; + +type CDXJEntry = { + url: string; + digest: string; + mime: string; + offset: number; + length: number; + recordDigest: string; + status: number; + + method?: string; + filename?: string; + requestBody?: string; +}; + +type DLPageData = { + title: string; + url: string; + id: string; + size: number; + ts: string; + + favIconUrl?: string; + text?: string; +}; + +type Gen = + | AsyncGenerator + | AsyncGenerator + | Generator + | Generator; + +type WARCVersion = "WARC/1.0" | "WARC/1.1"; + +type DigestCache = { + url: string; + date: string; + payloadDigest?: string; +}; + +type DataPackageJSON = { + profile: string; + resources: { + name: string; + path: string; + hash: string; + bytes: number; + }[]; + + wacz_version: string; + software: string; + created: string; + + title?: string; + description?: string; + modified?: string; +}; + +export type Metadata = { + uploadId?: string; + uploadTime?: number; + ctime?: number; + mtime?: number; + size?: number; + title?: string; + desc?: string; + status?: string; + totalSize?: number; +}; // =========================================================================== const WACZ_VERSION = "1.1.1"; @@ -28,11 +153,17 @@ const encoder = new TextEncoder(); const EMPTY = new Uint8Array([]); -async function* getPayload(payload) { +async function* getPayload(payload: Uint8Array) { yield payload; } -async function* hashingGen(gen, stats, hasher, sizeCallback, zipMarker) { +async function* hashingGen( + gen: Gen, + stats: FileStats, + hasher: IHasher, + sizeCallback: SizeCallback | null, + zipMarker?: Uint8Array, +) { stats.size = 0; hasher.init(); @@ -42,7 +173,7 @@ async function* hashingGen(gen, stats, hasher, sizeCallback, zipMarker) { } for await (let chunk of gen) { - if (typeof(chunk) === "string") { + if (typeof chunk === "string") { chunk = encoder.encode(chunk); } @@ -62,117 +193,171 @@ async function* hashingGen(gen, stats, hasher, sizeCallback, zipMarker) { } // =========================================================================== -class Downloader -{ - constructor({coll, format = "wacz", filename = null, pageList = null, signer = null, - softwareString = null, gzip = true, uuidNamespace = null, markers = null}) { - +class Downloader { + db: ArchiveDB; + pageList: string[] | null; + collId: string; + metadata: Metadata; + gzip: boolean; + + markers: Markers; + warcName: string; + alreadyDecoded: boolean; + + softwareString: string; + uuidNamespace: string; + + createdDateDt: Date; + createdDate: string; + modifiedDate: string | null; + + format: string; + warcVersion: WARCVersion; + + digestOpts: { + algo: string; + prefix: string; + base32?: boolean; + }; + + filename: string; + + signer: Signer | null; + + offset = 0; + firstResources: ResourceEntry[] = []; + textResources: DLResourceEntry[] = []; + cdxjLines: string[] = []; + + // compressed index (idx) entries + indexLines: string[] = []; + + digestsVisted: Record = {}; + fileHasher: IHasher | null = null; + recordHasher: IHasher | null = null; + + datapackageDigest = ""; + + fileStats: FileStats[] = []; + hashType = ""; + + lastUrl?: string; + lastPageId?: string; + + constructor({ + coll, + format = "wacz", + filename, + pageList, + signer, + softwareString, + gzip = true, + uuidNamespace, + markers, + }: DownloaderOpts) { this.db = coll.store; - this.pageList = pageList; + this.pageList = pageList || null; this.collId = coll.name; - this.metadata = coll.config.metadata; + this.metadata = coll.config.metadata || {}; this.gzip = gzip; this.markers = markers || {}; this.warcName = this.gzip ? "data.warc.gz" : "data.warc"; - this.alreadyDecoded = !coll.config.decode && !coll.config.loadUrl; + this.alreadyDecoded = !coll.config["decode"] && !coll.config["loadUrl"]; this.softwareString = softwareString || "ArchiveWeb.page"; this.uuidNamespace = uuidNamespace || DEFAULT_UUID_NAMESPACE; - this.createdDateDt = new Date(coll.config.ctime); + this.createdDateDt = new Date(coll.config.ctime!); this.createdDate = this.createdDateDt.toISOString(); - this.modifiedDate = coll.config.metadata.mtime ? new Date(coll.config.metadata.mtime).toISOString() : null; + this.modifiedDate = coll.config.metadata!.mtime + ? new Date(coll.config.metadata!.mtime).toISOString() + : null; this.format = format; - this.warcVersion = (format === "warc1.0") ? "WARC/1.0" : "WARC/1.1"; + this.warcVersion = format === "warc1.0" ? "WARC/1.0" : "WARC/1.1"; if (format === "warc1.0") { - this.digestOpts = {algo: "sha-1", prefix: "sha1:", base32: true}; + this.digestOpts = { algo: "sha-1", prefix: "sha1:", base32: true }; } else { - this.digestOpts = {algo: "sha-256", prefix: "sha256:"}; + this.digestOpts = { algo: "sha-256", prefix: "sha256:" }; } - this.filename = filename; - // determine filename from title, if it exists - if (!this.filename && coll.config.metadata.title) { - this.filename = encodeURIComponent(coll.config.metadata.title.toLowerCase().replace(/\s/g, "-")); - } - - if (!this.filename) { - this.filename = "webarchive"; + if (!filename && coll.config.metadata!.title) { + filename = encodeURIComponent( + coll.config.metadata!.title.toLowerCase().replace(/\s/g, "-"), + ); } - this.offset = 0; - this.firstResources = []; - this.textResources = []; - this.cdxjLines = []; - - // compressed index (idx) entries - this.indexLines = []; - - this.digestsVisted = {}; - this.fileHasher = null; - this.recordHasher = null; - - this.datapackageDigest = null; - this.signer = signer; + if (!filename) { + filename = "webarchive"; + } + this.filename = filename; - this.fileStats = []; + this.signer = signer || null; } - download(sizeCallback = null) { + async download(sizeCallback: SizeCallback | null = null) { switch (this.format) { - case "wacz": - return this.downloadWACZ(this.filename, sizeCallback); + case "wacz": + return this.downloadWACZ(this.filename, sizeCallback); - case "warc": - case "warc1.0": - return this.downloadWARC(this.filename, sizeCallback); + case "warc": + case "warc1.0": + return this.downloadWARC(this.filename, sizeCallback); - default: - return {"error": "invalid 'format': must be wacz or warc"}; + default: + return { error: "invalid 'format': must be wacz or warc" }; } } - downloadWARC(filename, sizeCallback = null) { + downloadWARC(filename: string, sizeCallback: SizeCallback | null = null) { filename = (filename || "webarchive").split(".")[0] + ".warc"; + // eslint-disable-next-line @typescript-eslint/no-this-alias const dl = this; const rs = new ReadableStream({ - start(controller) { - dl.queueWARC(controller, filename, sizeCallback); - } + async start(controller) { + await dl.queueWARC(controller, filename, sizeCallback); + }, }); const headers = { "Content-Disposition": `attachment; filename="${filename}"`, - "Content-Type": "application/octet-stream" + "Content-Type": "application/octet-stream", }; - const resp = new Response(rs, {headers}); + const resp: ResponseWithFilename = new Response(rs, { headers }); resp.filename = filename; return resp; } - async loadResourcesBlock(start = []) { - return await this.db.db.getAll("resources", IDBKeyRange.lowerBound(start, true), RESOURCE_BATCH_SIZE); + async loadResourcesBlock( + start: [string, number] | [] = [], + ): Promise { + return await this.db.db!.getAll( + "resources", + IDBKeyRange.lowerBound(start, true), + RESOURCE_BATCH_SIZE, + ); } - async* iterResources(resources) { - let start = []; + async *iterResources(resources: ResourceEntry[]) { + let start: [string, number] | [] = []; //let count = 0; while (resources.length) { - const last = resources[resources.length - 1]; + const last: ResourceEntry = resources[resources.length - 1]!; if (this.pageList) { - resources = resources.filter((res) => this.pageList.includes(res.pageId)); + resources = resources.filter((res) => + this.pageList!.includes(res.pageId || ""), + ); } //count += resources.length; yield* resources; @@ -185,7 +370,11 @@ class Downloader // } } - async queueWARC(controller, filename, sizeCallback) { + async queueWARC( + controller: ReadableStreamDefaultController, + filename: string, + sizeCallback: SizeCallback | null, + ) { this.firstResources = await this.loadResourcesBlock(); for await (const chunk of this.generateWARC(filename)) { @@ -205,8 +394,13 @@ class Downloader controller.close(); } - addFile(zip, filename, generator, sizeCallback/*, compressed = false*/) { - const stats = {filename, size: 0}; + addFile( + zip: ClientZipEntry[], + filename: string, + generator: Gen, + sizeCallback: SizeCallback | null, + ) { + const stats: FileStats = { filename, size: 0 }; if (filename !== DATAPACKAGE_FILENAME && filename !== DIGEST_FILENAME) { this.fileStats.push(stats); @@ -215,59 +409,88 @@ class Downloader zip.push({ name: filename, lastModified: this.createdDateDt, - input: hashingGen(generator, stats, this.fileHasher, sizeCallback, this.markers.ZIP) + input: hashingGen( + generator, + stats, + this.fileHasher!, + sizeCallback, + this.markers.ZIP, + ), }); } - recordDigest(data) { - this.recordHasher.init(); - this.recordHasher.update(data); - return this.hashType + ":" + this.recordHasher.digest("hex"); + recordDigest(data: Uint8Array | string) { + this.recordHasher!.init(); + this.recordHasher!.update(data); + return this.hashType + ":" + this.recordHasher!.digest("hex"); } - getWARCRecordUUID(name) { + getWARCRecordUUID(name: string) { return ``; } - async downloadWACZ(filename, sizeCallback) { + async downloadWACZ(filename: string, sizeCallback: SizeCallback | null) { filename = (filename || "webarchive").split(".")[0] + ".wacz"; this.fileHasher = await createSHA256(); this.recordHasher = await createSHA256(); this.hashType = "sha256"; - const zip = []; + const zip: ClientZipEntry[] = []; this.firstResources = await this.loadResourcesBlock(); - this.addFile(zip, "pages/pages.jsonl", this.generatePages(), sizeCallback, true); - this.addFile(zip, `archive/${this.warcName}`, this.generateWARC(filename + `#/archive/${this.warcName}`, true), sizeCallback, false); + this.addFile(zip, "pages/pages.jsonl", this.generatePages(), sizeCallback); + this.addFile( + zip, + `archive/${this.warcName}`, + this.generateWARC(filename + `#/archive/${this.warcName}`, true), + sizeCallback, + ); //this.addFile(zip, "archive/text.warc", this.generateTextWARC(filename + "#/archive/text.warc"), false); // don't use compressed index if we'll have a single block, need to have at least enough for 2 blocks - if (this.firstResources.length < (2 * LINES_PER_BLOCK)) { - this.addFile(zip, "indexes/index.cdx", this.generateCDX(), sizeCallback, true); + if (this.firstResources.length < 2 * LINES_PER_BLOCK) { + this.addFile(zip, "indexes/index.cdx", this.generateCDX(), sizeCallback); } else { - this.addFile(zip, "indexes/index.cdx.gz", this.generateCompressedCDX("index.cdx.gz"), sizeCallback, false); - this.addFile(zip, "indexes/index.idx", this.generateIDX(), sizeCallback, true); + this.addFile( + zip, + "indexes/index.cdx.gz", + this.generateCompressedCDX("index.cdx.gz"), + sizeCallback, + ); + this.addFile(zip, "indexes/index.idx", this.generateIDX(), sizeCallback); } - - this.addFile(zip, DATAPACKAGE_FILENAME, this.generateDataPackage(), sizeCallback); - this.addFile(zip, DIGEST_FILENAME, this.generateDataManifest(), sizeCallback); + this.addFile( + zip, + DATAPACKAGE_FILENAME, + this.generateDataPackage(), + sizeCallback, + ); + + this.addFile( + zip, + DIGEST_FILENAME, + this.generateDataManifest(), + sizeCallback, + ); const headers = { "Content-Disposition": `attachment; filename="${filename}"`, - "Content-Type": "application/zip" + "Content-Type": "application/zip", }; - let rs = makeZip(zip); - const response = new Response(rs, {headers}); + const rs = makeZip(zip); + const response: ResponseWithFilename = new Response(rs, { headers }); response.filename = filename; return response; } - async* generateWARC(filename, digestRecordAndCDX = false) { + async *generateWARC( + filename: string, + digestRecordAndCDX = false, + ): AsyncGenerator { try { let offset = 0; @@ -282,7 +505,8 @@ class Downloader yield this.markers.WARC_GROUP; } - for await (const resource of this.iterResources(this.firstResources)) { + for await (const res of this.iterResources(this.firstResources)) { + const resource: DLResourceEntry = res as DLResourceEntry; resource.offset = offset; const records = await this.createWARCRecord(resource); if (!records) { @@ -291,8 +515,8 @@ class Downloader } // response record - const responseData = {length: 0}; - yield* this.emitRecord(records[0], digestRecordAndCDX, responseData); + const responseData: { length: number; digest?: string } = { length: 0 }; + yield* this.emitRecord(records[0]!, digestRecordAndCDX, responseData); offset += responseData.length; resource.length = responseData.length; if (digestRecordAndCDX && !resource.recordDigest) { @@ -302,8 +526,8 @@ class Downloader // request record, if any if (records.length > 1) { - const requestData = {length: 0}; - yield* this.emitRecord(records[1], false, requestData); + const requestData = { length: 0 }; + yield* this.emitRecord(records[1]!, false, requestData); offset += requestData.length; } @@ -320,28 +544,37 @@ class Downloader } } - async* emitRecord(record, doDigest, output) { - const opts = {gzip: this.gzip, digest: this.digestOpts}; + async *emitRecord( + record: WARCRecord, + doDigest: boolean, + output: { length: number; digest?: string }, + ) { + const opts = { gzip: this.gzip, digest: this.digestOpts }; const s = new WARCSerializer(record, opts); const chunks = []; if (doDigest) { - this.recordHasher.init(); + this.recordHasher!.init(); } for await (const chunk of s) { if (doDigest) { - this.recordHasher.update(chunk); + this.recordHasher!.update(chunk as Uint8Array); } chunks.push(chunk); output.length += chunk.length; } if (doDigest) { - output.digest = this.hashType + ":" + this.recordHasher.digest("hex"); + output.digest = this.hashType + ":" + this.recordHasher!.digest("hex"); } - if (!this.gzip && this.markers.WARC_PAYLOAD && record.warcType !== "request" && (chunks.length === 5 || chunks.length === 4)) { + if ( + !this.gzip && + this.markers.WARC_PAYLOAD && + record.warcType !== "request" && + (chunks.length === 5 || chunks.length === 4) + ) { if (chunks.length === 5) { yield chunks[0]; yield chunks[1]; @@ -369,7 +602,7 @@ class Downloader } } - async* generateTextWARC(filename) { + async *generateTextWARC(filename: string) { try { let offset = 0; @@ -392,15 +625,15 @@ class Downloader } } - getCDXJ(resource, filename) { - const data = { + getCDXJ(resource: DLResourceEntry, filename: string): string { + const data: CDXJEntry = { url: resource.url, digest: resource.digest, - mime: resource.mime, - offset: resource.offset, - length: resource.length, - recordDigest: resource.recordDigest, - status: resource.status + mime: resource.mime!, + offset: resource.offset!, + length: resource.length!, + recordDigest: resource.recordDigest!, + status: resource.status!, }; if (filename) { @@ -410,7 +643,7 @@ class Downloader if (resource.method && resource.method !== "GET") { const m = resource.url.match(SPLIT_REQUEST_Q_RX); if (m) { - data.url = m[1]; + data.url = m[1]!; // resource.requestBody is the raw payload, use the converted one from the url for the cdx data.requestBody = m[2]; } @@ -426,40 +659,42 @@ class Downloader yield* this.cdxjLines; } - *generateCompressedCDX(filename) { + *generateCompressedCDX(filename: string) { let offset = 0; - let chunkDeflater = null; + let chunkDeflater: Deflate | null = null; let count = 0; - let key = null; + let key = ""; + // eslint-disable-next-line @typescript-eslint/no-this-alias const dl = this; const finishChunk = () => { - const data = chunkDeflater.result; + const data = chunkDeflater!.result as Uint8Array; const length = data.length; const digest = dl.recordDigest(data); - - const idx = key + " " + JSON.stringify({offset, length, digest, filename}); + + const idx = + key + " " + JSON.stringify({ offset, length, digest, filename }); dl.indexLines.push(idx); - + offset += length; - + chunkDeflater = null; count = 0; - key = null; - + key = ""; + return data; }; for (const cdx of this.generateCDX()) { if (!chunkDeflater) { - chunkDeflater = new Deflate({gzip: true}); + chunkDeflater = new Deflate({ gzip: true }); } if (!key) { - key = cdx.split(" {", 1)[0]; + key = cdx.split(" {", 1)[0] || ""; } if (++count === LINES_PER_BLOCK) { @@ -476,12 +711,15 @@ class Downloader } } - async* generateDataManifest() { + async *generateDataManifest() { const hash = this.datapackageDigest; const path = DATAPACKAGE_FILENAME; - const data = {path, hash}; + const data: { path: string; hash: string; signedData?: DataSignature } = { + path, + hash, + }; if (this.signer) { try { @@ -489,7 +727,7 @@ class Downloader this.signer.close(); this.signer = null; - } catch(e) { + } catch (e) { // failed to sign console.log(e); } @@ -500,23 +738,24 @@ class Downloader yield res; } - - async* generateDataPackage() { - const root = {}; - - root.profile = "data-package"; - - root.resources = this.fileStats.map((stats) => { - const path = stats.filename; - return { - name: path.slice(path.lastIndexOf("/") + 1), - path, - hash: this.hashType + ":" + stats.hash, - bytes: stats.size, - }; - }); - - root.wacz_version = WACZ_VERSION; + async *generateDataPackage() { + const root: DataPackageJSON = { + profile: "data-package", + + resources: this.fileStats.map((stats) => { + const path = stats.filename; + return { + name: path.slice(path.lastIndexOf("/") + 1), + path, + hash: this.hashType + ":" + stats.hash, + bytes: stats.size, + }; + }), + + wacz_version: WACZ_VERSION, + software: this.softwareString, + created: this.createdDate, + }; if (this.metadata.title) { root.title = this.metadata.title; @@ -525,32 +764,39 @@ class Downloader root.description = this.metadata.desc; } - root.software = this.softwareString; - root.created = this.createdDate; if (this.modifiedDate) { root.modified = this.modifiedDate; } - //root.config = {decodeResponses: false}; const datapackageText = JSON.stringify(root, null, 2); this.datapackageDigest = this.recordDigest(datapackageText); yield datapackageText; } - async* generatePages() { - const pageIter = this.pageList ? await this.db.getPages(this.pageList) : await this.db.getAllPages(); - - yield JSON.stringify({"format": "json-pages-1.0", "id": "pages", "title": "All Pages", "hasText": true}); + async *generatePages() { + const pageIter: ExtPageEntry[] = ( + this.pageList + ? await this.db.getPages(this.pageList) + : await this.db.getAllPages() + ) as ExtPageEntry[]; + + yield JSON.stringify({ + format: "json-pages-1.0", + id: "pages", + title: "All Pages", + hasText: true, + }); for (const page of pageIter) { const ts = new Date(page.ts).toISOString(); - const pageData = { + const pageData: DLPageData = { title: page.title, url: page.url, id: page.id, size: page.size, - ts}; + ts, + }; if (page.favIconUrl) { pageData.favIconUrl = page.favIconUrl; @@ -562,7 +808,13 @@ class Downloader yield "\n" + JSON.stringify(pageData); if (page.text) { - this.textResources.push({url: page.url, ts: page.ts, text: page.text}); + this.textResources.push({ + url: page.url, + ts: page.ts, + text: page.text, + pageId: page.id, + digest: "", + }); } } } @@ -578,34 +830,43 @@ class Downloader } } */ - async* generateIDX() { + async *generateIDX() { yield this.indexLines.join("\n"); } - async createWARCInfo(filename) { + async createWARCInfo(filename: string) { const warcVersion = this.warcVersion; const type = "warcinfo"; const info = { - "software": this.softwareString, - "format": (warcVersion === "WARC/1.0") ? "WARC File Format 1.0" : "WARC File Format 1.1", - "isPartOf": this.metadata.title || this.collId, + software: this.softwareString, + format: + warcVersion === "WARC/1.0" + ? "WARC File Format 1.0" + : "WARC File Format 1.1", + isPartOf: this.metadata["title"] || this.collId, }; //info["json-metadata"] = JSON.stringify(metadata); const warcHeaders = { - "WARC-Record-ID": this.getWARCRecordUUID(JSON.stringify(info)) + "WARC-Record-ID": this.getWARCRecordUUID(JSON.stringify(info)), }; const date = this.createdDate; - const record = await WARCRecord.createWARCInfo({filename, type, date, warcHeaders, warcVersion}, info); - const buffer = await WARCSerializer.serialize(record, {gzip: this.gzip, digest: this.digestOpts}); + const record = WARCRecord.createWARCInfo( + { filename, type, date, warcHeaders, warcVersion }, + info, + ); + const buffer = await WARCSerializer.serialize(record, { + gzip: this.gzip, + digest: this.digestOpts, + }); return buffer; } - fixupHttpHeaders(headersMap, length) { + fixupHttpHeaders(headersMap: Record, length: number) { // how many headers are we parsing here const numHeaders = this.alreadyDecoded ? 3 : 1; @@ -613,19 +874,19 @@ class Downloader for (const [name] of Object.entries(headersMap)) { const lowerName = name.toLowerCase(); switch (lowerName) { - case "content-encoding": - case "transfer-encoding": - if (this.alreadyDecoded) { - headersMap["x-orig-" + name] = headersMap[name]; - delete headersMap[name]; + case "content-encoding": + case "transfer-encoding": + if (this.alreadyDecoded) { + headersMap["x-orig-" + name] = headersMap[name]!; + delete headersMap[name]; + ++count; + } + break; + + case "content-length": + headersMap[name] = "" + length; ++count; - } - break; - - case "content-length": - headersMap[name] = "" + length; - ++count; - break; + break; } if (count === numHeaders) { break; @@ -633,7 +894,7 @@ class Downloader } } - async createWARCRecord(resource) { + async createWARCRecord(resource: DLResourceEntry) { let url = resource.url; const date = new Date(resource.ts).toISOString(); resource.timestamp = getTSMillis(date); @@ -642,12 +903,12 @@ class Downloader const pageId = resource.pageId; - let payload = resource.payload; - let type = null; + let payload: Uint8Array | null | undefined = resource.payload; + let type: "response" | "request" | "resource" | "revisit"; let refersToUrl, refersToDate; let refersToDigest; - let storeDigest = null; + let storeDigest: DigestCache | null = null; let method = "GET"; let requestBody; @@ -655,9 +916,17 @@ class Downloader // non-GET request/response: // if original request body + original requestURL is preserved, write that with original method // otherwise, just serialize the converted-to-GET form - if (resource.method && resource.method !== "GET" && resource.requestBody && resource.requestUrl) { + if ( + resource.method && + resource.method !== "GET" && + resource.requestBody && + resource.requestUrl + ) { // ensure payload is an arraybuffer - requestBody = typeof(resource.requestBody) === "string" ? encoder.encode(resource.requestBody) : resource.requestBody; + requestBody = + typeof resource.requestBody === "string" + ? encoder.encode(resource.requestBody) + : resource.requestBody; method = resource.method; url = resource.requestUrl; } else { @@ -672,7 +941,11 @@ class Downloader if (resource.digest && digestOriginal) { // if exact resource in a row, and same page, then just skip instead of writing revisit - if (url === this.lastUrl && method === "GET" && pageId === this.lastPageId) { + if ( + url === this.lastUrl && + method === "GET" && + pageId === this.lastPageId + ) { //console.log("Skip Dupe: " + url); return null; } @@ -684,7 +957,6 @@ class Downloader refersToUrl = digestOriginal.url; refersToDate = digestOriginal.date; refersToDigest = digestOriginal.payloadDigest || resource.digest; - } else if (resource.origURL && resource.origTS) { if (!resource.digest || !digestOriginal) { //console.log("Skip fuzzy resource with no digest"); @@ -698,11 +970,13 @@ class Downloader refersToUrl = resource.origURL; refersToDate = new Date(resource.origTS).toISOString(); refersToDigest = digestOriginal.payloadDigest || resource.digest; - } else { type = "response"; if (!payload) { - payload = await this.db.loadPayload(resource); + payload = (await this.db.loadPayload( + resource, + {}, + )) as Uint8Array | null; } if (!payload) { @@ -711,7 +985,7 @@ class Downloader } if (method === "GET") { - storeDigest = {url, date}; + storeDigest = { url, date }; this.digestsVisted[resource.digest] = storeDigest; } } @@ -721,8 +995,12 @@ class Downloader const statusline = `HTTP/1.1 ${status} ${statusText}`; - const warcHeaders = { - "WARC-Record-ID": this.getWARCRecordUUID(type + ":" + resource.timestamp + "/" + resource.url), + const responseRecordId = this.getWARCRecordUUID( + type + ":" + resource.timestamp + "/" + resource.url, + ); + + const warcHeaders: Record = { + "WARC-Record-ID": responseRecordId, }; if (pageId) { @@ -740,15 +1018,26 @@ class Downloader // remove encoding, set content-length as encoding never preserved in browser-based capture this.fixupHttpHeaders(httpHeaders, payload.length); - const record = await WARCRecord.create({ - url, date, type, warcVersion, warcHeaders, statusline, httpHeaders, - refersToUrl, refersToDate}, getPayload(payload)); + const record = WARCRecord.create( + { + url, + date, + type, + warcVersion, + warcHeaders, + statusline, + httpHeaders, + refersToUrl, + refersToDate, + }, + getPayload(payload), + ); //const buffer = await WARCSerializer.serialize(record, {gzip: this.gzip, digest: this.digestOpts}); - if (!resource.digest) { + if (!resource.digest && record.warcPayloadDigest) { resource.digest = record.warcPayloadDigest; } - if (storeDigest) { + if (storeDigest && record.warcPayloadDigest) { storeDigest.payloadDigest = record.warcPayloadDigest; } @@ -759,21 +1048,29 @@ class Downloader if (resource.reqHeaders) { const type = "request"; - const reqWarcHeaders = { - "WARC-Record-ID": this.getWARCRecordUUID(type + ":" + resource.timestamp + "/" + resource.url), + const reqWarcHeaders: Record = { + "WARC-Record-ID": this.getWARCRecordUUID( + type + ":" + resource.timestamp + "/" + resource.url, + ), "WARC-Page-ID": pageId, - "WARC-Concurrent-To": record.warcHeader("WARC-Record-ID"), + "WARC-Concurrent-To": responseRecordId, }; const urlParsed = new URL(url); const statusline = `${method} ${url.slice(urlParsed.origin.length)} HTTP/1.1`; - const reqRecord = await WARCRecord.create({ - url, date, warcVersion, type, - warcHeaders: reqWarcHeaders, - httpHeaders: resource.reqHeaders, - statusline, - }, getPayload(requestBody)); + const reqRecord = WARCRecord.create( + { + url, + date, + warcVersion, + type, + warcHeaders: reqWarcHeaders, + httpHeaders: resource.reqHeaders, + statusline, + }, + getPayload(requestBody), + ); //records.push(await WARCSerializer.serialize(reqRecord, {gzip: this.gzip, digest: this.digestOpts})); records.push(reqRecord); @@ -782,7 +1079,7 @@ class Downloader return records; } - async createTextWARCRecord(resource) { + async createTextWARCRecord(resource: DLResourceEntry) { const date = new Date(resource.ts).toISOString(); const timestamp = getTSMillis(date); resource.timestamp = timestamp; @@ -790,15 +1087,21 @@ class Downloader resource.url = url; const type = "resource"; - const warcHeaders = {"Content-Type": "text/plain; charset=\"UTF-8\""}; + const warcHeaders = { "Content-Type": 'text/plain; charset="UTF-8"' }; const warcVersion = this.warcVersion; const payload = getPayload(encoder.encode(resource.text)); - const record = await WARCRecord.create({url, date, warcHeaders, warcVersion, type}, payload); + const record = WARCRecord.create( + { url, date, warcHeaders, warcVersion, type }, + payload, + ); - const buffer = await WARCSerializer.serialize(record, {gzip: this.gzip, digest: this.digestOpts}); - if (!resource.digest) { + const buffer = await WARCSerializer.serialize(record, { + gzip: this.gzip, + digest: this.digestOpts, + }); + if (!resource.digest && record.warcPayloadDigest) { resource.digest = record.warcPayloadDigest; } return buffer; @@ -806,4 +1109,3 @@ class Downloader } export { Downloader }; - diff --git a/src/globals.d.ts b/src/globals.d.ts new file mode 100644 index 0000000..c4ff7df --- /dev/null +++ b/src/globals.d.ts @@ -0,0 +1,8 @@ +declare const __SW_NAME__: string; +declare const __WARCIO_VERSION__: string; +declare const __AWP_VERSION__: string; +declare const __VERSION__: string; +declare const __WEB3_STORAGE_TOKEN__: string; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +declare type TODOFixMe = any; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index e2d2921..0000000 --- a/src/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export { Signer } from "./keystore.js"; -export { RecordingCollections } from "./recproxy.js"; -export { Downloader } from "./downloader.js"; -export { ExtAPI } from "./api.js"; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..2e95b6f --- /dev/null +++ b/src/index.ts @@ -0,0 +1,4 @@ +export { Signer } from "./keystore"; +export { RecordingCollections } from "./recproxy"; +export { Downloader } from "./downloader"; +export { ExtAPI } from "./api"; diff --git a/src/ipfsutils.js b/src/ipfsutils.ts similarity index 60% rename from src/ipfsutils.js rename to src/ipfsutils.ts index d930e99..3dc8f8f 100644 --- a/src/ipfsutils.js +++ b/src/ipfsutils.ts @@ -1,24 +1,53 @@ -import { Downloader } from "./downloader.js"; +import { type CollMetadata, type Collection } from "@webrecorder/wabac/swlib"; +import { Downloader, type DownloaderOpts, type Markers } from "./downloader"; +// @ts-expect-error no types import { create as createAutoIPFS } from "auto-js-ipfs"; import * as UnixFS from "@ipld/unixfs"; import { CarWriter } from "@ipld/car/writer"; import Queue from "p-queue"; -// eslint-disable-next-line no-undef -const autoipfsOpts = {web3StorageToken: __WEB3_STORAGE_TOKEN__}; +import { type Link } from "@ipld/unixfs/file/layout/queue"; +import { type FileLink } from "@ipld/unixfs/directory"; -let autoipfs = null; +const autoipfsOpts = { + web3StorageToken: __WEB3_STORAGE_TOKEN__, + daemonURL: "", +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let autoipfs: any = null; + +type ReplayOpts = { + filename?: string; + customSplits?: boolean; + gzip?: boolean; + replayBaseUrl?: string; + showEmbed?: boolean; + pageUrl?: string; + pageTitle?: string; + deepLink?: boolean; + loading?: boolean; +}; -export async function setAutoIPFSUrl(url) { +type MetadataWithIPFS = CollMetadata & { + ipfsPins?: { url: string; cid: string }[] | null; +}; + +export async function setAutoIPFSUrl(url: string) { if (autoipfsOpts.daemonURL !== url) { autoipfs = null; } autoipfsOpts.daemonURL = url; } -export async function ipfsAdd(coll, downloaderOpts = {}, replayOpts = {}, progress = null) { +export async function ipfsAdd( + coll: Collection, + downloaderOpts: DownloaderOpts, + replayOpts: ReplayOpts = {}, + progress: (incSize: number, totalSize: number) => void, +) { if (!autoipfs) { autoipfs = await createAutoIPFS(autoipfsOpts); } @@ -29,16 +58,22 @@ export async function ipfsAdd(coll, downloaderOpts = {}, replayOpts = {}, progre const ZIP = new Uint8Array([]); const WARC_PAYLOAD = new Uint8Array([]); const WARC_GROUP = new Uint8Array([]); - downloaderOpts.markers = {ZIP, WARC_PAYLOAD, WARC_GROUP}; + downloaderOpts.markers = { ZIP, WARC_PAYLOAD, WARC_GROUP }; } const gzip = replayOpts.gzip !== undefined ? replayOpts.gzip : true; - const dl = new Downloader({...downloaderOpts, coll, filename, gzip}); + const dl = new Downloader({ ...downloaderOpts, coll, filename, gzip }); const dlResponse = await dl.download(); - if (!coll.config.metadata.ipfsPins) { - coll.config.metadata.ipfsPins = []; + if (!(dlResponse instanceof Response)) { + throw new Error(dlResponse.error); + } + + const metadata: MetadataWithIPFS = coll.config.metadata || {}; + + if (!metadata.ipfsPins) { + metadata.ipfsPins = []; } let concur; @@ -60,7 +95,7 @@ export async function ipfsAdd(coll, downloaderOpts = {}, replayOpts = {}, progre const { readable, writable } = new TransformStream( {}, - UnixFS.withCapacity(capacity) + UnixFS.withCapacity(capacity), ); const baseUrl = replayOpts.replayBaseUrl || self.location.href; @@ -72,33 +107,38 @@ export async function ipfsAdd(coll, downloaderOpts = {}, replayOpts = {}, progre try { favicon = await fetchBuffer("icon.png", baseUrl); - } catch (e) { + } catch (_e) { console.warn("Couldn't load favicon"); } - const htmlContent = getReplayHtml(dlResponse.filename, replayOpts); + const htmlContent = getReplayHtml(dlResponse.filename!, replayOpts); let totalSize = 0; - if (coll.config && coll.config.metadata && coll.config.metadata.size) { - totalSize = coll.config.metadata.size + - swContent.length + uiContent.length + (favicon ? favicon.length : 0) + htmlContent.length; + if (coll.config.metadata?.size) { + totalSize = + coll.config.metadata.size + + swContent.length + + uiContent.length + + (favicon ? favicon.length : 0) + + htmlContent.length; } progress(0, totalSize); - let url, cid; + let url = ""; + let cid = ""; - let reject = null; + let reject: ((reason?: string) => void) | null = null; - const p2 = new Promise((res, rej) => reject = rej); + const p2 = new Promise((res, rej) => (reject = rej)); const p = readable .pipeThrough(new ShardingStream(shardSize)) - .pipeThrough(new ShardStoringStream(autoipfs, concur, reject)) + .pipeThrough(new ShardStoringStream(autoipfs, concur, reject!)) .pipeTo( new WritableStream({ - write: (res) => { + write: (res: { url: string; cid: string; size: number }) => { if (res.url && res.cid) { url = res.url; cid = res.cid; @@ -107,77 +147,98 @@ export async function ipfsAdd(coll, downloaderOpts = {}, replayOpts = {}, progre progress(res.size, totalSize); } }, - }) + }), ); - ipfsGenerateCar( - writable, - dlResponse.filename, dlResponse.body, - swContent, uiContent, htmlContent, replayOpts, - downloaderOpts.markers, favicon, - ); + ipfsGenerateCar( + writable, + dlResponse.filename || "", + dlResponse.body!, + swContent, + uiContent, + htmlContent, + replayOpts, + downloaderOpts.markers!, + favicon, + ).catch((e: unknown) => console.log("generate car failed", e)); await Promise.race([p, p2]); - const res = {cid: cid.toString(), url}; + const res = { cid: cid.toString(), url }; - coll.config.metadata.ipfsPins.push(res); + metadata.ipfsPins.push(res); console.log("ipfs cid added " + url); return res; } -export async function ipfsRemove(coll) { +export async function ipfsRemove(coll: Collection) { if (!autoipfs) { autoipfs = await createAutoIPFS(autoipfsOpts); } - if (coll.config.metadata.ipfsPins) { + const metadata: MetadataWithIPFS = coll.config.metadata || {}; - for (const {url} of coll.config.metadata.ipfsPins) { + if (metadata.ipfsPins) { + for (const { url } of metadata.ipfsPins) { try { await autoipfs.clear(url); - } catch (e) { + } catch (_e) { console.log("Failed to unpin"); - autoipfsOpts.daemonURL = null; + autoipfsOpts.daemonURL = ""; return false; } } - coll.config.metadata.ipfsPins = null; + metadata.ipfsPins = null; return true; } return false; } -async function fetchBuffer(filename, replayBaseUrl) { +async function fetchBuffer(filename: string, replayBaseUrl: string) { const resp = await fetch(new URL(filename, replayBaseUrl).href); return new Uint8Array(await resp.arrayBuffer()); } -async function ipfsWriteBuff(writer, name, content, dir) { +async function ipfsWriteBuff( + writer: UnixFS.View, + name: string, + content: Uint8Array | AsyncIterable, + dir: UnixFS.DirectoryWriterView, +) { const file = UnixFS.createFileWriter(writer); if (content instanceof Uint8Array) { - file.write(content); + await file.write(content); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (content[Symbol.asyncIterator]) { for await (const chunk of content) { - file.write(chunk); + await file.write(chunk); } } - const link = await file.close(); + const link = await file.close(); dir.set(name, link); } // =========================================================================== -export async function ipfsGenerateCar(writable, waczPath, - waczContent, swContent, uiContent, htmlContent, replayOpts, markers, favicon) { - - const writer = UnixFS.createWriter({ writable }); - - const rootDir = UnixFS.createDirectoryWriter(writer); +export async function ipfsGenerateCar( + writable: WritableStream, + waczPath: string, + waczContent: ReadableStream, + swContent: Uint8Array, + uiContent: Uint8Array, + htmlContent: string, + replayOpts: ReplayOpts, + markers: Markers | null, + favicon: Uint8Array | null, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Promise { + const writer = UnixFS.createWriter({ writable }); + + const rootDir = UnixFS.createDirectoryWriter(writer); const encoder = new TextEncoder(); @@ -186,7 +247,7 @@ export async function ipfsGenerateCar(writable, waczPath, if (replayOpts.showEmbed) { const replayDir = UnixFS.createDirectoryWriter(writer); await ipfsWriteBuff(writer, "sw.js", swContent, replayDir); - await rootDir.set("replay", await replayDir.close()); + rootDir.set("replay", await replayDir.close()); } else { await ipfsWriteBuff(writer, "sw.js", swContent, rootDir); } @@ -195,42 +256,58 @@ export async function ipfsGenerateCar(writable, waczPath, await ipfsWriteBuff(writer, "favicon.ico", favicon, rootDir); } - await ipfsWriteBuff(writer, "index.html", encoder.encode(htmlContent), rootDir); + await ipfsWriteBuff( + writer, + "index.html", + encoder.encode(htmlContent), + rootDir, + ); if (!markers) { await ipfsWriteBuff(writer, waczPath, iterate(waczContent), rootDir); } else { - await splitByWarcRecordGroup(writer, waczPath, iterate(waczContent), rootDir, markers); + await splitByWarcRecordGroup( + writer, + waczPath, + iterate(waczContent), + rootDir, + markers, + ); } - const {cid} = await rootDir.close(); + const { cid } = await rootDir.close(); - writer.close(); + await writer.close(); return cid; } - -async function splitByWarcRecordGroup(writer, waczPath, warcIter, rootDir, markers) { - let links = []; - const fileLinks = []; - let secondaryLinks = []; +async function splitByWarcRecordGroup( + writer: UnixFS.View, + waczPath: string, + warcIter: AsyncGenerator, + rootDir: UnixFS.DirectoryWriterView, + markers: Markers, +) { + let links: FileLink[] = []; + const fileLinks: FileLink[] = []; + let secondaryLinks: FileLink[] = []; let inZipFile = false; let lastChunk = null; - let currName = null; + let currName = ""; const decoder = new TextDecoder(); - const dirs = {}; + const dirs: Record> = {}; - const {ZIP, WARC_PAYLOAD, WARC_GROUP} = markers; + const { ZIP, WARC_PAYLOAD, WARC_GROUP } = markers; let file = UnixFS.createFileWriter(writer); - function getDirAndName(fullpath) { + function getDirAndName(fullpath: string): [string, string] { const parts = fullpath.split("/"); - const filename = parts.pop(); + const filename = parts.pop() || ""; return [parts.join("/"), filename]; } @@ -250,9 +327,7 @@ async function splitByWarcRecordGroup(writer, waczPath, warcIter, rootDir, marke count = 0; file = UnixFS.createFileWriter(writer); } - } else if (chunk === ZIP && inZipFile) { - if (count) { links.push(await file.close()); count = 0; @@ -272,10 +347,11 @@ async function splitByWarcRecordGroup(writer, waczPath, warcIter, rootDir, marke links = []; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument fileLinks.push(link); const [dirName, filename] = getDirAndName(currName); - currName = null; + currName = ""; let dir; @@ -288,11 +364,11 @@ async function splitByWarcRecordGroup(writer, waczPath, warcIter, rootDir, marke dir = dirs[dirName]; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument dir.set(filename, link); inZipFile = false; } else if (chunk === WARC_PAYLOAD || chunk === WARC_GROUP) { - if (!inZipFile) { throw new Error("invalid state"); } @@ -303,6 +379,7 @@ async function splitByWarcRecordGroup(writer, waczPath, warcIter, rootDir, marke file = UnixFS.createFileWriter(writer); if (chunk === WARC_GROUP) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument secondaryLinks.push(await concat(writer, links)); links = []; } @@ -311,7 +388,7 @@ async function splitByWarcRecordGroup(writer, waczPath, warcIter, rootDir, marke if (!inZipFile) { lastChunk = chunk; } - file.write(chunk); + await file.write(chunk); count++; } } @@ -339,13 +416,20 @@ async function splitByWarcRecordGroup(writer, waczPath, warcIter, rootDir, marke rootDir.set("webarchive", await waczDir.close()); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument rootDir.set(waczPath, await concat(writer, fileLinks)); } -async function concat(writer, links) { +async function concat( + writer: UnixFS.View, + links: Link[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Promise { //TODO: is this the right way to do this? - const {fileEncoder, hasher, linker} = writer.settings; - const advanced = fileEncoder.createAdvancedFile(links); + const { fileEncoder, hasher, linker } = writer.settings; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const advanced = (fileEncoder as any).createAdvancedFile(links); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const bytes = fileEncoder.encode(advanced); const hash = await hasher.digest(bytes); const cid = linker.createLink(fileEncoder.code, hash); @@ -354,15 +438,18 @@ async function concat(writer, links) { const link = { cid, - contentByteLength: fileEncoder.cumulativeContentByteLength(links), - dagByteLength: fileEncoder.cumulativeDagByteLength(bytes, links), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + contentByteLength: (fileEncoder as any).cumulativeContentByteLength(links), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dagByteLength: (fileEncoder as any).cumulativeDagByteLength(bytes, links), }; return link; } -export const iterate = async function* (stream) { +export const iterate = async function* (stream: ReadableStream) { const reader = stream.getReader(); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { const next = await reader.read(); if (next.done) { @@ -373,8 +460,9 @@ export const iterate = async function* (stream) { } }; -export async function encodeBlocks(blocks, root) { - // @ts-expect-error +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function encodeBlocks(blocks: UnixFS.Block[], root?: any) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const { writer, out } = CarWriter.create(root); /** @type {Error?} */ let error; @@ -384,7 +472,7 @@ export async function encodeBlocks(blocks, root) { // @ts-expect-error await writer.put(block); } - } catch (/** @type {any} */ err) { + } catch (err: unknown) { error = err; } finally { await writer.close(); @@ -392,14 +480,14 @@ export async function encodeBlocks(blocks, root) { })(); const chunks = []; for await (const chunk of out) chunks.push(chunk); - // @ts-expect-error + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (error != null) throw error; const roots = root != null ? [root] : []; console.log("chunks", chunks.length); return Object.assign(new Blob(chunks), { version: 1, roots }); } -function getReplayHtml(waczPath, replayOpts = {}) { +function getReplayHtml(waczPath: string, replayOpts: ReplayOpts = {}) { const { showEmbed, pageUrl, pageTitle, deepLink, loading } = replayOpts; return ` @@ -420,16 +508,17 @@ function getReplayHtml(waczPath, replayOpts = {}) { } - ${showEmbed ? ` - ` : ` + ${ + showEmbed + ? ` + ` + : ` ` -} + } `; } - - // Copied from https://github.com/web3-storage/w3protocol/blob/main/packages/upload-client/src/sharding.js /** @@ -442,11 +531,11 @@ export class ShardingStream extends TransformStream { /** * @param {import('./types').ShardingOptions} [options] */ - constructor(shardSize) { + constructor(shardSize: number) { /** @type {import('@ipld/unixfs').Block[]} */ - let shard = []; + let shard: UnixFS.Block[] = []; /** @type {import('@ipld/unixfs').Block[] | null} */ - let readyShard = null; + let readyShard: UnixFS.Block[] | null = null; let readySize = 0; let currSize = 0; @@ -456,7 +545,7 @@ export class ShardingStream extends TransformStream { if (readyShard != null) { const blocks = await encodeBlocks(readyShard); const size = readySize; - controller.enqueue({blocks, size}); + controller.enqueue({ blocks, size }); readyShard = null; } if (shard.length && currSize + block.bytes.length > shardSize) { @@ -465,6 +554,7 @@ export class ShardingStream extends TransformStream { shard = []; currSize = 0; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument shard.push(block); currSize += block.bytes.length; }, @@ -473,14 +563,14 @@ export class ShardingStream extends TransformStream { if (readyShard != null) { const blocks = await encodeBlocks(readyShard); const size = readySize; - controller.enqueue({blocks, size}); + controller.enqueue({ blocks, size }); } const rootBlock = shard.at(-1); if (rootBlock != null) { const blocks = await encodeBlocks(shard, rootBlock.cid); const size = currSize; - controller.enqueue({blocks, size}); + controller.enqueue({ blocks, size }); } }, }); @@ -499,11 +589,17 @@ export class ShardingStream extends TransformStream { * @extends {TransformStream} */ export class ShardStoringStream extends TransformStream { - constructor(autoipfs, concurrency, reject) { + constructor( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + autoipfs: any, + concurrency: number, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reject: (reason?: any) => void, + ) { const queue = new Queue({ concurrency }); const abortController = new AbortController(); super({ - async transform({blocks, size}, controller) { + async transform({ blocks, size }, controller) { void queue.add( async () => { try { @@ -512,18 +608,18 @@ export class ShardStoringStream extends TransformStream { const resUrls = await autoipfs.uploadCAR(blocks); const url = resUrls[0]; - controller.enqueue({cid, url, size}); + controller.enqueue({ cid, url, size }); //const { version, roots, size } = car //controller.enqueue({ version, roots, cid, size }) } catch (err) { controller.error(err); abortController.abort(err); - autoipfsOpts.daemonURL = null; + autoipfsOpts.daemonURL = ""; reject(err); } }, - { signal: abortController.signal } + { signal: abortController.signal }, ); // retain backpressure by not returning until no items queued to be run diff --git a/src/keystore.js b/src/keystore.js deleted file mode 100644 index c9cab3d..0000000 --- a/src/keystore.js +++ /dev/null @@ -1,152 +0,0 @@ -import { openDB } from "idb/with-async-ittr"; -import { fromByteArray as encodeBase64, toByteArray as decodeBase64 } from "base64-js"; - - -// ==================================================================== -export class KeyStore -{ - constructor({dbname = "_keystore", mainStore = "store", key = "id", version = 1} = {}) { - this.dbname = dbname; - this.mainStore = mainStore; - this.key = key; - this.version = version; - this._ready = this.init(); - } - - async init() { - //let oldVersion = 0; - - this.db = await openDB(this.dbname, this.version, { - upgrade: (db, oldV, newV, tx) => { - //oldVersion = oldV; - this._initDB(db, oldV, newV, tx); - }, - blocking: (e) => { if (!e || e.newVersion === null) { this.close(); }} - }); - } - - _initDB(db, oldV/*, newV, tx*/) { - if (!oldV) { - db.createObjectStore(this.mainStore, { keyPath: this.key }); - } - } - - async listAll() { - await this._ready; - return await this.db.getAll(this.mainStore); - } - - async get(name) { - await this._ready; - return await this.db.get(this.mainStore, name); - } - - async delete(name) { - await this._ready; - return this.db.delete(this.mainStore, name); - } - - async put(value) { - await this._ready; - return await this.db.put(this.mainStore, value); - } - - close() { - if (this.db) { - this.db.close(); - this.db = null; - } - } -} - -// ==================================================================== -export class Signer -{ - constructor(softwareString, opts = {}) { - this._store = new KeyStore(); - this.softwareString = softwareString || "ArchiveWeb.page"; - this.cacheSig = opts.cacheSig; - } - - close() { - if (this._store) { - this._store.close(); - this._store = null; - } - } - - async sign(string, created) { - let keyPair; - let keys = await this.loadKeys(); - - const ecdsaImportParams = { - name: "ECDSA", - namedCurve: "P-384" - }; - - const extractable = true; - const usage = ["sign", "verify"]; - - const ecdsaSignParams = { - name: "ECDSA", - hash: "SHA-256" - }; - - if (!keys) { - keyPair = await crypto.subtle.generateKey(ecdsaImportParams, extractable, usage); - - const privateKey = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey); - const publicKey = await crypto.subtle.exportKey("spki", keyPair.publicKey); - keys = { - private: encodeBase64(new Uint8Array(privateKey)), - public: encodeBase64(new Uint8Array(publicKey)) - }; - - await this.saveKeys(keys); - } else { - const privateKey = decodeBase64(keys.private); - const publicKey = decodeBase64(keys.public); - - keyPair = {}; - keyPair.privateKey = await crypto.subtle.importKey("pkcs8", privateKey, ecdsaImportParams, true, ["sign"]); - keyPair.publicKey = await crypto.subtle.importKey("spki", publicKey, ecdsaImportParams, true, ["verify"]); - } - - let signature = this.cacheSig ? await this.loadSig(string) : null; - - if (!signature) { - const data = new TextEncoder().encode(string); - signature = await crypto.subtle.sign(ecdsaSignParams, keyPair.privateKey, data); - signature = encodeBase64(new Uint8Array(signature)); - await this.saveSig(string, signature); - } - - //console.log("verify", await crypto.subtle.verify(ecdsaSignParams, keyPair.publicKey, signature, data)); - - return { - hash: string, - signature, - publicKey: keys.public, - created, - software: this.softwareString - }; - } - - async saveSig(id, sig) { - return await this._store.put({id, sig}); - } - - async loadSig(id) { - const res = await this._store.get(id); - return res && res.sig; - } - - async saveKeys(keys, id = "_userkey") { - return await this._store.put({id, keys}); - } - - async loadKeys(id = "_userkey") { - const res = await this._store.get(id); - return res && res.keys; - } -} diff --git a/src/keystore.ts b/src/keystore.ts new file mode 100644 index 0000000..a02206f --- /dev/null +++ b/src/keystore.ts @@ -0,0 +1,225 @@ +import { openDB } from "idb/with-async-ittr"; +import { + fromByteArray as encodeBase64, + toByteArray as decodeBase64, +} from "base64-js"; +import { type IDBPDatabase } from "idb"; + +type KeyPair = { + public: string; + private: string; +}; + +type IdSig = { + id: string; + sig?: string; + keys?: KeyPair; +}; + +export type DataSignature = { + hash: string; + signature: string; + publicKey: string; + created: string; + software: string; +}; + +// ==================================================================== +export class KeyStore { + dbname: string; + mainStore: string; + key: string; + version: number; + _ready: Promise; + db: IDBPDatabase | null = null; + + constructor({ + dbname = "_keystore", + mainStore = "store", + key = "id", + version = 1, + } = {}) { + this.dbname = dbname; + this.mainStore = mainStore; + this.key = key; + this.version = version; + this._ready = this.init(); + } + + async init() { + //let oldVersion = 0; + + this.db = await openDB(this.dbname, this.version, { + upgrade: (db, oldV, _newV, _tx) => { + //oldVersion = oldV; + this._initDB(db, oldV); + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + blocking: (e: any) => { + if (!e || e.newVersion === null) { + this.close(); + } + }, + }); + } + + _initDB(db: IDBPDatabase, oldV: number /*, newV, tx*/) { + if (!oldV) { + db.createObjectStore(this.mainStore, { keyPath: this.key }); + } + } + + async listAll() { + await this._ready; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return await this.db!.getAll(this.mainStore); + } + + async get(name: string) { + await this._ready; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return await this.db!.get(this.mainStore, name); + } + + async delete(name: string) { + await this._ready; + return this.db!.delete(this.mainStore, name); + } + + async put(value: IdSig) { + await this._ready; + return await this.db!.put(this.mainStore, value); + } + + close() { + if (this.db) { + this.db.close(); + this.db = null; + } + } +} + +// ==================================================================== +export class Signer { + softwareString: string; + _store: KeyStore | null; + cacheSig: boolean; + + constructor(softwareString: string, opts: { cacheSig?: boolean } = {}) { + this._store = new KeyStore(); + this.softwareString = softwareString || "ArchiveWeb.page"; + this.cacheSig = opts.cacheSig || false; + } + + close() { + if (this._store) { + this._store.close(); + this._store = null; + } + } + + async sign(string: string, created: string): Promise { + let keyPair: CryptoKeyPair; + let keys = await this.loadKeys(); + + const ecdsaImportParams = { + name: "ECDSA", + namedCurve: "P-384", + }; + + const extractable = true; + const usage = ["sign", "verify"] as KeyUsage[]; + + const ecdsaSignParams = { + name: "ECDSA", + hash: "SHA-256", + }; + + if (!keys) { + keyPair = await crypto.subtle.generateKey( + ecdsaImportParams, + extractable, + usage, + ); + + const privateKey = await crypto.subtle.exportKey( + "pkcs8", + keyPair.privateKey, + ); + const publicKey = await crypto.subtle.exportKey( + "spki", + keyPair.publicKey, + ); + keys = { + private: encodeBase64(new Uint8Array(privateKey)), + public: encodeBase64(new Uint8Array(publicKey)), + }; + + await this.saveKeys(keys); + } else { + const privateDecoded = decodeBase64(keys.private); + const publicDecoded = decodeBase64(keys.public); + + const privateKey = await crypto.subtle.importKey( + "pkcs8", + privateDecoded, + ecdsaImportParams, + true, + ["sign"], + ); + const publicKey = await crypto.subtle.importKey( + "spki", + publicDecoded, + ecdsaImportParams, + true, + ["verify"], + ); + keyPair = { privateKey, publicKey }; + } + + let signature: string | null = this.cacheSig + ? await this.loadSig(string) + : null; + + if (!signature) { + const data = new TextEncoder().encode(string); + const signatureBuff = await crypto.subtle.sign( + ecdsaSignParams, + keyPair.privateKey, + data, + ); + signature = encodeBase64(new Uint8Array(signatureBuff)); + await this.saveSig(string, signature); + } + + //console.log("verify", await crypto.subtle.verify(ecdsaSignParams, keyPair.publicKey, signature, data)); + + return { + hash: string, + signature, + publicKey: keys.public, + created, + software: this.softwareString, + }; + } + + async saveSig(id: string, sig: string) { + return await this._store!.put({ id, sig }); + } + + async loadSig(id: string): Promise { + const res = await this._store!.get(id); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return res?.sig; + } + + async saveKeys(keys: KeyPair, id = "_userkey") { + return await this._store!.put({ id, keys }); + } + + async loadKeys(id = "_userkey"): Promise { + const res = await this._store!.get(id); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return res?.keys; + } +} diff --git a/src/recproxy.js b/src/recproxy.js deleted file mode 100644 index 79fceff..0000000 --- a/src/recproxy.js +++ /dev/null @@ -1,175 +0,0 @@ -import { ArchiveDB } from "@webrecorder/wabac/src/archivedb.js"; -import { LiveProxy } from "@webrecorder/wabac/src/liveproxy.js"; -import { SWCollections } from "@webrecorder/wabac/src/swmain.js"; -import { randomId } from "@webrecorder/wabac/src/utils.js"; -import { postToGetUrl } from "warcio"; - - -// =========================================================================== -export class RecProxy extends ArchiveDB -{ - constructor(config, collLoader) { - super(config.dbname); - - this.name = config.dbname.slice(3); - - this.collLoader = collLoader; - - this.recordProxied = config.extraConfig.recordProxied || false; - - this.liveProxy = new LiveProxy(config.extraConfig, {cloneResponse: true, allowBody: true}); - - this.pageId = randomId(); - this.isNew = true; - this.firstPageOnly = config.extraConfig.firstPageOnly || false; - - this.counter = 0; - } - - _initDB(db, oldV, newV, tx) { - super._initDB(db, oldV, newV, tx); - db.createObjectStore("rec"); - } - - async decCounter() { - this.counter--; - //console.log("rec counter", this.counter); - await this.db.put("rec", this.counter, "numPending"); - } - - async getCounter() { - return await this.db.get("rec", "numPending"); - } - - async getResource(request, prefix) { - let req; - - if (request.method === "POST" || request.method === "PUT") { - req = request.request.clone(); - } else { - req = request.request; - } - - let response = null; - - try { - this.counter++; - response = await this.liveProxy.getResource(request, prefix); - } catch (e) { - await this.decCounter(); - return null; - } - - //this.cookie = response.headers.get("x-wabac-preset-cookie"); - - // don't record content proxied from specified hosts - if (!this.recordProxied && this.liveProxy.hostProxy) { - const parsedUrl = new URL(response.url); - if (this.liveProxy.hostProxy[parsedUrl.host]) { - await this.decCounter(); - return response; - } - } - - this.doRecord(response, req).finally(() => this.decCounter()); - - return response; - } - - async doRecord(response, request) { - let url = response.url; - const ts = response.date.getTime(); - - const mime = (response.headers.get("content-type") || "").split(";")[0]; - - const range = response.headers.get("content-range"); - - if (range && !range.startsWith("bytes 0-")) { - console.log("skip range request: " + range); - return; - } - - const status = response.status; - const statusText = response.statusText; - - const respHeaders = Object.fromEntries(response.headers.entries()); - const reqHeaders = Object.fromEntries(request.headers.entries()); - - const payload = new Uint8Array(await response.clonedResponse.arrayBuffer()); - - if (range) { - const expectedRange = `bytes 0-${payload.length - 1}/${payload.length}`; - if (range !== expectedRange) { - console.log("skip range request: " + range); - return; - } - } - - if (request.mode === "navigate") { - this.pageId = randomId(); - if (!this.firstPageOnly) { - this.isNew = true; - } - } - - const pageId = this.pageId; - - const referrer = request.referrer; - - if (request.method === "POST" || request.method === "PUT") { - const data = { - method: request.method, - postData: await request.text(), - headers: request.headers, - url, - }; - - if (postToGetUrl(data)) { - url = new URL(data.url).href; - } - } - - const data = { - url, - ts, - status, - statusText, - pageId, - payload, - mime, - respHeaders, - reqHeaders, - referrer - }; - - await this.addResource(data); - - await this.collLoader.updateSize(this.name, payload.length, payload.length); - - // don't add page for redirects - if (this.isNew && (status < 301 || status >= 400) && request.mode === "navigate") { - //console.log("Page", url, "Referrer", referrer); - await this.addPages([{id: pageId, url, ts}]); - this.isNew = false; - } - } -} - -// =========================================================================== -export class RecordingCollections extends SWCollections -{ - async _initStore(type, config) { - let store; - - switch (type) { - case "recordingproxy": - store = new RecProxy(config, this); - if (store.initing) { - await store.initing; - } - return store; - } - - return await super._initStore(type, config); - } -} diff --git a/src/recproxy.ts b/src/recproxy.ts new file mode 100644 index 0000000..1ec572a --- /dev/null +++ b/src/recproxy.ts @@ -0,0 +1,319 @@ +import { + type ADBType, + ArchiveDB, + type ArchiveRequest, + type ArchiveResponse, + type CollectionLoader, + type PageEntry, + LiveProxy, + SWCollections, + randomId, +} from "@webrecorder/wabac/swlib"; + +//declare let self: ServiceWorkerGlobalScope; + +import { type IDBPDatabase, type IDBPTransaction } from "idb"; +import { postToGetUrl } from "warcio"; + +//export interface RecDBType extends ADBType { +export type RecDBType = ADBType & { + rec: { + key: string; + }; +}; + +export type ExtPageEntry = PageEntry & { + id: string; + title: string; + size: number; + ts: number; + + favIconUrl?: string; + text?: string; +}; + +// =========================================================================== +export class RecProxy extends ArchiveDB { + collLoader: CollectionLoader; + recordProxied: boolean; + liveProxy: LiveProxy; + pageId: string; + isNew = true; + firstPageOnly: boolean; + counter = 0; + isRecording = true; + allPages = new Map(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(config: any, collLoader: CollectionLoader) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + super(config.dbname); + + this.name = config.dbname.slice(3); + + this.collLoader = collLoader; + + this.recordProxied = config.extraConfig.recordProxied || false; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + this.liveProxy = new LiveProxy(config.extraConfig, { + cloneResponse: true, + allowBody: true, + }); + + this.pageId = randomId(); + this.isNew = true; + this.firstPageOnly = config.extraConfig.firstPageOnly || false; + + this.counter = 0; + } + + override _initDB( + db: IDBPDatabase, + oldV: number, + newV: number | null, + tx: IDBPTransaction< + ADBType, + (keyof ADBType)[], + "readwrite" | "versionchange" + >, + ) { + super._initDB(db, oldV, newV, tx); + //TODO: fix + (db as unknown as IDBPDatabase).createObjectStore("rec"); + } + + async decCounter() { + this.counter--; + //console.log("rec counter", this.counter); + //TODO: fix + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (this.db! as any).put("rec", this.counter, "numPending"); + } + + async getCounter(): Promise { + //TODO: fix + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return + return await (this.db! as any).get("rec", "numPending"); + } + + override async getResource(request: ArchiveRequest, prefix: string, event: FetchEvent) { + if (!this.isRecording) { + return await super.getResource(request, prefix, event); + } + + let req; + + if (request.method === "POST" || request.method === "PUT") { + req = request.request.clone(); + } else { + req = request.request; + } + + let response: ArchiveResponse | null = null; + + try { + this.counter++; + response = await this.liveProxy.getResource(request, prefix); + } catch (_e) { + await this.decCounter(); + return null; + } + + // error response, don't record + if (response?.noRW && response.status >= 400) { + await this.decCounter(); + return response; + } + + // don't record content proxied from specified hosts + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!this.recordProxied && this.liveProxy.hostProxy) { + const parsedUrl = new URL(response!.url); + if (this.liveProxy.hostProxy[parsedUrl.host]) { + await this.decCounter(); + return response; + } + } + + this.doRecord(response!, req, request.mod) + .catch(() => {}) + .finally(async () => this.decCounter()); + + return response; + } + + async doRecord(response: ArchiveResponse, request: Request, mod: string) { + let url = response.url; + const ts = response.date.getTime(); + + const mime = (response.headers.get("content-type") || "").split(";")[0]; + + const range = response.headers.get("content-range"); + + if (range && !range.startsWith("bytes 0-")) { + console.log("skip range request: " + range); + return; + } + + const status = response.status; + const statusText = response.statusText; + + const respHeaders = Object.fromEntries(response.headers.entries()); + const reqHeaders = Object.fromEntries(request.headers.entries()); + + const payload = new Uint8Array( + await response.clonedResponse!.arrayBuffer(), + ); + + if (range) { + const expectedRange = `bytes 0-${payload.length - 1}/${payload.length}`; + if (range !== expectedRange) { + console.log("skip range request: " + range); + return; + } + } + + if (request.mode === "navigate" && mod === "mp_") { + this.pageId = randomId(); + if (!this.firstPageOnly) { + this.isNew = true; + } + } + + const pageId = this.pageId; + const referrer = request.referrer; + + if (request.method === "POST" || request.method === "PUT") { + const data = { + method: request.method, + postData: await request.text(), + headers: request.headers, + url, + }; + + if (postToGetUrl(data)) { + url = new URL(data.url).href; + } + } + + const data = { + url, + ts, + status, + statusText, + pageId, + payload, + mime, + respHeaders, + reqHeaders, + referrer, + }; + + await this.addResource(data); + + await this.collLoader.updateSize(this.name, payload.length, payload.length); + + // don't add page for redirects + if (this.isPage(url, request, status, referrer, mod)) { + await this.addPages([{ id: pageId, url, ts }]); + this.allPages.set(url, pageId); + this.isNew = false; + } else { + console.log("not page", url); + } + } + + isPage(url: string, request: Request, status: number, referrer: string, mod: string) { + if (!this.isNew) { + return false; + } + + if ((status >= 301 && status < 400) || status === 204) { + return false; + } + + if (request.mode !== "navigate" || mod !== "mp_") { + return false; + } + + if (!referrer) { + return true; + } + + const inx = referrer.indexOf("mp_/"); + if (inx > 0) { + const refUrl = referrer.slice(inx + 4); + return url === refUrl || this.allPages.has(refUrl); + } else if (referrer.indexOf("if_/") > 0) { + return false; + } else if (referrer.indexOf("?source=")) { + return true; + } else { + return false; + } + } + + async updateFavIcon(url: string, favIconUrl: string) { + const pageId = this.allPages.get(url); + if (!pageId) { + return; + } + const page = await this.db!.get("pages", pageId) as ExtPageEntry | undefined; + if (!page) { + return; + } + page.favIconUrl = favIconUrl; + try { + await this.db!.put("pages", page); + } catch (_e: unknown) { + // ignore + } + } +} + +// =========================================================================== +export class RecordingCollections extends SWCollections { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + override async _initStore(type: string, config: any) { + let store; + + switch (type) { + case "recordingproxy": + store = new RecProxy(config, this); + await store.initing; + return store; + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + return await super._initStore(type, config); + } + + override async _handleMessage(event: MessageEvent) { + let coll; + + switch (event.data.msg_type) { + case "toggle-record": + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + coll = await this.getColl(event.data.id); + if (coll && coll.store instanceof RecProxy) { + console.log("Recording Toggled!", event.data.isRecording); + coll.store.isRecording = event.data.isRecording; + } + break; + + case "update-favicon": + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + coll = await this.getColl(event.data.id); + if (coll && coll.store instanceof RecProxy) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + await coll.store.updateFavIcon(event.data.url, event.data.favIconUrl); + } + break; + + + default: + return await super._handleMessage(event); + } + } +} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000..0378f0d --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": "./tsconfig.json", + "include": ["**/*.ts", "**/*.js", ".*.js"], + "exclude": ["__generated__", "__mocks__", "dist"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..83468d9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "module": "esnext", + "target": "esnext", + "moduleResolution": "Bundler", + "allowJs": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noPropertyAccessFromIndexSignature": false, + "noUncheckedIndexedAccess": true, + "declaration": true, + "declarationMap": true, + "declarationDir": "./dist/types", + "noImplicitAny": true, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "importHelpers": true, + "sourceMap": true, + "inlineSources": true, + "skipLibCheck": true, + "incremental": true, + "lib": ["webworker", "esnext", "webworker.iterable"] + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.d.ts"] +} diff --git a/webpack.config.cjs b/webpack.config.cjs index 2b74f40..a34042e 100644 --- a/webpack.config.cjs +++ b/webpack.config.cjs @@ -1,54 +1,76 @@ /*eslint-env node */ - +const path = require("path"); const webpack = require("webpack"); const TerserPlugin = require("terser-webpack-plugin"); +const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); const BANNER_TEXT = `'[name].js is part of the ArchiveWeb.page system (https://archiveweb.page) Copyright (C) 2020-${new Date().getFullYear()}, Webrecorder Software. Licensed under the Affero General Public License v3.'`; -module.exports = { - target: "webworker", - entry: { - "main": "./src/index.js", - }, - output: { - filename: "sw.js", - library: { - type: "self" - } - }, - optimization: { - minimize: true, - minimizer: [ - new TerserPlugin({ - extractComments: false, - }), - ] - }, - - plugins: [ - new webpack.NormalModuleReplacementPlugin( - /^node:*/, - (resource) => { - switch (resource.request) { - case "node:stream": - resource.request = "stream-browserify"; - break; - } - }, - ), - - new webpack.BannerPlugin(BANNER_TEXT), - ], - - module: { - rules: [ - { - test: /wombat.js|wombatWorkers.js|index.html$/i, - use: ["raw-loader"], +module.exports = (env, argv) => { + return { + target: "web", + entry: { + "main": "./src/index.ts", + }, + output: { + filename: "index.js", + globalObject: "self", + library: { + type: "module" } - ] - }, -}; + }, + experiments: { + outputModule: true + }, + optimization: { + minimize: true, + minimizer: [ + new TerserPlugin({ + extractComments: false, + }), + ] + }, + + resolve: { + extensions: [".ts", ".js"], + plugins: [new TsconfigPathsPlugin()], + }, + + devtool: argv.mode === "production" ? undefined : "source-map", + + plugins: [ + new webpack.NormalModuleReplacementPlugin( + /^node:*/, + (resource) => { + switch (resource.request) { + case "node:stream": + resource.request = "stream-browserify"; + break; + } + }, + ), + + new webpack.BannerPlugin(BANNER_TEXT), + ], + + module: { + rules: [ + { + test: /wombat.js|wombatWorkers.js|index.html$/i, + use: ["raw-loader"], + }, + { + test: /\.tsx?$/, + loader: "ts-loader", + include: path.resolve(__dirname, "src"), + options: { + onlyCompileBundledFiles: false, + }, + }, + ], + }, + }; +} diff --git a/yarn.lock b/yarn.lock index 55840e5..1553d49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,28 +7,45 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@eslint/eslintrc@^1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95" - integrity sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg== +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.4.0" - globals "^13.15.0" + espree "^9.6.0" + globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.11.6": - version "0.11.7" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" - integrity sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw== +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" minimatch "^3.0.5" "@humanwhocodes/module-importer@^1.0.1": @@ -36,15 +53,15 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== -"@ipld/car@^5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@ipld/car/-/car-5.3.1.tgz#6a967b2f929cab007466edab3171c18f489036d4" - integrity sha512-8fNkYAZvL9yX2zesF32k7tYqUDGG41felmmBnwjCZJto06QXCb0NOMPJc/mhNgnVa5gkKqxPO1ZdSoHuaYcVSw== +"@ipld/car@^5.3.2": + version "5.3.2" + resolved "https://registry.yarnpkg.com/@ipld/car/-/car-5.3.2.tgz#b6f9b5e30e0de5d45aff4494e8c3e2667ce9e0a4" + integrity sha512-Bb4XrCFlnsCb9tTzZ1I8zo9O61D9qm7HfvuYrQ9gzdE8YhjyVIjrjmHmnoSWV/uCmyc2/bcqiDPIg+9WljXNzg== dependencies: "@ipld/dag-cbor" "^9.0.7" cborg "^4.0.5" @@ -52,19 +69,19 @@ varint "^6.0.0" "@ipld/dag-cbor@^9.0.7": - version "9.2.0" - resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-9.2.0.tgz#3a3f0bee02d7e1c2f15582e896843d5b00fbba9f" - integrity sha512-N14oMy0q4gM6OuZkIpisKe0JBSjf1Jb39VI+7jMLiWX9124u1Z3Fdj/Tag1NA0cVxxqWDh0CqsjcVfOKtelPDA== + version "9.2.1" + resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-9.2.1.tgz#e61f413770bb0fb27ffafa9577049869272d2056" + integrity sha512-nyY48yE7r3dnJVlxrdaimrbloh4RokQaNRdI//btfTkcTEZbpmSrbYcBQ4VKTf8ZxXAOUJy4VsRpkJo+y9RTnA== dependencies: cborg "^4.0.0" multiformats "^13.1.0" "@ipld/dag-pb@^4.0.0": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@ipld/dag-pb/-/dag-pb-4.0.3.tgz#2fedd3804ad46ea6206be9302bfed5d3a0af26b3" - integrity sha512-bOe+Z2ZJs9pmP/aIUBYMTdXq0i5z1x71qXeOIIhZvnKFLuzTIbbW0u5b7OfTGzUEbSv1dkUZBIXa7G/+OA8dnA== + version "4.1.2" + resolved "https://registry.yarnpkg.com/@ipld/dag-pb/-/dag-pb-4.1.2.tgz#39db25311aeb2745ec20bfc745d91a577832b6ac" + integrity sha512-BSztO4l3C+ya9HjCaQot26Y4AVsqIKtnn6+23ubc1usucnf6yoTBme18oCCdM6gKBMxuPqju5ye3lh9WEJsdeQ== dependencies: - multiformats "^11.0.0" + multiformats "^13.1.0" "@ipld/unixfs@^3.0.0": version "3.0.0" @@ -124,12 +141,12 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@multiformats/murmur3@^2.1.3": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@multiformats/murmur3/-/murmur3-2.1.4.tgz#e93cce560c381f8326b2271facf2ea04f6fd8a66" - integrity sha512-qHHmZKD1Dy6PDi35pAowE1pQtnH7gwaJpUE/Ju+cOYVdWD4T8VVtKAumGCxwd31JKyNC0W1IzAaHQz1dnXXvBw== +"@multiformats/murmur3@^2.1.0", "@multiformats/murmur3@^2.1.3": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@multiformats/murmur3/-/murmur3-2.1.8.tgz#81c1c15b6391109f3febfca4b3205196615a04e9" + integrity sha512-6vId1C46ra3R1sbJUOFCZnsUIveR9oF20yhPmAFxPm0JfrX3/ZRCgP3YDrBzlGoEppOXnA9czHeYc0T9mB6hbA== dependencies: - multiformats "^11.0.0" + multiformats "^13.0.0" murmurhash3js-revisited "^3.0.0" "@nodelib/fs.scandir@2.1.5": @@ -140,12 +157,12 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.8": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -278,10 +295,11 @@ tsyringe "^4.7.0" "@perma/map@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@perma/map/-/map-1.0.2.tgz#838043c5f4c2ec30f1f9dfef7862ab50e2947d5b" - integrity sha512-hujwGOY6yTYnpf5YAtpD5MJAI1kcsVPqyN0lxG8Sampf/InO3jmX/MlJCHCGFPpPqB5JyO5WNnL+tUs1Umqe0A== + version "1.0.3" + resolved "https://registry.yarnpkg.com/@perma/map/-/map-1.0.3.tgz#c80021c9626276298c69a44dec6a4e041bbd47f3" + integrity sha512-Bf5njk0fnJGTFE2ETntq0N1oJ6YdCPIpTDn3R3KYZJQdeYSOCNL7mBrFlGnbqav8YQhJA/p81pvHINX9vAtHkQ== dependencies: + "@multiformats/murmur3" "^2.1.0" murmurhash3js-revisited "^3.0.0" "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": @@ -337,42 +355,136 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== -"@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.4.10" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.10.tgz#19731b9685c19ed1552da7052b6f668ed7eb64bb" - integrity sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== - "@types/estree@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== -"@types/json-schema@*", "@types/json-schema@^7.0.8": +"@types/js-levenshtein@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.3.tgz#a6fd0bdc8255b274e5438e0bfb25f154492d1106" + integrity sha512-jd+Q+sD20Qfu9e2aEXogiO3vpOC1PYJOUdyN9gvs4Qrvkg4wF43L5OhqrPeokdv8TL0/mXoYfpkcoGZMNN2pkQ== + +"@types/json-schema@^7.0.8": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/node@*", "@types/node@>=13.7.0": +"@types/node@*": version "18.11.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== +"@types/node@>=13.7.0": + version "22.5.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.4.tgz#83f7d1f65bc2ed223bdbf57c7884f1d5a4fa84e8" + integrity sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg== + dependencies: + undici-types "~6.19.2" + +"@types/pako@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.7.tgz#aa0e4af9855d81153a29ff84cc44cce25298eda9" + integrity sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A== + +"@types/stream-buffers@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/stream-buffers/-/stream-buffers-3.0.7.tgz#0b719fa1bd2ca2cc0908205a440e5e569e1aa21e" + integrity sha512-azOCy05sXVXrO+qklf0c/B07H/oHaIuDDAiHPVwlk3A9Ek+ksHyTeMajLZl3r76FxpPpxem//4Te61G1iW3Giw== + dependencies: + "@types/node" "*" + +"@types/uuid@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d" + integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ== + +"@typescript-eslint/eslint-plugin@^8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.4.0.tgz#188c65610ef875a086404b5bfe105df936b035da" + integrity sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.4.0" + "@typescript-eslint/type-utils" "8.4.0" + "@typescript-eslint/utils" "8.4.0" + "@typescript-eslint/visitor-keys" "8.4.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.4.0.tgz#36b7cd7643a1c190d49dc0278192b2450f615a6f" + integrity sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA== + dependencies: + "@typescript-eslint/scope-manager" "8.4.0" + "@typescript-eslint/types" "8.4.0" + "@typescript-eslint/typescript-estree" "8.4.0" + "@typescript-eslint/visitor-keys" "8.4.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz#8a13d3c0044513d7960348db6f4789d2a06fa4b4" + integrity sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A== + dependencies: + "@typescript-eslint/types" "8.4.0" + "@typescript-eslint/visitor-keys" "8.4.0" + +"@typescript-eslint/type-utils@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.4.0.tgz#4a91b5789f41946adb56d73e2fb4639fdcf37af7" + integrity sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A== + dependencies: + "@typescript-eslint/typescript-estree" "8.4.0" + "@typescript-eslint/utils" "8.4.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.4.0.tgz#b44d6a90a317a6d97a3e5fabda5196089eec6171" + integrity sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw== + +"@typescript-eslint/typescript-estree@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz#00ed79ae049e124db37315cde1531a900a048482" + integrity sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A== + dependencies: + "@typescript-eslint/types" "8.4.0" + "@typescript-eslint/visitor-keys" "8.4.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.4.0.tgz#35c552a404858c853a1f62ba6df2214f1988afc3" + integrity sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.4.0" + "@typescript-eslint/types" "8.4.0" + "@typescript-eslint/typescript-estree" "8.4.0" + +"@typescript-eslint/visitor-keys@8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz#1e8a8b8fd3647db1e42361fdd8de3e1679dec9d2" + integrity sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A== + dependencies: + "@typescript-eslint/types" "8.4.0" + eslint-visitor-keys "^3.4.3" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + "@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" @@ -509,21 +621,22 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e" integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== -"@webrecorder/wabac@^2.18.1": - version "2.18.1" - resolved "https://registry.yarnpkg.com/@webrecorder/wabac/-/wabac-2.18.1.tgz#9e65bc7388d7fedb5b82df8bb3b6a0729b8f8200" - integrity sha512-DgX24jaE0ZpdZ06eVCLVaWLXpICmnaPeHpDbyURaGldWypmwDBhpj9n4yrGKyZBC1wLQP2ytrV0LgnvrZdDl1Q== +"@webrecorder/wabac@^2.20.0": + version "2.20.0" + resolved "https://registry.yarnpkg.com/@webrecorder/wabac/-/wabac-2.20.0.tgz#d9f87a909a0c09e460cf8b86082a71c176e0c9f8" + integrity sha512-zeR+CmAfJO2MQSKwRwcccy7+TfeKTX0A1yeHlO98mtsT3VMtTqrdasIDEjIxe39aEbBl11k+m8DQyrz3CctJNQ== dependencies: "@peculiar/asn1-ecc" "^2.3.4" "@peculiar/asn1-schema" "^2.3.3" "@peculiar/x509" "^1.9.2" - "@webrecorder/wombat" "^3.7.4" + "@types/js-levenshtein" "^1.1.3" + "@webrecorder/wombat" "^3.8.2" acorn "^8.10.0" auto-js-ipfs "^2.1.1" base64-js "^1.5.1" brotli "^1.3.3" buffer "^6.0.3" - fast-xml-parser "^4.2.5" + fast-xml-parser "^4.4.1" hash-wasm "^4.9.0" http-link-header "^1.1.3" http-status-codes "^2.1.4" @@ -536,14 +649,14 @@ path-parser "^6.1.0" process "^0.11.10" stream-browserify "^3.0.0" - warcio "^2.2.1" + warcio "^2.3.1" -"@webrecorder/wombat@^3.7.4": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@webrecorder/wombat/-/wombat-3.7.4.tgz#782f36955df82acf8653f68298fbb62df95c3c68" - integrity sha512-5ksnnm0J1xt2pNUVkn/sth6MxC6p3Y9aDrs7g4bqvuqQlVAeSOaCd3+vfEunGXZFbISbuywcQjt4czr8+GIOCA== +"@webrecorder/wombat@^3.8.2": + version "3.8.2" + resolved "https://registry.yarnpkg.com/@webrecorder/wombat/-/wombat-3.8.2.tgz#e46e18719834d633175eec52ce753a4dc4e48e27" + integrity sha512-uUZr9V4UYpVOpM64Tm27ND/hMjDbT37+/qyNaNV6loqDuVzBVQh5w7SfTEy0Bbjj1MYyNZP244mOtWtotTpUEA== dependencies: - warcio "^2.2.0" + warcio "^2.3.1" "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -555,10 +668,10 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn-jsx@^5.3.2: version "5.3.2" @@ -570,7 +683,7 @@ acorn@^8.10.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== -acorn@^8.7.1, acorn@^8.8.0: +acorn@^8.7.1: version "8.8.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== @@ -580,6 +693,11 @@ acorn@^8.8.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +acorn@^8.9.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + actor@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/actor/-/actor-2.3.1.tgz#80ce158bb41338a0c38863bddf0947c1850b6e20" @@ -590,7 +708,7 @@ ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -666,6 +784,20 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + brotli@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/brotli/-/brotli-1.3.3.tgz#7365d8cc00f12cf765d2b2c898716bcf4b604d48" @@ -707,11 +839,11 @@ caniuse-lite@^1.0.30001587: integrity sha512-4zgNiB8nTyV/tHhwZrFs88ryjls/lHiqFhrxCW4qSTeuRByBVnPYpDInchOIySWknznucaf31Z4KYqjfbrecVw== cborg@^4.0.0, cborg@^4.0.5: - version "4.2.0" - resolved "https://registry.yarnpkg.com/cborg/-/cborg-4.2.0.tgz#e14e36bb081a0044e78f875d440accb4d4e6aa97" - integrity sha512-q6cFW5m3KxfP/9xGI3yGLaC1l5DP6DWM9IvjiJojnIwohL5CQDl02EXViPV852mOfQo+7PJGPN01MI87vFGzyA== + version "4.2.3" + resolved "https://registry.yarnpkg.com/cborg/-/cborg-4.2.3.tgz#0c16217022c085579edc5b204f693a261a12eb49" + integrity sha512-XBFbEJ6WMfn9L7woc2t+EzOxF8vGqddoopKBbrhIvZBt2WIUgSlT8xLmM6Aq1xv8eWt4yOSjwxWjYeuHU3CpJA== -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -795,7 +927,14 @@ crypto-random-string@^4.0.0: dependencies: type-fest "^1.0.1" -debug@^4.1.1, debug@^4.3.2: +debug@^4.3.1, debug@^4.3.4: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +debug@^4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -829,10 +968,10 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -enhanced-resolve@^5.16.0: - version "5.16.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz#e8bc63d51b826d6f1cbc0a150ecb5a8b0c62e567" - integrity sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw== +enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -867,6 +1006,11 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +eslint-config-prettier@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" + integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== + eslint-scope@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -875,89 +1019,81 @@ eslint-scope@5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - eslint-visitor-keys@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.28.0: - version "8.28.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.28.0.tgz#81a680732634677cc890134bcdd9fdfea8e63d6e" - integrity sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ== - dependencies: - "@eslint/eslintrc" "^1.3.3" - "@humanwhocodes/config-array" "^0.11.6" +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.56.1: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" - ajv "^6.10.0" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.3.0" - espree "^9.4.0" - esquery "^1.4.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.15.0" - grapheme-splitter "^1.0.4" + globals "^13.19.0" + graphemer "^1.4.0" ignore "^5.2.0" - import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" - js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" - regexpp "^3.2.0" + optionator "^0.9.3" strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.4.0: - version "9.4.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.1.tgz#51d6092615567a2c2cff7833445e37c28c0065bd" - integrity sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg== +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^8.8.0" + acorn "^8.9.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" + eslint-visitor-keys "^3.4.1" -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== dependencies: estraverse "^5.1.0" @@ -983,10 +1119,10 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eventemitter3@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== events@^3.2.0: version "3.3.0" @@ -998,6 +1134,17 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -1008,10 +1155,10 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-xml-parser@^4.2.5: - version "4.2.7" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz#871f2ca299dc4334b29f8da3658c164e68395167" - integrity sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig== +fast-xml-parser@^4.4.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz#2882b7d01a6825dfdf909638f2de0256351def37" + integrity sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg== dependencies: strnum "^1.0.5" @@ -1034,6 +1181,13 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -1051,17 +1205,18 @@ find-up@^5.0.0: path-exists "^4.0.0" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== dependencies: - flatted "^3.1.0" + flatted "^3.2.9" + keyv "^4.5.3" rimraf "^3.0.2" -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== fs.realpath@^1.0.0: version "1.0.0" @@ -1078,6 +1233,13 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" @@ -1102,10 +1264,10 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^13.15.0: - version "13.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.18.0.tgz#fb224daeeb2bb7d254cd2c640f003528b8d0c1dc" - integrity sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A== +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2" @@ -1119,10 +1281,10 @@ graceful-fs@^4.2.11, graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== has-flag@^4.0.0: version "4.0.0" @@ -1166,7 +1328,12 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -import-fresh@^3.0.0, import-fresh@^3.2.1: +ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -1227,13 +1394,18 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^4.0.0, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -1275,11 +1447,6 @@ js-levenshtein@^1.1.6: resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== -js-sdsl@^4.1.4: - version "4.2.0" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0" - integrity sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ== - js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -1287,6 +1454,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -1307,6 +1479,18 @@ json5@^2.1.2: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -1354,15 +1538,28 @@ lodash.merge@^4.6.2: integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== long@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f" - integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.0, micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" @@ -1382,20 +1579,32 @@ minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multiformats@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-11.0.0.tgz#728dd8adfe4b169cd5b4b22d9dc1054d48bbe2d0" - integrity sha512-vqF8bmMtbxw9Zn3eTpk0OZQdBVmAT/+bTGwXb3C2qCNkp45aJMmkCDds3lrtObECWPf+KFjFtTOHkvCaT/c/xQ== +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== multiformats@^13.0.0, multiformats@^13.0.1, multiformats@^13.1.0: - version "13.1.0" - resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-13.1.0.tgz#5aa9d2175108a448fc3bdb54ba8a3d0b6cab3ac3" - integrity sha512-HzdtdBwxsIkzpeXzhQ5mAhhuxcHbjEHH+JQoxt7hG/2HGFjjwyolLo7hbaexcnhoEuV4e0TNJ8kkpMjiEYY4VQ== + version "13.2.2" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-13.2.2.tgz#16da153ee8b68d8c9da31b52176e90b3cd8b43ef" + integrity sha512-RWI+nyf0q64vyOxL8LbKtjJMki0sogRL/8axvklNtiTM0iFCVtHwME9w6+0P1/v4dQvsIg8A45oT3ka1t/M/+A== murmurhash3js-revisited@^3.0.0: version "3.0.0" @@ -1424,17 +1633,17 @@ once@^1.3.0: dependencies: wrappy "1" -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== dependencies: deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" - word-wrap "^1.2.3" + word-wrap "^1.2.5" p-limit@^2.2.0: version "2.3.0" @@ -1464,18 +1673,18 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-queue@^7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-7.3.4.tgz#7ef7d89b6c1a0563596d98adbc9dc404e9ed4a84" - integrity sha512-esox8CWt0j9EZECFvkFl2WNPat8LN4t7WWeXq73D9ha0V96qPRufApZi4ZhPwXAln1uVVal429HVVKPa2X0yQg== +p-queue@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-8.0.1.tgz#718b7f83836922ef213ddec263ff4223ce70bef8" + integrity sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA== dependencies: - eventemitter3 "^4.0.7" - p-timeout "^5.0.2" + eventemitter3 "^5.0.1" + p-timeout "^6.1.2" -p-timeout@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-5.1.0.tgz#b3c691cf4415138ce2d9cfe071dba11f0fee085b" - integrity sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew== +p-timeout@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-6.1.2.tgz#22b8d8a78abf5e103030211c5fc6dee1166a6aa5" + integrity sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ== p-try@^2.0.0: version "2.2.0" @@ -1550,6 +1759,11 @@ picocolors@^1.0.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -1562,15 +1776,20 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== protobufjs@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.1.2.tgz#a0cf6aeaf82f5625bffcf5a38b7cd2a7de05890c" - integrity sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ== + version "7.4.0" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.4.0.tgz#7efe324ce9b3b61c82aae5de810d287bc08a248a" + integrity sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -1648,11 +1867,6 @@ reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -1731,6 +1945,11 @@ search-params@3.0.0: resolved "https://registry.yarnpkg.com/search-params/-/search-params-3.0.0.tgz#dbc7c243058e5a33ae1e9870be91f5aced4100d8" integrity sha512-8CYNl/bjkEhXWbDTU/K7c2jQtrnqEffIPyOLMqygW/7/b+ym8UtQumcAZjOfMLjZKR6AxK5tOr9fChbQZCzPqg== +semver@^7.3.4, semver@^7.6.0: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + serialize-javascript@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" @@ -1770,6 +1989,11 @@ source-map@^0.6.0: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + stream-browserify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" @@ -1801,7 +2025,12 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -1881,6 +2110,47 @@ to-data-view@^2.0.0: resolved "https://registry.yarnpkg.com/to-data-view/-/to-data-view-2.0.0.tgz#4cc3f5c9eb59514a7436fc54c587c3c34c9b1d60" integrity sha512-RGEM5KqlPHr+WVTPmGNAXNeFEmsBnlkxXaIfEpUYV0AST2Z5W1EGq9L/MENFrMMmL2WQr1wjkmZy/M92eKhjYA== +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + +ts-loader@^9.5.1: + version "9.5.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.1.tgz#63d5912a86312f1fbe32cef0859fb8b2193d9b89" + integrity sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.0.0" + micromatch "^4.0.0" + semver "^7.3.4" + source-map "^0.7.4" + +tsconfig-paths-webpack-plugin@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz#3c6892c5e7319c146eee1e7302ed9e6f2be4f763" + integrity sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.7.0" + tsconfig-paths "^4.1.2" + +tsconfig-paths@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@^1.10.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -1920,6 +2190,16 @@ type-fest@^2.12.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== +typescript@^5.5.4: + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + unique-string@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-3.0.0.tgz#84a1c377aff5fd7a8bc6b55d8244b2bd90d75b9a" @@ -1962,23 +2242,13 @@ varint@^6.0.0: resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0" integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== -warcio@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/warcio/-/warcio-2.2.0.tgz#9f307174c7b05b5546dda9e45098c4212c363561" - integrity sha512-+ahadJnbAqqRlI1OFzyj1Nk+yy9BrR/4jDksuolJBZuesY39y9pUeYzSoje8vWONgZKPU24s3jMD8jPU7J/Q6w== - dependencies: - base32-encode "^2.0.0" - hash-wasm "^4.9.0" - pako "^1.0.11" - tempy "^3.1.0" - uuid-random "^1.3.2" - yargs "^17.6.2" - -warcio@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/warcio/-/warcio-2.2.1.tgz#3619728fde716291c9b364744c276362a94bacec" - integrity sha512-KPLoz3aFtdTjexG+QQaubMyuLiNANzvcadGMyNKdpcmhl0k6lBHQQVpxZw3Hx9+4pbyqDXyiF4cr/h2tS8kvcw== +warcio@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/warcio/-/warcio-2.3.1.tgz#8ac9de897de1a556161168f2a3938b60929908ca" + integrity sha512-PjcWqzXfs6HdWfHi1V/i8MoMmV5M0Csg3rOa2mqCJ1dmCJXswVfQ0VXbEVumwavNIW2oFFj6LJoCHHeL4Ls/zw== dependencies: + "@types/pako" "^1.0.7" + "@types/stream-buffers" "^3.0.7" base32-encode "^2.0.0" hash-wasm "^4.9.0" pako "^1.0.11" @@ -2026,21 +2296,20 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.91.0: - version "5.91.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9" - integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw== +webpack@^5.94.0: + version "5.94.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" + integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== dependencies: - "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.5" "@webassemblyjs/ast" "^1.12.1" "@webassemblyjs/wasm-edit" "^1.12.1" "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" + acorn-import-attributes "^1.9.5" browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.16.0" + enhanced-resolve "^5.17.1" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" @@ -2068,10 +2337,10 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== -word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== wrap-ansi@^7.0.0: version "7.0.0"