UNPKG

@fanoutio/serve-grip

Version:
225 lines (224 loc) 10.1 kB
import CallableInstance from 'callable-instance'; import debug from './debug.js'; import { GripInstruct, Publisher, validateSig, ConnectionIdMissingException, WebSocketDecodeEventException, } from '@fanoutio/grip'; import { GripInstructNotAvailableException } from './GripInstructNotAvailableException.js'; import { GripInstructAlreadyStartedException } from './GripInstructAlreadyStartedException.js'; export class ServeGripBase extends CallableInstance { gripProxies; prefix = ''; isGripProxyRequired = false; _publisherClass; _publisher; constructor(config, fn = 'run') { super(fn); this.applyConfig(config); } applyConfig(config = {}) { const { grip, gripVerifyKey, gripProxyRequired = false, prefix = '' } = config; if (this._publisher != null) { throw new Error('applyConfig called on ServeGrip that already has an instantiated publisher.'); } let gripProxies = undefined; if (grip != null) { if (grip instanceof Publisher) { // by reference gripProxies = grip; } else if (typeof grip === 'string') { if (gripVerifyKey != null) { // Add gripVerifyKey to GRIP URL if verify-key doesn't already exist on it const url = new URL(grip); if (url.searchParams.get('verify-key') == null) { const verifyKeyValue = gripVerifyKey instanceof Buffer ? 'base64:' + gripVerifyKey.toString('base64') : gripVerifyKey; url.searchParams.set('verify-key', verifyKeyValue); } gripProxies = url.toString(); } else { // copy the GRIP URL directly gripProxies = grip; } } else { gripProxies = (Array.isArray(grip) ? grip : [grip]).map(config => { const gripProxy = { ...config }; if (gripProxy.verify_key == null && gripVerifyKey != null) { gripProxy.verify_key = gripVerifyKey; } return gripProxy; }); } } this.gripProxies = gripProxies; this.isGripProxyRequired = gripProxyRequired; this.prefix = prefix; this._publisherClass = config.publisherClass; } getPublisher() { debug('ServeGrip#getPublisher - start'); if (this._publisher == null) { let publisher; if (this.gripProxies == null) { debug('ServeGrip#getPublisher - ERROR - no grip proxies specified'); throw new Error('No Grip configuration provided. Provide one to the constructor of ServeGrip, or call applyConfig() with a Grip configuration, before calling getPublisher().'); } if (this.gripProxies instanceof Publisher) { debug('ServeGrip#getPublisher - initializing with existing publisher'); publisher = this.gripProxies; } else { debug('ServeGrip#getPublisher - initializing with grip settings', this.gripProxies); publisher = new (this._publisherClass ?? Publisher)(undefined, { prefix: this.prefix }); publisher.applyConfigs(this.gripProxies); } debug('ServeGrip#getPublisher - instantiating prefixed publisher'); this._publisher = publisher; } else { debug('returning publisher'); } debug('ServeGrip#getPublisher - end'); return this._publisher; } async run(req, res) { debug('ServeGrip#run - start'); if (this.getRequestGrip(req) != null) { // This would indicate that we are already running for this request. // We don't install ourselves multiple times. debug('Already ran for this request, returning true'); return true; } try { // Config check if (this.gripProxies == null) { debug('ERROR - No Grip configuration provided. Send error, returning false'); this.setResponseStatus(res, 500); this.endResponse(res, 'No Grip configuration provided.\n'); return false; } debug("gripProxies", this.gripProxies); // ## Set up req.grip debug('Set up req.grip - start'); const gripSigHeader = this.getRequestHeaderValue(req, 'grip-sig'); let isProxied = false; let isSigned = false; let needsSigned = false; if (gripSigHeader !== undefined) { debug('grip-sig header exists'); const publisher = this.getPublisher(); const clients = publisher.clients; if (clients.length > 0) { if (clients.every((client) => client.getVerifyKey?.() != null)) { needsSigned = true; // If all proxies have keys, then only consider the request // signed if at least one of them has signed it if (clients.some((client) => validateSig(gripSigHeader, client.getVerifyKey?.() ?? '', client.getVerifyIss?.()))) { isProxied = true; isSigned = true; } } else { isProxied = true; } } } if (isProxied) { debug('Request is proxied'); } else { debug('Request is not proxied'); } if (isSigned) { debug('Request is signed'); } else { debug('Request is not signed'); } if (!isProxied && this.isGripProxyRequired) { // If we require a GRIP proxy but we detect there is // not one, we needs to fail now debug('ERROR - isGripProxyRequired is true, but is not proxied. Send error, returning false.'); this.setResponseStatus(res, 501); this.endResponse(res, 'Not Implemented.\n'); return false; } let wsContext = null; if (this.isRequestWsOverHttp(req)) { try { wsContext = await this.getRequestWebSocketContext(req); } catch (ex) { if (ex instanceof ConnectionIdMissingException) { debug("ERROR - connection-id header needed. Send Error, returning false"); this.setResponseStatus(res, 400); this.endResponse(res, 'WebSocket event missing connection-id header.\n'); return false; } if (ex instanceof WebSocketDecodeEventException) { debug("ERROR - error parsing websocket events. Send Error, returning false"); this.setResponseStatus(res, 400); this.endResponse(res, 'Error parsing WebSocket events.\n'); return false; } debug("ERROR - unknown exception getting web socket context from request"); debug(ex); this.setResponseStatus(res, 400); this.endResponse(res, 'Error getting web socket Context.\n'); return false; } } this.setRequestGrip(req, { isProxied, isSigned, needsSigned, wsContext, }); debug('Set up req.grip - end'); // ## Set up res.grip debug('Set up res.grip - start'); let gripInstruct = null; this.setResponseGrip(res, { startInstruct() { try { debug('startInstruct - start'); // In WebSocket-over-HTTP or if request is not proxied, // startInstruct is not available. if (wsContext == null && isProxied) { if (gripInstruct != null) { debug('ERROR - GripInstruct is already started'); throw new GripInstructAlreadyStartedException(); } debug('Creating GripInstruct'); gripInstruct = new GripInstruct(); return gripInstruct; } else { debug('ERROR - GripInstruct is not available'); throw new GripInstructNotAvailableException(); } } finally { debug('startInstruct - end'); } }, }); debug('Set up res.grip - end'); // ## Monkey-patch res methods if (wsContext != null) { debug('Monkey-patch res methods for WS-over-HTTP - start'); this.monkeyPatchResMethodsForWebSocket(res, wsContext); debug('Monkey-patch res methods for WS-over-HTTP - end'); } else { debug('Monkey-patch res methods for GripInstruct - start'); this.monkeyPatchResMethodsForGripInstruct(res, () => gripInstruct); debug('Monkey-patch res methods for GripInstruct - end'); } } catch (ex) { throw ex instanceof Error ? ex : new Error(String(ex)); } debug('ServeGrip#run - end'); return true; } }