diff --git a/.changeset/nasty-steaks-yell.md b/.changeset/nasty-steaks-yell.md new file mode 100644 index 000000000..ed471159e --- /dev/null +++ b/.changeset/nasty-steaks-yell.md @@ -0,0 +1,5 @@ +--- +'@powersync/node': minor +--- + +Switch to undici WebSocket for Dispatcher and diagnostics_channel support. This now adds support for the `ALL_PROXY` environment variable by default, as well as `WSS_PROXY` for websocket connections. diff --git a/.changeset/swift-waves-tease.md b/.changeset/swift-waves-tease.md new file mode 100644 index 000000000..654754043 --- /dev/null +++ b/.changeset/swift-waves-tease.md @@ -0,0 +1,5 @@ +--- +'@powersync/common': minor +--- + +Preserve more details on websocket errors. diff --git a/demos/example-node/.env b/demos/example-node/.env index 170d91d37..6ccb07d23 100644 --- a/demos/example-node/.env +++ b/demos/example-node/.env @@ -1,2 +1,4 @@ BACKEND=http://localhost:6060 SYNC_SERVICE=http://localhost:8080 +POWERSYNC_TOKEN= +POWERSYNC_DEBUG=1 \ No newline at end of file diff --git a/demos/example-node/package.json b/demos/example-node/package.json index cdabd63ce..e1623e7ed 100644 --- a/demos/example-node/package.json +++ b/demos/example-node/package.json @@ -7,11 +7,12 @@ "scripts": { "build": "tsc -b", "watch": "tsc -b -w", - "start": "node --loader ts-node/esm -r dotenv/config src/main.ts" + "start": "node --import ./register.mjs src/main.ts" }, "dependencies": { "@powersync/node": "workspace:*", - "dotenv": "^16.4.7" + "dotenv": "^16.4.7", + "undici": "^7.10.0" }, "devDependencies": { "ts-node": "^10.9.2", diff --git a/demos/example-node/register.mjs b/demos/example-node/register.mjs new file mode 100644 index 000000000..9eecf7f6d --- /dev/null +++ b/demos/example-node/register.mjs @@ -0,0 +1,6 @@ +// For cli usage: node --import ./register.mjs src/main.ts +import { register } from 'node:module'; +import { pathToFileURL } from 'node:url'; +import 'dotenv/config'; + +register('ts-node/esm', pathToFileURL('./')); diff --git a/demos/example-node/src/UndiciDiagnostics.ts b/demos/example-node/src/UndiciDiagnostics.ts new file mode 100644 index 000000000..6f250f49e --- /dev/null +++ b/demos/example-node/src/UndiciDiagnostics.ts @@ -0,0 +1,151 @@ +import * as diagnostics_channel from 'node:diagnostics_channel'; +import type { DiagnosticsChannel } from 'undici'; + +/** + * Enable Undici diagnostics channel instrumentation for detailed connection and request logging. + * + * This includes fetch requests and websocket connections. + * + * Usage: enableUncidiDiagnostics(); + */ +export function enableUncidiDiagnostics() { + new UndiciDiagnostics().enable(); +} + +class UndiciDiagnostics { + private requestCounter: number = 0; + private activeRequests: WeakMap = new WeakMap(); + + enable() { + // Available events are documented here: + // https://github.com/nodejs/undici/blob/main/docs/docs/api/DiagnosticsChannel.md + + diagnostics_channel.subscribe('undici:request:create', (message: DiagnosticsChannel.RequestCreateMessage) => { + const requestId = ++this.requestCounter; + const request = message.request; + this.activeRequests.set(message.request, requestId); + + console.log(`🔄 [DIAG-${requestId}] REQUEST CREATE:`, { + host: request.origin, + path: request.path, + method: request.method, + headers: formatHeaders(request.headers), + contentType: (request as any).contentType, + contentLength: (request as any).contentLength + }); + }); + + diagnostics_channel.subscribe('undici:request:bodySent', (message: DiagnosticsChannel.RequestBodySentMessage) => { + const requestId = this.activeRequests.get(message.request); + console.log(`📤 [DIAG-${requestId}] REQUEST BODY SENT`); + }); + + diagnostics_channel.subscribe('undici:request:headers', (message: DiagnosticsChannel.RequestHeadersMessage) => { + const requestId = this.activeRequests.get(message.request); + console.log(`📥 [DIAG-${requestId}] RESPONSE HEADERS:`, { + statusCode: message.response.statusCode, + statusText: message.response.statusText, + headers: formatHeaders(message.response.headers) + }); + }); + + diagnostics_channel.subscribe('undici:request:trailers', (message: DiagnosticsChannel.RequestTrailersMessage) => { + const requestId = this.activeRequests.get(message.request); + console.log(`🏁 [DIAG-${requestId}] REQUEST TRAILERS:`, { + trailers: message.trailers + }); + }); + + diagnostics_channel.subscribe('undici:request:error', (message: DiagnosticsChannel.RequestErrorMessage) => { + const requestId = this.activeRequests.get(message.request); + console.log(`❌ [DIAG-${requestId}] REQUEST ERROR:`, { + error: message.error + }); + + // Clean up tracking + this.activeRequests.delete(message.request); + }); + + // Client connection events + diagnostics_channel.subscribe( + 'undici:client:sendHeaders', + (message: DiagnosticsChannel.ClientSendHeadersMessage) => { + console.log(`📡 [DIAG] CLIENT SEND HEADERS:`, { + headers: formatHeaders(message.headers) + }); + } + ); + + diagnostics_channel.subscribe( + 'undici:client:beforeConnect', + (message: DiagnosticsChannel.ClientBeforeConnectMessage) => { + console.log(`🔌 [DIAG] CLIENT BEFORE CONNECT:`, { + connectParams: message.connectParams + }); + } + ); + + diagnostics_channel.subscribe('undici:client:connected', (message: DiagnosticsChannel.ClientConnectedMessage) => { + console.log(`✅ [DIAG] CLIENT CONNECTED:`, { + connectParams: message.connectParams, + connector: message.connector?.name, + socket: { + localAddress: message.socket?.localAddress, + localPort: message.socket?.localPort, + remoteAddress: message.socket?.remoteAddress, + remotePort: message.socket?.remotePort + } + }); + }); + + diagnostics_channel.subscribe( + 'undici:client:connectError', + (message: DiagnosticsChannel.ClientConnectErrorMessage) => { + console.log(`❌ [DIAG] CLIENT CONNECT ERROR:`, { + connectParams: message.connectParams, + error: message.error + }); + } + ); + + // WebSocket events + diagnostics_channel.subscribe('undici:websocket:open', (message: any) => { + console.log(`🌐 [DIAG] WEBSOCKET OPEN:`, { + address: message.address, + protocol: message.protocol, + extensions: message.extensions + }); + }); + + diagnostics_channel.subscribe('undici:websocket:close', (message: any) => { + console.log(`🌐 [DIAG] WEBSOCKET CLOSE:`, { + websocket: message.websocket?.url, + code: message.code, + reason: message.reason + }); + }); + + diagnostics_channel.subscribe('undici:websocket:socket_error', (message: any) => { + console.log(`❌ [DIAG] WEBSOCKET SOCKET ERROR:`, { + websocket: message.websocket?.url, + error: message.error + }); + }); + } +} + +function formatHeaders(headers: any[] | string | undefined) { + if (typeof headers === 'string') { + return headers; + } + + return headers?.map((header) => { + if (typeof header == 'string') { + return header; + } else if (Buffer.isBuffer(header)) { + return header.toString('utf-8'); + } else { + return header; + } + }); +} diff --git a/demos/example-node/src/main.ts b/demos/example-node/src/main.ts index 95abbfad1..c68c01360 100644 --- a/demos/example-node/src/main.ts +++ b/demos/example-node/src/main.ts @@ -4,13 +4,20 @@ import repl_factory from 'node:repl'; import { createBaseLogger, createLogger, PowerSyncDatabase, SyncStreamConnectionMethod } from '@powersync/node'; import { exit } from 'node:process'; import { AppSchema, DemoConnector } from './powersync.js'; +import { enableUncidiDiagnostics } from './UndiciDiagnostics.js'; const main = async () => { const baseLogger = createBaseLogger(); const logger = createLogger('PowerSyncDemo'); - baseLogger.useDefaults({ defaultLevel: logger.WARN }); + const debug = process.env.POWERSYNC_DEBUG == '1'; + baseLogger.useDefaults({ defaultLevel: debug ? logger.TRACE : logger.WARN }); - if (!('BACKEND' in process.env) || !('SYNC_SERVICE' in process.env)) { + // Enable detailed request/response logging for debugging purposes. + if (debug) { + enableUncidiDiagnostics(); + } + + if (!('SYNC_SERVICE' in process.env)) { console.warn( 'Set the BACKEND and SYNC_SERVICE environment variables to point to a sync service and a running demo backend.' ); @@ -26,7 +33,24 @@ const main = async () => { }); console.log(await db.get('SELECT powersync_rs_version();')); - await db.connect(new DemoConnector(), { connectionMethod: SyncStreamConnectionMethod.HTTP }); + await db.connect(new DemoConnector(), { + connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET + }); + // Example using a proxy agent for more control over the connection: + // const proxyAgent = new (await import('undici')).ProxyAgent({ + // uri: 'http://localhost:8080', + // requestTls: { + // ca: '' + // }, + // proxyTls: { + // ca: '' + // } + // }); + // await db.connect(new DemoConnector(), { + // connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET, + // dispatcher: proxyAgent + // }); + await db.waitForFirstSync(); console.log('First sync complete!'); diff --git a/demos/example-node/src/powersync.ts b/demos/example-node/src/powersync.ts index cee9e1825..2896a8dc9 100644 --- a/demos/example-node/src/powersync.ts +++ b/demos/example-node/src/powersync.ts @@ -2,6 +2,13 @@ import { AbstractPowerSyncDatabase, column, PowerSyncBackendConnector, Schema, T export class DemoConnector implements PowerSyncBackendConnector { async fetchCredentials() { + if (process.env.POWERSYNC_TOKEN) { + return { + endpoint: process.env.SYNC_SERVICE!, + token: process.env.POWERSYNC_TOKEN + }; + } + const response = await fetch(`${process.env.BACKEND}/api/auth/token`); if (response.status != 200) { throw 'Could not fetch token'; diff --git a/packages/common/src/client/sync/stream/AbstractRemote.ts b/packages/common/src/client/sync/stream/AbstractRemote.ts index 0ccc8503d..1d0b49931 100644 --- a/packages/common/src/client/sync/stream/AbstractRemote.ts +++ b/packages/common/src/client/sync/stream/AbstractRemote.ts @@ -312,18 +312,12 @@ export abstract class AbstractRemote { // automatically as a header. const userAgent = this.getUserAgent(); - let socketCreationError: Error | undefined; - + const url = this.options.socketUrlTransformer(request.url); const connector = new RSocketConnector({ transport: new WebsocketClientTransport({ - url: this.options.socketUrlTransformer(request.url), + url, wsCreator: (url) => { - const s = this.createSocket(url); - s.addEventListener('error', (e: Event) => { - socketCreationError = new Error('Failed to create connection to websocket: ', (e.target as any).url ?? ''); - this.logger.warn('Socket error', e); - }); - return s; + return this.createSocket(url); } }), setup: { @@ -347,11 +341,8 @@ export abstract class AbstractRemote { try { rsocket = await connector.connect(); } catch (ex) { - /** - * On React native the connection exception can be `undefined` this causes issues - * with detecting the exception inside async-mutex - */ - throw new Error(`Could not connect to PowerSync instance: ${JSON.stringify(ex ?? socketCreationError)}`); + this.logger.error(`Failed to connect WebSocket`, ex); + throw ex; } const stream = new DataStream({ diff --git a/packages/common/src/client/sync/stream/WebsocketClientTransport.ts b/packages/common/src/client/sync/stream/WebsocketClientTransport.ts index 55a09c863..12d3a2ed8 100644 --- a/packages/common/src/client/sync/stream/WebsocketClientTransport.ts +++ b/packages/common/src/client/sync/stream/WebsocketClientTransport.ts @@ -44,7 +44,17 @@ export class WebsocketClientTransport implements ClientTransport { const errorListener = (ev: ErrorEvent) => { removeListeners(); - reject(ev.error); + // We add a default error in that case. + if (ev.error != null) { + // undici typically provides an error object + reject(ev.error); + } else if (ev.message != null) { + // React Native typically does not provide an error object, but does provide a message + reject(new Error(`Failed to create websocket connection: ${ev.message}`)); + } else { + // Browsers often provide no details at all + reject(new Error(`Failed to create websocket connection to ${this.url}`)); + } }; /** diff --git a/packages/node/package.json b/packages/node/package.json index 77a644452..410d32183 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -53,9 +53,7 @@ "async-lock": "^1.4.0", "bson": "^6.6.0", "comlink": "^4.4.2", - "proxy-agent": "^6.5.0", - "undici": "^7.8.0", - "ws": "^8.18.1" + "undici": "^7.10.0" }, "devDependencies": { "@powersync/drizzle-driver": "workspace:*", diff --git a/packages/node/src/db/PowerSyncDatabase.ts b/packages/node/src/db/PowerSyncDatabase.ts index 0c4501678..e4ce8c481 100644 --- a/packages/node/src/db/PowerSyncDatabase.ts +++ b/packages/node/src/db/PowerSyncDatabase.ts @@ -13,10 +13,9 @@ import { SQLOpenFactory } from '@powersync/common'; -import { NodeRemote } from '../sync/stream/NodeRemote.js'; +import { NodeCustomConnectionOptions, NodeRemote } from '../sync/stream/NodeRemote.js'; import { NodeStreamingSyncImplementation } from '../sync/stream/NodeStreamingSyncImplementation.js'; -import { Dispatcher } from 'undici'; import { BetterSQLite3DBAdapter } from './BetterSQLite3DBAdapter.js'; import { NodeSQLOpenOptions } from './options.js'; @@ -30,13 +29,7 @@ export type NodePowerSyncDatabaseOptions = PowerSyncDatabaseOptions & { remoteOptions?: Partial; }; -export type NodeAdditionalConnectionOptions = AdditionalConnectionOptions & { - /** - * Optional custom dispatcher for HTTP connections (e.g. using undici). - * Only used when the connection method is SyncStreamConnectionMethod.HTTP - */ - dispatcher?: Dispatcher; -}; +export type NodeAdditionalConnectionOptions = AdditionalConnectionOptions & NodeCustomConnectionOptions; export type NodePowerSyncConnectionOptions = PowerSyncConnectionOptions & NodeAdditionalConnectionOptions; @@ -76,7 +69,7 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase { connect( connector: PowerSyncBackendConnector, - options?: PowerSyncConnectionOptions & { dispatcher?: Dispatcher } + options?: PowerSyncConnectionOptions & NodeCustomConnectionOptions ): Promise { return super.connect(connector, options); } diff --git a/packages/node/src/sync/stream/ErrorRecordingDispatcher.ts b/packages/node/src/sync/stream/ErrorRecordingDispatcher.ts new file mode 100644 index 000000000..8e9ccdaf7 --- /dev/null +++ b/packages/node/src/sync/stream/ErrorRecordingDispatcher.ts @@ -0,0 +1,58 @@ +import { Dispatcher } from 'undici'; + +/** + * A simple dispatcher wrapper that only records the last error. + * Everything else passes straight through to the original handler. + */ +export class ErrorRecordingDispatcher extends Dispatcher { + private targetDispatcher: Dispatcher; + public onError: ((error: Error) => void) | undefined; + + constructor(targetDispatcher: Dispatcher) { + super(); + this.targetDispatcher = targetDispatcher; + } + + dispatch(opts: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean { + // Create a simple wrapper that only intercepts errors + const errorRecordingHandler: Dispatcher.DispatchHandler = { + // New API methods (preferred) + onRequestStart: handler.onRequestStart?.bind(handler), + onRequestUpgrade: handler.onRequestUpgrade?.bind(handler), + onResponseStart: handler.onResponseStart?.bind(handler), + onResponseData: handler.onResponseData?.bind(handler), + onResponseEnd: handler.onResponseEnd?.bind(handler), + + onResponseError: (controller: any, error: Error) => { + this.onError?.(error); + // Pass through to original handler + return handler.onResponseError?.(controller, error); + }, + + // Legacy API methods (for backward compatibility) + onConnect: handler.onConnect?.bind(handler), + onUpgrade: handler.onUpgrade?.bind(handler), + onHeaders: handler.onHeaders?.bind(handler), + onData: handler.onData?.bind(handler), + onComplete: handler.onComplete?.bind(handler), + + onError: (error: Error) => { + this.onError?.(error); + + // Pass through to original handler + return handler.onError?.(error); + } + }; + + // Delegate to the target dispatcher with our simple error-recording handler + return this.targetDispatcher.dispatch(opts, errorRecordingHandler); + } + + async close(): Promise { + return this.targetDispatcher.close(); + } + + async destroy(): Promise { + return this.targetDispatcher.destroy(); + } +} diff --git a/packages/node/src/sync/stream/NodeRemote.ts b/packages/node/src/sync/stream/NodeRemote.ts index 3e8e3e18d..8074aaf64 100644 --- a/packages/node/src/sync/stream/NodeRemote.ts +++ b/packages/node/src/sync/stream/NodeRemote.ts @@ -11,9 +11,15 @@ import { RemoteConnector } from '@powersync/common'; import { BSON } from 'bson'; -import Agent from 'proxy-agent'; -import { EnvHttpProxyAgent, Dispatcher } from 'undici'; -import { WebSocket } from 'ws'; +import { + Dispatcher, + EnvHttpProxyAgent, + ErrorEvent, + getGlobalDispatcher, + ProxyAgent, + WebSocket as UndiciWebSocket +} from 'undici'; +import { ErrorRecordingDispatcher } from './ErrorRecordingDispatcher.js'; export const STREAMING_POST_TIMEOUT_MS = 30_000; @@ -23,36 +29,77 @@ class NodeFetchProvider extends FetchImplementationProvider { } } -export type NodeRemoteOptions = AbstractRemoteOptions & { +export type NodeCustomConnectionOptions = { + /** + * Optional custom dispatcher for HTTP or WEB_SOCKET connections. + * + * This can be used to customize proxy usage (using undici ProxyAgent), + * or other connection options. + */ dispatcher?: Dispatcher; }; +export type NodeRemoteOptions = AbstractRemoteOptions & NodeCustomConnectionOptions; + export class NodeRemote extends AbstractRemote { + private wsDispatcher: Dispatcher | undefined; + constructor( protected connector: RemoteConnector, protected logger: ILogger = DEFAULT_REMOTE_LOGGER, options?: Partial ) { - // EnvHttpProxyAgent automatically uses relevant env vars for HTTP - const dispatcher = options?.dispatcher ?? new EnvHttpProxyAgent(); + const fetchDispatcher = options?.dispatcher ?? defaultFetchDispatcher(); super(connector, logger, { fetchImplementation: options?.fetchImplementation ?? new NodeFetchProvider(), fetchOptions: { - dispatcher + dispatcher: fetchDispatcher }, ...(options ?? {}) }); + + this.wsDispatcher = options?.dispatcher; } protected createSocket(url: string): globalThis.WebSocket { - return new WebSocket(url, { - // Automatically uses relevant env vars for web sockets - agent: new Agent.ProxyAgent(), + // Create dedicated dispatcher for this WebSocket + const baseDispatcher = this.getWebsocketDispatcher(url); + const errorRecordingDispatcher = new ErrorRecordingDispatcher(baseDispatcher); + + // Create WebSocket with dedicated dispatcher + const ws = new UndiciWebSocket(url, { + dispatcher: errorRecordingDispatcher, headers: { 'User-Agent': this.getUserAgent() } - }) as any as globalThis.WebSocket; // This is compatible in Node environments + }); + + errorRecordingDispatcher.onError = (error: Error) => { + // When we receive an error from the Dispatcher, emit the event on the websocket. + // This will take precedence over the WebSocket's own error event, giving more details on what went wrong. + const event = new ErrorEvent('error', { + error, + message: error.message + }); + ws.dispatchEvent(event); + }; + + return ws as globalThis.WebSocket; + } + + protected getWebsocketDispatcher(url: string) { + if (this.wsDispatcher != null) { + return this.wsDispatcher; + } + + const protocol = new URL(url).protocol.replace(':', ''); + const proxy = getProxyForProtocol(protocol); + if (proxy != null) { + return new ProxyAgent(proxy); + } else { + return getGlobalDispatcher(); + } } getUserAgent(): string { @@ -68,3 +115,21 @@ export class NodeRemote extends AbstractRemote { return BSON; } } + +function defaultFetchDispatcher(): Dispatcher { + // EnvHttpProxyAgent automatically uses HTTP_PROXY, HTTPS_PROXY and NO_PROXY env vars by default. + // We add ALL_PROXY support. + return new EnvHttpProxyAgent({ + httpProxy: getProxyForProtocol('http'), + httpsProxy: getProxyForProtocol('https') + }); +} + +function getProxyForProtocol(protocol: string): string | undefined { + return ( + process.env[`${protocol.toLowerCase()}_proxy`] ?? + process.env[`${protocol.toUpperCase()}_PROXY`] ?? + process.env[`all_proxy`] ?? + process.env[`ALL_PROXY`] + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6898087a6..81dd1b131 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -243,16 +243,16 @@ importers: dependencies: '@capacitor/android': specifier: ^6.0.0 - version: 6.2.1(@capacitor/core@7.2.0) + version: 6.2.1(@capacitor/core@7.3.0) '@capacitor/core': specifier: latest - version: 7.2.0 + version: 7.3.0 '@capacitor/ios': specifier: ^6.0.0 - version: 6.2.1(@capacitor/core@7.2.0) + version: 6.2.1(@capacitor/core@7.3.0) '@capacitor/splash-screen': specifier: latest - version: 7.0.1(@capacitor/core@7.2.0) + version: 7.0.1(@capacitor/core@7.3.0) '@journeyapps/wa-sqlite': specifier: ^1.2.0 version: 1.2.4 @@ -578,6 +578,9 @@ importers: dotenv: specifier: ^16.4.7 version: 16.5.0 + undici: + specifier: ^7.10.0 + version: 7.10.0 devDependencies: ts-node: specifier: ^10.9.2 @@ -1777,15 +1780,9 @@ importers: comlink: specifier: ^4.4.2 version: 4.4.2 - proxy-agent: - specifier: ^6.5.0 - version: 6.5.0 undici: - specifier: ^7.8.0 + specifier: ^7.10.0 version: 7.10.0 - ws: - specifier: ^8.18.1 - version: 8.18.2 devDependencies: '@powersync/drizzle-driver': specifier: workspace:* @@ -3419,8 +3416,8 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - '@capacitor/core@7.2.0': - resolution: {integrity: sha512-2zCnA6RJeZ9ec4470o8QMZEQTWpekw9FNoqm5TLc10jeCrhvHVI8MPgxdZVc3mOdFlyieYu4AS1fNxSqbS57Pw==} + '@capacitor/core@7.3.0': + resolution: {integrity: sha512-t/DdTyBchQ2eAZuCmAARlqQsrEm0WyeNwh5zeRuv+cR6gnAsw+86/EWvJ/em5dTnZyaqEy8vlmOMdWarrUbnuQ==} '@capacitor/ios@6.2.1': resolution: {integrity: sha512-tbMlQdQjxe1wyaBvYVU1yTojKJjgluZQsJkALuJxv/6F8QTw5b6vd7X785O/O7cMpIAZfUWo/vtAHzFkRV+kXw==} @@ -8622,9 +8619,6 @@ packages: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} - '@tootallnate/quickjs-emscripten@0.23.0': - resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -9862,10 +9856,6 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} - ast-types@0.13.4: - resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} - engines: {node: '>=4'} - ast-types@0.15.2: resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} engines: {node: '>=4'} @@ -10078,10 +10068,6 @@ packages: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} - basic-ftp@5.0.5: - resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} - engines: {node: '>=10.0.0'} - batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} @@ -11184,10 +11170,6 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} - data-uri-to-buffer@6.0.2: - resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} - engines: {node: '>= 14'} - data-urls@3.0.2: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} engines: {node: '>=12'} @@ -11364,10 +11346,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - degenerator@5.0.1: - resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} - engines: {node: '>= 14'} - del-cli@5.1.0: resolution: {integrity: sha512-xwMeh2acluWeccsfzE7VLsG3yTr7nWikbfw+xhMnpRrF15pGSkw+3/vJZWlGoE4I86UiLRNHicmKt4tkIX9Jtg==} engines: {node: '>=14.16'} @@ -12986,10 +12964,6 @@ packages: get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - get-uri@6.0.4: - resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} - engines: {node: '>= 14'} - getenv@1.0.0: resolution: {integrity: sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==} engines: {node: '>=6'} @@ -15682,10 +15656,6 @@ packages: nested-error-stacks@2.0.1: resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} - netmask@2.0.2: - resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} - engines: {node: '>= 0.4.0'} - next@14.2.3: resolution: {integrity: sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==} engines: {node: '>=18.17.0'} @@ -16199,14 +16169,6 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - pac-proxy-agent@7.2.0: - resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} - engines: {node: '>= 14'} - - pac-resolver@7.0.1: - resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} - engines: {node: '>= 14'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -17120,10 +17082,6 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - proxy-agent@6.5.0: - resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} - engines: {node: '>= 14'} - proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -22318,9 +22276,9 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 19.0.0 - '@capacitor/android@6.2.1(@capacitor/core@7.2.0)': + '@capacitor/android@6.2.1(@capacitor/core@7.3.0)': dependencies: - '@capacitor/core': 7.2.0 + '@capacitor/core': 7.3.0 '@capacitor/cli@6.2.1': dependencies: @@ -22344,17 +22302,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@capacitor/core@7.2.0': + '@capacitor/core@7.3.0': dependencies: tslib: 2.8.1 - '@capacitor/ios@6.2.1(@capacitor/core@7.2.0)': + '@capacitor/ios@6.2.1(@capacitor/core@7.3.0)': dependencies: - '@capacitor/core': 7.2.0 + '@capacitor/core': 7.3.0 - '@capacitor/splash-screen@7.0.1(@capacitor/core@7.2.0)': + '@capacitor/splash-screen@7.0.1(@capacitor/core@7.3.0)': dependencies: - '@capacitor/core': 7.2.0 + '@capacitor/core': 7.3.0 '@changesets/apply-release-plan@7.0.12': dependencies: @@ -30649,8 +30607,6 @@ snapshots: '@tootallnate/once@2.0.0': {} - '@tootallnate/quickjs-emscripten@0.23.0': {} - '@trysound/sax@0.2.0': {} '@tsconfig/node10@1.0.11': {} @@ -32328,10 +32284,6 @@ snapshots: ast-types-flow@0.0.8: {} - ast-types@0.13.4: - dependencies: - tslib: 2.8.1 - ast-types@0.15.2: dependencies: tslib: 2.8.1 @@ -32638,8 +32590,6 @@ snapshots: dependencies: safe-buffer: 5.1.2 - basic-ftp@5.0.5: {} - batch@0.6.1: {} beasties@0.3.2: @@ -34037,8 +33987,6 @@ snapshots: data-uri-to-buffer@4.0.1: {} - data-uri-to-buffer@6.0.2: {} - data-urls@3.0.2: dependencies: abab: 2.0.6 @@ -34181,12 +34129,6 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - degenerator@5.0.1: - dependencies: - ast-types: 0.13.4 - escodegen: 2.1.0 - esprima: 4.0.1 - del-cli@5.1.0: dependencies: del: 7.1.0 @@ -36595,14 +36537,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - get-uri@6.0.4: - dependencies: - basic-ftp: 5.0.5 - data-uri-to-buffer: 6.0.2 - debug: 4.4.1(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - getenv@1.0.0: {} github-slugger@1.5.0: {} @@ -40803,8 +40737,6 @@ snapshots: nested-error-stacks@2.0.1: {} - netmask@2.0.2: {} - next@14.2.3(@babel/core@7.26.10)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.89.1): dependencies: '@next/env': 14.2.3 @@ -41360,24 +41292,6 @@ snapshots: p-try@2.2.0: {} - pac-proxy-agent@7.2.0: - dependencies: - '@tootallnate/quickjs-emscripten': 0.23.0 - agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) - get-uri: 6.0.4 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - pac-resolver: 7.0.1 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - - pac-resolver@7.0.1: - dependencies: - degenerator: 5.0.1 - netmask: 2.0.2 - package-json-from-dist@1.0.1: {} package-json@8.1.1: @@ -42370,19 +42284,6 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-agent@6.5.0: - dependencies: - agent-base: 7.1.3 - debug: 4.4.1(supports-color@8.1.1) - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - lru-cache: 7.18.3 - pac-proxy-agent: 7.2.0 - proxy-from-env: 1.1.0 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - proxy-from-env@1.1.0: {} prr@1.0.1: