UNPKG

@techteamer/janus-api

Version:
423 lines (422 loc) 15.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const isomorphic_ws_1 = __importDefault(require("isomorphic-ws")); const JanusPlugin_js_1 = __importDefault(require("./JanusPlugin.js")); const uuid_1 = require("uuid"); const ignoredErrorCodes = [ 458, // JANUS_ERROR_SESSION_NOT_FOUND 459 // JANUS_ERROR_HANDLE_NOT_FOUND ]; class Janus { constructor(config, logger) { this.ws = undefined; this.isConnected = false; this.sessionId = undefined; this.logger = logger; this.transactions = {}; this.pluginHandles = {}; this.config = config; this.protocol = 'janus-protocol'; this.sendCreate = true; } connect() { if (this.isConnected) { return Promise.resolve(this); } return new Promise((resolve, reject) => { this.ws = new isomorphic_ws_1.default(this.config.url, this.protocol, this.config.options); this.ws.addEventListener('error', (err) => { this.logger.error('Error connecting to the Janus WebSockets server...', err); this.isConnected = false; reject(err); }); this.ws.addEventListener('close', () => { this.cleanup(); }); this.ws.addEventListener('open', () => { if (!this.sendCreate) { this.isConnected = true; this.keepAlive(true); return resolve(this); } const transaction = (0, uuid_1.v4)(); const request = { janus: 'create', transaction }; this.transactions[transaction] = { resolve: (json) => { if (json.janus !== 'success') { this.logger.error('Cannot connect to Janus', json); reject(json); return; } this.sessionId = json.data.id; this.isConnected = true; this.keepAlive(true); this.logger.debug('Janus connected, sessionId: ', this.sessionId); resolve(this); }, reject, replyType: 'success' }; this.websocketSend(JSON.stringify(request)); }); this.ws.addEventListener('message', (event) => { this.onMessage(event); }); this.ws.addEventListener('close', () => { this.onClose(); }); }); } /** * * @param {JanusPlugin} plugin * @return {Promise} * */ addPlugin(plugin) { if (!(plugin instanceof JanusPlugin_js_1.default)) { return Promise.reject(new Error('plugin is not a JanusPlugin')); } const request = plugin.getAttachPayload(); return this.transaction('attach', request, 'success').then((json) => { if (json.janus !== 'success') { this.logger.error('Cannot add plugin', json); plugin.error(json); throw new Error(json); } this.pluginHandles[json.data.id] = plugin; return plugin.success(this, json.data.id); }); } transaction(type, payload, replyType, timeoutMs) { if (!replyType) { replyType = 'ack'; } const transactionId = (0, uuid_1.v4)(); return new Promise((resolve, reject) => { if (timeoutMs) { setTimeout(() => { reject(new Error('Transaction timed out after ' + timeoutMs + ' ms')); }, timeoutMs); } if (!this.isConnected) { reject(new Error('Janus is not connected')); return; } const request = Object.assign({}, payload, { janus: type, session_id: (payload && parseInt(payload.session_id, 10)) || this.sessionId, transaction: transactionId }); this.transactions[request.transaction] = { resolve, reject, replyType, request }; this.websocketSend(JSON.stringify(request)); }); } send(type, payload) { return new Promise((resolve, reject) => { if (!this.isConnected) { reject(new Error('Janus is not connected')); return; } const request = Object.assign({}, payload, { janus: type, session_id: this.sessionId, transaction: (0, uuid_1.v4)() }); this.logger.debug('Janus sending', request); this.websocketSend(JSON.stringify(request), {}, (err) => { if (err) { reject(err); } else { resolve(); } }); }); } /** * Send a data message. * * @param {*} data The message to send * @param {Object} options Options object * @param {Function} cb Callback which is executed when data is written out * @public */ websocketSend(data, options, cb) { try { this.ws.send(data, options, cb); } catch (err) { if (cb) { cb(err); } this.logger.error('Websocket send method exception', err); } } destroy() { if (!this.isConnected) { return Promise.resolve(); } return this.transaction('destroy', {}, 'success', 5000).then(() => { this.cleanup(); }).catch(() => { this.cleanup(); }); } destroyPlugin(plugin) { return new Promise((resolve, reject) => { if (!(plugin instanceof JanusPlugin_js_1.default)) { reject(new Error('plugin is not a JanusPlugin')); return; } if (!this.pluginHandles[plugin.janusHandleId]) { reject(new Error('unknown plugin')); return; } this.transaction('detach', { plugin: plugin.pluginName, handle_id: plugin.janusHandleId }, 'success', 5000).then(() => { delete this.pluginHandles[plugin.janusHandleId]; plugin.detach(); resolve(); }).catch((err) => { delete this.pluginHandles[plugin.janusHandleId]; plugin.detach(); reject(err); }); }); } onMessage(messageEvent) { let json; try { json = JSON.parse(messageEvent.data); } catch (err) { this.logger.error('cannot parse message', messageEvent.data); return; } // this.logger.debug('JANUS GOT', json) if (json.janus === 'timeout' && json.session_id !== this.sessionId) { this.logger.debug('GOT timeout from another websocket'); // seems like a bug in janus timeout handler :) return; } if (json.janus === 'keepalive') { // Do nothing return; } if (json.janus === 'ack') { // Just an ack, we can probably ignore const transaction = this.getTransaction(json); if (transaction && transaction.resolve) { transaction.resolve(json); } return; } if (json.janus === 'success') { // Success! const transaction = this.getTransaction(json); if (!transaction) { return; } const pluginData = json.plugindata; if (pluginData === undefined || pluginData === null) { transaction.resolve(json); return; } const sender = json.sender; if (!sender) { transaction.resolve(json); this.logger.error('Missing sender for plugindata', json); return; } const pluginHandle = this.pluginHandles[sender]; if (!pluginHandle) { this.logger.error('This handle is not attached to this session', json); return; } transaction.resolve({ data: pluginData.data, json }); return; } if (json.janus === 'webrtcup') { // The PeerConnection with the gateway is up! Notify this const sender = json.sender; if (!sender) { this.logger.warn('Missing sender...'); return; } const pluginHandle = this.pluginHandles[sender]; if (!pluginHandle) { this.logger.error('This handle is not attached to this session', sender); return; } pluginHandle.webrtcState(true); return; } if (json.janus === 'hangup') { // A plugin asked the core to hangup a PeerConnection on one of our handles const sender = json.sender; if (!sender) { this.logger.warn('Missing sender...'); return; } const pluginHandle = this.pluginHandles[sender]; if (!pluginHandle) { this.logger.error('This handle is not attached to this session', sender); return; } pluginHandle.webrtcState(false, json.reason); pluginHandle.hangup(); return; } if (json.janus === 'detached') { // A plugin asked the core to detach one of our handles const sender = json.sender; if (!sender) { this.logger.warn('Missing sender...'); return; } return; } if (json.janus === 'media') { // Media started/stopped flowing const sender = json.sender; if (!sender) { this.logger.warn('Missing sender...'); return; } const pluginHandle = this.pluginHandles[sender]; if (!pluginHandle) { this.logger.error('This handle is not attached to this session', sender); return; } pluginHandle.mediaState(json.type, json.receiving); return; } if (json.janus === 'slowlink') { // Trouble uplink or downlink this.logger.debug('Got a slowlink event on session ' + this.sessionId); this.logger.debug(json); const sender = json.sender; if (!sender) { this.logger.warn('Missing sender...'); return; } const pluginHandle = this.pluginHandles[sender]; if (!pluginHandle) { this.logger.error('This handle is not attached to this session', sender); return; } pluginHandle.slowLink(json.uplink, json.nacks); return; } if (json.janus === 'error') { // Oops, something wrong happened if (json.error && json.error.code && !ignoredErrorCodes.includes(json.error.code)) { this.logger.error('Janus error response' + json); } const transaction = this.getTransaction(json, true); if (transaction && transaction.reject) { if (transaction.request) { this.logger.debug('Janus Error: rejecting transaction', transaction.request, json); } transaction.reject(json); } return; } if (json.janus === 'event') { const sender = json.sender; if (!sender) { this.logger.warn('Missing sender...'); return; } const pluginData = json.plugindata; if (pluginData === undefined || pluginData === null) { this.logger.error('Missing plugindata...'); return; } const pluginHandle = this.pluginHandles[sender]; if (!pluginHandle) { this.logger.error('This handle is not attached to this session', sender); return; } const data = pluginData.data; const transaction = this.getTransaction(json); if (transaction) { if (data.error_code) { transaction.reject({ data, json }); } else { transaction.resolve({ data, json }); } return; } pluginHandle.onmessage(data, json); return; } this.logger.warn('Unknown message/event ' + json.janus + ' on session ' + this.sessionId); this.logger.debug(json); } onClose() { if (!this.isConnected) { return; } this.isConnected = false; this.logger.error('Lost connection to the gateway (is it down?)'); } keepAlive(isScheduled) { if (!this.ws || !this.isConnected || !this.sessionId) { return; } if (isScheduled) { setTimeout(() => { this.keepAlive(); }, this.config.keepAliveIntervalMs); } else { // logger.debug('Sending Janus keepalive') this.transaction('keepalive').then(() => { setTimeout(() => { this.keepAlive(); }, this.config.keepAliveIntervalMs); }).catch((err) => { this.logger.warn('Janus keepalive error', err); }); } } getTransaction(json, ignoreReplyType = false) { const type = json.janus; const transactionId = json.transaction; if (transactionId && Object.prototype.hasOwnProperty.call(this.transactions, transactionId) && (ignoreReplyType || this.transactions[transactionId].replyType === type)) { const ret = this.transactions[transactionId]; delete this.transactions[transactionId]; return ret; } } cleanup() { this._cleanupPlugins(); this._cleanupWebSocket(); this._cleanupTransactions(); } _cleanupWebSocket() { if (this.ws) { if (this.ws.removeAllListeners) { this.ws.removeAllListeners(); } if (this.ws.readyState === isomorphic_ws_1.default.OPEN) { this.ws.close(); } } this.ws = undefined; this.isConnected = false; } _cleanupPlugins() { Object.keys(this.pluginHandles).forEach((pluginId) => { const plugin = this.pluginHandles[pluginId]; delete this.pluginHandles[pluginId]; plugin.detach(); }); } _cleanupTransactions() { Object.keys(this.transactions).forEach((transactionId) => { const transaction = this.transactions[transactionId]; if (transaction.reject) { transaction.reject(); } }); this.transactions = {}; } } exports.default = Janus;