diff --git a/README.md b/README.md index d6ef65f..f0eb70a 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,30 @@ yarn serve prod The `near-social-viewer` web component supports several attributes: -- `src`: the src of the widget to render (e.g. `devs.near/widget/default`) -- `code`: raw, valid, stringified widget code to render (e.g. `"return

hello world

"`) -- `initialprops`: initial properties to be passed to the rendered widget. -- `rpc`: rpc url to use for requests within the VM -- `network`: network to connect to for rpc requests & wallet connection +* `src`: the src of the widget to render (e.g. `devs.near/widget/default`) +* `code`: raw, valid, stringified widget code to render (e.g. `"return

hello world

"`) +* `initialprops`: initial properties to be passed to the rendered widget +* `rpc`: rpc url to use for requests within the VM +* `network`: network to connect to for rpc requests & wallet connection +* `config`: options to modify the underlying VM or usage with devtools, see available [configurations](#configuration-options) + +## Configuration Options + +To support specific features of the VM or an accompanying development server, provide a configuration following this structure: + +```jsonc +{ + "dev": { + // Configuration options dedicated to the development server + "hotreload": { + "enabled": boolean, // Determines if hot reload is enabled (e.g., true) + "wss": string // WebSocket server URL to connect to. Optional. Defaults to `ws://${window.location.host}` (e.g., "ws://localhost:3001") + } + } +} +``` -## Configuring VM Custom Elements +## Adding VM Custom Elements Since [NearSocial/VM v2.1.0](https://github.com/NearSocial/VM/blob/master/CHANGELOG.md#210), a gateway can register custom elements where the key is the name of the element, and the value is a function that returns a React component. For example: @@ -126,7 +143,17 @@ yarn test:ui:codespaces In general it is a good practice, and very helpful for reviewers and users of this project, that all use cases are covered in Playwright tests. Also, when contributing, try to make your tests as simple and clear as possible, so that they serve as examples on how to use the functionality. -## Use redirectmap for development +## Local Widget Development + +There are several strategies for accessing local widget code during development. + +### Proxy RPC + +The recommended, least invasive strategy is to provide a custom RPC url that proxies requests for widget code. Widget code is stored in the [socialdb](https://github.com/NearSocial/social-db), and so it involves an RPC request to get the stringified code. We can proxy this request to use our local code instead. + +You can build a custom proxy server, or [bos-workspace](https://github.com/nearbuilders/bos-workspace) provides a proxy by default and will automatically inject it to the `rpc` attribute if you provide the path to your web component's dist, or a link to it stored on [NEARFS](https://github.com/vgrichina/nearfs). See more in [Customizing the Gateway](https://github.com/NEARBuilders/bos-workspace?tab=readme-ov-file#customizing-the-gateway). + +### Redirect Map The NEAR social VM supports a feature called `redirectMap` which allows you to load widgets from other sources than the on chain social db. An example redirect map can look like this: @@ -142,6 +169,12 @@ By setting the session storage key `nearSocialVMredirectMap` to the JSON value o You can also use the same mechanism as [near-discovery](https://github.com/near/near-discovery/) where you can load components from a locally hosted [bos-loader](https://github.com/near/bos-loader) by adding the key `flags` to localStorage with the value `{"bosLoaderUrl": "http://127.0.0.1:3030" }`. +### Hot Reload + +The above strategies require changes to be reflected either on page reload, or from a fresh rpc request. For faster updates, there is an option in `config` to enable hot reload via dev.hotreload (see [configurations](#configuration-options)), which will try to connect to a web socket server on the same port and use redirectMap with most recent data. + +This feature works best when accompanied with [bos-workspace](https://github.com/nearbuilders/bos-workspace), which will automatically inject a config to the attribute if you provide the path to your web component's dist, or a link to it stored on [NEARFS](https://github.com/vgrichina/nearfs). See more in [Customizing the Gateway](https://github.com/NEARBuilders/bos-workspace?tab=readme-ov-file#customizing-the-gateway). It can be disabled with the `--no-hot` flag. + ## Configuring Ethers Since [NearSocial/VM v1.3.0](https://github.com/NearSocial/VM/blob/master/CHANGELOG.md#130), the VM has exposed Ethers and ethers in the global scope, as well as a Web3Connect custom element for bringing up wallet connect. diff --git a/package.json b/package.json index 7ac417b..0112576 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "react-bootstrap-typeahead": "^6.1.2", "react-dom": "^18.2.0", "react-router-dom": "^6.20.0", + "socket.io": "^4.7.5", + "socket.io-client": "^4.7.5", "styled-components": "^5.3.6" }, "scripts": { diff --git a/playwright-tests/tests/redirectmap.spec.js b/playwright-tests/tests/redirectmap.spec.js index 88dcb89..25cd9ed 100644 --- a/playwright-tests/tests/redirectmap.spec.js +++ b/playwright-tests/tests/redirectmap.spec.js @@ -1,4 +1,7 @@ -import { test, describe, expect } from "@playwright/test"; +import { describe, expect, test } from "@playwright/test"; +import http from "http"; +import { Server } from "socket.io"; +import { waitForSelectorToBeVisible } from "../testUtils"; describe("bos-loader-url", () => { test.use({ @@ -42,14 +45,151 @@ describe("session-storage", () => { }) ); }); - await page.goto("/something.near/widget/testcomponent"); - await page.evaluate(() => { - console.log( - JSON.parse(sessionStorage.getItem("nearSocialVMredirectMap")) - ); + }); + + describe("hot-reload", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/"); + }); + + test("should trigger api request to */socket.io/* if hot reload is enabled", async ({ + page, + }) => { + let websocketCount = 0; + + await page.route("**/socket.io/*", (route) => { + websocketCount++; + route.continue(); + }); + + await page.evaluate(() => { + document.body.innerHTML = ``; + }); + + await waitForSelectorToBeVisible(page, "near-social-viewer"); + + expect(websocketCount).toBeGreaterThan(0); + }); + + test("should not trigger api request to */socket.io/* if hot reload is not enabled", async ({ + page, + }) => { + let websocketCount = 0; + + await page.route("**/socket.io/*", (route) => { + websocketCount++; + route.continue(); + }); + + await page.evaluate(() => { + document.body.innerHTML = ``; + }); + + await waitForSelectorToBeVisible(page, "near-social-viewer"); + + expect(websocketCount).toEqual(0); + }); + + describe("with running socket server", () => { + let io, httpServer; + const PORT = 3001; + let HOST = "localhost"; + + test.beforeAll(async () => { + httpServer = http.createServer(); + + io = new Server(httpServer, { + cors: { + origin: `http://${HOST}:3000`, + methods: ["GET", "POST"], + }, + }); + + io.on("connection", () => { + io.emit("fileChange", { + "anybody.near/widget/test": { + code: "return

hello world

;", + }, + }); + }); + + // wait for socket start + await new Promise((resolve) => { + httpServer.listen(PORT, HOST, () => { + resolve(); + }); + }); + }); + + test("should show local redirect map and react to changes", async ({ + page, + }) => { + // Verify the viewer is visible + await waitForSelectorToBeVisible(page, "near-social-viewer"); + + await page.evaluate(() => { + const viewer = document.querySelector("near-social-viewer"); + viewer.setAttribute("src", "anybody.near/widget/test"); // this code does not exist + }); + + await page.waitForSelector( + 'div.alert.alert-danger:has-text("is not found")' + ); + + // Verify error + const errMsg = await page.locator( + 'div.alert.alert-danger:has-text("is not found")' + ); + + expect(await errMsg.isVisible()).toBe(true); + + let websocketCount = 0; + + await page.route("**/socket.io/*", (route) => { + websocketCount++; + route.continue(); + }); + + const config = { + dev: { hotreload: { enabled: true, wss: `ws://${HOST}:${PORT}` } }, + }; + + // Enable hot reload + await page.evaluate( + ({ config }) => { + const viewer = document.querySelector("near-social-viewer"); + viewer.setAttribute("config", JSON.stringify(config)); + }, + { config } + ); + + await page.waitForSelector("near-social-viewer"); + + // Get the value of the config attribute + const actualConfig = await page.evaluate(() => { + const viewer = document.querySelector("near-social-viewer"); + return viewer.getAttribute("config"); + }); + + // Assert it is set and equals custom value + expect(actualConfig).toBe(JSON.stringify(config)); + + // Assert web socket was hit + expect(websocketCount).toBeGreaterThan(0); + + await expect(await page.getByText("hello world")).toBeVisible(); + + io.emit("fileChange", { + "anybody.near/widget/test": { code: "return

goodbye world

;" }, + }); + + await expect(await page.getByText("goodbye world")).toBeVisible(); + }); + + test.afterAll(() => { + io.close(); + httpServer.close(); + }); }); - await expect( - await page.getByText("I come from a redirect map from session storage") - ).toBeVisible(); }); }); diff --git a/playwright-tests/tests/web3.spec.js b/playwright-tests/tests/web3.spec.js index 632920a..822d81f 100644 --- a/playwright-tests/tests/web3.spec.js +++ b/playwright-tests/tests/web3.spec.js @@ -27,7 +27,5 @@ test("should be possible to interact with web3 widgets", async ({ page }) => { await Web3ConnectButton.click(); - await expect( - page.getByRole("button", { name: "Connecting" }) - ).toBeVisible(); + await expect(page.getByRole("button", { name: "Connecting" })).toBeVisible(); }); diff --git a/src/App.js b/src/App.js index f5e4344..9af4f93 100644 --- a/src/App.js +++ b/src/App.js @@ -1,7 +1,7 @@ import "App.scss"; import "bootstrap-icons/font/bootstrap-icons.css"; import "bootstrap/dist/js/bootstrap.bundle"; -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo } from "react"; import "react-bootstrap-typeahead/css/Typeahead.css"; import { isValidAttribute } from "dompurify"; @@ -13,10 +13,9 @@ import { useLocation, } from "react-router-dom"; +import { BosWorkspaceProvider, useRedirectMap } from "./utils/bos-workspace"; import { EthersProvider } from "./utils/web3/ethers"; -const SESSION_STORAGE_REDIRECT_MAP_KEY = "nearSocialVMredirectMap"; - function Viewer({ widgetSrc, code, initialProps }) { const location = useLocation(); const searchParams = new URLSearchParams(location.search); @@ -36,23 +35,7 @@ function Viewer({ widgetSrc, code, initialProps }) { return pathSrc; }, [widgetSrc, path]); - const [redirectMap, setRedirectMap] = useState(null); - useEffect(() => { - (async () => { - const localStorageFlags = JSON.parse(localStorage.getItem("flags")); - - if (localStorageFlags?.bosLoaderUrl) { - setRedirectMap( - (await fetch(localStorageFlags.bosLoaderUrl).then((r) => r.json())) - .components - ); - } else { - setRedirectMap( - JSON.parse(sessionStorage.getItem(SESSION_STORAGE_REDIRECT_MAP_KEY)) - ); - } - })(); - }, []); + const redirectMap = useRedirectMap(); return ( <> @@ -67,12 +50,15 @@ function Viewer({ widgetSrc, code, initialProps }) { } function App(props) { - const { src, code, initialProps, rpc, network, selectorPromise } = props; + const { src, code, initialProps, rpc, network, selectorPromise, config } = + props; + const { initNear } = useInitNear(); useAccount(); + useEffect(() => { - const config = { + const VM = { networkId: network || "mainnet", selector: selectorPromise, customElements: { @@ -100,10 +86,10 @@ function App(props) { }; if (rpc) { - config.config.nodeUrl = rpc; + VM.config.nodeUrl = rpc; } - initNear && initNear(config); + initNear && initNear(VM); }, [initNear, rpc]); const router = createBrowserRouter([ @@ -117,7 +103,11 @@ function App(props) { }, ]); - return ; + return ( + + + + ); } export default App; diff --git a/src/index.js b/src/index.js index fb378cb..0258b26 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import React from "react"; import { createRoot } from "react-dom/client"; -import "./index.css"; import App from "./App"; +import "./index.css"; class NearSocialViewerElement extends HTMLElement { constructor() { @@ -27,7 +27,7 @@ class NearSocialViewerElement extends HTMLElement { } static get observedAttributes() { - return ["src", "code", "initialprops", "rpc", "network"]; + return ["src", "code", "initialprops", "rpc", "network", "config"]; } renderRoot() { @@ -36,6 +36,7 @@ class NearSocialViewerElement extends HTMLElement { const initialProps = this.getAttribute("initialprops"); const rpc = this.getAttribute("rpc"); const network = this.getAttribute("network"); + const config = this.getAttribute("config"); this.reactRoot.render( ); } diff --git a/src/utils/bos-workspace.js b/src/utils/bos-workspace.js new file mode 100644 index 0000000..735ac0c --- /dev/null +++ b/src/utils/bos-workspace.js @@ -0,0 +1,73 @@ +import React, { createContext, useContext, useEffect, useState } from "react"; +import io from "socket.io-client"; + +const SESSION_STORAGE_REDIRECT_MAP_KEY = "nearSocialVMredirectMap"; + +const defaultContext = { + hotreload: { + enabled: false, + }, +}; + +const BosWorkspaceContext = createContext(defaultContext); + +export const BosWorkspaceProvider = ({ config, children }) => { + return ( + + {children} + + ); +}; + +export function useRedirectMap() { + const { hotreload } = useContext(BosWorkspaceContext); + + const [hotReloadEnabled, setHotReloadEnabled] = useState(hotreload?.enabled); + const [devJson, setDevJson] = useState({}); + + useEffect(() => { + setHotReloadEnabled(hotreload?.enabled); + }, [hotreload]); + + useEffect(() => { + (async () => { + if (hotReloadEnabled) { + const socket = io(hotreload?.wss || `ws://${window.location.host}`, { + reconnectionAttempts: 1, // Limit reconnection attempts + }); + + socket.on("fileChange", (d) => { + console.log("File change detected via WebSocket", d); + setDevJson(d); + }); + + socket.on("connect_error", (error) => { + console.warn("WebSocket connection error. Switching to HTTP."); + console.warn(error); + + setHotReloadEnabled(false); + socket.disconnect(); + }); + + return () => { + socket.disconnect(); + }; + } else { + const localStorageFlags = JSON.parse(localStorage.getItem("flags")); + + if (localStorageFlags?.bosLoaderUrl) { + setDevJson( + (await fetch(localStorageFlags.bosLoaderUrl).then((r) => r.json())) + .components + ); + } else { + setDevJson( + JSON.parse(sessionStorage.getItem(SESSION_STORAGE_REDIRECT_MAP_KEY)) + ); + } + } + })(); + }, [hotReloadEnabled]); + + return devJson; +} diff --git a/yarn.lock b/yarn.lock index 417f7a7..4ba30db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4275,6 +4275,11 @@ "@smithy/types" "^3.3.0" tslib "^2.6.2" +"@socket.io/component-emitter@~3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" + integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== + "@stablelib/aead@^1.0.1": version "1.0.1" resolved "https://registry.npmjs.org/@stablelib/aead/-/aead-1.0.1.tgz#c4b1106df9c23d1b867eb9b276d8f42d5fc4c0c3" @@ -4461,6 +4466,18 @@ dependencies: "@types/node" "*" +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + +"@types/cors@^2.8.12": + version "2.8.17" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== + dependencies: + "@types/node" "*" + "@types/debug@^4.0.0": version "4.1.12" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" @@ -4592,6 +4609,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@>=10.0.0": + version "20.14.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420" + integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg== + dependencies: + undici-types "~5.26.4" + "@types/node@^18.0.6": version "18.19.39" resolved "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz#c316340a5b4adca3aee9dcbf05de385978590593" @@ -5771,6 +5795,11 @@ base64-js@^1.3.1: resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64id@2.0.0, base64id@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + basic-auth@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" @@ -6496,6 +6525,11 @@ cookie@0.6.0: resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookie@~0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + cookies@~0.9.0: version "0.9.1" resolved "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz#3ffed6f60bb4fb5f146feeedba50acc418af67e3" @@ -6528,6 +6562,14 @@ core-util-is@~1.0.0: resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cors@~2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + corser@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87" @@ -6784,7 +6826,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: version "4.3.5" resolved "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== @@ -7156,6 +7198,38 @@ end-of-stream@^1.4.1: dependencies: once "^1.4.0" +engine.io-client@~6.5.2: + version "6.5.4" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.5.4.tgz#b8bc71ed3f25d0d51d587729262486b4b33bd0d0" + integrity sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.17.1" + xmlhttprequest-ssl "~2.0.0" + +engine.io-parser@~5.2.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.2.tgz#37b48e2d23116919a3453738c5720455e64e1c49" + integrity sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw== + +engine.io@~6.5.2: + version "6.5.5" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.5.tgz#430b80d8840caab91a50e9e23cb551455195fc93" + integrity sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA== + dependencies: + "@types/cookie" "^0.4.1" + "@types/cors" "^2.8.12" + "@types/node" ">=10.0.0" + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.4.1" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.17.1" + enhanced-resolve@^5.17.0: version "5.17.0" resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz#d037603789dd9555b89aaec7eb78845c49089bc5" @@ -10366,7 +10440,7 @@ o3@^1.0.3: dependencies: capability "^0.2.5" -object-assign@^4.0.1, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -12141,6 +12215,45 @@ smart-buffer@^4.2.0: resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +socket.io-adapter@~2.5.2: + version "2.5.5" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz#c7a1f9c703d7756844751b6ff9abfc1780664082" + integrity sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg== + dependencies: + debug "~4.3.4" + ws "~8.17.1" + +socket.io-client@^4.7.5: + version "4.7.5" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.7.5.tgz#919be76916989758bdc20eec63f7ee0ae45c05b7" + integrity sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.5.2" + socket.io-parser "~4.2.4" + +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + +socket.io@^4.7.5: + version "4.7.5" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.5.tgz#56eb2d976aef9d1445f373a62d781a41c7add8f8" + integrity sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA== + dependencies: + accepts "~1.3.4" + base64id "~2.0.0" + cors "~2.8.5" + debug "~4.3.2" + engine.io "~6.5.2" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" + sockjs@^0.3.24: version "0.3.24" resolved "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" @@ -13078,7 +13191,7 @@ varint@^6.0.0: resolved "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0" integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== -vary@^1.1.2, vary@~1.1.2: +vary@^1, vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== @@ -13445,6 +13558,11 @@ ws@^8.13.0: resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== +ws@~8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + xml2js@^0.5.0: version "0.5.0" resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" @@ -13458,6 +13576,11 @@ xmlbuilder@~11.0.0: resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + xtend@^4.0.0, xtend@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"