snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
209
apps/meteor/client/providers/ServerProvider.tsx
Normal file
209
apps/meteor/client/providers/ServerProvider.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
import type { Serialized } from '@rocket.chat/core-typings';
|
||||
import type {
|
||||
ServerMethodName,
|
||||
ServerMethodParameters,
|
||||
ServerMethodReturn,
|
||||
StreamerCallbackArgs,
|
||||
StreamerEvents,
|
||||
StreamNames,
|
||||
StreamKeys,
|
||||
} from '@rocket.chat/ddp-client';
|
||||
import type { Method, PathFor, OperationParams, OperationResult, UrlParams, PathPattern } from '@rocket.chat/rest-typings';
|
||||
import type { UploadResult, ServerContextValue } from '@rocket.chat/ui-contexts';
|
||||
import { ServerContext } from '@rocket.chat/ui-contexts';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { compile } from 'path-to-regexp';
|
||||
import { useMemo, useSyncExternalStore, type ReactNode } from 'react';
|
||||
|
||||
import { Info as info } from '../../app/utils/rocketchat.info';
|
||||
import { sdk } from '../lib/SDKClient';
|
||||
import { absoluteUrl } from '../lib/absoluteUrl';
|
||||
import { ensureConnectedAndAuthenticated, getDdpSdk } from '../lib/sdk/ddpSdk';
|
||||
import { isSdkTransportEnabled } from '../lib/sdk/sdkTransportEnabled';
|
||||
|
||||
const sdkTransportEnabled = isSdkTransportEnabled();
|
||||
|
||||
const callMethod = <MethodName extends ServerMethodName>(
|
||||
methodName: MethodName,
|
||||
...args: ServerMethodParameters<MethodName>
|
||||
): Promise<ServerMethodReturn<MethodName>> => sdk.call(methodName, ...(args as any)) as Promise<ServerMethodReturn<MethodName>>;
|
||||
|
||||
const callEndpoint = <TMethod extends Method, TPathPattern extends PathPattern>({
|
||||
method,
|
||||
pathPattern,
|
||||
keys,
|
||||
params,
|
||||
signal,
|
||||
}: {
|
||||
method: TMethod;
|
||||
pathPattern: TPathPattern;
|
||||
keys: UrlParams<TPathPattern>;
|
||||
params: OperationParams<TMethod, TPathPattern>;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<Serialized<OperationResult<TMethod, TPathPattern>>> => {
|
||||
const compiledPath = compile(pathPattern, { encode: encodeURIComponent })(keys) as any;
|
||||
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unnecessary-type-assertion
|
||||
return sdk.rest.get(compiledPath, params as any, { signal }) as any;
|
||||
|
||||
case 'POST':
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unnecessary-type-assertion
|
||||
return sdk.rest.post(compiledPath, params as any, { signal }) as any;
|
||||
|
||||
case 'PUT':
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
return sdk.rest.put(compiledPath, params as never, { signal }) as never;
|
||||
|
||||
case 'DELETE':
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unnecessary-type-assertion
|
||||
return sdk.rest.delete(compiledPath, params as any, { signal }) as any;
|
||||
|
||||
default:
|
||||
throw new Error('Invalid HTTP method');
|
||||
}
|
||||
};
|
||||
|
||||
const uploadToEndpoint = (endpoint: PathFor<'POST'>, formData: any): Promise<UploadResult> => sdk.rest.post(endpoint as any, formData);
|
||||
|
||||
const getStream =
|
||||
<N extends StreamNames>(
|
||||
streamName: N,
|
||||
_options?: {
|
||||
retransmit?: boolean | undefined;
|
||||
retransmitToSelf?: boolean | undefined;
|
||||
},
|
||||
) =>
|
||||
<K extends StreamKeys<N>>(eventName: K, callback: (...args: StreamerCallbackArgs<N, K>) => void): (() => void) =>
|
||||
sdk.stream(streamName, [eventName], callback).stop;
|
||||
|
||||
const getStreamAll =
|
||||
<N extends StreamNames>(streamName: N) =>
|
||||
(callback: (eventName: string, args: StreamerEvents[N][number]['args']) => void): (() => void) =>
|
||||
sdk.onAnyStreamEvent(streamName, callback).stop;
|
||||
|
||||
const writeStream = <N extends StreamNames, K extends StreamKeys<N>>(streamName: N, streamKey: K, ...args: StreamerCallbackArgs<N, K>) =>
|
||||
sdk.publish(streamName, [streamKey, ...args]);
|
||||
|
||||
const disconnect = sdkTransportEnabled
|
||||
? () => {
|
||||
Meteor.disconnect();
|
||||
try {
|
||||
getDdpSdk().connection.close();
|
||||
} catch {
|
||||
// no-op — DDPSDK may not be connected yet
|
||||
}
|
||||
}
|
||||
: () => Meteor.disconnect();
|
||||
|
||||
const reconnect = sdkTransportEnabled
|
||||
? () => {
|
||||
Meteor.reconnect();
|
||||
// ensureConnectedAndAuthenticated handles both 'connect' and loginWithToken,
|
||||
// so reconnecting here also re-establishes the DDPSDK session with the
|
||||
// same token Meteor resumes with.
|
||||
void ensureConnectedAndAuthenticated();
|
||||
}
|
||||
: () => Meteor.reconnect();
|
||||
|
||||
type CombinedStatus = ReturnType<typeof Meteor.status>;
|
||||
|
||||
const sdkStatusToMeteor = (sdkStatus: string, meteor: CombinedStatus): CombinedStatus => {
|
||||
const retry = { retryCount: meteor.retryCount, retryTime: meteor.retryTime };
|
||||
|
||||
switch (sdkStatus) {
|
||||
case 'connected':
|
||||
return { status: 'connected', connected: true, ...retry };
|
||||
case 'connecting':
|
||||
return { status: 'connecting', connected: false, ...retry };
|
||||
case 'reconnecting':
|
||||
return { status: 'connecting', connected: false, ...retry };
|
||||
case 'failed':
|
||||
return { status: 'failed', connected: false, ...retry };
|
||||
case 'closed':
|
||||
case 'disconnected':
|
||||
return { status: 'waiting', connected: false, ...retry };
|
||||
case 'idle':
|
||||
default:
|
||||
return { status: 'offline', connected: false, ...retry };
|
||||
}
|
||||
};
|
||||
|
||||
// With SDK transport on, combine Meteor's DDP status with DDPSDK's so the
|
||||
// ConnectionStatusBar / idle-connection hooks reflect the worst-case of both
|
||||
// transports. With the flag off, route status straight through Meteor —
|
||||
// `meteorBackedSdk` already bridges Meteor's `_stream` events into
|
||||
// `sdk.connection.on('connection')`, so the same subscription works in both
|
||||
// modes.
|
||||
const computeStatus: () => CombinedStatus = sdkTransportEnabled
|
||||
? () => sdkStatusToMeteor(getDdpSdk().connection.status, Meteor.status())
|
||||
: () => ({ ...Meteor.status() });
|
||||
|
||||
const isStatusEqual = (a: CombinedStatus, b: CombinedStatus): boolean =>
|
||||
a.status === b.status && a.connected === b.connected && a.retryCount === b.retryCount && a.retryTime === b.retryTime;
|
||||
|
||||
let cachedStatus: CombinedStatus = computeStatus();
|
||||
const statusListeners = new Set<() => void>();
|
||||
let statusBridgeStarted = false;
|
||||
|
||||
const ensureStatusBridge = (): void => {
|
||||
if (statusBridgeStarted) return;
|
||||
statusBridgeStarted = true;
|
||||
getDdpSdk().connection.on('connection', () => {
|
||||
const next = computeStatus();
|
||||
if (isStatusEqual(cachedStatus, next)) return;
|
||||
cachedStatus = next;
|
||||
statusListeners.forEach((cb) => cb());
|
||||
});
|
||||
};
|
||||
|
||||
const subscribeStatus = (cb: () => void): (() => void) => {
|
||||
ensureStatusBridge();
|
||||
// Close the race between the cachedStatus computed at module load and the
|
||||
// first event delivered through the bridge: status may have changed in
|
||||
// between (e.g. the socket connected before <ServerProvider> mounted), and
|
||||
// useSyncExternalStore would otherwise surface the stale snapshot until
|
||||
// the next external transition.
|
||||
const next = computeStatus();
|
||||
if (!isStatusEqual(cachedStatus, next)) {
|
||||
cachedStatus = next;
|
||||
cb();
|
||||
}
|
||||
statusListeners.add(cb);
|
||||
return () => {
|
||||
statusListeners.delete(cb);
|
||||
};
|
||||
};
|
||||
|
||||
const getStatusSnapshot = (): CombinedStatus => cachedStatus;
|
||||
|
||||
export type ServerProviderProps = { children?: ReactNode };
|
||||
|
||||
const ServerProvider = ({ children }: ServerProviderProps) => {
|
||||
const { connected, status, retryCount, retryTime } = useSyncExternalStore(subscribeStatus, getStatusSnapshot);
|
||||
|
||||
const value = useMemo(
|
||||
(): ServerContextValue => ({
|
||||
connected,
|
||||
status,
|
||||
retryCount,
|
||||
retryTime,
|
||||
info,
|
||||
absoluteUrl,
|
||||
callMethod,
|
||||
callEndpoint,
|
||||
uploadToEndpoint,
|
||||
getStream,
|
||||
getStreamAll,
|
||||
writeStream,
|
||||
disconnect,
|
||||
reconnect,
|
||||
}),
|
||||
[connected, retryCount, retryTime, status],
|
||||
);
|
||||
|
||||
return <ServerContext.Provider value={value}>{children}</ServerContext.Provider>;
|
||||
};
|
||||
|
||||
export default ServerProvider;
|
||||
Reference in New Issue
Block a user