@duncte123/obs-websocket-js
Version:
OBS Websocket API in Javascript, consumes @Palakis/obs-websocket
186 lines (185 loc) • 8.08 kB
JavaScript
"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;