UNPKG

amplify-appsync-simulator

Version:

An AppSync Simulator to test AppSync API.

190 lines 8.01 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SubscriptionServer = void 0; const chalk_1 = __importDefault(require("chalk")); const crypto_1 = __importDefault(require("crypto")); const promise_toolbox_1 = require("promise-toolbox"); const http_1 = require("http"); const ip_1 = require("ip"); const util_1 = require("util"); const mqtt_server_1 = require("./subscription/mqtt-server"); const get_port_1 = __importDefault(require("get-port")); const MINUTE = 1000 * 60; const CONNECTION_TIMEOUT = 2 * MINUTE; const TOPIC_EXPIRATION_TIMEOUT = 60 * MINUTE; const BASE_PORT = 8900; const MAX_PORT = 9999; const log = console; class SubscriptionServer { constructor(config, appSyncServerContext) { this.config = config; this.appSyncServerContext = appSyncServerContext; this.port = config.wsPort; this.mqttWebSocketServer = (0, http_1.createServer)(); this.mqttServer = new mqtt_server_1.MQTTServer({ logger: { level: process.env.DEBUG ? 'debug' : 'error', }, }); this.mqttServer.attachHttpServer(this.mqttWebSocketServer); this.clientRegistry = new Map(); this.mqttIteratorTimeout = new Map(); this.mqttServer.on('clientConnected', this.afterMQTTClientConnect.bind(this)); this.mqttServer.on('clientDisconnected', this.afterMQTTClientDisconnect.bind(this)); this.mqttServer.on('subscribed', this.afterSubscription.bind(this)); this.mqttServer.on('unsubscribed', this.afterMQTTClientUnsubscribe.bind(this)); this.realtimeSocketServer = (0, http_1.createServer)(); } async start() { if (!this.port) { this.port = await (0, get_port_1.default)({ port: get_port_1.default.makeRange(BASE_PORT, MAX_PORT), }); } else { try { await (0, get_port_1.default)({ port: this.port, }); } catch (e) { throw new Error(`Port ${this.port} is already in use. Please kill the program using this port and restart Mock`); } } const server = this.mqttWebSocketServer.listen(this.port); return await (0, promise_toolbox_1.fromEvent)(server, 'listening').then(() => { const address = server.address(); this.url = `ws://${(0, ip_1.address)()}:${address.port}/`; return server; }); } stop() { if (this.mqttWebSocketServer) { this.mqttWebSocketServer.close(); this.url = null; this.mqttWebSocketServer = null; } } async afterMQTTClientConnect(client) { const { id: clientId } = client; log.info(`Client (${chalk_1.default.bold(clientId)}) connected to subscription server`); const timeout = this.mqttIteratorTimeout.get(client.id); if (timeout) { clearTimeout(timeout); } } async afterSubscription(topic, client) { const { id: clientId } = client; log.info(`Client (${chalk_1.default.bold(clientId)}) subscribed to topic ${topic}`); const regs = this.clientRegistry.get(clientId); if (!regs) { log.error(`No registration for client (${chalk_1.default.bold(clientId)})`); return; } const reg = regs.find(({ topicId }) => topicId === topic); if (!reg) { log.error(`Client (${chalk_1.default.bold(clientId)}) tried to subscribe to non-existent topic ${topic}`); return; } const { asyncIterator, topicId } = reg; if (!reg.isRegistered) { this.register(reg.document, reg.variables, reg.context, asyncIterator); } while (true) { let { value: payload } = await asyncIterator.next(); log.info(`Publishing payload for topic ${topicId}`); log.log('Payload:'); log.log((0, util_1.inspect)(payload)); this.mqttServer.publish({ topic: topicId, payload: JSON.stringify(payload), qos: 0, retain: false, }); } } afterMQTTClientUnsubscribe(topic, client) { const { id: clientId } = client; log.info(`Client (${chalk_1.default.bold(clientId)}) unsubscribed from topic ${topic}`); const registration = this.clientRegistry.get(clientId); if (!registration) { log.error(`No registration for client (${chalk_1.default.bold(clientId)})`); return; } const reg = registration.find(({ topicId }) => topicId === topic); if (!reg) { log.error(`Client (${chalk_1.default.bold(clientId)}) tried to unsubscribe from non-existent topic ${topic}`); return; } reg.asyncIterator.return(); reg.isRegistered = false; } afterMQTTClientDisconnect(client) { const { id: clientId } = client; log.info(`Client (${chalk_1.default.bold(clientId)}) disconnected`); const reg = this.clientRegistry.get(clientId); if (!reg) { log.error(`Unregistered client (${chalk_1.default.bold(clientId)}) disconnected`); } reg.forEach(subscription => { subscription.asyncIterator.return(); }); this.clientRegistry.delete(clientId); } async register(document, variables, context, asyncIterator) { const connection = context.request.connection; const remoteAddress = `${connection.remoteAddress}:${connection.remotePort}`; const clientId = crypto_1.default.createHash('MD5').update(remoteAddress).digest().toString('hex'); const subscriptionName = document.definitions[0].selectionSet.selections.find(s => s.kind === 'Field').name.value; const paramHash = variables && Object.keys(variables).length ? crypto_1.default.createHash('MD5').update(JSON.stringify(variables)).digest().toString('hex') : null; const topicId = [clientId, subscriptionName, paramHash].join('/'); log.info(`Client (${chalk_1.default.bold(clientId)}) registered for topic ${topicId}`); const registration = { context, document, variables, topicId, asyncIterator: asyncIterator, isRegistered: true, }; const currentRegistrations = this.clientRegistry.get(clientId) || []; const existingSubscription = currentRegistrations.find(reg => reg.topicId === topicId); if (!existingSubscription) { currentRegistrations.push(registration); this.clientRegistry.set(clientId, currentRegistrations); this.mqttIteratorTimeout.set(clientId, setTimeout(() => { asyncIterator.return(); this.mqttIteratorTimeout.delete(clientId); }, CONNECTION_TIMEOUT)); } else { Object.assign(existingSubscription, registration); } return { extensions: { subscription: { mqttConnections: [ { url: this.url, topics: currentRegistrations.map(reg => reg.topicId), client: clientId, }, ], newSubscriptions: { [subscriptionName]: { topic: topicId, expireTime: Date.now() + TOPIC_EXPIRATION_TIMEOUT, }, }, }, }, }; } } exports.SubscriptionServer = SubscriptionServer; //# sourceMappingURL=subscription.js.map