Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sdk-tracker-easy-autoruns.md
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 11 additions & 6 deletions apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
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';
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 = (
Expand Down Expand Up @@ -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 } => {
Expand Down
8 changes: 4 additions & 4 deletions apps/meteor/app/utils/client/lib/SDKClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -299,8 +299,8 @@ const createStreamManager = () => {
const stream =
streams.get(eventLiteral) ||
(sdkTransportEnabled
? createNewDdpSdkStream(streamProxy, name as StreamNames, key as StreamKeys<StreamNames>, args)
: createNewMeteorStream(name as StreamNames, key as StreamKeys<StreamNames>, args));
? createNewDdpSdkStream(streamProxy, name, key as StreamKeys<StreamNames>, args)
: createNewMeteorStream(name, key as StreamKeys<StreamNames>, args));

const stop = (): void => {
streamProxy.off(eventLiteral, proxyCallback);
Expand Down
19 changes: 9 additions & 10 deletions apps/meteor/client/lib/cachedStores/CachedStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -315,16 +314,16 @@ export abstract class CachedStore<T extends IRocketChatRecord, U = T> 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();
}
});
Expand Down Expand Up @@ -362,7 +361,7 @@ export abstract class CachedStore<T extends IRocketChatRecord, U = T> implements
this.setReady(false);
}

private reconnectionComputation: Tracker.Computation | undefined;
private reconnectionUnsubscribe: (() => void) | undefined;

setReady(ready: boolean) {
this.useReady.setState(ready);
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/client/lib/sdk/ddpSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export const ensureConnectedAndAuthenticated = async (): Promise<void> => {
// 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);
Expand Down
41 changes: 29 additions & 12 deletions apps/meteor/client/lib/sdk/meteorBackedSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -28,15 +27,33 @@ 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: `'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?._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('reset', handler);
stream.on('disconnect', handler);
Comment on lines +50 to +51

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all early/eager consumers of getDdpSdk / sdk.connection to confirm none subscribe before Meteor's livedata Connection wires its own '_stream' handlers.
rg -nP --type=ts -C2 "sdk\.connection\.(on|status)\b"
rg -nP --type=ts -C2 "getDdpSdk\s*\(\s*\)"

Repository: RocketChat/Rocket.Chat

Length of output: 16209


🌐 Web query:

In Meteor's livedata package, in what order does Connection register its _stream.on('reset', ...)handler relative to when client code can attach listeners via_stream.on('reset', ...)? Does Meteor.status().connected become true synchronously before or after external reset listeners run?

💡 Result:

No definitive source explicitly states the exact registration order of the Connection's internal _stream.on('reset', ...) handler relative to client-attached listeners via connection._stream.on('reset', ...), nor the synchronous timing of Meteor.status().connected becoming true relative to external reset listeners firing. However, based on standard Node.js EventEmitter behavior and Meteor's DDP client architecture from available sources, the following can be inferred with moderate confidence. The Connection's internal handler is registered during the Connection constructor in packages/ddp-client/common/livedata_connection.js, shortly after creating the _stream (ClientStream instance) [1][2][3]. Client code attaching listeners via _stream.on('reset', ...) can occur at any time after the connection object is available, typically after Meteor.startup or via Tracker.autorun observing connection status [4][5]. Since Node.js EventEmitter adds listeners to an internal array in the order on() is called, if client code attaches its listener after the constructor runs (which is the standard case), the internal handler is first in the list and executes before external (client) listeners when a 'reset' event fires [Node.js EventEmitter docs implied by general knowledge, confirmed by Meteor's use of streams]. For Meteor.status().connected: This becomes true synchronously when the DDP 'connected' message is fully processed in _processOneDataMessage, which occurs after the stream's 'reset' event (triggered on new transport connection or protocol reset) [1][5][3]. The reset handlers run first upon stream reset, then DDP negotiation completes, 'connected' message arrives, status updates to true. Thus, status.connected becomes true after external reset listeners run. This sequence ensures internal reset logic (e.g., clearing stores, method invokers [2][6]) completes before client code reacts and before status reflects full connection readiness [4][5]. No changes noted in recent Meteor 3.x discussions [1][6].

Citations:


🏁 Script executed:

sed -n '1,100p' apps/meteor/client/lib/sdk/meteorBackedSdk.ts | cat -n

Repository: RocketChat/Rocket.Chat

Length of output: 4941


🏁 Script executed:

sed -n '100,200p' apps/meteor/client/lib/sdk/meteorBackedSdk.ts | cat -n

Repository: RocketChat/Rocket.Chat

Length of output: 4256


Add defensive queueMicrotask to defer status check until after Meteor's internal reset handler completes.

The stream emits 'reset' when a new DDP session begins, triggering all registered 'reset' listeners in order. Meteor's internal Connection handler updates the reactive status variable, but this update is not synchronous relative to listener invocation—external listeners fire while Meteor's internal reset logic is still pending. The handler at line 47 checks safeMeteorStatus()?.connected (line 66) synchronously and may see a stale value, causing the 'connected' event emission (line 69) to be missed until the next status change. The 'connection' event at line 71 still fires as a fallback.

Wrap the callback invocation with queueMicrotask to defer status evaluation until after the listener call stack unwinds:

Suggested fix
const handler = (): void => {
	if (!stopped) queueMicrotask(cb);
};

This ensures safeMeteorStatus()?.connected reflects Meteor's updated state when checked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/meteor/client/lib/sdk/meteorBackedSdk.ts` around lines 50 - 51, The
handler registered on stream.reset/stream.disconnect (the function referenced as
handler used in stream.on('reset', handler) and stream.on('disconnect',
handler)) should defer invoking cb so Meteor's internal Connection reset
completes first; modify the handler to check stopped and then call cb inside
queueMicrotask (so safeMeteorStatus()?.connected will reflect the updated
reactive state when evaluated). Ensure you reference the existing stopped flag
and cb closure and only wrap the callback invocation in queueMicrotask, leaving
the rest of the logic intact.

// 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 => {
Expand All @@ -58,16 +75,16 @@ 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<typeof Meteor.connection.subscribe>) => {
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, {
id: '',
isReady: false,
ready: () => Promise.resolve(),
onChange: () => undefined,
}) as unknown as ReturnType<DDPSDK['client']['subscribe']>;
});
};

const callAsync = (method: string, ...args: unknown[]): Promise<unknown> & { id: string } => {
Expand All @@ -91,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.
Expand Down
20 changes: 11 additions & 9 deletions apps/meteor/client/lib/streamer/streamer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -46,7 +48,7 @@ export class StreamerCentral extends EV {
return;
}

ddpConnection._stream.on('message', (rawMessage?: unknown) => {
ddpConnection._stream!.on('message', (rawMessage?: unknown) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all non-null assertions on optional Meteor streams to audit crash paths.
rg -nP --type=ts -C2 '\._stream!\.on\('

Repository: RocketChat/Rocket.Chat

Length of output: 1300


🏁 Script executed:

# First, examine the StreamerDDPConnection class definition and _stream field
cat -n apps/meteor/client/lib/streamer/streamer.ts | head -60

Repository: RocketChat/Rocket.Chat

Length of output: 2098


🏁 Script executed:

# Get more context around the problematic lines to understand initialization
cat -n apps/meteor/client/lib/streamer/streamer.ts | sed -n '20,130p'

Repository: RocketChat/Rocket.Chat

Length of output: 3838


🏁 Script executed:

# Check the type definition of _stream - search for where it's declared
rg -n "_stream" apps/meteor/client/lib/streamer/streamer.ts -B2 -A2

Repository: RocketChat/Rocket.Chat

Length of output: 435


🏁 Script executed:

# Check if StreamerDDPConnection extends or uses Meteor's DDP connection
rg -n "class StreamerDDPConnection\|extends\|implements" apps/meteor/client/lib/streamer/streamer.ts | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 48


Guard _stream before registering listeners.

StreamerDDPConnection._stream is explicitly optional per the interface definition (lines 23–31), but both registration sites force non-null with ! without runtime checks. If _stream is absent, this throws at runtime and breaks streamer setup/reconnect wiring.

Suggested fix
-		ddpConnection._stream!.on('message', (rawMessage?: unknown) => {
+		const stream = ddpConnection._stream;
+		if (!stream) {
+			return;
+		}
+		stream.on('message', (rawMessage?: unknown) => {
-		this.ddpConnection._stream!.on('reset', () => {
+		const stream = this.ddpConnection._stream;
+		if (!stream) {
+			return;
+		}
+		stream.on('reset', () => {

Applies to lines 51 and 112.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ddpConnection._stream!.on('message', (rawMessage?: unknown) => {
const stream = ddpConnection._stream;
if (!stream) {
return;
}
stream.on('message', (rawMessage?: unknown) => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/meteor/client/lib/streamer/streamer.ts` at line 51, The code currently
force-unwraps ddpConnection._stream when registering listeners (e.g., the
`on('message', ...)` and the other listener registration site), which can throw
if `_stream` is undefined; update both registration sites to first read and
guard the optional `_stream` (e.g., const stream = ddpConnection._stream) and
only call stream.on(...) when stream is truthy, otherwise skip or defer listener
registration so the streamer setup/reconnect logic doesn't crash when `_stream`
is absent.

if (typeof rawMessage !== 'string') {
return;
}
Expand Down Expand Up @@ -75,7 +77,7 @@ export class StreamerCentral extends EV {
getStreamer<N extends EventNames>(name: N, options: StreamerOptions): Streamer<N> {
const existingInstance = this.instances[name];
if (existingInstance) {
return existingInstance as Streamer<N>;
return existingInstance;
}

const streamer = new Streamer(name, options);
Expand Down Expand Up @@ -107,7 +109,7 @@ export class Streamer<N extends EventNames> extends EV {
this.name = name;
this.useCollection = useCollection;

this.ddpConnection._stream.on('reset', () => {
this.ddpConnection._stream!.on('reset', () => {
super.emit('__reconnect__');
});
}
Expand Down
8 changes: 1 addition & 7 deletions apps/meteor/client/meteor/overrides/killMeteorStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>;
_methodsBlockingQuiescence: Record<string, unknown>;
_messagesBufferedUntilQuiescence: unknown[];
_outstandingMethodBlocks: unknown[];
_methodInvokers: Record<string, unknown>;
};
const conn = Meteor.connection;

conn._subsBeingRevived = Object.create(null);
conn._methodsBlockingQuiescence = Object.create(null);
Expand Down
44 changes: 7 additions & 37 deletions apps/meteor/client/meteor/overrides/stubMeteorStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Array<(...args: unknown[]) => 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!;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n apps/meteor/client/meteor/overrides/stubMeteorStream.ts

Repository: RocketChat/Rocket.Chat

Length of output: 12072


Avoid hard non-null assertion on conn._stream at override install time.

The stream may not be initialized when this module loads. If _stream is undefined during the Meteor connection setup phase, the ! assertion throws immediately with no fallback, preventing the override from installing gracefully.

Suggested fix
-	const realStream = conn._stream!;
+	const realStream = conn._stream;
+	if (!realStream) {
+		return;
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/meteor/client/meteor/overrides/stubMeteorStream.ts` at line 36, The code
currently uses a hard non-null assertion on conn._stream when installing the
override (const realStream = conn._stream!), which throws if the stream isn't
initialized; change the install to access conn._stream defensively (e.g., const
realStream = conn._stream ?? undefined) and avoid dereferencing if undefined,
and if the override must attach to the live stream, defer installation or
subscribe to the connection/stream-ready event so you call your attach logic
(the code that uses realStream) only once conn._stream is present; update any
places referencing realStream to handle the undefined case or to run the attach
function when conn._stream becomes available.


// Carry Meteor's already-registered handlers (registered in the Connection
// constructor BEFORE we got a chance to swap `_stream`) over to the stub —
Expand All @@ -76,11 +48,11 @@ function installStubMeteorStream(): void {
// already closed / never opened
}

const eventCallbacks: Record<string, Array<(...args: unknown[]) => void>> = Object.create(null);
const eventCallbacks: Record<string, Array<(...args: any[]) => 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));
Expand All @@ -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<typeof conn._stream>['eventCallbacks'],
statusListeners,

on(name, callback) {
Expand Down Expand Up @@ -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<typeof DDPCommon.stringifyDDP>[0]),
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/client/meteor/overrides/subscribeViaSDK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -61,5 +61,5 @@ if (isSdkTransportEnabled()) {
},
ready: () => subscription.isReady,
} as MeteorSubscriptionHandle;
}) as Meteor.IMeteorConnection['subscribe'];
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<true> = (Meteor as any)[loginWithService] as any;
const method: (config: unknown, cb: (error: any) => void) => Promise<true> = (Meteor as any)[loginWithService];

if (!method) {
return () => Promise.reject(new Error('Login method not found'));
Expand Down Expand Up @@ -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
}
Expand Down
Loading
Loading