From b5662b9d58cdb68947c0a51388eb16fcf898c96e Mon Sep 17 00:00:00 2001 From: G Maldonado Date: Fri, 17 May 2024 08:08:16 -0700 Subject: [PATCH] Create_api_mock (#1) * create base tests * start refining api_mock * update api_mock * lint * make the ApiMock as robust as it can be * update api_mock to mock xhr * polish api_mock * Get wrapper working. FIXME: button is not swapping wrapper modes --- apps/owlserver-chrome/package.json | 5 +- apps/owlserver-chrome/src/background.ts | 13 +- .../owlserver-chrome/src/components/Popup.tsx | 54 +++++- .../src/components/record_button.tsx | 24 ++- apps/owlserver-chrome/src/main.tsx | 37 +++- apps/owlserver-chrome/src/manifest.json | 9 +- .../src/services/api_mock.test.ts | 112 ++++++++++++ .../owlserver-chrome/src/services/api_mock.ts | 158 +++++++++++++++++ .../src/services/api_recorder.ts | 80 +++++++++ .../src/services/scenario_runner.ts | 63 ------- apps/owlserver-chrome/src/wrappers.ts | 132 ++++++-------- apps/owlserver-chrome/tsconfig.json | 2 +- package.json | 2 - packages/typescript-config/base.json | 2 +- pnpm-lock.yaml | 165 ++++++++++++++++-- 15 files changed, 675 insertions(+), 183 deletions(-) create mode 100644 apps/owlserver-chrome/src/services/api_mock.test.ts create mode 100644 apps/owlserver-chrome/src/services/api_mock.ts create mode 100644 apps/owlserver-chrome/src/services/api_recorder.ts delete mode 100644 apps/owlserver-chrome/src/services/scenario_runner.ts diff --git a/apps/owlserver-chrome/package.json b/apps/owlserver-chrome/package.json index 24d5fe5d..f98e806b 100644 --- a/apps/owlserver-chrome/package.json +++ b/apps/owlserver-chrome/package.json @@ -8,7 +8,9 @@ "build:rollup": "pnpm exec rollup -c --bundleConfigAsCjs", "build:copy": "cp ./src/manifest.json dist/ && cp -r ./assets dist/", "build": "pnpm run build:clean && pnpm run build:rollup && pnpm run build:copy", - "test": "pnpm exec vitest" + "test": "pnpm exec vitest", + "lint": "biome ci .", + "format": "biome check --apply . && biome lint --apply ." }, "keywords": [], "author": "", @@ -21,6 +23,7 @@ "@repo/ui": "workspace:*", "@vscode/codicons": "^0.0.35", "framer-motion": "^6", + "jsdom": "^24.0.0", "next": "^14.0.4", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/apps/owlserver-chrome/src/background.ts b/apps/owlserver-chrome/src/background.ts index 08f3023d..22723355 100644 --- a/apps/owlserver-chrome/src/background.ts +++ b/apps/owlserver-chrome/src/background.ts @@ -93,7 +93,17 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab: chrome.tabs.Tab) => { } }); -let webSocket = new WebSocket("ws://localhost:8080/ws"); +let webSocket: WebSocket | null = null; +try { + webSocket = new WebSocket("ws://localhost:8080/ws"); +} catch (error) { + console.error("websocket connection failed"); +} + +if (!webSocket) { + console.error("websocket connection failed"); +} + webSocket.onopen = (event) => { keepAlive(); }; @@ -146,6 +156,7 @@ function sendDataToServer(): void { if (sessionId) { payload.sessionId = sessionId; } + console.log(`sending data to server: ${data}`); webSocket.send(JSON.stringify(payload)); }); diff --git a/apps/owlserver-chrome/src/components/Popup.tsx b/apps/owlserver-chrome/src/components/Popup.tsx index aefe40ad..4c79b9d0 100644 --- a/apps/owlserver-chrome/src/components/Popup.tsx +++ b/apps/owlserver-chrome/src/components/Popup.tsx @@ -1,7 +1,8 @@ import { Button, HStack } from "@chakra-ui/react"; import { Box, Center, Text } from "@chakra-ui/react"; +// biome-ignore lint/style/useImportType: false positive import React, { useState, useEffect } from "react"; -import RecordButton from "./record_button"; +import RecordButton, { MOCKSI_CURRENT_STEP } from "./record_button"; function runStartRecording(isRecording: boolean) { const action = isRecording ? "startRecording" : "stopRecording"; @@ -10,8 +11,17 @@ function runStartRecording(isRecording: boolean) { }); } -function PopUp() { - // FIXME: create color constants +interface StepProps { + currentStep: string; + onNextStep: () => void; +} + +const StepOne: React.FC = ({ currentStep, onNextStep }) => { + if (Number(currentStep) > 1) { + return null; + } + + // FIXME: use correct color constants return ( @@ -23,13 +33,49 @@ function PopUp() {
-
); +}; + +const StepTwo: React.FC = ({ currentStep, onNextStep }) => { + if (Number(currentStep) !== 2) { + return null; + } + return

Step two

; +}; + +function PopUp() { + const [currentStep, setCurrentStep] = useState("0"); + + useEffect(() => { + const storedStep = localStorage.getItem(MOCKSI_CURRENT_STEP); + if (storedStep) { + setCurrentStep(storedStep); + } + }); + + const advanceToNextStep = () => { + const prevStep = Number(currentStep); + const nextStep = String(prevStep + 1); + localStorage.setItem(MOCKSI_CURRENT_STEP, nextStep); + setCurrentStep(nextStep); + }; + + return ( + <> + + + + ); } export default PopUp; diff --git a/apps/owlserver-chrome/src/components/record_button.tsx b/apps/owlserver-chrome/src/components/record_button.tsx index 96dbf99a..5ea2742f 100644 --- a/apps/owlserver-chrome/src/components/record_button.tsx +++ b/apps/owlserver-chrome/src/components/record_button.tsx @@ -2,7 +2,8 @@ import { Button } from "@chakra-ui/react"; // biome-ignore lint/style/useImportType: it's ok import React, { useState, useEffect } from "react"; -const MOCKSI_RECORDING_STATE = "mocksi-recordingState"; +const MOCKSI_RECORDING_STATE = "mocksi-recording-state"; +export const MOCKSI_CURRENT_STEP = "mocksi-current-step"; interface RecordButtonProps { onRecordChange: (isRecording: boolean) => void; } @@ -26,6 +27,27 @@ const RecordButton: React.FC = ({ onRecordChange }) => { onRecordChange(newRecordingState); setIsRecording(newRecordingState); localStorage.setItem(MOCKSI_RECORDING_STATE, newRecordingState.toString()); + if (newRecordingState) { + localStorage.setItem(MOCKSI_CURRENT_STEP, "1"); + } + const type = newRecordingState + ? "startRecordingAction" + : "stopRecordingAction"; + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + if (tabs[0].id) { + chrome.scripting.executeScript( + { + target: { tabId: tabs[0].id }, + files: ["main.js"], + }, + () => { + chrome.tabs.sendMessage(tabs[0].id, { type: type }, (response) => { + console.log("Response from content script:", response); + }); + }, + ); + } + }); }; const label = isRecording ? "stop" : "start"; diff --git a/apps/owlserver-chrome/src/main.tsx b/apps/owlserver-chrome/src/main.tsx index c2120017..acc06e95 100644 --- a/apps/owlserver-chrome/src/main.tsx +++ b/apps/owlserver-chrome/src/main.tsx @@ -6,19 +6,25 @@ import PopUp from "src/components/Popup"; const s = document.createElement("script"); s.src = chrome.runtime.getURL("wrappers.js"); (document.head || document.documentElement).appendChild(s); +let wrapperInstance = null; + s.onload = () => { + wrapperInstance = window.wrapper; s.remove(); }; -const root = document.getElementById("popup-container"); -const rootDiv = ReactDOM.createRoot(root || document.createElement("div")); -rootDiv.render( - - - - - , -); +const rootDiv = document.getElementById("popup-container"); +if (rootDiv) { + rootDiv.id = "popup-container"; + const reactRoot = ReactDOM.createRoot(rootDiv); + reactRoot.render( + + + + + , + ); +} document.addEventListener("wrapperToBackground", (e): void => { const jsonHolder = document.getElementById("jsonHolder"); @@ -33,3 +39,16 @@ document.addEventListener("wrapperToBackground", (e): void => { }, ); }); + +// FIXME: this is not working +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.message === "startRecordingAction") { + wrapperInstance?.record(); + } + if (message.message === "stopRecordingAction") { + wrapperInstance?.remove(); + } + console.log("window.wrapper mode", wrapperInstance?.mode); + const status = wrapperInstance ? "success" : "error"; + sendResponse({ status: status }); +}); diff --git a/apps/owlserver-chrome/src/manifest.json b/apps/owlserver-chrome/src/manifest.json index 5ccf1cb6..2ef916bf 100644 --- a/apps/owlserver-chrome/src/manifest.json +++ b/apps/owlserver-chrome/src/manifest.json @@ -28,6 +28,13 @@ "background": { "service_worker": "background.js" }, - "permissions": ["activeTab", "webRequest", "downloads", "storage"], + "permissions": [ + "activeTab", + "webRequest", + "downloads", + "storage", + "tabs", + "scripting" + ], "host_permissions": [""] } diff --git a/apps/owlserver-chrome/src/services/api_mock.test.ts b/apps/owlserver-chrome/src/services/api_mock.test.ts new file mode 100644 index 00000000..51d5c6e1 --- /dev/null +++ b/apps/owlserver-chrome/src/services/api_mock.test.ts @@ -0,0 +1,112 @@ +/** + * @vitest-environment jsdom + */ + +import { afterAll, beforeAll, describe, expect, test } from "vitest"; +import ApiMock from "./api_mock"; + +// Mock setup to simulate a browser XMLHttpRequest environment +beforeAll(() => { + const config = { delay: 0, mockData }; + const apiMock = new ApiMock(config); + globalThis.XMLHttpRequest = + apiMock.createFakeXHR() as unknown as typeof XMLHttpRequest; +}); + +afterAll(() => { + // biome-ignore lint/performance/noDelete: + delete globalThis.XMLHttpRequest; +}); + +// Define mock data with various response types and scenarios +const mockData = { + "GET /api/text": { + ResponseBody: "Simple text response", + ResponseStatus: 200, + ResponseHeaders: { "Content-Type": "text/plain" }, + ResponseType: "text", + }, + "POST /api/json": { + ResponseBody: { message: "Data received" }, + ResponseStatus: 201, + ResponseHeaders: { "Content-Type": "application/json" }, + ResponseType: "json", + }, + "PUT /api/blob": { + ResponseBody: new Blob(["binary data"], { + type: "application/octet-stream", + }), + ResponseStatus: 200, + ResponseHeaders: { "Content-Type": "application/octet-stream" }, + ResponseType: "blob", + }, + "DELETE /api/formData": { + ResponseBody: new FormData(), + ResponseStatus: 204, + ResponseHeaders: { "Content-Type": "multipart/form-data" }, + ResponseType: "formData", + }, +}; + +// Tests for different response types using XMLHttpRequest +describe("ApiMock tests for different HTTP methods and response types", () => { + test("should handle text response correctly", (done) => { + const xhr = new XMLHttpRequest(); + xhr.open("GET", "/api/text"); + xhr.onload = () => { + expect(xhr.responseText).toBe("Simple text response"); + expect(xhr.getResponseHeader("Content-Type")).toBe("text/plain"); + done(); + }; + xhr.send(); + }); + + test("should handle JSON response correctly", (done) => { + const xhr = new XMLHttpRequest(); + xhr.open("POST", "/api/json"); + xhr.onload = () => { + expect(JSON.parse(xhr.responseText)).toEqual({ + message: "Data received", + }); + expect(xhr.status).toBe(201); + done(); + }; + xhr.send(); + }); + + test("should handle Blob response correctly", (done) => { + const xhr = new XMLHttpRequest(); + xhr.responseType = "blob"; + xhr.open("PUT", "/api/blob"); + xhr.onload = () => { + expect(xhr.response.size).toBeGreaterThan(0); + expect(xhr.getResponseHeader("Content-Type")).toBe( + "application/octet-stream", + ); + done(); + }; + xhr.send(); + }); + + test("should handle FormData response correctly", (done) => { + const xhr = new XMLHttpRequest(); + xhr.open("DELETE", "/api/formData"); + xhr.onload = () => { + expect(xhr.status).toBe(204); + // FormData is not directly inspectable in the response, just check for correct handling + done(); + }; + xhr.send(); + }); + + test("should handle aborted requests", (done) => { + const xhr = new XMLHttpRequest(); + xhr.open("GET", "/api/text"); + xhr.onerror = () => { + expect(xhr.statusText).toBe("AbortError"); + done(); + }; + xhr.send(); + xhr.abort(); + }); +}); diff --git a/apps/owlserver-chrome/src/services/api_mock.ts b/apps/owlserver-chrome/src/services/api_mock.ts new file mode 100644 index 00000000..c7a763b9 --- /dev/null +++ b/apps/owlserver-chrome/src/services/api_mock.ts @@ -0,0 +1,158 @@ +type Config = { + mockData: Record; + delay?: number; +}; + +export type MockResponse = { + ResponseStatus: number; + ResponseBody: Blob | FormData | ArrayBuffer | string; + ResponseHeaders?: HeadersInit; + ResponseType?: "json" | "text" | "blob" | "formData" | "arrayBuffer"; +}; + +class ApiMock { + private mockData: Record; + private delay: number; + + constructor(config: Config) { + this.mockData = config.mockData; + this.delay = config.delay ?? 100; // Default delay + } + + createFakeXHR() { + const mock = this.mockData; + const delay = this.delay; + + return class FakeXMLHttpRequest { + static DONE = 4; + static UNSENT = 0; + static HEADERS_RECEIVED = 2; + static LOADING = 3; + static OPENED = 1; + + readyState: number = FakeXMLHttpRequest.UNSENT; + // biome-ignore lint/suspicious/noExplicitAny: + onreadystatechange: ((this: XMLHttpRequest, ev: Event) => any) | null = + null; + // biome-ignore lint/suspicious/noExplicitAny: + response: any; + responseText = ""; + responseType = ""; + status = 0; + statusText = ""; // Default empty status text + responseURL = ""; // Default empty response URL + responseXML = null; // Default null for responseXML + timeout = 0; // Default timeout + is_async: boolean; + method: string; + url: string; + requestHeaders: Record = {}; + responseHeaders: Record = {}; + + open(method: string, url: string, async = true) { + this.method = method; + this.url = url; + this.is_async = async; + this.readyState = FakeXMLHttpRequest.OPENED; + this.triggerEvent("readystatechange"); + } + + // biome-ignore lint/suspicious/noExplicitAny: + send(data?: any) { + const responseKey = `${this.method} ${this.url}`; + const mockResponse = mock[responseKey]; + + if (!mockResponse) { + this.status = 404; + this.triggerEvent("readystatechange"); + this.triggerEvent("error"); + return; + } + + setTimeout(() => { + this.status = mockResponse.ResponseStatus; + // Use the helper function to ensure type safety + this.responseHeaders = ApiMock.convertHeaders( + mockResponse.ResponseHeaders, + ); + this.handleResponseType(mockResponse); + this.readyState = FakeXMLHttpRequest.DONE; + this.triggerEvent("readystatechange"); + this.triggerEvent("load"); + }, delay); + } + + setRequestHeader(header: string, value: string) { + this.requestHeaders[header] = value; + } + + getAllResponseHeaders() { + return this.responseHeaders; + } + + abort() { + this.readyState = FakeXMLHttpRequest.UNSENT; + this.triggerEvent("abort"); + } + + private handleResponseType(mockResponse: MockResponse) { + switch (mockResponse.ResponseType) { + case "json": + this.response = JSON.stringify(mockResponse.ResponseBody); + this.responseText = this.response; + break; + case "blob": + this.response = new Blob([mockResponse.ResponseBody as BlobPart], { + type: "application/octet-stream", + }); + break; + case "arrayBuffer": { + const encoder = new TextEncoder(); + this.response = encoder.encode( + mockResponse.ResponseBody.toString(), + ).buffer; + break; + } + // NOTE: Default also handles "text" case. + default: + this.response = mockResponse.ResponseBody.toString(); + this.responseText = this.response; + + if (!DOMParser) { + return; + } + + this.responseXML = new DOMParser().parseFromString( + this.response, + "text/xml", + ); + break; + } + } + + private triggerEvent(type: string) { + if (this.onreadystatechange) { + // Explicitly setting the 'this' context to current instance when calling the handler + this.onreadystatechange.call(this, new Event(type)); + } + } + }; + } + + private static convertHeaders(headers?: HeadersInit): Record { + if (!headers) return {}; + if (headers instanceof Headers) { + const result: Record = {}; + headers.forEach((value, key) => { + result[key] = value; + }); + return result; + } + if (Array.isArray(headers)) { + return Object.fromEntries(headers); + } + return headers; + } +} + +export default ApiMock; diff --git a/apps/owlserver-chrome/src/services/api_recorder.ts b/apps/owlserver-chrome/src/services/api_recorder.ts new file mode 100644 index 00000000..e24243da --- /dev/null +++ b/apps/owlserver-chrome/src/services/api_recorder.ts @@ -0,0 +1,80 @@ +import { ActionType, sendToBackground } from "./background_interactor"; + +class APIRecorder extends XMLHttpRequest { + private _method: string; + private _url: string; + private _requestHeaders: Record = {}; + // biome-ignore lint/suspicious/noExplicitAny: it's fine + private _postData: any; + private _startTime: string; + + open( + method: string, + url: string | URL, + async = true, + username?: string, + password?: string, + ): void { + this._method = method; + this._url = url.toString(); + this._requestHeaders = {}; + this._startTime = new Date().toISOString(); + this._postData = {}; + super.open(method, url.toString(), async, username, password); + } + + setRequestHeader(header: string, value: string): void { + this._requestHeaders[header] = value; + super.setRequestHeader(header, value); + } + + send(postData?: Document | BodyInit | null): void { + this._postData = postData; + this.addEventListener("load", () => { + sendToBackground(this._method, ActionType.RequestMethod); + sendToBackground(this._url.toLowerCase(), ActionType.RequestUrl); + sendToBackground(JSON.stringify(this._postData), ActionType.RequestBody); + sendToBackground( + JSON.stringify(this._requestHeaders), + ActionType.RequestHeaders, + ); + + const responseHeaders = this.getAllResponseHeaders(); + sendToBackground( + JSON.stringify(responseHeaders), + ActionType.ResponseHeaders, + ); + + let responseBody = ""; + switch (this.responseType) { + case "": + case "text": + responseBody = this.responseText; + break; + case "arraybuffer": { + const buffer = new Uint8Array(this.response); + const decoder = new TextDecoder(); + responseBody = decoder.decode(buffer); + break; + } + case "blob": + responseBody = "BLOB"; + break; + case "document": { + const serializer = new XMLSerializer(); + responseBody = serializer.serializeToString(this.response); + break; + } + case "json": + responseBody = JSON.stringify(this.response); + break; + } + if (this.responseType !== "blob") { + sendToBackground(responseBody, ActionType.ResponseBody); + } + }); + super.send(postData as XMLHttpRequestBodyInit); + } +} + +export default APIRecorder; diff --git a/apps/owlserver-chrome/src/services/scenario_runner.ts b/apps/owlserver-chrome/src/services/scenario_runner.ts deleted file mode 100644 index 2801de7a..00000000 --- a/apps/owlserver-chrome/src/services/scenario_runner.ts +++ /dev/null @@ -1,63 +0,0 @@ -"use client"; - -type Config = { - delay?: number; -}; - -type FetchOptions = { - method?: string; - dataType?: string; -}; - -type MockResponse = { - body: string; - status: number; - headers?: HeadersInit; -}; - -class ApiMock { - private mockData: Record; - private delay: number; - - constructor(config: Config) { - this.mockData = config.mockData; - this.delay = config.delay ?? 100; // Use nullish coalescing instead of logical OR for default value - } - - async fetch(url: string, options?: FetchOptions): Promise { - const response = this.findMockResponse(url, options); - if (response) { - return Promise.resolve( - new Response(JSON.stringify(response.body), { - status: response.status, - headers: response.headers, - }), - ); - } - return Promise.resolve(new Response(null, { status: 404 })); - } - - private findMockResponse( - url: string, - options?: FetchOptions, - ): MockResponse | null { - if (this.mockData[url]) { - return this.mockData[url]; - } - - const methodUrlKey = options?.method - ? `${options.method.toUpperCase()} ${url}` - : ""; - if (this.mockData[methodUrlKey]) { - return this.mockData[methodUrlKey]; - } - - const dataTypeKey = options?.dataType ?? ""; - if (this.mockData[dataTypeKey]) { - return this.mockData[dataTypeKey]; - } - return null; - } -} - -export default ApiMock; diff --git a/apps/owlserver-chrome/src/wrappers.ts b/apps/owlserver-chrome/src/wrappers.ts index df621273..5edd3626 100644 --- a/apps/owlserver-chrome/src/wrappers.ts +++ b/apps/owlserver-chrome/src/wrappers.ts @@ -1,81 +1,51 @@ -import "./observers/document"; -import { ActionType, sendToBackground } from "./services/background_interactor"; - -((xhr) => { - const XHR = XMLHttpRequest.prototype; - - const open = XHR.open; - const send = XHR.send; - const setRequestHeader = XHR.setRequestHeader; - - XHR.open = function (method: string, url: string | URL) { - this._method = method; - this._url = url.toString(); - this._requestHeaders = {}; - this._startTime = new Date().toISOString(); - this._postData = {}; - // biome-ignore lint/style/noArguments: dynamic - return open.apply(this, arguments); - }; - - XHR.setRequestHeader = function (header: string, value: string) { - this._requestHeaders[header] = value; - - // biome-ignore lint/style/noArguments: dynamic - return setRequestHeader.apply(this, arguments); - }; - - XHR.send = function (postData?: Document | BodyInit | null) { - this._postData = postData; - this.addEventListener("load", () => { - sendToBackground(this._method, ActionType.RequestMethod); - sendToBackground(this._url.toLowerCase(), ActionType.RequestUrl); - sendToBackground(JSON.stringify(this._postData), ActionType.RequestBody); - sendToBackground( - JSON.stringify(this._requestHeaders), - ActionType.RequestHeaders, - ); - - const responseHeaders = this.getAllResponseHeaders(); - sendToBackground( - JSON.stringify(responseHeaders), - ActionType.ResponseHeaders, - ); - - let responseBody = ""; - switch (this.responseType) { - case "": - case "text": - responseBody = this.responseText; - break; - case "arraybuffer": { - const buffer = new Uint8Array(this.response); - const decoder = new TextDecoder(); - responseBody = decoder.decode(buffer); - break; - } - case "blob": { - responseBody = "BLOB"; - break; - } - case "document": { - const serializer = new XMLSerializer(); - responseBody = serializer.serializeToString(this.response); - break; - } - case "json": - responseBody = JSON.stringify(this.response); - break; - } - if (this.responseType !== "blob") { - // Ensure we do not send blob data synchronously - sendToBackground(responseBody, ActionType.ResponseBody); - } - }); - - // biome-ignore lint/style/noArguments: dynamic - return send.apply(this, arguments); - }; -})(XMLHttpRequest); - -window.XMLHttpRequest = XMLHttpRequest; // Ensure global XMLHttpRequest is modified +// Import necessary classes and types +import APIRecorder from "./services/api_recorder"; + +// Define the mode types +enum ModeType { + Record = "record", + Mock = "mock", + Original = "original", +} + +// Declare new property on the window object +declare global { + interface Window { + wrapper: typeof XHRWrapper; + } +} + +// Define the XHRWrapper class +// biome-ignore lint/complexity/noStaticOnlyClass: +class XHRWrapper extends XMLHttpRequest { + public static originalXHR: typeof XMLHttpRequest = XMLHttpRequest; + public static injectedXHR: typeof XMLHttpRequest = XMLHttpRequest; + public static mode: ModeType = ModeType.Original; + static XHRWrapper: typeof XHRWrapper; + + // Set the mode to record + static record() { + XHRWrapper.injectedXHR = APIRecorder; // Assuming APIRecorder extends XMLHttpRequest + XHRWrapper.wrap(ModeType.Record); + } + + // Revert to the original XMLHttpRequest + static remove() { + XHRWrapper.injectedXHR = XHRWrapper.originalXHR; + XHRWrapper.wrap(ModeType.Original); + } + + // Wrap the XMLHttpRequest with the specified mode + static wrap(mode: ModeType) { + XHRWrapper.mode = mode; + ((xhr) => XHRWrapper.injectedXHR)(XMLHttpRequest); + window.XMLHttpRequest = XMLHttpRequest; + } +} + +if (typeof window !== "undefined") { + window.wrapper = XHRWrapper; // Make the wrapper available globally on the window object +} + +// Export the XHRWrapper class as a default export +export default XHRWrapper; diff --git a/apps/owlserver-chrome/tsconfig.json b/apps/owlserver-chrome/tsconfig.json index 48a93278..2feb755a 100644 --- a/apps/owlserver-chrome/tsconfig.json +++ b/apps/owlserver-chrome/tsconfig.json @@ -17,5 +17,5 @@ "typeRoots": ["node_modules/@types", "node_modules/@chrome"] }, "include": ["./src"], - "exclude": ["./node_modules", "./dist"] + "exclude": ["./node_modules", "./dist", "./**/*.test.ts"] } diff --git a/package.json b/package.json index d6d531d4..f6b9b25f 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,6 @@ "scripts": { "build": "turbo build", "dev": "turbo dev", - "lint": "turbo lint", - "format": "prettier --write \"**/*.{ts,tsx,md}\"", "navigate": "/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222 --no-sandbox --incognito", "capture": "pnpm exec node ./apps/capturer/index.mjs" }, diff --git a/packages/typescript-config/base.json b/packages/typescript-config/base.json index 0f80cfd6..aa444a46 100644 --- a/packages/typescript-config/base.json +++ b/packages/typescript-config/base.json @@ -7,7 +7,7 @@ "esModuleInterop": true, "incremental": false, "isolatedModules": true, - "lib": ["es2022", "DOM", "DOM.Iterable"], + "lib": ["es2022", "DOM", "DOM.Iterable", "esnext"], "module": "NodeNext", "moduleDetection": "force", "moduleResolution": "NodeNext", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09681a19..1abbd8eb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,6 +133,9 @@ importers: framer-motion: specifier: ^6 version: 6.5.1(react-dom@18.3.1)(react@18.3.1) + jsdom: + specifier: ^24.0.0 + version: 24.0.0 next: specifier: ^14.0.4 version: 14.2.3(react-dom@18.3.1)(react@18.3.1) @@ -205,7 +208,7 @@ importers: version: 5.3.3 vitest: specifier: ^1.5.2 - version: 1.5.2(@types/node@20.12.7) + version: 1.5.2(@types/node@20.12.7)(jsdom@24.0.0) apps/rag: {} @@ -301,7 +304,7 @@ importers: version: 5.3.3 vitest: specifier: ^1.5.2 - version: 1.5.2(@types/node@20.12.7) + version: 1.5.2(@types/node@20.12.7)(jsdom@24.0.0) packages/typescript-config: {} @@ -5375,7 +5378,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: true /agentkeepalive@4.5.0: resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} @@ -5675,7 +5677,6 @@ packages: /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: true /autoprefixer@10.4.19(postcss@8.4.38): resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} @@ -6276,7 +6277,6 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - dev: true /commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} @@ -6559,6 +6559,12 @@ packages: hasBin: true dev: true + /cssstyle@4.0.1: + resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} + engines: {node: '>=18'} + dependencies: + rrweb-cssom: 0.6.0 + /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -6578,6 +6584,13 @@ packages: engines: {node: '>= 14'} dev: true + /data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + /data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} @@ -6637,6 +6650,9 @@ packages: dependencies: ms: 2.1.2 + /decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -6739,7 +6755,6 @@ packages: /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - dev: true /delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} @@ -6931,7 +6946,6 @@ packages: /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - dev: true /env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} @@ -7900,7 +7914,6 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: true /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} @@ -8358,6 +8371,12 @@ packages: wbuf: 1.7.3 dev: true + /html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + dependencies: + whatwg-encoding: 3.1.1 + /html-entities@2.5.2: resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} dev: true @@ -8423,7 +8442,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: true /http-proxy-middleware@2.0.6(@types/express@4.17.21): resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==} @@ -8475,7 +8493,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: true /human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} @@ -8513,7 +8530,6 @@ packages: requiresBuild: true dependencies: safer-buffer: 2.1.2 - optional: true /icss-utils@5.1.0(postcss@8.4.38): resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} @@ -8857,6 +8873,9 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + /is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} dependencies: @@ -9059,6 +9078,41 @@ packages: /jsbn@1.1.0: resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + /jsdom@24.0.0: + resolution: {integrity: sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + cssstyle: 4.0.1 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.4 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.10 + parse5: 7.1.2 + rrweb-cssom: 0.6.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.17.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + /jsesc@0.5.0: resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} hasBin: true @@ -9474,14 +9528,12 @@ packages: /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: true /mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - dev: true /mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} @@ -9887,6 +9939,9 @@ packages: dev: false optional: true + /nwsapi@2.2.10: + resolution: {integrity: sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==} + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -10199,7 +10254,6 @@ packages: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} dependencies: entities: 4.5.0 - dev: true /parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -10994,6 +11048,9 @@ packages: dev: true optional: true + /psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: @@ -11004,7 +11061,6 @@ packages: /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - dev: true /qs@6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} @@ -11013,6 +11069,9 @@ packages: side-channel: 1.0.6 dev: true + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -11320,7 +11379,6 @@ packages: /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - dev: true /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} @@ -11424,6 +11482,9 @@ packages: fsevents: 2.3.3 dev: true + /rrweb-cssom@0.6.0: + resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} + /run-applescript@7.0.0: resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} engines: {node: '>=18'} @@ -11528,6 +11589,12 @@ packages: dev: true optional: true + /saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + dependencies: + xmlchars: 2.2.0 + /scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} dependencies: @@ -12339,6 +12406,9 @@ packages: upper-case: 1.1.3 dev: true + /symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + /synckit@0.9.0: resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -12558,6 +12628,21 @@ packages: engines: {node: '>=0.6'} dev: true + /tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + /tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + dependencies: + punycode: 2.3.1 + /tree-dump@1.0.1(tslib@2.6.2): resolution: {integrity: sha512-WCkcRBVPSlHHq1dc/px9iOfqklvzCbdRwvlNfxGZsrHqf6aZttfPrd7DJTt6oR10dwUfpFFQeVTkPbBIZxX/YA==} engines: {node: '>=10.0'} @@ -12906,6 +12991,10 @@ packages: dev: false optional: true + /universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + /universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -12950,6 +13039,12 @@ packages: punycode: 2.3.1 dev: true + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + /use-callback-ref@1.3.2(@types/react@18.3.1)(react@18.3.1): resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} engines: {node: '>=10'} @@ -13084,7 +13179,7 @@ packages: fsevents: 2.3.3 dev: true - /vitest@1.5.2(@types/node@20.12.7): + /vitest@1.5.2(@types/node@20.12.7)(jsdom@24.0.0): resolution: {integrity: sha512-l9gwIkq16ug3xY7BxHwcBQovLZG75zZL0PlsiYQbf76Rz6QGs54416UWMtC0jXeihvHvcHrf2ROEjkQRVpoZYw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -13119,6 +13214,7 @@ packages: chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 + jsdom: 24.0.0 local-pkg: 0.5.0 magic-string: 0.30.10 pathe: 1.1.2 @@ -13140,6 +13236,12 @@ packages: - terser dev: true + /w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + dependencies: + xml-name-validator: 5.0.0 + /watchpack@2.4.1: resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} engines: {node: '>=10.13.0'} @@ -13164,6 +13266,10 @@ packages: resolution: {integrity: sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==} dev: true + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + /webpack-browser-extension-common-errors@1.1.0(webpack@5.89.0): resolution: {integrity: sha512-1EqyQx5LZ85zWZr7NwmEjV4vdof1NzSTbYyh0bCauwTKP738vUrSETkzhMmZcLE4AERkMDk7Mp6BvpUlLJkB9g==} engines: {node: '>=18'} @@ -13484,6 +13590,23 @@ packages: engines: {node: '>=0.8.0'} dev: true + /whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + dependencies: + iconv-lite: 0.6.3 + + /whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + /whatwg-url@14.0.0: + resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + engines: {node: '>=18'} + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: @@ -13639,7 +13762,13 @@ packages: optional: true utf-8-validate: optional: true - dev: true + + /xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + /xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}