diff --git a/packages/wallet-sdk/package.json b/packages/wallet-sdk/package.json index 45b13ec8fd..6d2fabd4e1 100644 --- a/packages/wallet-sdk/package.json +++ b/packages/wallet-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@coinbase/wallet-sdk", - "version": "4.3.2", + "version": "4.3.5", "description": "Coinbase Wallet JavaScript SDK", "keywords": [ "coinbase", @@ -23,7 +23,7 @@ "test": "vitest", "test:coverage": "yarn test:unit && open coverage/lcov-report/index.html", "prebuild": "rm -rf ./dist && node -p \"'export const VERSION = \\'' + require('./package.json').version + '\\';\\nexport const NAME = \\'' + require('./package.json').name + '\\';'\" > src/sdk-info.ts", - "build": "node compile-assets.cjs && tsc -p ./tsconfig.build.json && tsc-alias && cp -a src/vendor-js dist", + "build": "node compile-assets.cjs && tsc -p ./tsconfig.build.json && tsc-alias && cp -a src/vendor-js dist && cp src/sign/walletlink/relay/connection/HeartbeatWorker.js dist/sign/walletlink/relay/connection/", "dev": "yarn build && tsc --watch & nodemon --watch dist --delay 1 --exec tsc-alias", "typecheck": "tsc --noEmit", "lint": "eslint . --ext .ts,.tsx --fix", @@ -33,7 +33,8 @@ "@noble/hashes": "^1.4.0", "clsx": "^1.2.1", "eventemitter3": "^5.0.1", - "preact": "^10.24.2" + "preact": "^10.24.2", + "viem": "^2.27.2" }, "devDependencies": { "@size-limit/preset-big-lib": "^11.1.6", @@ -42,12 +43,15 @@ "@types/node": "^14.18.54", "@typescript-eslint/eslint-plugin": "^6.2.0", "@typescript-eslint/parser": "^6.2.0", + "@vitest/coverage-v8": "2.1.2", + "@vitest/web-worker": "3.2.1", "eslint": "^8.45.0", "eslint-config-preact": "^1.3.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-unused-imports": "^3.0.0", + "fake-indexeddb": "^6.0.0", "glob": "^11.0.0", "jest-websocket-mock": "^2.4.0", "jsdom": "^25.0.1", diff --git a/packages/wallet-sdk/src/sdk-info.ts b/packages/wallet-sdk/src/sdk-info.ts index 7c3c42582f..03398a1205 100644 --- a/packages/wallet-sdk/src/sdk-info.ts +++ b/packages/wallet-sdk/src/sdk-info.ts @@ -1,2 +1,2 @@ -export const VERSION = '4.3.0'; +export const VERSION = '4.3.5'; export const NAME = '@coinbase/wallet-sdk'; diff --git a/packages/wallet-sdk/src/sign/scw/SCWSigner.ts b/packages/wallet-sdk/src/sign/scw/SCWSigner.ts index 74bc1e3a76..33ef5c1d6d 100644 --- a/packages/wallet-sdk/src/sign/scw/SCWSigner.ts +++ b/packages/wallet-sdk/src/sign/scw/SCWSigner.ts @@ -1,5 +1,8 @@ +import { hexToNumber, isAddressEqual } from 'viem'; + import { Signer } from '../interface.js'; import { SCWKeyManager } from './SCWKeyManager.js'; +import { assertGetCapabilitiesParams } from './utils.js'; import { Communicator } from ':core/communicator/Communicator.js'; import { standardErrors } from ':core/error/errors.js'; import { RPCRequestMessage, RPCResponseMessage } from ':core/message/RPCMessage.js'; @@ -118,7 +121,7 @@ export class SCWSigner implements Signer { case 'eth_chainId': return hexStringFromNumber(this.chain.id); case 'wallet_getCapabilities': - return this.storage.loadObject(WALLET_CAPABILITIES_STORAGE_KEY); + return this.handleGetCapabilitiesRequest(request); case 'wallet_switchEthereumChain': return this.handleSwitchChainRequest(request); case 'eth_ecRecover': @@ -191,6 +194,51 @@ export class SCWSigner implements Signer { return popupResult; } + private async handleGetCapabilitiesRequest(request: RequestArguments) { + assertGetCapabilitiesParams(request.params); + + const requestedAccount = request.params[0]; + const filterChainIds = request.params[1]; // Optional second parameter + + if ( + !this.accounts.some((account: AddressString) => + isAddressEqual(account as `0x${string}`, requestedAccount) + ) + ) { + throw standardErrors.provider.unauthorized('no active account found'); + } + + const capabilities = this.storage.loadObject(WALLET_CAPABILITIES_STORAGE_KEY); + + // Return empty object if capabilities is undefined + if (!capabilities) { + return {}; + } + + // If no filter is provided, return all capabilities + if (!filterChainIds || filterChainIds.length === 0) { + return capabilities; + } + + // Convert filter chain IDs to numbers once for efficient lookup + const filterChainNumbers = new Set(filterChainIds.map((chainId) => hexToNumber(chainId))); + + // Filter capabilities + const filteredCapabilities = Object.fromEntries( + Object.entries(capabilities).filter(([capabilityKey]) => { + try { + const capabilityChainNumber = hexToNumber(capabilityKey as `0x${string}`); + return filterChainNumbers.has(capabilityChainNumber); + } catch { + // If capabilityKey is not a valid hex string, exclude it + return false; + } + }) + ); + + return filteredCapabilities; + } + private async sendEncryptedRequest(request: RequestArguments): Promise { const sharedSecret = await this.keyManager.getSharedSecret(); if (!sharedSecret) { diff --git a/packages/wallet-sdk/src/sign/scw/utils.ts b/packages/wallet-sdk/src/sign/scw/utils.ts new file mode 100644 index 0000000000..7f6de0f44c --- /dev/null +++ b/packages/wallet-sdk/src/sign/scw/utils.ts @@ -0,0 +1,27 @@ +import { isAddress } from 'viem'; + +import { standardErrors } from ':core/error/errors.js'; + +export function assertGetCapabilitiesParams( + params: unknown +): asserts params is [`0x${string}`, `0x${string}`[]?] { + if (!params || !Array.isArray(params) || (params.length !== 1 && params.length !== 2)) { + throw standardErrors.rpc.invalidParams(); + } + + if (typeof params[0] !== 'string' || !isAddress(params[0])) { + throw standardErrors.rpc.invalidParams(); + } + + if (params.length === 2) { + if (!Array.isArray(params[1])) { + throw standardErrors.rpc.invalidParams(); + } + + for (const param of params[1]) { + if (typeof param !== 'string' || !param.startsWith('0x')) { + throw standardErrors.rpc.invalidParams(); + } + } + } +} diff --git a/packages/wallet-sdk/src/sign/walletlink/relay/connection/HeartbeatWorker.js b/packages/wallet-sdk/src/sign/walletlink/relay/connection/HeartbeatWorker.js new file mode 100644 index 0000000000..f772671838 --- /dev/null +++ b/packages/wallet-sdk/src/sign/walletlink/relay/connection/HeartbeatWorker.js @@ -0,0 +1,62 @@ +// Copyright (c) 2018-2025 Coinbase, Inc. + +/** + * This worker is used to send heartbeat messages to the main thread. + * It is used to keep the websocket connection alive when the webpage is backgrounded. + * + */ + +// Define the heartbeat interval constant directly in the worker to avoid import issues +const HEARTBEAT_INTERVAL = 10000; + +let heartbeatInterval; + +// Listen for messages from the main thread +self.addEventListener('message', (event) => { + const { type } = event.data; + + switch (type) { + case 'start': + startHeartbeat(); + break; + case 'stop': + stopHeartbeat(); + break; + default: + console.warn('Unknown message type received by HeartbeatWorker:', type); + } +}); + +function startHeartbeat() { + // Clear any existing interval + if (heartbeatInterval) { + clearInterval(heartbeatInterval); + } + + // Start the heartbeat interval + heartbeatInterval = setInterval(() => { + // Send heartbeat message to main thread + const response = { type: 'heartbeat' }; + self.postMessage(response); + }, HEARTBEAT_INTERVAL); + + // Send confirmation that heartbeat started + const response = { type: 'started' }; + self.postMessage(response); +} + +function stopHeartbeat() { + if (heartbeatInterval) { + clearInterval(heartbeatInterval); + heartbeatInterval = undefined; + } + + // Send confirmation that heartbeat stopped + const response = { type: 'stopped' }; + self.postMessage(response); +} + +// Handle worker termination +self.addEventListener('beforeunload', () => { + stopHeartbeat(); +}); \ No newline at end of file diff --git a/packages/wallet-sdk/src/sign/walletlink/relay/connection/HeartbeatWorker.test.ts b/packages/wallet-sdk/src/sign/walletlink/relay/connection/HeartbeatWorker.test.ts new file mode 100644 index 0000000000..026ce2fd4a --- /dev/null +++ b/packages/wallet-sdk/src/sign/walletlink/relay/connection/HeartbeatWorker.test.ts @@ -0,0 +1,234 @@ +import '@vitest/web-worker'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +describe('HeartbeatWorker', () => { + let worker: Worker; + + beforeEach(async () => { + // Create a new worker instance for each test + worker = new Worker(new URL('./HeartbeatWorker.js', import.meta.url)); + }); + + afterEach(() => { + if (worker) { + worker.terminate(); + } + }); + + describe('Message Handling', () => { + it('should start heartbeat and send confirmation', async () => { + const messagePromise = new Promise((resolve) => { + worker.addEventListener('message', resolve, { once: true }); + }); + + worker.postMessage({ type: 'start' }); + + const event = await messagePromise; + expect(event.data).toEqual({ type: 'started' }); + }); + + it('should send heartbeat messages at regular intervals', async () => { + worker.postMessage({ type: 'start' }); + + await new Promise((resolve) => { + worker.addEventListener('message', (event) => { + if (event.data.type === 'started') { + resolve(); + } + }, { once: true }); + }); + + const heartbeats: MessageEvent[] = []; + const heartbeatPromise = new Promise((resolve) => { + let count = 0; + worker.addEventListener('message', (event) => { + if (event.data.type === 'heartbeat') { + heartbeats.push(event); + count++; + if (count >= 2) { + resolve(); + } + } + }); + }); + + // Wait for at least 2 heartbeat messages (this will take ~20 seconds in real time) + // For testing, we'll use a shorter timeout and verify the structure + await Promise.race([ + heartbeatPromise, + new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for heartbeats')), 25000)) + ]); + + expect(heartbeats.length).toBeGreaterThanOrEqual(2); + heartbeats.forEach(event => { + expect(event.data).toEqual({ type: 'heartbeat' }); + }); + }, 30000); // 30 second timeout for this test + + it('should stop heartbeat and send confirmation', async () => { + worker.postMessage({ type: 'start' }); + + await new Promise((resolve) => { + worker.addEventListener('message', (event) => { + if (event.data.type === 'started') { + resolve(); + } + }, { once: true }); + }); + + const stopPromise = new Promise((resolve) => { + worker.addEventListener('message', (event) => { + if (event.data.type === 'stopped') { + resolve(event); + } + }, { once: true }); + }); + + worker.postMessage({ type: 'stop' }); + + const event = await stopPromise; + expect(event.data).toEqual({ type: 'stopped' }); + }); + + it('should handle unknown message types gracefully', async () => { + const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + worker.postMessage({ type: 'unknown' }); + + // Give the worker time to process the message + await new Promise(resolve => setTimeout(resolve, 100)); + + // Note: We can't directly verify console.warn was called in the worker context + // but we can verify the worker doesn't crash or send unexpected messages + + const messagePromise = new Promise((resolve) => { + worker.addEventListener('message', resolve, { once: true }); + }); + + worker.postMessage({ type: 'start' }); + const event = await messagePromise; + expect(event.data).toEqual({ type: 'started' }); + + consoleSpy.mockRestore(); + }); + }); + + describe('Heartbeat Interval Management', () => { + it('should handle restart without issues', async () => { + worker.postMessage({ type: 'start' }); + + await new Promise((resolve) => { + worker.addEventListener('message', (event) => { + if (event.data.type === 'started') { + resolve(); + } + }, { once: true }); + }); + + // Start again (should clear previous interval) + const secondStartPromise = new Promise((resolve) => { + worker.addEventListener('message', (event) => { + if (event.data.type === 'started') { + resolve(event); + } + }, { once: true }); + }); + + worker.postMessage({ type: 'start' }); + const event = await secondStartPromise; + expect(event.data).toEqual({ type: 'started' }); + }); + + it('should stop cleanly even when no heartbeat is running', async () => { + const stopPromise = new Promise((resolve) => { + worker.addEventListener('message', resolve, { once: true }); + }); + + // Stop without starting first + worker.postMessage({ type: 'stop' }); + + const event = await stopPromise; + expect(event.data).toEqual({ type: 'stopped' }); + }); + }); + + describe('Message Flow', () => { + it('should handle complete start-heartbeat-stop cycle', async () => { + const messages: any[] = []; + + worker.addEventListener('message', (event) => { + messages.push(event.data); + }); + + worker.postMessage({ type: 'start' }); + + await new Promise((resolve) => { + const checkMessages = () => { + if (messages.some(msg => msg.type === 'started')) { + resolve(); + } else { + setTimeout(checkMessages, 10); + } + }; + checkMessages(); + }); + + await new Promise((resolve) => { + const checkMessages = () => { + if (messages.some(msg => msg.type === 'heartbeat')) { + resolve(); + } else { + setTimeout(checkMessages, 100); + } + }; + checkMessages(); + }); + + worker.postMessage({ type: 'stop' }); + + await new Promise((resolve) => { + const checkMessages = () => { + if (messages.some(msg => msg.type === 'stopped')) { + resolve(); + } else { + setTimeout(checkMessages, 10); + } + }; + checkMessages(); + }); + + // Verify we got all expected message types + expect(messages.some(msg => msg.type === 'started')).toBe(true); + expect(messages.some(msg => msg.type === 'heartbeat')).toBe(true); + expect(messages.some(msg => msg.type === 'stopped')).toBe(true); + }, 15000); // 15 second timeout + + it('should use correct heartbeat interval timing', async () => { + const heartbeatTimes: number[] = []; + + worker.addEventListener('message', (event) => { + if (event.data.type === 'heartbeat') { + heartbeatTimes.push(Date.now()); + } + }); + + worker.postMessage({ type: 'start' }); + + await new Promise((resolve) => { + const checkHeartbeats = () => { + if (heartbeatTimes.length >= 2) { + resolve(); + } else { + setTimeout(checkHeartbeats, 100); + } + }; + checkHeartbeats(); + }); + + // Verify the interval is approximately 10 seconds (allow some tolerance) + const interval = heartbeatTimes[1] - heartbeatTimes[0]; + expect(interval).toBeGreaterThan(9500); // 9.5 seconds minimum + expect(interval).toBeLessThan(10500); // 10.5 seconds maximum + }, 25000); // 25 second timeout + }); +}); \ No newline at end of file diff --git a/packages/wallet-sdk/src/sign/walletlink/relay/connection/WalletLinkConnection.test.ts b/packages/wallet-sdk/src/sign/walletlink/relay/connection/WalletLinkConnection.test.ts index d2e06f5beb..a4c9626814 100644 --- a/packages/wallet-sdk/src/sign/walletlink/relay/connection/WalletLinkConnection.test.ts +++ b/packages/wallet-sdk/src/sign/walletlink/relay/connection/WalletLinkConnection.test.ts @@ -1,5 +1,6 @@ import { vi } from 'vitest'; +import { ScopedLocalStorage } from ':core/storage/ScopedLocalStorage.js'; import { APP_VERSION_KEY, WALLET_USER_NAME_KEY } from '../constants.js'; import { WalletLinkSession } from '../type/WalletLinkSession.js'; import { WalletLinkCipher } from './WalletLinkCipher.js'; @@ -7,7 +8,6 @@ import { WalletLinkConnection, WalletLinkConnectionUpdateListener, } from './WalletLinkConnection.js'; -import { ScopedLocalStorage } from ':core/storage/ScopedLocalStorage.js'; const decryptMock = vi.fn().mockImplementation((text) => Promise.resolve(`decrypted ${text}`)); @@ -18,10 +18,18 @@ describe('WalletLinkConnection', () => { let connection: WalletLinkConnection; let listener: WalletLinkConnectionUpdateListener; + let mockWorker: any; beforeEach(() => { vi.clearAllMocks(); + mockWorker = { + postMessage: vi.fn(), + terminate: vi.fn(), + addEventListener: vi.fn(), + }; + global.Worker = vi.fn().mockImplementation(() => mockWorker); + connection = new WalletLinkConnection({ session, linkAPIUrl: 'http://link-api-url', @@ -142,4 +150,77 @@ describe('WalletLinkConnection', () => { }); }); }); + + describe('Heartbeat Worker Management', () => { + it('should create a heartbeat worker when startHeartbeat is called', () => { + (connection as any).startHeartbeat(); + + expect(global.Worker).toHaveBeenCalledWith(expect.any(URL), { type: 'module' }); + + expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'start' }); + }); + + it('should stop heartbeat worker when stopHeartbeat is called', () => { + (connection as any).startHeartbeat(); + + vi.clearAllMocks(); + + (connection as any).stopHeartbeat(); + + expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'stop' }); + expect(mockWorker.terminate).toHaveBeenCalled(); + }); + + it('should terminate existing worker before creating new one', () => { + (connection as any).startHeartbeat(); + const firstWorker = mockWorker; + + const secondWorker = { + postMessage: vi.fn(), + terminate: vi.fn(), + addEventListener: vi.fn(), + }; + global.Worker = vi.fn().mockImplementation(() => secondWorker); + + (connection as any).startHeartbeat(); + + // First worker should be terminated + expect(firstWorker.terminate).toHaveBeenCalled(); + + // New worker should be created and started + expect(secondWorker.postMessage).toHaveBeenCalledWith({ type: 'start' }); + }); + + it('should handle heartbeat messages from worker', () => { + const heartbeatSpy = vi.spyOn(connection as any, 'heartbeat').mockImplementation(() => {}); + + (connection as any).startHeartbeat(); + + const messageListener = mockWorker.addEventListener.mock.calls.find( + (call: any[]) => call[0] === 'message' + )?.[1]; + + expect(messageListener).toBeDefined(); + + messageListener({ data: { type: 'heartbeat' } }); + + expect(heartbeatSpy).toHaveBeenCalled(); + }); + + it('should handle stop when no worker exists', () => { + expect(() => { + (connection as any).stopHeartbeat(); + }).not.toThrow(); + + expect(mockWorker.postMessage).not.toHaveBeenCalled(); + expect(mockWorker.terminate).not.toHaveBeenCalled(); + }); + + it('should setup worker listeners correctly', () => { + (connection as any).startHeartbeat(); + + expect(mockWorker.addEventListener).toHaveBeenCalledWith('message', expect.any(Function)); + expect(mockWorker.addEventListener).toHaveBeenCalledWith('error', expect.any(Function)); + }); + }); }); diff --git a/packages/wallet-sdk/src/sign/walletlink/relay/connection/WalletLinkConnection.ts b/packages/wallet-sdk/src/sign/walletlink/relay/connection/WalletLinkConnection.ts index c3c1b6f193..fa3491b65d 100644 --- a/packages/wallet-sdk/src/sign/walletlink/relay/connection/WalletLinkConnection.ts +++ b/packages/wallet-sdk/src/sign/walletlink/relay/connection/WalletLinkConnection.ts @@ -1,5 +1,6 @@ // Copyright (c) 2018-2023 Coinbase, Inc. +import { IntNumber } from ':core/type/index.js'; import { APP_VERSION_KEY, WALLET_USER_NAME_KEY } from '../constants.js'; import { ClientMessage } from '../type/ClientMessage.js'; import { ServerMessage, ServerMessageType } from '../type/ServerMessage.js'; @@ -9,7 +10,6 @@ import { Web3Response } from '../type/Web3Response.js'; import { WalletLinkCipher } from './WalletLinkCipher.js'; import { WalletLinkHTTP } from './WalletLinkHTTP.js'; import { ConnectionState, WalletLinkWebSocket } from './WalletLinkWebSocket.js'; -import { IntNumber } from ':core/type/index.js'; const HEARTBEAT_INTERVAL = 10000; const REQUEST_TIMEOUT = 60000; @@ -43,6 +43,7 @@ export class WalletLinkConnection { private cipher: WalletLinkCipher; private ws: WalletLinkWebSocket; private http: WalletLinkHTTP; + private heartbeatWorker?: Worker; /** * Constructor @@ -62,6 +63,9 @@ export class WalletLinkConnection { let connected = false; switch (state) { case ConnectionState.DISCONNECTED: + // Stop heartbeat when disconnected + this.stopHeartbeat(); + // if DISCONNECTED and not destroyed if (!this.destroyed) { const connect = async () => { @@ -85,13 +89,9 @@ export class WalletLinkConnection { connected = await this.handleConnected(); // send heartbeat every n seconds while connected - // if CONNECTED, start the heartbeat timer - // first timer event updates lastHeartbeat timestamp - // subsequent calls send heartbeat message + // if CONNECTED, start the heartbeat timer using WebWorker this.updateLastHeartbeat(); - setInterval(() => { - this.heartbeat(); - }, HEARTBEAT_INTERVAL); + this.startHeartbeat(); // check for unseen events if (this.shouldFetchUnseenEventsOnConnect) { @@ -174,6 +174,7 @@ export class WalletLinkConnection { ); this.destroyed = true; + this.stopHeartbeat(); this.ws.disconnect(); this.listener = undefined; } @@ -307,6 +308,53 @@ export class WalletLinkConnection { this.lastHeartbeatResponse = Date.now(); } + private startHeartbeat(): void { + if (this.heartbeatWorker) { + this.heartbeatWorker.terminate(); + } + + try { + // We put the heartbeat interval on a worker to avoid dropping the websocket connection when the webpage is backgrounded. + const workerUrl = new URL('./HeartbeatWorker.js', import.meta.url); + this.heartbeatWorker = new Worker(workerUrl, { type: 'module' }); + this.setupWorkerListeners(); + + this.heartbeatWorker.postMessage({ type: 'start' }); + } catch (error) { + console.warn('Failed to create external heartbeat worker', error); + } + } + + private setupWorkerListeners(): void { + if (!this.heartbeatWorker) return; + + this.heartbeatWorker.addEventListener('message', (event: MessageEvent<{ type: 'heartbeat' | 'started' | 'stopped' }>) => { + const { type } = event.data; + + switch (type) { + case 'heartbeat': + this.heartbeat(); + break; + case 'started': + case 'stopped': + // noop + break; + } + }); + + this.heartbeatWorker.addEventListener('error', (error) => { + console.error('Heartbeat worker error:', error); + }); + } + + private stopHeartbeat(): void { + if (this.heartbeatWorker) { + this.heartbeatWorker.postMessage({ type: 'stop' }); + this.heartbeatWorker.terminate(); + this.heartbeatWorker = undefined; + } + } + private heartbeat(): void { if (Date.now() - this.lastHeartbeatResponse > HEARTBEAT_INTERVAL * 2) { this.ws.disconnect(); diff --git a/packages/wallet-sdk/src/sign/walletlink/relay/ui/WLMobileRelayUI.ts b/packages/wallet-sdk/src/sign/walletlink/relay/ui/WLMobileRelayUI.ts index 4846a4968c..82ede3cd98 100644 --- a/packages/wallet-sdk/src/sign/walletlink/relay/ui/WLMobileRelayUI.ts +++ b/packages/wallet-sdk/src/sign/walletlink/relay/ui/WLMobileRelayUI.ts @@ -1,7 +1,7 @@ +import { CBW_MOBILE_DEEPLINK_URL } from ':core/constants.js'; +import { RelayUI } from './RelayUI.js'; import { RedirectDialog } from './components/RedirectDialog/RedirectDialog.js'; import { getLocation } from './components/util.js'; -import { RelayUI } from './RelayUI.js'; -import { CBW_MOBILE_DEEPLINK_URL } from ':core/constants.js'; export class WLMobileRelayUI implements RelayUI { private readonly redirectDialog: RedirectDialog; @@ -35,16 +35,17 @@ export class WLMobileRelayUI implements RelayUI { } openCoinbaseWalletDeeplink(walletLinkUrl?: string): void { - this.redirectDialog.present({ - title: 'Redirecting to Coinbase Wallet...', - buttonText: 'Open', - onButtonClick: () => { - this.redirectToCoinbaseWallet(walletLinkUrl); - }, - }); + // redirect to coinbase wallet immediately to avoid Safari/Chrome popup(deeplink) blocking + this.redirectToCoinbaseWallet(walletLinkUrl); setTimeout(() => { - this.redirectToCoinbaseWallet(walletLinkUrl); + this.redirectDialog.present({ + title: 'Redirecting to Coinbase Wallet...', + buttonText: 'Open', + onButtonClick: () => { + this.redirectToCoinbaseWallet(walletLinkUrl); + }, + }); }, 99); } diff --git a/packages/wallet-sdk/tsconfig.build.json b/packages/wallet-sdk/tsconfig.build.json index 0fe1f07eb0..c72951bb34 100644 --- a/packages/wallet-sdk/tsconfig.build.json +++ b/packages/wallet-sdk/tsconfig.build.json @@ -1,11 +1,11 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "allowJs": false, + "allowJs": true, "declaration": true, "declarationMap": true, "sourceMap": true, }, "include": ["src"], - "exclude": ["dist", "build", "**/*.test.*", "mocks"] + "exclude": ["dist", "build", "**/*.test.*", "mocks", "**/HeartbeatWorker.js"] } diff --git a/yarn.lock b/yarn.lock index 4e97924e80..a851d2bdfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,7 +19,14 @@ __metadata: languageName: node linkType: hard -"@ampproject/remapping@npm:^2.2.0": +"@adraffy/ens-normalize@npm:^1.10.1": + version: 1.11.0 + resolution: "@adraffy/ens-normalize@npm:1.11.0" + checksum: b2911269e3e0ec6396a2e5433a99e0e1f9726befc6c167994448cd0e53dbdd0be22b4835b4f619558b568ed9aa7312426b8fa6557a13999463489daa88169ee5 + languageName: node + linkType: hard + +"@ampproject/remapping@npm:^2.2.0, @ampproject/remapping@npm:^2.3.0": version: 2.3.0 resolution: "@ampproject/remapping@npm:2.3.0" dependencies: @@ -156,6 +163,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 0a8464adc4b39b138aedcb443b09f4005d86207d7126e5e079177e05c3116107d856ec08282b365e9a79a9872f40f4092a6127f8d74c8a01c1ef789dacfc25d6 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-validator-identifier@npm:7.24.7" @@ -163,6 +177,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-identifier@npm:7.27.1" + checksum: 3c7e8391e59d6c85baeefe9afb86432f2ab821c6232b00ea9082a51d3e7e95a2f3fb083d74dc1f49ac82cf238e1d2295dafcb001f7b0fab479f3f56af5eaaa47 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.24.8": version: 7.24.8 resolution: "@babel/helper-validator-option@npm:7.24.8" @@ -203,6 +224,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.4": + version: 7.27.5 + resolution: "@babel/parser@npm:7.27.5" + dependencies: + "@babel/types": ^7.27.3 + bin: + parser: ./bin/babel-parser.js + checksum: 16f00a12895522c1682f1f047332010e129ba517add3a2db347a658e02f60434fc38f9105a9d6ec3fd6bfb5d1b0b70d88585c1f10e06e2b58fba29004a42d648 + languageName: node + linkType: hard + "@babel/plugin-syntax-class-properties@npm:^7.12.13": version: 7.12.13 resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" @@ -271,6 +303,23 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.4, @babel/types@npm:^7.27.3": + version: 7.27.3 + resolution: "@babel/types@npm:7.27.3" + dependencies: + "@babel/helper-string-parser": ^7.27.1 + "@babel/helper-validator-identifier": ^7.27.1 + checksum: f0d43c0231f3ebc118480e149292dcd92ea128e2650285ced99ff2e5610db2171305f59aa07406ba0cb36af8e4331a53a69576d6b0c3f3176144dd3ad514b9ae + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^0.2.3": + version: 0.2.3 + resolution: "@bcoe/v8-coverage@npm:0.2.3" + checksum: 850f9305536d0f2bd13e9e0881cb5f02e4f93fad1189f7b2d4bebf694e3206924eadee1068130d43c11b750efcc9405f88a8e42ef098b6d75239c0f047de1a27 + languageName: node + linkType: hard + "@chakra-ui/accordion@npm:2.3.1": version: 2.3.1 resolution: "@chakra-ui/accordion@npm:2.3.1" @@ -1540,6 +1589,8 @@ __metadata: "@types/node": ^14.18.54 "@typescript-eslint/eslint-plugin": ^6.2.0 "@typescript-eslint/parser": ^6.2.0 + "@vitest/coverage-v8": 2.1.2 + "@vitest/web-worker": 3.2.1 clsx: ^1.2.1 eslint: ^8.45.0 eslint-config-preact: ^1.3.0 @@ -1548,6 +1599,7 @@ __metadata: eslint-plugin-simple-import-sort: ^10.0.0 eslint-plugin-unused-imports: ^3.0.0 eventemitter3: ^5.0.1 + fake-indexeddb: ^6.0.0 glob: ^11.0.0 jest-websocket-mock: ^2.4.0 jsdom: ^25.0.1 @@ -1559,6 +1611,7 @@ __metadata: tsc-alias: ^1.8.8 tslib: ^2.6.0 typescript: ^5.1.6 + viem: ^2.27.2 vitest: ^2.1.2 languageName: unknown linkType: soft @@ -2009,6 +2062,13 @@ __metadata: languageName: node linkType: hard +"@istanbuljs/schema@npm:^0.1.2": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 5282759d961d61350f33d9118d16bcaed914ebf8061a52f4fa474b2cb08720c9c81d165e13b82f2e5a8a212cc5af482f0c6fc1ac27b9e067e5394c9a6ed186c9 + languageName: node + linkType: hard + "@jest/schemas@npm:^29.6.3": version: 29.6.3 resolution: "@jest/schemas@npm:29.6.3" @@ -2060,7 +2120,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": +"@jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: @@ -2292,6 +2352,13 @@ __metadata: languageName: node linkType: hard +"@noble/ciphers@npm:^1.3.0": + version: 1.3.0 + resolution: "@noble/ciphers@npm:1.3.0" + checksum: 19722c35475df9bc78db60d261d0b5ef8a6d722561efc2135453f943eaa421b492195dc666e3e4df2b755bca3739e04f04b9c660198559f5dd05d3cfbf1b9e92 + languageName: node + linkType: hard + "@noble/curves@npm:1.4.0": version: 1.4.0 resolution: "@noble/curves@npm:1.4.0" @@ -2310,6 +2377,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.9.1": + version: 1.9.1 + resolution: "@noble/curves@npm:1.9.1" + dependencies: + "@noble/hashes": 1.8.0 + checksum: 4f3483a1001538d2f55516cdcb19319d1eaef79550633f670e7d570b989cdbc0129952868b72bb67643329746b8ffefe8e4cd791c8cc35574e05a37f873eef42 + languageName: node + linkType: hard + "@noble/curves@npm:^1.4.0, @noble/curves@npm:^1.4.2": version: 1.6.0 resolution: "@noble/curves@npm:1.6.0" @@ -2319,6 +2395,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:^1.6.0, @noble/curves@npm:~1.9.0": + version: 1.9.2 + resolution: "@noble/curves@npm:1.9.2" + dependencies: + "@noble/hashes": 1.8.0 + checksum: bac582aefe951032cb04ed7627f139c3351ddfefd2625a25fe7f7a8043e7d781be4fad320d4ae75e31fa5d7e05ba643f16139877375130fd3cff86d81512e0f2 + languageName: node + linkType: hard + "@noble/hashes@npm:1.4.0, @noble/hashes@npm:~1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" @@ -2333,6 +2418,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.5.0, @noble/hashes@npm:~1.8.0": + version: 1.8.0 + resolution: "@noble/hashes@npm:1.8.0" + checksum: c94e98b941963676feaba62475b1ccfa8341e3f572adbb3b684ee38b658df44100187fa0ef4220da580b13f8d27e87d5492623c8a02ecc61f23fb9960c7918f5 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -2558,6 +2650,13 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:~1.2.5": + version: 1.2.6 + resolution: "@scure/base@npm:1.2.6" + checksum: 1058cb26d5e4c1c46c9cc0ae0b67cc66d306733baf35d6ebdd8ddaba242b80c3807b726e3b48cb0411bb95ec10d37764969063ea62188f86ae9315df8ea6b325 + languageName: node + linkType: hard + "@scure/bip32@npm:1.4.0": version: 1.4.0 resolution: "@scure/bip32@npm:1.4.0" @@ -2569,6 +2668,17 @@ __metadata: languageName: node linkType: hard +"@scure/bip32@npm:1.7.0, @scure/bip32@npm:^1.5.0": + version: 1.7.0 + resolution: "@scure/bip32@npm:1.7.0" + dependencies: + "@noble/curves": ~1.9.0 + "@noble/hashes": ~1.8.0 + "@scure/base": ~1.2.5 + checksum: c83adca5a74ec5c4ded8ba93900d0065e4767c4759cf24c2674923aef01d45ba56f171574e3519f2341be99f53a333f01b674eb6cfeb6fa8379607c6d1bc90b5 + languageName: node + linkType: hard + "@scure/bip39@npm:1.3.0": version: 1.3.0 resolution: "@scure/bip39@npm:1.3.0" @@ -2579,6 +2689,16 @@ __metadata: languageName: node linkType: hard +"@scure/bip39@npm:1.6.0, @scure/bip39@npm:^1.4.0": + version: 1.6.0 + resolution: "@scure/bip39@npm:1.6.0" + dependencies: + "@noble/hashes": ~1.8.0 + "@scure/base": ~1.2.5 + checksum: 96d46420780473d6c6c9700254a0eceec60302f61d7f9d7f29024e90c7acff3e8e40a5ee52dfaf104db539a10462e531996aaf9e69f082b8540b0a25870545fc + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -3110,6 +3230,32 @@ __metadata: languageName: node linkType: hard +"@vitest/coverage-v8@npm:2.1.2": + version: 2.1.2 + resolution: "@vitest/coverage-v8@npm:2.1.2" + dependencies: + "@ampproject/remapping": ^2.3.0 + "@bcoe/v8-coverage": ^0.2.3 + debug: ^4.3.6 + istanbul-lib-coverage: ^3.2.2 + istanbul-lib-report: ^3.0.1 + istanbul-lib-source-maps: ^5.0.6 + istanbul-reports: ^3.1.7 + magic-string: ^0.30.11 + magicast: ^0.3.4 + std-env: ^3.7.0 + test-exclude: ^7.0.1 + tinyrainbow: ^1.2.0 + peerDependencies: + "@vitest/browser": 2.1.2 + vitest: 2.1.2 + peerDependenciesMeta: + "@vitest/browser": + optional: true + checksum: 510efabda04f765f427ff6d2df528cc3cd1534ec8c6f5cfc2c403e23e9969f148979d8363109aef1ba0dd0724d19e0e2140b893caa86218345872852cc51d591 + languageName: node + linkType: hard + "@vitest/expect@npm:2.1.2": version: 2.1.2 resolution: "@vitest/expect@npm:2.1.2" @@ -3192,6 +3338,17 @@ __metadata: languageName: node linkType: hard +"@vitest/web-worker@npm:3.2.1": + version: 3.2.1 + resolution: "@vitest/web-worker@npm:3.2.1" + dependencies: + debug: "npm:^4.4.1" + peerDependencies: + vitest: 3.2.1 + checksum: c10731380041978a6a49aeb2866856c4067d178eb664b94ea96c9334f2d8ae54195d9fd8be0bc8f03bb3a3b9806b290cff34a775f731ecd23826cfc036217bff + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/ast@npm:1.12.1" @@ -3414,6 +3571,21 @@ __metadata: languageName: node linkType: hard +"abitype@npm:1.0.8, abitype@npm:^1.0.6": + version: 1.0.8 + resolution: "abitype@npm:1.0.8" + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + checksum: 104bc2f6820ced8d2cb61521916f7f22c0981a846216f5b6144f69461265f7da137a4ae108bf4b84cd8743f2dd1e9fdadffc0f95371528e15a59e0a369e08438 + languageName: node + linkType: hard + "acorn-import-attributes@npm:^1.9.5": version: 1.9.5 resolution: "acorn-import-attributes@npm:1.9.5" @@ -4440,6 +4612,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.4.1": + version: 4.4.1 + resolution: "debug@npm:4.4.1" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: a43826a01cda685ee4cec00fb2d3322eaa90ccadbef60d9287debc2a886be3e835d9199c80070ede75a409ee57828c4c6cd80e4b154f2843f0dc95a570dc0729 + languageName: node + linkType: hard + "decimal.js@npm:^10.4.3": version: 10.4.3 resolution: "decimal.js@npm:10.4.3" @@ -5381,7 +5565,7 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^5.0.1": +"eventemitter3@npm:5.0.1, eventemitter3@npm:^5.0.1": version: 5.0.1 resolution: "eventemitter3@npm:5.0.1" checksum: 543d6c858ab699303c3c32e0f0f47fc64d360bf73c3daf0ac0b5079710e340d6fe9f15487f94e66c629f5f82cd1a8678d692f3dbb6f6fcd1190e1b97fcad36f8 @@ -5426,6 +5610,13 @@ __metadata: languageName: node linkType: hard +"fake-indexeddb@npm:^6.0.0": + version: 6.0.1 + resolution: "fake-indexeddb@npm:6.0.1" + checksum: c4b8a0576cf3165238494b67641539d4ff36194e038b36e6992449eb882923dfaadba78a62cfc7d5ae9a5c0ac2fa1e70af5cb6c2228dc764ac79b65f0e68e942 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -5835,7 +6026,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2, glob@npm:^10.3.10": +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.1": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -6024,6 +6215,13 @@ __metadata: languageName: node linkType: hard +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: d2df2da3ad40ca9ee3a39c5cc6475ef67c8f83c234475f24d8e9ce0dc80a2c82df8e1d6fa78ddd1e9022a586ea1bd247a615e80a5cd9273d90111ddda7d9e974 + languageName: node + linkType: hard + "http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" @@ -6476,6 +6674,54 @@ __metadata: languageName: node linkType: hard +"isows@npm:1.0.7": + version: 1.0.7 + resolution: "isows@npm:1.0.7" + peerDependencies: + ws: "*" + checksum: 044b949b369872882af07b60b613b5801ae01b01a23b5b72b78af80c8103bbeed38352c3e8ceff13a7834bc91fd2eb41cf91ec01d59a041d8705680e6b0ec546 + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.2": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 2367407a8d13982d8f7a859a35e7f8dd5d8f75aae4bb5484ede3a9ea1b426dc245aff28b976a2af48ee759fdd9be374ce2bd2669b644f31e76c5f46a2e29a831 + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: ^3.0.0 + make-dir: ^4.0.0 + supports-color: ^7.1.0 + checksum: fd17a1b879e7faf9bb1dc8f80b2a16e9f5b7b8498fe6ed580a618c34df0bfe53d2abd35bf8a0a00e628fb7405462576427c7df20bbe4148d19c14b431c974b21 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^5.0.6": + version: 5.0.6 + resolution: "istanbul-lib-source-maps@npm:5.0.6" + dependencies: + "@jridgewell/trace-mapping": ^0.3.23 + debug: ^4.1.1 + istanbul-lib-coverage: ^3.0.0 + checksum: 8dd6f2c1e2ecaacabeef8dc9ab52c4ed0a6036310002cf7f46ea6f3a5fb041da8076f5350e6a6be4c60cd4f231c51c73e042044afaf44820d857d92ecfb8ab6c + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.7": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" + dependencies: + html-escaper: ^2.0.0 + istanbul-lib-report: ^3.0.0 + checksum: 2072db6e07bfbb4d0eb30e2700250636182398c1af811aea5032acb219d2080f7586923c09fa194029efd6b92361afb3dcbe1ebcc3ee6651d13340f7c6c4ed95 + languageName: node + linkType: hard + "iterator.prototype@npm:^1.1.2": version: 1.1.2 resolution: "iterator.prototype@npm:1.1.2" @@ -6913,6 +7159,26 @@ __metadata: languageName: node linkType: hard +"magicast@npm:^0.3.4": + version: 0.3.5 + resolution: "magicast@npm:0.3.5" + dependencies: + "@babel/parser": ^7.25.4 + "@babel/types": ^7.25.4 + source-map-js: ^1.2.0 + checksum: 668f07ade907a44bccfc9a9321588473f6d5fa25329aa26b9ad9a3bf87cc2e6f9c482cbdd3e33c0b9ab9b79c065630c599cc055a12f881c8c924ee0d7282cdce + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: ^7.5.3 + checksum: bf0731a2dd3aab4db6f3de1585cea0b746bb73eb5a02e3d8d72757e376e64e6ada190b1eddcde5b2f24a81b688a9897efd5018737d05e02e2a671dda9cff8a8a + languageName: node + linkType: hard + "make-fetch-happen@npm:^13.0.0": version: 13.0.1 resolution: "make-fetch-happen@npm:13.0.1" @@ -7470,6 +7736,27 @@ __metadata: languageName: node linkType: hard +"ox@npm:0.7.1": + version: 0.7.1 + resolution: "ox@npm:0.7.1" + dependencies: + "@adraffy/ens-normalize": ^1.10.1 + "@noble/ciphers": ^1.3.0 + "@noble/curves": ^1.6.0 + "@noble/hashes": ^1.5.0 + "@scure/bip32": ^1.5.0 + "@scure/bip39": ^1.4.0 + abitype: ^1.0.6 + eventemitter3: 5.0.1 + peerDependencies: + typescript: ">=5.4.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 632d45f6d58ed3dd0e0f256f5227a22d584914e81f09556d39058e0efbf0ae6e4ecfa74c7cdc04c4f670e4db76ac9a180f96c06b8025b36a5ac8094e0c86c5dc + languageName: node + linkType: hard + "p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" @@ -8627,7 +8914,7 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.2, source-map-js@npm:^1.2.1": +"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.2, source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 4eb0cd997cdf228bc253bcaff9340afeb706176e64868ecd20efbe6efea931465f43955612346d6b7318789e5265bdc419bc7669c1cebe3db0eb255f57efa76b @@ -9048,6 +9335,17 @@ __metadata: languageName: node linkType: hard +"test-exclude@npm:^7.0.1": + version: 7.0.1 + resolution: "test-exclude@npm:7.0.1" + dependencies: + "@istanbuljs/schema": ^0.1.2 + glob: ^10.4.1 + minimatch: ^9.0.4 + checksum: e5a49a054bf2da74467dd8149b202166e36275c0dc2c9585f7d34de99c6d055d2287ac8d2a8e4c27c59b893acbc671af3fa869e8069a58ad117250e9c01c726b + languageName: node + linkType: hard + "text-decoder@npm:^1.1.0": version: 1.2.0 resolution: "text-decoder@npm:1.2.0" @@ -9549,6 +9847,27 @@ __metadata: languageName: node linkType: hard +"viem@npm:^2.27.2": + version: 2.30.6 + resolution: "viem@npm:2.30.6" + dependencies: + "@noble/curves": 1.9.1 + "@noble/hashes": 1.8.0 + "@scure/bip32": 1.7.0 + "@scure/bip39": 1.6.0 + abitype: 1.0.8 + isows: 1.0.7 + ox: 0.7.1 + ws: 8.18.2 + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + checksum: 8732bd05bd730cfe4bf34c0e740967c27f073482fadf42eccc539ee65a50a7ac9a3b1c5efaf590cdcb868eb8d09dc96def04dc776825e845eb18ba83ac2930d5 + languageName: node + linkType: hard + "vite-node@npm:2.1.2": version: 2.1.2 resolution: "vite-node@npm:2.1.2" @@ -9935,6 +10254,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:8.18.2": + version: 8.18.2 + resolution: "ws@npm:8.18.2" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: e38beae19ba4d68577ec24eb34fbfab376333fedd10f99b07511a8e842e22dbc102de39adac333a18e4c58868d0703cd5f239b04b345e22402d0ed8c34ea0aa0 + languageName: node + linkType: hard + "ws@npm:^7.5.10": version: 7.5.10 resolution: "ws@npm:7.5.10"