UNPKG

amplify-appsync-simulator

Version:

An AppSync Simulator to test AppSync API.

360 lines 12.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Client = void 0; const uuid_1 = require("uuid"); const retimer_1 = __importDefault(require("retimer")); const steed_1 = __importDefault(require("steed")); class Client { constructor(connection, server) { this.connection = connection; this.server = server; this.subscriptions = {}; this.nextId = 1; this.inflight = {}; this.inflightCounter = 0; this.keepAlive = 0; this._lastDedupId = -1; this._closed = false; this._closing = false; this.logger = server.logger; this.setup(); } setup() { const connection = this.connection; connection.on('error', () => { }); const completeConnection = () => { this.setupTimer(); connection.connack({ returnCode: 0, sessionPresent: false, }); this.logger.info('client connected'); this.server.emit('clientConnected', this); connection.on('puback', packet => { this.setupTimer(); this.handlePubAck(packet); }); connection.on('pingreq', () => { this.logger.debug('pingreq'); this.setupTimer(); this.handlePingReq(); this.connection.pingresp(); }); connection.on('subscribe', packet => { this.setupTimer(); this.handleSubscribe(packet); }); connection.on('publish', packet => { this.setupTimer(); this.handlePublish(packet); }); connection.on('unsubscribe', packet => { this.setupTimer(); this.logger.info({ packet }, 'unsubscribe received'); (0, steed_1.default)().map(this, packet.unsubscriptions, this.unsubscribeMapTo, err => { if (err) { this.logger.warn(err); this.close(null, err.message); return; } connection.unsuback({ messageId: packet.messageId, }); }); }); connection.on('disconnect', () => { this.logger.debug('disconnect requested'); this.close(null, 'disconnect request'); }); connection.on('error', this.handleError.bind(this)); connection.removeListener('error', () => { }); connection.on('close', () => { this.onNonDisconnectClose('close'); }); }; connection.once('connect', packet => { this.handleConnect(packet, completeConnection); }); } handleError(err) { this.server.emit('clientError', err, this); this.onNonDisconnectClose(err.message); } setupTimer() { if (this.keepAlive <= 0) { return; } const timeout = (this.keepAlive * 1000 * 3) / 2; this.logger.debug({ timeout: timeout }, 'setting keepAlive timeout'); if (this.timer) { this.timer.reschedule(timeout); } else { this.timer = (0, retimer_1.default)(() => { this.logger.info('keepAlive timeout'); this.onNonDisconnectClose('keepAlive timeout'); }, timeout); } } doForward(err, packet) { if (err) { return this.connection && this.connection.emit('error', err); } this.connection.publish(packet); if (packet.qos === 1) { this.inflight[packet.messageId] = packet; } } forward(topic, payload, options, subTopic, qos, cb) { if (options._dedupId <= this._lastDedupId) { return; } this.logger.trace({ topic: topic }, 'delivering message'); const indexWildcard = subTopic.indexOf('#'); const indexPlus = subTopic.indexOf('+'); let forward = true; const newId = this.nextId++; this.nextId %= 65536; const packet = { topic, payload, qos, messageId: newId, }; if (qos) { this.inflightCounter++; } if (this._closed || this._closing) { this.logger.debug({ packet: packet }, 'trying to send a packet to a disconnected client'); forward = false; } else if (this.inflightCounter >= this.server.options.maxInflightMessages) { this.logger.warn('too many inflight packets, closing'); this.close(null, 'too many inflight packets'); forward = false; } if (cb) { cb(); } forward = forward && !(topic.indexOf('$SYS') >= 0 && ((indexWildcard >= 0 && indexWildcard < 2) || (indexPlus >= 0 && indexPlus < 2))); if (forward) { if (options._dedupId === undefined) { options._dedupId = this.server.nextDedupId(); this._lastDedupId = options._dedupId; } if (qos && options.messageId) { this.server.updateOfflinePacket(this, options.messageId, packet, this.doForward); } else { this.doForward(null, packet); } } } unsubscribeMapTo(topic, cb) { const sub = this.subscriptions[topic]; if (!sub || !sub.handler) { this.server.emit('unsubscribed', topic, this); return cb(); } this.server.listener.unsubscribe(topic, sub.handler, err => { if (err) { cb(err); return; } if (!this._closing || this.clean) { delete this.subscriptions[topic]; this.logger.info({ topic: topic }, 'unsubscribed'); this.server.emit('unsubscribed', topic, this); } cb(); }); } handleConnect(packet, completeConnection) { const client = this.connection; this.id = packet.clientId; this.logger = this.logger.child({ client: this }); if (!this.id) { if (packet.protocolVersion == 4 && packet.clean) { this.id = (0, uuid_1.v4)(); } else { this.logger.info('identifier rejected'); client.connack({ returnCode: 2, }); client.stream.end(); return; } } this.keepAlive = packet.keepalive; this.will = packet.will; this.clean = packet.clean; if (this.id in this.server.clients) { this.server.clients[this.id].close(completeConnection, 'new connection request'); } else { completeConnection(); } } handlePingReq() { this.server.emit('pingReq', this); } handlePubAck(packet) { const logger = this.logger; logger.debug({ packet: packet }, 'pubAck'); if (this.inflight[packet.messageId]) { this.server.emit('delivered', this.inflight[packet.messageId], this); this.inflightCounter--; delete this.inflight[packet.messageId]; } else { logger.info({ packet: packet }, 'no matching packet'); } } doSubscribe(s, cb) { const handler = (topic, payload, options) => { this.forward(topic, payload, options, s.topic, s.qos); }; if (this.subscriptions[s.topic] === undefined) { this.subscriptions[s.topic] = { qos: s.qos, handler: handler }; this.server.listener.subscribe(s.topic, handler, err => { if (err) { delete this.subscriptions[s.topic]; cb(err); return; } this.logger.info({ topic: s.topic, qos: s.qos }, 'subscribed to topic'); this.subscriptions[s.topic] = { qos: s.qos, handler: handler }; cb(null, true); }); } else { cb(null, true); } } handleEachSub(s, cb) { if (this.subscriptions[s.topic] === undefined) { this.doSubscribe(s, cb); } else { cb(null, true); } } handleSubscribe(packet) { const logger = this.logger; logger.debug({ packet: packet }, 'subscribe received'); const granted = Client.calculateGranted(this, packet); (0, steed_1.default)().map(this, packet.subscriptions, this.handleEachSub, (err, authorized) => { if (err) { this.close(null, err.message); return; } packet.subscriptions.forEach((sub, index) => { if (authorized[index]) { this.server.emit('subscribed', sub.topic, this); } else { granted[index] = 0x80; } }); if (!this._closed) { this.connection.suback({ messageId: packet.messageId, granted: granted, }); } }); } handlePublish(packet) { if (packet.qos === 2) { switch (this.server.onQoS2publish) { case 'dropToQoS1': packet.qos = 1; break; case 'disconnect': if (!this._closed && !this._closing) { this.close(null, 'qos2 caused disconnect'); } return; default: break; } } const doPubAck = () => { if (packet.qos === 1 && !(this._closed || this._closing)) { this.connection.puback({ messageId: packet.messageId, }); } }; doPubAck(); } onNonDisconnectClose(reason) { const logger = this.logger; const will = this.will; if (this._closed || this._closing) { return; } if (this.will) { logger.info({ packet: will }, 'delivering last will'); setImmediate(() => { this.handlePublish(will); }); } this.close(null, reason); } close(callback, reason) { callback = callback || (() => { }); if (this._closed || this._closing) { return callback(); } if (this.id) { this.logger.debug('closing client, reason: ' + reason); if (this.timer) { this.timer.clear(); } } const cleanup = () => { this._closed = true; this.logger.info('closed'); this.connection.removeAllListeners(); this.connection.on('error', function () { }); this.server.emit('clientDisconnected', this, reason); callback(); }; this._closing = true; steed_1.default.map(this, Object.keys(this.subscriptions), this.unsubscribeMapTo, err => { if (err) { this.logger.info(err); } if (!this._closed) { cleanup(); if (this.connection.stream.destroySoon) { this.connection.stream.destroySoon(); } else if (this.connection.stream.destroy) { this.connection.stream.destroy(); } else { this.connection.stream.end(); } } }); } static calculateGranted(client, packet) { return packet.subscriptions.map(function (e) { if (e.qos === 2) { e.qos = 1; } if (client.subscriptions[e.topic] !== undefined) { client.subscriptions[e.topic].qos = e.qos; } return e.qos; }); } } exports.Client = Client; //# sourceMappingURL=client.js.map