diff --git a/.github/workflows/deploy-react-sample-apps.yml b/.github/workflows/deploy-react-sample-apps.yml index eed044290e..a12cfc21f2 100644 --- a/.github/workflows/deploy-react-sample-apps.yml +++ b/.github/workflows/deploy-react-sample-apps.yml @@ -72,7 +72,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x cache: 'yarn' - name: Install Dependencies diff --git a/.github/workflows/egress-composite-e2e.yml b/.github/workflows/egress-composite-e2e.yml index 5f3dd7a0d2..4c5ca344de 100644 --- a/.github/workflows/egress-composite-e2e.yml +++ b/.github/workflows/egress-composite-e2e.yml @@ -28,7 +28,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x cache: 'yarn' cache-dependency-path: 'yarn.lock' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d5416dc168..caf5e6e505 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x cache: 'yarn' - name: ESLint Cache diff --git a/.github/workflows/version-and-release.yml b/.github/workflows/version-and-release.yml index 451da6e716..c9fe6d5f8c 100644 --- a/.github/workflows/version-and-release.yml +++ b/.github/workflows/version-and-release.yml @@ -29,7 +29,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x cache: 'yarn' - name: ESLint Cache diff --git a/.nvmrc b/.nvmrc index 9a2a0e219c..53d1c14db3 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20 +v22 diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index ab669a6804..1d1eb419c9 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -2,6 +2,13 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.11.7](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.6...@stream-io/video-client-1.11.7) (2024-11-26) + + +### Bug Fixes + +* remove unused code from the coordinator websocket impl ([#1563](https://github.com/GetStream/stream-video-js/issues/1563)) ([921b820](https://github.com/GetStream/stream-video-js/commit/921b820133885dac299dab343cee3fc4b08705ce)) + ## [1.11.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.5...@stream-io/video-client-1.11.6) (2024-11-22) diff --git a/packages/client/package.json b/packages/client/package.json index 49833cf025..74d19ed7d0 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-client", - "version": "1.11.6", + "version": "1.11.7", "packageManager": "yarn@3.2.4", "main": "dist/index.cjs.js", "module": "dist/index.es.js", @@ -31,15 +31,11 @@ "@protobuf-ts/runtime": "^2.9.4", "@protobuf-ts/runtime-rpc": "^2.9.4", "@protobuf-ts/twirp-transport": "^2.9.4", - "@types/ws": "^8.5.7", - "axios": "^1.6.0", - "base64-js": "^1.5.1", - "isomorphic-ws": "^5.0.0", + "axios": "^1.7.7", "rxjs": "~7.8.1", "sdp-transform": "^2.14.1", "ua-parser-js": "^1.0.36", - "webrtc-adapter": "^8.2.3", - "ws": "^8.14.2" + "webrtc-adapter": "^8.2.3" }, "devDependencies": { "@openapitools/openapi-generator-cli": "^2.13.4", @@ -49,7 +45,7 @@ "@stream-io/node-sdk": "^0.4.3", "@types/sdp-transform": "^2.4.7", "@types/ua-parser-js": "^0.7.37", - "@vitest/coverage-v8": "^0.34.4", + "@vitest/coverage-v8": "^2.1.4", "dotenv": "^16.3.1", "happy-dom": "^11.0.2", "prettier": "^3.3.2", @@ -57,7 +53,7 @@ "rollup": "^4.22.0", "typescript": "^5.5.2", "vite": "^5.4.6", - "vitest": "^1.0.0", - "vitest-mock-extended": "^1.2.1" + "vitest": "^2.1.4", + "vitest-mock-extended": "^2.0.2" } } diff --git a/packages/client/src/__tests__/Call.test.ts b/packages/client/src/__tests__/Call.test.ts index fcd06fb10a..39ef1c8139 100644 --- a/packages/client/src/__tests__/Call.test.ts +++ b/packages/client/src/__tests__/Call.test.ts @@ -10,7 +10,7 @@ import { import { StreamVideoClient } from '../StreamVideoClient'; import 'dotenv/config'; import { StreamClient } from '@stream-io/node-sdk'; -import { generateUUIDv4 } from '../coordinator/connection/utils'; +import { generateUUIDv4, sleep } from '../coordinator/connection/utils'; import { CallingState } from '../store'; import { Dispatcher } from '../rtc'; import { Call } from '../Call'; @@ -22,7 +22,7 @@ const secret = process.env.STREAM_SECRET!; const serverClient = new StreamClient(apiKey, secret); const userId = 'jane'; const tokenProvider = async () => - serverClient.createToken(userId, undefined, Date.now() / 1000 - 10); + serverClient.generateUserToken({ user_id: userId, role: 'user' }); let client: StreamVideoClient; @@ -34,6 +34,7 @@ beforeEach(async () => { user: { id: 'jane' }, }); + await sleep(50); await client.streamClient.wsPromise; }); diff --git a/packages/client/src/coordinator/connection/base64.ts b/packages/client/src/coordinator/connection/base64.ts deleted file mode 100644 index 070e041b8e..0000000000 --- a/packages/client/src/coordinator/connection/base64.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { fromByteArray } from 'base64-js'; - -function isString(arrayOrString: string | T[]): arrayOrString is string { - return typeof (arrayOrString as string) === 'string'; -} - -type MapGenericCallback = (value: T, index: number, array: T[]) => U; -type MapStringCallback = (value: string, index: number, string: string) => U; - -function isMapStringCallback( - arrayOrString: string | T[], - callback: MapGenericCallback | MapStringCallback, -): callback is MapStringCallback { - return !!callback && isString(arrayOrString); -} - -// source - https://github.com/beatgammit/base64-js/blob/master/test/convert.js#L72 -function map(array: T[], callback: MapGenericCallback): U[]; -function map(string: string, callback: MapStringCallback): U[]; -function map( - arrayOrString: string | T[], - callback: MapGenericCallback | MapStringCallback, -): U[] { - const res = []; - - if (isString(arrayOrString) && isMapStringCallback(arrayOrString, callback)) { - for (let k = 0, len = arrayOrString.length; k < len; k++) { - if (arrayOrString.charAt(k)) { - const kValue = arrayOrString.charAt(k); - const mappedValue = callback(kValue, k, arrayOrString); - res[k] = mappedValue; - } - } - } else if ( - !isString(arrayOrString) && - !isMapStringCallback(arrayOrString, callback) - ) { - for (let k = 0, len = arrayOrString.length; k < len; k++) { - if (k in arrayOrString) { - const kValue = arrayOrString[k]; - const mappedValue = callback(kValue, k, arrayOrString); - res[k] = mappedValue; - } - } - } - - return res; -} - -export const encodeBase64 = (data: string): string => - fromByteArray(new Uint8Array(map(data, (char) => char.charCodeAt(0)))); - -// base-64 decoder throws exception if encoded string is not padded by '=' to make string length -// in multiples of 4. So gonna use our own method for this purpose to keep backwards compatibility -// https://github.com/beatgammit/base64-js/blob/master/index.js#L26 -export const decodeBase64 = (s: string): string => { - const e = {} as { [key: string]: number }, - w = String.fromCharCode, - L = s.length; - let i, - b = 0, - c, - x, - l = 0, - a, - r = ''; - const A = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - for (i = 0; i < 64; i++) { - e[A.charAt(i)] = i; - } - for (x = 0; x < L; x++) { - c = e[s.charAt(x)]; - b = (b << 6) + c; - l += 6; - while (l >= 8) { - ((a = (b >>> (l -= 8)) & 0xff) || x < L - 2) && (r += w(a)); - } - } - return r; -}; diff --git a/packages/client/src/coordinator/connection/client.ts b/packages/client/src/coordinator/connection/client.ts index 106c876560..bca035f747 100644 --- a/packages/client/src/coordinator/connection/client.ts +++ b/packages/client/src/coordinator/connection/client.ts @@ -1,27 +1,22 @@ import axios, { AxiosError, - AxiosHeaders, AxiosInstance, AxiosRequestConfig, AxiosResponse, } from 'axios'; import https from 'https'; import { StableWSConnection } from './connection'; -import { DevToken } from './signing'; import { TokenManager } from './token_manager'; -import { WSConnectionFallback } from './connection_fallback'; -import { isErrorResponse, isWSFailure } from './errors'; import { addConnectionEventListeners, + isErrorResponse, isFunction, - isOnline, KnownCodes, randomId, removeConnectionEventListeners, retryInterval, sleep, } from './utils'; - import { AllClientEvents, AllClientEventTypes, @@ -36,7 +31,6 @@ import { User, UserWithId, } from './types'; -import { InsightMetrics, postInsights } from './insights'; import { getLocationHint } from './location'; import { ConnectedEvent, @@ -71,11 +65,8 @@ export class StreamClient { userID?: string; wsBaseURL?: string; wsConnection: StableWSConnection | null; - wsFallback?: WSConnectionFallback; private wsPromiseSafe: SafePromise | null; consecutiveFailures: number; - insightMetrics: InsightMetrics; - defaultWSTimeoutWithFallback: number; defaultWSTimeout: number; resolveConnectionId?: Function; rejectConnectionId?: Function; @@ -122,7 +113,6 @@ export class StreamClient { this.options = { timeout: 5000, withCredentials: false, // making sure cookies are not sent - warmUp: false, ...inputOptions, }; @@ -137,22 +127,6 @@ export class StreamClient { this.options.baseURL || 'https://video.stream-io-api.com/video', ); - if ( - typeof process !== 'undefined' && - 'env' in process && - process.env.STREAM_LOCAL_TEST_RUN - ) { - this.setBaseURL('http://localhost:3030/video'); - } - - if ( - typeof process !== 'undefined' && - 'env' in process && - process.env.STREAM_LOCAL_TEST_HOST - ) { - this.setBaseURL(`http://${process.env.STREAM_LOCAL_TEST_HOST}/video`); - } - this.axiosInstance = axios.create({ ...this.options, baseURL: this.baseURL, @@ -172,9 +146,7 @@ export class StreamClient { // generated from secret. this.tokenManager = new TokenManager(this.secret); this.consecutiveFailures = 0; - this.insightMetrics = new InsightMetrics(); - this.defaultWSTimeoutWithFallback = 6000; this.defaultWSTimeout = 15000; this.logger = isFunction(inputOptions.logger) @@ -182,10 +154,6 @@ export class StreamClient { : () => null; } - devToken = (userID: string) => { - return DevToken(userID); - }; - getAuthType = () => { return this.anonymous ? 'anonymous' : 'jwt'; }; @@ -212,8 +180,7 @@ export class StreamClient { return hint; }; - _getConnectionID = () => - this.wsConnection?.connectionID || this.wsFallback?.connectionID; + _getConnectionID = () => this.wsConnection?.connectionID; _hasConnectionID = () => Boolean(this._getConnectionID()); @@ -328,11 +295,7 @@ export class StreamClient { * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent */ closeConnection = async (timeout?: number) => { - await Promise.all([ - this.wsConnection?.disconnect(timeout), - this.wsFallback?.disconnect(timeout), - ]); - return Promise.resolve(); + await this.wsConnection?.disconnect(timeout); }; /** @@ -354,10 +317,7 @@ export class StreamClient { return await wsPromise; } - if ( - (this.wsConnection?.isHealthy || this.wsFallback?.isHealthy()) && - this._hasConnectionID() - ) { + if (this.wsConnection?.isHealthy && this._hasConnectionID()) { this.logger( 'info', 'client:openConnection() - openConnection called twice, healthy connection already exists', @@ -366,7 +326,7 @@ export class StreamClient { return; } - this._setupConnectionIdPromise(); + await this._setupConnectionIdPromise(); this.clientID = `${this.userID}--${randomId()}`; const newWsPromise = this.connect(); @@ -404,16 +364,7 @@ export class StreamClient { this.guestUserCreatePromise = this.doAxiosRequest< CreateGuestResponse, CreateGuestRequest - >( - 'post', - '/guest', - { - user: { - ...user, - }, - }, - { publicEndpoint: true }, - ); + >('post', '/guest', { user }, { publicEndpoint: true }); const response = await this.guestUserCreatePromise; this.guestUserCreatePromise.finally( @@ -431,7 +382,7 @@ export class StreamClient { tokenOrProvider: TokenOrProvider, ) => { addConnectionEventListeners(this.updateNetworkConnectionStatus); - this._setupConnectionIdPromise(); + await this._setupConnectionIdPromise(); this.anonymous = true; await this._setToken(user, tokenOrProvider, this.anonymous); @@ -705,89 +656,14 @@ export class StreamClient { 'Call connectUser or connectAnonymousUser before starting the connection', ); } - if (!this.wsBaseURL) { - throw Error('Websocket base url not set'); - } - if (!this.clientID) { - throw Error('clientID is not set'); - } + if (!this.wsBaseURL) throw Error('Websocket base url not set'); + if (!this.clientID) throw Error('clientID is not set'); - if ( - !this.wsConnection && - (this.options.warmUp || this.options.enableInsights) - ) { - this._sayHi(); - } // The StableWSConnection handles all the reconnection logic. - if (this.options.wsConnection && this.node) { - // Intentionally avoiding adding ts generics on wsConnection in options since its only useful for unit test purpose. - (this.options.wsConnection as unknown as StableWSConnection).setClient( - this, - ); - this.wsConnection = this.options - .wsConnection as unknown as StableWSConnection; - } else { - this.wsConnection = new StableWSConnection(this); - } + this.wsConnection = new StableWSConnection(this); - try { - // if fallback is used before, continue using it instead of waiting for WS to fail - if (this.wsFallback) { - return await this.wsFallback.connect(); - } - this.logger('info', 'StreamClient.connect: this.wsConnection.connect()'); - // if WSFallback is enabled, ws connect should timeout faster so fallback can try - return await this.wsConnection.connect( - this.options.enableWSFallback - ? this.defaultWSTimeoutWithFallback - : this.defaultWSTimeout, - ); - } catch (err) { - // run fallback only if it's WS/Network error and not a normal API error - // make sure browser is online before even trying the longpoll - if ( - this.options.enableWSFallback && - // @ts-ignore - isWSFailure(err) && - isOnline(this.logger) - ) { - this.logger( - 'warn', - 'client:connect() - WS failed, fallback to longpoll', - ); - this.dispatchEvent({ type: 'transport.changed', mode: 'longpoll' }); - - this.wsConnection._destroyCurrentWSConnection(); - this.wsConnection.disconnect().then(); // close WS so no retry - this.wsFallback = new WSConnectionFallback(this); - return await this.wsFallback.connect(); - } - - throw err; - } - }; - - /** - * Check the connectivity with server for warmup purpose. - * - * @private - */ - _sayHi = () => { - const client_request_id = randomId(); - const opts = { - headers: AxiosHeaders.from({ - 'x-client-request-id': client_request_id, - }), - }; - this.doAxiosRequest('get', this.baseURL + '/hi', null, opts).catch((e) => { - if (this.options.enableInsights) { - postInsights('http_hi_failed', { - api_key: this.key, - err: e, - client_request_id, - }); - } - }); + this.logger('info', 'StreamClient.connect: this.wsConnection.connect()'); + return await this.wsConnection.connect(this.defaultWSTimeout); }; getUserAgent = () => { @@ -856,19 +732,6 @@ export class StreamClient { return this.tokenManager.getToken(); }; - /** - * encode ws url payload - * @private - * @returns json string - */ - _buildWSPayload = (client_request_id?: string) => { - return JSON.stringify({ - user_id: this.userID, - user_details: this._user, - client_request_id, - }); - }; - updateNetworkConnectionStatus = ( event: { type: 'online' | 'offline' } | Event, ) => { diff --git a/packages/client/src/coordinator/connection/connection.ts b/packages/client/src/coordinator/connection/connection.ts index 12297965d0..4b9a7db672 100644 --- a/packages/client/src/coordinator/connection/connection.ts +++ b/packages/client/src/coordinator/connection/connection.ts @@ -1,13 +1,7 @@ -import WebSocket from 'isomorphic-ws'; import { StreamClient } from './client'; -import { - buildWsFatalInsight, - buildWsSuccessAfterFailureInsight, - postInsights, -} from './insights'; import { addConnectionEventListeners, - convertErrorToJson, + isCloseEvent, KnownCodes, randomId, removeConnectionEventListeners, @@ -15,20 +9,13 @@ import { sleep, } from './utils'; import type { LogLevel, StreamVideoEvent, UR } from './types'; -import type { ConnectedEvent, WSAuthMessage } from '../../gen/coordinator'; +import type { + ConnectedEvent, + ConnectionErrorEvent, + WSAuthMessage, +} from '../../gen/coordinator'; import { makeSafePromise, type SafePromise } from '../../helpers/promise'; -// Type guards to check WebSocket error type -const isCloseEvent = ( - res: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent, -): res is WebSocket.CloseEvent => - (res as WebSocket.CloseEvent).code !== undefined; - -const isErrorEvent = ( - res: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent, -): res is WebSocket.ErrorEvent => - (res as WebSocket.ErrorEvent).error !== undefined; - /** * StableWSConnection - A WS connection that reconnects upon failure. * - the browser will sometimes report that you're online or offline @@ -50,7 +37,6 @@ export class StableWSConnection { // local vars connectionID?: string; private connectionOpenSafe?: SafePromise; - authenticationSent: boolean; consecutiveFailures: number; pingInterval: number; healthCheckTimeoutRef?: NodeJS.Timeout; @@ -84,8 +70,6 @@ export class StableWSConnection { this.totalFailures = 0; /** We only make 1 attempt to reconnect at the same time.. */ this.isConnecting = false; - /** True after the auth payload is sent to the server */ - this.authenticationSent = false; /** To avoid reconnect if client is disconnected */ this.isDisconnected = false; /** Boolean that indicates if the connection promise is resolved */ @@ -219,12 +203,9 @@ export class StableWSConnection { */ _buildUrl = () => { const params = new URLSearchParams(); - // const qs = encodeURIComponent(this.client._buildWSPayload(this.requestID)); - // params.set('json', qs); params.set('api_key', this.client.key); params.set('stream-auth-type', this.client.getAuthType()); params.set('X-Stream-Client', this.client.getUserAgent()); - // params.append('authorization', this.client._getToken()!); return `${this.client.wsBaseURL}/connect?${params.toString()}`; }; @@ -243,22 +224,13 @@ export class StableWSConnection { this.isDisconnected = true; // start by removing all the listeners - if (this.healthCheckTimeoutRef) { - clearInterval(this.healthCheckTimeoutRef); - } - if (this.connectionCheckTimeoutRef) { - clearInterval(this.connectionCheckTimeoutRef); - } + clearInterval(this.healthCheckTimeoutRef); + clearInterval(this.connectionCheckTimeoutRef); removeConnectionEventListeners(this.onlineStatusChanged); this.isHealthy = false; - // remove ws handlers... - if (this.ws && this.ws.removeAllListeners) { - this.ws.removeAllListeners(); - } - let isClosedPromise: Promise; // and finally close... // Assigning to local here because we will remove it from this before the @@ -266,7 +238,7 @@ export class StableWSConnection { const { ws } = this; if (ws && ws.close && ws.readyState === ws.OPEN) { isClosedPromise = new Promise((resolve) => { - const onclose = (event: WebSocket.CloseEvent) => { + const onclose = (event: CloseEvent) => { this._log( `disconnect() - resolving isClosedPromise ${ event ? 'with' : 'without' @@ -308,14 +280,9 @@ export class StableWSConnection { * @return {ConnectAPIResponse} Promise that completes once the first health check message is received */ async _connect() { - if ( - this.isConnecting || - (this.isDisconnected && this.client.options.enableWSFallback) - ) - return; // simply ignore _connect if it's currently trying to connect + if (this.isConnecting) return; // simply ignore _connect if it's currently trying to connect this.isConnecting = true; this.requestID = randomId(); - this.client.insightMetrics.connectionStartTimestamp = new Date().getTime(); let isTokenReady = false; try { this._log(`_connect() - waiting for token`); @@ -342,7 +309,8 @@ export class StableWSConnection { wsURL, requestID: this.requestID, }); - this.ws = new WebSocket(wsURL); + const WS = this.client.options.WebSocketImpl ?? WebSocket; + this.ws = new WS(wsURL); this.ws.onopen = this.onopen.bind(this, this.wsID); this.ws.onclose = this.onclose.bind(this, this.wsID); this.ws.onerror = this.onerror.bind(this, this.wsID); @@ -353,18 +321,6 @@ export class StableWSConnection { if (response) { this.connectionID = response.connection_id; this.client.resolveConnectionId?.(this.connectionID); - if ( - this.client.insightMetrics.wsConsecutiveFailures > 0 && - this.client.options.enableInsights - ) { - postInsights( - 'ws_success_after_failure', - buildWsSuccessAfterFailureInsight( - this as unknown as StableWSConnection, - ), - ); - this.client.insightMetrics.wsConsecutiveFailures = 0; - } return response; } } catch (err) { @@ -372,16 +328,6 @@ export class StableWSConnection { this.isConnecting = false; // @ts-ignore this._log(`_connect() - Error - `, err); - if (this.client.options.enableInsights) { - this.client.insightMetrics.wsConsecutiveFailures++; - this.client.insightMetrics.wsTotalFailures++; - - const insights = buildWsFatalInsight( - this as unknown as StableWSConnection, - convertErrorToJson(err as Error), - ); - postInsights?.('ws_fatal', insights); - } this.client.rejectConnectionId?.(err); throw err; } @@ -422,7 +368,7 @@ export class StableWSConnection { return; } - if (this.isDisconnected && this.client.options.enableWSFallback) { + if (this.isDisconnected) { this._log('_reconnect() - Abort (3) since disconnect() is called'); return; } @@ -518,12 +464,11 @@ export class StableWSConnection { }, }; - this.authenticationSent = true; this.ws?.send(JSON.stringify(authMessage)); this._log('onopen() - onopen callback', { wsID }); }; - onmessage = (wsID: number, event: WebSocket.MessageEvent) => { + onmessage = (wsID: number, event: MessageEvent) => { if (this.wsID !== wsID) return; this._log('onmessage() - onmessage callback', { event, wsID }); @@ -538,7 +483,6 @@ export class StableWSConnection { if (!this.isResolved && data && data.type === 'connection.error') { this.isResolved = true; if (data.error) { - // @ts-expect-error - the types of _errorFromWSEvent are incorrect this.rejectPromise?.(this._errorFromWSEvent(data, false)); return; } @@ -584,7 +528,7 @@ export class StableWSConnection { this.scheduleConnectionCheck(); }; - onclose = (wsID: number, event: WebSocket.CloseEvent) => { + onclose = (wsID: number, event: CloseEvent) => { if (this.wsID !== wsID) return; this._log('onclose() - onclose callback - ' + event.code, { event, wsID }); @@ -594,11 +538,15 @@ export class StableWSConnection { // usually caused by invalid auth details const error = new Error( `WS connection reject with error ${event.reason}`, - ) as Error & WebSocket.CloseEvent; + ); + // @ts-expect-error error.reason = event.reason; + // @ts-expect-error error.code = event.code; + // @ts-expect-error error.wasClean = event.wasClean; + // @ts-expect-error error.target = event.target; this.rejectPromise?.(error); @@ -622,14 +570,14 @@ export class StableWSConnection { } }; - onerror = (wsID: number, event: WebSocket.ErrorEvent) => { + onerror = (wsID: number, event: Event) => { if (this.wsID !== wsID) return; this.consecutiveFailures += 1; this.totalFailures += 1; this._setHealth(false); this.isConnecting = false; - this.rejectPromise?.(this._errorFromWSEvent(event)); + this.rejectPromise?.(new Error(`WebSocket error: ${event}`)); this._log(`onerror() - WS connection resulted into error`, { event }); this._reconnect(); @@ -641,7 +589,6 @@ export class StableWSConnection { * * @param {boolean} healthy boolean indicating if the connection is healthy or not * @param {boolean} dispatchImmediately boolean indicating to dispatch event immediately even if the connection is unhealthy - * */ _setHealth = (healthy: boolean, dispatchImmediately = false) => { if (healthy === this.isHealthy) return; @@ -668,40 +615,31 @@ export class StableWSConnection { /** * _errorFromWSEvent - Creates an error object for the WS event - * */ - _errorFromWSEvent = ( - event: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent, + private _errorFromWSEvent = ( + event: CloseEvent | ConnectionErrorEvent, isWSFailure = true, ) => { - let code; - let statusCode; - let message; + let code: number; + let statusCode: number; + let message: string; if (isCloseEvent(event)) { code = event.code; - statusCode = 'unknown'; message = event.reason; + statusCode = 0; + } else { + const { error } = event; + code = error.code; + message = error.message; + statusCode = error.StatusCode; } - if (isErrorEvent(event)) { - code = event.error.code; - statusCode = event.error.StatusCode; - message = event.error.message; - } - - // Keeping this `warn` level log, to avoid cluttering of error logs from ws failures. - this._log( - `_errorFromWSEvent() - WS failed with code ${code}`, - { event }, - 'warn', - ); - - const error = new Error( - `WS failed with code ${code} and reason - ${message}`, - ) as Error & { - code?: string | number; + const msg = `WS failed with code: ${code} and reason: ${message}`; + this._log(msg, { event }, 'warn'); + const error = new Error(msg) as Error & { + code?: number; isWSFailure?: boolean; - StatusCode?: string | number; + StatusCode?: number; }; error.code = code; /** @@ -723,7 +661,6 @@ export class StableWSConnection { this.wsID += 1; try { - this?.ws?.removeAllListeners(); this?.ws?.close(); } catch (e) { // we don't care @@ -752,11 +689,8 @@ export class StableWSConnection { * Schedules a next health check ping for websocket. */ scheduleNextPing = () => { - if (this.healthCheckTimeoutRef) { - clearTimeout(this.healthCheckTimeoutRef); - } - // 30 seconds is the recommended interval (messenger uses this) + clearTimeout(this.healthCheckTimeoutRef); this.healthCheckTimeoutRef = setTimeout(() => { // send the healthcheck..., server replies with a health check event const data = [{ type: 'health.check', client_id: this.client.clientID }]; @@ -775,10 +709,7 @@ export class StableWSConnection { * to be reconnected. */ scheduleConnectionCheck = () => { - if (this.connectionCheckTimeoutRef) { - clearTimeout(this.connectionCheckTimeoutRef); - } - + clearTimeout(this.connectionCheckTimeoutRef); this.connectionCheckTimeoutRef = setTimeout(() => { const now = new Date(); if ( diff --git a/packages/client/src/coordinator/connection/connection_fallback.ts b/packages/client/src/coordinator/connection/connection_fallback.ts deleted file mode 100644 index 15fb03c3f5..0000000000 --- a/packages/client/src/coordinator/connection/connection_fallback.ts +++ /dev/null @@ -1,242 +0,0 @@ -import axios, { AxiosRequestConfig, CancelTokenSource } from 'axios'; -import { StreamClient } from './client'; -import { - addConnectionEventListeners, - removeConnectionEventListeners, - retryInterval, - sleep, -} from './utils'; -import { isAPIError, isConnectionIDError, isErrorRetryable } from './errors'; -import { LogLevel, StreamVideoEvent, UR } from './types'; -import { ConnectedEvent } from '../../gen/coordinator'; - -export enum ConnectionState { - Closed = 'CLOSED', - Connected = 'CONNECTED', - Connecting = 'CONNECTING', - Disconnected = 'DISCONNECTED', - Init = 'INIT', -} - -export class WSConnectionFallback { - client: StreamClient; - state: ConnectionState; - consecutiveFailures: number; - connectionID?: string; - cancelToken?: CancelTokenSource; - - constructor(client: StreamClient) { - this.client = client; - this.state = ConnectionState.Init; - this.consecutiveFailures = 0; - - addConnectionEventListeners(this._onlineStatusChanged); - } - - _log(msg: string, extra: UR = {}, level: LogLevel = 'info') { - this.client.logger(level, 'WSConnectionFallback:' + msg, { - ...extra, - }); - } - - _setState(state: ConnectionState) { - this._log(`_setState() - ${state}`); - - // transition from connecting => connected - if ( - this.state === ConnectionState.Connecting && - state === ConnectionState.Connected - ) { - this.client.dispatchEvent({ type: 'connection.changed', online: true }); - } - - if ( - state === ConnectionState.Closed || - state === ConnectionState.Disconnected - ) { - this.client.dispatchEvent({ type: 'connection.changed', online: false }); - } - - this.state = state; - } - - /** @private */ - _onlineStatusChanged = (event: { type: string }) => { - this._log(`_onlineStatusChanged() - ${event.type}`); - - if (event.type === 'offline') { - this._setState(ConnectionState.Closed); - this.cancelToken?.cancel('disconnect() is called'); - this.cancelToken = undefined; - return; - } - - if (event.type === 'online' && this.state === ConnectionState.Closed) { - this.connect(true); - } - }; - - /** @private */ - _req = async ( - params: UR, - config: AxiosRequestConfig, - retry: boolean, - ): Promise => { - if (!this.cancelToken && !params.close) { - this.cancelToken = axios.CancelToken.source(); - } - - try { - const res = await this.client.doAxiosRequest( - 'get', - (this.client.baseURL as string).replace(':3030', ':8900') + '/longpoll', // replace port if present for testing with local API - undefined, - { - config: { ...config, cancelToken: this.cancelToken?.token }, - params, - publicEndpoint: true, - }, - ); - - this.consecutiveFailures = 0; // always reset in case of no error - return res; - } catch (err) { - this.consecutiveFailures += 1; - - // @ts-ignore - if (retry && isErrorRetryable(err)) { - this._log(`_req() - Retryable error, retrying request`); - await sleep(retryInterval(this.consecutiveFailures)); - return this._req(params, config, retry); - } - - throw err; - } - }; - - /** @private */ - _poll = async () => { - while (this.state === ConnectionState.Connected) { - try { - const data = await this._req<{ - events: StreamVideoEvent[]; - }>( - {}, - { - timeout: 30000, - }, - true, - ); // 30s => API responds in 20s if there is no event - - if (data.events?.length) { - for (let i = 0; i < data.events.length; i++) { - this.client.dispatchEvent(data.events[i]); - } - } - } catch (err) { - if (axios.isCancel(err)) { - this._log(`_poll() - axios canceled request`); - return; - } - - /** client.doAxiosRequest will take care of TOKEN_EXPIRED error */ - - // @ts-ignore - if (isConnectionIDError(err)) { - this._log(`_poll() - ConnectionID error, connecting without ID...`); - this._setState(ConnectionState.Disconnected); - this.connect(true); - return; - } - - // @ts-ignore - if (isAPIError(err) && !isErrorRetryable(err)) { - this._setState(ConnectionState.Closed); - return; - } - - await sleep(retryInterval(this.consecutiveFailures)); - } - } - }; - - /** - * connect try to open a longpoll request - * @param reconnect should be false for first call and true for subsequent calls to keep the connection alive and call recoverState - */ - connect = async (reconnect = false) => { - if (this.state === ConnectionState.Connecting) { - this._log( - 'connect() - connecting already in progress', - { reconnect }, - 'warn', - ); - return; - } - if (this.state === ConnectionState.Connected) { - this._log( - 'connect() - already connected and polling', - { reconnect }, - 'warn', - ); - return; - } - - this._setState(ConnectionState.Connecting); - this.connectionID = undefined; // connect should be sent with empty connection_id so API creates one - try { - const { event } = await this._req<{ - event: ConnectedEvent; - }>( - { json: this.client._buildWSPayload() }, - { - timeout: 8000, // 8s - }, - reconnect, - ); - - this._setState(ConnectionState.Connected); - this.connectionID = event.connection_id; - this.client.resolveConnectionId?.(); - // @ts-expect-error - this.client.dispatchEvent(event); - this._poll(); - return event; - } catch (err) { - this._setState(ConnectionState.Closed); - this.client.rejectConnectionId?.(); - throw err; - } - }; - - /** - * isHealthy checks if there is a connectionID and connection is in Connected state - */ - isHealthy = () => { - return !!this.connectionID && this.state === ConnectionState.Connected; - }; - - disconnect = async (timeout = 2000) => { - removeConnectionEventListeners(this._onlineStatusChanged); - - this._setState(ConnectionState.Disconnected); - this.cancelToken?.cancel('disconnect() is called'); - this.cancelToken = undefined; - - const connection_id = this.connectionID; - this.connectionID = undefined; - - try { - await this._req( - { close: true, connection_id }, - { - timeout, - }, - false, - ); - this._log(`disconnect() - Closed connectionID`); - } catch (err) { - this._log(`disconnect() - Failed`, { err }, 'error'); - } - }; -} diff --git a/packages/client/src/coordinator/connection/errors.ts b/packages/client/src/coordinator/connection/errors.ts deleted file mode 100644 index 34c8b4f640..0000000000 --- a/packages/client/src/coordinator/connection/errors.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { AxiosResponse } from 'axios'; -import { APIErrorResponse } from './types'; - -export const APIErrorCodes: Record< - string, - { name: string; retryable: boolean } -> = { - '-1': { name: 'InternalSystemError', retryable: true }, - '2': { name: 'AccessKeyError', retryable: false }, - '3': { name: 'AuthenticationFailedError', retryable: true }, - '4': { name: 'InputError', retryable: false }, - '6': { name: 'DuplicateUsernameError', retryable: false }, - '9': { name: 'RateLimitError', retryable: true }, - '16': { name: 'DoesNotExistError', retryable: false }, - '17': { name: 'NotAllowedError', retryable: false }, - '18': { name: 'EventNotSupportedError', retryable: false }, - '19': { name: 'ChannelFeatureNotSupportedError', retryable: false }, - '20': { name: 'MessageTooLongError', retryable: false }, - '21': { name: 'MultipleNestingLevelError', retryable: false }, - '22': { name: 'PayloadTooBigError', retryable: false }, - '23': { name: 'RequestTimeoutError', retryable: true }, - '24': { name: 'MaxHeaderSizeExceededError', retryable: false }, - '40': { name: 'AuthErrorTokenExpired', retryable: false }, - '41': { name: 'AuthErrorTokenNotValidYet', retryable: false }, - '42': { name: 'AuthErrorTokenUsedBeforeIssuedAt', retryable: false }, - '43': { name: 'AuthErrorTokenSignatureInvalid', retryable: false }, - '44': { name: 'CustomCommandEndpointMissingError', retryable: false }, - '45': { name: 'CustomCommandEndpointCallError', retryable: true }, - '46': { name: 'ConnectionIDNotFoundError', retryable: false }, - '60': { name: 'CoolDownError', retryable: true }, - '69': { name: 'ErrWrongRegion', retryable: false }, - '70': { name: 'ErrQueryChannelPermissions', retryable: false }, - '71': { name: 'ErrTooManyConnections', retryable: true }, - '99': { name: 'AppSuspendedError', retryable: false }, -}; - -// todo: this is not a correct error declaration. /recordings endpoint returns error objects as follows: -// { -// "code": 16, -// "message": "ListRecordings failed with error: \"Can't find call with id default:bbbb\"", -// "StatusCode": 404, -// "duration": "0.00ms", -// "more_info": "https://getstream.io/chat/docs/api_errors_response", -// "details": [] -// } - -type APIError = Error & { code: number; isWSFailure?: boolean }; - -export function isAPIError(error: Error): error is APIError { - return (error as APIError).code !== undefined; -} - -export function isErrorRetryable(error: APIError) { - if (!error.code) return false; - const err = APIErrorCodes[`${error.code}`]; - if (!err) return false; - return err.retryable; -} - -export function isConnectionIDError(error: APIError) { - return error.code === 46; // ConnectionIDNotFoundError -} - -export function isWSFailure(err: APIError): boolean { - if (typeof err.isWSFailure === 'boolean') { - return err.isWSFailure; - } - - try { - return JSON.parse(err.message).isWSFailure; - } catch (_) { - return false; - } -} - -export function isErrorResponse( - res: AxiosResponse, -): res is AxiosResponse { - return !res.status || res.status < 200 || 300 <= res.status; -} diff --git a/packages/client/src/coordinator/connection/insights.ts b/packages/client/src/coordinator/connection/insights.ts deleted file mode 100644 index efc996b8a9..0000000000 --- a/packages/client/src/coordinator/connection/insights.ts +++ /dev/null @@ -1,88 +0,0 @@ -import axios from 'axios'; -import { StableWSConnection } from './connection'; -import { randomId, sleep } from './utils'; - -export type InsightTypes = - | 'ws_fatal' - | 'ws_success_after_failure' - | 'http_hi_failed'; -export class InsightMetrics { - connectionStartTimestamp: number | null; - wsConsecutiveFailures: number; - wsTotalFailures: number; - instanceClientId: string; - - constructor() { - this.connectionStartTimestamp = null; - this.wsTotalFailures = 0; - this.wsConsecutiveFailures = 0; - this.instanceClientId = randomId(); - } -} - -/** - * postInsights is not supposed to be used by end users directly within chat application, and thus is kept isolated - * from all the client/connection code/logic. - * - * @param insightType - * @param insights - */ -export const postInsights = async ( - insightType: InsightTypes, - insights: Record, -) => { - const maxAttempts = 3; - for (let i = 0; i < maxAttempts; i++) { - try { - await axios.post( - `https://chat-insights.getstream.io/insights/${insightType}`, - insights, - ); - } catch (e) { - await sleep((i + 1) * 3000); - continue; - } - break; - } -}; - -export function buildWsFatalInsight( - connection: StableWSConnection, - event: Record, -) { - return { - ...event, - ...buildWsBaseInsight(connection), - }; -} - -function buildWsBaseInsight(connection: StableWSConnection) { - const { client } = connection; - return { - ready_state: connection.ws?.readyState, - url: connection._buildUrl(), - api_key: client.key, - start_ts: client.insightMetrics.connectionStartTimestamp, - end_ts: new Date().getTime(), - auth_type: client.getAuthType(), - token: client.tokenManager.token, - user_id: client.userID, - user_details: client._user, - // device: client.options.device, - device: 'browser', - client_id: connection.connectionID, - ws_details: connection.ws, - ws_consecutive_failures: client.insightMetrics.wsConsecutiveFailures, - ws_total_failures: client.insightMetrics.wsTotalFailures, - request_id: connection.requestID, - online: typeof navigator !== 'undefined' ? navigator?.onLine : null, - user_agent: typeof navigator !== 'undefined' ? navigator?.userAgent : null, - instance_client_id: client.insightMetrics.instanceClientId, - }; -} - -export function buildWsSuccessAfterFailureInsight( - connection: StableWSConnection, -) { - return buildWsBaseInsight(connection); -} diff --git a/packages/client/src/coordinator/connection/signing.ts b/packages/client/src/coordinator/connection/signing.ts index 3710c4220f..4cf6d3fe7c 100644 --- a/packages/client/src/coordinator/connection/signing.ts +++ b/packages/client/src/coordinator/connection/signing.ts @@ -1,19 +1,4 @@ -import { decodeBase64, encodeBase64 } from './base64'; - -/** - * - * @param {string} userId the id of the user - * @return {string} - */ -export function DevToken(userId: string) { - return [ - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', //{"alg": "HS256", "typ": "JWT"} - encodeBase64(JSON.stringify({ user_id: userId })), - 'devtoken', // hardcoded signature - ].join('.'); -} - -export function UserFromToken(token: string) { +export function getUserFromToken(token: string) { const fragments = token.split('.'); if (fragments.length !== 3) { return ''; @@ -21,5 +6,34 @@ export function UserFromToken(token: string) { const b64Payload = fragments[1]; const payload = decodeBase64(b64Payload); const data = JSON.parse(payload); - return data.user_id as string; + return data.user_id as string | undefined; } + +// base-64 decoder throws exception if encoded string is not padded by '=' to make string length +// in multiples of 4. So gonna use our own method for this purpose to keep backwards compatibility +// https://github.com/beatgammit/base64-js/blob/master/index.js#L26 +const decodeBase64 = (s: string): string => { + const e = {} as { [key: string]: number }, + w = String.fromCharCode, + L = s.length; + let i, + b = 0, + c, + x, + l = 0, + a, + r = ''; + const A = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + for (i = 0; i < 64; i++) { + e[A.charAt(i)] = i; + } + for (x = 0; x < L; x++) { + c = e[s.charAt(x)]; + b = (b << 6) + c; + l += 6; + while (l >= 8) { + ((a = (b >>> (l -= 8)) & 0xff) || x < L - 2) && (r += w(a)); + } + } + return r; +}; diff --git a/packages/client/src/coordinator/connection/token_manager.ts b/packages/client/src/coordinator/connection/token_manager.ts index 9393132678..918e81148f 100644 --- a/packages/client/src/coordinator/connection/token_manager.ts +++ b/packages/client/src/coordinator/connection/token_manager.ts @@ -1,4 +1,4 @@ -import { UserFromToken } from './signing'; +import { getUserFromToken } from './signing'; import { isFunction } from './utils'; import type { TokenOrProvider, UserWithId } from './types'; @@ -16,15 +16,10 @@ export class TokenManager { user?: UserWithId; /** * Constructor - * - * @param {Secret} secret */ constructor(secret?: string) { this.loadTokenPromise = null; - if (secret) { - this.secret = secret; - } - + this.secret = secret; this.type = 'static'; } @@ -95,7 +90,7 @@ export class TokenManager { // Allow empty token for anonymous users if (isAnonymous && tokenOrProvider === '') return; - const tokenUserId = UserFromToken(tokenOrProvider); + const tokenUserId = getUserFromToken(tokenOrProvider); if ( tokenOrProvider != null && (tokenUserId == null || @@ -116,7 +111,6 @@ export class TokenManager { // Fetches a token from tokenProvider function and sets in tokenManager. // In case of static token, it will simply resolve to static token. loadToken = () => { - // eslint-disable-next-line no-async-promise-executor this.loadTokenPromise = new Promise(async (resolve, reject) => { if (this.type === 'static') { return resolve(this.token as string); diff --git a/packages/client/src/coordinator/connection/types.ts b/packages/client/src/coordinator/connection/types.ts index beb933c852..5483804c3d 100644 --- a/packages/client/src/coordinator/connection/types.ts +++ b/packages/client/src/coordinator/connection/types.ts @@ -1,5 +1,4 @@ import { AxiosRequestConfig, AxiosResponse } from 'axios'; -import { StableWSConnection } from './connection'; import { ConnectedEvent, UserRequest, WSEvent } from '../../gen/coordinator'; import { AllSfuEvents } from '../../rtc'; @@ -115,10 +114,6 @@ export type StreamClientOptions = Partial & { */ baseURL?: string; browser?: boolean; - // device?: BaseDeviceFields; - enableInsights?: boolean; - /** experimental feature, please contact support if you want this feature enabled for you */ - enableWSFallback?: boolean; logger?: Logger; logLevel?: LogLevel; /** @@ -146,10 +141,11 @@ export type StreamClientOptions = Partial & { */ secret?: string; - warmUp?: boolean; - // Set the instance of StableWSConnection on chat client. Its purely for testing purpose and should - // not be used in production apps. - wsConnection?: StableWSConnection; + /** + * The WebSocket implementation to use. This is mainly useful for testing. + * In Node.js environment, you can use the `ws` package. + */ + WebSocketImpl?: typeof WebSocket; }; export type TokenProvider = () => Promise; diff --git a/packages/client/src/coordinator/connection/utils.ts b/packages/client/src/coordinator/connection/utils.ts index 2276e7c006..4fd2ce619c 100644 --- a/packages/client/src/coordinator/connection/utils.ts +++ b/packages/client/src/coordinator/connection/utils.ts @@ -1,7 +1,8 @@ -import { Logger } from './types'; +import type { AxiosResponse } from 'axios'; +import type { APIErrorResponse } from './types'; +import type { ConnectionErrorEvent } from '../../gen/coordinator'; -export const sleep = (m: number): Promise => - new Promise((r) => setTimeout(r, m)); +export const sleep = (m: number) => new Promise((r) => setTimeout(r, m)); export function isFunction(value: Function | T): value is Function { return ( @@ -92,63 +93,17 @@ function getRandomBytes(length: number): Uint8Array { return bytes; } -export function convertErrorToJson(err: Error) { - const jsonObj = {} as Record; - - if (!err) return jsonObj; - - try { - Object.getOwnPropertyNames(err).forEach((key) => { - jsonObj[key] = Object.getOwnPropertyDescriptor(err, key); - }); - } catch (_) { - return { - error: 'failed to serialize the error', - }; - } - - return jsonObj; -} - /** * Informs if a promise is yet to be resolved or rejected */ export async function isPromisePending(promise: Promise) { const emptyObj = {}; return Promise.race([promise, emptyObj]).then( - (value) => (value === emptyObj ? true : false), + (value) => value === emptyObj, () => false, ); } -/** - * isOnline safely return the navigator.online value for browser env - * if navigator is not in global object, it always return true - */ -export function isOnline(logger: Logger) { - const nav = - typeof navigator !== 'undefined' - ? navigator - : typeof window !== 'undefined' && window.navigator - ? window.navigator - : undefined; - - if (!nav) { - logger( - 'warn', - 'isOnline failed to access window.navigator and assume browser is online', - ); - return true; - } - - // RN navigator has undefined for onLine - if (typeof nav.onLine !== 'boolean') { - return true; - } - - return nav.onLine; -} - /** * listenForConnectionChanges - Adds an event listener fired on browser going online or offline */ @@ -165,3 +120,16 @@ export function removeConnectionEventListeners(cb: (e: Event) => void) { window.removeEventListener('online', cb); } } + +export function isErrorResponse( + res: AxiosResponse, +): res is AxiosResponse { + return !res.status || res.status < 200 || 300 <= res.status; +} + +// Type guards to check WebSocket error type +export function isCloseEvent( + res: CloseEvent | ConnectionErrorEvent, +): res is CloseEvent { + return (res as CloseEvent).code !== undefined; +} diff --git a/packages/client/src/devices/__tests__/InputMediaDeviceManagerState.test.ts b/packages/client/src/devices/__tests__/InputMediaDeviceManagerState.test.ts index 1d10aa04b5..e82495bbac 100644 --- a/packages/client/src/devices/__tests__/InputMediaDeviceManagerState.test.ts +++ b/packages/client/src/devices/__tests__/InputMediaDeviceManagerState.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest'; +import { beforeAll, describe, expect, it, vi } from 'vitest'; import { InputMediaDeviceManagerState } from '../InputMediaDeviceManagerState'; import { firstValueFrom } from 'rxjs'; import { BrowserPermission } from '../BrowserPermission'; @@ -27,10 +27,18 @@ function mockPermissionStatus(state: PermissionState): PermissionStatus { describe('InputMediaDeviceManagerState', () => { describe('hasBrowserPermission', () => { + beforeAll(() => { + Object.defineProperty(navigator, 'permissions', { + value: { + query: vi.fn(), + }, + }); + }); + it('should emit true when permission is granted', async () => { const permissionStatus = mockPermissionStatus('granted'); const query = vi.fn(() => Promise.resolve(permissionStatus)); - globalThis.navigator = { permissions: { query } } as any; + vi.spyOn(navigator.permissions, 'query').mockImplementation(query); const state = new TestInputMediaDeviceManagerState(); const hasPermission = await firstValueFrom(state.hasBrowserPermission$); @@ -43,7 +51,7 @@ describe('InputMediaDeviceManagerState', () => { it('should emit false when permission is denied', async () => { const permissionStatus = mockPermissionStatus('denied'); const query = vi.fn(() => Promise.resolve(permissionStatus)); - globalThis.navigator = { permissions: { query } } as any; + vi.spyOn(navigator.permissions, 'query').mockImplementation(query); const state = new TestInputMediaDeviceManagerState(); const hasPermission = await firstValueFrom(state.hasBrowserPermission$); @@ -56,7 +64,7 @@ describe('InputMediaDeviceManagerState', () => { it('should emit true when prompt is needed', async () => { const permissionStatus = mockPermissionStatus('prompt'); const query = vi.fn(() => Promise.resolve(permissionStatus)); - globalThis.navigator = { permissions: { query } } as any; + vi.spyOn(navigator.permissions, 'query').mockImplementation(query); const state = new TestInputMediaDeviceManagerState(); const hasPermission = await firstValueFrom(state.hasBrowserPermission$); @@ -68,7 +76,7 @@ describe('InputMediaDeviceManagerState', () => { it('should emit true when permissions cannot be queried', async () => { const query = vi.fn(() => Promise.reject()); - globalThis.navigator = { permissions: { query } } as any; + vi.spyOn(navigator.permissions, 'query').mockImplementation(query); const state = new TestInputMediaDeviceManagerState(); const hasPermission = await firstValueFrom(state.hasBrowserPermission$); @@ -78,11 +86,8 @@ describe('InputMediaDeviceManagerState', () => { }); it('should emit true when permissions API is unavailable', async () => { - globalThis.navigator = {} as any; const state = new TestInputMediaDeviceManagerState(); - const hasPermission = await firstValueFrom(state.hasBrowserPermission$); - expect(hasPermission).toBe(true); }); }); diff --git a/packages/client/src/devices/__tests__/mocks.ts b/packages/client/src/devices/__tests__/mocks.ts index 55a8c4bf23..70d4029728 100644 --- a/packages/client/src/devices/__tests__/mocks.ts +++ b/packages/client/src/devices/__tests__/mocks.ts @@ -207,10 +207,6 @@ export const mockScreenShareStream = (includeAudio: boolean = true) => { let deviceIds: Subject; export const mockDeviceIds$ = () => { - global.navigator = { - //@ts-expect-error - mediaDevices: {}, - }; deviceIds = new Subject(); return deviceIds; }; diff --git a/packages/react-bindings/CHANGELOG.md b/packages/react-bindings/CHANGELOG.md index 72249218c5..f6cca80b5e 100644 --- a/packages/react-bindings/CHANGELOG.md +++ b/packages/react-bindings/CHANGELOG.md @@ -2,6 +2,11 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.2.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-bindings-1.2.0...@stream-io/video-react-bindings-1.2.1) (2024-11-26) + +### Dependency Updates + +* `@stream-io/video-client` updated to version `1.11.7` ## [1.2.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-bindings-1.1.23...@stream-io/video-react-bindings-1.2.0) (2024-11-25) diff --git a/packages/react-bindings/package.json b/packages/react-bindings/package.json index 010c25c24a..ddf884c113 100644 --- a/packages/react-bindings/package.json +++ b/packages/react-bindings/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-react-bindings", - "version": "1.2.0", + "version": "1.2.1", "packageManager": "yarn@3.2.4", "main": "./dist/index.cjs.js", "module": "./dist/index.es.js", diff --git a/packages/react-native-sdk/CHANGELOG.md b/packages/react-native-sdk/CHANGELOG.md index eb0a17f0ed..4a718fa332 100644 --- a/packages/react-native-sdk/CHANGELOG.md +++ b/packages/react-native-sdk/CHANGELOG.md @@ -2,6 +2,19 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.4.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.4.1...@stream-io/video-react-native-sdk-1.4.2) (2024-11-27) + + +### Bug Fixes + +* disable join call button to prevent multiple call joins ([#1602](https://github.com/GetStream/stream-video-js/issues/1602)) ([9079217](https://github.com/GetStream/stream-video-js/commit/9079217ab7cc5a87a948059d206c334433c7da8f)) + +## [1.4.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.4.0...@stream-io/video-react-native-sdk-1.4.1) (2024-11-26) + +### Dependency Updates + +* `@stream-io/video-client` updated to version `1.11.7` +* `@stream-io/video-react-bindings` updated to version `1.2.1` ## [1.4.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.3.8...@stream-io/video-react-native-sdk-1.4.0) (2024-11-25) ### Dependency Updates diff --git a/packages/react-native-sdk/package.json b/packages/react-native-sdk/package.json index afbaf72398..63b2ade071 100644 --- a/packages/react-native-sdk/package.json +++ b/packages/react-native-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-react-native-sdk", - "version": "1.4.0", + "version": "1.4.2", "packageManager": "yarn@3.2.4", "main": "dist/commonjs/index.js", "module": "dist/module/index.js", diff --git a/packages/react-native-sdk/src/components/Call/Lobby/JoinCallButton.tsx b/packages/react-native-sdk/src/components/Call/Lobby/JoinCallButton.tsx index 76c9bef7c4..ad61272556 100644 --- a/packages/react-native-sdk/src/components/Call/Lobby/JoinCallButton.tsx +++ b/packages/react-native-sdk/src/components/Call/Lobby/JoinCallButton.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { LobbyProps } from './Lobby'; import { Pressable, StyleSheet, Text } from 'react-native'; import { useCall, useI18n } from '@stream-io/video-react-bindings'; @@ -23,10 +23,12 @@ export const JoinCallButton = ({ theme: { colors, typefaces, joinCallButton }, } = useTheme(); const styles = useStyles(); + const [isLoading, setIsLoading] = useState(false); const { t } = useI18n(); const call = useCall(); const onPress = async () => { + setIsLoading(true); if (onPressHandler) { onPressHandler(); return; @@ -39,17 +41,20 @@ export const JoinCallButton = ({ } catch (error) { const logger = getLogger(['JoinCallButton']); logger('error', 'Error joining call:', error); + } finally { + setIsLoading(false); } }; + const backgroundColor = isLoading + ? colors.buttonDisabled + : colors.buttonPrimary; + return ( - {t('Join')} + {isLoading ? t('Joining...') : t('Join')} ); diff --git a/packages/react-native-sdk/src/translations/en.json b/packages/react-native-sdk/src/translations/en.json index c64f028136..491571cbc1 100644 --- a/packages/react-native-sdk/src/translations/en.json +++ b/packages/react-native-sdk/src/translations/en.json @@ -4,6 +4,7 @@ "Mute All": "Mute All", "Invite": "Invite", "Join": "Join", + "Joining...": "Joining...", "You": "You", "Reconnecting...": "Reconnecting...", "Loading...": "Loading...", diff --git a/packages/react-sdk/CHANGELOG.md b/packages/react-sdk/CHANGELOG.md index 2502d783ef..470382ead0 100644 --- a/packages/react-sdk/CHANGELOG.md +++ b/packages/react-sdk/CHANGELOG.md @@ -2,6 +2,12 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.7.23](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.7.22...@stream-io/video-react-sdk-1.7.23) (2024-11-26) + +### Dependency Updates + +* `@stream-io/video-client` updated to version `1.11.7` +* `@stream-io/video-react-bindings` updated to version `1.2.1` ## [1.7.22](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.7.21...@stream-io/video-react-sdk-1.7.22) (2024-11-25) ### Dependency Updates diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json index dc5c4ab0be..5faa6603f8 100644 --- a/packages/react-sdk/package.json +++ b/packages/react-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-react-sdk", - "version": "1.7.22", + "version": "1.7.23", "packageManager": "yarn@3.2.4", "main": "./dist/index.cjs.js", "module": "./dist/index.es.js", diff --git a/sample-apps/react-native/dogfood/package.json b/sample-apps/react-native/dogfood/package.json index 5646fad5bd..dbd3047b1c 100644 --- a/sample-apps/react-native/dogfood/package.json +++ b/sample-apps/react-native/dogfood/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-react-native-dogfood", - "version": "4.7.0", + "version": "4.8.1", "private": true, "scripts": { "android": "react-native run-android", diff --git a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx index b7d5847fc5..5d704b1fb2 100644 --- a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx +++ b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx @@ -3,6 +3,7 @@ import { useCall, CallContent, useTheme, + useIsInPiPMode, } from '@stream-io/video-react-native-sdk'; import { ActivityIndicator, StatusBar, StyleSheet, View } from 'react-native'; import { ParticipantsInfoList } from './ParticipantsInfoList'; @@ -34,6 +35,7 @@ export const ActiveCall = ({ const styles = useStyles(); const { selectedLayout } = useLayout(); const themeMode = useAppGlobalStoreValue((store) => store.themeMode); + const isInPiPMode = useIsInPiPMode(false); const onOpenCallParticipantsInfo = useCallback(() => { setIsCallParticipantsVisible(true); @@ -87,7 +89,7 @@ export const ActiveCall = ({ - + {!isInPiPMode && } ( - - - -); +export const AuthenticationProgress = () => { + const styles = useStyles(); + return ( + + + + ); +}; -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: appTheme.colors.static_grey, - }, -}); +const useStyles = () => { + const { theme } = useTheme(); + return useMemo( + () => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: theme.colors.sheetPrimary, + }, + }), + [theme], + ); +}; diff --git a/yarn.lock b/yarn.lock index 624e2dea88..19dec7dcf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,13 +24,13 @@ __metadata: languageName: node linkType: hard -"@ampproject/remapping@npm:^2.2.0, @ampproject/remapping@npm:^2.2.1": - version: 2.2.1 - resolution: "@ampproject/remapping@npm:2.2.1" +"@ampproject/remapping@npm:^2.2.0, @ampproject/remapping@npm:^2.3.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" dependencies: - "@jridgewell/gen-mapping": ^0.3.0 - "@jridgewell/trace-mapping": ^0.3.9 - checksum: 03c04fd526acc64a1f4df22651186f3e5ef0a9d6d6530ce4482ec9841269cf7a11dbb8af79237c282d721c5312024ff17529cd72cc4768c11e999b58e2302079 + "@jridgewell/gen-mapping": ^0.3.5 + "@jridgewell/trace-mapping": ^0.3.24 + checksum: d3ad7b89d973df059c4e8e6d7c972cbeb1bb2f18f002a3bd04ae0707da214cb06cc06929b65aa2313b9347463df2914772298bae8b1d7973f246bb3f2ab3e8f0 languageName: node linkType: hard @@ -368,7 +368,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.25.7, @babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.2": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.25.7, @babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.2": version: 7.26.2 resolution: "@babel/parser@npm:7.26.2" dependencies: @@ -1759,7 +1759,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.19.0, @babel/types@npm:^7.2.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.6, @babel/types@npm:^7.25.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.19.0, @babel/types@npm:^7.2.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.6, @babel/types@npm:^7.25.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.26.0 resolution: "@babel/types@npm:7.26.0" dependencies: @@ -4224,7 +4224,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.9": +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: @@ -8552,13 +8552,10 @@ __metadata: "@stream-io/node-sdk": ^0.4.3 "@types/sdp-transform": ^2.4.7 "@types/ua-parser-js": ^0.7.37 - "@types/ws": ^8.5.7 - "@vitest/coverage-v8": ^0.34.4 - axios: ^1.6.0 - base64-js: ^1.5.1 + "@vitest/coverage-v8": ^2.1.4 + axios: ^1.7.7 dotenv: ^16.3.1 happy-dom: ^11.0.2 - isomorphic-ws: ^5.0.0 prettier: ^3.3.2 rimraf: ^5.0.7 rollup: ^4.22.0 @@ -8567,10 +8564,9 @@ __metadata: typescript: ^5.5.2 ua-parser-js: ^1.0.36 vite: ^5.4.6 - vitest: ^1.0.0 - vitest-mock-extended: ^1.2.1 + vitest: ^2.1.4 + vitest-mock-extended: ^2.0.2 webrtc-adapter: ^8.2.3 - ws: ^8.14.2 languageName: unknown linkType: soft @@ -9649,15 +9645,6 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.5.7": - version: 8.5.7 - resolution: "@types/ws@npm:8.5.7" - dependencies: - "@types/node": "*" - checksum: 4502085c0f7ae6b36d5419c0fc6ce4b9002ee5e997a8708d6ed10b393e97091e1b986e8038ec604945c194f14aac150e74d6596629a2775628d122f552009c35 - languageName: node - linkType: hard - "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -10313,78 +10300,110 @@ __metadata: languageName: node linkType: hard -"@vitest/coverage-v8@npm:^0.34.4": - version: 0.34.4 - resolution: "@vitest/coverage-v8@npm:0.34.4" +"@vitest/coverage-v8@npm:^2.1.4": + version: 2.1.4 + resolution: "@vitest/coverage-v8@npm:2.1.4" dependencies: - "@ampproject/remapping": ^2.2.1 + "@ampproject/remapping": ^2.3.0 "@bcoe/v8-coverage": ^0.2.3 - istanbul-lib-coverage: ^3.2.0 + debug: ^4.3.7 + istanbul-lib-coverage: ^3.2.2 istanbul-lib-report: ^3.0.1 - istanbul-lib-source-maps: ^4.0.1 - istanbul-reports: ^3.1.5 - magic-string: ^0.30.1 - picocolors: ^1.0.0 - std-env: ^3.3.3 - test-exclude: ^6.0.0 - v8-to-istanbul: ^9.1.0 + istanbul-lib-source-maps: ^5.0.6 + istanbul-reports: ^3.1.7 + magic-string: ^0.30.12 + magicast: ^0.3.5 + std-env: ^3.7.0 + test-exclude: ^7.0.1 + tinyrainbow: ^1.2.0 + peerDependencies: + "@vitest/browser": 2.1.4 + vitest: 2.1.4 + peerDependenciesMeta: + "@vitest/browser": + optional: true + checksum: 29ad6800afd93fa36d86b0df45d1a108395501743f9248c6a46baa35ea4d74cafbd6f0a29d897460c81eb4f4bbe96bfd915458d0160b5a45b9cab5c565db9f2f + languageName: node + linkType: hard + +"@vitest/expect@npm:2.1.4": + version: 2.1.4 + resolution: "@vitest/expect@npm:2.1.4" + dependencies: + "@vitest/spy": 2.1.4 + "@vitest/utils": 2.1.4 + chai: ^5.1.2 + tinyrainbow: ^1.2.0 + checksum: 613d527e74c2dd6f6ecd75cdf8c67a2abfaca0e9795883ff3cba529c6ce462e149a9ee359bab4b49cbb68b4e31a51cf489281d3e2f24820a47438ec774f7672a + languageName: node + linkType: hard + +"@vitest/mocker@npm:2.1.4": + version: 2.1.4 + resolution: "@vitest/mocker@npm:2.1.4" + dependencies: + "@vitest/spy": 2.1.4 + estree-walker: ^3.0.3 + magic-string: ^0.30.12 peerDependencies: - vitest: ">=0.32.0 <1" - checksum: ddda0d708d65e63b74a472d7b22360b5c1250042c0230605201d38179f7909f2d632f7778513aa6eb65de1759276f43998029d6b9db958f8f8949b41d7b4a011 + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: ba5b6e1084b69aaa0e5b93be5f3a1c37c3f1ba4b2c977f38aaa048ec337e03694cd8de002bb5199e69259206cc03cbc0b911e4e167c95d84b88aaa0b6a40b5a7 languageName: node linkType: hard -"@vitest/expect@npm:1.6.0": - version: 1.6.0 - resolution: "@vitest/expect@npm:1.6.0" +"@vitest/pretty-format@npm:2.1.4, @vitest/pretty-format@npm:^2.1.4": + version: 2.1.4 + resolution: "@vitest/pretty-format@npm:2.1.4" dependencies: - "@vitest/spy": 1.6.0 - "@vitest/utils": 1.6.0 - chai: ^4.3.10 - checksum: f3a9959ea387622297efed9e3689fd405044a813df5d5923302eaaea831e250d8d6a0ccd44fb387a95c19963242695ed803afc7c46ae06c48a8e06f194951984 + tinyrainbow: ^1.2.0 + checksum: 4d6c799d9b9418ebfce77df518a5ea8717051bdb5eecce63d6b7009634d4843630c0bc424d0f405bd12b8f404e4bf09b6f208a331568de9b6b9395322221e41c languageName: node linkType: hard -"@vitest/runner@npm:1.6.0": - version: 1.6.0 - resolution: "@vitest/runner@npm:1.6.0" +"@vitest/runner@npm:2.1.4": + version: 2.1.4 + resolution: "@vitest/runner@npm:2.1.4" dependencies: - "@vitest/utils": 1.6.0 - p-limit: ^5.0.0 - pathe: ^1.1.1 - checksum: 2dcd953477d5effc051376e35a7f2c2b28abbe07c54e61157c9a6d6f01c880e079592c959397b3a55471423256ab91709c150881a33632558b81b1e251a0bf9c + "@vitest/utils": 2.1.4 + pathe: ^1.1.2 + checksum: f0584dc51ae2ebe6a768e9d832ff3821e107c0fed3b20a83879be83cb452a662ffa1d57abf06276915f13d0590874cade6f7a184d401285e1fe1cc1fb77b5f32 languageName: node linkType: hard -"@vitest/snapshot@npm:1.6.0": - version: 1.6.0 - resolution: "@vitest/snapshot@npm:1.6.0" +"@vitest/snapshot@npm:2.1.4": + version: 2.1.4 + resolution: "@vitest/snapshot@npm:2.1.4" dependencies: - magic-string: ^0.30.5 - pathe: ^1.1.1 - pretty-format: ^29.7.0 - checksum: c4249fbf3ce310de86a19529a0a5c10b1bde4d8d8a678029c632335969b86cbdbf51cedc20d5e9c9328afee834d13cec1b8de5d0fd58139bf8e2dd8dcd0797f4 + "@vitest/pretty-format": 2.1.4 + magic-string: ^0.30.12 + pathe: ^1.1.2 + checksum: c45055e483e7276197e6e67aa6f310f75cad4639ae5732308319eab19ce2c2772b326563a12b59fca0bb68a19a48d6a8eeda0bab81de9048fa927f8964635c04 languageName: node linkType: hard -"@vitest/spy@npm:1.6.0": - version: 1.6.0 - resolution: "@vitest/spy@npm:1.6.0" +"@vitest/spy@npm:2.1.4": + version: 2.1.4 + resolution: "@vitest/spy@npm:2.1.4" dependencies: - tinyspy: ^2.2.0 - checksum: 0201975232255e1197f70fc6b23a1ff5e606138a5b96598fff06077d5b747705391013ee98f951affcfd8f54322e4ae1416200393248bb6a9c794f4ef663a066 + tinyspy: ^3.0.2 + checksum: c91874b7e4f42cbdd4bd71a5afb9a4f25da72bf076f7e011d9f600749ef50d8baf7cbf32b0f3d7a418e0923ba402e2f8835041f950d449fe40faef335c6a07f8 languageName: node linkType: hard -"@vitest/utils@npm:1.6.0": - version: 1.6.0 - resolution: "@vitest/utils@npm:1.6.0" +"@vitest/utils@npm:2.1.4": + version: 2.1.4 + resolution: "@vitest/utils@npm:2.1.4" dependencies: - diff-sequences: ^29.6.3 - estree-walker: ^3.0.3 - loupe: ^2.3.7 - pretty-format: ^29.7.0 - checksum: a4749533a48e7e4bbc8eafee0fee0e9a0d4eaa4910fbdb490d34e16f8ebcce59a2b38529b9e6b4578e3b4510ea67b29384c93165712b0a19f2e71946922d2c56 + "@vitest/pretty-format": 2.1.4 + loupe: ^3.1.2 + tinyrainbow: ^1.2.0 + checksum: f7d9a4e8c411b9e80e7df46a725933977c9a330b133cc17e39e6f069d31832a849c070ef18b96abe2f07847f1860f36c8dd081eff2dad62b2874592d68e7dc43 languageName: node linkType: hard @@ -10507,7 +10526,7 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.1.1, acorn-walk@npm:^8.3.2": +"acorn-walk@npm:^8.1.1": version: 8.3.3 resolution: "acorn-walk@npm:8.3.3" dependencies: @@ -10516,7 +10535,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.11.3, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.6.0, acorn@npm:^8.8.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": +"acorn@npm:^8.11.0, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.6.0, acorn@npm:^8.8.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": version: 8.12.1 resolution: "acorn@npm:8.12.1" bin: @@ -10962,10 +10981,10 @@ __metadata: languageName: node linkType: hard -"assertion-error@npm:^1.1.0": - version: 1.1.0 - resolution: "assertion-error@npm:1.1.0" - checksum: fd9429d3a3d4fd61782eb3962ae76b6d08aa7383123fca0596020013b3ebd6647891a85b05ce821c47d1471ed1271f00b0545cf6a4326cf2fc91efcc3b0fbecf +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: a0789dd882211b87116e81e2648ccb7f60340b34f19877dd020b39ebb4714e475eb943e14ba3e22201c221ef6645b7bfe10297e76b6ac95b48a9898c1211ce66 languageName: node linkType: hard @@ -11089,7 +11108,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:1.6.8, axios@npm:^1.0.0, axios@npm:^1.6.0": +"axios@npm:1.6.8": version: 1.6.8 resolution: "axios@npm:1.6.8" dependencies: @@ -11100,6 +11119,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.0.0, axios@npm:^1.6.0, axios@npm:^1.7.7": + version: 1.7.7 + resolution: "axios@npm:1.7.7" + dependencies: + follow-redirects: ^1.15.6 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: 882d4fe0ec694a07c7f5c1f68205eb6dc5a62aecdb632cc7a4a3d0985188ce3030e0b277e1a8260ac3f194d314ae342117660a151fabffdc5081ca0b5a8b47fe + languageName: node + linkType: hard + "axobject-query@npm:^3.1.1": version: 3.1.1 resolution: "axobject-query@npm:3.1.1" @@ -11888,18 +11918,16 @@ __metadata: languageName: node linkType: hard -"chai@npm:^4.3.10": - version: 4.5.0 - resolution: "chai@npm:4.5.0" +"chai@npm:^5.1.2": + version: 5.1.2 + resolution: "chai@npm:5.1.2" dependencies: - assertion-error: ^1.1.0 - check-error: ^1.0.3 - deep-eql: ^4.1.3 - get-func-name: ^2.0.2 - loupe: ^2.3.6 - pathval: ^1.1.1 - type-detect: ^4.1.0 - checksum: 70e5a8418a39e577e66a441cc0ce4f71fd551a650a71de30dd4e3e31e75ed1f5aa7119cf4baf4a2cb5e85c0c6befdb4d8a05811fad8738c1a6f3aa6a23803821 + assertion-error: ^2.0.1 + check-error: ^2.1.1 + deep-eql: ^5.0.1 + loupe: ^3.1.0 + pathval: ^2.0.0 + checksum: f2341967ab5632612548d372c27b46219adad3af35021d8cba2ae3c262f588de2c60cb3f004e6ad40e363a9cad6d20d0de51f00e7e9ac31cce17fb05d4efa316 languageName: node linkType: hard @@ -11992,12 +12020,10 @@ __metadata: languageName: node linkType: hard -"check-error@npm:^1.0.3": - version: 1.0.3 - resolution: "check-error@npm:1.0.3" - dependencies: - get-func-name: ^2.0.2 - checksum: e2131025cf059b21080f4813e55b3c480419256914601750b0fee3bd9b2b8315b531e551ef12560419b8b6d92a3636511322752b1ce905703239e7cc451b6399 +"check-error@npm:^2.1.1": + version: 2.1.1 + resolution: "check-error@npm:2.1.1" + checksum: d785ed17b1d4a4796b6e75c765a9a290098cf52ff9728ce0756e8ffd4293d2e419dd30c67200aee34202463b474306913f2fcfaf1890641026d9fc6966fea27a languageName: node linkType: hard @@ -12506,13 +12532,6 @@ __metadata: languageName: node linkType: hard -"confbox@npm:^0.1.7": - version: 0.1.7 - resolution: "confbox@npm:0.1.7" - checksum: bde836c26f5154a348b0c0a757f8a0138929e5737e0553be3c4f07a056abca618b861aa63ac3b22d344789b56be99a1382928933e08cd500df00213bf4d8fb43 - languageName: node - linkType: hard - "confusing-browser-globals@npm:^1.0.11": version: 1.0.11 resolution: "confusing-browser-globals@npm:1.0.11" @@ -13155,15 +13174,15 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": - version: 4.3.4 - resolution: "debug@npm:4.3.4" +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.7": + version: 4.3.7 + resolution: "debug@npm:4.3.7" dependencies: - ms: 2.1.2 + ms: ^2.1.3 peerDependenciesMeta: supports-color: optional: true - checksum: 3dbad3f94ea64f34431a9cbf0bafb61853eda57bff2880036153438f50fb5a84f27683ba0d8e5426bf41a8c6ff03879488120cf5b3a761e77953169c0600a708 + checksum: 822d74e209cd910ef0802d261b150314bbcf36c582ccdbb3e70f0894823c17e49a50d3e66d96b633524263975ca16b6a833f3e3b7e030c157169a5fabac63160 languageName: node linkType: hard @@ -13176,6 +13195,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:4.3.4": + version: 4.3.4 + resolution: "debug@npm:4.3.4" + dependencies: + ms: 2.1.2 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 3dbad3f94ea64f34431a9cbf0bafb61853eda57bff2880036153438f50fb5a84f27683ba0d8e5426bf41a8c6ff03879488120cf5b3a761e77953169c0600a708 + languageName: node + linkType: hard + "debug@npm:^3.1.0, debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -13237,12 +13268,10 @@ __metadata: languageName: node linkType: hard -"deep-eql@npm:^4.1.3": - version: 4.1.4 - resolution: "deep-eql@npm:4.1.4" - dependencies: - type-detect: ^4.0.0 - checksum: 01c3ca78ff40d79003621b157054871411f94228ceb9b2cab78da913c606631c46e8aa79efc4aa0faf3ace3092acd5221255aab3ef0e8e7b438834f0ca9a16c7 +"deep-eql@npm:^5.0.1": + version: 5.0.2 + resolution: "deep-eql@npm:5.0.2" + checksum: 6aaaadb4c19cbce42e26b2bbe5bd92875f599d2602635dc97f0294bae48da79e89470aedee05f449e0ca8c65e9fd7e7872624d1933a1db02713d99c2ca8d1f24 languageName: node linkType: hard @@ -14887,23 +14916,6 @@ __metadata: languageName: node linkType: hard -"execa@npm:^8.0.1": - version: 8.0.1 - resolution: "execa@npm:8.0.1" - dependencies: - cross-spawn: ^7.0.3 - get-stream: ^8.0.1 - human-signals: ^5.0.0 - is-stream: ^3.0.0 - merge-stream: ^2.0.0 - npm-run-path: ^5.1.0 - onetime: ^6.0.0 - signal-exit: ^4.1.0 - strip-final-newline: ^3.0.0 - checksum: cac1bf86589d1d9b73bdc5dda65c52012d1a9619c44c526891956745f7b366ca2603d29fe3f7460bacc2b48c6eab5d6a4f7afe0534b31473d3708d1265545e1f - languageName: node - linkType: hard - "exit@npm:^0.1.2": version: 0.1.2 resolution: "exit@npm:0.1.2" @@ -14911,6 +14923,13 @@ __metadata: languageName: node linkType: hard +"expect-type@npm:^1.1.0": + version: 1.1.0 + resolution: "expect-type@npm:1.1.0" + checksum: 65d25ec10bca32bcf650dcfe734532acc4b7a73677c656f299a7cbed273b5c4d6a3dab11af76f452645d54a95c4ef39fc73772f2c8eb6684ba35672958d6f3b3 + languageName: node + linkType: hard + "expect@npm:^29.0.0, expect@npm:^29.7.0": version: 29.7.0 resolution: "expect@npm:29.7.0" @@ -16092,13 +16111,6 @@ __metadata: languageName: node linkType: hard -"get-func-name@npm:^2.0.1, get-func-name@npm:^2.0.2": - version: 2.0.2 - resolution: "get-func-name@npm:2.0.2" - checksum: 3f62f4c23647de9d46e6f76d2b3eafe58933a9b3830c60669e4180d6c601ce1b4aa310ba8366143f55e52b139f992087a9f0647274e8745621fa2af7e0acf13b - languageName: node - linkType: hard - "get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": version: 1.2.4 resolution: "get-intrinsic@npm:1.2.4" @@ -16165,13 +16177,6 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^8.0.1": - version: 8.0.1 - resolution: "get-stream@npm:8.0.1" - checksum: 01e3d3cf29e1393f05f44d2f00445c5f9ec3d1c49e8179b31795484b9c117f4c695e5e07b88b50785d5c8248a788c85d9913a79266fc77e3ef11f78f10f1b974 - languageName: node - linkType: hard - "get-symbol-description@npm:^1.0.2": version: 1.0.2 resolution: "get-symbol-description@npm:1.0.2" @@ -16918,13 +16923,6 @@ __metadata: languageName: node linkType: hard -"human-signals@npm:^5.0.0": - version: 5.0.0 - resolution: "human-signals@npm:5.0.0" - checksum: 6504560d5ed91444f16bea3bd9dfc66110a339442084e56c3e7fa7bbdf3f406426d6563d662bdce67064b165eac31eeabfc0857ed170aaa612cf14ec9f9a464c - languageName: node - linkType: hard - "humanize-ms@npm:^1.2.1": version: 1.2.1 resolution: "humanize-ms@npm:1.2.1" @@ -17678,13 +17676,6 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^3.0.0": - version: 3.0.0 - resolution: "is-stream@npm:3.0.0" - checksum: 172093fe99119ffd07611ab6d1bcccfe8bc4aa80d864b15f43e63e54b7abc71e779acd69afdb854c4e2a67fdc16ae710e370eda40088d1cfc956a50ed82d8f16 - languageName: node - linkType: hard - "is-string@npm:^1.0.5, is-string@npm:^1.0.7": version: 1.0.7 resolution: "is-string@npm:1.0.7" @@ -17839,19 +17830,10 @@ __metadata: languageName: node linkType: hard -"isomorphic-ws@npm:^5.0.0": - version: 5.0.0 - resolution: "isomorphic-ws@npm:5.0.0" - peerDependencies: - ws: "*" - checksum: e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 - languageName: node - linkType: hard - -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": - version: 3.2.0 - resolution: "istanbul-lib-coverage@npm:3.2.0" - checksum: a2a545033b9d56da04a8571ed05c8120bf10e9bce01cf8633a3a2b0d1d83dff4ac4fe78d6d5673c27fc29b7f21a41d75f83a36be09f82a61c367b56aa73c1ff9 +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.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 @@ -17892,7 +17874,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-source-maps@npm:^4.0.0, istanbul-lib-source-maps@npm:^4.0.1": +"istanbul-lib-source-maps@npm:^4.0.0": version: 4.0.1 resolution: "istanbul-lib-source-maps@npm:4.0.1" dependencies: @@ -17903,13 +17885,24 @@ __metadata: languageName: node linkType: hard -"istanbul-reports@npm:^3.1.3, istanbul-reports@npm:^3.1.5": - version: 3.1.6 - resolution: "istanbul-reports@npm:3.1.6" +"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.3, 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: 44c4c0582f287f02341e9720997f9e82c071627e1e862895745d5f52ec72c9b9f38e1d12370015d2a71dcead794f34c7732aaef3fab80a24bc617a21c3d911d6 + checksum: 2072db6e07bfbb4d0eb30e2700250636182398c1af811aea5032acb219d2080f7586923c09fa194029efd6b92361afb3dcbe1ebcc3ee6651d13340f7c6c4ed95 languageName: node linkType: hard @@ -18506,13 +18499,6 @@ __metadata: languageName: node linkType: hard -"js-tokens@npm:^9.0.0": - version: 9.0.0 - resolution: "js-tokens@npm:9.0.0" - checksum: 427d0db681caab0c906cfc78a0235bbe7b41712cee83f3f14785c1de079a1b1a85693cc8f99a3f71685d0d76acaa5b9c8920850b67f93d3eeb7ef186987d186c - languageName: node - linkType: hard - "js-yaml@npm:4.1.0, js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" @@ -19010,16 +18996,6 @@ __metadata: languageName: node linkType: hard -"local-pkg@npm:^0.5.0": - version: 0.5.0 - resolution: "local-pkg@npm:0.5.0" - dependencies: - mlly: ^1.4.2 - pkg-types: ^1.0.3 - checksum: b0a6931e588ad4f7bf4ab49faacf49e07fc4d05030f895aa055d46727a15b99300d39491cf2c3e3f05284aec65565fb760debb74c32e64109f4a101f9300d81a - languageName: node - linkType: hard - "locate-path@npm:^2.0.0": version: 2.0.0 resolution: "locate-path@npm:2.0.0" @@ -19297,12 +19273,10 @@ __metadata: languageName: node linkType: hard -"loupe@npm:^2.3.6, loupe@npm:^2.3.7": - version: 2.3.7 - resolution: "loupe@npm:2.3.7" - dependencies: - get-func-name: ^2.0.1 - checksum: 96c058ec7167598e238bb7fb9def2f9339215e97d6685d9c1e3e4bdb33d14600e11fe7a812cf0c003dfb73ca2df374f146280b2287cae9e8d989e9d7a69a203b +"loupe@npm:^3.1.0, loupe@npm:^3.1.2": + version: 3.1.2 + resolution: "loupe@npm:3.1.2" + checksum: 4a75bbe8877a1ced3603e08b1095cd6f4c987c50fe63719fdc3009029560f91e07a915e7f6eff1322bb62bfb2a2beeef06b13ccb3c12f81bda9f3674434dcab9 languageName: node linkType: hard @@ -19347,12 +19321,23 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.1, magic-string@npm:^0.30.3, magic-string@npm:^0.30.5": - version: 0.30.11 - resolution: "magic-string@npm:0.30.11" +"magic-string@npm:^0.30.12, magic-string@npm:^0.30.3": + version: 0.30.12 + resolution: "magic-string@npm:0.30.12" dependencies: "@jridgewell/sourcemap-codec": ^1.5.0 - checksum: e041649453c9a3f31d2e731fc10e38604d50e20d3585cd48bc7713a6e2e1a3ad3012105929ca15750d59d0a3f1904405e4b95a23b7e69dc256db3c277a73a3ca + checksum: 3f0d23b74371765f0e6cad4284eebba0ac029c7a55e39292de5aa92281afb827138cb2323d24d2924f6b31f138c3783596c5ccaa98653fe9cf122e1f81325b59 + languageName: node + linkType: hard + +"magicast@npm:^0.3.5": + 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 @@ -21107,13 +21092,6 @@ __metadata: languageName: node linkType: hard -"mimic-fn@npm:^4.0.0": - version: 4.0.0 - resolution: "mimic-fn@npm:4.0.0" - checksum: 995dcece15ee29aa16e188de6633d43a3db4611bcf93620e7e62109ec41c79c0f34277165b8ce5e361205049766e371851264c21ac64ca35499acb5421c2ba56 - languageName: node - linkType: hard - "min-indent@npm:^1.0.0": version: 1.0.1 resolution: "min-indent@npm:1.0.1" @@ -21333,18 +21311,6 @@ __metadata: languageName: node linkType: hard -"mlly@npm:^1.4.2, mlly@npm:^1.7.1": - version: 1.7.1 - resolution: "mlly@npm:1.7.1" - dependencies: - acorn: ^8.11.3 - pathe: ^1.1.2 - pkg-types: ^1.1.1 - ufo: ^1.5.3 - checksum: 956a6d54119eef782f302580f63a9800654e588cd70015b4218a00069c6ef11b87984e8ffe140a4668b0100ad4022b11d1f9b11ac2c6dbafa4d8bc33ae3a08a8 - languageName: node - linkType: hard - "mml-react@npm:^0.4.7": version: 0.4.7 resolution: "mml-react@npm:0.4.7" @@ -21420,7 +21386,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -21852,15 +21818,6 @@ __metadata: languageName: node linkType: hard -"npm-run-path@npm:^5.1.0": - version: 5.3.0 - resolution: "npm-run-path@npm:5.3.0" - dependencies: - path-key: ^4.0.0 - checksum: ae8e7a89da9594fb9c308f6555c73f618152340dcaae423e5fb3620026fefbec463618a8b761920382d666fa7a2d8d240b6fe320e8a6cdd54dc3687e2b659d25 - languageName: node - linkType: hard - "npmlog@npm:^5.0.1": version: 5.0.1 resolution: "npmlog@npm:5.0.1" @@ -22210,15 +22167,6 @@ __metadata: languageName: node linkType: hard -"onetime@npm:^6.0.0": - version: 6.0.0 - resolution: "onetime@npm:6.0.0" - dependencies: - mimic-fn: ^4.0.0 - checksum: 0846ce78e440841335d4e9182ef69d5762e9f38aa7499b19f42ea1c4cd40f0b4446094c455c713f9adac3f4ae86f613bb5e30c99e52652764d06a89f709b3788 - languageName: node - linkType: hard - "open@npm:^6.2.0": version: 6.4.0 resolution: "open@npm:6.4.0" @@ -22387,15 +22335,6 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^5.0.0": - version: 5.0.0 - resolution: "p-limit@npm:5.0.0" - dependencies: - yocto-queue: ^1.0.0 - checksum: 87bf5837dee6942f0dbeff318436179931d9a97848d1b07dbd86140a477a5d2e6b90d9701b210b4e21fe7beaea2979dfde366e4f576fa644a59bd4d6a6371da7 - languageName: node - linkType: hard - "p-locate@npm:^2.0.0": version: 2.0.0 resolution: "p-locate@npm:2.0.0" @@ -22606,13 +22545,6 @@ __metadata: languageName: node linkType: hard -"path-key@npm:^4.0.0": - version: 4.0.0 - resolution: "path-key@npm:4.0.0" - checksum: 8e6c314ae6d16b83e93032c61020129f6f4484590a777eed709c4a01b50e498822b00f76ceaf94bc64dbd90b327df56ceadce27da3d83393790f1219e07721d7 - languageName: node - linkType: hard - "path-match@npm:1.2.4": version: 1.2.4 resolution: "path-match@npm:1.2.4" @@ -22703,17 +22635,17 @@ __metadata: languageName: node linkType: hard -"pathe@npm:^1.1.1, pathe@npm:^1.1.2": +"pathe@npm:^1.1.2": version: 1.1.2 resolution: "pathe@npm:1.1.2" checksum: ec5f778d9790e7b9ffc3e4c1df39a5bb1ce94657a4e3ad830c1276491ca9d79f189f47609884671db173400256b005f4955f7952f52a2aeb5834ad5fb4faf134 languageName: node linkType: hard -"pathval@npm:^1.1.1": - version: 1.1.1 - resolution: "pathval@npm:1.1.1" - checksum: 090e3147716647fb7fb5b4b8c8e5b55e5d0a6086d085b6cd23f3d3c01fcf0ff56fd3cc22f2f4a033bd2e46ed55d61ed8379e123b42afe7d531a2a5fc8bb556d6 +"pathval@npm:^2.0.0": + version: 2.0.0 + resolution: "pathval@npm:2.0.0" + checksum: 682b6a6289de7990909effef7dae9aa7bb6218c0426727bccf66a35b34e7bfbc65615270c5e44e3c9557a5cb44b1b9ef47fc3cb18bce6ad3ba92bcd28467ed7d languageName: node linkType: hard @@ -22866,17 +22798,6 @@ __metadata: languageName: node linkType: hard -"pkg-types@npm:^1.0.3, pkg-types@npm:^1.1.1": - version: 1.1.3 - resolution: "pkg-types@npm:1.1.3" - dependencies: - confbox: ^0.1.7 - mlly: ^1.7.1 - pathe: ^1.1.2 - checksum: 1085f1ed650db71d62ec9201d0ad4dc9455962b0e40d309e26bb8c01bb5b1560087e44d49e8e034497668c7cdde7cb5397995afa79c9fa1e2b35af9c9abafa82 - languageName: node - linkType: hard - "playwright-core@npm:1.37.1": version: 1.37.1 resolution: "playwright-core@npm:1.37.1" @@ -25459,7 +25380,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": +"signal-exit@npm:^4.0.1": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" checksum: 64c757b498cb8629ffa5f75485340594d2f8189e9b08700e69199069c8e3070fb3e255f7ab873c05dc0b3cec412aea7402e10a5990cb6a050bd33ba062a6c549 @@ -25562,7 +25483,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 @@ -25791,7 +25712,7 @@ __metadata: languageName: node linkType: hard -"std-env@npm:^3.3.3, std-env@npm:^3.5.0": +"std-env@npm:^3.7.0": version: 3.7.0 resolution: "std-env@npm:3.7.0" checksum: 4f489d13ff2ab838c9acd4ed6b786b51aa52ecacdfeaefe9275fcb220ff2ac80c6e95674723508fd29850a694569563a8caaaea738eb82ca16429b3a0b50e510 @@ -26177,13 +26098,6 @@ __metadata: languageName: node linkType: hard -"strip-final-newline@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-final-newline@npm:3.0.0" - checksum: 23ee263adfa2070cd0f23d1ac14e2ed2f000c9b44229aec9c799f1367ec001478469560abefd00c5c99ee6f0b31c137d53ec6029c53e9f32a93804e18c201050 - languageName: node - linkType: hard - "strip-indent@npm:^3.0.0": version: 3.0.0 resolution: "strip-indent@npm:3.0.0" @@ -26207,15 +26121,6 @@ __metadata: languageName: node linkType: hard -"strip-literal@npm:^2.0.0": - version: 2.1.0 - resolution: "strip-literal@npm:2.1.0" - dependencies: - js-tokens: ^9.0.0 - checksum: 37c2072634d2de11a3644fe1bcf4abd566d85e89f0d8e8b10d35d04e7bef962e7c112fbe5b805ce63e59dfacedc240356eeef57976351502966b7c64b742c6ac - languageName: node - linkType: hard - "strnum@npm:^1.0.5": version: 1.0.5 resolution: "strnum@npm:1.0.5" @@ -26572,6 +26477,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-encoding-polyfill@npm:0.6.7": version: 0.6.7 resolution: "text-encoding-polyfill@npm:0.6.7" @@ -26667,17 +26583,24 @@ __metadata: languageName: node linkType: hard -"tinybench@npm:^2.5.1": +"tinybench@npm:^2.9.0": version: 2.9.0 resolution: "tinybench@npm:2.9.0" checksum: 1ab00d7dfe0d1f127cbf00822bacd9024f7a50a3ecd1f354a8168e0b7d2b53a639a24414e707c27879d1adc0f5153141d51d76ebd7b4d37fe245e742e5d91fe8 languageName: node linkType: hard -"tinypool@npm:^0.8.3": - version: 0.8.4 - resolution: "tinypool@npm:0.8.4" - checksum: d40c40e062d5eeae85dadc39294dde6bc7b9a7a7cf0c972acbbe5a2b42491dfd4c48381c1e48bbe02aff4890e63de73d115b2e7de2ce4c81356aa5e654a43caf +"tinyexec@npm:^0.3.1": + version: 0.3.1 + resolution: "tinyexec@npm:0.3.1" + checksum: 691b531d464bdc09eeba934e43d8ac2a74c9d22a4bec9cd7f4991375c64e22712f7e5a95ba243a9369a478afd34d41171359012a2248ea49615cd2816ab12959 + languageName: node + linkType: hard + +"tinypool@npm:^1.0.1": + version: 1.0.1 + resolution: "tinypool@npm:1.0.1" + checksum: 5cd6b8cbccd9b88d461f400c9599e69f66563ddf75a2b8ab6b48250481f1b254d180a68ee735f379fa6eb88f11c3b1814735bb1f3306b1a860bf6d8f08074d6b languageName: node linkType: hard @@ -26688,10 +26611,17 @@ __metadata: languageName: node linkType: hard -"tinyspy@npm:^2.2.0": - version: 2.2.1 - resolution: "tinyspy@npm:2.2.1" - checksum: 170d6232e87f9044f537b50b406a38fbfd6f79a261cd12b92879947bd340939a833a678632ce4f5c4a6feab4477e9c21cd43faac3b90b68b77dd0536c4149736 +"tinyrainbow@npm:^1.2.0": + version: 1.2.0 + resolution: "tinyrainbow@npm:1.2.0" + checksum: d1e2cb5400032c0092be00e4a3da5450bea8b4fad58bfb5d3c58ca37ff5c5e252f7fcfb9af247914854af79c46014add9d1042fe044358c305a129ed55c8be35 + languageName: node + linkType: hard + +"tinyspy@npm:^3.0.2": + version: 3.0.2 + resolution: "tinyspy@npm:3.0.2" + checksum: 5db671b2ff5cd309de650c8c4761ca945459d7204afb1776db9a04fb4efa28a75f08517a8620c01ee32a577748802231ad92f7d5b194dc003ee7f987a2a06337 languageName: node linkType: hard @@ -26810,12 +26740,15 @@ __metadata: languageName: node linkType: hard -"ts-essentials@npm:^9.3.2": - version: 9.3.2 - resolution: "ts-essentials@npm:9.3.2" +"ts-essentials@npm:>=10.0.0": + version: 10.0.3 + resolution: "ts-essentials@npm:10.0.3" peerDependencies: - typescript: ">=4.1.0" - checksum: 88726b3f9bdc8f59f9a998bb925c989268d7e2f24d29ac13545e457d3ac8ee5472c78990eaa415b4c58244d55b989985a999345d6845055f5a58bb0f098c6f44 + typescript: ">=4.5.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 7919df8b46be0dcfaade3ddbc98c0fd5236585ea0b8a46c898eda1a7229c17673e0286179c6bece41c26e16e62037f0fda38f4f5246a61026698022427806354 languageName: node linkType: hard @@ -27027,13 +26960,6 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:^4.0.0, type-detect@npm:^4.1.0": - version: 4.1.0 - resolution: "type-detect@npm:4.1.0" - checksum: 3b32f873cd02bc7001b00a61502b7ddc4b49278aabe68d652f732e1b5d768c072de0bc734b427abf59d0520a5f19a2e07309ab921ef02018fa1cb4af155cdb37 - languageName: node - linkType: hard - "type-fest@npm:^0.16.0": version: 0.16.0 resolution: "type-fest@npm:0.16.0" @@ -27220,13 +27146,6 @@ __metadata: languageName: node linkType: hard -"ufo@npm:^1.5.3": - version: 1.5.4 - resolution: "ufo@npm:1.5.4" - checksum: f244703b7d4f9f0df4f9af23921241ab73410b591f4e5b39c23e3147f3159b139a4b1fb5903189c306129f7a16b55995dac0008e0fbae88a37c3e58cbc34d833 - languageName: node - linkType: hard - "uglify-es@npm:^3.1.9": version: 3.3.10 resolution: "uglify-es@npm:3.3.10" @@ -27845,7 +27764,7 @@ __metadata: languageName: node linkType: hard -"v8-to-istanbul@npm:^9.0.1, v8-to-istanbul@npm:^9.1.0": +"v8-to-istanbul@npm:^9.0.1": version: 9.1.0 resolution: "v8-to-istanbul@npm:9.1.0" dependencies: @@ -27956,18 +27875,17 @@ __metadata: languageName: node linkType: hard -"vite-node@npm:1.6.0": - version: 1.6.0 - resolution: "vite-node@npm:1.6.0" +"vite-node@npm:2.1.4": + version: 2.1.4 + resolution: "vite-node@npm:2.1.4" dependencies: cac: ^6.7.14 - debug: ^4.3.4 - pathe: ^1.1.1 - picocolors: ^1.0.0 + debug: ^4.3.7 + pathe: ^1.1.2 vite: ^5.0.0 bin: vite-node: vite-node.mjs - checksum: ce111c5c7a4cf65b722baa15cbc065b7bfdbf1b65576dd6372995f6a72b2b93773ec5df59f6c5f08cfe1284806597b44b832efcea50d5971102428159ff4379f + checksum: 2ab745aa9f1154e6dde4c47647b2785968828f9d5c46ae06facea35a276c89ab550346c4ec3116d1d095b5cb96aeef668ed8fbd4d26477c6dd15fa598bbc1098 languageName: node linkType: hard @@ -28014,47 +27932,47 @@ __metadata: languageName: node linkType: hard -"vitest-mock-extended@npm:^1.2.1": - version: 1.2.1 - resolution: "vitest-mock-extended@npm:1.2.1" +"vitest-mock-extended@npm:^2.0.2": + version: 2.0.2 + resolution: "vitest-mock-extended@npm:2.0.2" dependencies: - ts-essentials: ^9.3.2 + ts-essentials: ">=10.0.0" peerDependencies: typescript: 3.x || 4.x || 5.x - vitest: ">=0.31.1" - checksum: d3dc3940513cc053166bc912b0b9fdda0b073c5a5d2c2deba4f87ddae7a3b8c53a31227030a9f0eeb9f48c443b6978c451946ef0d36eea6976ef77e5bb4d6c04 + vitest: ">=2.0.0" + checksum: 700d58a002273fa1ac4d53a716d78d25a056ba2482ffeb699ffee1f9ad20007918040f3154af3b03a5c59ac4133067888998822acc63c58ac1cc2802c01fb7fd languageName: node linkType: hard -"vitest@npm:^1.0.0": - version: 1.6.0 - resolution: "vitest@npm:1.6.0" - dependencies: - "@vitest/expect": 1.6.0 - "@vitest/runner": 1.6.0 - "@vitest/snapshot": 1.6.0 - "@vitest/spy": 1.6.0 - "@vitest/utils": 1.6.0 - acorn-walk: ^8.3.2 - chai: ^4.3.10 - debug: ^4.3.4 - execa: ^8.0.1 - local-pkg: ^0.5.0 - magic-string: ^0.30.5 - pathe: ^1.1.1 - picocolors: ^1.0.0 - std-env: ^3.5.0 - strip-literal: ^2.0.0 - tinybench: ^2.5.1 - tinypool: ^0.8.3 +"vitest@npm:^2.1.4": + version: 2.1.4 + resolution: "vitest@npm:2.1.4" + dependencies: + "@vitest/expect": 2.1.4 + "@vitest/mocker": 2.1.4 + "@vitest/pretty-format": ^2.1.4 + "@vitest/runner": 2.1.4 + "@vitest/snapshot": 2.1.4 + "@vitest/spy": 2.1.4 + "@vitest/utils": 2.1.4 + chai: ^5.1.2 + debug: ^4.3.7 + expect-type: ^1.1.0 + magic-string: ^0.30.12 + pathe: ^1.1.2 + std-env: ^3.7.0 + tinybench: ^2.9.0 + tinyexec: ^0.3.1 + tinypool: ^1.0.1 + tinyrainbow: ^1.2.0 vite: ^5.0.0 - vite-node: 1.6.0 - why-is-node-running: ^2.2.2 + vite-node: 2.1.4 + why-is-node-running: ^2.3.0 peerDependencies: "@edge-runtime/vm": "*" "@types/node": ^18.0.0 || >=20.0.0 - "@vitest/browser": 1.6.0 - "@vitest/ui": 1.6.0 + "@vitest/browser": 2.1.4 + "@vitest/ui": 2.1.4 happy-dom: "*" jsdom: "*" peerDependenciesMeta: @@ -28072,7 +27990,7 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: a9b9b97e5685d630e5d8d221e6d6cd2e1e9b5b2dd61e82042839ef11549c8d2d780cf696307de406dce804bf41c1219398cb20b4df570b3b47ad1e53af6bfe51 + checksum: 6b38640d0d5340d80da82d0fcc075d340a30b4c89066057e873da1207f9d5856e7e8f3282cca4ad27861b73df2465aad58643e078c1cc5e36de3968547f45bd7 languageName: node linkType: hard @@ -28335,15 +28253,15 @@ __metadata: languageName: node linkType: hard -"why-is-node-running@npm:^2.2.2": - version: 2.2.2 - resolution: "why-is-node-running@npm:2.2.2" +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" dependencies: siginfo: ^2.0.0 stackback: 0.0.2 bin: why-is-node-running: cli.js - checksum: 50820428f6a82dfc3cbce661570bcae9b658723217359b6037b67e495255409b4c8bc7931745f5c175df71210450464517cab32b2f7458ac9c40b4925065200a + checksum: 58ebbf406e243ace97083027f0df7ff4c2108baf2595bb29317718ef207cc7a8104e41b711ff65d6fa354f25daa8756b67f2f04931a4fd6ba9d13ae8197496fb languageName: node linkType: hard @@ -28462,7 +28380,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.12.1, ws@npm:^8.14.2": +"ws@npm:^8.12.1": version: 8.14.2 resolution: "ws@npm:8.14.2" peerDependencies: @@ -28701,13 +28619,6 @@ __metadata: languageName: node linkType: hard -"yocto-queue@npm:^1.0.0": - version: 1.0.0 - resolution: "yocto-queue@npm:1.0.0" - checksum: 2cac84540f65c64ccc1683c267edce396b26b1e931aa429660aefac8fbe0188167b7aee815a3c22fa59a28a58d898d1a2b1825048f834d8d629f4c2a5d443801 - languageName: node - linkType: hard - "zod-validation-error@npm:^2.1.0": version: 2.1.0 resolution: "zod-validation-error@npm:2.1.0"