UNPKG

@mos-connection/connector

Version:
911 lines 77.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MosDevice = void 0; const net_1 = require("net"); const model_1 = require("@mos-connection/model"); const helper_1 = require("@mos-connection/helper"); const lib_1 = require("./lib"); const mosModel_1 = require("@mos-connection/helper/dist/mosModel"); const { ensureXMLObject, ensureArray, ensureSingular, ensureSingularArray } = helper_1.MosModel; class MosDevice { constructor(idPrimary, idSecondary, connectionConfig, primaryConnection, secondaryConnection, offSpecFailover, strict) { this._debug = false; this.supportedProfiles = { deviceType: 'MOS', profile0: false, profile1: false, profile2: false, profile3: false, profile4: false, profile5: false, profile6: false, profile7: false, }; // Use same names as IProfiles? this._disposed = false; this._scheduleCheckProfileValidnessTimeout = null; this._primaryConnection = null; this._secondaryConnection = null; this._currentConnection = null; // this._id = this.mosTypes.mosString128.create(connectionConfig.mosID).toString() this._idPrimary = idPrimary; this._idSecondary = idSecondary; this.socket = new net_1.Socket(); this.strict = strict; this.mosTypes = (0, model_1.getMosTypes)(this.strict); this.parseMosTypes = helper_1.MosModel.getParseMosTypes(this.strict); // Add params to this in MosConnection/MosDevice this.manufacturer = this.mosTypes.mosString128.create('RadioVision, Ltd.'); this.model = this.mosTypes.mosString128.create('TCS6000'); this.hwRev = this.mosTypes.mosString128.create('0.1'); // empty string returnes <hwRev/> this.swRev = this.mosTypes.mosString128.create('0.1'); this.DOM = this.mosTypes.mosTime.create(Date.now()); this.SN = this.mosTypes.mosString128.create('927748927'); this.ID = this.mosTypes.mosString128.create(connectionConfig ? connectionConfig.mosID : ''); this.time = this.mosTypes.mosTime.create(Date.now()); this.opTime = this.mosTypes.mosTime.create(Date.now()); this.mosRev = this.mosTypes.mosString128.create('2.8.5'); if (connectionConfig) { if (connectionConfig.profiles['0']) this.supportedProfiles.profile0 = true; if (connectionConfig.profiles['1']) this.supportedProfiles.profile1 = true; if (connectionConfig.profiles['2']) this.supportedProfiles.profile2 = true; if (connectionConfig.profiles['3']) this.supportedProfiles.profile3 = true; if (connectionConfig.profiles['4']) this.supportedProfiles.profile4 = true; if (connectionConfig.profiles['5']) this.supportedProfiles.profile5 = true; if (connectionConfig.profiles['6']) this.supportedProfiles.profile6 = true; if (connectionConfig.profiles['7']) this.supportedProfiles.profile7 = true; if (connectionConfig.isNCS) this.supportedProfiles.deviceType = 'NCS'; if (connectionConfig.debug) this._debug = connectionConfig.debug; } if (primaryConnection) { this._primaryConnection = primaryConnection; this._primaryConnection.on('connectionChanged', () => { this._emitConnectionChange(); if (offSpecFailover && this._currentConnection !== this._primaryConnection && this._primaryConnection?.connected) { this.switchConnections(); // and hope no current message goes lost } }); } if (secondaryConnection) { this._secondaryConnection = secondaryConnection; this._secondaryConnection.on('connectionChanged', () => this._emitConnectionChange()); } this._currentConnection = this._primaryConnection ?? this._secondaryConnection ?? null; if (this.strict) { const orgStack = new Error(); this._scheduleCheckProfileValidness(orgStack); } } /** True if MOS-device has connection to server (can send messages) */ get hasConnection() { return !!this._currentConnection; } /** Primary ID (probably the NCS-ID) */ get idPrimary() { return this._idPrimary; } /** Secondary / Buddy ID (probably the MOS-ID) */ get idSecondary() { return this._idSecondary; } /** Host name (IP-address) of the primary server */ get primaryHost() { return this._primaryConnection ? this._primaryConnection.host : null; } /** Name (ID) of the primary server */ get primaryId() { return this._primaryConnection ? this._primaryConnection.id : null; } /** Host name (IP-address) of the secondary (buddy) server */ get secondaryHost() { return this._secondaryConnection ? this._secondaryConnection.host : null; } /** Name (ID) of the secondary (buddy) server */ get secondaryId() { return this._secondaryConnection ? this._secondaryConnection.id : null; } connect() { if (this._primaryConnection) this._primaryConnection.connect(); if (this._secondaryConnection) this._secondaryConnection.connect(); } async dispose() { this._currentConnection = null; const ps = []; if (this._primaryConnection) ps.push(this._primaryConnection.dispose()); if (this._secondaryConnection) ps.push(this._secondaryConnection.dispose()); await Promise.all(ps); } async routeData(data, port) { // Suppress console spam: if (!(0, lib_1.has)(data, 'heartbeat')) { this.debugTrace('parsedData', data); // this.debugTrace('keys', Object.keys(data)) } // Route and format data: // Profile 0: ------------------------------------------------------------------------------------------------- if (data.heartbeat) { // send immediate reply on the same port: return new helper_1.MosModel.HeartBeat(port, undefined, this.strict); } else if (data.reqMachInfo && typeof this._callbackOnRequestMachineInfo === 'function') { if (port === 'query') throw new Error('message "reqMachInfo" is invalid on query port'); const m = await this._callbackOnRequestMachineInfo(); return new helper_1.MosModel.ListMachineInfo(m, port, this.strict); } // Profile 1: ------------------------------------------------------------------------------------------------- if (data.mosReqObj && typeof this._callbackOnRequestMOSOBject === 'function') { const mosReqObj = ensureXMLObject(data.mosReqObj, this.strict); const mosObj = await this._callbackOnRequestMOSOBject(this.parseMosTypes.string.createRequired(mosReqObj.objID, 'objID')); if (!mosObj) return null; return new helper_1.MosModel.MosObj(mosObj, this.strict); } else if (data.mosReqAll && typeof this._callbackOnRequestAllMOSObjects === 'function') { const mosReqAll = ensureXMLObject(data.mosReqAll, this.strict); const pause = this.parseMosTypes.number.createOptional(mosReqAll.pause, 'pause') || 0; const mosObjects = await this._callbackOnRequestAllMOSObjects(); setImmediate(() => { // spec: Pause, when greater than zero, indicates the number of seconds to pause // between individual mosObj messages. // Pause of zero indicates that all objects will be sent using the mosListAll message.. // https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqAll if (pause > 0) { if (mosObjects.length) { // const firstObject = mosObjects.shift() as IMOSObject // const resp = new MosObj(firstObject) // resolve(resp) const sendNextObject = () => { if (this._disposed) return; const nextObject = mosObjects.shift(); if (nextObject) { this.sendMOSObject(nextObject) .then(() => { setTimeout(sendNextObject, pause * 1000); }) .catch((e) => { // eslint-disable-next-line no-console console.error('Error in async mosObj response to mosReqAll', e); }); } }; setTimeout(sendNextObject, pause * 1000); } } else { this._sendAllMOSObjects(mosObjects).catch((e) => { // eslint-disable-next-line no-console console.error('Error in async mosListAll response to mosReqAll', e); }); } }); // What this should contain isn't well defined in the protocol return new helper_1.MosModel.MOSAck({ ID: this.mosTypes.mosString128.create('0'), Revision: 0, Description: this.mosTypes.mosString128.create(''), Status: model_1.IMOSAckStatus.ACK, }, this.strict); } else if (data.mosObj && typeof this._callbackOnMOSObjects === 'function') { const obj = helper_1.MosModel.XMLMosObject.fromXML('mosObj', data.mosObj, this.strict); const resp = await this._callbackOnMOSObjects([obj]); return new helper_1.MosModel.MOSAck(resp, this.strict); } else if (data.mosListAll && typeof this._callbackOnMOSObjects === 'function') { const objs = helper_1.MosModel.XMLMosObjects.fromXML('mosListAll', data.mosListAll, this.strict); const resp = await this._callbackOnMOSObjects(objs); return new helper_1.MosModel.MOSAck(resp, this.strict); } // Profile 2: ------------------------------------------------------------------------------------------------- // Translate deprecated messages into the functionally equivalent roElementActions: if (data.roStoryAppend) { // This is equivalent to inserting a story at the end of the running order const roStoryAppend = ensureXMLObject(data.roStoryAppend, this.strict); data.roElementAction = { roID: roStoryAppend.roID, operation: 'INSERT', element_target: { storyID: '', }, element_source: { story: roStoryAppend.story, }, }; } else if (data.roStoryInsert) { const roStoryInsert = ensureXMLObject(data.roStoryInsert, this.strict); data.roElementAction = { roID: roStoryInsert.roID, operation: 'INSERT', element_target: { storyID: roStoryInsert.storyID, }, element_source: { story: roStoryInsert.story, }, }; } else if (data.roStoryReplace) { const roStoryReplace = ensureXMLObject(data.roStoryReplace, this.strict); data.roElementAction = { roID: roStoryReplace.roID, operation: 'REPLACE', element_target: { storyID: roStoryReplace.storyID, }, element_source: { story: roStoryReplace.story, }, }; } else if (data.roStoryMove) { // From documentation: // **Note**: If the second <storyID> tag is blank move the story to the bottom of the Running Order. const roStoryMove = ensureXMLObject(data.roStoryMove, this.strict); let storyIDs; if (Array.isArray(roStoryMove.storyID)) { storyIDs = ensureSingularArray(roStoryMove.storyID, this.strict); } else { if (this.strict) { // The storyID is xml-converted to a string if the second tag is missing. // The spec says that there must be two storyID tags, so we'll throw an error here: return new helper_1.MosModel.ROAck({ ID: this.parseMosTypes.mosString128.createRequired(roStoryMove.roID, 'roID'), Status: this.mosTypes.mosString128.create(`The second <storyID> tag is missing in <roStoryMove>.`), Stories: [], }, this.strict); } else { // Non strict mode: This is technically out of spec, but it's a common mistake, so we'll handle it like so: const storyID = ensureSingular(roStoryMove.storyID, this.strict); storyIDs = [storyID, '']; } } data.roElementAction = { roID: roStoryMove.roID, operation: 'MOVE', element_target: { storyID: storyIDs[1], }, element_source: { storyID: storyIDs[0], }, }; } else if (data.roStorySwap) { const roStorySwap = ensureXMLObject(data.roStorySwap, this.strict); data.roElementAction = { roID: roStorySwap.roID, operation: 'SWAP', element_source: { storyID: roStorySwap.storyID, // an array }, }; } else if (data.roStoryDelete) { const roStoryDelete = ensureXMLObject(data.roStoryDelete, this.strict); data.roElementAction = { roID: roStoryDelete.roID, operation: 'DELETE', // element_target: { // storyID: roStoryDelete.storyID[1], // }, element_source: { storyID: roStoryDelete.storyID, }, }; } else if ((0, mosModel_1.isXMLObject)(data.roStoryMoveMultiple) && data.roStoryMoveMultiple.storyID) { const roStoryMoveMultiple = ensureXMLObject(data.roStoryMoveMultiple, this.strict); const stories = (Array.isArray(roStoryMoveMultiple.storyID) ? ensureSingularArray(roStoryMoveMultiple.storyID, this.strict) : [ensureSingular(roStoryMoveMultiple.storyID, this.strict)]).map((s) => this.parseMosTypes.string.createRequired(s, 'storyID')); { // From documentation: // Validation: Duplicate storyIDs are not permitted with in the storyID list. // This prevents the move from being ambiguous; if two IDs are the same, it is unclear // where in the playlist the story with duplicate ID must be placed. const uniqueStoryIds = new Set(); for (const storyId of stories) { if (uniqueStoryIds.has(storyId)) return new helper_1.MosModel.ROAck({ ID: this.parseMosTypes.mosString128.createRequired(roStoryMoveMultiple.roID, 'roID'), Status: this.mosTypes.mosString128.create(`Duplicate storyIDs are not permitted with in the storyID list.`), Stories: [], }, this.strict); uniqueStoryIds.add(storyId); } } let target; let sources; if (stories.length > 1) { target = stories[stories.length - 1]; sources = stories.slice(0, stories.length - 1); } else { if (this.strict) { // Technically a no-op: target = stories[0]; sources = []; } else { // Handling of edge-case: // If there is only a single storyId, we assume that the single mentioned story should be moved to the end of the playlist // (ie that there is supposed to be a second, blank storyId that was just omitted by the sender) target = ''; sources = stories; } } data.roElementAction = { roID: roStoryMoveMultiple.roID, operation: 'MOVE', element_target: { storyID: target, }, element_source: { storyID: sources, }, }; } else if (data.roItemInsert) { const roItemInsert = ensureXMLObject(data.roItemInsert, this.strict); data.roElementAction = { roID: roItemInsert.roID, operation: 'INSERT', element_target: { storyID: roItemInsert.storyID, itemID: roItemInsert.itemID, }, element_source: { item: roItemInsert.item, }, }; } else if (data.roItemReplace) { const roItemReplace = ensureXMLObject(data.roItemReplace, this.strict); data.roElementAction = { roID: roItemReplace.roID, operation: 'REPLACE', element_target: { storyID: roItemReplace.storyID, itemID: roItemReplace.itemID, }, element_source: { item: roItemReplace.item, }, }; } else if (data.roItemDelete) { const roItemDelete = ensureXMLObject(data.roItemDelete, this.strict); data.roElementAction = { roID: roItemDelete.roID, operation: 'DELETE', element_target: { storyID: roItemDelete.storyID, }, element_source: { itemID: roItemDelete.itemID, }, }; } else if ((0, mosModel_1.isXMLObject)(data.roItemMoveMultiple) && data.roItemMoveMultiple?.itemID && data.roItemMoveMultiple?.storyID) { const roItemMoveMultiple = ensureXMLObject(data.roItemMoveMultiple, this.strict); const items = (Array.isArray(roItemMoveMultiple.itemID) ? ensureSingularArray(roItemMoveMultiple.itemID, this.strict) : [ensureSingular(roItemMoveMultiple.itemID, this.strict)]).map((s) => this.parseMosTypes.string.createRequired(s, 'itemID')); // An additional validation checking the length of items can be added const l = items.length; const target = items[l - 1]; const sources = items.slice(0, l - 1); data.roElementAction = { roID: roItemMoveMultiple.roID, operation: 'MOVE', element_target: { storyID: roItemMoveMultiple.storyID, itemID: l === 1 ? undefined : target, }, element_source: { itemID: l === 1 ? items : sources, }, }; } if (data.roCreate && typeof this._callbackOnCreateRunningOrder === 'function') { const ro = helper_1.MosModel.XMLRunningOrder.fromXML('roCreate', data.roCreate, this.strict); const resp = await this._callbackOnCreateRunningOrder(ro); return new helper_1.MosModel.ROAck(resp, this.strict); } else if (data.roReplace && typeof this._callbackOnReplaceRunningOrder === 'function') { const ro = helper_1.MosModel.XMLRunningOrder.fromXML('roReplace', data.roReplace, this.strict); const resp = await this._callbackOnReplaceRunningOrder(ro); return new helper_1.MosModel.ROAck(resp, this.strict); } else if (data.roDelete && typeof this._callbackOnDeleteRunningOrder === 'function') { const roDelete = ensureXMLObject(data.roDelete, this.strict); const resp = await this._callbackOnDeleteRunningOrder(this.parseMosTypes.mosString128.createRequired(roDelete.roID, 'roID')); return new helper_1.MosModel.ROAck(resp, this.strict); } else if (data.roReq && typeof this._callbackOnRequestRunningOrder === 'function') { const roReq = ensureXMLObject(data.roReq, this.strict); const roId = this.parseMosTypes.mosString128.createRequired(roReq.roID, 'roID'); const ro = await this._callbackOnRequestRunningOrder(roId); if (ro) { return new helper_1.MosModel.ROList(ro, this.strict); } else { // RO not found return new helper_1.MosModel.ROAck({ ID: roId, Status: this.mosTypes.mosString128.create(model_1.IMOSAckStatus.NACK), Stories: [], }, this.strict); } } else if (data.roMetadataReplace && typeof this._callbackOnMetadataReplace === 'function') { const ro = helper_1.MosModel.XMLRunningOrderBase.fromXML('roMetadataReplace', data.roMetadataReplace, this.strict); const resp = await this._callbackOnMetadataReplace(ro); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementStat) && data.roElementStat.element === 'RO' && typeof this._callbackOnRunningOrderStatus === 'function') { const status = { ID: this.parseMosTypes.mosString128.createRequired(data.roElementStat.roID, 'roID'), Status: this.parseMosTypes.stringEnum.createRequired({ value: data.roElementStat.status, enum: model_1.IMOSObjectStatus, }, 'status'), Time: this.parseMosTypes.mosTime.createRequired(data.roElementStat.time, 'time'), }; const resp = await this._callbackOnRunningOrderStatus(status); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementStat) && data.roElementStat.element === 'STORY' && typeof this._callbackOnStoryStatus === 'function') { const status = { RunningOrderId: this.parseMosTypes.mosString128.createRequired(data.roElementStat.roID, 'roID'), ID: this.parseMosTypes.mosString128.createRequired(data.roElementStat.storyID, 'storyID'), Status: this.parseMosTypes.stringEnum.createRequired({ value: data.roElementStat.status, enum: model_1.IMOSObjectStatus, }, 'status'), Time: this.parseMosTypes.mosTime.createRequired(data.roElementStat.time, 'time'), }; const resp = await this._callbackOnStoryStatus(status); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementStat) && data.roElementStat.element === 'ITEM' && typeof this._callbackOnItemStatus === 'function') { const status = { RunningOrderId: this.parseMosTypes.mosString128.createRequired(data.roElementStat.roID, 'roID'), StoryId: this.parseMosTypes.mosString128.createRequired(data.roElementStat.storyID, 'storyID'), ID: this.parseMosTypes.mosString128.createRequired(data.roElementStat.itemID, 'itemID'), Status: this.parseMosTypes.stringEnum.createRequired({ value: data.roElementStat.status, enum: model_1.IMOSObjectStatus, }, 'status'), Time: this.parseMosTypes.mosTime.createRequired(data.roElementStat.time, 'time'), ObjectId: this.parseMosTypes.mosString128.createOptional(data.roElementStat.objID, 'objID'), Channel: this.parseMosTypes.mosString128.createOptional(data.roElementStat.itemChannel, 'itemChannel'), }; const resp = await this._callbackOnItemStatus(status); return new helper_1.MosModel.ROAck(resp, this.strict); } else if (data.roReadyToAir && typeof this._callbackOnReadyToAir === 'function') { const roReadyToAir = ensureXMLObject(data.roReadyToAir, this.strict); const resp = await this._callbackOnReadyToAir({ ID: this.parseMosTypes.mosString128.createRequired(roReadyToAir.roID, 'roID'), Status: this.parseMosTypes.stringEnum.createRequired({ value: roReadyToAir.roAir, enum: model_1.IMOSObjectAirStatus, }, 'roAir'), }); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementAction) && data.roElementAction.operation === 'INSERT' && (0, mosModel_1.isXMLObject)(data.roElementAction.element_source) && data.roElementAction.element_source.story && (0, mosModel_1.isXMLObject)(data.roElementAction.element_target) && typeof this._callbackOnROInsertStories === 'function') { const action = { RunningOrderID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.roID, 'roID'), StoryID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.element_target.storyID, 'element_target.storyID'), }; const stories = helper_1.MosModel.XMLROStories.fromXML('element_source.story', data.roElementAction.element_source.story, this.strict); const resp = await this._callbackOnROInsertStories(action, stories); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementAction) && data.roElementAction.operation === 'INSERT' && (0, mosModel_1.isXMLObject)(data.roElementAction.element_source) && data.roElementAction.element_source.item && (0, mosModel_1.isXMLObject)(data.roElementAction.element_target) && typeof this._callbackOnROInsertItems === 'function') { const action = { RunningOrderID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.roID, 'roID'), StoryID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.element_target.storyID, 'element_target.storyID'), ItemID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.element_target.itemID, 'element_target.itemID'), }; const items = helper_1.MosModel.XMLMosItems.fromXML('element_source.item', data.roElementAction.element_source.item, this.strict); const resp = await this._callbackOnROInsertItems(action, items); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementAction) && data.roElementAction.operation === 'REPLACE' && (0, mosModel_1.isXMLObject)(data.roElementAction.element_source) && data.roElementAction.element_source.story && (0, mosModel_1.isXMLObject)(data.roElementAction.element_target) && typeof this._callbackOnROReplaceStories === 'function') { const action = { RunningOrderID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.roID, 'roID'), StoryID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.element_target.storyID, 'element_target.storyID'), }; const stories = helper_1.MosModel.XMLROStories.fromXML('element_source.story', data.roElementAction.element_source.story, this.strict); const resp = await this._callbackOnROReplaceStories(action, stories); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementAction) && data.roElementAction.operation === 'REPLACE' && (0, mosModel_1.isXMLObject)(data.roElementAction.element_source) && data.roElementAction.element_source.item && (0, mosModel_1.isXMLObject)(data.roElementAction.element_target) && typeof this._callbackOnROReplaceItems === 'function') { const action = { RunningOrderID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.roID, 'roID'), StoryID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.element_target.storyID, 'element_target.storyID'), ItemID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.element_target.itemID, 'element_target.itemID'), }; const items = helper_1.MosModel.XMLMosItems.fromXML('element_source.item', data.roElementAction.element_source.item, this.strict); const resp = await this._callbackOnROReplaceItems(action, items); resp.Stories = []; // dont return these (?) return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementAction) && data.roElementAction.operation === 'MOVE' && (0, mosModel_1.isXMLObject)(data.roElementAction.element_source) && data.roElementAction.element_source.storyID && (0, mosModel_1.isXMLObject)(data.roElementAction.element_target) && typeof this._callbackOnROMoveStories === 'function') { const action = { RunningOrderID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.roID, 'roID'), StoryID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.element_target.storyID, 'element_target.storyID'), }; const storyIDs = helper_1.MosModel.XMLMosIDs.fromXML('element_source.storyID', data.roElementAction.element_source.storyID, this.strict); const resp = await this._callbackOnROMoveStories(action, storyIDs); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementAction) && data.roElementAction.operation === 'MOVE' && (0, mosModel_1.isXMLObject)(data.roElementAction.element_source) && data.roElementAction.element_source.itemID && (0, mosModel_1.isXMLObject)(data.roElementAction.element_target) && typeof this._callbackOnROMoveItems === 'function') { const action = { RunningOrderID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.roID, 'roID'), StoryID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.element_target.storyID, 'element_target.storyID'), ItemID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.element_target.itemID, 'element_target.itemID'), }; const itemIDs = helper_1.MosModel.XMLMosIDs.fromXML('element_source.itemID', data.roElementAction.element_source.itemID, this.strict); const resp = await this._callbackOnROMoveItems(action, itemIDs); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementAction) && data.roElementAction.operation === 'DELETE' && (0, mosModel_1.isXMLObject)(data.roElementAction.element_source) && data.roElementAction.element_source.storyID && typeof this._callbackOnRODeleteStories === 'function') { const stories = helper_1.MosModel.XMLMosIDs.fromXML('element_source.storyID', data.roElementAction.element_source.storyID, this.strict); const resp = await this._callbackOnRODeleteStories({ RunningOrderID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.roID, 'roID'), }, stories); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementAction) && data.roElementAction.operation === 'DELETE' && (0, mosModel_1.isXMLObject)(data.roElementAction.element_source) && data.roElementAction.element_source.itemID && (0, mosModel_1.isXMLObject)(data.roElementAction.element_target) && typeof this._callbackOnRODeleteItems === 'function') { const action = { RunningOrderID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.roID, 'roID'), StoryID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.element_target.storyID, 'element_target.storyID'), }; const items = helper_1.MosModel.XMLMosIDs.fromXML('element_source.itemID', data.roElementAction.element_source.itemID, this.strict); const resp = await this._callbackOnRODeleteItems(action, items); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementAction) && data.roElementAction.operation === 'SWAP' && (0, mosModel_1.isXMLObject)(data.roElementAction.element_source) && data.roElementAction.element_source.storyID && data.roElementAction.element_source.storyID.length === 2 && typeof this._callbackOnROSwapStories === 'function') { const stories = helper_1.MosModel.XMLMosIDs.fromXML('element_source.storyID', data.roElementAction.element_source.storyID, this.strict); const resp = await this._callbackOnROSwapStories({ RunningOrderID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.roID, 'roID'), }, stories[0], stories[1]); return new helper_1.MosModel.ROAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.roElementAction) && data.roElementAction.operation === 'SWAP' && (0, mosModel_1.isXMLObject)(data.roElementAction.element_source) && data.roElementAction.element_source.itemID && data.roElementAction.element_source.itemID.length === 2 && (0, mosModel_1.isXMLObject)(data.roElementAction.element_target) && typeof this._callbackOnROSwapItems === 'function') { const items = helper_1.MosModel.XMLMosIDs.fromXML('element_source.itemID', data.roElementAction.element_source.itemID, this.strict); const resp = await this._callbackOnROSwapItems({ RunningOrderID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.roID, 'roID'), StoryID: this.parseMosTypes.mosString128.createRequired(data.roElementAction.element_target.storyID, 'element_target.storyID'), }, items[0], items[1]); return new helper_1.MosModel.ROAck(resp, this.strict); } // Profile 3: ------------------------------------------------------------------------------------------------- if (data.mosItemReplace && typeof this._callbackOnItemReplace === 'function') { const mosItemReplace = ensureXMLObject(data.mosItemReplace, this.strict); const resp = await this._callbackOnItemReplace(this.parseMosTypes.mosString128.createRequired(mosItemReplace.ID, 'ID'), this.parseMosTypes.mosString128.createRequired(mosItemReplace.itemID, 'itemID'), helper_1.MosModel.XMLMosItem.fromXML('item', mosItemReplace.item, this.strict)); return new helper_1.MosModel.ROAck(resp, this.strict); } else if (data.mosObjCreate && typeof this._callbackOnObjectCreate === 'function') { const mosObjCreate = ensureXMLObject(data.mosObjCreate, this.strict); const resp = await this._callbackOnObjectCreate(helper_1.MosModel.XMLMosObject.fromXML('mosObjCreate', mosObjCreate, this.strict)); const ack = new helper_1.MosModel.MOSAck(resp, this.strict); return ack; } else if ((0, mosModel_1.isXMLObject)(data.mosReqObjAction) && data.mosReqObjAction.operation === 'NEW' && typeof this._callbackOnRequestObjectActionNew === 'function') { const resp = await this._callbackOnRequestObjectActionNew(helper_1.MosModel.XMLMosObject.fromXML('mosReqObjAction', data.mosReqObjAction, this.strict)); return new helper_1.MosModel.MOSAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.mosReqObjAction) && data.mosReqObjAction.operation === 'UPDATE' && typeof this._callbackOnRequestObjectActionUpdate === 'function') { const resp = await this._callbackOnRequestObjectActionUpdate(this.parseMosTypes.mosString128.createRequired(data.mosReqObjAction.objID, 'objID'), helper_1.MosModel.XMLMosObject.fromXML('mosReqObjAction', data.mosReqObjAction, this.strict)); return new helper_1.MosModel.MOSAck(resp, this.strict); } else if ((0, mosModel_1.isXMLObject)(data.mosReqObjAction) && data.mosReqObjAction.operation === 'DELETE' && typeof this._callbackOnRequestObjectActionDelete === 'function') { const resp = await this._callbackOnRequestObjectActionDelete(this.parseMosTypes.mosString128.createRequired(data.mosReqObjAction.objID, 'objID')); return new helper_1.MosModel.MOSAck(resp, this.strict); } else if (data.mosReqObjList && typeof this._callbackOnRequestObjectList === 'function') { const resp = await this._callbackOnRequestObjectList(helper_1.MosModel.XMLMosRequestObjectList.fromXML('mosReqObjList', data.mosReqObjList, this.strict)); return new helper_1.MosModel.MosObjList(resp, this.strict); } else if (data.mosReqSearchableSchema && typeof this._callbackOnRequestSearchableSchema === 'function') { const mosReqSearchableSchema = ensureXMLObject(data.mosReqSearchableSchema, this.strict); const resp = await this._callbackOnRequestSearchableSchema(this.parseMosTypes.string.createRequired(mosReqSearchableSchema.username, 'username')); return new helper_1.MosModel.MosListSearchableSchema(resp, this.strict); } // Profile 4: ------------------------------------------------------------------------------------------------- if (data.roReqAll && typeof this._callbackOnRequestAllRunningOrders === 'function') { const list = await this._callbackOnRequestAllRunningOrders(); const roListAll = new helper_1.MosModel.ROListAll(list, this.strict); return roListAll; } else if (data.roStorySend && typeof this._callbackOnRunningOrderStory === 'function') { const story = helper_1.MosModel.XMLROFullStory.fromXML('roStorySend', data.roStorySend, this.strict); const resp = await this._callbackOnRunningOrderStory(story); return new helper_1.MosModel.ROAck(resp, this.strict); // TODO: Use reject if function dont exists? Put Nack in ondata } // Unsupported messages: -------------------------------------------------------------------------------------- { this.debugTrace('Unsupported function'); this.debugTrace(data); const keys = Object.keys(data).filter((key) => ['ncsID', 'mosID', 'messageID'].indexOf(key) === -1); return new helper_1.MosModel.MOSAck({ ID: this.mosTypes.mosString128.create('0'), Revision: 0, Description: this.mosTypes.mosString128.create(`Unsupported function: "${keys.join(', ')}"`), Status: model_1.IMOSAckStatus.NACK, }, this.strict); } } // ============================================================================================================ // ========================== Profile 0 =================================================================== // ============================================================================================================ async requestMachineInfo() { const message = new helper_1.MosModel.ReqMachInfo(this.strict); const reply = await this.executeCommand(message); return this.handleParseReply((strict) => helper_1.MosModel.XMLMosListMachInfo.fromXML('listMachInfo', reply.mos.listMachInfo, strict)); } onRequestMachineInfo(cb) { this.checkProfile('onRequestMachineInfo', 'profile1'); this._callbackOnRequestMachineInfo = cb; } onConnectionChange(cb) { this.checkProfile('onConnectionChange', 'profile1'); this._callbackOnConnectionChange = cb; } getConnectionStatus() { const primary = this._primaryConnection?.getConnectedStatus(); const secondary = this._secondaryConnection?.getConnectedStatus(); return { PrimaryConnected: primary ? primary.connected : false, PrimaryStatus: primary ? `Primary: ${primary.status}` : 'No primary connection', SecondaryConnected: secondary ? secondary.connected : false, SecondaryStatus: secondary ? `Secondary: ${secondary.status}` : 'No secondary connection', }; } // Deprecated methods: /** @deprecated getMachineInfo is deprecated, use requestMachineInfo instead */ async getMachineInfo() { return this.requestMachineInfo(); } /** @deprecated onGetMachineInfo is deprecated, use onRequestMachineInfo instead */ onGetMachineInfo(cb) { return this.onRequestMachineInfo(cb); } // ============================================================================================================ // ========================== Profile 1 =================================================================== // ============================================================================================================ async sendMOSObject(obj) { const message = new helper_1.MosModel.MosObj(obj, this.strict); const reply = await this.executeCommand(message); return this.handleParseReply((strict) => helper_1.MosModel.XMLMosAck.fromXML('mosAck', reply.mos.mosAck, strict)); } onRequestMOSObject(cb) { this.checkProfile('onRequestMOSObject', 'profile1'); this._callbackOnRequestMOSOBject = cb; } async sendRequestMOSObject(objID) { const message = new helper_1.MosModel.ReqMosObj(objID, this.strict); const reply = await this.executeCommand(message); if (reply.mos.roAck) { throw this.badRoAckReply(ensureXMLObject(reply.mos.roAck, this.strict)); } else if (reply.mos.mosObj) { return this.handleParseReply((strict) => helper_1.MosModel.XMLMosObject.fromXML('mosObj', reply.mos.mosObj, strict)); } else { throw this.unknownReply(reply); } } onRequestAllMOSObjects(cb) { this.checkProfile('onRequestAllMOSObjects', 'profile1'); this._callbackOnRequestAllMOSObjects = cb; } onMOSObjects(cb) { this.checkProfile('onMOSObjects', 'profile1'); this._callbackOnMOSObjects = cb; } async sendRequestAllMOSObjects(pause = 0) { if (typeof this._callbackOnMOSObjects !== 'function') { throw new Error('Cannot sent request, because callback onMOSObjects() is required'); } const message = new helper_1.MosModel.ReqMosObjAll(pause, this.strict); const reply = await this.executeCommand(message); return this.handleParseReply((strict) => helper_1.MosModel.XMLMosAck.fromXML('mosAck', reply.mos.mosAck, strict)); // Then we'll be sent mosListAll or mosObj messages separately, // handled in the callback in this.onMOSObjects(cb) } /** * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosListAll */ async _sendAllMOSObjects(objs) { const message = new helper_1.MosModel.MosListAll(objs, this.strict); const reply = await this.executeCommand(message); if (reply.mos) { const ack = helper_1.MosModel.XMLMosAck.fromXML('mosAck', reply.mos.mosAck, this.strict); return ack; } else { throw this.unknownReply(reply); } } /** * @deprecated getMOSObject is deprecated, use sendRequestMOSObject instead */ async getMOSObject(objId) { return this.sendRequestMOSObject(objId); } /** @deprecated getAllMOSObjects is deprecated, use sendRequestAllMOSObjects instead */ async getAllMOSObjects() { return this.sendRequestAllMOSObjects(); } // ============================================================================================================ // ========================== Profile 2 =================================================================== // ============================================================================================================ onCreateRunningOrder(cb) { this.checkProfile('onCreateRunningOrder', 'profile2'); this._callbackOnCreateRunningOrder = cb; } async sendCreateRunningOrder(ro) { const message = new helper_1.MosModel.ROCreate(ro, this.strict); const reply = await this.executeCommand(message); return this.handleParseReply((strict) => helper_1.MosModel.XMLMosROAck.fromXML('roAck', reply.mos.roAck, strict)); } onReplaceRunningOrder(cb) { this.checkProfile('onReplaceRunningOrder', 'profile2'); this._callbackOnReplaceRunningOrder = cb; } async sendReplaceRunningOrder(ro) { const message = new helper_1.MosModel.ROReplace(ro, this.strict); const reply = await this.executeCommand(message); return this.handleParseReply((strict) => helper_1.MosModel.XMLMosROAck.fromXML('roAck', reply.mos.roAck, strict)); } onDeleteRunningOrder(cb) { this.checkProfile('onDeleteRunningOrder', 'profile2'); this._callbackOnDeleteRunningOrder = cb; } async sendDeleteRunningOrder(runningOrderId) { const message = new helper_1.MosModel.RODelete(runningOrderId, this.strict); const reply = await this.executeCommand(message); return this.handleParseReply((strict) => helper_1.MosModel.XMLMosROAck.fromXML('roAck', reply.mos.roAck, strict)); } onRequestRunningOrder(cb) { this.checkProfile('onRequestRunningOrder', 'profile2'); this._callbackOnRequestRunningOrder = cb; } async sendRequestRunningOrder(runningOrderId) { const message = new helper_1.MosModel.ROReq(runningOrderId, this.strict); const reply = await this.executeCommand(message); if (reply.mos.roList) { return this.handleParseReply((strict) => helper_1.MosModel.XMLRunningOrder.fromXML('roList', ensureXMLObject(reply.mos.roList, strict), strict)); } else if (reply.mos.roAck) { throw this.badRoAckReply(ensureXMLObject(reply.mos.roAck, this.strict)); } throw this.unknownReply(reply); } /** * @deprecated getRunningOrder is deprecated, use sendRequestRunningOrder instead */ async getRunningOrder(runningOrderId) { return this.sendRequestRunningOrder(runningOrderId); } onMetadataReplace(cb) { this.checkProfile('onMetadataReplace', 'profile2'); this._callbackOnMetadataReplace = cb; } async sendMetadataReplace(metadata) { const message = new helper_1.MosModel.ROMetadataReplace(metadata, this.strict); const reply = await this.executeCommand(message); return this.handleParseReply((strict) => helper_1.MosModel.XMLMosROAck.fromXML('roAck', reply.mos.roAck, strict)); } onRunningOrderStatus(cb) { this.checkProfile('onRunningOrderStatus', 'profile2'); this._callbackOnRunningOrderStatus = cb; } onStoryStatus(cb) { this.checkProfile('onStoryStatus', 'profile2'); this._callbackOnStoryStatus = cb; } onItemStatus(cb) { this.checkProfile('onItemStatus', 'profile2'); this._callbackOnItemStatus = cb; } /** @deprecated setRunningOrderStatus is deprecated, use sendRunningOrderStatus instead */ async setRunningOrderStatus(status) { return this.sendRunningOrderStatus(status); } /** @deprecated setStoryStatus is deprecated, use sendStoryStatus instead */ async setStoryStatus(status) { return this.sendStoryStatus(status); } /** @deprecated setItemStatus is deprec