From 3a50188a787c2bf9c949e4a1b4ccce3f7f038375 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 7 May 2026 14:18:11 -0300 Subject: [PATCH 1/3] refactor(client): drop Tracker.autorun from 3 easy autorun bridges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace `Tracker.autorun` with direct event/store subscriptions at three contained call sites: - `meteorBackedSdk.onMeteorStatusChange` (`apps/meteor/client/lib/sdk/meteorBackedSdk.ts`) listens on `Meteor.connection._stream`'s `'connected'`/`'disconnect'`/`'reset'` events instead of riding Meteor.status's Tracker reactivity. Same downstream `sdk.connection.on('connected'/'disconnected'/'connection')` API. - `CachedStore` reconnection sync (`apps/meteor/client/lib/cachedStores/CachedStore.ts`) hooks `sdk.connection.on('connection', …)` directly. The `Tracker.nonreactive` initial-offline read becomes a synchronous `sdk.connection.status` check. `reconnectionComputation` field renamed to `reconnectionUnsubscribe`. - `createComposerAPI` formatting watcher (`apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts`) subscribes to `settings.observe('*', recompute)` instead of an autorun that filters `formattingButtons[].condition()`. Coarse-grained but cheap. Out of scope (have own follow-up plans): `userAndUsers.ts`'s userId bridge (ordering-sensitive), `createReactiveSubscriptionFactory` + React provider stack, `watch.ts` (zustand→Tracker bridge), Minimongo's `Cursor.ts`/`ObserveHandle.ts`, `stubMeteorStream.ts`. Net: 3 of the 10 client `meteor/tracker` imports gone; 7 remain in those out-of-scope files. --- .changeset/sdk-tracker-easy-autoruns.md | 5 ++++ .../client/messageBox/createComposerAPI.ts | 17 +++++++----- .../client/lib/cachedStores/CachedStore.ts | 19 +++++++------ apps/meteor/client/lib/sdk/meteorBackedSdk.ts | 27 +++++++++++++------ 4 files changed, 44 insertions(+), 24 deletions(-) create mode 100644 .changeset/sdk-tracker-easy-autoruns.md diff --git a/.changeset/sdk-tracker-easy-autoruns.md b/.changeset/sdk-tracker-easy-autoruns.md new file mode 100644 index 0000000000000..0398696d842e6 --- /dev/null +++ b/.changeset/sdk-tracker-easy-autoruns.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Drop Meteor `Tracker.autorun` from three client call sites in favour of direct event subscriptions. `meteorBackedSdk`'s connection-status bridge now listens on `Meteor.connection._stream`'s low-level `'connected'`/`'disconnect'`/`'reset'` events instead of riding `Meteor.status()`'s reactive layer; `CachedStore`'s post-reconnect cache sync now hooks `sdk.connection.on('connection', …)` directly; `createComposerAPI`'s formatting-button watcher subscribes to `settings.observe('*', …)`. Net behaviour is unchanged — same recompute / re-sync triggers — but the reactivity travels through the SDK and the settings store rather than Tracker, removing 3 of the 10 client `meteor/tracker` imports. diff --git a/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts b/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts index fa05d6c369ffc..04447635ad45e 100644 --- a/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts +++ b/apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts @@ -1,6 +1,5 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { Emitter } from '@rocket.chat/emitter'; -import { Tracker } from 'meteor/tracker'; import type { RefObject } from 'react'; import { limitQuoteChain } from './limitQuoteChain'; @@ -8,6 +7,7 @@ import type { FormattingButton } from './messageBoxFormatting'; import { formattingButtons } from './messageBoxFormatting'; import type { ComposerAPI } from '../../../../client/lib/chats/ChatAPI'; import { createUploadsAPI } from '../../../../client/lib/chats/uploads'; +import { settings } from '../../../../client/lib/settings'; import { withDebouncing } from '../../../../lib/utils/highOrderFunctions'; export const createComposerAPI = ( @@ -195,26 +195,31 @@ export const createComposerAPI = ( setEditing(editing); }; - const [formatters, stopFormatterTracker] = (() => { + const [formatters, stopFormatterSubscription] = (() => { let actions: FormattingButton[] = []; - const c = Tracker.autorun(() => { + const recompute = (): void => { actions = formattingButtons.filter(({ condition }) => !condition || condition()); emitter.emit('formatting'); - }); + }; + recompute(); + // Coarse-grained: fires on every setting change, but the only condition() + // today is Katex_Enabled and the recompute is a cheap zustand read, so the + // extra work per unrelated setting change is negligible. + const stop = settings.observe('*', recompute); return [ { get: () => actions, subscribe: (callback: () => void) => emitter.on('formatting', callback), }, - c, + stop, ]; })(); const release = (): void => { input.removeEventListener('input', persist); - stopFormatterTracker.stop(); + stopFormatterSubscription(); }; const wrapSelection = (pattern: string): { selectionStart: number; selectionEnd: number; value: string } => { diff --git a/apps/meteor/client/lib/cachedStores/CachedStore.ts b/apps/meteor/client/lib/cachedStores/CachedStore.ts index e0adae432aaca..84702f8676cf0 100644 --- a/apps/meteor/client/lib/cachedStores/CachedStore.ts +++ b/apps/meteor/client/lib/cachedStores/CachedStore.ts @@ -3,7 +3,6 @@ import type { StreamNames } from '@rocket.chat/ddp-client'; import { isTruthy } from '@rocket.chat/tools'; import localforage from 'localforage'; import { Meteor } from 'meteor/meteor'; -import { Tracker } from 'meteor/tracker'; import { create, type StoreApi, type UseBoundStore } from 'zustand'; import { baseURI } from '../baseURI'; @@ -315,16 +314,16 @@ export abstract class CachedStore implements await this.loadFromServerAndPopulate(); } - this.reconnectionComputation?.stop(); - let wentOffline = Tracker.nonreactive(() => Meteor.status().status === 'offline'); - this.reconnectionComputation = Tracker.autorun(() => { - const { status } = Meteor.status(); - - if (status === 'offline') { + this.reconnectionUnsubscribe?.(); + const sdk = getDdpSdk(); + let wentOffline = sdk.connection.status !== 'connected'; + this.reconnectionUnsubscribe = sdk.connection.on('connection', () => { + if (sdk.connection.status !== 'connected') { wentOffline = true; + return; } - - if (status === 'connected' && wentOffline) { + if (wentOffline) { + wentOffline = false; this.trySync(); } }); @@ -362,7 +361,7 @@ export abstract class CachedStore implements this.setReady(false); } - private reconnectionComputation: Tracker.Computation | undefined; + private reconnectionUnsubscribe: (() => void) | undefined; setReady(ready: boolean) { this.useReady.setState(ready); diff --git a/apps/meteor/client/lib/sdk/meteorBackedSdk.ts b/apps/meteor/client/lib/sdk/meteorBackedSdk.ts index 281ab6aa87504..01436c9b17024 100644 --- a/apps/meteor/client/lib/sdk/meteorBackedSdk.ts +++ b/apps/meteor/client/lib/sdk/meteorBackedSdk.ts @@ -3,7 +3,6 @@ import { Emitter } from '@rocket.chat/emitter'; import { Accounts } from 'meteor/accounts-base'; import { DDPCommon } from 'meteor/ddp-common'; import { Meteor } from 'meteor/meteor'; -import { Tracker } from 'meteor/tracker'; /** * Meteor-backed pass-through DDPSDK used when the SDK transport is OFF. @@ -28,15 +27,27 @@ const safeMeteorStatus = (): { status: string; connected: boolean; retryCount?: }; const onMeteorStatusChange = (cb: () => void): (() => void) => { - if (typeof Meteor.status !== 'function' || typeof Tracker.autorun !== 'function') { - // Test / SSR environment with a stubbed Meteor — no reactive status to bridge. + // Subscribe to Meteor's underlying WebSocket lifecycle events directly instead + // of riding Meteor.status's Tracker reactivity. The stream is the canonical + // non-reactive source: 'connected' / 'disconnect' / 'reset' fire from + // LivedataStream regardless of any Tracker context. + const stream = (Meteor.connection as unknown as { _stream?: { on?: (event: string, cb: () => void) => void } } | undefined)?._stream; + if (!stream || typeof stream.on !== 'function') { + // Test / SSR environment with a stubbed Meteor — no stream to subscribe to. return noopUnsubscribe; } - const computation = Tracker.autorun(() => { - Meteor.status(); - cb(); - }); - return () => computation.stop(); + let stopped = false; + const handler = (): void => { + if (!stopped) cb(); + }; + stream.on('connected', handler); + stream.on('disconnect', handler); + stream.on('reset', handler); + // Meteor's stream `on` doesn't expose an `off`; flip a flag instead so the + // stale listener becomes a no-op once stopBridge runs. + return () => { + stopped = true; + }; }; const meteorStatusToSdkStatus = (): string => { From a8d5dc3263ee14485741768cf7ab475093b4f538 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 7 May 2026 18:17:23 -0300 Subject: [PATCH 2/3] fix(client): drop invalid 'connected' subscription in meteorBackedSdk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Meteor's socket-stream-client only accepts 'message' / 'reset' / 'disconnect' as event types — `_stream.on('connected', ...)` throws `Error: unknown event type: connected`. The throw propagated up through `connection.on(...)` to `CachedStore.performInitialization`, aborting it before `setupListener()` could open the stream subscription. The error was caught by `init().catch(console.error)` so the cache silently appeared ready while real-time updates from public-settings, subscriptions, etc. never reached the client. Use 'reset' (fired when a new DDP session is established, effectively the post-reconnect signal) plus 'disconnect' instead. The bridge's `safeMeteorStatus()` readback covers determining the actual state for emitting `connected`/`disconnected`/`connection`. Caught by deterministic E2E failures across PR #40445 / #40446: homepage custom-body, omnichannel department removal toggle, system-messages, account-security 2FA — all tests that change a setting via REST and expect the client UI to reflect it through the public-settings stream. --- apps/meteor/client/lib/sdk/meteorBackedSdk.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/meteor/client/lib/sdk/meteorBackedSdk.ts b/apps/meteor/client/lib/sdk/meteorBackedSdk.ts index 01436c9b17024..3584cf45640d8 100644 --- a/apps/meteor/client/lib/sdk/meteorBackedSdk.ts +++ b/apps/meteor/client/lib/sdk/meteorBackedSdk.ts @@ -29,8 +29,15 @@ const safeMeteorStatus = (): { status: string; connected: boolean; retryCount?: const onMeteorStatusChange = (cb: () => void): (() => void) => { // Subscribe to Meteor's underlying WebSocket lifecycle events directly instead // of riding Meteor.status's Tracker reactivity. The stream is the canonical - // non-reactive source: 'connected' / 'disconnect' / 'reset' fire from - // LivedataStream regardless of any Tracker context. + // non-reactive source: `'reset'` fires when a new DDP session is established + // (effectively the "connected" signal — see socket-stream-client.js), and + // `'disconnect'` fires when the WebSocket drops or each retry attempt restarts. + // `'connected'` is intentionally NOT subscribed: the stream's allowed event + // list is `['message', 'reset', 'disconnect']` and `on('connected')` throws + // `Error: unknown event type: connected`. Throwing here would propagate up + // through `connection.on(...)` callers (notably `CachedStore.performInitialization`) + // and abort their initialization before `setupListener()` runs, silently + // breaking real-time stream subscriptions for settings, subscriptions, etc. const stream = (Meteor.connection as unknown as { _stream?: { on?: (event: string, cb: () => void) => void } } | undefined)?._stream; if (!stream || typeof stream.on !== 'function') { // Test / SSR environment with a stubbed Meteor — no stream to subscribe to. @@ -40,9 +47,8 @@ const onMeteorStatusChange = (cb: () => void): (() => void) => { const handler = (): void => { if (!stopped) cb(); }; - stream.on('connected', handler); - stream.on('disconnect', handler); stream.on('reset', handler); + stream.on('disconnect', handler); // Meteor's stream `on` doesn't expose an `off`; flip a flag instead so the // stale listener becomes a no-op once stopBridge runs. return () => { From cb120594541faa445e1a9fa1832e25920ed6e5cc Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Tue, 12 May 2026 16:32:13 -0300 Subject: [PATCH 3/3] chore: readapt `Meteor.connection` type --- apps/meteor/app/utils/client/lib/SDKClient.ts | 8 +-- apps/meteor/client/lib/sdk/ddpSdk.ts | 2 +- apps/meteor/client/lib/sdk/meteorBackedSdk.ts | 10 ++-- apps/meteor/client/lib/streamer/streamer.ts | 20 ++++--- .../meteor/overrides/killMeteorStream.ts | 8 +-- .../meteor/overrides/stubMeteorStream.ts | 44 +++----------- .../meteor/overrides/subscribeViaSDK.ts | 4 +- .../AuthenticationProvider.tsx | 4 +- .../definition/externals/meteor/meteor.d.ts | 60 +++++++++++++------ 9 files changed, 76 insertions(+), 84 deletions(-) diff --git a/apps/meteor/app/utils/client/lib/SDKClient.ts b/apps/meteor/app/utils/client/lib/SDKClient.ts index f54819a43d805..387adde9fbe75 100644 --- a/apps/meteor/app/utils/client/lib/SDKClient.ts +++ b/apps/meteor/app/utils/client/lib/SDKClient.ts @@ -198,7 +198,7 @@ const createNewDdpSdkStream = ( if (data?.msg !== 'changed') return; if (data.collection !== `stream-${streamName}`) return; if (data.fields?.eventName !== key) return; - streamProxy.emit(`stream-${streamName}/${key}` as keyof EventMap, data.fields.args); + streamProxy.emit(`stream-${streamName}/${key}`, data.fields.args); }); }); @@ -266,7 +266,7 @@ const createStreamManager = () => { // per-stream callbacks fire. With SDK transport on, the frames arrive on // the SDK socket and createNewDdpSdkStream registers its own onCollection // listener instead. - Meteor.connection._stream.on('message', (rawMsg: string) => { + Meteor.connection._stream!.on('message', (rawMsg: string) => { const msg = DDPCommon.parseDDP(rawMsg); if (!isChangedCollectionPayload(msg)) { return; @@ -299,8 +299,8 @@ const createStreamManager = () => { const stream = streams.get(eventLiteral) || (sdkTransportEnabled - ? createNewDdpSdkStream(streamProxy, name as StreamNames, key as StreamKeys, args) - : createNewMeteorStream(name as StreamNames, key as StreamKeys, args)); + ? createNewDdpSdkStream(streamProxy, name, key as StreamKeys, args) + : createNewMeteorStream(name, key as StreamKeys, args)); const stop = (): void => { streamProxy.off(eventLiteral, proxyCallback); diff --git a/apps/meteor/client/lib/sdk/ddpSdk.ts b/apps/meteor/client/lib/sdk/ddpSdk.ts index 38354ef22fcfe..f9cb3dbc2b953 100644 --- a/apps/meteor/client/lib/sdk/ddpSdk.ts +++ b/apps/meteor/client/lib/sdk/ddpSdk.ts @@ -143,7 +143,7 @@ export const ensureConnectedAndAuthenticated = async (): Promise => { // parallel re-auth flows in CI's parallel-shard environment and // kicked otherwise-healthy tests out. Accounts._unstoreLoginToken(); - (Meteor.connection as unknown as { setUserId: (uid: string | null) => void }).setUserId(null); + Meteor.connection.setUserId(null); return; } console.warn('[ddpSdk] loginWithToken failed', error); diff --git a/apps/meteor/client/lib/sdk/meteorBackedSdk.ts b/apps/meteor/client/lib/sdk/meteorBackedSdk.ts index 3584cf45640d8..d50e3a70da4aa 100644 --- a/apps/meteor/client/lib/sdk/meteorBackedSdk.ts +++ b/apps/meteor/client/lib/sdk/meteorBackedSdk.ts @@ -38,7 +38,7 @@ const onMeteorStatusChange = (cb: () => void): (() => void) => { // through `connection.on(...)` callers (notably `CachedStore.performInitialization`) // and abort their initialization before `setupListener()` runs, silently // breaking real-time stream subscriptions for settings, subscriptions, etc. - const stream = (Meteor.connection as unknown as { _stream?: { on?: (event: string, cb: () => void) => void } } | undefined)?._stream; + const stream = Meteor.connection?._stream; if (!stream || typeof stream.on !== 'function') { // Test / SSR environment with a stubbed Meteor — no stream to subscribe to. return noopUnsubscribe; @@ -75,8 +75,8 @@ const meteorStatusToSdkStatus = (): string => { }; const createMeteorBackedClient = () => { - const subscribe = (name: string, ...args: unknown[]) => { - const sub = (Meteor.connection.subscribe as (name: string, ...args: unknown[]) => Meteor.SubscriptionHandle)(name, ...args); + const subscribe = (name: string, ...args: Parameters) => { + const sub = Meteor.connection.subscribe(name, ...args); // Approximate DDPSDK's Subscription shape with Meteor's handle. The // codebase only reads `stop`/`ready`/`isReady`/`id` from it. return Object.assign(sub, { @@ -84,7 +84,7 @@ const createMeteorBackedClient = () => { isReady: false, ready: () => Promise.resolve(), onChange: () => undefined, - }) as unknown as ReturnType; + }); }; const callAsync = (method: string, ...args: unknown[]): Promise & { id: string } => { @@ -108,7 +108,7 @@ const createMeteorBackedClient = () => { if ((msg as { collection?: unknown }).collection !== id) return; callback(msg); }; - const stream = (Meteor.connection as unknown as { _stream: { on: (k: 'message', cb: (raw: string) => void) => void } })._stream; + const stream = Meteor.connection._stream!; stream.on('message', handler); // Meteor's stream `on` doesn't expose an off; the listener is harmless // and lives for the page lifetime. Caller's stop is a no-op. diff --git a/apps/meteor/client/lib/streamer/streamer.ts b/apps/meteor/client/lib/streamer/streamer.ts index 1eb31015acaf5..4e256c614737c 100644 --- a/apps/meteor/client/lib/streamer/streamer.ts +++ b/apps/meteor/client/lib/streamer/streamer.ts @@ -21,12 +21,14 @@ interface StreamerOptions { } interface StreamerDDPConnection { - _stream: { - on: { - (key: 'message', callback: (data: string) => void): void; - (key: 'reset', callback: () => void): void; - }; - }; + _stream: + | { + on: { + (key: 'message', callback: (data: string) => void): void; + (key: 'reset', callback: () => void): void; + }; + } + | undefined; subscribe(name: string, ...args: unknown[]): SubscriptionHandle; call(methodName: string, ...args: unknown[]): void; hasMeteorStreamerEventListeners?: boolean; @@ -46,7 +48,7 @@ export class StreamerCentral extends EV { return; } - ddpConnection._stream.on('message', (rawMessage?: unknown) => { + ddpConnection._stream!.on('message', (rawMessage?: unknown) => { if (typeof rawMessage !== 'string') { return; } @@ -75,7 +77,7 @@ export class StreamerCentral extends EV { getStreamer(name: N, options: StreamerOptions): Streamer { const existingInstance = this.instances[name]; if (existingInstance) { - return existingInstance as Streamer; + return existingInstance; } const streamer = new Streamer(name, options); @@ -107,7 +109,7 @@ export class Streamer extends EV { this.name = name; this.useCollection = useCollection; - this.ddpConnection._stream.on('reset', () => { + this.ddpConnection._stream!.on('reset', () => { super.emit('__reconnect__'); }); } diff --git a/apps/meteor/client/meteor/overrides/killMeteorStream.ts b/apps/meteor/client/meteor/overrides/killMeteorStream.ts index 128f797e92138..897fac79b115c 100644 --- a/apps/meteor/client/meteor/overrides/killMeteorStream.ts +++ b/apps/meteor/client/meteor/overrides/killMeteorStream.ts @@ -30,13 +30,7 @@ import { userIdStore } from '../../lib/user'; * ddpOverREST intercepts and routes to REST (or DDPSDK for `login`). */ if (isSdkTransportEnabled()) { - const conn = Meteor.connection as unknown as { - _subsBeingRevived: Record; - _methodsBlockingQuiescence: Record; - _messagesBufferedUntilQuiescence: unknown[]; - _outstandingMethodBlocks: unknown[]; - _methodInvokers: Record; - }; + const conn = Meteor.connection; conn._subsBeingRevived = Object.create(null); conn._methodsBlockingQuiescence = Object.create(null); diff --git a/apps/meteor/client/meteor/overrides/stubMeteorStream.ts b/apps/meteor/client/meteor/overrides/stubMeteorStream.ts index fbdebd028eef1..8862926f1759b 100644 --- a/apps/meteor/client/meteor/overrides/stubMeteorStream.ts +++ b/apps/meteor/client/meteor/overrides/stubMeteorStream.ts @@ -26,42 +26,14 @@ import { isSdkTransportEnabled } from '../../lib/sdk/sdkTransportEnabled'; * - connect/pong frames — discarded; the SDK socket has its own handshake. */ -type MeteorIDDPStream = { - currentStatus: { - status: string; - connected: boolean; - retryCount: number; - retryTime?: number; - reason?: string; - }; - eventCallbacks?: Record void>>; - statusListeners?: { changed(): void }; - on(event: string, callback: (...args: unknown[]) => void): void; - forEachCallback(name: string, cb: (callback: (...args: unknown[]) => void) => void): void; - send(data: string): void; - status(): MeteorIDDPStream['currentStatus']; - statusChanged(): void; - reconnect(options?: unknown): void; - disconnect(options?: { _permanent?: boolean; _error?: unknown }): void; - _lostConnection(error?: unknown): void; -}; - -type MeteorConnectionInternals = { - _stream: MeteorIDDPStream; - _streamHandlers: { - onMessage(raw: string): void; - onReset(): void; - }; -}; - if (isSdkTransportEnabled()) { installStubMeteorStream(); } function installStubMeteorStream(): void { - const conn = Meteor.connection as unknown as MeteorConnectionInternals; + const conn = Meteor.connection; - const realStream = conn._stream; + const realStream = conn._stream!; // Carry Meteor's already-registered handlers (registered in the Connection // constructor BEFORE we got a chance to swap `_stream`) over to the stub — @@ -76,11 +48,11 @@ function installStubMeteorStream(): void { // already closed / never opened } - const eventCallbacks: Record void>> = Object.create(null); + const eventCallbacks: Record void>> = Object.create(null); for (const [name, callbacks] of Object.entries(inheritedCallbacks)) { - eventCallbacks[name] = (callbacks as Array<(...args: unknown[]) => void>).slice(); + eventCallbacks[name] = callbacks.slice(); } - const fire = (name: string, ...args: unknown[]): void => { + const fire = (name: string, ...args: any[]): void => { const list = eventCallbacks[name]; if (!list) return; list.slice().forEach((cb) => cb(...args)); @@ -89,14 +61,14 @@ function installStubMeteorStream(): void { const TrackerDependency = (Tracker as unknown as { Dependency?: new () => { changed(): void } }).Dependency; const statusListeners = TrackerDependency ? new TrackerDependency() : undefined; - const stub: MeteorIDDPStream = { + conn._stream = { currentStatus: { status: 'connected', connected: true, retryCount: 0, }, - eventCallbacks, + eventCallbacks: eventCallbacks as NonNullable['eventCallbacks'], statusListeners, on(name, callback) { @@ -141,8 +113,6 @@ function installStubMeteorStream(): void { }, }; - conn._stream = stub; - const bridgePongFor = (id?: string): void => { conn._streamHandlers.onMessage( DDPCommon.stringifyDDP({ msg: 'pong', ...(id != null && { id }) } as unknown as Parameters[0]), diff --git a/apps/meteor/client/meteor/overrides/subscribeViaSDK.ts b/apps/meteor/client/meteor/overrides/subscribeViaSDK.ts index e14b23a447a82..aee4279676c34 100644 --- a/apps/meteor/client/meteor/overrides/subscribeViaSDK.ts +++ b/apps/meteor/client/meteor/overrides/subscribeViaSDK.ts @@ -45,7 +45,7 @@ const extractCallbacks = (args: unknown[]): { params: unknown[]; callbacks: Subs type MeteorSubscriptionHandle = Meteor.SubscriptionHandle; if (isSdkTransportEnabled()) { - (Meteor.connection as any).subscribe = ((name: string, ...rest: unknown[]): MeteorSubscriptionHandle => { + Meteor.connection.subscribe = (name: string, ...rest: unknown[]): MeteorSubscriptionHandle => { const { params, callbacks } = extractCallbacks(rest); const subscription = getDdpSdk().client.subscribe(name, ...params); @@ -61,5 +61,5 @@ if (isSdkTransportEnabled()) { }, ready: () => subscription.isReady, } as MeteorSubscriptionHandle; - }) as Meteor.IMeteorConnection['subscribe']; + }; } diff --git a/apps/meteor/client/providers/AuthenticationProvider/AuthenticationProvider.tsx b/apps/meteor/client/providers/AuthenticationProvider/AuthenticationProvider.tsx index aeb6e7e576555..0bf8b1c30bc8f 100644 --- a/apps/meteor/client/providers/AuthenticationProvider/AuthenticationProvider.tsx +++ b/apps/meteor/client/providers/AuthenticationProvider/AuthenticationProvider.tsx @@ -74,7 +74,7 @@ const AuthenticationProvider = ({ children }: AuthenticationProviderProps): Reac const loginWithService = `loginWith${loginMethods[serviceName] || capitalize(String(serviceName || ''))}`; - const method: (config: unknown, cb: (error: any) => void) => Promise = (Meteor as any)[loginWithService] as any; + const method: (config: unknown, cb: (error: any) => void) => Promise = (Meteor as any)[loginWithService]; if (!method) { return () => Promise.reject(new Error('Login method not found')); @@ -131,7 +131,7 @@ const AuthenticationProvider = ({ children }: AuthenticationProviderProps): Reac // ignore } try { - (Meteor.connection as unknown as { setUserId: (uid: string | null) => void }).setUserId(null); + Meteor.connection.setUserId(null); } catch { // ignore } diff --git a/apps/meteor/definition/externals/meteor/meteor.d.ts b/apps/meteor/definition/externals/meteor/meteor.d.ts index 477107d55d338..a2b3f400acb39 100644 --- a/apps/meteor/definition/externals/meteor/meteor.d.ts +++ b/apps/meteor/definition/externals/meteor/meteor.d.ts @@ -64,6 +64,42 @@ declare module 'meteor/meteor' { methods: string[]; } + interface IDDPStream { + eventCallbacks: { + message: Array<(data: string) => void>; + reset: Array<() => void>; + disconnect: Array<() => void>; + }; + socket?: { + onmessage: (data: { type: string; data: string }) => void; + _didMessage: (data: string) => void; + send: (data: string) => void; + }; + _launchConnectionAsync?: () => void; + on: { + (key: 'message', callback: (data: string) => void): void; + (key: 'reset', callback: () => void): void; + (key: 'disconnect', callback: () => void): void; + }; + disconnect(options?: { _permanent?: boolean; _error?: unknown }): void; + + currentStatus: { + status: string; + connected: boolean; + retryCount: number; + retryTime?: number; + reason?: string; + }; + statusListeners?: { changed(): void }; + forEachCallback(name: string, cb: (callback: (...args: unknown[]) => void) => void): void; + send(data: string): void; + status(): IDDPStream['currentStatus']; + statusChanged(): void; + reconnect(options?: unknown): void; + disconnect(options?: { _permanent?: boolean; _error?: unknown }): void; + _lostConnection(error?: unknown): void; + } + interface IMeteorConnection { httpHeaders: Record; referer: string; @@ -71,26 +107,14 @@ declare module 'meteor/meteor' { _send(message: IDDPMessage): void; _methodInvokers: Record; + _subsBeingRevived: Record; + _methodsBlockingQuiescence: Record; + _messagesBufferedUntilQuiescence: unknown[]; + _outstandingMethodBlocks: unknown[]; _livedata_data(message: IDDPUpdatedMessage): void; - _stream: { - eventCallbacks: { - message: Array<(data: string) => void>; - }; - socket: { - onmessage: (data: { type: string; data: string }) => void; - _didMessage: (data: string) => void; - send: (data: string) => void; - }; - _launchConnectionAsync: () => void; - on: { - (key: 'message', callback: (data: string) => void): void; - (key: 'reset', callback: () => void): void; - }; - }; - - _outstandingMethodBlocks: unknown[]; + _stream: IDDPStream | undefined; // Updated: onMessage is now inside _streamHandlers _streamHandlers: { @@ -120,6 +144,8 @@ declare module 'meteor/meteor' { ): SubscriptionHandle; call(methodName: string, ...args: [...unknown, callback?: (error: Error | null, result: unknown) => void]): void; + + setUserId(uid: string | null): void; } const connection: IMeteorConnection;