serverless-offline-msk
Version:
A serverless offline plugin that enables AWS MSK events
172 lines (146 loc) • 5 kB
JavaScript
'use strict';
/**
* Connection
*/
const WS = require('isomorphic-ws');
class Connection {
constructor(sdk) {
this.sdk = sdk;
this.connection = null;
this.context = {};
}
isConnected() {
return !!this.connection;
}
/**
* Connects to websockets and returns the connection with event handlers .on('open', 'message', 'error')
*/
async connect(options) {
const self = this;
if (options.orgName) {
this.sdk.context.orgName = options.orgName;
}
if (options.orgUid) {
this.sdk.context.orgUid = options.orgUid;
}
// Validate org
if (!this.sdk.context.orgName && !this.sdk.context.orgUid) {
throw new Error('You must specify an "orgName" or "orgUid" to connect');
}
// Sanitize filter
options.filter = options.filter || {};
options.filter.stageName = options.filter.stageName ? options.filter.stageName.trim() : null;
options.filter.appName = options.filter.appName ? options.filter.appName.trim() : null;
options.filter.instanceName = options.filter.instanceName
? options.filter.instanceName.trim()
: null;
options.filter.events = options.filter.events || ['*'];
// Add data to a query param
let queryParams = '?';
queryParams += `accessKey=${this.sdk.accessKey}&`;
if (this.sdk.context.orgName) {
queryParams += `orgName=${this.sdk.context.orgName}&`;
}
if (this.sdk.context.orgUid) {
queryParams += `orgUid=${this.sdk.context.orgUid}&`;
}
queryParams += `filter=${encodeURIComponent(JSON.stringify(options.filter))}&`;
// Set domain
const domain = this.sdk.getDomain('events-streaming') + queryParams;
// Instantiate websockets library
this.connection = new WS(domain);
// Handle message event
if (options.onEvent) {
this.connection.onmessage = (message) => {
message = message && message.data ? message.data : null;
if (typeof message === 'string') {
message = JSON.parse(message);
}
return options.onEvent(message);
};
}
this.connection.onclose = (closeEvent) => {
clearInterval(this.pingInterval);
this.pingInterval = null;
// to learn more about closeEvent, ref:
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
// 1000 close code is the normal close event. It is expected.
if (closeEvent.code !== 1000) {
// the most encountered unexpected close codes are 1001 & 1006.
// 1001 is for "Going Away", which is what AWS sends on idle/timeout
// not sure what causes 1006, but it happens very often
// in any case, always reconnect if it's an abnormal close event
return self.connect(options);
}
// run onDisconnect hook if provided by the user
// and move on with a clean disconnect
if (options.onDisconnect) {
return options.onDisconnect(closeEvent);
}
return null;
};
return new Promise((resolve, reject) => {
if (options.onError) {
self.connection.onerror = options.onError;
} else {
// Error handler for errors that don't have an unexpected response
self.connection.onerror = (error) => {
return reject(
new Error(`Unexpected Serverless Platform Streaming Error: ${error.message}`)
);
};
}
self.connection.onopen = () => {
// Send a `ping` every second and expect the server to respond back with
// a `pong` within 500 ms.
if (this.connection.ping) {
// NOT available in browser
this.pingInterval = setInterval(() => {
this.pingTimeout = setTimeout(() => {
clearInterval(this.pingInterval);
this.pingInterval = null;
// Use `WebSocket#terminate()`, which immediately destroys the connection,
// instead of `WebSocket#close()`, which waits for the close timer.
this.connection.terminate();
this.connection = null;
}, 500);
if (this.connection) {
this.connection.ping();
}
}, 1000);
this.connection.on('pong', () => {
clearTimeout(this.pingTimeout);
this.pingTimeout = null;
});
}
return resolve();
};
});
}
/**
* Sends data up to the websocket connection
* @param {*} data
*/
send(data) {
this.connection.send(data);
}
/**
* Terminates the websockets connection
*/
disconnect(code) {
// close with 1000 close code by default
if (this.connection) {
if (this.pingInterval) {
clearTimeout(this.pingInterval);
this.pingInterval = null;
}
if (this.pingTimeout) {
clearTimeout(this.pingTimeout);
this.pingTimeout = null;
}
this.connection.close(code || 1000);
this.connection = null;
}
}
}
module.exports = Connection;