gremlin-aws-sigv4-ts
Version:
This package provides an extension for Apache's TinkerPop3 Gremlin [javascript driver](https://github.com/apache/tinkerpop/tree/master/gremlin-javascript/src/main/javascript/gremlin-javascript) compatible with [IAM Database Authentication for Neptune](htt
166 lines (148 loc) • 4.92 kB
JavaScript
const debug = require('debug')('gremlin-aws-sigv4:driver');
const gremlin = require('gremlin');
const { getUrlAndHeaders, uuid, request } = require('../utils');
/**
* @extends {gremlin.driver.RemoteConnection}
*/
class AwsSigV4DriverRemoteConnection extends gremlin.driver.RemoteConnection {
constructor(host, port, options = {}, cbConnected = null, cbDisconnected = null, cbError = null) {
debug(`constructor: ${JSON.stringify({ host, port, options })}`);
const { url } = getUrlAndHeaders(host, port, options, '/gremlin', options.secure ? 'wss' : 'ws');
super(url);
this.host = host;
this.port = port;
this.options = options;
this.cbConnected = cbConnected;
this.cbDisconnected = cbDisconnected;
this.cbError = cbError;
this.secure = options.secure || false;
this.try = 0;
this.maxRetry = this.options.maxRetry || 10;
this.clientOptions = {
mimeType: 'application/vnd.gremlin-v2.0+json',
pingEnabled: true,
pingInterval: 1000,
pongTimeout: 2000,
...this.options,
};
this._rejections = {};
if (options.connectOnStartup != null) {
delete this.clientOptions.connectOnStartup;
}
if (options.connectOnStartup !== false) {
this._connect();
}
}
_connect() {
this.try += 1;
const { url, headers } = getUrlAndHeaders(this.host, this.port, this.options, '/status', this.secure ? 'https' : 'http');
debug(`get Neptune status: ${JSON.stringify(url, headers)} (try #${this.try})`);
request(
url,
{ headers, timeout: 3000 },
(err, response, body) => { this._statusCallback(err, response, body); },
);
}
_statusCallback(error, response, body) {
if (error) {
this._errorHandler(error);
} else if (response) {
const msg = `Neptune responded with a HTTP-${response.statusCode}`;
debug(msg);
if (body) {
debug(`Neptune status: ${body}`);
}
if (response.statusCode === 200) {
this._connectSocket();
} else {
this._errorHandler(new Error(msg));
}
} else {
const msg = 'No response or error received from request';
debug(msg);
this._errorHandler(new Error(msg));
}
}
_connectSocket() {
const { url, headers } = getUrlAndHeaders(this.host, this.port, this.options, '/gremlin', this.secure ? 'wss' : 'ws');
debug(`connect: ${JSON.stringify(url, headers)} (try #${this.try})`);
this._client = new gremlin.driver.Client(url, ({ headers, ...this.clientOptions }));
this._client._connection.on('log', (log) => this._logHandler(log));
this._client._connection.on('close', (code, message) => this._closeHandler(code, message));
this._client._connection.on('error', (error) => this._errorHandler(error));
this._client.open();
this._client._connection._ws.on('open', () => this._connectHandler());
}
_logHandler(log) { // eslint-disable-line class-methods-use-this
debug('connection event: log', { log });
}
_connectHandler() {
debug('connection/socket event: open');
if (this.cbConnected) {
this.cbConnected();
}
if (this._client._connection.isOpen) {
this.try = 0;
}
}
_closeHandler(code, message) {
debug('connection event: close', { code, message });
this._cancelPendingQueries(new Error('Neptune connection is closed'));
if (this.cbDisconnected) {
this.cbDisconnected(code, message);
}
if (this.options.autoReconnect && this.try < this.maxRetry) {
this._connect();
}
}
_errorHandler(error) {
debug('connection event: error', { error });
this._cancelPendingQueries(error);
if (this.cbError) {
this.cbError(error);
}
if (this.options.autoReconnect && this.try < this.maxRetry) {
this._connect();
} else if (error instanceof Error) {
throw error;
}
}
_cancelPendingQueries(error) {
Object.values(this._rejections).forEach((reject) => reject(error));
this._rejections = {};
}
/** @override */
open() {
debug('open');
this._connectSocket();
}
/** @override */
submit(bytecode) {
debug('submit');
return new Promise((resolve, reject) => {
const queryId = uuid();
this._rejections[queryId] = reject;
if (this._client == null) {
if (this.options.autoReconnect) {
this._connectSocket();
} else {
reject(new Error('Disconnected from database'));
}
}
this._client.submit(bytecode)
.then((result) => {
delete this._rejections[queryId];
resolve(new gremlin.driver.RemoteTraversal(result.toArray()));
})
.catch(reject);
})
.then((result) => result);
}
/** @override */
close() {
debug('close');
this.options.autoReconnect = false;
return this._client.close();
}
}
module.exports = AwsSigV4DriverRemoteConnection;