UNPKG

@skyway-sdk/analytics-client

Version:

The official Next Generation JavaScript SDK for SkyWay

253 lines 10.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Socket = void 0; const isomorphic_ws_1 = __importDefault(require("isomorphic-ws")); const payloadTypes_1 = require("./payloadTypes"); const event_1 = require("./utils/event"); const ServerEventType = ['Open', 'Acknowledge']; const getReconnectWaitTime = (reconnectCount) => { return (Math.pow(2, reconnectCount) + Math.random()) * 1000; }; class Socket { constructor({ sessionEndpoint, token, logger, sdkVersion, contextId }) { this._isOpen = false; this._isClosed = false; this._reconnectCount = 0; this.connectionState = 'connecting'; // コンストラクタ作成時点で繋ぎにいくので初期値はconnecting this.onConnectionStateChanged = new event_1.Event(); this.onOpened = new event_1.Event(); this.onTokenExpired = new event_1.Event(); this.onEventReceived = new event_1.Event(); this.onConnectionFailed = new event_1.Event(); this._resendClientEvents = []; this._sessionEndpoint = sessionEndpoint; this._token = token; this._logger = logger; this._sdkVersion = sdkVersion; this._contextId = contextId; this._connect(); } _setConnectionState(state) { if (this.connectionState === state) return; this._logger.debug(`connectionState changed : ${state}`); this.connectionState = state; this.onConnectionStateChanged.emit(state); } _connect() { let ws; try { // We use the SubProtocol header to send the token. // This is because the browser's WebSocket class does not allow the header to be changed freely. const subProtocol = `SkyWayAuthToken!${this._token}`; const wsProperties = { sdkPlatform: 'js', sdkVersion: this._sdkVersion, contextId: this._contextId, }; const queryString = Object.entries(wsProperties) .filter(([_, v]) => v !== undefined) .map((pair) => pair.join('=')) .join('&'); const wsURL = `${this._sessionEndpoint}?${queryString}`; ws = new isomorphic_ws_1.default(wsURL, subProtocol); this._logger.debug(`Connecting to analytics-logging-server: ${this._sessionEndpoint}`); ws.onerror = (event) => { this._logger.error('WebSocket error occurred', event.error); ws.close(4202); }; } catch (err) { const error = err instanceof Error ? err : new Error(); this._logger.error('Failed to create WebSocket instance', error); this.reconnect(); return; } ws.onopen = () => { this._logger.debug('Connected to analytics-logging-server'); }; ws.onclose = (event) => { const logMessage = 'Close event fired: ' + JSON.stringify({ code: event.code, reason: event.reason, type: event.type }); // 1000, 4000~4099: normal case (should not reconnect) // 1009, 4100~4199: non-normal case (should not reconnect) // 4200~4299: non-normal case (should reconnect) // others: unexpected case (should reconnect) if ((4100 <= event.code && event.code <= 4199) || event.code === 1009) { this._logger.error(logMessage, new Error()); } else { this._logger.debug(logMessage); } if (event.code !== 1000 && event.code !== 1009 && !(4000 <= event.code && event.code <= 4199)) { if (4200 === event.code) { this.onTokenExpired.emit(); } else { this.reconnect(); } return; } this._logger.debug('Closed the connection to analytics-logging-server'); this.onConnectionFailed.emit({ code: event.code, reason: event.reason }); this.close(); }; ws.onmessage = (event) => { this._messageHandler(event.data); }; this._ws = ws; } updateAuthToken(token) { this._token = token; } reconnect() { if (this._ws !== undefined) { this._ws.close(4000); } this._ws = undefined; this._isOpen = false; // getReconnectWaitTime により30秒程まで再試行するため5を指定している if (this._reconnectCount >= 5) { this.onConnectionFailed.emit({}); this.close(); this._logger.error('Failed to reconnect for five times', new Error()); } else { this._setConnectionState('reconnecting'); const waitTime = getReconnectWaitTime(this._reconnectCount); this._reconnectTimer = setTimeout(() => { this._connect(); this._reconnectCount++; this._logger.debug(`Try to reconnect: count = ${this._reconnectCount}`); }, waitTime); } } close() { this._isClosed = true; this.destroy(); } destroy() { this._setConnectionState('closed'); this.onConnectionStateChanged.removeAllListeners(); this.onOpened.removeAllListeners(); this.onEventReceived.removeAllListeners(); this.onConnectionFailed.removeAllListeners(); if (this._reconnectTimer) { clearTimeout(this._reconnectTimer); } if (this._ws !== undefined) { this._ws.close(1000); } } send(clientEvent) { return __awaiter(this, void 0, void 0, function* () { if (this._ws === undefined || !this._isOpen || this._ws.readyState !== isomorphic_ws_1.default.OPEN) { this._logger.debug('Try to reconnect because connection is lost'); this.resendAfterReconnect(clientEvent); return; } const data = JSON.stringify(clientEvent.toJSON()); this._ws.send(data, (err) => { if (err) { this._logger.debug(`Try to reconnect because failed to send: ${err.message}`); this.resendAfterReconnect(clientEvent); return; } }); }); } resendAfterReconnect(data) { const isEventExist = this._resendClientEvents.some((event) => event.id === data.id); if (!isEventExist) this._resendClientEvents.push(data); // この関数が複数回呼ばれた際に再接続の試行が重複しないよう、connectionStateを確認してから再接続する if (this.connectionState !== 'reconnecting') { this.reconnect(); } } pushResendClientEventsQueue(data) { this._resendClientEvents.push(data); } isClosed() { return this._isClosed; } _messageHandler(data) { if (typeof data !== 'string') { this._logger.error('Received invalid message: not string', new Error()); return; } let parsedData; try { parsedData = JSON.parse(data); } catch (err) { const error = err instanceof Error ? err : new Error(); this._logger.error('Received invalid message: parse error', error); return; } if (!isServerEvent(parsedData)) { this._logger.error(`Received invalid message: ${JSON.stringify(parsedData)}`, new Error()); return; } if (parsedData.type === 'Open') { if (!(0, payloadTypes_1.isOpenServerEventPayload)(parsedData.payload)) { this._logger.error(`Received invalid message: ${JSON.stringify(parsedData.payload)}`, new Error()); return; } this._logger.debug('Received a open event'); this._isOpen = true; this._setConnectionState('connected'); if (this._reconnectCount !== 0) { this._reconnectCount = 0; this._logger.debug('Succeeded to reconnect'); } if (this._resendClientEvents.length > 0) { for (const event of this._resendClientEvents) { if (this._ws === undefined || !this._isOpen || this._ws.readyState !== isomorphic_ws_1.default.OPEN) { this._logger.error(`Failed to resend event because connection lost after reconnect: ${event}`, new Error()); continue; } const data = JSON.stringify(event.toJSON()); this._ws.send(data, (err) => { if (err) { this._logger.error(`Failed to resend event: ${event}`, err); return; } this._logger.debug(`Succeed to resend ClientEvent: ${event}`); }); } this._logger.debug('Process of resending ClientEvents is completed'); this._resendClientEvents = []; } this.onOpened.emit(parsedData.payload); } else { this._logger.debug(`Received the event: ${parsedData.type}, payload: ${JSON.stringify(parsedData.payload)}`); this.onEventReceived.emit(parsedData); } } } exports.Socket = Socket; function isServerEvent(data) { if (!data || typeof data !== 'object') return false; if (typeof data.type !== 'string' || !ServerEventType.includes(data.type)) return false; if (typeof data.id !== 'string') return false; if (data.payload && typeof data.payload !== 'object') return false; return true; } //# sourceMappingURL=socket.js.map