UNPKG

kuzzle-sdk

Version:
282 lines 11.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const KuzzleError_1 = require("../KuzzleError"); const Realtime_1 = require("./abstract/Realtime"); const Http_1 = __importDefault(require("./Http")); const DisconnectionOrigin_1 = require("./DisconnectionOrigin"); /** * WebSocket protocol used to connect to a Kuzzle server. */ class WebSocketProtocol extends Realtime_1.BaseProtocolRealtime { /** * @param host Kuzzle server hostname or IP * @param options WebSocket connection options * - `autoReconnect` Automatically reconnect to kuzzle after a `disconnected` event. (default: `true`) * - `port` Kuzzle server port (default: `7512`) * - `headers` Connection custom HTTP headers (Not supported by browsers) * - `reconnectionDelay` Number of milliseconds between reconnection attempts (default: `1000`) * - `pingInterval` Number of milliseconds between two pings (default: `2000`) * - `ssl` Use SSL to connect to Kuzzle server. Default `false` unless port is 443 or 7443. */ constructor(host, options = {}) { super(host, options, "ws"); if (typeof host !== "string" || host === "") { throw new Error("host is required"); } // Browsers WebSocket API if (typeof WebSocket !== "undefined") { this.WebSocketClient = WebSocket; // There are no options allowed in the browsers WebSocket API this.options = null; } else { this.WebSocketClient = require("ws"); this.options = { headers: options.headers || null, perMessageDeflate: false, }; if (this.options.headers !== null && (Array.isArray(this.options.headers) || typeof this.options.headers !== "object")) { throw new Error('Invalid "headers" option: expected an object'); } } this._pingInterval = typeof options.pingInterval === "number" ? options.pingInterval : 2000; this.client = null; this.lasturl = null; } /** * Connect to the websocket server */ _connect() { return new Promise((resolve, reject) => { const url = `${this.ssl ? "wss" : "ws"}://${this.host}:${this.port}`; super.connect(); if (url !== this.lasturl) { this.wasConnected = false; this.lasturl = url; } this.client = new this.WebSocketClient(url, this.options); /** * Defining behavior depending on the Websocket client type * Which can be the browser or node one. */ if (typeof WebSocket !== "undefined") { this.ping = () => { this.client.send('{"p":1}'); }; } else { this.ping = () => { this.client.ping(); }; this.client.on("pong", () => { this.waitForPong = false; }); } this.client.onopen = () => { this.clientConnected(); this.setupPingPong(); return resolve(); }; this.client.onclose = (closeEvent, message) => { let status, reason = message; if (typeof closeEvent === "number") { status = closeEvent; } else { status = closeEvent.code; if (closeEvent.reason) { reason = closeEvent.reason; } } if (status === 1000) { this.clientDisconnected(DisconnectionOrigin_1.DisconnectionOrigin.USER_CONNECTION_CLOSED); } // do not forward a connection close error if no // connection has been previously established else if (this.wasConnected) { const error = new Error(reason); error.status = status; this.clientNetworkError(error); } }; this.client.onerror = (error) => { let err = error; if (!(error instanceof Error)) { // browser-side, the payload sent to this event is a generic "Event" // object bearing no information about the cause of the error err = error && (typeof Event === "undefined" || !(error instanceof Event)) ? new Error(error.message || error) : new Error("Connection error"); } this.clientNetworkError(err); if ([this.client.CLOSING, this.client.CLOSED].indexOf(this.client.readyState) > -1) { return reject(err); } }; this.client.onmessage = (payload) => { const data = JSON.parse(payload.data || payload); /** * Since Kuzzle 2.10.0 * Corresponds to a custom pong response message */ if (data && data.p && data.p === 2 && Object.keys(data).length === 1) { this.waitForPong = false; return; } // for responses, data.room == requestId if (data.type === "TokenExpired") { this.emit("tokenExpired"); } else if (data.room) { this.emit(data.room, data); } else { // @deprecated this.emit("discarded", data); const error = new KuzzleError_1.KuzzleError(data.error, new Error().stack, this.constructor.name); this.emit("queryError", { error, request: data }); } /** * In case you're running a Kuzzle version under 2.10.0 * The response from a browser custom ping will be another payload. * We need to clear this timeout at each message to keep * the connection alive if it's the case */ this.waitForPong = false; }; }); } connect() { if (this.cookieSupport) { return this._httpProtocol.connect().then(() => this._connect()); } return this._connect(); } enableCookieSupport() { if (typeof XMLHttpRequest === "undefined") { throw new Error("Support for cookie cannot be enabled outside of a browser"); } super.enableCookieSupport(); this._httpProtocol = new Http_1.default(this.host, { port: this.port, ssl: this.ssl, }); this._httpProtocol.enableCookieSupport(); } /** * Sends a payload to the connected server * * @param {Object} payload */ send(request, options = {}) { if (!this.client || this.client.readyState !== this.client.OPEN) { return; } if (!this.cookieSupport || request.controller !== "auth" || (request.action !== "login" && request.action !== "logout" && request.action !== "refreshToken")) { this.client.send(JSON.stringify(request)); return; } const formattedRequest = this._httpProtocol.formatRequest(request, options); if (!formattedRequest) { return; } this.emit("websocketRenewalStart"); // Notify that the websocket is going to renew his connection with Kuzzle if (this.client) { this.client.onclose = undefined; // Remove the listener that will emit disconnected / networkError event before closing this.client.close(1000); } this.client = null; this.clientDisconnected(DisconnectionOrigin_1.DisconnectionOrigin.WEBSOCKET_AUTH_RENEWAL); // Simulate a disconnection, this will enable offline queue and trigger realtime subscriptions backup this._httpProtocol ._sendHttpRequest(formattedRequest) .then((response) => { // Reconnection return this.connect().then(() => { this.emit(formattedRequest.payload.requestId, response); this.emit("websocketRenewalDone"); // Notify that the websocket has finished renewing his connection with Kuzzle }); }) .catch((error) => { this.emit(formattedRequest.payload.requestId, { error }); this.emit("websocketRenewalDone"); // Notify that the websocket has finished renewing his connection with Kuzzle }); } /** * @override */ clientDisconnected(origin) { clearInterval(this.pingIntervalId); this.pingIntervalId = null; super.clientDisconnected(origin); } /** * @override * * @param {Error} error */ clientNetworkError(error) { clearInterval(this.pingIntervalId); this.pingIntervalId = null; super.clientNetworkError(error); } /** * Closes the connection */ close() { this.state = "offline"; this.wasConnected = false; if (this.client) { this.client.close(1000); // Close with 1000 will trigger the `disconnect` } // Remove all listerner after closing the connection, this way the `disconnect` can be emitted when calling close. this.removeAllListeners(); this.client = null; this.stopRetryingToConnect = true; clearInterval(this.pingIntervalId); this.pingIntervalId = null; super.close(); } setupPingPong() { clearInterval(this.pingIntervalId); // Reset when connection is established this.waitForPong = false; this.pingIntervalId = setInterval(() => { // If the connection is established and we are not waiting for a pong we ping Kuzzle if (this.client && this.client.readyState === this.client.OPEN && !this.waitForPong) { this.ping(); this.waitForPong = true; return; } // If we were waiting for a pong that never occured before the next ping cycle we throw an error if (this.waitForPong) { const error = new Error("Kuzzle does'nt respond to ping. Connection lost."); error.status = 503; /** * Ensure that the websocket connection is closed because if the connection * was fine but Kuzzle could not respond in time a new connection will be * created if `autoReconnect=true` and there would 2 opened websocket connection. */ clearInterval(this.pingIntervalId); this.client.onclose = undefined; // Remove the listener that will emit disconnected / networkError event before closing this.client.close(1000); this.waitForPong = false; this.clientNetworkError(error); } }, this._pingInterval); } } exports.default = WebSocketProtocol; //# sourceMappingURL=WebSocket.js.map