UNPKG

infobip-rtc

Version:

Infobip RTC JavaScript SDK - Infobip WebRTC API Implementation

186 lines 7.12 kB
import Properties from "./Properties"; import Status from "./Status"; import { version } from "../Version"; import Retry from '../util/Retry'; import Browser from '../util/Browser'; import { PortunusHostEvaluator } from "./PortunusHostEvaluator"; export class InfobipGatewayImpl { constructor(eventEmitter, logger, accessToken) { this.eventEmitter = eventEmitter; this.logger = logger; this.status = Status.OFFLINE; this.accessToken = accessToken; this.portunusHostEvaluator = new PortunusHostEvaluator(this.logger); this.deviceInfo = this.encodeDeviceInfo(); this.initRetry(); } async connect(isReconnect = false) { if (!this.portunusInstanceHash) { this.portunusHost = await this.portunusHostEvaluator.evaluate(this.accessToken); } this.ws = new WebSocket(this.generatePortunusUrl(), [this.accessToken.jwt, this.deviceInfo]); this.status = isReconnect ? Status.RECONNECTING : Status.CONNECTING; this.ws.onopen = this.onOpen.bind(this); this.ws.onclose = this.onClose.bind(this); this.ws.onmessage = this.onMessage.bind(this); this.ws.onerror = this.onError.bind(this); } disconnect() { if ([Status.RECONNECTING, Status.CONNECTING, Status.CONNECTED].indexOf(this.status) >= 0) { this.ws.close(4000, "USER_DISCONNECT"); } if (this.retry.cancel()) { this.eventEmitter.emit('disconnected', ({ code: 4000, reason: 'USER_DISCONNECT' })); } this.status = Status.OFFLINE; } ; send(data) { if (this.status !== Status.CONNECTED) { this.logger?.error(`Socket to Infobip WebRTC Gateway not opened, could not send ${JSON.stringify(data)}!`); return; } this.logSendingMessage(data); let message = JSON.stringify(data); this.ws.send(message); } setLogger(logger) { this.logger = logger; } encodeDeviceInfo() { let generatedDeviceInfo = JSON.stringify(this.generateDeviceInfo()); return this.base64EncodeUrl(window.btoa(generatedDeviceInfo)); } generatePortunusUrl() { let url = `wss://${this.portunusHost}`; if (this.portunusInstanceHash) { url += `?instance=${this.portunusInstanceHash}`; } return url; } logSendingMessage(message) { if (message.action !== 'heartbeat' && message.action !== 'heartbeat_resp') { this.logger?.debug(`Sending ${JSON.stringify(message)} to Infobip WebRTC Gateway...`); } } onOpen() { this.logger?.info('Socket to Infobip WebRTC Gateway opened.'); this.eventEmitter.once("registered", event => { this.portunusInstanceHash = event.instanceHash; }); let reconnected = this.status === Status.RECONNECTING; if (reconnected) { this.eventEmitter.once("registered", () => { this.eventEmitter.emit('reconnected'); }); } this.status = Status.CONNECTED; this.scheduleHeartbeat(); this.initRetry(); } onMessage(message) { let data = JSON.parse(message.data); this.logReceivedMessage(data); if (data.event === 'heartbeat_resp') { this.measureHeartbeatRate(); this.cancelHeartbeatCheck(); return; } if (data.event === 'heartbeat') { this.sendHeartbeatResponse(); return; } if (data.event === 'registration_failed') { this.disconnect(); } this.eventEmitter.emit(data.event, data); } sendHeartbeatResponse() { this.send({ action: 'heartbeat_resp' }); } logReceivedMessage(message) { if (message.event !== 'heartbeat_resp' && message.event !== 'heartbeat') { this.logger?.debug(`Received message from Infobip Gateway: ${JSON.stringify(message)}.`); } } onClose(event) { this.logger?.info(`Socket closed, code ${event.code}, reason: ${event.reason}.`); if (event.code === 1013) { this.portunusInstanceHash = null; } const prevStatus = this.status; this.status = Status.OFFLINE; this.cleanup(); if (prevStatus !== Status.OFFLINE && event.code < 4000) { this.retry.retry(); if (prevStatus === Status.CONNECTED) { this.eventEmitter.emit('reconnecting'); } } else { this.eventEmitter.emit('disconnected', event); } } cleanup() { clearInterval(this.heartbeat); this.cancelHeartbeatCheck(); this.ws.onopen = null; this.ws.onclose = null; this.ws.onmessage = null; this.ws.onerror = null; delete this.ws; } onError(event) { this.logger?.error(`Socket error: ${JSON.stringify(event)}.`); } scheduleHeartbeat() { this.heartbeat = setInterval(() => { if (this.ws.readyState === WebSocket.OPEN) { this.send({ 'action': 'heartbeat' }); this.scheduleHeartbeatCheck(); } }, Properties.HEARTBEAT_PERIOD); } scheduleHeartbeatCheck() { if (this.heartbeatCheck) { this.logger?.warn("Did not receive previous heartbeat response, skipping new heartbeat check."); return; } this.heartbeatCheck = setTimeout(() => { this.logger?.info(`No heartbeat response in ${Properties.HEARTBEAT_TIMEOUT} milliseconds. Closing socket...`); this.onClose({ code: 3000, reason: 'Heartbeat timeout.' }); }, Properties.HEARTBEAT_TIMEOUT); this.heartbeatStartTime = performance.now(); } initRetry() { this.retry?.cancel(); this.retry = new Retry(async (iteration) => { this.logger?.info(`Reconnect attempt ${iteration}...`); await this.connect(true); }, Retry.exponentialDelay(Properties.RECONNECT_INITIAL_DELAY), Properties.RECONNECT_MAX_DELAY, Properties.RECONNECT_MAX).catch(reason => { this.logger?.info(`Reconnecting failed, reason: ${reason}`); this.eventEmitter.emit('disconnected', { reason: 'Connection error.' }); }); } generateDeviceInfo() { let browser = new Browser(); return { sdk: { type: 'js', version: version }, device: { browser: browser.getBrowser(), os: browser.getOS() } }; } base64EncodeUrl(value) { return value.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } cancelHeartbeatCheck() { clearTimeout(this.heartbeatCheck); this.heartbeatCheck = undefined; } measureHeartbeatRate() { const durationMs = performance.now() - this.heartbeatStartTime; if (durationMs > Properties.HEARTBEAT_SLOW_THRESHOLD) { this.logger?.warn(`Heartbeat check took too long: ${durationMs} ms`); } } } //# sourceMappingURL=InfobipGateway.js.map