Skip to content
Open
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
786 changes: 786 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"@testing-library/react": "^13.3.0",
"@types/cli-table": "^0.3.4",
"@types/jmespath": "^0.15.2",
"@types/mocha": "^10.0.10",
"@types/node": "^18.0.0",
Comment on lines +84 to 85
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repo constraints:"
jq -r '.engines.node, .devDependencies.mocha, .devDependencies["@types/mocha"], .devDependencies.tsx' package.json

echo "tsx@4.21.0 engines:"
curl -s https://registry.npmjs.org/tsx/4.21.0 | jq -r '.engines.node // "no engines field"'

echo "Mocha runtime/types versions:"
curl -s https://registry.npmjs.org/mocha/8.1.3 | jq -r '.version'
curl -s https://registry.npmjs.org/@types%2Fmocha/10.0.10 | jq -r '.version'

Repository: ably/ably-js

Length of output: 176


🏁 Script executed:

# Check the lines mentioned in the review (84-85, 133-133, 163-163)
head -90 package.json | tail -10
echo "---"
sed -n '130,136p' package.json
echo "---"
sed -n '160,166p' package.json

Repository: ably/ably-js

Length of output: 987


🌐 Web query:

@types/mocha 10 compatibility with mocha 8

💡 Result:

@types/mocha version 10 is compatible with Mocha 8. Type definitions packages like @types/mocha do not have strict peer dependencies on specific runtime versions of the library they type. They evolve independently to support TypeScript improvements and add types for newer library features while maintaining backward compatibility with older library versions where possible. @types/mocha v8 was released specifically for Mocha 8 (July 2020), v9 for Mocha 9 (July 2021), and v10 starting September 2022 (after Mocha 10). Projects commonly use newer @types/mocha versions with older Mocha runtimes without issues, as core APIs (describe, it, before, etc.) remain stable across Mocha major versions. No sources report incompatibility between @types/mocha 10 and Mocha 8. Type errors in v10.0.2 (fixed in later patches like v10.0.10) were internal TypeScript compilation issues, not runtime incompatibilities with Mocha versions. Modern projects use @types/mocha ^10 with Mocha ^10, but cross-version usage (e.g., Mocha 8 + @types/mocha 10) works for type checking test files.

Citations:


Fix tsx version to support Node 16.

tsx@^4.21.0 requires Node >=18, breaking the test:uts command on Node 16 since it uses mocha --require tsx/cjs. This is incompatible with the repo's declared support of node >=16.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 84 - 85, package.json currently depends on a tsx
release that requires Node >=18 and breaks the test:uts script (which uses
"mocha --require tsx/cjs"); update the "tsx" dependency in package.json to a
version compatible with Node 16 (downgrade/pin to a 3.x tsx release that
supports Node 16) so the test:uts script continues to work on supported Node
>=16, then run the test:uts script to verify mocha --require tsx/cjs loads
correctly.

"@types/request": "^2.48.7",
"@types/ws": "^8.2.0",
Expand Down Expand Up @@ -129,6 +130,7 @@
"ts-loader": "^9.4.2",
"tsconfig-paths-webpack-plugin": "^4.0.1",
"tslib": "^2.3.1",
"tsx": "^4.21.0",
"typedoc": "^0.24.7",
"typescript": "^4.9.5",
"vite": "^4.4.9",
Expand Down Expand Up @@ -158,6 +160,7 @@
"test:playwright": "node test/support/runPlaywrightTests.js",
"test:react": "vitest run",
"test:package": "grunt test:package",
"test:uts": "npm run build:node && mocha --no-config --require tsx/cjs 'test/uts/**/*.test.ts'",
"concat": "grunt concat",
"build": "grunt build:all && npm run build:react",
"build:node": "grunt build:node",
Expand Down
4 changes: 2 additions & 2 deletions src/common/lib/client/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ class Auth {
return new Promise((resolve, reject) => {
let tokenRequestCallbackTimeoutExpired = false,
timeoutLength = this.client.options.timeouts.realtimeRequestTimeout,
tokenRequestCallbackTimeout = setTimeout(() => {
tokenRequestCallbackTimeout = Platform.Config.setTimeout(() => {
tokenRequestCallbackTimeoutExpired = true;
const msg = 'Token request callback timed out after ' + timeoutLength / 1000 + ' seconds';
Logger.logAction(this.logger, Logger.LOG_ERROR, 'Auth.requestToken()', msg);
Expand All @@ -617,7 +617,7 @@ class Auth {

tokenRequestCallback!(resolvedTokenParams, (err, tokenRequestOrDetails, contentType) => {
if (tokenRequestCallbackTimeoutExpired) return;
clearTimeout(tokenRequestCallbackTimeout);
Platform.Config.clearTimeout(tokenRequestCallbackTimeout);

if (err) {
Logger.logAction(
Expand Down
2 changes: 1 addition & 1 deletion src/common/lib/client/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ class BaseClient {
}

getTimestampUsingOffset(): number {
return Date.now() + (this.serverTimeOffset || 0);
return Platform.Config.now() + (this.serverTimeOffset || 0);
}

isTimeOffsetSet(): boolean {
Expand Down
9 changes: 5 additions & 4 deletions src/common/lib/client/realtimechannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ChannelStateChange from './channelstatechange';
import ErrorInfo, { PartialErrorInfo } from '../types/errorinfo';
import * as API from '../../../../ably';
import ConnectionManager from '../transport/connectionmanager';
import Platform from '../../platform';
import { StandardCallback } from '../../types/utils';
import BaseRealtime from './baserealtime';
import { ChannelOptions } from '../../types/channel';
Expand Down Expand Up @@ -940,7 +941,7 @@ class RealtimeChannel extends EventEmitter {

startStateTimerIfNotRunning(): void {
if (!this.stateTimer) {
this.stateTimer = setTimeout(() => {
this.stateTimer = Platform.Config.setTimeout(() => {
Logger.logAction(this.logger, Logger.LOG_MINOR, 'RealtimeChannel.startStateTimerIfNotRunning', 'timer expired');
this.stateTimer = null;
this.timeoutPendingState();
Expand All @@ -951,7 +952,7 @@ class RealtimeChannel extends EventEmitter {
clearStateTimer(): void {
const stateTimer = this.stateTimer;
if (stateTimer) {
clearTimeout(stateTimer);
Platform.Config.clearTimeout(stateTimer as unknown as ReturnType<typeof setTimeout>);
this.stateTimer = null;
}
}
Expand All @@ -962,7 +963,7 @@ class RealtimeChannel extends EventEmitter {
this.retryCount++;
const retryDelay = Utils.getRetryTime(this.client.options.timeouts.channelRetryTimeout, this.retryCount);

this.retryTimer = setTimeout(() => {
this.retryTimer = Platform.Config.setTimeout(() => {
/* If connection is not connected, just leave in suspended, a reattach
* will be triggered once it connects again */
if (this.state === 'suspended' && this.connectionManager.state.sendEvents) {
Expand All @@ -980,7 +981,7 @@ class RealtimeChannel extends EventEmitter {

cancelRetryTimer(): void {
if (this.retryTimer) {
clearTimeout(this.retryTimer as NodeJS.Timeout);
Platform.Config.clearTimeout(this.retryTimer as unknown as ReturnType<typeof setTimeout>);
this.retryTimer = null;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/common/lib/client/realtimepresence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ChannelStateChange from './channelstatechange';
import { ErrCallback } from '../../types/utils';
import { PaginatedResult } from './paginatedresource';
import { PresenceMap, RealtimePresenceParams } from './presencemap';
import Platform from '../../platform';

interface RealtimeHistoryParams {
start?: number;
Expand Down Expand Up @@ -401,7 +402,7 @@ class RealtimePresence extends EventEmitter {
clientId: item.clientId,
data: item.data,
encoding: item.encoding,
timestamp: Date.now(),
timestamp: Platform.Config.now(),
});
subscriptions.emit('leave', presence);
});
Expand Down
2 changes: 1 addition & 1 deletion src/common/lib/client/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class Rest {
throw new ErrorInfo('Internal error (unexpected result type from GET /time)', 50000, 500);
}
/* calculate time offset only once for this device by adding to the prototype */
this.client.serverTimeOffset = time - Date.now();
this.client.serverTimeOffset = time - Platform.Config.now();
return time;
}

Expand Down
36 changes: 18 additions & 18 deletions src/common/lib/transport/connectionmanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ class ConnectionManager extends EventEmitter {
return;
}

const sinceLast = Date.now() - this.lastActivity;
const sinceLast = Platform.Config.now() - this.lastActivity;
if (sinceLast > this.connectionStateTtl + (this.maxIdleInterval as number)) {
Logger.logAction(
this.logger,
Expand All @@ -893,7 +893,7 @@ class ConnectionManager extends EventEmitter {
if (recoveryKey) {
this.setSessionRecoverData({
recoveryKey: recoveryKey,
disconnectedAt: Date.now(),
disconnectedAt: Platform.Config.now(),
location: globalObject.location,
clientId: this.realtime.auth.clientId,
});
Expand Down Expand Up @@ -988,10 +988,10 @@ class ConnectionManager extends EventEmitter {
'ConnectionManager.startTransitionTimer()',
'clearing already-running timer',
);
clearTimeout(this.transitionTimer as number);
Platform.Config.clearTimeout(this.transitionTimer as unknown as ReturnType<typeof setTimeout>);
}

this.transitionTimer = setTimeout(() => {
this.transitionTimer = Platform.Config.setTimeout(() => {
if (this.transitionTimer) {
this.transitionTimer = null;
Logger.logAction(
Expand All @@ -1008,14 +1008,14 @@ class ConnectionManager extends EventEmitter {
cancelTransitionTimer(): void {
Logger.logAction(this.logger, Logger.LOG_MINOR, 'ConnectionManager.cancelTransitionTimer()', '');
if (this.transitionTimer) {
clearTimeout(this.transitionTimer as number);
Platform.Config.clearTimeout(this.transitionTimer as unknown as ReturnType<typeof setTimeout>);
this.transitionTimer = null;
}
}

startSuspendTimer(): void {
if (this.suspendTimer) return;
this.suspendTimer = setTimeout(() => {
this.suspendTimer = Platform.Config.setTimeout(() => {
if (this.suspendTimer) {
this.suspendTimer = null;
Logger.logAction(
Expand All @@ -1037,13 +1037,13 @@ class ConnectionManager extends EventEmitter {
cancelSuspendTimer(): void {
this.states.connecting.failState = 'disconnected';
if (this.suspendTimer) {
clearTimeout(this.suspendTimer as number);
Platform.Config.clearTimeout(this.suspendTimer as unknown as ReturnType<typeof setTimeout>);
this.suspendTimer = null;
}
}

startRetryTimer(interval: number): void {
this.retryTimer = setTimeout(() => {
this.retryTimer = Platform.Config.setTimeout(() => {
Logger.logAction(this.logger, Logger.LOG_MINOR, 'ConnectionManager retry timer expired', 'retrying');
this.retryTimer = null;
this.requestState({ state: 'connecting' });
Expand All @@ -1052,13 +1052,13 @@ class ConnectionManager extends EventEmitter {

cancelRetryTimer(): void {
if (this.retryTimer) {
clearTimeout(this.retryTimer as NodeJS.Timeout);
Platform.Config.clearTimeout(this.retryTimer as unknown as ReturnType<typeof setTimeout>);
this.retryTimer = null;
}
}

startWebSocketSlowTimer() {
this.webSocketSlowTimer = setTimeout(() => {
this.webSocketSlowTimer = Platform.Config.setTimeout(() => {
Logger.logAction(
this.logger,
Logger.LOG_MINOR,
Expand Down Expand Up @@ -1113,13 +1113,13 @@ class ConnectionManager extends EventEmitter {

cancelWebSocketSlowTimer() {
if (this.webSocketSlowTimer) {
clearTimeout(this.webSocketSlowTimer);
Platform.Config.clearTimeout(this.webSocketSlowTimer);
this.webSocketSlowTimer = null;
}
}

startWebSocketGiveUpTimer(transportParams: TransportParams) {
this.webSocketGiveUpTimer = setTimeout(() => {
this.webSocketGiveUpTimer = Platform.Config.setTimeout(() => {
if (!this.wsCheckResult) {
Logger.logAction(
this.logger,
Expand Down Expand Up @@ -1147,7 +1147,7 @@ class ConnectionManager extends EventEmitter {

cancelWebSocketGiveUpTimer() {
if (this.webSocketGiveUpTimer) {
clearTimeout(this.webSocketGiveUpTimer);
Platform.Config.clearTimeout(this.webSocketGiveUpTimer);
this.webSocketGiveUpTimer = null;
}
}
Expand Down Expand Up @@ -1215,11 +1215,11 @@ class ConnectionManager extends EventEmitter {
if (retryImmediately) {
const autoReconnect = () => {
if (this.state === this.states.disconnected) {
this.lastAutoReconnectAttempt = Date.now();
this.lastAutoReconnectAttempt = Platform.Config.now();
this.requestState({ state: 'connecting' });
}
};
const sinceLast = this.lastAutoReconnectAttempt && Date.now() - this.lastAutoReconnectAttempt + 1;
const sinceLast = this.lastAutoReconnectAttempt && Platform.Config.now() - this.lastAutoReconnectAttempt + 1;
if (sinceLast && sinceLast < 1000) {
Logger.logAction(
this.logger,
Expand All @@ -1231,7 +1231,7 @@ class ConnectionManager extends EventEmitter {
(1000 - sinceLast) +
'ms before trying again',
);
setTimeout(autoReconnect, 1000 - sinceLast);
Platform.Config.setTimeout(autoReconnect, 1000 - sinceLast);
} else {
Platform.Config.nextTick(autoReconnect);
}
Expand Down Expand Up @@ -1891,15 +1891,15 @@ class ConnectionManager extends EventEmitter {

Logger.logAction(this.logger, Logger.LOG_MINOR, 'ConnectionManager.ping()', 'transport = ' + transport);

const pingStart = Date.now();
const pingStart = Platform.Config.now();
const id = Utils.cheapRandStr();

return Utils.withTimeoutAsync<number>(
new Promise((resolve) => {
const onHeartbeat = (responseId: string) => {
if (responseId === id) {
transport.off('heartbeat', onHeartbeat);
resolve(Date.now() - pingStart);
resolve(Platform.Config.now() - pingStart);
}
};
transport.on('heartbeat', onHeartbeat);
Expand Down
14 changes: 7 additions & 7 deletions src/common/lib/transport/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ abstract class Transport extends EventEmitter {
this.isFinished = true;
this.isConnected = false;
this.maxIdleInterval = null;
clearTimeout(this.idleTimer ?? undefined);
Platform.Config.clearTimeout((this.idleTimer ?? undefined) as unknown as ReturnType<typeof setTimeout>);
this.idleTimer = null;
this.emit(event, err);
this.dispose();
Expand Down Expand Up @@ -270,13 +270,13 @@ abstract class Transport extends EventEmitter {
if (!this.maxIdleInterval) {
return;
}
this.lastActivity = this.connectionManager.lastActivity = Date.now();
this.lastActivity = this.connectionManager.lastActivity = Platform.Config.now();
this.setIdleTimer(this.maxIdleInterval + 100);
}

setIdleTimer(timeout: number): void {
if (!this.idleTimer) {
this.idleTimer = setTimeout(() => {
this.idleTimer = Platform.Config.setTimeout(() => {
this.onIdleTimerExpire();
}, timeout);
}
Expand All @@ -287,7 +287,7 @@ abstract class Transport extends EventEmitter {
throw new Error('Transport.onIdleTimerExpire(): lastActivity/maxIdleInterval not set');
}
this.idleTimer = null;
const sinceLast = Date.now() - this.lastActivity;
const sinceLast = Platform.Config.now() - this.lastActivity;
const timeRemaining = this.maxIdleInterval - sinceLast;
if (timeRemaining <= 0) {
const msg = 'No activity seen from realtime in ' + sinceLast + 'ms; assuming connection has dropped';
Expand All @@ -310,12 +310,12 @@ abstract class Transport extends EventEmitter {
let transportAttemptTimer: NodeJS.Timeout | number;

const errorCb = function (this: { event: string }, err: ErrorInfo) {
clearTimeout(transportAttemptTimer);
Platform.Config.clearTimeout(transportAttemptTimer as unknown as ReturnType<typeof setTimeout>);
callback({ event: this.event, error: err });
};

const realtimeRequestTimeout = connectionManager.options.timeouts.realtimeRequestTimeout;
transportAttemptTimer = setTimeout(() => {
transportAttemptTimer = Platform.Config.setTimeout(() => {
transport.off(['preconnect', 'disconnected', 'failed']);
transport.dispose();
errorCb.call(
Expand All @@ -332,7 +332,7 @@ abstract class Transport extends EventEmitter {
'Transport.tryConnect()',
'viable transport ' + transport,
);
clearTimeout(transportAttemptTimer);
Platform.Config.clearTimeout(transportAttemptTimer as unknown as ReturnType<typeof setTimeout>);
transport.off(['failed', 'disconnected'], errorCb);
callback(null, transport);
});
Expand Down
2 changes: 1 addition & 1 deletion src/common/lib/util/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function pad(timeSegment: number, three?: number) {
function getHandler(logger: Function): Function {
return Platform.Config.logTimestamps
? function (msg: unknown) {
const time = new Date();
const time = new Date(Platform.Config.now());
logger(
pad(time.getHours()) +
':' +
Expand Down
5 changes: 4 additions & 1 deletion src/common/lib/util/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,10 @@ export function throwMissingPluginError(pluginName: keyof ModularPlugins): never

export async function withTimeoutAsync<A>(promise: Promise<A>, timeout = 5000, err = 'Timeout expired'): Promise<A> {
const e = new ErrorInfo(err, 50000, 500);
return Promise.race([promise, new Promise<A>((_resolve, reject) => setTimeout(() => reject(e), timeout))]);
return Promise.race([
promise,
new Promise<A>((_resolve, reject) => Platform.Config.setTimeout(() => reject(e), timeout)),
]);
}

type NonFunctionKeyNames<A> = { [P in keyof A]: A[P] extends Function ? never : P }[keyof A];
Expand Down
3 changes: 3 additions & 0 deletions src/common/types/IPlatformConfig.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export interface ICommonPlatformConfig {
supportsBinary: boolean;
preferBinary: boolean;
nextTick: process.nextTick;
setTimeout: (handler: () => void, timeout?: number) => ReturnType<typeof globalThis.setTimeout>;
clearTimeout: (id: ReturnType<typeof globalThis.setTimeout> | null | undefined) => void;
now: () => number;
inspect: (value: unknown) => string;
stringByteSize: Buffer.byteLength;
getRandomArrayBuffer: (byteLength: number) => Promise<ArrayBuffer>;
Expand Down
10 changes: 5 additions & 5 deletions src/common/types/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export class Http {

const currentFallback = client._currentFallback;
if (currentFallback) {
if (currentFallback.validUntil > Date.now()) {
if (currentFallback.validUntil > Platform.Config.now()) {
/* Use stored fallback */
const result = await this.doUri(method, uriFromHost(currentFallback.host), headers, body, params);
if (result.error && this.platformHttp.shouldFallback(result.error as ErrnoException)) {
Expand All @@ -205,14 +205,14 @@ export class Http {
return this.doUri(method, uriFromHost(hosts[0]), headers, body, params);
}

let tryAHostStartedAt: Date | null = null;
let tryAHostStartedAt: number | null = null;
const tryAHost = async (candidateHosts: Array<string>, persistOnSuccess?: boolean): Promise<RequestResult> => {
const host = candidateHosts.shift();
tryAHostStartedAt = tryAHostStartedAt ?? new Date();
tryAHostStartedAt = tryAHostStartedAt ?? Platform.Config.now();
const result = await this.doUri(method, uriFromHost(host as string), headers, body, params);
if (result.error && this.platformHttp.shouldFallback(result.error as ErrnoException) && candidateHosts.length) {
// TO3l6
const elapsedTime = Date.now() - tryAHostStartedAt.getTime();
const elapsedTime = Platform.Config.now() - tryAHostStartedAt;
if (elapsedTime > client.options.timeouts.httpMaxRetryDuration) {
return {
error: new ErrorInfo(
Expand All @@ -229,7 +229,7 @@ export class Http {
/* RSC15f */
client._currentFallback = {
host: host as string,
validUntil: Date.now() + client.options.timeouts.fallbackRetryTimeout,
validUntil: Platform.Config.now() + client.options.timeouts.fallbackRetryTimeout,
};
}
return result;
Expand Down
Loading
Loading