UNPKG

@duncte123/obs-websocket-js

Version:

OBS Websocket API in Javascript, consumes @Palakis/obs-websocket

186 lines (185 loc) 8.08 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 events_1 = require("events"); const debug_1 = __importDefault(require("debug")); const Status_js_1 = require("./Status.js"); const authenticationHashing_js_1 = __importDefault(require("./utils/authenticationHashing.js")); const logAmbiguousError_js_1 = __importDefault(require("./utils/logAmbiguousError.js")); const camelCaseKeys_js_1 = __importDefault(require("./utils/camelCaseKeys.js")); class Socket extends events_1.EventEmitter { constructor() { super(...arguments); this.connected = false; this.debug = debug_1.default('obs-websocket-js:Socket'); } on(event, listener) { return super.on(event, listener); } emit(event, ...args) { this.debug('[emit] %s err: %o data: %o', event, ...args); return super.emit(event, ...args); } connect(args = {}) { return __awaiter(this, void 0, void 0, function* () { // eslint-disable-next-line no-param-reassign args = Object.assign({ address: 'localhost:4444', password: '', secure: false }, args); if (this.socket) { try { // Blindly try to close the socket. // Don't care if its already closed. // We just don't want any sockets to leak. this.socket.close(); } catch (error) { // These errors are probably safe to ignore, but debug log them just in case. this.debug('Failed to close previous WebSocket:', error.message); } } try { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion yield this.connect0(args.address, args.secure); yield this.authenticate(args.password); } catch (e) { this.socket.close(); this.connected = false; logAmbiguousError_js_1.default(this.debug, 'Connection failed:', e); // retrhow to let the user handle it throw e; } }); } /** * Opens a WebSocket connection to an obs-websocket server, but does not attempt any authentication. * * @param {String} address url without ws:// or wss:// prefix. * @param {Boolean} secure whether to us ws:// or wss:// * @returns {Promise} * @private * @return {Promise} on attempted creation of WebSocket connection. */ connect0(address, secure) { // we need to wrap this in a promise so we can resolve only when connected return new Promise((resolve, reject) => { let settled = false; this.debug('Attempting to connect to: %s (secure: %s)', address, secure); this.socket = new isomorphic_ws_1.default((secure ? 'wss://' : 'ws://') + address); // We only handle the initial connection error. // Beyond that, the consumer is responsible for adding their own generic `error` event listener. // FIXME: Unsure how best to expose additional information about the WebSocket error. this.socket.onerror = (err) => { if (settled) { logAmbiguousError_js_1.default(this.debug, 'Unknown Socket Error', err); this.emit('error', err); return; } settled = true; logAmbiguousError_js_1.default(this.debug, 'Websocket Connection failed:', err); reject(Status_js_1.Status.CONNECTION_ERROR); }; this.socket.onopen = () => { if (settled) { return; } this.connected = true; settled = true; this.debug('Connection opened: %s', address); this.emit('ConnectionOpened'); resolve(); }; // Looks like this should be bound. We don't technically cancel the connection when the authentication fails. this.socket.onclose = () => { this.connected = false; this.debug('Connection closed: %s', address); this.emit('ConnectionClosed'); }; // This handler must be present before we can call _authenticate. this.socket.onmessage = (msg) => { this.debug('[OnMessage]: %o', msg); const message = camelCaseKeys_js_1.default(JSON.parse(String(msg.data))); let err = {}; let data = {}; if (message.status === 'error') { err = message; } else { data = message; } // Emit the message with ID if available, otherwise try to find a non-messageId driven event. if (message.messageId) { this.emit(`obs:internal:message:id-${message.messageId}`, err, data); } else if (message.updateType) { this.emit(message.updateType, data); } else { logAmbiguousError_js_1.default(this.debug, 'Unrecognized Socket Message:', message); this.emit('message', message); } }; }); } /** * Authenticates to an obs-websocket server. Must already have an active connection before calling this method. * * @param {String} [password=''] authentication string. * @private * @return {Promise} on resolution of authentication call. */ authenticate(password = '') { return __awaiter(this, void 0, void 0, function* () { if (!this.connected) { throw Status_js_1.Status.NOT_CONNECTED; } const auth = yield this.send('GetAuthRequired'); if (!auth.authRequired) { this.debug('Authentication not Required'); this.emit('AuthenticationSuccess'); return Status_js_1.Status.AUTH_NOT_REQUIRED; } try { yield this.send('Authenticate', { auth: authenticationHashing_js_1.default(auth.salt || '', auth.challenge || '', password) }); } catch (e) { this.debug('Authentication Failure %o', e); this.emit('AuthenticationFailure'); throw e; } this.debug('Authentication Success'); this.emit('AuthenticationSuccess'); return null; }); } /** * Close and disconnect the WebSocket connection. * * @function * @category request * @return {Promise} */ disconnect() { return __awaiter(this, void 0, void 0, function* () { this.debug('Disconnect requested.'); if (this.socket) { yield this.socket.close(); } }); } } exports.Socket = Socket;