Skip to content

Commit

Permalink
Merge pull request #1862 from ably/1850/wsConnectivityUrl-client-option
Browse files Browse the repository at this point in the history
[ECO-4941] Add `wsConnectivityCheckUrl` client option
  • Loading branch information
VeskeR authored Sep 11, 2024
2 parents 7d6d512 + 59551f5 commit 6e85f91
Show file tree
Hide file tree
Showing 9 changed files with 35 additions and 15 deletions.
9 changes: 9 additions & 0 deletions ably.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,15 @@ export interface ClientOptions<Plugins = CorePlugins> extends AuthOptions {
*/
connectivityCheckUrl?: string;

/**
* Override the URL used by the realtime client to check if WebSocket connections are available.
*
* If the client suspects that WebSocket connections are unavailable on the current network,
* it will attempt to open a WebSocket connection to this URL to check WebSocket connectivity.
* If this fails, the client will attempt to connect to Ably systems using fallback transports, if available.
*/
wsConnectivityCheckUrl?: string;

/**
* Disable the check used by the realtime client to check if the internet
* is available before connecting to a fallback host.
Expand Down
3 changes: 2 additions & 1 deletion src/common/lib/transport/connectionmanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2023,7 +2023,8 @@ class ConnectionManager extends EventEmitter {
}

checkWsConnectivity() {
const ws = new Platform.Config.WebSocket(Defaults.wsConnectivityUrl);
const wsConnectivityCheckUrl = this.options.wsConnectivityCheckUrl || Defaults.wsConnectivityCheckUrl;
const ws = new Platform.Config.WebSocket(wsConnectivityCheckUrl);
return new Promise<void>((resolve, reject) => {
let finished = false;
ws.onopen = () => {
Expand Down
6 changes: 6 additions & 0 deletions src/common/lib/util/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ export function normaliseOptions(
connectivityCheckUrl = uri;
}

let wsConnectivityCheckUrl = options.wsConnectivityCheckUrl;
if (wsConnectivityCheckUrl && wsConnectivityCheckUrl.indexOf('://') === -1) {
wsConnectivityCheckUrl = 'wss://' + wsConnectivityCheckUrl;
}

return {
...options,
realtimeHost,
Expand All @@ -319,6 +324,7 @@ export function normaliseOptions(
timeouts,
connectivityCheckParams,
connectivityCheckUrl,
wsConnectivityCheckUrl,
headers,
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/common/types/IDefaults.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { RestAgentOptions } from './ClientOptions';

export default interface IDefaults {
connectivityCheckUrl: string;
wsConnectivityUrl: string;
wsConnectivityCheckUrl: string;
defaultTransports: TransportName[];
restAgentOptions?: RestAgentOptions;
}
2 changes: 1 addition & 1 deletion src/platform/nodejs/lib/util/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TransportNames } from '../../../../common/constants/TransportName';

const Defaults: IDefaults = {
connectivityCheckUrl: 'https://internet-up.ably-realtime.com/is-the-internet-up.txt',
wsConnectivityUrl: 'wss://ws-up.ably-realtime.com',
wsConnectivityCheckUrl: 'wss://ws-up.ably-realtime.com',
/* Note: order matters here: the base transport is the leftmost one in the
* intersection of baseTransportOrder and the transports clientOption that's supported. */
defaultTransports: [TransportNames.WebSocket],
Expand Down
2 changes: 1 addition & 1 deletion src/platform/web/lib/util/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TransportNames } from 'common/constants/TransportName';

const Defaults: IDefaults = {
connectivityCheckUrl: 'https://internet-up.ably-realtime.com/is-the-internet-up.txt',
wsConnectivityUrl: 'wss://ws-up.ably-realtime.com',
wsConnectivityCheckUrl: 'wss://ws-up.ably-realtime.com',
/* Order matters here: the base transport is the leftmost one in the
* intersection of baseTransportOrder and the transports clientOption that's
* supported. */
Expand Down
4 changes: 3 additions & 1 deletion test/common/modules/private_api_recorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ define(['test/support/output_directory_paths'], function (outputDirectoryPaths)
'pass.clientOption.pushRecipientChannel',
'pass.clientOption.webSocketConnectTimeout',
'pass.clientOption.webSocketSlowTimeout',
'pass.clientOption.wsConnectivityCheckUrl', // actually ably-js public API (i.e. it’s in the TypeScript typings) but no other SDK has it. At the same time it's not entirely clear if websocket connectivity check should be considered an ably-js-specific functionality (as for other params above), so for the time being we consider it as private API
'read.Defaults.version',
'read.EventEmitter.events',
'read.Platform.Config.push',
Expand Down Expand Up @@ -125,7 +126,7 @@ define(['test/support/output_directory_paths'], function (outputDirectoryPaths)
'replace.transport.send',
'serialize.recoveryKey',
'write.Defaults.ENVIRONMENT',
'write.Defaults.wsConnectivityUrl',
'write.Defaults.wsConnectivityCheckUrl',
'write.Platform.Config.push', // This implies using a mock implementation of the internal IPlatformPushConfig interface. Our mock (in push_channel_transport.js) then interacts with internal objects and private APIs of public objects to implement this interface; I haven’t added annotations for that private API usage, since there wasn’t an easy way to pass test context information into the mock. I think that for now we can just say that if we wanted to get rid of this private API usage, then we’d need to remove this mock entirely.
'write.auth.authOptions.requestHeaders',
'write.auth.key',
Expand All @@ -139,6 +140,7 @@ define(['test/support/output_directory_paths'], function (outputDirectoryPaths)
'write.connectionManager.msgSerial',
'write.connectionManager.wsHosts',
'write.realtime.options.realtimeHost',
'write.realtime.options.wsConnectivityCheckUrl',
'write.realtime.options.timeouts.realtimeRequestTimeout',
'write.rest._currentFallback.validUntil',
];
Expand Down
2 changes: 2 additions & 0 deletions test/common/modules/shared_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ define([
/* IANA reserved; requests to it will hang forever */
var unroutableHost = '10.255.255.1';
var unroutableAddress = 'http://' + unroutableHost + '/';
var unroutableWssAddress = 'wss://' + unroutableHost + '/';

class SharedHelper {
getTestApp = testAppModule.getTestApp;
Expand All @@ -31,6 +32,7 @@ define([

unroutableHost = unroutableHost;
unroutableAddress = unroutableAddress;
unroutableWssAddress = unroutableWssAddress;
flushTestLogs = globals.flushLogs;

constructor(context) {
Expand Down
20 changes: 10 additions & 10 deletions test/realtime/transports.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai, Ably) {
const expect = chai.expect;
const Defaults = Ably.Rest.Platform.Defaults;
const originialWsCheckUrl = Defaults.wsConnectivityUrl;
const originialWsCheckUrl = Defaults.wsConnectivityCheckUrl;
const transportPreferenceName = 'ably-transport-preference';
const localStorageSupported = globalThis.localStorage;
const urlScheme = 'https://';
Expand All @@ -20,8 +20,9 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai
}).connection.connectionManager.baseTransport;
}

function restoreWsConnectivityUrl() {
Defaults.wsConnectivityUrl = originialWsCheckUrl;
function restoreWsConnectivityCheckUrl() {
Helper.forHook(this).recordPrivateApi('write.Defaults.wsConnectivityCheckUrl');
Defaults.wsConnectivityCheckUrl = originialWsCheckUrl;
}

const Config = Ably.Rest.Platform.Config;
Expand Down Expand Up @@ -50,7 +51,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai
});
});

afterEach(restoreWsConnectivityUrl);
afterEach(restoreWsConnectivityCheckUrl);
afterEach(restoreWebSocketConstructor);

if (
Expand Down Expand Up @@ -163,14 +164,13 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai
helper.recordPrivateApi('read.realtime.options.realtimeHost');
const goodHost = helper.AblyRest().options.realtimeHost;

// use unroutable host ws connectivity check to simulate no internet
helper.recordPrivateApi('write.Defaults.wsConnectivityUrl');
Defaults.wsConnectivityUrl = `wss://${helper.unroutableAddress}`;

helper.recordPrivateApi('pass.clientOption.webSocketSlowTimeout');
helper.recordPrivateApi('pass.clientOption.wsConnectivityCheckUrl');
const realtime = helper.AblyRealtime(
options(helper, {
realtimeHost: helper.unroutableAddress,
// use unroutable host ws connectivity check to simulate no internet
wsConnectivityCheckUrl: helper.unroutableWssAddress,
// ensure ws slow timeout procs and performs ws connectivity check, which would fail due to unroutable host
webSocketSlowTimeout: 1,
// give up trying to connect fairly quickly
Expand Down Expand Up @@ -198,8 +198,8 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai
// restore original settings
helper.recordPrivateApi('replace.connectionManager.tryATransport');
connection.connectionManager.tryATransport = tryATransportOriginal;
helper.recordPrivateApi('write.Defaults.wsConnectivityUrl');
Defaults.wsConnectivityUrl = originialWsCheckUrl;
helper.recordPrivateApi('write.realtime.options.wsConnectivityCheckUrl');
realtime.options.wsConnectivityCheckUrl = originialWsCheckUrl;
helper.recordPrivateApi('write.realtime.options.realtimeHost');
realtime.options.realtimeHost = goodHost;
helper.recordPrivateApi('write.connectionManager.wsHosts');
Expand Down

0 comments on commit 6e85f91

Please sign in to comment.