@slack/socket-mode
Advanced tools
Comparing version 1.3.0-rc.0 to 1.3.0-rc.1
@@ -32,3 +32,4 @@ import { EventEmitter } from 'eventemitter3'; | ||
/** | ||
* Begin a Socket Mode session. | ||
* Start a Socket Mode session app. | ||
* It may take a few milliseconds before being connected. | ||
* This method must be called before any messages can be sent or received. | ||
@@ -38,2 +39,8 @@ */ | ||
/** | ||
* Start a Socket Mode session asynchronously. | ||
* It may take a few milliseconds before being connected. | ||
* This method must be called before any messages can be sent or received. | ||
*/ | ||
connect(): Promise<void>; | ||
/** | ||
* End a Socket Mode session. After this method is called no messages will be sent or received | ||
@@ -96,2 +103,6 @@ * unless you call start() again later. | ||
/** | ||
* This flag can be true when this client is switching to a new connection. | ||
*/ | ||
private isSwitchingConnection; | ||
/** | ||
* WebClient options we pass to our WebClient instance | ||
@@ -113,4 +124,8 @@ * We also reuse agent and tls for our WebSocket connection | ||
private handleConnectionFailure; | ||
private handleDisconnection; | ||
private markCurrentWebSocketAsInactive; | ||
/** | ||
* Clean up all the remaining connections. | ||
*/ | ||
private terminateAllConnections; | ||
/** | ||
* Set up method for the client's WebSocket instance. This method will attach event listeners. | ||
@@ -122,8 +137,12 @@ */ | ||
*/ | ||
private tearDownHeartBeatJobs; | ||
private terminateActiveHeartBeatJobs; | ||
/** | ||
* Switch the active connection to the secondary if exists. | ||
*/ | ||
private switchWebSocketConnection; | ||
/** | ||
* Tear down method for the client's WebSocket instance. | ||
* This method undoes the work in setupWebSocket(url). | ||
*/ | ||
private tearDownWebSocket; | ||
private terminateWebSocketSafely; | ||
private startPeriodicallySendingPingToSlack; | ||
@@ -130,0 +149,0 @@ private handlePingPongErrorReconnection; |
@@ -88,7 +88,4 @@ "use strict"; | ||
.onEnter(() => { | ||
if (this.badConnection) { | ||
// The state arrived here because Event.ServerPingTimeout occurred | ||
// and a new connection was created. | ||
// Tearing down the old connection. | ||
this.tearDownWebSocket(); | ||
if (this.isSwitchingConnection) { | ||
this.switchWebSocketConnection(); | ||
this.badConnection = false; | ||
@@ -128,3 +125,3 @@ } | ||
.onEnter(() => { | ||
this.logger.info('Going to establish a new connectiont to Slack ...'); | ||
this.logger.info('Going to establish a new connection to Slack ...'); | ||
}) | ||
@@ -136,7 +133,7 @@ .submachine(this.connectingStateMachineConfig) | ||
.transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this)) | ||
.transitionTo(State.Disconnected).withAction(this.handleDisconnection.bind(this)) | ||
.transitionTo(State.Disconnecting) | ||
.on(Event.ExplicitDisconnect) | ||
.transitionTo(State.Disconnecting) | ||
.on(Event.Failure) | ||
.transitionTo(State.Disconnected) | ||
.on(Event.ExplicitDisconnect) | ||
.transitionTo(State.Disconnecting) | ||
.state(State.Connected) | ||
@@ -148,34 +145,25 @@ .onEnter(() => { | ||
.submachine(this.connectedStateMachineConfig) | ||
.on(Event.ServerDisconnectWarning) | ||
.transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this)) | ||
.on(Event.WebSocketClose) | ||
.transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this)) | ||
.transitionTo(State.Disconnected).withAction(this.handleDisconnection.bind(this)) | ||
.transitionTo(State.Reconnecting) | ||
.withCondition(this.autoReconnectCondition.bind(this)) | ||
.withAction(() => this.markCurrentWebSocketAsInactive()) | ||
.transitionTo(State.Disconnecting) | ||
.on(Event.ExplicitDisconnect) | ||
.transitionTo(State.Disconnecting) | ||
.on(Event.ServerDisconnectWarning) | ||
.transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this)) | ||
.withAction(() => this.markCurrentWebSocketAsInactive()) | ||
.on(Event.ServerPingsNotReceived) | ||
.transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this)) | ||
.transitionTo(State.Disconnecting) | ||
.on(Event.ServerPongsNotReceived) | ||
.transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this)) | ||
.transitionTo(State.Disconnecting) | ||
.on(Event.ServerDisconnectWarning) | ||
.transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this)) | ||
.transitionTo(State.Disconnecting) | ||
.on(Event.ServerDisconnectOldSocket) | ||
.transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this)) | ||
.transitionTo(State.Disconnecting) | ||
.onExit(() => { | ||
this.connected = false; | ||
this.authenticated = false; | ||
this.tearDownHeartBeatJobs(); | ||
this.terminateActiveHeartBeatJobs(); | ||
}) | ||
.state(State.Disconnecting) | ||
.onEnter(() => { | ||
// Most of the time, a WebSocket will exist. | ||
// The only time it does not is when transitioning from connecting, | ||
// before the client.start() has finished and the WebSocket hasn't been set up. | ||
if (this.websocket !== undefined) { | ||
this.websocket.close(); | ||
} | ||
}) | ||
.on(Event.WebSocketClose) | ||
.transitionTo(State.Disconnected) | ||
.onExit(() => this.tearDownWebSocket()) | ||
.state(State.Reconnecting) | ||
@@ -186,6 +174,17 @@ .onEnter(() => { | ||
.do(async () => { | ||
this.badConnection = true; | ||
this.tearDownHeartBeatJobs(); | ||
this.isSwitchingConnection = true; | ||
}) | ||
.onSuccess().transitionTo(State.Connecting) | ||
.onFailure().transitionTo(State.Failed) | ||
.state(State.Disconnecting) | ||
.onEnter(() => { | ||
this.logger.info('Disconnecting ...'); | ||
}) | ||
.do(async () => { | ||
this.terminateActiveHeartBeatJobs(); | ||
this.terminateAllConnections(); | ||
this.logger.info('Disconnected from Slack'); | ||
}) | ||
.onSuccess().transitionTo(State.Disconnected) | ||
.onFailure().transitionTo(State.Failed) | ||
.getConfig(); | ||
@@ -196,2 +195,6 @@ /** | ||
this.badConnection = false; | ||
/** | ||
* This flag can be true when this client is switching to a new connection. | ||
*/ | ||
this.isSwitchingConnection = false; | ||
if (appToken === undefined) { | ||
@@ -232,7 +235,8 @@ throw new Error('Must provide an App-Level Token when initializing a Socket Mode Client'); | ||
/** | ||
* Begin a Socket Mode session. | ||
* Start a Socket Mode session app. | ||
* It may take a few milliseconds before being connected. | ||
* This method must be called before any messages can be sent or received. | ||
*/ | ||
start() { | ||
this.logger.debug('Starting a Socket Mode client'); | ||
this.logger.debug('Starting a Socket Mode client ...'); | ||
// Delegate behavior to state machine | ||
@@ -253,2 +257,19 @@ this.stateMachine.handle(Event.Start); | ||
/** | ||
* Start a Socket Mode session asynchronously. | ||
* It may take a few milliseconds before being connected. | ||
* This method must be called before any messages can be sent or received. | ||
*/ | ||
connect() { | ||
return new Promise((resolve, reject) => { | ||
try { | ||
// The state machine handles all the WebSocket releated operations. | ||
this.stateMachine.handle(Event.Start); | ||
resolve(); | ||
} | ||
catch (e) { | ||
reject(e); | ||
} | ||
}); | ||
} | ||
/** | ||
* End a Socket Mode session. After this method is called no messages will be sent or received | ||
@@ -345,11 +366,28 @@ * unless you call start() again later. | ||
handleConnectionFailure(_state, context) { | ||
this.logger.error(`The internal logic unexpectedly failed (error: ${context.error})`); | ||
// Terminate everything, just in case | ||
this.terminateActiveHeartBeatJobs(); | ||
this.terminateAllConnections(); | ||
// dispatch 'failure' on parent machine to transition out of this submachine's states | ||
this.stateMachine.handle(Event.Failure, context.error); | ||
} | ||
handleDisconnection() { | ||
// This transition circumvents the 'disconnecting' state | ||
// (since the websocket is already closed), so we need to execute its onExit behavior here. | ||
this.tearDownWebSocket(); | ||
markCurrentWebSocketAsInactive() { | ||
this.badConnection = true; | ||
this.connected = false; | ||
this.authenticated = false; | ||
} | ||
/** | ||
* Clean up all the remaining connections. | ||
*/ | ||
terminateAllConnections() { | ||
if (this.secondaryWebsocket !== undefined) { | ||
this.terminateWebSocketSafely(this.secondaryWebsocket); | ||
this.secondaryWebsocket = undefined; | ||
} | ||
if (this.websocket !== undefined) { | ||
this.terminateWebSocketSafely(this.websocket); | ||
this.websocket = undefined; | ||
} | ||
} | ||
/** | ||
* Set up method for the client's WebSocket instance. This method will attach event listeners. | ||
@@ -369,8 +407,8 @@ */ | ||
else { | ||
// setup secondary websocket | ||
// this is used when creating a new connection because the first is about to disconnect | ||
// Set up secondary websocket | ||
// This is used when creating a new connection because the first is about to disconnect | ||
this.secondaryWebsocket = new ws_1.default(url, options); | ||
websocket = this.secondaryWebsocket; | ||
} | ||
// attach event listeners | ||
// Attach event listeners | ||
websocket.addEventListener('open', (event) => { | ||
@@ -408,17 +446,20 @@ this.stateMachine.handle(Event.WebSocketOpen, event); | ||
*/ | ||
tearDownHeartBeatJobs() { | ||
terminateActiveHeartBeatJobs() { | ||
if (this.serverPingTimeout !== undefined) { | ||
clearTimeout(this.serverPingTimeout); | ||
this.serverPingTimeout = undefined; | ||
this.logger.debug('Cancelled the job waiting for ping from Slack'); | ||
} | ||
if (this.clientPingTimeout !== undefined) { | ||
clearTimeout(this.clientPingTimeout); | ||
this.clientPingTimeout = undefined; | ||
this.logger.debug('Terminated the heart beat job'); | ||
} | ||
} | ||
/** | ||
* Tear down method for the client's WebSocket instance. | ||
* This method undoes the work in setupWebSocket(url). | ||
* Switch the active connection to the secondary if exists. | ||
*/ | ||
tearDownWebSocket() { | ||
switchWebSocketConnection() { | ||
if (this.secondaryWebsocket !== undefined && this.websocket !== undefined) { | ||
this.logger.debug('Since the secondary WebSocket exists, going to tear down the first and assign second ...'); | ||
this.logger.debug('Switching to the secondary connection ...'); | ||
// Currently have two WebSocket objects, so tear down the older one | ||
@@ -429,32 +470,27 @@ const oldWebsocket = this.websocket; | ||
this.secondaryWebsocket = undefined; | ||
this.logger.debug('Switched to the secondary connection'); | ||
// Swithcing the connection is done | ||
this.isSwitchingConnection = false; | ||
// Clean up the old one | ||
try { | ||
oldWebsocket.removeAllListeners('open'); | ||
oldWebsocket.removeAllListeners('close'); | ||
oldWebsocket.removeAllListeners('error'); | ||
oldWebsocket.removeAllListeners('message'); | ||
oldWebsocket.close(); | ||
oldWebsocket.terminate(); | ||
} | ||
catch (e) { | ||
this.logger.error(`Failed to terminate the old WS connection (error: ${e})`); | ||
} | ||
this.terminateWebSocketSafely(oldWebsocket); | ||
this.logger.debug('Terminated the old connection'); | ||
} | ||
else if (this.secondaryWebsocket === undefined && this.websocket !== undefined) { | ||
this.logger.debug('Since only the primary WebSocket exists, going to tear it down ...'); | ||
// The only one WebSocket to tear down | ||
} | ||
/** | ||
* Tear down method for the client's WebSocket instance. | ||
* This method undoes the work in setupWebSocket(url). | ||
*/ | ||
terminateWebSocketSafely(websocket) { | ||
if (websocket !== undefined) { | ||
try { | ||
this.websocket.removeAllListeners('open'); | ||
this.websocket.removeAllListeners('close'); | ||
this.websocket.removeAllListeners('error'); | ||
this.websocket.removeAllListeners('message'); | ||
this.websocket.close(); | ||
this.websocket.terminate(); | ||
websocket.removeAllListeners('open'); | ||
websocket.removeAllListeners('close'); | ||
websocket.removeAllListeners('error'); | ||
websocket.removeAllListeners('message'); | ||
websocket.terminate(); | ||
} | ||
catch (e) { | ||
this.logger.error(`Failed to terminate the old WS connection (error: ${e})`); | ||
this.logger.error(`Failed to terminate a connection (error: ${e})`); | ||
} | ||
this.websocket = undefined; | ||
} | ||
this.logger.debug('Tearing down the old WebSocket connection has finished'); | ||
} | ||
@@ -500,2 +536,3 @@ startPeriodicallySendingPingToSlack() { | ||
}, this.clientPingTimeoutMillis / 3); | ||
this.logger.debug('Started running a new heart beat job'); | ||
} | ||
@@ -505,2 +542,3 @@ } | ||
try { | ||
this.badConnection = true; | ||
this.stateMachine.handle(Event.ServerPongsNotReceived); | ||
@@ -572,4 +610,2 @@ } | ||
this.stateMachine.handle(Event.ServerDisconnectOldSocket); | ||
// TODO: instead of using this event to reassign secondaryWebsocket to this.websocket, | ||
// use the WebSocket close event | ||
return; | ||
@@ -576,0 +612,0 @@ } |
{ | ||
"name": "@slack/socket-mode", | ||
"version": "1.3.0-rc.0", | ||
"version": "1.3.0-rc.1", | ||
"description": "Official library for using the Slack Platform's Socket Mode API", | ||
@@ -5,0 +5,0 @@ "author": "Slack Technologies, LLC", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
83076
1016