att-client
Advanced tools
Comparing version 0.2.17 to 0.2.18
@@ -58,2 +58,3 @@ "use strict"; | ||
webSocketPingInterval: config.webSocketPingInterval ?? constants_1.DEFAULTS.webSocketPingInterval, | ||
webSocketRecoveryRetryDelay: config.webSocketRecoveryRetryDelay ?? constants_1.DEFAULTS.webSocketRecoveryRetryDelay, | ||
webSocketRequestAttempts: config.webSocketRequestAttempts ?? constants_1.DEFAULTS.webSocketRequestAttempts, | ||
@@ -60,0 +61,0 @@ webSocketRequestRetryDelay: config.webSocketRequestRetryDelay ?? constants_1.DEFAULTS.webSocketRequestRetryDelay, |
@@ -18,2 +18,3 @@ import type { Verbosity } from '../Logger'; | ||
webSocketPingInterval?: number; | ||
webSocketRecoveryRetryDelay?: number; | ||
webSocketRequestAttempts?: number; | ||
@@ -20,0 +21,0 @@ webSocketRequestRetryDelay?: number; |
@@ -14,4 +14,5 @@ "use strict"; | ||
const WEBSOCKET_PING_INTERVAL = 5 * MINUTE; | ||
const WEBSOCKET_RECOVERY_RETRY_DELAY = 5 * SECOND; | ||
const WEBSOCKET_REQUEST_ATTEMPTS = 3; | ||
const WEBSOCKET_REQUEST_RETRY_DELAY = 2 * SECOND; | ||
const WEBSOCKET_REQUEST_RETRY_DELAY = 3 * SECOND; | ||
const WEBSOCKET_URL = 'wss://5wx2mgoj95.execute-api.ap-southeast-2.amazonaws.com/dev'; | ||
@@ -31,2 +32,3 @@ const X_API_KEY = '2l6aQGoNes8EHb94qMhqQ5m2iaiOM9666oDTPORf'; | ||
webSocketPingInterval: WEBSOCKET_PING_INTERVAL, | ||
webSocketRecoveryRetryDelay: WEBSOCKET_RECOVERY_RETRY_DELAY, | ||
webSocketRequestAttempts: WEBSOCKET_REQUEST_ATTEMPTS, | ||
@@ -33,0 +35,0 @@ webSocketRequestRetryDelay: WEBSOCKET_REQUEST_RETRY_DELAY, |
@@ -108,3 +108,3 @@ "use strict"; | ||
const message = { id, content: command }; | ||
this.logger.debug(`Sending command to ${this.server.id} (${this.server.name}).`, message); | ||
this.logger.debug(`Sending command-${id} to ${this.server.id} (${this.server.name}).`, message); | ||
this.ws.send(JSON.stringify(message), error => typeof error !== 'undefined' && reject(error)); | ||
@@ -111,0 +111,0 @@ }); |
@@ -6,3 +6,3 @@ import type { Client } from '../Client'; | ||
client: Client; | ||
migration: Promise<void>; | ||
halted: Promise<void>; | ||
private events; | ||
@@ -9,0 +9,0 @@ private logger; |
@@ -12,3 +12,3 @@ "use strict"; | ||
this.client = client; | ||
this.migration = Promise.resolve(); | ||
this.halted = Promise.resolve(); | ||
this.messageId = 1; | ||
@@ -71,4 +71,4 @@ this.subscriptions = {}; | ||
clearInterval(interval); | ||
/* Migrations close WebSocket with code 3000. */ | ||
if (code !== 3000) { | ||
/* Migrations close WebSocket with code 3000 or 3001. */ | ||
if (code !== 3000 && code !== 3001) { | ||
that.logger.error(`WebSocket closed abnormally with code ${code}: ${reason}`); | ||
@@ -112,3 +112,3 @@ that.logger.info('Restarting WebSocket and subscriptions.'); | ||
interval = setInterval(() => { | ||
that.ping(ws); | ||
that.ping(this); | ||
}, that.client.config.webSocketPingInterval); | ||
@@ -132,3 +132,3 @@ resolve(); | ||
async migrate() { | ||
await this.migration; // Prevent parallel migrations. | ||
await this.halted; | ||
if (typeof this.ws === 'undefined') { | ||
@@ -156,3 +156,3 @@ this.logger.warn('There is no WebSocket to migrate. Creating new WebSocket.'); | ||
/* Track migration state. Will halt all outbound messages. */ | ||
this.migration = new Promise(resolve => { | ||
this.halted = new Promise(resolve => { | ||
this.resolveMigration = resolve; | ||
@@ -163,3 +163,4 @@ }); | ||
delete this.ws; | ||
await this.init(); | ||
this.ws = await this.createWebSocket(this.client.accessToken); | ||
await this.registerEventHandlers(this.ws); | ||
/* Send the migration token over new WebSocket. */ | ||
@@ -170,2 +171,4 @@ try { | ||
catch (error) { | ||
this.ws.removeAllListeners(); | ||
this.ws.close(3001, 'Migration aborted.'); | ||
delete this.ws; | ||
@@ -180,10 +183,6 @@ this.ws = oldWs; | ||
/* Gracefully discard old WebSocket instance. */ | ||
await new Promise(resolve => { | ||
this.logger.info(`Successfully migrated WebSocket. Gracefully shutting down old WebSocket in ${this.client.config.webSocketMigrationHandoverPeriod} ms.`); | ||
setTimeout(() => { | ||
oldWs.close(3000, 'Migration completed.'); | ||
this.logger.info(`Closed old WebSocket.`); | ||
resolve(true); | ||
}, this.client.config.webSocketMigrationHandoverPeriod); | ||
}); | ||
this.logger.info(`Successfully migrated WebSocket. Gracefully shutting down old WebSocket in ${this.client.config.webSocketMigrationHandoverPeriod} ms.`); | ||
await new Promise(resolve => setTimeout(resolve, this.client.config.webSocketMigrationHandoverPeriod)); | ||
oldWs.close(3000, 'Migration completed.'); | ||
this.logger.info(`Closed old WebSocket.`); | ||
} | ||
@@ -202,7 +201,7 @@ /** | ||
if (message.event === 'response' && message.responseCode === 200 && message.key === 'POST /ws/migrate') { | ||
return resolve(message.content); | ||
resolve(message.content); | ||
} | ||
else { | ||
this.logger.error(message.message); | ||
return reject(); | ||
this.logger.error('Something went wrong posting the WebSocket migration token. Received message:', JSON.stringify(message, null, 2)); | ||
reject(); | ||
} | ||
@@ -218,8 +217,4 @@ }); | ||
this.logger.error(`Client failed to migrate WebSocket. Retrying in ${this.client.config.webSocketMigrationRetryDelay} ms.`); | ||
await new Promise(resolve => { | ||
setTimeout(async () => { | ||
await this.migrate(); | ||
resolve(true); | ||
}, this.client.config.webSocketMigrationRetryDelay); | ||
}); | ||
await new Promise(resolve => setTimeout(resolve, this.client.config.webSocketMigrationRetryDelay)); | ||
await this.migrate(); | ||
} | ||
@@ -242,5 +237,18 @@ /** | ||
async recoverWebSocket() { | ||
await this.halted; | ||
if (typeof this.client.accessToken === 'undefined') { | ||
this.logger.warn("Can't migrate WebSocket without an access token. Ordering client to refresh tokens."); | ||
await this.client.refreshTokens(); | ||
await this.recoverWebSocket(); | ||
return; | ||
} | ||
this.logger.info('Recovering WebSocket connection.'); | ||
/* Track recovery state. Will halt all outbound messages. */ | ||
this.halted = new Promise(resolve => { | ||
this.resolveMigration = resolve; | ||
}); | ||
/* Create new WebSocket */ | ||
await this.init(); | ||
delete this.ws; | ||
this.ws = await this.createWebSocket(this.client.accessToken); | ||
await this.registerEventHandlers(this.ws); | ||
/* Unblock all backed-up messages. */ | ||
@@ -252,5 +260,19 @@ this.clearMigration(); | ||
/* Resubscribe to all saved subscriptions. */ | ||
for (const [entry, callback] of Object.entries(subscriptions)) { | ||
const resubscriptions = await Promise.allSettled(Object.entries(subscriptions).map(([entry, callback]) => { | ||
const [subscription, key] = entry.split('/'); | ||
subscription && key && this.subscribe(subscription, key, callback); | ||
if (typeof subscription !== 'string' || typeof key !== 'string') | ||
return; | ||
this.events.removeAllListeners(entry); | ||
return this.subscribe(subscription, key, callback); | ||
})); | ||
/* Verify resubscriptions. */ | ||
if (resubscriptions.some(resub => resub.status === 'rejected')) { | ||
/* WebSocket recovery has failed. */ | ||
this.halted = new Promise(resolve => { | ||
this.resolveMigration = resolve; | ||
}); | ||
this.logger.error(`WebSocket recovery failed! Retrying recovery in ${this.client.config.webSocketRecoveryRetryDelay} ms.`); | ||
await new Promise(resolve => setTimeout(resolve, this.client.config.webSocketRecoveryRetryDelay)); | ||
this.clearMigration(); | ||
await this.recoverWebSocket(); | ||
} | ||
@@ -269,3 +291,3 @@ } | ||
if (path !== 'migrate') | ||
await this.migration; | ||
await this.halted; | ||
return new Promise((resolve, reject) => { | ||
@@ -272,0 +294,0 @@ if (typeof this.client.accessToken === 'undefined' || typeof this.ws === 'undefined') { |
{ | ||
"name": "att-client", | ||
"version": "0.2.17", | ||
"version": "0.2.18", | ||
"description": "Node bot library for A Township Tale, a VR game by Alta Reality Pty Ltd.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
97383
2304