UNPKG

janode

Version:

Meetecho adapter for the Janus WebRTC Server

532 lines (475 loc) 17.8 kB
'use strict'; /** * This module contains the implementation of the TextRoom plugin (ref. {@link https://janus.conf.meetecho.com/docs/textroom.html}). * Notice this only covers what's possible via the Janus API: messages only sent via datachannels are not covered by this module. * @module textroom-plugin */ import Handle from '../handle.js'; /* The plugin ID exported in the plugin descriptor */ const PLUGIN_ID = 'janus.plugin.textroom'; /* These are the requests defined for the Janus TextRoom API */ const REQUEST_SETUP = 'setup'; const REQUEST_ACK = 'ack'; const REQUEST_RESTART = 'restart'; const REQUEST_LIST_ROOMS = 'list'; const REQUEST_LIST_PARTICIPANTS = 'listparticipants'; const REQUEST_EXISTS = 'exists'; const REQUEST_CREATE = 'create'; const REQUEST_ALLOW = 'allowed'; const REQUEST_ANNOUNCEMENT = 'announcement'; const REQUEST_KICK = 'kick'; const REQUEST_DESTROY = 'destroy'; /* These are the events/responses that the Janode plugin will manage */ /* Some of them will be exported in the plugin descriptor */ const PLUGIN_EVENT = { ROOMS_LIST: 'textroom_list', PARTICIPANTS_LIST: 'textroom_participants_list', EXISTS: 'textroom_exists', CREATED: 'textroom_created', DESTROYED: 'textroom_destroyed', SUCCESS: 'textroom_success', ERROR: 'textroom_error', }; /** * The class implementing the TextRoom plugin (ref. {@link https://janus.conf.meetecho.com/docs/textroom.html}). * Notice this only covers what's possible via the Janus API: messages only sent via datachannels are not covered by this module.<br> * * It extends the base Janode Handle class and overrides the base "handleMessage" method.<br> * * Moreover it defines many methods to support TextRoom operations. * * @hideconstructor * @extends module:handle~Handle */ class TextRoomHandle extends Handle { /** * Create a Janode TextRoom handle. * * @param {module:session~Session} session - A reference to the parent session * @param {number} id - The handle identifier */ constructor(session, id) { super(session, id); } /** * The custom "handleMessage" needed for handling TextRoom messages. * * @private * @param {Object} janus_message * @returns {Object} A falsy value for unhandled events, a truthy value for handled events */ handleMessage(janus_message) { const { plugindata, transaction } = janus_message; if (plugindata && plugindata.data && plugindata.data.textroom) { /** * @type {TextRoomData} */ const message_data = plugindata.data; const { textroom, error, error_code, room } = message_data; /* Prepare an object for the output Janode event */ const janode_event = this._newPluginEvent(janus_message); /* Add room information if available */ if (room) janode_event.data.room = room; /* The plugin will emit an event only if the handle does not own the transaction */ /* That means that a transaction has already been closed or this is an async event */ const emit = (this.ownsTransaction(transaction) === false); switch (textroom) { /* success response */ case 'success': /* Room exists API */ if (typeof message_data.exists !== 'undefined') { janode_event.data.exists = message_data.exists; janode_event.event = PLUGIN_EVENT.EXISTS; break; } /* Room list API */ if (typeof message_data.list !== 'undefined') { janode_event.data.list = message_data.list; janode_event.event = PLUGIN_EVENT.ROOMS_LIST; break; } /* Participants list API */ if (typeof message_data.participants !== 'undefined') { janode_event.data.participants = message_data.participants; janode_event.event = PLUGIN_EVENT.PARTICIPANTS_LIST; break; } /* Generic success (might be token disable) */ if (typeof message_data.allowed !== 'undefined') { janode_event.data.list = message_data.allowed; } /* In this case the "event" field of the Janode event is "success" */ janode_event.event = PLUGIN_EVENT.SUCCESS; break; /* TextRoom room created */ case 'created': janode_event.event = PLUGIN_EVENT.CREATED; janode_event.data.permanent = message_data.permanent; break; /* TextRoom room destroyed */ case 'destroyed': janode_event.event = PLUGIN_EVENT.DESTROYED; janode_event.data.permanent = message_data.permanent; break; /* Generic event (e.g. errors) */ case 'event': /* TextRoom error */ if (error) { janode_event.event = PLUGIN_EVENT.ERROR; janode_event.data = new Error(`${error_code} ${error}`); /* In case of error, close a transaction */ this.closeTransactionWithError(transaction, janode_event.data); break; } /* Configuration success for this handle */ if (typeof message_data.result !== 'undefined') { if (message_data.result === 'ok') { janode_event.event = PLUGIN_EVENT.SUCCESS; } break; } } /* The event has been handled */ if (janode_event.event) { /* Try to close the transaction */ this.closeTransactionWithSuccess(transaction, janus_message); /* If the transaction was not owned, emit the event */ if (emit) this.emit(janode_event.event, janode_event.data); return janode_event; } } /* The event has not been handled, return a falsy value */ return null; } /*----------*/ /* USER API */ /*----------*/ /* These are the APIs that users need to work with the textroom plugin */ /** * Setup a datachannel connection. * * @returns {Promise<module:textroom-plugin~TEXTROOM_EVENT_SUCCESS>} */ async setup() { const body = { request: REQUEST_SETUP, }; const response = await this.message(body); const { event, data: evtdata } = this._getPluginEvent(response); if (event === PLUGIN_EVENT.SUCCESS) return evtdata; const error = new Error(`unexpected response to ${body.request} request`); throw (error); } /** * Complete the setup or restart of a datachannel connection. * * @param {Object} params * @param {RTCSessionDescription} params.jsep * @returns {Promise<module:textroom-plugin~TEXTROOM_EVENT_SUCCESS>} */ async ack(jsep) { const body = { request: REQUEST_ACK, }; const response = await this.message(body, jsep); const { event, data: evtdata } = this._getPluginEvent(response); if (event === PLUGIN_EVENT.SUCCESS) return evtdata; const error = new Error(`unexpected response to ${body.request} request`); throw (error); } /** * Restart the setup of a datachannel connection. * * @returns {Promise<module:textroom-plugin~TEXTROOM_EVENT_SUCCESS>} */ async restart() { const body = { request: REQUEST_RESTART, }; const response = await this.message(body); const { event, data: evtdata } = this._getPluginEvent(response); if (event === PLUGIN_EVENT.SUCCESS) return evtdata; const error = new Error(`unexpected response to ${body.request} request`); throw (error); } /*----------------*/ /* Management API */ /*----------------*/ /* These are the APIs needed to manage textroom resources (rooms, forwarders ...) */ /** * List available textroom rooms. * * @param {Object} params * @param {string} [params.admin_key] - The admin key needed for invoking the API * @returns {Promise<module:textroom-plugin~TEXTROOM_EVENT_ROOMS_LIST>} */ async list({ admin_key }) { const body = { request: REQUEST_LIST_ROOMS, }; if (typeof admin_key === 'string') body.admin_key = admin_key; const response = await this.message(body); const { event, data: evtdata } = this._getPluginEvent(response); if (event === PLUGIN_EVENT.ROOMS_LIST) return evtdata; const error = new Error(`unexpected response to ${body.request} request`); throw (error); } /** * List participants inside a room. * * @param {Object} params * @param {number|string} params.room - The room where to execute the list * @returns {Promise<module:textroom-plugin~TEXTROOM_EVENT_PARTICIPANTS_LIST>} */ async listParticipants({ room, secret }) { const body = { request: REQUEST_LIST_PARTICIPANTS, room, }; if (typeof secret === 'string') body.secret = secret; const response = await this.message(body); const { event, data: evtdata } = this._getPluginEvent(response); if (event === PLUGIN_EVENT.PARTICIPANTS_LIST) return evtdata; const error = new Error(`unexpected response to ${body.request} request`); throw (error); } /** * Check if a room exists. * * @param {Object} params * @param {number|string} params.room - The involved room * @returns {Promise<module:textroom-plugin~TEXTROOM_EVENT_EXISTS>} */ async exists({ room }) { const body = { request: REQUEST_EXISTS, room, }; const response = await this.message(body); const { event, data: evtdata } = this._getPluginEvent(response); if (event === PLUGIN_EVENT.EXISTS) return evtdata; const error = new Error(`unexpected response to ${body.request} request`); throw (error); } /** * Create a textroom room. * * @param {Object} params * @param {number|string} params.room - The room identifier * @param {string} [params.admin_key] - The admin key needed for invoking the API * @param {string} [params.description] - A room description * @param {string} [params.secret] - The secret to be used when managing the room * @param {string} [params.pin] - The ping needed for joining the room * @param {boolean} [params.is_private] - Set room as private (hidden in list) * @param {boolean} [params.history] - Set number of messages to store as a history * @param {boolean} [params.post] - Set HTTP backend to forward incoming chat messages to * @param {boolean} [params.permanent] - Set to true to persist the room in the Janus config file * @returns {Promise<module:textroom-plugin~TEXTROOM_EVENT_CREATED>} */ async create({ room, admin_key, description, secret, pin, is_private, history, post, permanent }) { const body = { request: REQUEST_CREATE, room, }; if (typeof admin_key === 'string') body.admin_key = admin_key; if (typeof description === 'string') body.description = description; if (typeof secret === 'string') body.secret = secret; if (typeof pin === 'string') body.pin = pin; if (typeof is_private === 'boolean') body.is_private = is_private; if (typeof history === 'number') body.history = history; if (typeof post === 'string') body.post = post; if (typeof permanent === 'boolean') body.permanent = permanent; const response = await this.message(body); const { event, data: evtdata } = this._getPluginEvent(response); if (event === PLUGIN_EVENT.CREATED) return evtdata; const error = new Error(`unexpected response to ${body.request} request`); throw (error); } /** * Edit a textroom token list. * * @param {Object} params * @param {number|string} params.room - The involved room * @param {"enable"|"disable"|"add"|"remove"} params.action - The action to perform * @param {string[]} params.list - The list of tokens to add/remove * @param {string} [params.secret] - The optional secret needed to manage the room * @returns {Promise<module:textroom-plugin~TEXTROOM_EVENT_ALLOWED_RESPONSE>} */ async allow({ room, action, list, secret }) { const body = { request: REQUEST_ALLOW, room, action, }; if (list && list.length > 0) body.allowed = list; if (typeof secret === 'string') body.secret = secret; const response = await this.message(body); const { event, data: evtdata } = this._getPluginEvent(response); if (event === PLUGIN_EVENT.SUCCESS) return evtdata; const error = new Error(`unexpected response to ${body.request} request`); throw (error); } /** * Send an announcement to a textroom room. * * @param {Object} params * @param {number|string} params.room - The involved room * @param {string} params.text - The content of the announcement, as text * @param {string} [params.secret] - The optional secret needed to manage the room * @returns {Promise<module:textroom-plugin~TEXTROOM_EVENT_SUCCESS>} */ async announcement({ room, text, secret }) { const body = { request: REQUEST_ANNOUNCEMENT, room, text, }; if (typeof secret === 'string') body.secret = secret; const response = await this.message(body); const { event, data: evtdata } = this._getPluginEvent(response); if (event === PLUGIN_EVENT.SUCCESS) return evtdata; const error = new Error(`unexpected response to ${body.request} request`); throw (error); } /** * Kick an user out from a room. * * @param {Object} params * @param {number|string} params.room - The involved room * @param {string} params.username - The user to kick out * @param {string} [params.secret] - The optional secret needed for managing the room * @returns {Promise<module:textroom-plugin~TEXTROOM_EVENT_KICK_RESPONSE>} */ async kick({ room, username, secret }) { const body = { request: REQUEST_KICK, room, username, }; if (typeof secret === 'string') body.secret = secret; const response = await this.message(body); const { event, data: evtdata } = this._getPluginEvent(response); if (event === PLUGIN_EVENT.SUCCESS) { /* Add data missing from Janus response */ evtdata.room = body.room; evtdata.username = body.username; return evtdata; } const error = new Error(`unexpected response to ${body.request} request`); throw (error); } /** * Destroy a textroom room. * * @param {Object} params * @param {number|string} params.room - The room to destroy * @param {boolean} [params.permanent] - Set to true to remove the room from the Janus config file * @param {string} [params.secret] - The optional secret needed to manage the room * @returns {Promise<module:textroom-plugin~TEXTROOM_EVENT_DESTROYED>} */ async destroy({ room, permanent, secret }) { const body = { request: REQUEST_DESTROY, room, }; if (typeof permanent === 'boolean') body.permanent = permanent; if (typeof secret === 'string') body.secret = secret; const response = await this.message(body); const { event, data: evtdata } = this._getPluginEvent(response); if (event === PLUGIN_EVENT.DESTROYED) return evtdata; const error = new Error(`unexpected response to ${body.request} request`); throw (error); } } /** * The payload of the plugin message (cfr. Janus docs). * {@link https://janus.conf.meetecho.com/docs/textroom.html} * * @private * @typedef {Object} TextRoomData */ /** * The response event for textroom WebRTC establishment. * * @typedef {Object} TEXTROOM_EVENT_SUCCESS */ /** * The response event for textroom room list request. * * @typedef {Object} TEXTROOM_EVENT_ROOMS_LIST * @property {object[]} list - The list of the rooms as returned by Janus */ /** * The response event for textroom participants list request. * * @typedef {Object} TEXTROOM_EVENT_PARTICIPANTS_LIST * @property {number|string} room - The involved room * @property {object[]} participants - The list of participants as returned by Janus */ /** * The response event for textroom room exists request. * * @typedef {Object} TEXTROOM_EVENT_EXISTS * @property {number|string} room - The involved room * @property {boolean} exists - True if the rooms exists */ /** * The response event for textroom room create request. * * @typedef {Object} TEXTROOM_EVENT_CREATED * @property {number|string} room - The created room * @property {boolean} permanent - True if the room is being persisted in the Janus config file */ /** * The response event for textroom room destroy request. * * @typedef {Object} TEXTROOM_EVENT_DESTROYED * @property {number|string} room - The destroyed room * @property {boolean} permanent - True if the room removal is being persisted in the Janus config file */ /** * The response event for textroom participant kick request. * * @typedef {Object} TEXTROOM_EVENT_KICK_RESPONSE * @property {number|string} room - The involved room * @property {string} username - The username that has been kicked out */ /** * The response event for textroom ACL token edit request. * * @typedef {Object} TEXTROOM_EVENT_ALLOWED_RESPONSE * @property {number|string} room - The involved room * @property {string[]} list - The updated, complete, list of allowed tokens */ /** * The exported plugin descriptor. * * @type {Object} * @property {string} id - The plugin identifier used when attaching to Janus * @property {module:textroom-plugin~TextRoomHandle} Handle - The custom class implementing the plugin * @property {Object} EVENT - The events emitted by the plugin * @property {string} EVENT.TEXTROOM_ERROR {@link module:textroom-plugin~TextRoomHandle#event:TEXTROOM_ERROR TEXTROOM_ERROR} */ export default { id: PLUGIN_ID, Handle: TextRoomHandle, EVENT: { /** * Generic textroom error. * * @event module:textroom-plugin~TextRoomHandle#event:TEXTROOM_ERROR * @type {Error} */ TEXTROOM_ERROR: PLUGIN_EVENT.ERROR, }, };