From b5f46e88bd56ff988cb87e0914769143be85a4b8 Mon Sep 17 00:00:00 2001 From: olzzon Date: Mon, 31 Mar 2025 11:26:21 +0200 Subject: [PATCH 1/8] fix: Initial connection with hotStandby enabled, should always check both servers for availablity upon start. --- packages/connector/src/MosConnection.ts | 98 +++++++++++++++++-------- 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/packages/connector/src/MosConnection.ts b/packages/connector/src/MosConnection.ts index dcab6a88..d9728faa 100644 --- a/packages/connector/src/MosConnection.ts +++ b/packages/connector/src/MosConnection.ts @@ -130,6 +130,12 @@ export class MosConnection extends EventEmitter implements ) } + // Create the secondary connection if configured + const isHotStandby = connectionOptions.secondary?.openMediaHotStandby || false + // For hot-standby, initially we need both connections to be able to establish + // so we can determine which one is active + const useHeartbeatsOnSecondary = !isHotStandby || true + if (connectionOptions.secondary) { secondary = new NCSServerConnection( connectionOptions.secondary.id, @@ -158,13 +164,13 @@ export class MosConnection extends EventEmitter implements MosConnection.nextSocketID, connectionOptions.secondary.ports?.lower ?? MosConnection.CONNECTION_PORT_LOWER, 'lower', - !connectionOptions.secondary.openMediaHotStandby + useHeartbeatsOnSecondary ) secondary.createClient( MosConnection.nextSocketID, connectionOptions.secondary.ports?.upper ?? MosConnection.CONNECTION_PORT_UPPER, 'upper', - !connectionOptions.secondary.openMediaHotStandby + useHeartbeatsOnSecondary ) if (!connectionOptions.primary.dontUseQueryPort) { secondary.createClient( @@ -174,41 +180,71 @@ export class MosConnection extends EventEmitter implements false ) } + } + + if (isHotStandby && secondary) { + // Check both connections on startup to determine which one to use + this.emit('info', 'Initializing with OpenMedia hot-standby configuration') + + primary.connect() + secondary.connect() + // Wait for initial connection attempts + await new Promise((resolve) => setTimeout(resolve, 2000)) + + const primaryStatus = primary.getConnectedStatus() + const secondaryStatus = secondary.getConnectedStatus() + // Handle that .openMediaHotStandby should not check for heartbeats on // the secondary connection when the primary is connected // And disable heartbeats on the primary when the primary is disconnected - if (connectionOptions.secondary?.openMediaHotStandby) { - // Initially disable heartbeats on secondary since primary should be attempted first + if (primaryStatus.connected) { + // Initially disable heartbeats on secondary since primary is connected secondary.disableHeartbeats() primary.enableHeartbeats() - - primary.on('connectionChanged', () => { - if (primary.connected) { - secondary?.disableHeartbeats() - primary.enableHeartbeats() - } else { - secondary?.enableHeartbeats() - primary.disableHeartbeats() - } - }) - - // Handle secondary connection changes - setTimeout(() => { - secondary?.on('connectionChanged', () => { - if (!primary.connected) { - // Secondary is active - if (secondary?.connected) { - secondary.enableHeartbeats() - primary.disableHeartbeats() - } else { - // Secondary disconnected - try to re-enable primary - primary.enableHeartbeats() - secondary?.disableHeartbeats() - } - } - }) - }, 50) + } else if (secondaryStatus.connected) { + // Primary is down, but secondary is available - use secondary + secondary.enableHeartbeats() + primary.disableHeartbeats() + } else { + // Both are down initially, but try both + this.emit('info', 'No connections established, enabling heartbeats on both') + primary.enableHeartbeats() + secondary.enableHeartbeats() } + + // Failover handling: + primary.on('connectionChanged', () => { + // Refresh connectionstatus + const status = primary.getConnectedStatus() + if (status.connected) { + this.emit('info', 'Primary connection established, switching to primary') + secondary?.disableHeartbeats() + primary.enableHeartbeats() + } else { + // Primary disconnected, enable secondary heartbeats + this.emit('info', 'Primary disconnected, enabling secondary') + secondary?.enableHeartbeats() + primary.disableHeartbeats() + } + }) + + secondary.on('connectionChanged', () => { + // Refresh connectionstatus + const primaryStatus = primary.getConnectedStatus() + const secondaryStatus = secondary.getConnectedStatus() + + if (!primaryStatus.connected && secondaryStatus.connected) { + // Secondary is active when primary is down + this.emit('info', 'Secondary active, primary down') + secondary.enableHeartbeats() + primary.disableHeartbeats() + } else if (!secondaryStatus.connected && !primaryStatus.connected) { + // Both are down, try to connect to both + this.emit('info', 'Both connections down, attempting reconnection to both') + primary.enableHeartbeats() + secondary.enableHeartbeats() + } + }) } return this._registerMosDevice( From e9cf9e540235a0f837e77070506e824f172e73c1 Mon Sep 17 00:00:00 2001 From: olzzon Date: Mon, 31 Mar 2025 13:39:50 +0200 Subject: [PATCH 2/8] feat: initial sketch for mos-dummy-device --- .repomixignore | 5 + packages/mos-dummy-device/.eslintrc.js | 6 + packages/mos-dummy-device/README.md | 116 + packages/mos-dummy-device/package.json | 19 + .../rundowns/defaultrundown.json | 82 + packages/mos-dummy-device/src/index.ts | 462 ++ packages/mos-dummy-device/tsconfig.json | 18 + repomix-output.txt | 7165 +++++++++++++++++ yarn.lock | 72 +- 9 files changed, 7944 insertions(+), 1 deletion(-) create mode 100644 .repomixignore create mode 100644 packages/mos-dummy-device/.eslintrc.js create mode 100644 packages/mos-dummy-device/README.md create mode 100644 packages/mos-dummy-device/package.json create mode 100644 packages/mos-dummy-device/rundowns/defaultrundown.json create mode 100644 packages/mos-dummy-device/src/index.ts create mode 100644 packages/mos-dummy-device/tsconfig.json create mode 100644 repomix-output.txt diff --git a/.repomixignore b/.repomixignore new file mode 100644 index 00000000..72aca00a --- /dev/null +++ b/.repomixignore @@ -0,0 +1,5 @@ +__tests__/ +__mocks__/ +dist/ +yarn.lock +CHANGELOG.md \ No newline at end of file diff --git a/packages/mos-dummy-device/.eslintrc.js b/packages/mos-dummy-device/.eslintrc.js new file mode 100644 index 00000000..2f74b819 --- /dev/null +++ b/packages/mos-dummy-device/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + extends: '../../.eslintrc', + rules: { + 'no-console': 'off', + }, +} diff --git a/packages/mos-dummy-device/README.md b/packages/mos-dummy-device/README.md new file mode 100644 index 00000000..49c6311e --- /dev/null +++ b/packages/mos-dummy-device/README.md @@ -0,0 +1,116 @@ +# MOS Dummy Server + +A dummy MOS (Media Object Server) server for testing MOS clients with failover capabilities. This server implements the MOS protocol and allows you to simulate connection issues and manage rundowns through JSON files. + +## Features + +- MOS protocol support Profiles 0, 1, 2, 3, and 4 +- JSON-based rundown downs in the `rundowns` directory +- Hot reloading of rundowns from the filesystem +- Simulation of server outages for failover testing +- Command-line interface for manual testing + +## Installation + +1. Clone this repository +2. Install dependencies: + +```bash +npm install +``` + +3. Build the project: + +```bash +npm run build +``` + +## Usage + +### Starting the server + +```bash +npm start +``` + +This will start the MOS server on the default ports (10540, 10541, 10542). + +### Development mode + +To run in development mode with automatic reloading: + +```bash +npm run dev +``` + +Or with file watching: + +```bash +npm run watch +``` + +### Rundown Management + +Rundowns are stored as JSON files in the `rundowns` directory. The server watches this directory for changes: + +- Adding a new `.json` file creates a new rundown +- Modifying a file updates the rundown +- Deleting a file removes the rundown + +### Rundown JSON Format + +Here's an example of a rundown JSON file: + +```json +{ + "ID": "EXAMPLE_RO", + "Slug": "Example Rundown", + "DefaultChannel": "A", + "Stories": [ + { + "ID": "EXAMPLE_RO_STORY_1", + "Slug": "Story 1", + "Number": "1", + "Items": [ + { + "ID": "EXAMPLE_RO_STORY_1_ITEM_1", + "Slug": "Item 1 in Story 1", + "ObjectID": "OBJ_EXAMPLE_RO_STORY_1_ITEM_1", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "Item 1 in Story 1", + "Duration": 1000, + "TimeBase": 100 + } + ] + } + ] +} +``` + +### Command-Line Interface + +While the server is running, you can use the following commands: + +- `outage [duration_ms]` - Simulate a server outage for the specified duration (default: 5000ms) +- `exit` - Shutdown the server and exit + +Example: `outage 10000` will simulate a 10-second outage. + +## Configuration + +You can modify the configuration in the `SERVER_CONFIG` object in `src/index.ts`: + +- `mosID`: The MOS ID of the server +- `acceptsConnections`: Whether to accept incoming connections +- `profiles`: The MOS profiles to support +- `debug`: Enable/disable debug logging +- `ports`: The ports to use for MOS communication + +## Testing Failover + +To test failover with your MOS client: + +1. Start the server +2. Connect your MOS client to the server +3. Use the `outage` command to simulate an outage +4. Observe how your client handles the disconnection and reconnection diff --git a/packages/mos-dummy-device/package.json b/packages/mos-dummy-device/package.json new file mode 100644 index 00000000..9eff85f8 --- /dev/null +++ b/packages/mos-dummy-device/package.json @@ -0,0 +1,19 @@ +{ + "name": "mos-dummy-server", + "version": "1.0.0", + "description": "A dummy MOS server for testing MOS clients with failover capabilities", + "main": "dist/index.js", + "scripts": { + "dev": "ts-node src/index.ts" + }, + "license": "MIT", + "dependencies": { + "@mos-connection/connector": "^4.2.2", + "chokidar": "^3.5.3" + }, + "devDependencies": { + "@types/node": "^20.10.5", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + } +} diff --git a/packages/mos-dummy-device/rundowns/defaultrundown.json b/packages/mos-dummy-device/rundowns/defaultrundown.json new file mode 100644 index 00000000..88f3dbae --- /dev/null +++ b/packages/mos-dummy-device/rundowns/defaultrundown.json @@ -0,0 +1,82 @@ +{ + "ID": "EXAMPLE_RO", + "Slug": "Example Rundown", + "DefaultChannel": "A", + "Stories": [ + { + "ID": "EXAMPLE_RO_STORY_1", + "Slug": "Opening Story", + "Number": "1", + "Items": [ + { + "ID": "EXAMPLE_RO_STORY_1_ITEM_1", + "Slug": "Intro Graphic", + "ObjectID": "OBJ_INTRO_GRAPHIC", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "Opening Title Graphic", + "Duration": 500, + "TimeBase": 100 + }, + { + "ID": "EXAMPLE_RO_STORY_1_ITEM_2", + "Slug": "Presenter Introduction", + "ObjectID": "OBJ_PRESENTER_CAM1", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "Camera 1 - Wide", + "Duration": 1000, + "TimeBase": 100 + } + ] + }, + { + "ID": "EXAMPLE_RO_STORY_2", + "Slug": "News Story", + "Number": "2", + "Items": [ + { + "ID": "EXAMPLE_RO_STORY_2_ITEM_1", + "Slug": "News Intro", + "ObjectID": "OBJ_NEWS_GRAPHIC", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "News Title Card", + "Duration": 300, + "TimeBase": 100 + }, + { + "ID": "EXAMPLE_RO_STORY_2_ITEM_2", + "Slug": "News Package", + "ObjectID": "OBJ_NEWS_PKG_1", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "News Package Video", + "Duration": 3000, + "TimeBase": 100 + } + ] + }, + { + "ID": "EXAMPLE_RO_STORY_3", + "Slug": "Closing Story", + "Number": "3", + "Items": [ + { + "ID": "EXAMPLE_RO_STORY_3_ITEM_1", + "Slug": "Closing Remarks", + "ObjectID": "OBJ_PRESENTER_CAM2", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "Camera 2 - Close Up", + "Duration": 1500, + "TimeBase": 100 + }, + { + "ID": "EXAMPLE_RO_STORY_3_ITEM_2", + "Slug": "Outro Graphic", + "ObjectID": "OBJ_OUTRO_GRAPHIC", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "Closing Credits", + "Duration": 800, + "TimeBase": 100 + } + ] + } + ] + } \ No newline at end of file diff --git a/packages/mos-dummy-device/src/index.ts b/packages/mos-dummy-device/src/index.ts new file mode 100644 index 00000000..e11d0f88 --- /dev/null +++ b/packages/mos-dummy-device/src/index.ts @@ -0,0 +1,462 @@ +import { MosConnection, ConnectionConfig, getMosTypes, IMOSRunningOrder, MosDevice } from '@mos-connection/connector' +import * as fs from 'fs' +import * as path from 'path' +import * as chokidar from 'chokidar' + +const SERVER_CONFIG = { + mosID: 'DUMMY.MOS.PRIMARY', + acceptsConnections: true, + profiles: { + '0': true, // Profile 0 is mandatory + '1': true, // Basic object exchange + '2': true, // Running order/playlist exchange + '3': true, // Advanced object-based workflow + '4': true, // Advanced rundown functionality + }, + debug: true, + ports: { + lower: 10540, // Default MOS ports + upper: 10541, + query: 10542, + }, + openRelay: true, +} + +class DummyMosServer { + private mosConnection: MosConnection + private rundownsDir: string + private devicesMap: Map = new Map() + private rundowns: Map = new Map() + private isServerOnline = true + private mosTypes = getMosTypes(false) // Non-strict mode + + constructor(rundownsDir: string) { + this.rundownsDir = rundownsDir + + this.mosConnection = new MosConnection(new ConnectionConfig(SERVER_CONFIG)) + // Set up device connection callback + this.mosConnection.onConnection(this.handleNewConnection.bind(this)) + + this.setupRundownWatcher() + this.loadRundowns() + } + + public async start(): Promise { + console.log('Starting dummy MOS server...') + + await this.mosConnection.init() + + console.log(`MOS server started with ID: ${SERVER_CONFIG.mosID}`) + console.log( + `Listening on ports: ${SERVER_CONFIG.ports.lower}, ${SERVER_CONFIG.ports.upper}, ${SERVER_CONFIG.ports.query}` + ) + } + + // Handle new MOS device connections + private handleNewConnection(mosDevice: MosDevice): void { + console.log(`New connection from device: ${mosDevice.idPrimary}`) + + // Store reference to the device + this.devicesMap.set(mosDevice.idPrimary, mosDevice) + + // Set up callbacks for Profile 0 + mosDevice.onRequestMachineInfo(async () => { + console.log('Received machine info request') + return { + manufacturer: this.mosTypes.mosString128.create('DummyMosServer'), + model: this.mosTypes.mosString128.create('Testing Device'), + hwRev: this.mosTypes.mosString128.create('1.0'), + swRev: this.mosTypes.mosString128.create('1.0.0'), + DOM: this.mosTypes.mosString128.create('2023-01-01'), + SN: this.mosTypes.mosString128.create('DUMMY001'), + ID: this.mosTypes.mosString128.create(SERVER_CONFIG.mosID), + time: this.mosTypes.mosTime.create(new Date()), + mosRev: this.mosTypes.mosString128.create('2.8.5'), + supportedProfiles: { + deviceType: 'MOS', + profile0: true, + profile1: true, + profile2: true, + profile3: true, + profile4: true, + }, + } + }) + + // Set up callbacks for Profile 2 + mosDevice.onRequestRunningOrder(async (roId) => { + const roIdStr = this.mosTypes.mosString128.stringify(roId) + console.log(`Received request for running order: ${roIdStr}`) + + if (this.rundowns.has(roIdStr)) { + console.log(`Returning running order: ${roIdStr}`) + return this.rundowns.get(roIdStr) || null + } + + console.log(`Running order not found: ${roIdStr}`) + return null + }) + + // Set up Profile 4 callbacks + mosDevice.onRequestAllRunningOrders(async () => { + console.log('Received request for all running orders') + return Array.from(this.rundowns.values()) + }) + + // Send all rundowns to the device on connection + this.sendAllRunningOrders(mosDevice) + } + + // Load all rundowns from the rundowns directory + private loadRundowns(): void { + try { + if (!fs.existsSync(this.rundownsDir)) { + fs.mkdirSync(this.rundownsDir, { recursive: true }) + } + + const files = fs.readdirSync(this.rundownsDir).filter((file) => file.endsWith('.json')) + + files.forEach((file) => { + try { + const filePath = path.join(this.rundownsDir, file) + const content = fs.readFileSync(filePath, 'utf8') + const rundown = JSON.parse(content) as IMOSRunningOrder + + // Ensure ID is set correctly + if (!rundown.ID) { + const id = path.basename(file, '.json') + rundown.ID = this.mosTypes.mosString128.create(id) + } + + // Process and validate rundown (convert strings to MOS types) + const processedRundown = this.processRundown(rundown) + + // Add to rundowns map + const roId = this.mosTypes.mosString128.stringify(processedRundown.ID) + this.rundowns.set(roId, processedRundown) + + console.log(`Loaded rundown: ${roId}`) + } catch (error) { + console.error(`Error loading rundown from ${file}:`, error) + } + }) + + console.log(`Loaded ${this.rundowns.size} rundowns`) + } catch (error) { + console.error('Error loading rundowns:', error) + } + } + + // Watch the rundowns directory for changes + private setupRundownWatcher(): void { + const watcher = chokidar.watch(this.rundownsDir, { + persistent: true, + ignoreInitial: true, + }) + + watcher + .on('add', this.handleRundownFileAdded.bind(this)) + .on('change', this.handleRundownFileChanged.bind(this)) + .on('unlink', this.handleRundownFileRemoved.bind(this)) + + console.log(`Watching directory: ${this.rundownsDir} for rundown changes`) + } + + // Handle new rundown file + private handleRundownFileAdded(filePath: string): void { + if (!filePath.endsWith('.json')) return + + try { + console.log(`New rundown file detected: ${filePath}`) + const content = fs.readFileSync(filePath, 'utf8') + const rundown = JSON.parse(content) as IMOSRunningOrder + + // Ensure ID is set correctly + if (!rundown.ID) { + const id = path.basename(filePath, '.json') + rundown.ID = this.mosTypes.mosString128.create(id) + } + + // Process and validate rundown + const processedRundown = this.processRundown(rundown) + + // Add to rundowns map + const roId = this.mosTypes.mosString128.stringify(processedRundown.ID) + this.rundowns.set(roId, processedRundown) + + console.log(`Added new rundown: ${roId}`) + + // Send to all connected devices + this.broadcastRunningOrder(processedRundown) + } catch (error) { + console.error(`Error processing new rundown file: ${filePath}`, error) + } + } + + // Handle rundown file changes + private handleRundownFileChanged(filePath: string): void { + if (!filePath.endsWith('.json')) return + + try { + console.log(`Rundown file changed: ${filePath}`) + const content = fs.readFileSync(filePath, 'utf8') + const rundown = JSON.parse(content) as IMOSRunningOrder + + // Process and validate rundown + const processedRundown = this.processRundown(rundown) + + // Update in rundowns map + const roId = this.mosTypes.mosString128.stringify(processedRundown.ID) + this.rundowns.set(roId, processedRundown) + + console.log(`Updated rundown: ${roId}`) + + // Send to all connected devices + this.broadcastRunningOrder(processedRundown, true) + } catch (error) { + console.error(`Error processing changed rundown file: ${filePath}`, error) + } + } + + // Handle rundown file removals + private handleRundownFileRemoved(filePath: string): void { + if (!filePath.endsWith('.json')) return + + try { + const id = path.basename(filePath, '.json') + console.log(`Rundown file removed: ${filePath} (ID: ${id})`) + + // Remove from rundowns map + if (this.rundowns.has(id)) { + this.rundowns.delete(id) + console.log(`Removed rundown: ${id}`) + + // Notify all connected devices + this.broadcastRunningOrderDelete(id) + } + } catch (error) { + console.error(`Error handling removed rundown file: ${filePath}`, error) + } + } + + // Process a rundown to ensure all required MOS types are properly formatted + private processRundown(rundown: IMOSRunningOrder): IMOSRunningOrder { + // Deep copy to avoid modifying the original + const result = JSON.parse(JSON.stringify(rundown)) + + // Convert string values to MOS types + if (typeof result.ID === 'string') { + result.ID = this.mosTypes.mosString128.create(result.ID) + } + + if (typeof result.Slug === 'string') { + result.Slug = this.mosTypes.mosString128.create(result.Slug) + } + + if (typeof result.DefaultChannel === 'string') { + result.DefaultChannel = this.mosTypes.mosString128.create(result.DefaultChannel) + } + + // Process each story + if (Array.isArray(result.Stories)) { + result.Stories.forEach((story: any) => { + if (typeof story.ID === 'string') { + story.ID = this.mosTypes.mosString128.create(story.ID) + } + + if (typeof story.Slug === 'string') { + story.Slug = this.mosTypes.mosString128.create(story.Slug) + } + + // Process items in each story + if (Array.isArray(story.Items)) { + story.Items.forEach((item: any) => { + if (typeof item.ID === 'string') { + item.ID = this.mosTypes.mosString128.create(item.ID) + } + + if (typeof item.Slug === 'string') { + item.Slug = this.mosTypes.mosString128.create(item.Slug) + } + + if (typeof item.ObjectID === 'string') { + item.ObjectID = this.mosTypes.mosString128.create(item.ObjectID) + } + }) + } + }) + } + + return result + } + + // Send a running order to all connected devices + private broadcastRunningOrder(rundown: IMOSRunningOrder, isUpdate = false): void { + if (this.devicesMap.size === 0) { + console.log('No connected devices to broadcast to') + return + } + + for (const [id, device] of this.devicesMap) { + try { + if (isUpdate) { + console.log( + `Sending updated running order to device ${id}: ${this.mosTypes.mosString128.stringify( + rundown.ID + )}` + ) + device + .sendReplaceRunningOrder(rundown) + .catch((err) => console.error(`Error sending updated rundown to device ${id}:`, err)) + } else { + console.log( + `Sending new running order to device ${id}: ${this.mosTypes.mosString128.stringify(rundown.ID)}` + ) + device + .sendCreateRunningOrder(rundown) + .catch((err) => console.error(`Error sending new rundown to device ${id}:`, err)) + } + } catch (error) { + console.error(`Error broadcasting rundown to device ${id}:`, error) + } + } + } + + // Notify all connected devices about a deleted running order + private broadcastRunningOrderDelete(roId: string): void { + if (this.devicesMap.size === 0) { + console.log('No connected devices to broadcast to') + return + } + + const mosRoId = this.mosTypes.mosString128.create(roId) + + for (const [id, device] of this.devicesMap) { + try { + console.log(`Sending running order delete notification to device ${id}: ${roId}`) + device + .sendDeleteRunningOrder(mosRoId) + .catch((err) => console.error(`Error sending rundown delete to device ${id}:`, err)) + } catch (error) { + console.error(`Error broadcasting rundown delete to device ${id}:`, error) + } + } + } + + // Send all loaded rundowns to a newly connected device + private sendAllRunningOrders(device: MosDevice): void { + if (this.rundowns.size === 0) { + console.log('No rundowns to send to new device') + return + } + + console.log(`Sending ${this.rundowns.size} rundowns to device: ${device.idPrimary}`) + + for (const [id, rundown] of this.rundowns) { + try { + console.log(`Sending rundown to device ${device.idPrimary}: ${id}`) + device + .sendCreateRunningOrder(rundown) + .catch((err) => console.error(`Error sending rundown to device ${device.idPrimary}:`, err)) + } catch (error) { + console.error(`Error sending rundown to device ${device.idPrimary}:`, error) + } + } + } + + // Simulate a server outage + public simulateOutage(durationMs = 5000): void { + if (!this.isServerOnline) { + console.log('Server is already offline') + return + } + + console.log(`Simulating server outage for ${durationMs}ms`) + this.isServerOnline = false + + setTimeout(() => { + console.log('Recovering from simulated outage') + this.isServerOnline = true + }, durationMs) + } + + // Shutdown the server + public async shutdown(): Promise { + console.log('Shutting down MOS server...') + + try { + await this.mosConnection.dispose() + console.log('MOS server stopped') + } catch (error) { + console.error('Error shutting down MOS server:', error) + } + } +} + +// Main function +async function main() { + const rundownsDir = path.join(process.cwd(), 'rundowns') + + if (!fs.existsSync(rundownsDir) || fs.readdirSync(rundownsDir).filter((f) => f.endsWith('.json')).length === 0) { + console.log(`No rundowns found in ${rundownsDir}. Creating example rundown...`) + } + + // Is the server secondary: + if (process.argv.includes('--secondary')) { + SERVER_CONFIG.mosID = 'DUMMY.MOS.SERVER.SECONDARY' + SERVER_CONFIG.ports.lower = 10640 + SERVER_CONFIG.ports.upper = 10641 + SERVER_CONFIG.ports.query = 10642 + } + + const server = new DummyMosServer(rundownsDir) + // Create and start the MOS server + await server.start() + + // Handle process termination + process.on('SIGINT', async () => { + console.log('Received SIGINT signal') + await server.shutdown() + process.exit(0) + }) + + process.on('SIGTERM', async () => { + console.log('Received SIGTERM signal') + await server.shutdown() + process.exit(0) + }) + + // Command line interface for manual testing + console.log('\nCommands:') + console.log(' outage [duration_ms] - Simulate server outage') + console.log(' exit - Shutdown the server and exit') + + // Simple command processing + process.stdin.on('data', async (data) => { + const input = data.toString().trim() + const args = input.split(' ') + const command = args[0].toLowerCase() + + switch (command) { + case 'outage': + const duration = parseInt(args[1]) || 5000 + server.simulateOutage(duration) + break + + case 'exit': + await server.shutdown() + process.exit(0) + break + + default: + console.log('Unknown command:', command) + break + } + }) +} + +// Run the application +main().catch((error) => { + console.error('Fatal error:', error) + process.exit(1) +}) diff --git a/packages/mos-dummy-device/tsconfig.json b/packages/mos-dummy-device/tsconfig.json new file mode 100644 index 00000000..f1b35155 --- /dev/null +++ b/packages/mos-dummy-device/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "lib": ["es2020"], + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "types": ["node"], + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "**/*.spec.ts"] + } \ No newline at end of file diff --git a/repomix-output.txt b/repomix-output.txt new file mode 100644 index 00000000..f3097fdc --- /dev/null +++ b/repomix-output.txt @@ -0,0 +1,7165 @@ +This file is a merged representation of the entire codebase, combined into a single document by Repomix. +The content has been processed where content has been compressed (code blocks are separated by ⋮---- delimiter). + +================================================================ +File Summary +================================================================ + +Purpose: +-------- +This file contains a packed representation of the entire repository's contents. +It is designed to be easily consumable by AI systems for analysis, code review, +or other automated processes. + +File Format: +------------ +The content is organized as follows: +1. This summary section +2. Repository information +3. Directory structure +4. Multiple file entries, each consisting of: + a. A separator line (================) + b. The file path (File: path/to/file) + c. Another separator line + d. The full contents of the file + e. A blank line + +Usage Guidelines: +----------------- +- This file should be treated as read-only. Any changes should be made to the + original repository files, not this packed version. +- When processing this file, use the file path to distinguish + between different files in the repository. +- Be aware that this file may contain sensitive information. Handle it with + the same level of security as you would the original repository. + +Notes: +------ +- Some files may have been excluded based on .gitignore rules and Repomix's configuration +- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files +- Files matching patterns in .gitignore are excluded +- Files matching default ignore patterns are excluded +- Content has been compressed - code blocks are separated by ⋮---- delimiter +- Files are sorted by Git change count (files with more changes are at the bottom) + +Additional Info: +---------------- + +================================================================ +Directory Structure +================================================================ +.github/ + ISSUE_TEMPLATE/ + bug_report.yml + config.yml + other.yml + question.yml + rfc.yml + workflows/ + audit.yaml + node.yaml + publish-prerelease.yaml + sonar.yaml + dependabot.yml + PULL_REQUEST_TEMPLATE.md +.husky/ + pre-commit +packages/ + connector/ + src/ + config/ + connectionConfig.ts + connection/ + mosMessageParser.ts + mosSocketClient.ts + mosSocketServer.ts + NCSServerConnection.ts + Server.ts + socketConnection.ts + api.ts + index.ts + lib.ts + MosConnection.ts + MosDevice.ts + .eslintrc.js + .prettierignore + jest.config.js + package.json + README.md + tsconfig.build.json + tsconfig.json + examples/ + src/ + client.ts + server.ts + .eslintrc.js + .prettierignore + jest.config.js + package.json + README.md + tsconfig.build.json + tsconfig.json + helper/ + src/ + mosModel/ + profile0/ + heartBeat.ts + index.ts + listMachInfo.ts + reqMachInfo.ts + xmlConversion.ts + profile1/ + index.ts + mosAck.ts + mosListAll.ts + mosObj.ts + reqMosObj.ts + reqMosObjAll.ts + xmlConversion.ts + profile2/ + index.ts + ROAck.ts + roActions.ts + roCreate.ts + roDelete.ts + roElementStat.ts + ROList.ts + ROListAll.ts + roMetadataReplace.ts + roReadyToAir.ts + roReplace.ts + roReq.ts + xmlConversion.ts + profile3/ + index.ts + mosItemReplace.ts + mosListSearchableSchema.ts + mosObjCreate.ts + mosObjList.ts + mosReqObjAction.ts + mosReqObjList.ts + mosReqSearchableSchema.ts + xmlConversion.ts + profile4/ + index.ts + roReqAll.ts + roStory.ts + xmlConversion.ts + index.ts + lib.ts + MosMessage.ts + ParseError.ts + parseMosTypes.ts + stringify/ + stringifyMosObject.ts + utils/ + ensureMethods.ts + Errors.ts + Utils.ts + index.ts + .eslintrc.js + .prettierignore + jest.config.js + package.json + README.md + tsconfig.build.json + tsconfig.json + model/ + src/ + mosTypes/ + lib.ts + mosDuration.ts + mosString128.ts + mosTime.ts + index.ts + model.ts + mosTypes.ts + xmlParse.ts + .eslintrc.js + .prettierignore + jest.config.js + package.json + README.md + tsconfig.build.json + tsconfig.json + mos-dummy-device/ + rundowns/ + defaultrundown.json + src/ + index.ts + package.json + README.md + tsconfig.json + quick-mos/ + input/ + runningorders/ + .gitignore + empty.ts + README.md + config.ts + util.ts + src/ + convertFromSofieSnapshot.ts + index.ts + mosDiff.ts + .eslintrc.js + jest.config.js + LICENSE + package.json + README.md + tsconfig.build.json + tsconfig.json +scripts/ + fix-version.js + reset.js + update-packages.mjs +.editorconfig +.eslintrc.js +.gitattributes +.gitignore +.prettierignore +.repomixignore +.yarnrc.yml +commonPackage.json +CONTRIBUTING.md +jest.config.base.js +lerna.json +LICENSE +package.json +README.md +sonar-project.properties +tsconfig.build.json +tsconfig.json + +================================================================ +Files +================================================================ + +================ +File: .github/ISSUE_TEMPLATE/bug_report.yml +================ +name: Bug Report 🐛 +description: Use this if you've found a bug +title: "Bug Report: [Short description of the bug]" +labels: + - 🐛bug + +body: + - type: markdown + attributes: + value: | + Before you post, be sure to read our Contribution guidelines: + https://nrkno.github.io/sofie-core/docs/for-developers/contribution-guidelines + + - type: textarea + attributes: + label: About me + description: Tell us who / which organization you are representing, and how the Sofie team will be able to contact you. + placeholder: Example "This RFC is posted on behalf of the NRK." + validations: + required: true + + - type: textarea + attributes: + label: Observed Behavior + description: What happened? + validations: + required: true + + - type: textarea + attributes: + label: Expected Behavior + description: What did you expect to happen? + validations: + required: true + + - type: input + attributes: + label: Version + description: What version of Sofie Core / Gateways / other components are you using? + validations: + required: true + + - type: textarea + attributes: + label: Severity / Impact + description: How big of an issue is this? How does this limit your operations? + placeholder: | + Examples: + * This is a blocker for us, we cannot use feature X until this is fixed. + * Not a big issue for us, but it would be nice to have this fixed. + +================ +File: .github/ISSUE_TEMPLATE/config.yml +================ +blank_issues_enabled: false + +================ +File: .github/ISSUE_TEMPLATE/other.yml +================ +name: Other Issue 🔧 +description: Use this for other issues +title: "Other: [Short description of the issue]" +labels: + - Other + +body: + - type: markdown + attributes: + value: | + Before you post, be sure to read our Contribution guidelines: + https://nrkno.github.io/sofie-core/docs/for-developers/contribution-guidelines + + - type: textarea + attributes: + label: About me + description: Tell us who / which organization you are representing, and how the Sofie team will be able to contact you. + placeholder: Example "This RFC is posted on behalf of the NRK." + validations: + required: true + + - type: textarea + attributes: + label: Issue + description: Describe the issue below + validations: + required: true + +================ +File: .github/ISSUE_TEMPLATE/question.yml +================ +name: Question ❓ +description: Use this if you have a question to the Sofie team +title: "Question: [Short summary of the question]" +labels: + - ❓ Question + +body: + - type: markdown + attributes: + value: | + Before you post, be sure to read our Contribution guidelines: + https://nrkno.github.io/sofie-core/docs/for-developers/contribution-guidelines + + - type: textarea + attributes: + label: About me + description: Tell us who / which organization you are representing, and how the Sofie team will be able to contact you. + placeholder: Example "This RFC is posted on behalf of the NRK." + validations: + required: true + + - type: textarea + attributes: + label: My Question + description: Write your question below + validations: + required: true + +================ +File: .github/ISSUE_TEMPLATE/rfc.yml +================ +name: Request for Comments ❗ +description: Use this to initiate a discussion about a new feature or a larger change +title: "RFC: [Short description of the feature/change]" +labels: + - RFC + - Contribution + +body: + - type: markdown + attributes: + value: | + Before you post, be sure to read our Contribution guidelines: + https://nrkno.github.io/sofie-core/docs/for-developers/contribution-guidelines + + - type: textarea + attributes: + label: About me + description: Tell us who / which organization you are representing, and how the Sofie team will be able to contact you. + placeholder: Example "This RFC is posted on behalf of the NRK." + validations: + required: true + + - type: textarea + attributes: + label: Use case + description: "Please write some background information here, such as: What is your use case? What problem are you trying to solve?" + validations: + required: true + + - type: textarea + attributes: + label: Proposal + description: Please describe your proposal here + validations: + required: true + + - type: textarea + attributes: + label: Process + description: Please don't touch this section, the Sofie team will update this as the RFC progresses. + value: | + The Sofie Team will evaluate this RFC and open up a discussion about it, usually within a week. + - [x] RFC created + - [ ] Sofie Team has evaluated the RFC + - [ ] A workshop has been planned + - [ ] RFC has been discussed in a workshop + - [ ] A conclusion has been reached, see comments in thread + +================ +File: .github/dependabot.yml +================ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + reviewers: + - "@nrkno/sofie-ops" + +================ +File: .github/PULL_REQUEST_TEMPLATE.md +================ + + +## About the Contributor + + + +## Type of Contribution + +This is a: + +Bug fix / Feature / Code improvement / Documentation improvement / Other (please specify) + + +## Current Behavior + + + +## New Behavior + + + +## Testing Instructions + + + +## Other Information + + + +## Status + + +- [ ] PR is ready to be reviewed. +- [ ] The functionality has been tested by the author. +- [ ] Relevant unit tests has been added / updated. +- [ ] Relevant documentation (code comments, [system documentation](https://nrkno.github.io/sofie-core/)) has been added / updated. + +================ +File: .husky/pre-commit +================ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +node scripts/update-packages.mjs + +yarn run lint-staged + +================ +File: packages/connector/src/config/connectionConfig.ts +================ +import { IProfiles } from '@mos-connection/model' +import { IConnectionConfig, IMOSDeviceConnectionOptions } from '../api' +⋮---- +/** */ +⋮---- +export class ConnectionConfig implements IConnectionConfig +⋮---- +// options for on-the-fly-created connections +⋮---- +constructor(init: IConnectionConfig) +⋮---- +/* tslint:disable */ +⋮---- +/* tslint:enable */ +⋮---- +/** */ +get profiles(): IProfiles +⋮---- +/** */ +set profiles(profileSupport: IProfiles) +⋮---- +// profile 0 is mandatory +⋮---- +// Profile 1 depends on 0 +⋮---- +// Profile 2 depends on 0 and 1 +⋮---- +// Profile 3 depends on 0, 1 and 2 +⋮---- +// Profile 4 depends on 0, 1 and 2 +⋮---- +// Profile 5 depends on 0, 1 and 2 +⋮---- +// Profile 6 depends on 0, 1 and 2 +⋮---- +// Profile 7 depends on 0, 1 and 2 +⋮---- +// must support at least one other profile, other than Profile 0 +⋮---- +// machineInfo: { +// manufacturer: "SuperFly.tv", +// model: "YAAS" +// //hwRev: , +// swRev: '0.0.1.0' +// DOM: '', // date of manufacture +// /*927748927 +// airchache.newscenter.com +// +// 2009-03-01T23:55:10 +// 2.8.2 +// */ +// } + +================ +File: packages/connector/src/connection/Server.ts +================ +import { Socket } from 'net' +import { ConnectionType, SocketDescription } from './socketConnection' +⋮---- +/** */ +export class Server +⋮---- +// private _connected: boolean +// private _lastSeen: number +⋮---- +/** */ +registerIncomingConnection(socketID: number, socket: Socket, portDescription: ConnectionType): void +⋮---- +/** */ +removeSocket(socketID: number): void +⋮---- +private _getSockets(portDescription: string): Socket[] +/** */ +get lowerPortSockets(): Socket[] +⋮---- +/** */ +get upperPortSockets(): Socket[] +⋮---- +/** */ +get queryPortSockets(): Socket[] + +================ +File: packages/connector/src/connection/socketConnection.ts +================ +import { Socket } from 'net' +⋮---- +/** */ +export enum SocketConnectionEvent { + CONNECTED = 'eventsocketconnectionconnected', + DISCONNECTED = 'eventsocketconnectiondisconnected', + DISPOSED = 'eventsocketconnectiondisposed', + TIMEOUT = 'eventsocketconnectiontimeout', + ALIVE = 'eventsocketconnectionalive', + REGISTER = 'eventsocketconnectionregister', + UNREGISTER = 'eventsocketconnectionunregister', +} +⋮---- +/** */ +export enum SocketServerEvent { + // LISTENING = 'eventsocketserverlistening', + // DISPOSED = 'eventsocketserverdisposed', + // ALIVE = 'eventsocketserveralive', + CLIENT_CONNECTED = 'eventsocketserverclientconnected', + ERROR = 'eventsocketserverclienterror', + CLOSE = 'eventsocketserverclientclose', +} +⋮---- +// LISTENING = 'eventsocketserverlistening', +// DISPOSED = 'eventsocketserverdisposed', +// ALIVE = 'eventsocketserveralive', +⋮---- +/** */ +export type ConnectionType = IncomingConnectionType | OutgoingConnectionType +export type IncomingConnectionType = 'lower' | 'upper' | 'query' +export type OutgoingConnectionType = 'lower' | 'upper' +⋮---- +/** */ +export type SocketDescription = { + socket: Socket + portDescription: ConnectionType +} + +================ +File: packages/connector/src/lib.ts +================ +/** Return true if the object has a property */ +export function has(obj: unknown, property: string): boolean +export function safeStringify(obj: unknown): string +⋮---- +export function isTestingWithJest(): boolean +⋮---- +// If running in Jest, wait a bit longer before checking the profile validity: + +================ +File: packages/connector/.eslintrc.js +================ + + +================ +File: packages/connector/.prettierignore +================ +package.json + +================ +File: packages/connector/jest.config.js +================ + + +================ +File: packages/connector/README.md +================ +# @mos-connection/connector + +This is the **connector** of the [MOS-connection](https://github.com/nrkno/sofie-mos-connection). + +It is a Node.js library used to connect to MOS services. + +Read the full documentation at the main repo [MOS-connection](https://github.com/nrkno/sofie-mos-connection). + +================ +File: packages/connector/tsconfig.build.json +================ +{ + "extends": "../../tsconfig.build.json", + "include": [ + "src/**/*.ts" + ], + "compilerOptions": { + "outDir": "./dist", + } +} + +================ +File: packages/connector/tsconfig.json +================ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*.ts" + ] +} + +================ +File: packages/examples/src/client.ts +================ +import { + MosConnection, + ConnectionConfig, + IMOSROAck, + IMOSROAction, + IMOSROReadyToAir, + IMOSROStory, + IMOSRunningOrder, + IMOSStoryAction, + MosDevice, + IMOSString128, +} from '@mos-connection/connector' +⋮---- +// mos.on('rawMessage', (_source, _type, _message) => { +// console.log('rawMessage', _source, _type, _message) +// }) +⋮---- +// console.log(dev) +⋮---- +const mosTypes = mosDevice.mosTypes // Could also be retrieved with getMosTypes(strict) +⋮---- +const sendInit = async () => +⋮---- +// trigger a re-send of those running orders: +// return dev.getRunningOrder(mosTypes.mosString128.create('696297DF-1568-4B36-B43B3B79514B40D4')) +⋮---- +// opTime?: mosTypes.mosTime.create(), +⋮---- +// To connect to a server on custom ports: +// ports: { +// lower: 11540, +// upper: 11541, +// query: 11542, +// }, + +================ +File: packages/examples/src/server.ts +================ +import { MosConnection, ConnectionConfig } from '@mos-connection/connector' +⋮---- +// To start the server on custom ports: +// ports: { +// lower: 11540, +// upper: 11541, +// query: 11542, +// }, +⋮---- +const mosTypes = mosDevice.mosTypes // Could also be retrieved with getMosTypes(strict) +⋮---- +// opTime?: mosTypes.mosTime.create(), + +================ +File: packages/examples/.eslintrc.js +================ + + +================ +File: packages/examples/.prettierignore +================ +package.json + +================ +File: packages/examples/jest.config.js +================ + + +================ +File: packages/examples/README.md +================ +# @mos-connection/connector + +This is the **connector** of the [MOS-connection](https://github.com/nrkno/sofie-mos-connection). + +It is a Node.js library used to connect to MOS services. + +Read the full documentation at the main repo [MOS-connection](https://github.com/nrkno/sofie-mos-connection). + +================ +File: packages/examples/tsconfig.build.json +================ +{ + "extends": "../../tsconfig.build.json", + "include": [ + "src/**/*.ts" + ], + "compilerOptions": { + "outDir": "./dist", + } +} + +================ +File: packages/examples/tsconfig.json +================ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*.ts" + ] +} + +================ +File: packages/helper/src/mosModel/profile0/index.ts +================ + + +================ +File: packages/helper/src/mosModel/profile0/listMachInfo.ts +================ +import { IMOSListMachInfo } from '@mos-connection/model' +import { MosMessage } from '../MosMessage' +⋮---- +import { XMLMosListMachInfo } from './xmlConversion' +⋮---- +export class ListMachineInfo extends MosMessage +⋮---- +/** */ +constructor(info: IMOSListMachInfo, port: 'upper' | 'lower', strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile0/reqMachInfo.ts +================ +import { MosMessage } from '../MosMessage' +⋮---- +export class ReqMachInfo extends MosMessage +⋮---- +/** */ +constructor(strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile1/index.ts +================ + + +================ +File: packages/helper/src/mosModel/profile1/mosAck.ts +================ +import { MosMessage } from '../MosMessage' +import { IMOSAck, IMOSAckStatus, IMOSString128 } from '@mos-connection/model' +import { addTextElementInternal } from '../../utils/Utils' +⋮---- +export class MOSAck extends MosMessage implements IMOSAck +⋮---- +Revision: number // max 999 +⋮---- +/** */ +constructor(ack: IMOSAck, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile1/mosListAll.ts +================ +import { MosMessage } from '../MosMessage' +⋮---- +import { IMOSObject } from '@mos-connection/model' +import { XMLMosObject } from '../profile1/xmlConversion' +⋮---- +export class MosListAll extends MosMessage +⋮---- +/** */ +constructor(objs: Array, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile1/mosObj.ts +================ +import { MosMessage } from '../MosMessage' +⋮---- +import { IMOSObject } from '@mos-connection/model' +import { XMLMosObject } from './xmlConversion' +⋮---- +export class MosObj extends MosMessage +⋮---- +/** */ +constructor(obj: IMOSObject, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile1/reqMosObj.ts +================ +import { MosMessage } from '../MosMessage' +import { IMOSString128 } from '@mos-connection/model' +import { addTextElementInternal } from '../../utils/Utils' +⋮---- +export class ReqMosObj extends MosMessage +⋮---- +/** */ +constructor(objId: IMOSString128, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile2/index.ts +================ + + +================ +File: packages/helper/src/mosModel/profile2/ROAck.ts +================ +import { MosMessage } from '../MosMessage' +import { IMOSROAck, IMOSROAckStory, IMOSString128 } from '@mos-connection/model' +import { addTextElementInternal } from '../../utils/Utils' +⋮---- +export class ROAck extends MosMessage implements IMOSROAck +⋮---- +/** */ +constructor(roAck: IMOSROAck, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement +⋮---- +// Loop over Stories, Items and Object + +================ +File: packages/helper/src/mosModel/profile2/roActions.ts +================ +import { MosMessage } from '../MosMessage' +import { + IMOSStoryAction, + IMOSROStory, + IMOSItemAction, + IMOSItem, + IMOSROAction, + IMOSString128, +} from '@mos-connection/model' +import { XMLROStory, XMLMosItem } from './xmlConversion' +import { addTextElementInternal } from '../../utils/Utils' +⋮---- +export abstract class MosSendMessage extends MosMessage +⋮---- +constructor(strict: boolean) +⋮---- +export class ROInsertStories extends MosSendMessage +⋮---- +constructor(private Action: IMOSStoryAction, private Stories: Array, strict: boolean) +get messageXMLBlocks(): XMLBuilder.XMLElement +⋮---- +export class ROInsertItems extends MosSendMessage +⋮---- +constructor(private Action: IMOSItemAction, private Items: Array, strict: boolean) +⋮---- +export class ROReplaceStories extends MosSendMessage +export class ROReplaceItems extends MosSendMessage +export class ROMoveStories extends MosSendMessage +⋮---- +constructor(private Action: IMOSStoryAction, private Stories: Array, strict: boolean) +⋮---- +export class ROMoveItems extends MosSendMessage +⋮---- +constructor(private Action: IMOSItemAction, private Items: Array, strict: boolean) +⋮---- +export class RODeleteStories extends MosSendMessage +⋮---- +constructor(private Action: IMOSROAction, private Stories: Array, strict: boolean) +⋮---- +export class RODeleteItems extends MosSendMessage +⋮---- +constructor(private Action: IMOSStoryAction, private Items: Array, strict: boolean) +⋮---- +export class ROSwapStories extends MosSendMessage +⋮---- +constructor( + private Action: IMOSROAction, + private StoryID0: IMOSString128, + private StoryID1: IMOSString128, + strict: boolean +) +⋮---- +export class ROSwapItems extends MosSendMessage +⋮---- +constructor( + private Action: IMOSStoryAction, + private ItemID0: IMOSString128, + private ItemID1: IMOSString128, + strict: boolean +) + +================ +File: packages/helper/src/mosModel/profile2/roCreate.ts +================ +import { MosMessage } from '../MosMessage' +import { IMOSRunningOrder } from '@mos-connection/model' +import { XMLROStory, XMLRunningOrderBase } from './xmlConversion' +⋮---- +export class ROCreate extends MosMessage +⋮---- +/** */ +constructor(private ro: IMOSRunningOrder, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement +protected fillXMLWithROData(root: XMLBuilder.XMLElement): void + +================ +File: packages/helper/src/mosModel/profile2/roDelete.ts +================ +import { MosMessage } from '../MosMessage' +import { IMOSString128 } from '@mos-connection/model' +import { addTextElementInternal } from '../../utils/Utils' +⋮---- +export class RODelete extends MosMessage +⋮---- +constructor(private roId: IMOSString128, strict: boolean) +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile2/ROList.ts +================ +import { MosMessage } from '../MosMessage' +import { IMOSRunningOrder } from '@mos-connection/model' +import { XMLROStory, XMLRunningOrderBase } from './xmlConversion' +⋮---- +export class ROList extends MosMessage +⋮---- +/** */ +constructor(private ro: IMOSRunningOrder, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement +⋮---- +protected fillXMLWithROData(root: XMLBuilder.XMLElement): void + +================ +File: packages/helper/src/mosModel/profile2/roMetadataReplace.ts +================ +import { IMOSRunningOrderBase } from '@mos-connection/model' +import { MosMessage } from '../MosMessage' +import { XMLRunningOrderBase } from './xmlConversion' +⋮---- +export class ROMetadataReplace extends MosMessage +⋮---- +constructor(private metadata: IMOSRunningOrderBase, strict: boolean) +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile2/roReadyToAir.ts +================ +import { MosMessage } from '../MosMessage' +import { IMOSString128 } from '@mos-connection/model' +import { addTextElementInternal } from '../../utils/Utils' +⋮---- +// https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReadyToAir +⋮---- +export interface ROReadyToAirOptions { + roId: IMOSString128 + roAir: 'READY' | 'NOT READY' +} +export class ROReadyToAir extends MosMessage +⋮---- +constructor(options: ROReadyToAirOptions, strict: boolean) +⋮---- +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile2/roReplace.ts +================ +import { IMOSRunningOrder } from '@mos-connection/model' +import { ROCreate } from './roCreate' +⋮---- +export class ROReplace extends ROCreate +⋮---- +constructor(ro: IMOSRunningOrder, strict: boolean) +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile2/roReq.ts +================ +import { MosMessage } from '../MosMessage' +import { IMOSString128 } from '@mos-connection/model' +import { addTextElementInternal } from '../../utils/Utils' +⋮---- +export class ROReq extends MosMessage +⋮---- +/** */ +constructor(roId: IMOSString128, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile3/mosItemReplace.ts +================ +import { MosItemReplaceOptions } from '@mos-connection/model' +import { MosMessage } from '../MosMessage' +⋮---- +import { XMLMosItem } from '../profile2/xmlConversion' +import { addTextElementInternal } from '../../utils/Utils' +⋮---- +export class MosItemReplace extends MosMessage +⋮---- +constructor(options: MosItemReplaceOptions, strict: boolean) +⋮---- +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile3/mosObjCreate.ts +================ +import { IMOSObject } from '@mos-connection/model' +import { MosMessage } from '../MosMessage' +⋮---- +import { XMLMosObject } from '../profile1/xmlConversion' +⋮---- +// https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjCreate +export class MosObjCreate extends MosMessage +⋮---- +constructor(object: IMOSObject, strict: boolean) +⋮---- +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile3/mosObjList.ts +================ +import { IMOSObjectList } from '@mos-connection/model' +import { MosMessage } from '../MosMessage' +⋮---- +import { XMLMosObjects } from '../profile1/xmlConversion' +import { addTextElementInternal } from '../../utils/Utils' +⋮---- +export class MosObjList extends MosMessage +⋮---- +constructor(options: IMOSObjectList, strict: boolean) +⋮---- +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile3/mosReqObjAction.ts +================ +import { IMOSObject, IMOSString128 } from '@mos-connection/model' +import { MosMessage } from '../MosMessage' +⋮---- +import { XMLMosObject } from '../profile1/xmlConversion' +export interface MosReqObjActionOptionsNew { + object: IMOSObject +} +export class MosReqObjActionNew extends MosMessage +⋮---- +constructor(options: MosReqObjActionOptionsNew, strict: boolean) +get messageXMLBlocks(): XMLBuilder.XMLElement +⋮---- +export interface MosReqObjActionOptionsUpdate { + object: IMOSObject + objectId: IMOSString128 +} +export class MosReqObjActionUpdate extends MosMessage +⋮---- +constructor(options: MosReqObjActionOptionsUpdate, strict: boolean) +⋮---- +export interface MosReqObjActionOptionsDelete { + objectId: IMOSString128 +} +export class MosReqObjActionDelete extends MosMessage +⋮---- +constructor(options: MosReqObjActionOptionsDelete, strict: boolean) + +================ +File: packages/helper/src/mosModel/profile3/mosReqSearchableSchema.ts +================ +import { MosMessage } from '../MosMessage' +⋮---- +export class MosReqSearchableSchema extends MosMessage +⋮---- +constructor(options: +⋮---- +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile4/index.ts +================ + + +================ +File: packages/helper/src/mosModel/profile4/roReqAll.ts +================ +import { MosMessage } from '../MosMessage' +⋮---- +export class ROReqAll extends MosMessage +⋮---- +/** */ +constructor(strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/.eslintrc.js +================ + + +================ +File: packages/helper/.prettierignore +================ +package.json + +================ +File: packages/helper/jest.config.js +================ + + +================ +File: packages/helper/README.md +================ +# @mos-connection/helper + +This is the **helper** of the [MOS-connection](https://github.com/nrkno/sofie-mos-connection). + +It is a library that provides various functions useful for those that prepare/handle data that will be sent to (or has been received by) the [MOS-connection](https://github.com/nrkno/sofie-mos-connection). + +Read the full documentation at the main repo [MOS-connection](https://github.com/nrkno/sofie-mos-connection). + +================ +File: packages/helper/tsconfig.build.json +================ +{ + "extends": "../../tsconfig.build.json", + "include": [ + "src/**/*.ts" + ], + "compilerOptions": { + "outDir": "./dist", + } +} + +================ +File: packages/helper/tsconfig.json +================ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*.ts" + ] +} + +================ +File: packages/model/.eslintrc.js +================ + + +================ +File: packages/model/.prettierignore +================ +package.json + +================ +File: packages/model/jest.config.js +================ + + +================ +File: packages/model/README.md +================ +# @mos-connection/model + +This is the **model** of the [MOS-connection](https://github.com/nrkno/sofie-mos-connection). + +It is a library that contains types and enums, useful for applications that handles MOS-data. + +Read the full documentation at the main repo [MOS-connection](https://github.com/nrkno/sofie-mos-connection). + +================ +File: packages/model/tsconfig.build.json +================ +{ + "extends": "../../tsconfig.build.json", + "include": [ + "src/**/*.ts" + ], + "compilerOptions": { + "outDir": "./dist", + } +} + +================ +File: packages/model/tsconfig.json +================ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*.ts" + ] +} + +================ +File: packages/mos-dummy-device/rundowns/defaultrundown.json +================ +{ + "ID": "EXAMPLE_RO", + "Slug": "Example Rundown", + "DefaultChannel": "A", + "Stories": [ + { + "ID": "EXAMPLE_RO_STORY_1", + "Slug": "Opening Story", + "Number": "1", + "Items": [ + { + "ID": "EXAMPLE_RO_STORY_1_ITEM_1", + "Slug": "Intro Graphic", + "ObjectID": "OBJ_INTRO_GRAPHIC", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "Opening Title Graphic", + "Duration": 500, + "TimeBase": 100 + }, + { + "ID": "EXAMPLE_RO_STORY_1_ITEM_2", + "Slug": "Presenter Introduction", + "ObjectID": "OBJ_PRESENTER_CAM1", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "Camera 1 - Wide", + "Duration": 1000, + "TimeBase": 100 + } + ] + }, + { + "ID": "EXAMPLE_RO_STORY_2", + "Slug": "News Story", + "Number": "2", + "Items": [ + { + "ID": "EXAMPLE_RO_STORY_2_ITEM_1", + "Slug": "News Intro", + "ObjectID": "OBJ_NEWS_GRAPHIC", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "News Title Card", + "Duration": 300, + "TimeBase": 100 + }, + { + "ID": "EXAMPLE_RO_STORY_2_ITEM_2", + "Slug": "News Package", + "ObjectID": "OBJ_NEWS_PKG_1", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "News Package Video", + "Duration": 3000, + "TimeBase": 100 + } + ] + }, + { + "ID": "EXAMPLE_RO_STORY_3", + "Slug": "Closing Story", + "Number": "3", + "Items": [ + { + "ID": "EXAMPLE_RO_STORY_3_ITEM_1", + "Slug": "Closing Remarks", + "ObjectID": "OBJ_PRESENTER_CAM2", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "Camera 2 - Close Up", + "Duration": 1500, + "TimeBase": 100 + }, + { + "ID": "EXAMPLE_RO_STORY_3_ITEM_2", + "Slug": "Outro Graphic", + "ObjectID": "OBJ_OUTRO_GRAPHIC", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "Closing Credits", + "Duration": 800, + "TimeBase": 100 + } + ] + } + ] + } + +================ +File: packages/mos-dummy-device/src/index.ts +================ +/* eslint-disable node/no-unpublished-import */ +import { MosConnection, ConnectionConfig, getMosTypes, IMOSRunningOrder, MosDevice } from '@mos-connection/connector' +⋮---- +// Define the server configuration +⋮---- +'0': true, // Profile 0 is mandatory +'1': true, // Basic object exchange +'2': true, // Running order/playlist exchange +'3': true, // Advanced object-based workflow +'4': true, // Advanced rundown functionality +⋮---- +lower: 10540, // Default MOS ports, can be customized +⋮---- +// Enable relay mode to create new MOS devices on-the-fly when receiving messages +⋮---- +// Class for the dummy MOS server +class DummyMosServer +⋮---- +private mosTypes = getMosTypes(false) // Non-strict mode +⋮---- +constructor(rundownsDir: string) +⋮---- +// Create the MOS connection +⋮---- +// Set up device connection callback +⋮---- +// Watch the rundowns directory for changes +⋮---- +// Load any existing rundowns +⋮---- +// Initialize and start the server +public async start(): Promise +⋮---- +// Handle new MOS device connections +private handleNewConnection(mosDevice: MosDevice): void +⋮---- +// Store reference to the device +⋮---- +// Set up callbacks for Profile 0 +⋮---- +// Set up callbacks for Profile 2 +⋮---- +// Set up Profile 4 callbacks +⋮---- +// Send all rundowns to the device on connection +⋮---- +// Load all rundowns from the rundowns directory +private loadRundowns(): void +⋮---- +// Ensure ID is set correctly +⋮---- +// Process and validate rundown (convert strings to MOS types) +⋮---- +// Add to rundowns map +⋮---- +// Watch the rundowns directory for changes +private setupRundownWatcher(): void +⋮---- +// Handle new rundown file +private handleRundownFileAdded(filePath: string): void +⋮---- +// Ensure ID is set correctly +⋮---- +// Process and validate rundown +⋮---- +// Add to rundowns map +⋮---- +// Send to all connected devices +⋮---- +// Handle rundown file changes +private handleRundownFileChanged(filePath: string): void +⋮---- +// Process and validate rundown +⋮---- +// Update in rundowns map +⋮---- +// Send to all connected devices +⋮---- +// Handle rundown file removals +private handleRundownFileRemoved(filePath: string): void +⋮---- +// Remove from rundowns map +⋮---- +// Notify all connected devices +⋮---- +// Process a rundown to ensure all required MOS types are properly formatted +private processRundown(rundown: IMOSRunningOrder): IMOSRunningOrder +⋮---- +// Deep copy to avoid modifying the original +⋮---- +// Convert string values to MOS types +⋮---- +// Process each story +⋮---- +// Process items in each story +⋮---- +// Send a running order to all connected devices +private broadcastRunningOrder(rundown: IMOSRunningOrder, isUpdate = false): void +⋮---- +// Notify all connected devices about a deleted running order +private broadcastRunningOrderDelete(roId: string): void +⋮---- +// Send all loaded rundowns to a newly connected device +private sendAllRunningOrders(device: MosDevice): void +⋮---- +// Simulate a server outage +public simulateOutage(durationMs: number = 5000): void +⋮---- +// Shutdown the server +public async shutdown(): Promise +⋮---- +// Main function +async function main() +⋮---- +// Create and start the MOS server +⋮---- +// Handle process termination +⋮---- +// Command line interface for manual testing +⋮---- +// Simple command processing +⋮---- +// Run the application + +================ +File: packages/mos-dummy-device/package.json +================ +{ + "name": "mos-dummy-server", + "version": "1.0.0", + "description": "A dummy MOS server for testing MOS clients with failover capabilities", + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "ts-node src/index.ts", + "watch": "nodemon --watch src -e ts --exec ts-node src/index.ts" + }, + "keywords": [ + "mos", + "broadcast", + "testing", + "dummy", + "failover" + ], + "author": "", + "license": "MIT", + "dependencies": { + "@mos-connection/connector": "^4.2.2", + "chokidar": "^3.5.3" + }, + "devDependencies": { + "@types/node": "^20.10.5", + "nodemon": "^3.0.2", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + } +} + +================ +File: packages/mos-dummy-device/README.md +================ +# MOS Dummy Server + +A dummy MOS (Media Object Server) server for testing MOS clients with failover capabilities. This server implements the MOS protocol and allows you to simulate connection issues and manage rundowns through JSON files. + +## Features + +- Full MOS protocol support (Profiles 0, 1, 2, 3, and 4) +- JSON-based rundown management +- Hot reloading of rundowns from the filesystem +- Simulation of server outages for failover testing +- Command-line interface for manual testing + +## Installation + +1. Clone this repository +2. Install dependencies: + +```bash +npm install +``` + +3. Build the project: + +```bash +npm run build +``` + +## Usage + +### Starting the server + +```bash +npm start +``` + +This will start the MOS server on the default ports (10540, 10541, 10542). + +### Development mode + +To run in development mode with automatic reloading: + +```bash +npm run dev +``` + +Or with file watching: + +```bash +npm run watch +``` + +### Rundown Management + +Rundowns are stored as JSON files in the `rundowns` directory. The server watches this directory for changes: + +- Adding a new `.json` file creates a new rundown +- Modifying a file updates the rundown +- Deleting a file removes the rundown + +### Rundown JSON Format + +Here's an example of a rundown JSON file: + +```json +{ + "ID": "EXAMPLE_RO", + "Slug": "Example Rundown", + "DefaultChannel": "A", + "Stories": [ + { + "ID": "EXAMPLE_RO_STORY_1", + "Slug": "Story 1", + "Number": "1", + "Items": [ + { + "ID": "EXAMPLE_RO_STORY_1_ITEM_1", + "Slug": "Item 1 in Story 1", + "ObjectID": "OBJ_EXAMPLE_RO_STORY_1_ITEM_1", + "MOSID": "DUMMY.MOS.SERVER", + "ObjectSlug": "Item 1 in Story 1", + "Duration": 1000, + "TimeBase": 100 + } + ] + } + ] +} +``` + +### Command-Line Interface + +While the server is running, you can use the following commands: + +- `outage [duration_ms]` - Simulate a server outage for the specified duration (default: 5000ms) +- `create [id] [title] [story_count]` - Create an example rundown +- `exit` - Shutdown the server and exit + +Example: `outage 10000` will simulate a 10-second outage. + +## Configuration + +You can modify the configuration in the `SERVER_CONFIG` object in `src/index.ts`: + +- `mosID`: The MOS ID of the server +- `acceptsConnections`: Whether to accept incoming connections +- `profiles`: The MOS profiles to support +- `debug`: Enable/disable debug logging +- `ports`: The ports to use for MOS communication + +## Testing Failover + +To test failover with your MOS client: + +1. Start the server +2. Connect your MOS client to the server +3. Use the `outage` command to simulate an outage +4. Observe how your client handles the disconnection and reconnection + +================ +File: packages/mos-dummy-device/tsconfig.json +================ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "lib": ["es2020"], + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "**/*.spec.ts"] + } + +================ +File: packages/quick-mos/input/runningorders/.gitignore +================ +*.ts +*.json + +!empty.ts + +================ +File: packages/quick-mos/input/runningorders/empty.ts +================ +import { IMOSScope } from '@mos-connection/model' +⋮---- +// DefaultChannel?: '' +// EditorialStart?: MosTime; +// EditorialDuration?: MosDuration; +// Trigger?: '' +// MacroIn?: '' +// MacroOut?: '' + +================ +File: packages/quick-mos/src/mosDiff.ts +================ +export interface ListEntry { + /** ID that uniquely identifies this entry */ + id: string + /** Hash or timestamp, that changes whenever the content has changed */ + changedHash: string + content: T +} +⋮---- +/** ID that uniquely identifies this entry */ +⋮---- +/** Hash or timestamp, that changes whenever the content has changed */ +⋮---- +export type Operation = OperationInsert | OperationUpdate | OperationRemove | OperationMove +export interface OperationBase { + type: OperationType +} +export enum OperationType { + INSERT = 'insert', + UPDATE = 'update', + REMOVE = 'remove', + MOVE = 'move', +} +export interface OperationInsert { + type: OperationType.INSERT + beforeId: string + inserts: { + id: string + changedHash: string + content: T + }[] +} +export interface OperationUpdate { + type: OperationType.UPDATE + id: string + changedHash: string + content: T +} +export interface OperationRemove { + type: OperationType.REMOVE + ids: string[] +} +export interface OperationMove { + type: OperationType.MOVE + ids: string[] + beforeId: string +} +/** Takes an old and a new list, and returns the operations needed to synk the two */ +export function diffLists(oldList: ListEntry[], newList: ListEntry[]): Operation[] +⋮---- +// Preparations: +⋮---- +// Find Inserted and Updated: +⋮---- +// Find removed: +⋮---- +// Apply Inserts, updates & removes on intermediary list: +/** Intermediate list. Start with the old order and gradually ends up in the ending order, + * as move operations are determined + */ +⋮---- +// Find Moved: +⋮---- +const getInterPrevId = (id: string): string => +⋮---- +// const prevEntry: ListEntry | undefined = newList[i - 1] +⋮---- +// const nextEntry: ListEntry | undefined = newList[i + 1] +⋮---- +// The new entry is not following after the same element as the one that was before the beginning of the move operation +⋮---- +// The new entry is following the element that was before the move operation started. +⋮---- +// Optimize: when the tail of the move-operation is in the original order, it can be replaced by another smaller move operation: +⋮---- +// The previous id is in the original order, continue +⋮---- +// Yes, we can replace a number of ids with another move operation. +⋮---- +currentMoveOperation.ids.length = i // remove entries after i +⋮---- +// Commit the move operation: +// currentMoveOperation.beforeId = nextId +⋮---- +// The new entry is not following the same entry as the old does +⋮---- +// No change +⋮---- +// todo? Optimize: when the tail of the operation will be on the end anyway: +⋮---- +export function applyOperations(oldList: ListEntry[], operations: Operation[]): ListEntry[] +⋮---- +let newList: ListEntry[] = oldList.slice() // clone +⋮---- +// Apply Inserts, updates & removes on intermediary list: +⋮---- +// insert last +⋮---- +// move last: +⋮---- +/** Find and extract entries from list, and return them */ +function extractList(list: T[], fcn: (value: T) => number): T[] +function makeDenseArray(sparse: Array): T[] + +================ +File: packages/quick-mos/.eslintrc.js +================ + + +================ +File: packages/quick-mos/jest.config.js +================ + + +================ +File: packages/quick-mos/LICENSE +================ +MIT License + +Copyright (c) 2020 Johan Nyman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================ +File: packages/quick-mos/README.md +================ +# Quick-MOS + +An application for quick simulation of a MOS server/NCS. + +## Usage + +- `yarn start` to start the application + +- The application will monitor the contents in the folder `/input` and send mos commands. +- Files and folders that begin with "\_" (underscore) will be ignored + +- Note: quickmos and mos-gateway must be run on different machines (or docker containers) as they both try to bind to the same ports. This is a limitation in the current implementation of mos-connection + +================ +File: packages/quick-mos/tsconfig.build.json +================ +{ + "extends": "../../tsconfig.build.json", + "include": [ + "src/**/*.ts", + "input/**/*.ts" + ], + "compilerOptions": { + "outDir": "./dist", + } +} + +================ +File: packages/quick-mos/tsconfig.json +================ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*.ts", + "input/**/*.ts" + ] +} + +================ +File: scripts/fix-version.js +================ +/* eslint-disable no-console */ +⋮---- +// This script fixes a few things after a release version has been bumped +⋮---- +async function run() { +await removeFile('packages/examples/CHANGELOG.md') +await fixPackageVersion('packages/examples/package.json') +⋮---- +await removeFile('packages/quick-mos/CHANGELOG.md') +await fixPackageVersion('packages/quick-mos/package.json') +⋮---- +await commit() +⋮---- +async function fixPackageVersion(filePath) { +filePath = path.resolve(filePath) +const packageJsonStr = await fs.promises.readFile(filePath, 'utf8') +const packageJson = JSON.parse(packageJsonStr) +⋮---- +await fs.promises.writeFile(filePath, JSON.stringify(packageJson, null, '\t') + '\n', 'utf8') +⋮---- +async function removeFile(filePath) { +⋮---- +await fs.promises.unlink(filePath) +⋮---- +// File does not exist, do nothing +⋮---- +async function commit() { +const latestGitCommit = cp.execSync('git log -1') +⋮---- +// get last git commit message +const message = latestGitCommit.toString().split('\n')[4].trim() +// Check that the last commit message is a version bump +if (message.match(/v\d+\.\d+.\d+/)) { +⋮---- +console.log('Amending last commit with changes...') +exec('git add .') +exec('git commit --amend --no-edit --no-verify') +⋮---- +// Move tag: +exec(`git tag -d ${tag}`) +exec(`git tag ${tag}`) +⋮---- +console.log('NOT committing changes!') +⋮---- +function exec(cmd) { +console.log(cmd) +cp.execSync(cmd) +⋮---- +run() +.then(() => console.log('Done')) +.catch(console.error) + +================ +File: scripts/reset.js +================ +/* eslint-disable node/no-unpublished-require, node/no-extraneous-require */ +⋮---- +const glob = promisify(require('glob')) +const rimraf = promisify(require('rimraf')) +⋮---- +/* + Removing all /node_modules and /dist folders +*/ +⋮---- +const basePath = process.cwd() +⋮---- +log('Gathering files to remove...') +⋮---- +// Remove things that arent used, to reduce file size: +const searchForFolder = async (name) => { +⋮---- +...(await glob(`${basePath}/${name}`)), +...(await glob(`${basePath}/*/${name}`)), +...(await glob(`${basePath}/*/*/${name}`)), +...(await glob(`${basePath}/*/*/*/${name}`)), +...(await glob(`${basePath}/*/*/*/*/${name}`)), +⋮---- +...(await searchForFolder('node_modules')), +...(await searchForFolder('dist')), +...(await searchForFolder('deploy')), +⋮---- +if (folder.match(resultingFolder)) { +⋮---- +resultingFolders.push(folder) +⋮---- +const rootNodeModules = await glob(`${basePath}/node_modules`) +if (rootNodeModules.length !== 1) throw new Error(`Wrong length of root node_modules (${rootNodeModule.length})`) +⋮---- +log(`Removing ${resultingFolders.length} files...`) +⋮---- +log(`Removing ${folder}`) +await rimraf(folder) +⋮---- +// Finally, remove root node_modules +⋮---- +log(`...done!`) +})().catch(log) +⋮---- +function log(...args) { +// eslint-disable-next-line no-console +console.log(...args) + +================ +File: scripts/update-packages.mjs +================ +// /* eslint-disable node/no-unpublished-require */ +⋮---- +const glob = promisify(globOrg) +⋮---- +const rootPackageStr = await fs.readFile(path.join(path.dirname(fileURLToPath(import.meta.url)), '../package.json')) +const rootPackage = JSON.parse(rootPackageStr.toString()) +⋮---- +/* + This script copies some common properties (from commonPackage.json) + into package.json of each of the packages. + */ +⋮---- +const extendPackageStr = await fs.readFile('commonPackage.json') +const extendPackage = JSON.parse(extendPackageStr) +delete extendPackage.description // don't copy this propery +⋮---- +const packageJsons = await glob(`${workspaceDef}/package.json`) +⋮---- +if (!packageJsonPath.match(/node_modules/)) { +const packageJsonStr = await fs.readFile(packageJsonPath) +const packageJson = JSON.parse(packageJsonStr) +⋮---- +const newPackageJson = deepExtend({}, packageJson, extendPackage) +⋮---- +if (!_.isEqual(newPackageJson, packageJson)) { +await fs.writeFile(packageJsonPath, JSON.stringify(newPackageJson, undefined, '\t') + '\n') +⋮---- +console.error(`Error when processing ${packageJsonPath}`) +⋮---- +console.log(`Updated package.json of ${count} packages`) +console.log(`You should commit these changes and run yarn install again.`) +process.exit(1) + +================ +File: .editorconfig +================ +[*] +indent_style = tab + +[*.{cs,js,ts,json}] +indent_size = 4 + +[*.{yml,yaml}] +indent_size = 2 +indent_style = space + +================ +File: .eslintrc.js +================ + + +================ +File: .gitattributes +================ +* text=auto eol=lf + +================ +File: .gitignore +================ +node_modules +dist +test +src/**.js + +/**/coverage +/docs +.nyc_output +*.log + +wallaby.conf.js + +.DS_Store +docs/ +.vscode/settings.json + +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks + +================ +File: .prettierignore +================ +package.json + +================ +File: .repomixignore +================ +__tests__/ +__mocks__/ +dist/ +yarn.lock +CHANGELOG.md + +================ +File: .yarnrc.yml +================ +nodeLinker: node-modules + +plugins: + - path: .yarn/plugins/@yarnpkg/plugin-version.cjs + spec: "@yarnpkg/plugin-version" + - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs + spec: "@yarnpkg/plugin-interactive-tools" + - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs + spec: "@yarnpkg/plugin-workspace-tools" + +yarnPath: .yarn/releases/yarn-3.6.0.cjs + +================ +File: commonPackage.json +================ +{ + "description": "The properties of this file are copied to all packages' package.json files by running `yarn postinstall`", + "scripts": { + "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", + "validate:dev-dependencies": "yarn npm audit ", + "license-validate": "yarn sofie-licensecheck" + }, + "devDependencies": { + "@sofie-automation/code-standard-preset": "*", + "typescript": "~4.9.5" + }, + "engines": { + "node": ">=14.18.0" + }, + "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", + "lint-staged": { + "*.{css,json,md,scss}": [ + "run -T prettier" + ], + "*.{ts,tsx,js,jsx}": [ + "run -T eslint" + ] + }, + "files": [ + "/dist", + "/CHANGELOG.md", + "/README.md", + "/LICENSE" + ] +} + +================ +File: CONTRIBUTING.md +================ +# How to Contribute to this Repository + +Before contributing to this specific repository, please read the [Contribution Guidelines](https://nrkno.github.io/sofie-core/docs/for-developers/contribution-guidelines) for the Sofie project. + + +## Branches +This repository uses the following branches: + +* **_master_** is our main branch. We consider it stable and it is used in production. +* The **_develop_** branch is our in-development branch. When a release is ready, we decide to “freeze” that branch and merge it into **_master_** branch. + +We encourage you to base your contributions on the latest **_develop_** branch. The [_Sofie Releases_](https://nrkno.github.io/sofie-core/releases) page collects the status and timeline of the releases. + +================ +File: jest.config.base.js +================ +// globals: { +// 'ts-jest': { +// tsconfig: 'tsconfig.json', +// }, +// }, +⋮---- +// verbose: true, + +================ +File: LICENSE +================ +MIT License (MIT) + +Copyright (c) 2023 Norsk rikskringkasting AS (NRK) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================ +File: sonar-project.properties +================ +sonar.projectKey=nrkno_tv-automation-mos-connection +sonar.organization=nrkno + +# This is the name and version displayed in the SonarCloud UI. +#sonar.projectName=sofie-mos-connection +#sonar.projectVersion=1.0 + +# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. +#sonar.sources=. + +# Encoding of the source code. Default is default system encoding +#sonar.sourceEncoding=UTF-8 + +================ +File: tsconfig.build.json +================ +{ + "extends": "@sofie-automation/code-standard-preset/ts/tsconfig.lib", + "include": [ + "**/src/**/*.ts" + ], + "exclude": [ + "**/node_modules/**", + "**/*spec.ts", + "**/__tests__", + "**/__mocks__", + "**/scratch/**" + ], + "compilerOptions": { + "outDir": "./dist", + "baseUrl": "./", + "paths": { + "*": [ + "./node_modules/*" + ] + }, + "types": [ + "node" + ] + } +} + +================ +File: tsconfig.json +================ +{ + "extends": "./tsconfig.build.json", + "exclude": [ + "**/node_modules/**" + ], + "compilerOptions": { + "types": [ + "jest", + "node" + ] + } +} + +================ +File: .github/workflows/audit.yaml +================ +name: Audit dependencies + +on: + push: + branches: + - master + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + validate-dependencies: + name: Validate production dependencies + runs-on: ubuntu-latest + continue-on-error: false + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Use Node.js 16 + uses: actions/setup-node@v4 + with: + node-version: 16 + - name: Prepare Environment + run: | + yarn install + env: + CI: true + - name: Validate dependencies + run: | + yarn validate:dependencies + env: + CI: true + validate-all-dependencies: + name: Validate all dependencies + runs-on: ubuntu-latest + continue-on-error: true + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Use Node.js 16 + uses: actions/setup-node@v4 + with: + node-version: 16 + - name: Prepare Environment + run: | + yarn install + env: + CI: true + - name: Validate dependencies + run: | + yarn validate:dev-dependencies + env: + CI: true + +================ +File: .github/workflows/sonar.yaml +================ +on: + # Trigger analysis when pushing in master or pull requests, and when creating + # a pull request. + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] +name: SonarCloud +jobs: + sonarcloud: + name: SonarCloud + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'nrkno' }} + + steps: + - uses: actions/checkout@v4 + with: + # Disabling shallow clone is recommended for improving relevancy of reporting + fetch-depth: 0 + persist-credentials: false + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 16 + - name: Prepare Environment + run: | + yarn + env: + CI: true + + - name: SonarCloud Scan + uses: sonarsource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + +================ +File: packages/connector/src/connection/mosSocketServer.ts +================ +import { Server, Socket } from 'net' +import { EventEmitter } from 'eventemitter3' +import { IncomingConnectionType, SocketServerEvent } from './socketConnection' +⋮---- +export interface MosSocketServerEvents { + [SocketServerEvent.CLIENT_CONNECTED]: (options: { socket: Socket; portDescription: IncomingConnectionType }) => void + [SocketServerEvent.ERROR]: (err: Error) => void + [SocketServerEvent.CLOSE]: () => void +} +export class MosSocketServer extends EventEmitter +⋮---- +/** */ +constructor(port: number, description: IncomingConnectionType, debug: boolean) +async dispose(sockets: Socket[]): Promise +⋮---- +// close clients +⋮---- +// close server +⋮---- +// this._socketServer.on('close', resolve) +⋮---- +// close any server connections: +⋮---- +/** */ +async listen(): Promise +⋮---- +// already listening +⋮---- +// Listens and handles error and events +⋮---- +public setDebug(debug: boolean): void +get port(): number +get portDescription(): IncomingConnectionType +⋮---- +/** */ +private _onClientConnection(socket: Socket) +⋮---- +/** */ +private _onServerError(error: Error) +⋮---- +// @todo: implement +⋮---- +/** */ +private _onServerClose() +⋮---- +// @todo: implement +⋮---- +private debugTrace(...strs: any[]) +⋮---- +// eslint-disable-next-line no-console + +================ +File: packages/connector/src/index.ts +================ +// Backwards compatibility +import { xml2js, pad, addTextElement, xmlToObject } from '@mos-connection/helper' + +================ +File: packages/helper/src/mosModel/profile0/heartBeat.ts +================ +import { getMosTypes, IMOSTime } from '@mos-connection/model' +⋮---- +import { addTextElementInternal } from '../../utils/Utils' +import { MosMessage, PortType } from '../MosMessage' +⋮---- +export class HeartBeat extends MosMessage +⋮---- +/** */ +constructor(port: PortType, time: IMOSTime | undefined, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile1/reqMosObjAll.ts +================ +import { addTextElementInternal } from '../../utils/Utils' +import { MosMessage } from '../MosMessage' +⋮---- +export class ReqMosObjAll extends MosMessage +⋮---- +/** */ +constructor(pause: number, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile2/roElementStat.ts +================ +import { MosMessage } from '../MosMessage' +import { getMosTypes, IMOSObjectStatus, IMOSString128, IMOSTime } from '@mos-connection/model' +import { addTextElementInternal } from '../../utils/Utils' +⋮---- +export enum ROElementStatType { + RO = 'RO', + STORY = 'STORY', + ITEM = 'ITEM', +} +export interface ROElementStatOptions { + type: ROElementStatType + roId: IMOSString128 + storyId?: IMOSString128 + itemId?: IMOSString128 + objId?: IMOSString128 + itemChannel?: IMOSString128 + status: IMOSObjectStatus +} +export class ROElementStat extends MosMessage +⋮---- +/** */ +constructor(options: ROElementStatOptions, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile2/ROListAll.ts +================ +import { MosMessage } from '../MosMessage' +import { IMOSRunningOrder } from '@mos-connection/model' +import { XMLRunningOrderBase } from './xmlConversion' +⋮---- +export class ROListAll extends MosMessage +⋮---- +/** */ +constructor(list: IMOSRunningOrder[], strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile3/index.ts +================ + + +================ +File: packages/helper/src/mosModel/profile3/mosListSearchableSchema.ts +================ +import { MosMessage } from '../MosMessage' +⋮---- +import { IMOSListSearchableSchema } from '@mos-connection/model' +import { XMLMosListSearchableSchema } from './xmlConversion' +⋮---- +export class MosListSearchableSchema extends MosMessage +⋮---- +constructor(options: IMOSListSearchableSchema, strict: boolean) +⋮---- +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile3/mosReqObjList.ts +================ +import { IMOSRequestObjectList } from '@mos-connection/model' +import { MosMessage } from '../MosMessage' +⋮---- +import { XMLMosRequestObjectList } from './xmlConversion' +⋮---- +export class MosReqObjList extends MosMessage +⋮---- +constructor(options: IMOSRequestObjectList, strict: boolean) +⋮---- +get messageXMLBlocks(): XMLBuilder.XMLElement + +================ +File: packages/helper/src/mosModel/profile4/roStory.ts +================ +import { MosMessage } from '../MosMessage' +import { IMOSROFullStory, IMOSItem } from '@mos-connection/model' +import { XMLROStoryBase } from '../profile2/xmlConversion' +import { XMLMosExternalMetaData, XMLMosObjects, XMLObjectPaths } from '../profile1/xmlConversion' +import { addTextElementInternal } from '../../utils/Utils' +⋮---- +export class ROStory extends MosMessage +⋮---- +/** */ +constructor(private fullStory: IMOSROFullStory, strict: boolean) +⋮---- +/** */ +get messageXMLBlocks(): XMLBuilder.XMLElement +⋮---- +// TODO: mosAbstract?: string? +// TODO: Channel?: MosString128? +// TODO: MacroIn?: MosString128? +// TODO: MacroOut?: MosString128? +⋮---- +// Note: the is sent in roStorySend +⋮---- +// TODO: what is this? + +================ +File: packages/helper/src/stringify/stringifyMosObject.ts +================ +import { getMosTypes, IMOSDuration, IMOSString128, IMOSTime, MosTypes, stringifyMosType } from '@mos-connection/model' +⋮---- +/** + * Converts a MOS data object, + * replacing Mos-centric types with their stringified equivalents + */ +export function stringifyMosObject = { + [P in keyof T]: T[P] extends M[0] ? Replacement : T[P] extends object ? DeepReplace : T[P] +} +type Replacement = M extends any ? ([T] extends [M[0]] ? M[1] : never) : never +⋮---- +export type Stringified = DeepReplace< + Original, + [IMOSString128, string] | [IMOSDuration, string] | [IMOSTime, string] +> + +================ +File: packages/helper/src/utils/ensureMethods.ts +================ +import { AnyXMLObject, AnyXMLValue, AnyXMLValueSingular } from '@mos-connection/model' +import { assertStringLiteral, isEmpty } from '../mosModel/lib' +⋮---- +/** + * Ensures that the returned value is an array. + * If the input is not an array, it will be wrapped in an array. + */ +export function ensureArray(v: A | B | B[]): (A | B)[] +export function ensureArray(v: T | T[]): T[] +⋮---- +/** + * Ensures that the returned value is a string literal. + * If the input value is not of the correct type, will throw (if strict) or return the fallback value. + */ +export function ensureStringLiteral( + xmlValue: AnyXMLValue, + options: T[], + strict: boolean, + fallback: T +): T +/** + * Ensures that the returned value is a string. + * If the input value is not of the correct type, will throw (if strict) or return the fallback value. + */ +export function ensureString(value: AnyXMLValue, strict: boolean, fallback = ''): string +⋮---- +/** + * Ensures that the returned value is an object. + * If the input value is not of the correct type, will throw (if strict) or return the fallback value. + */ +export function ensureXMLObject(value: AnyXMLValue, strict: boolean, fallback: AnyXMLObject = +⋮---- +/** + * Ensures that the returned value is a singular value (ie a string or undefined). + * If the input value is not of the correct type, will throw (if strict) or return the fallback value. + */ +export function ensureSingular(value: AnyXMLValue, strict: boolean): AnyXMLValueSingular +⋮---- +// Quick-fix if it is in a xml element: +⋮---- +// Quick-fix for empty object +⋮---- +/** + * Ensures that the returned value is an array containing only singular values + * If the input value is not of the correct type, will throw (if strict) or return an empty array + */ +export function ensureSingularArray(value: AnyXMLValue, strict: boolean): AnyXMLValueSingular[] +/** + * Ensures that the returned value is an array containing only XMLObjects + * If the input value is not of the correct type, will throw (if strict) or return an empty array + */ +export function ensureXMLObjectArray(value: AnyXMLValue, strict: boolean): AnyXMLObject[] +⋮---- +export function isSingular(value: AnyXMLValue): value is AnyXMLValueSingular +export function isSingularArray(value: AnyXMLValue): value is AnyXMLValueSingular[] +export function isXMLObject(value: AnyXMLValue): value is AnyXMLObject +function isXMLTextElement(xml: any): xml is TextElement +interface TextElement { + $type: 'text' + $name: string + text: string +} + +================ +File: packages/helper/src/utils/Errors.ts +================ +/** An error that occurred while parsing a reply to a sent mos message */ +export class MosReplyError extends Error +⋮---- +constructor(orgError: unknown, public readonly parsedReply: unknown | undefined) + +================ +File: packages/model/src/mosTypes/lib.ts +================ +export function pad(n: string | number, width: number, z?: string): string + +================ +File: packages/quick-mos/input/runningorders/README.md +================ +Quick-MOS will monitor the contents of this folder. + +Put any RunningOrders that you want the Quick-MOS server to expose in here. + +Quick-MOS supports + +- ts files (see examples in the folder) +- json files (see examples in the folder) +- Sofie Playlist/Rundown Snapshots + +================ +File: packages/quick-mos/input/config.ts +================ +import { Config } from '../src/.' // eslint-disable-line node/no-unpublished-import +⋮---- +// This is the NCS-id, you might need to specify it in your mos-client that connects to Quick-MOS. +⋮---- +// ports: { +// Set these if you have a mos-client running on other ports than standard: +// lower: 11540, +// upper: 11541, +// query: 11542, +// }, +⋮---- +// Set these if you want quick-mos to run on other ports than standard: +⋮---- +// Set to true to turn on debug-logging: + +================ +File: packages/quick-mos/input/util.ts +================ +import { MosModel } from '@mos-connection/connector' +import type { IMOSROFullStory, IMOSString128 } from '@mos-connection/model' +⋮---- +export type NormalizeMosAttributes = { + [P in keyof T]: T[P] extends IMOSString128 + ? string + : T[P] extends IMOSString128 | undefined + ? string | undefined + : T[P] extends string | number | null | undefined + ? T[P] + : NormalizeMosAttributes +} +⋮---- +export function fixStoryBody( + stories: Array> +): Array> + +================ +File: packages/helper/src/mosModel/profile3/xmlConversion.ts +================ +import { + AnyXMLValue, + IMOSListSearchableSchema, + IMOSObjectList, + IMOSRequestObjectList, + IMOSSearchField, +} from '@mos-connection/model' +import { omitUndefined } from '../lib' +import { getParseMosTypes, getXMLAttributes } from '../parseMosTypes' +import { addTextElementInternal } from '../../utils/Utils' +import { XMLMosObjects } from '../profile1' +import { ParseError } from '../ParseError' +import { ensureXMLObject, ensureXMLObjectArray, isXMLObject } from '../../utils/ensureMethods' +/* eslint-disable @typescript-eslint/no-namespace */ +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSRequestObjectList +export function toXML(xml: XMLBuilder.XMLElement, objList: IMOSRequestObjectList, strict: boolean): void +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSObjectList +export function toXML(xml: XMLBuilder.XMLElement, objList: IMOSObjectList, strict: boolean): void +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSListSearchableSchema +export function toXML(xml: XMLBuilder.XMLElement, schema: IMOSListSearchableSchema, strict: boolean): void + +================ +File: packages/helper/src/mosModel/profile4/xmlConversion.ts +================ +import { IMOSROFullStoryBodyItem, IMOSROFullStory, AnyXMLValue } from '@mos-connection/model' +import { XMLROStory, XMLMosItem } from '../profile2/xmlConversion' +import { omitUndefined } from '../lib' +import { getParseMosTypes } from '../parseMosTypes' +import { ParseError } from '../ParseError' +import { ensureXMLObject, ensureXMLObjectArray } from '../../utils/ensureMethods' +⋮---- +/* eslint-disable @typescript-eslint/no-namespace */ +⋮---- +export function fromXML(path: string, xml0: AnyXMLValue, strict: boolean): IMOSROFullStory +⋮---- +function fromXMLStoryBody(xml: AnyXMLValue, strict: boolean): IMOSROFullStoryBodyItem[] +⋮---- +/* + // Not able to implement this currently, need to change {arrayNotation: true} in xml2json option + let elementKeys = Object.keys(xml) + elementKeys.forEach((key: string) => { + // let elements + let d = xml[key] + + if (!Array.isArray(d)) d = [d] + + d.forEach((el: any) => { + let bodyItem: IMOSROFullStoryBodyItem = { + Type: key, + Content: el + } + body.push(bodyItem) + }) + }) + */ +⋮---- +// Temporary implementation: + +================ +File: packages/helper/src/mosModel/index.ts +================ +import { AnyXMLObject } from '@mos-connection/model' +/** @deprecated use AnyXMLObject instead */ +export type AnyXML = AnyXMLObject // for backwards compatibility + +================ +File: packages/helper/src/mosModel/MosMessage.ts +================ +import { getMosTypes, MosTypes } from '@mos-connection/model' +⋮---- +import { addTextElementInternal } from '../utils/Utils' +⋮---- +export type PortType = 'upper' | 'lower' | 'query' +export abstract class MosMessage +⋮---- +constructor(public port: PortType, protected readonly strict: boolean) +⋮---- +private static getNewMessageID(): number +⋮---- +// increments and returns a signed 32-bit int counting from 1, resetting to 1 when wrapping +⋮---- +/** */ +prepare(messageID?: number): void +⋮---- +/** */ +get messageID(): number +⋮---- +/** */ +toString(): string +⋮---- +/** */ +protected abstract get messageXMLBlocks(): XMLBuilder.XMLElement +⋮---- +/** */ + +================ +File: packages/helper/src/mosModel/ParseError.ts +================ +/* + Typical use case: + function (xml) { + try { + // do something with xml.storyBody + } catch (e) { + throw ParseError.handleCaughtError('storyBody', e) + } + } + +*/ +⋮---- +export class ParseError +⋮---- +static handleCaughtError(basePath: string, e: unknown): ParseError +static isParseError(e: unknown): e is ParseError +static fromError(e: Error, path: string): ParseError +⋮---- +static handleError(func: () => T, path: string): T +⋮---- +private constructor(path: string, message: string, stack?: string) +⋮---- +this.message = '' // Updated in updateVars() +⋮---- +public addBreadcrumb(path: string): void +private updateVars() +⋮---- +toString(): string + +================ +File: packages/model/src/index.ts +================ + + +================ +File: packages/model/src/xmlParse.ts +================ +/** Parsed xml data objects */ +export type AnyXMLObject = { [key: string]: AnyXMLValue } +/** Parsed xml data values */ +export type AnyXMLValue = AnyXMLValueSingular | AnyXMLValueSingular[] | AnyXMLObject | AnyXMLObject[] +/** Parsed xml data values, singular */ +export type AnyXMLValueSingular = string | undefined + +================ +File: package.json +================ +{ + "name": "mos-connection-monorepo", + "version": "0.0.0", + "repository": "https://github.com/nrkno/tv-automation-mos-connection", + "private": true, + "workspaces": [ + "packages/*" + ], + "scripts": { + "prepare": "husky install", + "lerna": "lerna", + "postinstall": "node scripts/update-packages.mjs", + "ci": "run build && run lint && run test", + "release:set-version": "lerna version --exact --no-changelog --no-push --yes && yarn fix-version", + "release:bump-release": "lerna version --exact --conventional-commits --conventional-graduate --tag-version-prefix='v' --no-push --force-publish='*' && yarn fix-version", + "release:bump-prerelease": "lerna version --exact --conventional-commits --conventional-prerelease --tag-version-prefix='v' --no-push --force-publish='*' && yarn fix-version", + "release:bump-prerelease-ci": "lerna version --exact --conventional-commits --conventional-prerelease --tag-version-prefix='v' --no-push --force-publish='*'", + "fix-version": "node scripts/fix-version.js", + "setup": "lerna bootstrap", + "reset": "node scripts/reset.js", + "build": "lerna run build --stream", + "build:changed": "lerna run build --since head --exclude-dependents --stream", + "lint": "lerna exec --parallel -- run -T eslint . --ext .ts,.tsx", + "lintfix": "run lint --fix", + "lint:changed": "lerna exec --since origin/master --include-dependents -- run -T eslint . --ext .js,.jsx,.ts,.tsx", + "test": "lerna run test --stream", + "test:changed": "lerna run --since origin/master --include-dependents test", + "test:coverage": "lerna run test:coverage --stream", + "lint-staged": "./node_modules/.bin/lint-staged", + "prettier": "cd $INIT_CWD && \"$PROJECT_CWD/node_modules/.bin/prettier\"", + "eslint": "cd $INIT_CWD && \"$PROJECT_CWD/node_modules/.bin/eslint\"", + "validate:dependencies": "yarn lerna exec --parallel yarn validate:dependencies && yarn license-validate", + "validate:dev-dependencies": "yarn lerna exec --parallel yarn validate:dev-dependencies", + "license-validate": "yarn sofie-licensecheck --allowPackages \"mos-examples@0.0.0;quick-mos@0.0.0\"" + }, + "devDependencies": { + "@sofie-automation/code-standard-preset": "2.5.1", + "@types/jest": "^29.5.2", + "deep-extend": "^0.6.0", + "glob": "^7.2.3", + "jest": "^29.5.0", + "lerna": "^6.6.2", + "lodash": "^4.17.21", + "rimraf": "^5.0.1", + "ts-jest": "^29.1.0", + "typescript": "~4.9.5" + }, + "engines": { + "node": ">=14.18.0" + }, + "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", + "packageManager": "yarn@3.6.0" +} + +================ +File: README.md +================ +# MOS-Connection + +[![Tests](https://github.com/nrkno/sofie-mos-connection/actions/workflows/node.yaml/badge.svg)](https://github.com/nrkno/sofie-mos-connection/actions/workflows/node.yaml) +[![codecov](https://codecov.io/gh/nrkno/sofie-mos-connection/branch/master/graph/badge.svg?token=LQL02uXajF)](https://codecov.io/gh/nrkno/sofie-mos-connection) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nrkno_tv-automation-mos-connection&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=nrkno_tv-automation-mos-connection) + +[![@mos-connection/connector NPM Version](https://img.shields.io/npm/v/%40mos-connection%2Fconnector)](https://www.npmjs.com/package/@mos-connection/connector) +[![@mos-connection/helper NPM Version](https://img.shields.io/npm/v/%40mos-connection%2Fhelper)](https://www.npmjs.com/package/@mos-connection/helper) +[![@mos-connection/model NPM Version](https://img.shields.io/npm/v/%40mos-connection%2Fmodel)](https://www.npmjs.com/package/@mos-connection/model) + +_MOS-Connection_ is a Node.js library to communicate, using the [MOS Protocol](http://mosprotocol.com/), with systems such as Newsroom Computer Systems (NRCS) or other MOS devices. + +Features include: + +- Acting as a _MOS Device_ (ie a client), connecting to an NRCS. +- Acting as a _NRCS_ (ie a server), allowing other MOS Devices to connect to it. +- Helpful tools for developing _MOS Plugins_ and parsing MOS data. +- A simple tool for testing MOS connections, called _Quick-MOS_. + +This library is developed as a part of the [**Sofie** TV Automation System](https://github.com/nrkno/Sofie-TV-automation/). + +## General Sofie System Information + +- [_Sofie_ Documentation](https://nrkno.github.io/sofie-core/) +- [_Sofie_ Releases](https://nrkno.github.io/sofie-core/releases) +- [Contribution Guidelines](CONTRIBUTING.md) +- [License](LICENSE) + +--- + +## Packages + +MOS-Connection consists of 4 packages: + +- **@mos-connection/connector** is a Node.js library is used to connect to MOS devices or act as a MOS Server/NCS. + The `helper` and `model` functionality is included in this library as well. +- **@mos-connection/helper** is a library that provides various functions useful for those that prepare/handle data that will be sent to (or has been received by) the MOS-connection. + The `model` functionality is included in this library as well. +- **@mos-connection/model** is a library that contains types and enums, useful for applications that handles MOS-data. +- There is also a helper application **QuickMos** designed to be a minimal mock MOS server for testing client applications. + +## Getting Started + +### Installation + +```bash +npm install @mos-connection/connector +``` + +### Usage + +\_See more examples in the [examples](/packages/examples/src) folder, or the [QuickMos](/packages/quick-mos/src/index.ts) implementation! + +```typescript +import { MosConnection } from '@mos-connection/connector' + +let mos = new MosConnection(new ConnectionConfig({ + mosID: 'my.mos.application', + acceptsConnections: true, + profiles: { + '0': true, + '1': true, + '2': true, + '4': true + }, + openRelay: true + debug: false +})) +mos.onConnection((device: MosDevice) => { // called whenever there is a new connection to a mos-device + if (device.hasConnection) { // true if we can send messages to the mos-server + device.getMachineInfo().then((lm) => { + console.log('Machineinfo', lm) + }) + } + // Setup callbacks to pipe data: + device.onRequestMachineInfo(() => {}) + device.onCreateRunningOrder((ro) => {}) + device.onDeleteRunningOrder((RunningOrderID: MosString128) => {}) + device.onReadyToAir(() => {}) + // ... +}) +``` + +### Quick-MOS + +"Quick-MOS" is a simple MOS application that can be used to test the MOS-connection library. + +It reads data-files from disk and pretends to be an NRCS, so you can connect other MOS-clients to it. + +See [Quick-MOS](/packages/quick-mos/README.md) for more information. + +## MOS Support + +The MOS-Connection library currently supports the [MOS Protocol version **2.8.5**](https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS_Protocol_Version_2.8.5_Final.htm). + +### Supported MOS Profiles + +| Profile | Status | +| --------- | --------------------- | +| Profile 0 | Implemented | +| Profile 1 | Implemented | +| Profile 2 | Implemented | +| Profile 3 | Implemented | +| Profile 4 | Implemented | +| Profile 5 | Not implemented (yet) | +| Profile 6 | Not implemented (yet) | +| Profile 7 | Not implemented (yet) | + +Pull Requests for the remaining profiles would be happily accepted! + +--- + +_The NRK logo is a registered trademark of Norsk rikskringkasting AS. The license does not grant any right to use, in any way, any trademarks, service marks or logos of Norsk rikskringkasting AS._ + +================ +File: .github/workflows/publish-prerelease.yaml +================ +name: Publish prerelease + +on: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + test: + name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + node_version: ['14', '16', '18', '20'] + os: [ubuntu-latest] # [windows-latest, macOS-latest] + + timeout-minutes: 5 + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Use Node.js ${{ matrix.node_version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node_version }} + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Prepare Environment + run: | + yarn + yarn build + env: + CI: true + - name: Run unit tests + run: | + yarn test -- --maxWorkers=2 --forceExit + env: + CI: true + + prerelease: + name: Publish to NPM + runs-on: ubuntu-latest + timeout-minutes: 15 + + needs: + - test + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + - name: Use Node.js 16 + uses: actions/setup-node@v4 + with: + node-version: 16 + - name: Check release is desired + id: do-publish + run: | + if [ -z "${{ secrets.NPM_TOKEN }}" ]; then + echo "No Token" + elif [[ "${{ github.ref }}" == "refs/heads/master" ]]; then + echo "Publish nightly" + echo "publish=nightly" >> $GITHUB_OUTPUT + else + echo "Publish experimental" + echo "publish=experimental" >> $GITHUB_OUTPUT + fi + - name: Prepare Environment + if: ${{ steps.do-publish.outputs.publish }} + run: | + yarn install + yarn build + env: + CI: true + - name: Bump version + if: ${{ steps.do-publish.outputs.publish }} + run: | + git config --global user.email "info@superfly.tv" + git config --global user.name "superflytvab" + + COMMIT_TIMESTAMP=$(git log -1 --pretty=format:%ct HEAD) + COMMIT_DATE=$(date -d @$COMMIT_TIMESTAMP +%Y%m%d-%H%M%S) + GIT_HASH=$(git rev-parse --short HEAD) + PRERELEASE_TAG=nightly-$(echo "${{ github.ref_name }}" | sed -r 's/[^a-z0-9]+/-/gi') + yarn release:bump-prerelease-ci --no-changelog --no-commit-hooks --preid "$PRERELEASE_TAG-$COMMIT_DATE-$GIT_HASH" prerelease --yes + env: + CI: true + - name: Publish to NPM + if: ${{ steps.do-publish.outputs.publish }} + run: | + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc + yarn lerna publish from-package --tag-version-prefix='' --dist-tag ${{ steps.do-publish.outputs.publish }} --yes --no-verify-access + env: + CI: true + +================ +File: packages/connector/src/connection/mosMessageParser.ts +================ +import { EventEmitter } from 'eventemitter3' +import { MosModel, xml2js } from '@mos-connection/helper' +⋮---- +export interface MosMessageParserEvents { + message: (parsedData: ParsedMosMessage, messageString: string) => void +} +export class MosMessageParser extends EventEmitter +⋮---- +constructor(private description: string) +⋮---- +public parseMessage(messageString: string): void +⋮---- +// parse as many messages as possible from the data +⋮---- +// whitespace before a mos message is junk +⋮---- +// Nothing was plucked, so abort +⋮---- +private _tryParseData() +⋮---- +const startMatch = '' // +const endMatch = '' // +⋮---- +// No start tag, so looks like we have jibberish +⋮---- +// trim off anything before , as we'll never be able to parse that anyway. +⋮---- +// We have an end tag too +⋮---- +/** null = message is not complete */ +⋮---- +// fast-path: +⋮---- +// Figure out where in the message the end tag closes the start tag: +⋮---- +// Hmm, something is wrong, there should never be more end tags than start tags +⋮---- +// trim off anything before this end tag, we'll never be able to parse that anyway. +⋮---- +// We have a complete message, so pluck it out +⋮---- +const data = xml2js(messageString) as any as ParsedMosMessage // , { compact: true, trim: true, nativeType: true }) +⋮---- +// eslint-disable-next-line no-console +⋮---- +// eslint-disable-next-line no-console +⋮---- +// this.emit('error', e) +⋮---- +private debugTrace(str: string) +⋮---- +// Suppress console spam: +⋮---- +// eslint-disable-next-line no-console +⋮---- +/** Returns a list of indexes for the occurences of searchString in str */ +private indexesOf(str: string, searchString: string): number[] +⋮---- +// ^ Just to avoid an infinite loop +⋮---- +/** Definition of an incoming MOS Message */ +export interface ParsedMosMessage { + mos: { + ncsID: string + mosID: string + messageID?: string // Note: messageID is optional for some messages in older versions of the MOS Protocol + + [key: string]: MosModel.AnyXMLValue + } +} +⋮---- +messageID?: string // Note: messageID is optional for some messages in older versions of the MOS Protocol + +================ +File: packages/helper/src/mosModel/profile2/xmlConversion.ts +================ +import { + IMOSROStory, + IMOSItem, + IMOSROFullStory, + IMOSRunningOrderBase, + IMOSRunningOrder, + IMOSROAckStory, + IMOSROAckItem, + IMOSROAckObject, + AnyXMLValue, +} from '@mos-connection/model' +import { has, omitUndefined } from '../lib' +import { ensureArray, ensureXMLObject } from '../../utils/ensureMethods' +import { ROAck } from './ROAck' +import { XMLMosExternalMetaData, XMLMosObjects, XMLObjectPaths } from '../profile1/xmlConversion' +import { addTextElementInternal } from '../../utils/Utils' +import { getParseMosTypes } from '../parseMosTypes' +import { ParseError } from '../ParseError' +/* eslint-disable @typescript-eslint/no-namespace */ +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): ROAck +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSRunningOrderBase +export function toXML(xmlRo: XMLBuilder.XMLElement, ro: IMOSRunningOrderBase, strict: boolean): void +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSRunningOrder +⋮---- +export function fromXML(path: string, xmlStories: AnyXMLValue, strict: boolean): IMOSROStory[] +⋮---- +export function toXML( + xmlStory: XMLBuilder.XMLElement, + story: IMOSROStory | IMOSROFullStory, + strict: boolean +): void +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSROStory +⋮---- +// TODO: Add & test Number, ObjectID, MOSID, mosAbstract, Paths +// Channel, EditorialStart, EditorialDuration, UserTimingDuration, Trigger, MacroIn, MacroOut, MosExternalMetaData +// MosExternalMetaData: handleError(MOSExternalMetaData.fromXML(xml.mosExternalMetadata], 'mosExternalMetadata'). +⋮---- +// Note: the is sent in roStorySend +⋮---- +export function toXML(xmlRoot: XMLBuilder.XMLElement, story: IMOSROStory, strict: boolean): void +⋮---- +export function fromXML(path: string, xmlItems: AnyXMLValue, strict: boolean): Array +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSItem +⋮---- +// Note: the is sent in roStorySend +⋮---- +export function toXML(root: XMLBuilder.XMLElement, item: IMOSItem, strict: boolean): void +⋮---- +// TODO: MosObjects + +================ +File: packages/helper/src/utils/Utils.ts +================ +import { xml2js as xmlParser } from 'xml-js' +⋮---- +import { + AnyXMLValue, + getMosTypes, + IMOSDuration, + IMOSString128, + IMOSTime, + stringifyMosType, +} from '@mos-connection/model' +import { MosModel } from '..' +⋮---- +export function xml2js(messageString: string): MosModel.AnyXMLObject +⋮---- +nativeType: false, // we want to NOT auto-convert types, to avoid ambiguity +⋮---- +// common tags we typically want to know the order of the contents of: +⋮---- +/** + * Doing a post-order tree traversal we try to make the objectified tree as compact as possible. + * Whenever we find an "orderedTag" we keep the order of it's children. + * + * ps: post-order means we make a node's children as compact as possible first, and then try to make + * that node compact. + */ +const concatChildrenAndTraverseObject = (element: +⋮---- +// pure text node, hoist it up: +⋮---- +// leave it as is +⋮---- +// if the element name is contained in the set of orderedTag names we don't make it any more compact +⋮---- +// make array compact: +⋮---- +// might contain something useful like attributes +⋮---- +// all elements are unique +⋮---- +export function addTextElement( + root: XMLBuilder.XMLElement, + elementName: string, + text?: string | number | null | IMOSString128 | IMOSTime | IMOSDuration, + attributes?: { [key: string]: string }, + strict = true +): XMLBuilder.XMLElement +export function addTextElementInternal( + root: XMLBuilder.XMLElement, + elementName: string, + content: AnyXMLValue | number | null | IMOSString128 | IMOSTime | IMOSDuration, + attributes: { [key: string]: string | undefined } | undefined, + strict: boolean +): XMLBuilder.XMLElement +/** + * Utility-function to convert a XMLBuilder.XMLElement into the generic object which can be sent + * into the ***.fromXML(xml:any) methods in MosModel + */ +export function xmlToObject(root: XMLBuilder.XMLElement): any +⋮---- +// @ts-expect-error hack +⋮---- +// @ts-expect-error hack +⋮---- +// @ts-expect-error hack +⋮---- +obj[ch.name] = [obj[ch.name]] // make an array + +================ +File: packages/helper/src/index.ts +================ +// not addTextElementInternal + +================ +File: packages/model/src/mosTypes/mosString128.ts +================ +export interface IMOSString128 { + _mosString128: string + /** @deprecated use getMosTypes().mosString128.stringify() instead! */ + toString: never +} +⋮---- +/** @deprecated use getMosTypes().mosString128.stringify() instead! */ +⋮---- +export function create(anyValue: AnyValue, strict: boolean): IMOSString128 +⋮---- +// is empty? +⋮---- +export type AnyValue = string | { text: string; type: string } | IMOSString128 | undefined +⋮---- +export function validate(mosString128: IMOSString128, strict: boolean): void +export function valueOf(mosString128: IMOSString128): string +⋮---- +if (typeof mosString128 === 'string') return mosString128 // helpful hack +⋮---- +export function stringify(mosString128: IMOSString128): string +⋮---- +if (typeof mosString128 === 'string') return mosString128 // helpful hack +⋮---- +export function is(mosString128: IMOSString128 | any): mosString128 is IMOSString128 +export function fallback(): IMOSString128 + +================ +File: packages/model/src/model.ts +================ +import { IMOSTime, IMOSString128, IMOSDuration, IMOSExternalMetaData } from './mosTypes' +import { AnyXMLValue } from './xmlParse' +⋮---- +export interface IMOSROAction { + RunningOrderID: IMOSString128 +} +export interface IMOSStoryAction extends IMOSROAction { + StoryID: IMOSString128 +} +export interface IMOSItemAction extends IMOSStoryAction { + ItemID: IMOSString128 +} +export interface IMOSROReadyToAir { + ID: IMOSString128 + Status: IMOSObjectAirStatus +} +export interface IMOSRunningOrderStatus { + ID: IMOSString128 + Status: IMOSObjectStatus + Time: IMOSTime +} +export interface IMOSStoryStatus { + RunningOrderId: IMOSString128 + ID: IMOSString128 + Status: IMOSObjectStatus + Time: IMOSTime +} +export interface IMOSItemStatus { + RunningOrderId: IMOSString128 + StoryId: IMOSString128 + ID: IMOSString128 + Status: IMOSObjectStatus + Time: IMOSTime + ObjectId?: IMOSString128 + Channel?: IMOSString128 +} +export interface IMOSRunningOrderBase { + ID: IMOSString128 // running order id + Slug: IMOSString128 + DefaultChannel?: IMOSString128 + EditorialStart?: IMOSTime + EditorialDuration?: IMOSDuration + Trigger?: IMOSString128 + MacroIn?: IMOSString128 + MacroOut?: IMOSString128 + MosExternalMetaData?: Array +} +⋮---- +ID: IMOSString128 // running order id +⋮---- +export interface IMOSRunningOrder extends IMOSRunningOrderBase { + Stories: Array +} +export interface IMOSStory { + ID: IMOSString128 + Slug?: IMOSString128 + Number?: IMOSString128 + MosExternalMetaData?: Array +} +export interface IMOSROStory extends IMOSStory { + Items: Array +} +export interface IMOSROFullStory extends IMOSStory { + RunningOrderId: IMOSString128 + Body: Array +} +⋮---- +export type IMOSROFullStoryBodyItem = IMOSROFullStoryBodyStoryItem | IMOSROFullStoryBodyOtherItem +export type IMOSROFullStoryBodyStoryItem = { + itemType: 'storyItem' + Type: 'storyItem' + Content: IMOSItem // TODO: Make this stricter? IMOSItemObject?? +} +⋮---- +Content: IMOSItem // TODO: Make this stricter? IMOSItemObject?? +⋮---- +export type IMOSROFullStoryBodyOtherItem = { + itemType: 'other' + Type: string // e.g. 'p' + Content: AnyXMLValue +} +⋮---- +Type: string // e.g. 'p' +⋮---- +export interface IMOSItem { + ID: IMOSString128 + Slug?: IMOSString128 + ObjectSlug?: IMOSString128 + ObjectID: IMOSString128 + MOSID: string + mosAbstract?: string + Paths?: Array + Channel?: IMOSString128 + EditorialStart?: number + EditorialDuration?: number + Duration?: number + TimeBase?: number + UserTimingDuration?: number + Trigger?: AnyXMLValue + MacroIn?: IMOSString128 + MacroOut?: IMOSString128 + MosExternalMetaData?: Array + MosObjects?: Array +} +⋮---- +export interface IMOSAck { + ID: IMOSString128 + Revision: number // max 999 + Status: IMOSAckStatus + Description: IMOSString128 +} +⋮---- +Revision: number // max 999 +⋮---- +export interface IMOSROAck { + ID: IMOSString128 // Running order id + Status: IMOSString128 // OK or error desc + Stories: Array +} +⋮---- +ID: IMOSString128 // Running order id +Status: IMOSString128 // OK or error desc +⋮---- +export interface IMOSROAckStory { + ID: IMOSString128 // storyID + Items: Array +} +⋮---- +ID: IMOSString128 // storyID +⋮---- +export interface IMOSROAckItem { + ID: IMOSString128 + Channel: IMOSString128 + Objects: Array +} +⋮---- +export interface IMOSROAckObject { + Status: 'OK' | string + ID: IMOSString128 +} +⋮---- +export interface IProfiles { + [key: string]: boolean | undefined + '0': boolean + '1'?: boolean + '2'?: boolean + '3'?: boolean + '4'?: boolean + '5'?: boolean + '6'?: boolean + '7'?: boolean +} +⋮---- +/** https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObj */ +export interface IMOSObject { + ID?: IMOSString128 + Slug: IMOSString128 + MosAbstract?: AnyXMLValue // xml json + /** A stringified version of MosAbstract, where all HTML have been flattened */ + MosAbstractStr?: string + Group?: string + Type: IMOSObjectType + TimeBase: number + Revision?: number // max 999 + Duration: number + Status?: IMOSObjectStatus + AirStatus?: IMOSObjectAirStatus + Paths?: Array + CreatedBy?: IMOSString128 + Created?: IMOSTime + ChangedBy?: IMOSString128 // if not present, defaults to CreatedBy + Changed?: IMOSTime // if not present, defaults to Created + Description?: AnyXMLValue // xml json + /** A stringified version of Description, where all HTML have been flattened */ + DescriptionStr?: string + MosExternalMetaData?: Array + MosItemEditorProgID?: IMOSString128 +} +⋮---- +MosAbstract?: AnyXMLValue // xml json +/** A stringified version of MosAbstract, where all HTML have been flattened */ +⋮---- +Revision?: number // max 999 +⋮---- +ChangedBy?: IMOSString128 // if not present, defaults to CreatedBy +Changed?: IMOSTime // if not present, defaults to Created +Description?: AnyXMLValue // xml json +/** A stringified version of Description, where all HTML have been flattened */ +⋮---- +/** + * Returns selected object descriptions from a MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjList + */ +export interface IMOSObjectList { + username: string + queryID: string + listReturnStart: number + listReturnEnd: number + listReturnTotal: number + listReturnStatus?: string + list?: Array +} +⋮---- +/** + * mosReqObjList is a mechanism used by a NCS to retrieve only selected object descriptions from a MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObjList + */ +export interface IMOSRequestObjectList { + username: string + queryID: IMOSString128 + listReturnStart: number | null + listReturnEnd: number | null + generalSearch: IMOSString128 + mosSchema: string + searchGroups: Array<{ + searchFields: Array + }> +} +/** @see IMOSRequestObjectList */ +export interface IMOSSearchField { + XPath: string + sortByOrder?: number + sortType?: string +} +⋮---- +/** + * mosListSearchableSchema is a mechanism used by the MOS to send a pointer to a schema in which searchable fields are defined for the NCS device. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosListSearchableSchema + */ +export interface IMOSListSearchableSchema { + username: string + mosSchema: string +} +⋮---- +export enum IMOSObjectType { + STILL = 'STILL', + AUDIO = 'AUDIO', + VIDEO = 'VIDEO', + OTHER = 'OTHER', // unknown/not specified +} +⋮---- +OTHER = 'OTHER', // unknown/not specified +⋮---- +export enum IMOSObjectStatus { + NEW = 'NEW', + UPDATED = 'UPDATED', + MOVED = 'MOVED', + BUSY = 'BUSY', + DELETED = 'DELETED', + NCS_CTRL = 'NCS CTRL', + MANUAL_CTRL = 'MANUAL CTRL', + READY = 'READY', + NOT_READY = 'NOT READY', + PLAY = 'PLAY', + STOP = 'STOP', +} +⋮---- +export enum IMOSAckStatus { + ACK = 'ACK', + NACK = 'NACK', +} +⋮---- +export enum IMOSObjectAirStatus { + READY = 'READY', + NOT_READY = 'NOT READY', +} +⋮---- +export interface IMOSObjectPath { + Type: IMOSObjectPathType + Description: string + Target: string // Max 255 +} +⋮---- +Target: string // Max 255 +⋮---- +export enum IMOSObjectPathType { + PATH = 'PATH', + PROXY_PATH = 'PROXY PATH', + METADATA_PATH = 'METADATA PATH', +} +⋮---- +export interface MosItemReplaceOptions { + roID: IMOSString128 + storyID: IMOSString128 + item: IMOSItem +} +⋮---- +export enum IMOSListMachInfoDefaultActiveXMode { + MODALDIALOG = 'MODALDIALOG', + MODELESS = 'MODELESS', + CONTAINED = 'CONTAINED', + TOOLBAR = 'TOOLBAR', +} +⋮---- +export interface IMOSListMachInfo { + /** Used in MOS ActiveX messages. Manufacturer: Text description. 128 chars max. */ + manufacturer: IMOSString128 + /** Model: Text description. 128 chars max. */ + model: IMOSString128 + /** HW Revision: 128 chars max. */ + hwRev: IMOSString128 + /** Software Revision: (MOS) Text description. 128 chars max., example: '2.1.0.37' */ + swRev: IMOSString128 + /** Date of Manufacture. */ + DOM: IMOSString128 + /** Serial Number: text serial number. 128 chars max. ex: '927748927' */ + SN: IMOSString128 + /** Identification of a Machine: text. 128 chars max. */ + ID: IMOSString128 + /** Time: Time object changed status. Format is YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] */ + time: IMOSTime + /** Operational Time: date and time of last machine start. Format is YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] */ + opTime?: IMOSTime + /** MOS Revision: Text description. 128 chars max. */ + mosRev: IMOSString128 + + supportedProfiles: { + deviceType: 'NCS' | 'MOS' | 'N/A' + profile0?: boolean + profile1?: boolean + profile2?: boolean + profile3?: boolean + profile4?: boolean + profile5?: boolean + profile6?: boolean + profile7?: boolean + } + /** defaultActiveX contains tags that describe the correct settings for the ActiveX control (NOTE: no two elements can have the same value). */ + defaultActiveX?: Array + mosExternalMetaData?: Array +} +⋮---- +/** Used in MOS ActiveX messages. Manufacturer: Text description. 128 chars max. */ +⋮---- +/** Model: Text description. 128 chars max. */ +⋮---- +/** HW Revision: 128 chars max. */ +⋮---- +/** Software Revision: (MOS) Text description. 128 chars max., example: '2.1.0.37' */ +⋮---- +/** Date of Manufacture. */ +⋮---- +/** Serial Number: text serial number. 128 chars max. ex: '927748927' */ +⋮---- +/** Identification of a Machine: text. 128 chars max. */ +⋮---- +/** Time: Time object changed status. Format is YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] */ +⋮---- +/** Operational Time: date and time of last machine start. Format is YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] */ +⋮---- +/** MOS Revision: Text description. 128 chars max. */ +⋮---- +/** defaultActiveX contains tags that describe the correct settings for the ActiveX control (NOTE: no two elements can have the same value). */ +⋮---- +export interface IMOSDefaultActiveX { + /** Used in MOS ActiveX messages. How the ActiveX Plug-In window appears in the NCS Host window: MODALDIALOG, MODELESS, CONTAINED, TOOLBAR. */ + mode: IMOSListMachInfoDefaultActiveXMode + /** controlFileLocation is the file location for the default ActiveX control. */ + controlFileLocation: string + /** Defined by MOS 128 characters max */ + controlSlug: IMOSString128 + /** This value represents the key/classId key used to load the ActiveX from the registry., ex: "contained.containedCTRL.1" */ + controlName: string + /** This value represents the parameters that can be passed to an ActiveX. ex "URL=http:" */ + controlDefaultParams: string +} +⋮---- +/** Used in MOS ActiveX messages. How the ActiveX Plug-In window appears in the NCS Host window: MODALDIALOG, MODELESS, CONTAINED, TOOLBAR. */ +⋮---- +/** controlFileLocation is the file location for the default ActiveX control. */ +⋮---- +/** Defined by MOS 128 characters max */ +⋮---- +/** This value represents the key/classId key used to load the ActiveX from the registry., ex: "contained.containedCTRL.1" */ +⋮---- +/** This value represents the parameters that can be passed to an ActiveX. ex "URL=http:" */ + +================ +File: packages/model/src/mosTypes.ts +================ +import { AnyXMLValue } from './xmlParse' +⋮---- +/** + * Returns utility-functions for handling of MosTypes. + * @example + * const mosTypes = getMosTypes(true) + * const myString128 = mosTypes.mosString128.create('Hello world') + * const myString = mosTypes.mosString128.stringify(myString128) + * @param strict If true, creating out-of-spec values will throw an error (Example: Creating longer-than-128-characters long MosString128). + */ +export function getMosTypes(strict: boolean): MosTypes +export interface MosTypes { + strict: boolean + mosString128: MosType + mosDuration: MosType + mosTime: MosType +} +/** + * If value is a MosType, stringify it. + * Throw error otherwise + */ +export function stringifyMosType( +⋮---- +export interface MosType { + /** Creates a MosType using provided data. The MosType is then used in data sent into MOS-connection */ + create: (anyValue: CreateValue) => Serialized + /** (internal function) Validate the data. Throws if something is wrong with the data */ + validate: (value: Serialized) => void + /** Returns the value of the MosType */ + valueOf: (value: Serialized) => Value + /** Returns a stringified representation of the MosType */ + stringify: (value: Serialized) => string + /** Returns true if the provided data is of this MosType */ + is: (value: Serialized | any) => value is Serialized + + /** Returns a fallback value, used to replace missing or non-parsable data in non-strict mode */ + fallback: () => Serialized +} +⋮---- +/** Creates a MosType using provided data. The MosType is then used in data sent into MOS-connection */ +⋮---- +/** (internal function) Validate the data. Throws if something is wrong with the data */ +⋮---- +/** Returns the value of the MosType */ +⋮---- +/** Returns a stringified representation of the MosType */ +⋮---- +/** Returns true if the provided data is of this MosType */ +⋮---- +/** Returns a fallback value, used to replace missing or non-parsable data in non-strict mode */ +⋮---- +interface InternalMosType { + create: (anyValue: any, strict: boolean) => Serialized + validate: (value: Serialized, strict: boolean) => void + valueOf(value: Serialized): Value + stringify(value: Serialized): string + is(value: Serialized | any): value is Serialized + fallback(): Serialized +} +⋮---- +valueOf(value: Serialized): Value +stringify(value: Serialized): string +is(value: Serialized | any): value is Serialized +fallback(): Serialized +⋮---- +function getMosType( + mosType: InternalMosType, + strict: boolean +): MosType +⋮---- +export interface IMOSExternalMetaData { + MosScope?: IMOSScope + MosSchema: string + MosPayload: AnyXMLValue +} +export enum IMOSScope { + OBJECT = 'OBJECT', + STORY = 'STORY', + PLAYLIST = 'PLAYLIST', +} + +================ +File: packages/quick-mos/src/convertFromSofieSnapshot.ts +================ +import { getMosTypes, IMOSROFullStory, IMOSROStory, IMOSRunningOrder } from '@mos-connection/model' +⋮---- +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +⋮---- +export function convertFromSofieSnapshot( + filePath: string, + snapShotData: any +): +⋮---- +function fixStoryBody(stories: any[]) + +================ +File: packages/helper/src/mosModel/lib.ts +================ +import { AnyXMLValue } from '@mos-connection/model' +⋮---- +export function isEmpty(obj: unknown): boolean +⋮---- +/** Return true if the object has a property */ +export function has(obj: unknown, property: string): boolean +⋮---- +/** + * Asserts that a string type is of a certain literal. + * Example usage: const str = assertStringLiteral('foo', ['foo', 'bar']) // str is of type 'foo' | 'bar' + */ +export function assertStringLiteral(value: string, options: T[]): value is T +⋮---- +/** Type assertion */ +export function literal(o: T): T +⋮---- +/** + * Removes undefined properties from an object + */ +export function omitUndefined(obj: +export function flattenXMLText(xml: AnyXMLValue, strict: boolean): string +function flattenXMLTextInner(strings: string[], xml: AnyXMLValue, strict: boolean): void +⋮---- +// else + +================ +File: .github/workflows/node.yaml +================ +name: Node CI - Lint and Test + +on: + push: + branches: + - '**' + tags: + - 'v[0-9]+.[0-9]+.[0-9]+*' + pull_request: + +jobs: + lint: + name: Lint and Type check + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: '16' + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Prepare Environment + run: yarn + env: + CI: true + - name: Type check + run: yarn build + env: + CI: true + - name: Lint + run: | + yarn lint + env: + CI: true + + test: + name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + node_version: ['14', '16', '18', '20'] + os: [ubuntu-latest] # [windows-latest, macOS-latest] + + timeout-minutes: 5 + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Use Node.js ${{ matrix.node_version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node_version }} + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Prepare Environment + run: | + yarn + yarn build + env: + CI: true + - name: Run unit tests + run: | + yarn test -- --maxWorkers=2 --forceExit --coverage + env: + CI: true + - name: Send coverage + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + validate-dependencies: + name: Validate production dependencies + runs-on: ubuntu-latest + continue-on-error: false + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Use Node.js 16 + uses: actions/setup-node@v4 + with: + node-version: 16 + - name: Prepare Environment + run: | + yarn install + env: + CI: true + - name: Validate dependencies + run: | + yarn validate:dependencies + env: + CI: true + + release: + name: Release + runs-on: ubuntu-latest + timeout-minutes: 5 + + # only run for tags + if: contains(github.ref, 'refs/tags/') + + needs: + - test + - lint + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + - name: Use Node.js 16 + uses: actions/setup-node@v4 + with: + node-version: 16 + - name: Check release is desired + id: do-publish + run: | + if [ -z "${{ secrets.NPM_TOKEN }}" ]; then + echo "No Token" + else + cd packages/connector + PACKAGE_NAME=@mos-connection/connector + PUBLISHED_VERSION=$(yarn npm info --json $PACKAGE_NAME | jq -c '.version' -r) + THIS_VERSION=$(node -p "require('./package.json').version") + # Simple bash helper to comapre version numbers + verlte() { + [ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ] + } + verlt() { + [ "$1" = "$2" ] && return 1 || verlte $1 $2 + } + if verlt $PUBLISHED_VERSION $THIS_VERSION + then + echo "Publishing latest" + echo "tag=latest" >> $GITHUB_OUTPUT + else + echo "Publishing hotfix" + echo "tag=hotfix" >> $GITHUB_OUTPUT + fi + + fi + - name: Prepare Environment + if: ${{ steps.do-publish.outputs.tag }} + run: | + yarn install + env: + CI: true + - name: Build + if: ${{ steps.do-publish.outputs.tag }} + run: | + yarn build + env: + CI: true + - name: Publish to NPM + if: ${{ steps.do-publish.outputs.tag }} + run: | + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc + yarn lerna publish from-package --tag-version-prefix='' --dist-tag ${{ steps.do-publish.outputs.tag }} --yes --no-verify-access + env: + CI: true + +================ +File: packages/helper/src/mosModel/profile0/xmlConversion.ts +================ +import { + AnyXMLValue, + IMOSDefaultActiveX, + IMOSListMachInfo, + IMOSListMachInfoDefaultActiveXMode, + IMOSString128, +} from '@mos-connection/model' +import { has, omitUndefined } from '../lib' +import { ensureArray, ensureStringLiteral, ensureXMLObject } from '../../utils/ensureMethods' +import { addTextElementInternal } from '../../utils/Utils' +import { getParseMosTypes, getXMLAttributes } from '../parseMosTypes' +import { XMLMosExternalMetaData } from '../profile1' +import { ParseError } from '../ParseError' +⋮---- +/* eslint-disable @typescript-eslint/no-namespace */ +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSString128[] +⋮---- +/* eslint-disable @typescript-eslint/no-namespace */ +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSListMachInfo +export function toXML(xmlListMachInfo: XMLBuilder.XMLElement, info: IMOSListMachInfo, strict: boolean): void +⋮---- +export function fromXML( + path: string, + xmlSupportedProfiles: AnyXMLValue, + strict: boolean +): IMOSListMachInfo['supportedProfiles'] +⋮---- +// Note: .profiles are added below +⋮---- +// @ts-expect-error hack +⋮---- +export function toXML( + xml: XMLBuilder.XMLElement, + supportedProfiles: IMOSListMachInfo['supportedProfiles'], + strict: boolean +): void +⋮---- +// let p = addTextElement(root, 'supportedProfiles').att('deviceType', info.supportedProfiles.deviceType) +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSDefaultActiveX[] +export function toXML(xml: XMLBuilder.XMLElement, objs: IMOSDefaultActiveX[], strict: boolean): void + +================ +File: packages/helper/src/mosModel/profile1/xmlConversion.ts +================ +import { + IMOSObject, + IMOSAck, + IMOSAckStatus, + IMOSObjectPath, + IMOSObjectPathType, + IMOSExternalMetaData, + AnyXMLValue, + AnyXMLValueSingular, + IMOSObjectType, + IMOSObjectStatus, + IMOSObjectAirStatus, + IMOSScope, +} from '@mos-connection/model' +import { AnyXMLObject, flattenXMLText, has, isEmpty, literal, omitUndefined } from '../lib' +import { ensureArray, ensureXMLObject, ensureXMLObjectArray, isXMLObject } from '../../utils/ensureMethods' +import { addTextElementInternal } from '../../utils/Utils' +import { getParseMosTypes } from '../parseMosTypes' +import { ParseError } from '../ParseError' +⋮---- +/* eslint-disable @typescript-eslint/no-namespace */ +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSAck +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSObject[] +export function toXML(xml: XMLBuilder.XMLElement, objs: IMOSObject[], strict: boolean): void +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSObject +export function toXML(xml: XMLBuilder.XMLElement, obj: IMOSObject, strict: boolean): void +⋮---- +if (obj.Description) addTextElementInternal(xml, 'description', obj.Description, undefined, strict) // not handled (todo) +⋮---- +export function fromXML(path: string, xmlObjPaths: AnyXMLValue, strict: boolean): IMOSObjectPath[] +⋮---- +const xmlToArray = (obj: AnyXMLValue): IMOSObjectPath[] => +⋮---- +// Possible inputs: +/* + Single obj path + { + objPath: '41cfc641849c417eaa10e4f3c377f181', + techDescription: 'VIDEO' + } + + { + objPath: { + text: '41cfc641849c417eaa10e4f3c377f181', + techDescription: 'VIDEO' + }, + objProxyPath: { + text: 'http://server/proxy/clipe.wmv', + techDescription: 'WM9 750Kbps' + } + } + + { + objPath: { attributes: { techDescription: 'asdfasdf' }, text: 'asdfasdf' }, + objMetadataPath: { attributes: { techDescription: 'skdjhfb' }, text: '8372h4fv' } + } + { + objPath: [ + { + '$name': 'objPath', + '$type': 'text', + text: '41cfc641849c417eaa10e4f3c377f181', + techDescription: 'VIDEO' + }, + { + '$name': 'objPath', + '$type': 'text', + text: 'abc123.mp4', + techDescription: 'MYVIDEO' + } + ], + objProxyPath: { + '$name': 'objProxyPath', + '$type': 'text', + text: 'http://server/proxy/clipe.wmv', + techDescription: 'WM9 750Kbps' + } + } + */ +⋮---- +// When there is only one: ------------------------------------------- +⋮---- +// A single objPath +⋮---- +// A single objProxyPath +⋮---- +// A single objMetadataPath +⋮---- +// When there are multiple: ------------------------------------------- +⋮---- +export function toXML(xmlItem: XMLBuilder.XMLElement, paths: IMOSObjectPath[] | undefined, strict: boolean): void +⋮---- +export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSExternalMetaData[] +⋮---- +export function toXML(xml: XMLBuilder.XMLElement, metadatas: IMOSExternalMetaData[]): void +⋮---- +/** Replace any empty objects with "" */ +function fixXMLMosPayload(value: AnyXMLValueSingular): AnyXMLValueSingular +function fixXMLMosPayload(value: AnyXMLObject): AnyXMLObject +function fixXMLMosPayload(value: AnyXMLValue): AnyXMLValue +⋮---- +if (isEmpty(value)) return '' // In xml, an empty tag results in an empty object + +================ +File: packages/helper/src/mosModel/parseMosTypes.ts +================ +import { MosTypes, getMosTypes, MosType, AnyXMLObject } from '@mos-connection/model' +import { AnyXMLValue } from './lib' +import { ParseError } from './ParseError' +import { ensureSingular } from '../utils/ensureMethods' +⋮---- +export function getParseMosTypes(strict: boolean): MosParseTypes +export type MosParseTypes = { + [key in keyof MosTypes]: MosTypes[key] extends MosType + ? MosTypeParse + : MosTypes[key] +} & { + string: MosTypeParse + stringEnum: MosTypeParse + number: MosTypeParse +} +interface MosTypeParse extends Omit, 'create'> { + /** + * Used to parse data that is optional. + * If the data is missing, undefined is returned. + */ + createOptional: (anyValue: CreateValue, path: string) => Serialized | undefined + /** + * Used to parse data that is required. + * If in strict mode, the data must be present and parsable, otherwise an error is thrown. + * If not in strict mode, a fallback value will be used. + */ + createRequired: (anyValue: CreateValue, path: string) => Serialized +} +⋮---- +/** + * Used to parse data that is optional. + * If the data is missing, undefined is returned. + */ +⋮---- +/** + * Used to parse data that is required. + * If in strict mode, the data must be present and parsable, otherwise an error is thrown. + * If not in strict mode, a fallback value will be used. + */ +⋮---- +function wrapParseMethods( + mosType: MosType, + valueIsSingular: boolean, + strict: boolean +): MosTypeParse +function wrapParseMethodCreateOptional( + mosType: MosType, + valueIsSingular: boolean, + strict: boolean +): MosTypeParse['createOptional'] +function wrapParseMethodCreateRequired( + mosType: MosType, + valueIsSingular: boolean, + strict: boolean +): MosTypeParse['createRequired'] +⋮---- +export function parseOptional( + parser: (value: V) => R, + valueIsSingular: boolean, + strict: boolean +): (value: V | AnyXMLValue, path: string) => R | undefined +⋮---- +// handle empty string: +⋮---- +// handle empty object (can happen when parsing an empty xml tag): +⋮---- +export function parseRequired( + parser: (value: V) => R, + fallback: () => R, + valueIsSingular: boolean, + strict: boolean +): (value: V | AnyXMLValue, path: string) => R +⋮---- +// handle empty string: +⋮---- +// handle empty object (can happen when parsing an empty xml tag): +⋮---- +// Something might be wrong. value is undefined, but should not be (?) +⋮---- +// This will throw if the mosType doesn't handle undefined: +⋮---- +function getSpecialMosTypes(strict: boolean) +export function getXMLAttributes(obj: AnyXMLObject): + +================ +File: packages/model/src/mosTypes/mosDuration.ts +================ +import { pad } from './lib' +⋮---- +export interface IMOSDuration { + _mosDuration: number // in seconds + /** @deprecated use getMosTypes().mosDuration.stringify() instead! */ + toString: never +} +⋮---- +_mosDuration: number // in seconds +/** @deprecated use getMosTypes().mosDuration.stringify() instead! */ +⋮---- +export function create(anyValue: AnyValue, strict: boolean): IMOSDuration +⋮---- +value = anyValue // seconds +⋮---- +export type AnyValue = string | number | IMOSDuration +export function validate(_mosDuration: IMOSDuration, _strict: boolean): void +⋮---- +// nothing +⋮---- +export function valueOf(mosDuration: IMOSDuration): number +⋮---- +if (typeof mosDuration === 'number') return mosDuration // helpful hack +⋮---- +export function stringify(mosDuration: IMOSDuration): string +⋮---- +if (typeof mosDuration === 'string') return mosDuration // helpful hack +⋮---- +export function is(mosDuration: IMOSDuration | any): mosDuration is IMOSDuration +export function fallback(): IMOSDuration + +================ +File: packages/quick-mos/src/index.ts +================ +/* eslint-disable node/no-unpublished-import */ +⋮---- +import { + MosConnection, + IMOSRunningOrder, + IMOSROStory, + IMOSROFullStory, + IConnectionConfig, + IMOSDeviceConnectionOptions, + MosDevice, + IMOSListMachInfo, + IMOSObjectAirStatus, + getMosTypes, +} from '@mos-connection/connector' +import { diffLists, ListEntry, OperationType } from './mosDiff' +⋮---- +import { convertFromSofieSnapshot } from './convertFromSofieSnapshot' +⋮---- +const DELAY_TIME = 300 // ms +⋮---- +export interface Config { + mosConnection: IConnectionConfig + devices: IMOSDeviceConnectionOptions[] +} +⋮---- +// @ts-expect-error just a stub, will be overwritten by /input/config.ts +⋮---- +function triggerReload() +function loadFile(requirePath: string): any +⋮---- +// eslint-disable-next-line @typescript-eslint/no-var-requires +⋮---- +async function reloadInner() +⋮---- +// Save the new config: +⋮---- +// Kill the old: +⋮---- +// Set up the new: +⋮---- +// opTime?: MosTime; +⋮---- +// profile3?: boolean; +// profile4?: boolean; +// profile5?: boolean; +// profile6?: boolean; +// profile7?: boolean; +⋮---- +// defaultActiveX?: Array; +// mosExternalMetaData?: Array; +⋮---- +// mosDevice.onRequestMOSObject((objId: string) => Promise): void; +// mosDevice.onRequestAllMOSObjects((pause: number) => Promise | IMOSAck>): void; +// mosDevice.onCreateRunningOrder((ro: IMOSRunningOrder) => Promise): void; +// mosDevice.onReplaceRunningOrder((ro: IMOSRunningOrder) => Promise): void; +// mosDevice.onDeleteRunningOrder((runningOrderId: MosString128) => Promise): void; +// mosDevice.onRequestRunningOrder((runningOrderId: MosString128) => Promise): void; +// mosDevice.onMetadataReplace((metadata: IMOSRunningOrderBase) => Promise): void; +// mosDevice.onRunningOrderStatus((status: IMOSRunningOrderStatus) => Promise): void; +// mosDevice.onStoryStatus((status: IMOSStoryStatus) => Promise): void; +// mosDevice.onItemStatus((status: IMOSItemStatus) => Promise): void; +// mosDevice.onReadyToAir((Action: IMOSROReadyToAir) => Promise): void; +// mosDevice.onROInsertStories((Action: IMOSStoryAction, Stories: Array) => Promise): void; +// mosDevice.onROInsertItems((Action: IMOSItemAction, Items: Array) => Promise): void; +// mosDevice.onROReplaceStories((Action: IMOSStoryAction, Stories: Array) => Promise): void; +// mosDevice.onROReplaceItems((Action: IMOSItemAction, Items: Array) => Promise): void; +// mosDevice.onROMoveStories((Action: IMOSStoryAction, Stories: Array) => Promise): void; +// mosDevice.onROMoveItems((Action: IMOSItemAction, Items: Array) => Promise): void; +// mosDevice.onRODeleteStories((Action: IMOSROAction, Stories: Array) => Promise): void; +// mosDevice.onRODeleteItems((Action: IMOSStoryAction, Items: Array) => Promise): void; +// mosDevice.onROSwapStories((Action: IMOSROAction, StoryID0: MosString128, StoryID1: MosString128) => Promise): void; +// mosDevice.onROSwapItems((Action: IMOSStoryAction, ItemID0: MosString128, ItemID1: MosString128) => Promise): void; +// mosDevice.onMosObjCreate((object: IMOSObject) => Promise): void; +// mosDevice.onMosItemReplace((roID: MosString128, storyID: MosString128, item: IMOSItem) => Promise): void; +// mosDevice.onMosReqSearchableSchema((username: string) => Promise): void; +// mosDevice.onMosReqObjectList((objList: IMosRequestObjectList) => Promise): void; +// mosDevice.onMosReqObjectAction((action: string, obj: IMOSObject) => Promise): void; +⋮---- +// mosDevice.onROStory((story: IMOSROFullStory) => Promise): void; +⋮---- +function refreshFiles() +⋮---- +// Check data +⋮---- +function fetchRunningOrders() +⋮---- +requirePath.match(/[/\\]_/) || // ignore and folders files that begin with "_" +requirePath.match(/[/\\]lib\.ts/) // ignore lib files +⋮---- +// Is a Sofie snapshot +⋮---- +function getAllFilesInDirectory(dir: string): string[] +⋮---- +// ------------ +⋮---- +type MOSCommand = () => Promise +class MOSMonitor +⋮---- +constructor(private mosDevice: MosDevice) +⋮---- +setTimeout(() => this.triggerRandomUpdate(), 10000) // startup delay +⋮---- +triggerRandomUpdate() +⋮---- +// only fire if the queue is sufficiently small (to avoid a backlog) +⋮---- +// send it +⋮---- +// run again +setTimeout(() => this.triggerRandomUpdate(), 500) // abritrary gap +⋮---- +// run again +setTimeout(() => this.triggerRandomUpdate(), 10000) // abritrary gap +⋮---- +onDeletedRunningOrder(roId: string) +⋮---- +// At the end, store the updated RO: +⋮---- +resendRunningOrder(roId: string): IMOSRunningOrder +onUpdatedRunningOrder(ro: IMOSRunningOrder, fullStories: IMOSROFullStory[], readyToAir: boolean | undefined): void +⋮---- +// compare with +⋮---- +// New RO +⋮---- +// nothing changed, do nothing +⋮---- +// Only RO metadata has changed +⋮---- +// Only Stories has changed +⋮---- +/* + // const addedGroups = this.groupIndexes(o.added) + _.each(o.added, (stories, beforeId) => { + // const index = parseInt(index0, 10) + }) + _.each(o.changed, c => { + this.commands.push(() => { + console.log('sendROReplaceStories', ro.ID) + const story = ro.Stories[c.id] + return this.mosDevice.sendROReplaceStories({ + RunningOrderID: ro.ID, + StoryID: mosTypes.mosString128.create(c.id) + }, [ c.story ]) + }) + }) + // Swap logic: + // if ( + // o.moved.length === 2 && + // o.moved[0].ids.length === 1 && + // o.moved[1].ids.length === 1 && + + // o.moved[0].beforeId + + // o.moved[0].beforeId === o.moved[1].oldIndex && + // o.moved[1].index === o.moved[0].oldIndex + // ) { + // this.commands.push(() => { + // console.log('sendROSwapStories', ro.ID) + // return this.mosDevice.sendROSwapStories({ + // RunningOrderID: ro.ID + // }, + // mosTypes.mosString128.create(o.moved[0].id), + // mosTypes.mosString128.create(o.moved[1].id) + // ) + // }) + // } else { + // const movedGroups = this.groupIndexes(o.moved) + // console.log('movedGroups', movedGroups) + _.each(o.moved, (m) => { + // const index = parseInt(index0, 10) + this.commands.push(() => { + console.log('sendROMoveStories', ro.ID, m.afterId, m.ids) + // const behindStory = index > 0 && ro.Stories[index - 1] + return this.mosDevice.sendROMoveStories({ + RunningOrderID: ro.ID, + StoryID: mosTypes.mosString128.create(m.afterId) + }, m.ids.map(m => mosTypes.mosString128.create(m))) + }) + }) + */ +⋮---- +// last resort: replace the whole rundown +⋮---- +// At the end, store the updated RO: +⋮---- +groupIndexes[] +static md5(str: string): string +⋮---- +private triggerCheckQueue() +⋮---- +this.commands.splice(0, 1) // remove the command from queue, as it has now been executed successfully +⋮---- +function fakeOnUpdatedRunningOrder(ro: IMOSRunningOrder, _fullStories: IMOSROFullStory[]): void +⋮---- +// compare with +⋮---- +// console.log('fakeOnUpdatedRunningOrder', roId) +⋮---- +// New RO +⋮---- +// console.log(_.omit(localRo.ro, 'MosExternalMetaData', 'Stories')) +// console.log(_.omit(ro, 'MosExternalMetaData', 'Stories')) +// console.log(metadataEqual) +// console.log(roStoriesEqual) +// console.log(roBaseDataEqual) +⋮---- +// nothing changed, do nothing +⋮---- +// Only RO metadata has changed +⋮---- +// this.commands.push(() => { +// return this.mosDevice.sendMetadataReplace(ro) +// }) +⋮---- +// Only Stories has changed +⋮---- +// const inserts = operation.inserts.map((i) => i.content) +⋮---- +// this.commands.push(() => { +// return this.mosDevice.sendROInsertStories({ +// RunningOrderID: ro.ID, +// StoryID: mosTypes.mosString128.create(operation.beforeId) +// }, inserts) +// }) +⋮---- +// const updatedStory = operation.content +⋮---- +// this.commands.push(() => { +// return this.mosDevice.sendROReplaceStories({ +// RunningOrderID: ro.ID, +// StoryID: mosTypes.mosString128.create(operation.id) +// }, [ updatedStory ]) +// }) +⋮---- +// this.commands.push(() => { +// return this.mosDevice.sendRODeleteStories({ +// RunningOrderID: ro.ID +// }, removeIds.map(id => mosTypes.mosString128.create(id))) +// }) +⋮---- +// this.commands.push(() => { +// return this.mosDevice.sendROMoveStories({ +// RunningOrderID: ro.ID, +// StoryID: mosTypes.mosString128.create(beforeId) +// }, moveIds.map(id => mosTypes.mosString128.create(id))) +// }) +⋮---- +// last resort: replace the whole rundown +⋮---- +// this.commands.push(() => { +// return this.mosDevice.sendReplaceRunningOrder(ro) +// }) +⋮---- +// At the end, store the updated RO: +⋮---- +// this.triggerCheckQueue() + +================ +File: packages/connector/src/api.ts +================ +import { + IProfiles, + IMOSListMachInfo, + IMOSObject, + MosItemReplaceOptions, + IMOSListSearchableSchema, + IMOSRequestObjectList, + IMOSObjectList, + IMOSAck, + IMOSRunningOrder, + IMOSItem, + IMOSItemAction, + IMOSItemStatus, + IMOSROAck, + IMOSROAction, + IMOSROFullStory, + IMOSROReadyToAir, + IMOSROStory, + IMOSRunningOrderBase, + IMOSRunningOrderStatus, + IMOSStoryAction, + IMOSStoryStatus, + IMOSString128, +} from '@mos-connection/model' +import { MosDevice } from './MosDevice' +⋮---- +/* + This file defines the API for the MOS-connection. + Most of these interfaces are derived from the MOS protocol. + https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm +*/ +export interface IMosConnection { + readonly isListening: boolean + + readonly acceptsConnections: boolean + readonly profiles: IProfiles + readonly isCompliant: boolean + readonly complianceText: string + + dispose: () => Promise + /* */ + connect: (connectionOptions: IMOSDeviceConnectionOptions) => Promise // resolved when connection has been made (before .onConnection is fired) + onConnection: (cb: (mosDevice: MosDevice) => void) => void + + on(event: 'error', listener: (error: Error) => void): this + on(event: 'warning', listener: (message: string) => void): this + on(event: 'info', listener: (message: string, data?: any) => void): this + on(event: 'rawMessage', listener: (source: string, type: string, message: string) => void): this +} +⋮---- +/* */ +connect: (connectionOptions: IMOSDeviceConnectionOptions) => Promise // resolved when connection has been made (before .onConnection is fired) +⋮---- +on(event: 'error', listener: (error: Error) +on(event: 'warning', listener: (message: string) +on(event: 'info', listener: (message: string, data?: any) +on(event: 'rawMessage', listener: (source: string, type: string, message: string) +⋮---- +export interface IMOSDevice + extends IMOSDeviceProfile0, + IMOSDeviceProfile1, + IMOSDeviceProfile2, + IMOSDeviceProfile3, + IMOSDeviceProfile4 { + idPrimary: string // unique id for this device and session + idSecondary: string | null // unique id for this device and session (buddy) +} +⋮---- +idPrimary: string // unique id for this device and session +idSecondary: string | null // unique id for this device and session (buddy) +⋮---- +/** + * Method definitions for Profile 1 + * see https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#Profile1 + */ +export interface IMOSDeviceProfile0 { + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * The reqMachInfo message is a method for an NCS or MOS to determine more information about its counterpart. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#reqMachInfo + */ + requestMachineInfo: () => Promise + /** + * Send message (as NCS) to a MOS device: + * The reqMachInfo message is a method for an NCS or MOS to determine more information about its counterpart. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#reqMachInfo + */ + onRequestMachineInfo: (cb: () => Promise) => void + + /** Assign callback for when the connection status changes. */ + onConnectionChange: (cb: (connectionStatus: IMOSConnectionStatus) => void) => void + /** Get the current connection status */ + getConnectionStatus: () => IMOSConnectionStatus + + // Deprecated methods: + /** @deprecated getMachineInfo is deprecated, use requestMachineInfo instead */ + getMachineInfo: () => Promise + /** @deprecated onGetMachineInfo is deprecated, use onRequestMachineInfo instead */ + onGetMachineInfo: (cb: () => Promise) => void +} +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * The reqMachInfo message is a method for an NCS or MOS to determine more information about its counterpart. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#reqMachInfo + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * The reqMachInfo message is a method for an NCS or MOS to determine more information about its counterpart. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#reqMachInfo + */ +⋮---- +/** Assign callback for when the connection status changes. */ +⋮---- +/** Get the current connection status */ +⋮---- +// Deprecated methods: +/** @deprecated getMachineInfo is deprecated, use requestMachineInfo instead */ +⋮---- +/** @deprecated onGetMachineInfo is deprecated, use onRequestMachineInfo instead */ +⋮---- +/** + * Method definitions for Profile 1 + * see https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#Profile1 + */ +export interface IMOSDeviceProfile1 { + /** + * Contains information that describes a unique MOS Object to the NCS. + * The NCS uses this information to search for and reference the MOS Object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObj + */ + sendMOSObject(obj: IMOSObject): Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Message used by the NCS to request the description of an object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObj + */ + onRequestMOSObject: (cb: (objId: string) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Message used by the NCS to request the description of an object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObj + */ + sendRequestMOSObject: (objId: IMOSString128) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Contains information that describes a unique MOS Object to the NCS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObj + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosListAll + */ + onMOSObjects: (cb: (objs: IMOSObject[]) => Promise) => void + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Method for the NCS to request the MOS to send it a mosObj message for every Object in the MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqAll + */ + onRequestAllMOSObjects: (cb: () => Promise>) => void + /** + * Method for the NCS to request the MOS to send it a mosObj message for every Object in the MOS. + * The replies will be sent to the callback set up in onMOSObjects() + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqAll + */ + sendRequestAllMOSObjects: ( + /** + * 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. + * @default 0 + */ + pause?: number + ) => Promise + + // Deprecated methods: + /** @deprecated getMOSObject is deprecated, use sendRequestMOSObject instead */ + getMOSObject: (objId: IMOSString128) => Promise + /** @deprecated getAllMOSObjects is deprecated, use sendRequestAllMOSObjects instead */ + getAllMOSObjects: () => Promise +} +⋮---- +/** + * Contains information that describes a unique MOS Object to the NCS. + * The NCS uses this information to search for and reference the MOS Object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObj + */ +sendMOSObject(obj: IMOSObject): Promise +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Message used by the NCS to request the description of an object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObj + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Message used by the NCS to request the description of an object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObj + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Contains information that describes a unique MOS Object to the NCS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObj + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosListAll + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Method for the NCS to request the MOS to send it a mosObj message for every Object in the MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqAll + */ +⋮---- +/** + * Method for the NCS to request the MOS to send it a mosObj message for every Object in the MOS. + * The replies will be sent to the callback set up in onMOSObjects() + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqAll + */ +⋮---- +/** + * 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. + * @default 0 + */ +⋮---- +// Deprecated methods: +/** @deprecated getMOSObject is deprecated, use sendRequestMOSObject instead */ +⋮---- +/** @deprecated getAllMOSObjects is deprecated, use sendRequestAllMOSObjects instead */ +⋮---- +/** + * Method definitions for Profile 2 + * see https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#Profile2 + */ +export interface IMOSDeviceProfile2 { + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Message received from the NCS to the MOS that defines a new Running Order. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roCreate + */ + onCreateRunningOrder: (cb: (ro: IMOSRunningOrder) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Message from the NCS to the MOS that defines a new Running Order. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roCreate + */ + sendCreateRunningOrder: (ro: IMOSRunningOrder) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Message received from the NCS to the MOS that defines a new Running Order, replacing an existing one. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReplace + */ + onReplaceRunningOrder: (cb: (ro: IMOSRunningOrder) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Message received from the NCS to the MOS that defines a new Running Order, replacing an existing one. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReplace + */ + sendReplaceRunningOrder: (ro: IMOSRunningOrder) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Deletes a Running order in the MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roDelete + */ + onDeleteRunningOrder: (cb: (runningOrderId: IMOSString128) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Deletes a Running order in the MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roDelete + */ + sendDeleteRunningOrder: (runningOrderId: IMOSString128) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Request for a complete build of a Running Order Playlist. NOTE: This message can be used by either NCS or MOS. + * A MOS can use this to "resync" its Playlist with the NCS Running Order or to obtain a full description of the Playlist at any time. + * An NCS can use this as a diagnostic tool to check the order of the Playlist constructed in the MOS versus the sequence of Items in the Running Order. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReq + * Response: https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roList + */ + onRequestRunningOrder: (cb: (runningOrderId: IMOSString128) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Request for a complete build of a Running Order Playlist. NOTE: This message can be used by either NCS or MOS. + * A MOS can use this to "resync" its Playlist with the NCS Running Order or to obtain a full description of the Playlist at any time. + * An NCS can use this as a diagnostic tool to check the order of the Playlist constructed in the MOS versus the sequence of Items in the Running Order. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReq + * Response: https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roList + */ + sendRequestRunningOrder: (runningOrderId: IMOSString128) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * The roMetadataReplace message allows metadata associated with a running order to be replaced without deleting the running order and sending the entire running order again. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roMetadataReplace + */ + onMetadataReplace: (cb: (metadata: IMOSRunningOrderBase) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * The roMetadataReplace message allows metadata associated with a running order to be replaced without deleting the running order and sending the entire running order again. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roMetadataReplace + */ + sendMetadataReplace: (metadata: IMOSRunningOrderBase) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * A method for the MOS to update the NCS on the status of a RO. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ + onRunningOrderStatus: (cb: (status: IMOSRunningOrderStatus) => Promise) => void // get roElementStat + /** + * Send message (as NCS) to a MOS device: + * A method for the MOS to update the NCS on the status of a RO. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ + sendRunningOrderStatus: (status: IMOSRunningOrderStatus) => Promise // send roElementStat + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * A method for the MOS to update the NCS on the status of a STORY. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ + onStoryStatus: (cb: (status: IMOSStoryStatus) => Promise) => void // get roElementStat + /** + * Send message (as NCS) to a MOS device: + * A method for the MOS to update the NCS on the status of a STORY. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ + sendStoryStatus: (status: IMOSStoryStatus) => Promise // send roElementStat + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * A method for the MOS to update the NCS on the status of an ITEM. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ + onItemStatus: (cb: (status: IMOSItemStatus) => Promise) => void // get roElementStat + /** + * Send message (as NCS) to a MOS device: + * A method for the MOS to update the NCS on the status of an ITEM. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ + sendItemStatus: (status: IMOSItemStatus) => Promise // send roElementStat + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * The roReadyToAir message allows the NCS to signal the MOS that a Running Order has been editorially approved ready for air. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReadyToAir + */ + onReadyToAir: (cb: (Action: IMOSROReadyToAir) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * The roReadyToAir message allows the NCS to signal the MOS that a Running Order has been editorially approved ready for air. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReadyToAir + */ + sendReadyToAir: (Action: IMOSROReadyToAir) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Insert one or more story into a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + onROInsertStories: (cb: (Action: IMOSStoryAction, Stories: Array) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Insert one or more story into a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + sendROInsertStories: (Action: IMOSStoryAction, Stories: Array) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Insert one or more Items into a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + onROInsertItems: (cb: (Action: IMOSItemAction, Items: Array) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Insert one or more Items into a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + sendROInsertItems: (Action: IMOSItemAction, Items: Array) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Replace (update) a Story with a new story. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + onROReplaceStories: (cb: (Action: IMOSStoryAction, Stories: Array) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Replace (update) a Story with a new story. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + sendROReplaceStories: (Action: IMOSStoryAction, Stories: Array) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Replace (update) an Item with a new Item. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + onROReplaceItems: (cb: (Action: IMOSItemAction, Items: Array) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Replace (update) an Item with a new Item. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + sendROReplaceItems: (Action: IMOSItemAction, Items: Array) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Move one or more Stories within a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + onROMoveStories: (cb: (Action: IMOSStoryAction, Stories: Array) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Move one or more Stories within a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + sendROMoveStories: (Action: IMOSStoryAction, Stories: Array) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Move one or more Items within a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + onROMoveItems: (cb: (Action: IMOSItemAction, Items: Array) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Move one or more Items within a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + sendROMoveItems: (Action: IMOSItemAction, Items: Array) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Delete one or more Stories within a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + onRODeleteStories: (cb: (Action: IMOSROAction, Stories: Array) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Delete one or more Stories within a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + sendRODeleteStories: (Action: IMOSROAction, Stories: Array) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Delete one or more Items within a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + onRODeleteItems: (cb: (Action: IMOSStoryAction, Items: Array) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * Delete one or more Items within a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + sendRODeleteItems: (Action: IMOSStoryAction, Items: Array) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Swap two Stories + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + onROSwapStories: ( + cb: (Action: IMOSROAction, StoryID0: IMOSString128, StoryID1: IMOSString128) => Promise + ) => void + /** + * Send message (as NCS) to a MOS device: + * Swap two Stories + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + sendROSwapStories: (Action: IMOSROAction, StoryID0: IMOSString128, StoryID1: IMOSString128) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Swap two Items + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + onROSwapItems: ( + cb: (Action: IMOSStoryAction, ItemID0: IMOSString128, ItemID1: IMOSString128) => Promise + ) => void + /** + * Send message (as NCS) to a MOS device: + * Swap two Items + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ + sendROSwapItems: (Action: IMOSStoryAction, ItemID0: IMOSString128, ItemID1: IMOSString128) => Promise + + // Deprecated methods: + /** @deprecated getRunningOrder is deprecated, use sendRequestRunningOrder instead */ + getRunningOrder: (runningOrderId: IMOSString128) => Promise + /** @deprecated setRunningOrderStatus is deprecated, use sendRunningOrderStatus instead */ + setRunningOrderStatus: (status: IMOSRunningOrderStatus) => Promise + /** @deprecated setStoryStatus is deprecated, use sendStoryStatus instead */ + setStoryStatus: (status: IMOSStoryStatus) => Promise + /** @deprecated setItemStatus is deprecated, use sendItemStatus instead */ + setItemStatus: (status: IMOSItemStatus) => Promise +} +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Message received from the NCS to the MOS that defines a new Running Order. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roCreate + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Message from the NCS to the MOS that defines a new Running Order. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roCreate + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Message received from the NCS to the MOS that defines a new Running Order, replacing an existing one. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReplace + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Message received from the NCS to the MOS that defines a new Running Order, replacing an existing one. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReplace + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Deletes a Running order in the MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roDelete + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Deletes a Running order in the MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roDelete + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Request for a complete build of a Running Order Playlist. NOTE: This message can be used by either NCS or MOS. + * A MOS can use this to "resync" its Playlist with the NCS Running Order or to obtain a full description of the Playlist at any time. + * An NCS can use this as a diagnostic tool to check the order of the Playlist constructed in the MOS versus the sequence of Items in the Running Order. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReq + * Response: https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roList + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Request for a complete build of a Running Order Playlist. NOTE: This message can be used by either NCS or MOS. + * A MOS can use this to "resync" its Playlist with the NCS Running Order or to obtain a full description of the Playlist at any time. + * An NCS can use this as a diagnostic tool to check the order of the Playlist constructed in the MOS versus the sequence of Items in the Running Order. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReq + * Response: https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roList + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * The roMetadataReplace message allows metadata associated with a running order to be replaced without deleting the running order and sending the entire running order again. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roMetadataReplace + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * The roMetadataReplace message allows metadata associated with a running order to be replaced without deleting the running order and sending the entire running order again. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roMetadataReplace + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * A method for the MOS to update the NCS on the status of a RO. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ +onRunningOrderStatus: (cb: (status: IMOSRunningOrderStatus) => Promise) => void // get roElementStat +/** + * Send message (as NCS) to a MOS device: + * A method for the MOS to update the NCS on the status of a RO. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ +sendRunningOrderStatus: (status: IMOSRunningOrderStatus) => Promise // send roElementStat +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * A method for the MOS to update the NCS on the status of a STORY. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ +onStoryStatus: (cb: (status: IMOSStoryStatus) => Promise) => void // get roElementStat +/** + * Send message (as NCS) to a MOS device: + * A method for the MOS to update the NCS on the status of a STORY. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ +sendStoryStatus: (status: IMOSStoryStatus) => Promise // send roElementStat +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * A method for the MOS to update the NCS on the status of an ITEM. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ +onItemStatus: (cb: (status: IMOSItemStatus) => Promise) => void // get roElementStat +/** + * Send message (as NCS) to a MOS device: + * A method for the MOS to update the NCS on the status of an ITEM. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- + */ +sendItemStatus: (status: IMOSItemStatus) => Promise // send roElementStat +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * The roReadyToAir message allows the NCS to signal the MOS that a Running Order has been editorially approved ready for air. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReadyToAir + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * The roReadyToAir message allows the NCS to signal the MOS that a Running Order has been editorially approved ready for air. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReadyToAir + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Insert one or more story into a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Insert one or more story into a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Insert one or more Items into a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Insert one or more Items into a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Replace (update) a Story with a new story. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Replace (update) a Story with a new story. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Replace (update) an Item with a new Item. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Replace (update) an Item with a new Item. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Move one or more Stories within a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Move one or more Stories within a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Move one or more Items within a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Move one or more Items within a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Delete one or more Stories within a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Delete one or more Stories within a Running Order + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Delete one or more Items within a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Delete one or more Items within a Story + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Swap two Stories + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Swap two Stories + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Swap two Items + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * Swap two Items + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction + */ +⋮---- +// Deprecated methods: +/** @deprecated getRunningOrder is deprecated, use sendRequestRunningOrder instead */ +⋮---- +/** @deprecated setRunningOrderStatus is deprecated, use sendRunningOrderStatus instead */ +⋮---- +/** @deprecated setStoryStatus is deprecated, use sendStoryStatus instead */ +⋮---- +/** @deprecated setItemStatus is deprecated, use sendItemStatus instead */ +⋮---- +/** + * Method definitions for Profile 3 + * see https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#Profile3 + */ +export interface IMOSDeviceProfile3 { + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosObjCreate allows an NCS to request the Media Object Server to create a Media Object with specific metadata associated with it. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjCreate + */ + onObjectCreate: (cb: (object: IMOSObject) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * mosObjCreate allows an NCS to request the Media Object Server to create a Media Object with specific metadata associated with it. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjCreate + */ + sendObjectCreate: (object: IMOSObject) => Promise + + /** + * Assign callback (as a NCS device) for when receiving message from MOS: + * This message allows a Media Object Server to replace an Item Reference in a Story with new metadata values and/or additional tags. + * The Story must be in a MOS Active PlayList. + * This message is initiated by the Media Object Server, rather than the NCS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosItemReplace + */ + onItemReplace: (cb: (roID: IMOSString128, storyID: IMOSString128, item: IMOSItem) => Promise) => void + /** + * Send message (as MOS) to a NCS: + * This message allows a Media Object Server to replace an Item Reference in a Story with new metadata values and/or additional tags. + * The Story must be in a MOS Active PlayList. + * This message is initiated by the Media Object Server, rather than the NCS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosItemReplace + */ + sendItemReplace: (options: MosItemReplaceOptions) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosReqSearchable Schema is a mechanism used by the NCS to request the MOS to send a pointer to a schema in which searchable fields are defined by the MOS device. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqSearchableSchema + */ + onRequestSearchableSchema: (cb: (username: string) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * mosReqSearchable Schema is a mechanism used by the NCS to request the MOS to send a pointer to a schema in which searchable fields are defined by the MOS device. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqSearchableSchema + */ + sendRequestSearchableSchema: (username: string) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosReqObjList is a mechanism used by a NCS to retrieve only selected object descriptions from a MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObjList + */ + onRequestObjectList: (cb: (objList: IMOSRequestObjectList) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * mosReqObjList is a mechanism used by a NCS to retrieve only selected object descriptions from a MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObjList + */ + sendRequestObjectList: (reqObjList: IMOSRequestObjectList) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ + onRequestObjectActionNew: (cb: (obj: IMOSObject) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ + sendRequestObjectActionNew: (obj: IMOSObject) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ + onRequestObjectActionUpdate: (cb: (objId: IMOSString128, obj: IMOSObject) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ + sendRequestObjectActionUpdate: (objId: IMOSString128, obj: IMOSObject) => Promise + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ + onRequestObjectActionDelete: (cb: (objId: IMOSString128) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ + sendRequestObjectActionDelete: (objId: IMOSString128) => Promise + + // Deprecated methods: + /** @deprecated onMosObjCreate is deprecated, use onObjectCreate instead */ + onMosObjCreate: (cb: (object: IMOSObject) => Promise) => void + /** @deprecated mosObjCreate is deprecated, use sendObjectCreate instead */ + mosObjCreate: (object: IMOSObject) => Promise + /** @deprecated onMosItemReplace is deprecated, use onItemReplace instead */ + onMosItemReplace: (cb: (roID: IMOSString128, storyID: IMOSString128, item: IMOSItem) => Promise) => void + /** @deprecated mosItemReplace is deprecated, use sendItemReplace instead */ + mosItemReplace: (options: MosItemReplaceOptions) => Promise + /** @deprecated onMosReqSearchableSchema is deprecated, use onRequestSearchableSchema instead */ + onMosReqSearchableSchema: (cb: (username: string) => Promise) => void + /** @deprecated mosRequestSearchableSchema is deprecated, use sendRequestSearchableSchema instead */ + mosRequestSearchableSchema: (username: string) => Promise + /** @deprecated onMosReqObjectList is deprecated, use onRequestObjectList instead */ + onMosReqObjectList: (cb: (objList: IMOSRequestObjectList) => Promise) => void + /** @deprecated mosRequestObjectList is deprecated, use sendRequestObjectList instead */ + mosRequestObjectList: (reqObjList: IMOSRequestObjectList) => Promise + /** @deprecated onMosReqObjectAction is deprecated, use onRequestObjectAction*** instead */ + onMosReqObjectAction: (cb: (action: string, obj: IMOSObject) => Promise) => void +} +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosObjCreate allows an NCS to request the Media Object Server to create a Media Object with specific metadata associated with it. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjCreate + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * mosObjCreate allows an NCS to request the Media Object Server to create a Media Object with specific metadata associated with it. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjCreate + */ +⋮---- +/** + * Assign callback (as a NCS device) for when receiving message from MOS: + * This message allows a Media Object Server to replace an Item Reference in a Story with new metadata values and/or additional tags. + * The Story must be in a MOS Active PlayList. + * This message is initiated by the Media Object Server, rather than the NCS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosItemReplace + */ +⋮---- +/** + * Send message (as MOS) to a NCS: + * This message allows a Media Object Server to replace an Item Reference in a Story with new metadata values and/or additional tags. + * The Story must be in a MOS Active PlayList. + * This message is initiated by the Media Object Server, rather than the NCS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosItemReplace + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosReqSearchable Schema is a mechanism used by the NCS to request the MOS to send a pointer to a schema in which searchable fields are defined by the MOS device. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqSearchableSchema + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * mosReqSearchable Schema is a mechanism used by the NCS to request the MOS to send a pointer to a schema in which searchable fields are defined by the MOS device. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqSearchableSchema + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosReqObjList is a mechanism used by a NCS to retrieve only selected object descriptions from a MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObjList + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * mosReqObjList is a mechanism used by a NCS to retrieve only selected object descriptions from a MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObjList + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request + */ +⋮---- +// Deprecated methods: +/** @deprecated onMosObjCreate is deprecated, use onObjectCreate instead */ +⋮---- +/** @deprecated mosObjCreate is deprecated, use sendObjectCreate instead */ +⋮---- +/** @deprecated onMosItemReplace is deprecated, use onItemReplace instead */ +⋮---- +/** @deprecated mosItemReplace is deprecated, use sendItemReplace instead */ +⋮---- +/** @deprecated onMosReqSearchableSchema is deprecated, use onRequestSearchableSchema instead */ +⋮---- +/** @deprecated mosRequestSearchableSchema is deprecated, use sendRequestSearchableSchema instead */ +⋮---- +/** @deprecated onMosReqObjectList is deprecated, use onRequestObjectList instead */ +⋮---- +/** @deprecated mosRequestObjectList is deprecated, use sendRequestObjectList instead */ +⋮---- +/** @deprecated onMosReqObjectAction is deprecated, use onRequestObjectAction*** instead */ +⋮---- +/** + * Method definitions for Profile 4 + * see https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#Profile4 + */ +export interface IMOSDeviceProfile4 { + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * roReqAll is a request for a description of all Running Orders known by a NCS from a MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReqAll + */ + onRequestAllRunningOrders: (cb: () => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * roReqAll is a request for a description of all Running Orders known by a NCS from a MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReqAll + */ + sendRequestAllRunningOrders: () => Promise> + + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * This message enables sending the body of story from the NCS to a Media Object Server. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roStorySend + */ + onRunningOrderStory: (cb: (story: IMOSROFullStory) => Promise) => void + /** + * Send message (as NCS) to a MOS device: + * This message enables sending the body of story from the NCS to a Media Object Server. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roStorySend + */ + sendRunningOrderStory: (story: IMOSROFullStory) => Promise + + // Deprecated methods: + /** @deprecated onROReqAll is deprecated, use onRequestAllRunningOrders instead */ + onROReqAll: (cb: () => Promise) => void + /** @deprecated getAllRunningOrders is deprecated, use sendRequestAllRunningOrders instead */ + getAllRunningOrders: () => Promise> // send roReqAll + /** @deprecated onROStory is deprecated, use onRunningOrderStory instead */ + onROStory: (cb: (story: IMOSROFullStory) => Promise) => void // roStorySend + /** @deprecated sendROStory is deprecated, use sendRunningOrderStory instead */ + sendROStory: (story: IMOSROFullStory) => Promise // roStorySend +} +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * roReqAll is a request for a description of all Running Orders known by a NCS from a MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReqAll + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * roReqAll is a request for a description of all Running Orders known by a NCS from a MOS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReqAll + */ +⋮---- +/** + * Assign callback (as a MOS device) for when receiving message from NCS: + * This message enables sending the body of story from the NCS to a Media Object Server. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roStorySend + */ +⋮---- +/** + * Send message (as NCS) to a MOS device: + * This message enables sending the body of story from the NCS to a Media Object Server. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roStorySend + */ +⋮---- +// Deprecated methods: +/** @deprecated onROReqAll is deprecated, use onRequestAllRunningOrders instead */ +⋮---- +/** @deprecated getAllRunningOrders is deprecated, use sendRequestAllRunningOrders instead */ +getAllRunningOrders: () => Promise> // send roReqAll +/** @deprecated onROStory is deprecated, use onRunningOrderStory instead */ +onROStory: (cb: (story: IMOSROFullStory) => Promise) => void // roStorySend +/** @deprecated sendROStory is deprecated, use sendRunningOrderStory instead */ +sendROStory: (story: IMOSROFullStory) => Promise // roStorySend +⋮---- +// /** */ +export interface IMOSConnectionStatus { + PrimaryConnected: boolean + PrimaryStatus: string // if not connected this will contain human-readable error-message + SecondaryConnected: boolean + SecondaryStatus: string // if not connected this will contain human-readable error-message +} +⋮---- +PrimaryStatus: string // if not connected this will contain human-readable error-message +⋮---- +SecondaryStatus: string // if not connected this will contain human-readable error-message +⋮---- +/** Config object for creating a MOS-device */ +export interface IConnectionConfig { + /** The ID of this mos-device */ + mosID: string + /** Whether this mosConnection accepts new connections from othe MOS clients (ie acts as an NCS) */ + acceptsConnections: boolean + /** Only accept connections from this whitelist */ + accepsConnectionsFrom?: string[] + /** A list of which profile this mos device is to support */ + profiles: IProfiles + /** If true, this device is assumed to be an NCS (server). Defaults to a MOS (client). */ + isNCS?: boolean + /** Debugging-mode: logs raw mos-messages */ + debug?: boolean + offspecFailover?: boolean + /** If set to true, a strict check is performed to ensure that all required callbacks are set up for specified profiles */ + strict?: boolean + /** If set, overrides the standard port numbers */ + ports?: { + /** Set MOS Lower port (standard: 10540) */ + lower: number + /** Set MOS Upper port (standard: 10541) */ + upper: number + /** Set MOS Query port (standard: 10542) */ + query: number + } + /** When enabled, automatically create new mos-devices on-the-fly when receiving messages to unregistered MOS-ID:s */ + openRelay?: + | boolean + | { + // options for on-the-fly-created connections + options: IMOSDeviceConnectionOptions['primary'] + } +} +⋮---- +/** The ID of this mos-device */ +⋮---- +/** Whether this mosConnection accepts new connections from othe MOS clients (ie acts as an NCS) */ +⋮---- +/** Only accept connections from this whitelist */ +⋮---- +/** A list of which profile this mos device is to support */ +⋮---- +/** If true, this device is assumed to be an NCS (server). Defaults to a MOS (client). */ +⋮---- +/** Debugging-mode: logs raw mos-messages */ +⋮---- +/** If set to true, a strict check is performed to ensure that all required callbacks are set up for specified profiles */ +⋮---- +/** If set, overrides the standard port numbers */ +⋮---- +/** Set MOS Lower port (standard: 10540) */ +⋮---- +/** Set MOS Upper port (standard: 10541) */ +⋮---- +/** Set MOS Query port (standard: 10542) */ +⋮---- +/** When enabled, automatically create new mos-devices on-the-fly when receiving messages to unregistered MOS-ID:s */ +⋮---- +// options for on-the-fly-created connections +⋮---- +export interface IMOSDeviceConnectionOptions { + /** Connection options for the Primary NCS-server */ + primary: { + /** Name (NCS ID) of the NCS-server */ + id: string + /** Host address (IP-address) of the NCS-server */ + host: string // ip-addr + /** (Optional): Custom ports for communication */ + ports?: { + upper: number + lower: number + query: number + } + /** (Optional) Timeout for commands (ms) */ + timeout?: number + /** (Optional) Interval for sending of hearbeats (ms) */ + heartbeatInterval?: number + /** (Optional) Some server doesn't expose the Query port, which can cause connection-errors. + * Set this to true to not use that port (will cause some methods to stop working) + */ + dontUseQueryPort?: boolean + } + /** Connection options for the Secondary (Buddy) NCS-server */ + secondary?: { + /** Name (NCS ID) of the Buddy NCS-server */ + id: string + /** Host address (IP-address) of the NCS-server */ + host: string + /** (Optional): Custom ports for communication */ + ports?: { + upper: number + lower: number + query: number + } + /** (Optional) Timeout for commands (ms) */ + timeout?: number + /** (Optional) Interval for sending of hearbeats (ms) */ + heartbeatInterval?: number + + /** (Optional) Some server doesn't expose the Query port, which can cause connection-errors. + * Set this to true to not use that port (will cause some methods to stop working) + */ + dontUseQueryPort?: boolean + + /** (Optional) Treat the secondary server as a "hot standby". + * A "hot standby" is a server that is powered down / in standby while the primary server is up. + * When a server is a hot standby it is expected to be disconnected and hence we will not send + * heartbeat messages. The connection status will still be reported as disconnected however as we + * do not pretend to be connected to something that is powered down. + * + * (This was added to support the hot standby feature of OpenMedia NRCS.) + */ + openMediaHotStandby?: boolean + } +} +⋮---- +/** Connection options for the Primary NCS-server */ +⋮---- +/** Name (NCS ID) of the NCS-server */ +⋮---- +/** Host address (IP-address) of the NCS-server */ +host: string // ip-addr +/** (Optional): Custom ports for communication */ +⋮---- +/** (Optional) Timeout for commands (ms) */ +⋮---- +/** (Optional) Interval for sending of hearbeats (ms) */ +⋮---- +/** (Optional) Some server doesn't expose the Query port, which can cause connection-errors. + * Set this to true to not use that port (will cause some methods to stop working) + */ +⋮---- +/** Connection options for the Secondary (Buddy) NCS-server */ +⋮---- +/** Name (NCS ID) of the Buddy NCS-server */ +⋮---- +/** Host address (IP-address) of the NCS-server */ +⋮---- +/** (Optional): Custom ports for communication */ +⋮---- +/** (Optional) Timeout for commands (ms) */ +⋮---- +/** (Optional) Interval for sending of hearbeats (ms) */ +⋮---- +/** (Optional) Some server doesn't expose the Query port, which can cause connection-errors. + * Set this to true to not use that port (will cause some methods to stop working) + */ +⋮---- +/** (Optional) Treat the secondary server as a "hot standby". + * A "hot standby" is a server that is powered down / in standby while the primary server is up. + * When a server is a hot standby it is expected to be disconnected and hence we will not send + * heartbeat messages. The connection status will still be reported as disconnected however as we + * do not pretend to be connected to something that is powered down. + * + * (This was added to support the hot standby feature of OpenMedia NRCS.) + */ + +================ +File: packages/model/src/mosTypes/mosTime.ts +================ +import { pad } from './lib' +⋮---- +export interface IMOSTime { + _mosTime: number + _timezone: string + _timezoneOffset: number + + /** @deprecated use getMosTypes().mosTime.stringify() instead! */ + toString: never +} +⋮---- +/** @deprecated use getMosTypes().mosTime.stringify() instead! */ +⋮---- +export function create(timestamp: AnyValue, strict: boolean): IMOSTime +⋮---- +// let value: number +⋮---- +// create date from time-string or timestamp number +⋮---- +// formats: +// YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] +// Sun Feb 25 2018 08:59:08 GMT+0100 (CET) +// 2018-02-25T08:00:45.528Z +⋮---- +// parse out custom Z indicator (mos-centric) +⋮---- +// parse out custom timezones (mos local-local centric format) +⋮---- +// try to parse the time directly with Date, for Date-supported formats +⋮---- +export type AnyValue = Date | number | string | IMOSTime +⋮---- +export function validate(_mosDuration: IMOSTime, _strict: boolean): void +⋮---- +// nothing +⋮---- +export function valueOf(mosTime: IMOSTime): number +⋮---- +if (typeof mosTime === 'number') return mosTime // helpful hack +⋮---- +export function stringify(mosTime: IMOSTime): string +⋮---- +if (typeof mosTime === 'string') return mosTime // helpful hack +⋮---- +// Cheat a little bit to get the time-zone right: +// First add the local offset to get the Date to display the time in UTC, +// then add the timezoneOffset to get the time in the the correct time-zone: +⋮---- +export function is(mosTime: IMOSTime | any): mosTime is IMOSTime +export function fallback(): IMOSTime +⋮---- +function parseTimeOffset(timestamp: string): false | +⋮---- +/** */ +function parseMosCustomFormat(timestamp: string): + | false + | { + yy: string + mm: string + dd: string + hh: string + ii: string + ss: string + ms: string + timezoneIndicator: string + /** in minutes */ + timezoneOffset: number + } { + const timestampRegex = /(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)([,.](\d{3}))?(([+-Z])([:\d]+)?)?/i + const match = timestamp.match(timestampRegex) +if (match) +⋮---- +/** in minutes */ +⋮---- +const m = timezoneIndicator.match(/([+-])(\d+):(\d+)/) // +5:00, -05:00 + +================ +File: packages/examples/package.json +================ +{ + "name": "mos-examples", + "private": true, + "version": "0.0.0", + "description": "Examples for how to use MOS-connection", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/nrkno/sofie-mos-connection.git" + }, + "homepage": "https://github.com/nrkno/sofie-mos-connection#readme", + "scripts": { + "build": "run -T rimraf dist && run build:main", + "build:main": "run -T tsc -p tsconfig.build.json", + "__test": "run -T jest", + "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", + "validate:dev-dependencies": "yarn npm audit ", + "license-validate": "yarn sofie-licensecheck" + }, + "engines": { + "node": ">=14.18.0" + }, + "dependencies": { + "@mos-connection/connector": "4.2.2" + }, + "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", + "lint-staged": { + "*.{css,json,md,scss}": [ + "run -T prettier" + ], + "*.{ts,tsx,js,jsx}": [ + "run -T eslint" + ] + }, + "files": [ + "/dist", + "/CHANGELOG.md", + "/README.md", + "/LICENSE" + ], + "devDependencies": { + "@sofie-automation/code-standard-preset": "*", + "typescript": "~4.9.5" + } +} + +================ +File: packages/helper/package.json +================ +{ + "name": "@mos-connection/helper", + "version": "4.2.2", + "description": "Helper functions for the MOS-connection library", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/nrkno/sofie-mos-connection.git" + }, + "bugs": { + "url": "https://github.com/nrkno/sofie-mos-connection/issues" + }, + "homepage": "https://github.com/nrkno/sofie-mos-connection#readme", + "scripts": { + "build": "run -T rimraf dist && run build:main", + "build:main": "run -T tsc -p tsconfig.build.json", + "test": "run -T jest", + "test:coverage": "yarn test --coverage", + "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", + "validate:dev-dependencies": "yarn npm audit ", + "license-validate": "yarn sofie-licensecheck" + }, + "files": [ + "/dist", + "/CHANGELOG.md", + "/README.md", + "/LICENSE" + ], + "dependencies": { + "@mos-connection/model": "4.2.2", + "iconv-lite": "^0.6.3", + "tslib": "^2.5.3", + "xml-js": "^1.6.11", + "xmlbuilder": "^15.1.1" + }, + "keywords": [ + "mos", + "broadcast", + "socket", + "typescript", + "javascript", + "open", + "source", + "automation", + "ncs", + "nrcs", + "rundown", + "tv-production", + "sofie" + ], + "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", + "engines": { + "node": ">=14.18.0" + }, + "lint-staged": { + "*.{css,json,md,scss}": [ + "run -T prettier" + ], + "*.{ts,tsx,js,jsx}": [ + "run -T eslint" + ] + }, + "devDependencies": { + "@sofie-automation/code-standard-preset": "*", + "typescript": "~4.9.5" + } +} + +================ +File: packages/model/package.json +================ +{ + "name": "@mos-connection/model", + "version": "4.2.2", + "description": "Types and enums for the mos-connection library", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/nrkno/sofie-mos-connection.git" + }, + "homepage": "https://github.com/nrkno/sofie-mos-connection#readme", + "scripts": { + "build": "run -T rimraf dist && run build:main", + "build:main": "run -T tsc -p tsconfig.build.json", + "test": "run -T jest", + "test:coverage": "yarn test --coverage", + "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", + "validate:dev-dependencies": "yarn npm audit ", + "license-validate": "yarn sofie-licensecheck" + }, + "engines": { + "node": ">=14.18.0" + }, + "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", + "lint-staged": { + "*.{css,json,md,scss}": [ + "run -T prettier" + ], + "*.{ts,tsx,js,jsx}": [ + "run -T eslint" + ] + }, + "files": [ + "/dist", + "/CHANGELOG.md", + "/README.md", + "/LICENSE" + ], + "devDependencies": { + "@sofie-automation/code-standard-preset": "*", + "typescript": "~4.9.5" + } +} + +================ +File: lerna.json +================ +{ + "packages": [ + "packages/*" + ], + "version": "4.2.2", + "npmClient": "yarn", + "useWorkspaces": true +} + +================ +File: packages/connector/src/connection/mosSocketClient.ts +================ +import { EventEmitter } from 'eventemitter3' +import { Socket } from 'net' +import { SocketConnectionEvent } from './socketConnection' +import { MosModel } from '@mos-connection/helper' +import { DEFAULT_COMMAND_TIMEOUT, HandedOverQueue } from './NCSServerConnection' +⋮---- +import { ParsedMosMessage, MosMessageParser } from './mosMessageParser' +⋮---- +export type CallBackFunction = (data: { error: Error | string } | { reply: ParsedMosMessage }) => void +⋮---- +export interface QueueMessage { + time: number + msg: MosModel.MosMessage +} +⋮---- +export interface MosSocketClientEvents { + [SocketConnectionEvent.CONNECTED]: () => void + [SocketConnectionEvent.DISCONNECTED]: () => void + [SocketConnectionEvent.DISPOSED]: () => void + [SocketConnectionEvent.ALIVE]: () => void + // [SocketConnectionEvent.TIMEOUT]: () => void + // [SocketConnectionEvent.REGISTER]: () => void + // [SocketConnectionEvent.UNREGISTER]: () => void + + error: (error: Error) => void + warning: (warning: string) => void + rawMessage: (...args: any[]) => void +} +⋮---- +// [SocketConnectionEvent.TIMEOUT]: () => void +// [SocketConnectionEvent.REGISTER]: () => void +// [SocketConnectionEvent.UNREGISTER]: () => void +⋮---- +export class MosSocketClient extends EventEmitter +⋮---- +private _lingeringCallback: { [messageId: string]: CallBackFunction } = {} // for lingering messages +⋮---- +private _sentMessage: QueueMessage | null = null // sent message, waiting for reply +⋮---- +private _lingeringMessage: QueueMessage | null = null // sent message, NOT waiting for reply +// private _readyToSendMessage: boolean = true +⋮---- +// private _startingUp: boolean = true +⋮---- +/** */ +constructor(host: string, port: number, description: string, timeout: number, debug: boolean, strict: boolean) +⋮---- +/** */ +set autoReconnect(autoReconnect: boolean) +⋮---- +/** */ +set autoReconnectInterval(autoReconnectInterval: number) +⋮---- +/** */ +set autoReconnectAttempts(autoReconnectAttempts: number) +⋮---- +/** */ +connect(): void +⋮---- +// prevent manipulation of active socket +⋮---- +// throttling attempts +⋮---- +// !_lastReconnectionAttempt (means first attempt) OR time > _reconnectionDelay since last attempt +// recreate client if new attempt: +⋮---- +// (re)create client, either on first run or new attempt: +⋮---- +// connect: +⋮---- +// set timer to retry when needed: +⋮---- +/** */ +disconnect(): void +⋮---- +queueCommand(message: MosModel.MosMessage, cb: CallBackFunction, time?: number): void +⋮---- +// this.debugTrace('queueing', message.messageID, message.constructor.name ) +⋮---- +processQueue(): void +⋮---- +// Send the message: +⋮---- +// The queue is empty, do nothing +⋮---- +// Try again later: +⋮---- +/** + * Returns a queue of messages to be executed by a different connection. + * Will exclude hearbeats from the returned queue. The heartbeats must stay inside + * the internal queue because they are needed for the connection lifecycle. + */ +handOverQueue(): HandedOverQueue +⋮---- +// Temporary hack, to allow heartbeats to be received after a handover: +⋮---- +/** */ +get host(): string +⋮---- +/** */ +get port(): number +⋮---- +/** */ +dispose(): void +⋮---- +// this._readyToSendMessage = false +⋮---- +/** + * convenience wrapper to expose all logging calls to parent object + */ +log(...args: any[]): void +public setDebug(debug: boolean): void +/** */ +private set connected(connected: boolean) +⋮---- +/** */ +private get connected(): boolean +⋮---- +private _sendReply(messageId: number, response: +⋮---- +// this._onUnhandledCommandTimeout() +⋮---- +/** */ +private executeCommand(message: QueueMessage, isRetry?: boolean): void +⋮---- +// Command timeout: +⋮---- +/** */ +private _autoReconnectionAttempt(): void +⋮---- +// no reconnection if no valid reconnectionAttemps is set +⋮---- +// if current attempt is not less than max attempts +// reset reconnection behaviour +⋮---- +// new attempt if not allready connected +⋮---- +/** */ +private _clearConnectionAttemptTimer(): void +⋮---- +/** */ +private _onConnected() +⋮---- +/** */ +private _onData(data: Buffer) +⋮---- +private _handleMessage(parsedData: ParsedMosMessage, messageString: string) +⋮---- +// huh, we've got a reply to something we've not sent. +⋮---- +// error message? +⋮---- +// unknown message.. +⋮---- +// this._readyToSendMessage = true +⋮---- +private _getMessageId(parsedData: ParsedMosMessage, messageString: string): string | undefined +⋮---- +// If there is a messageID: +⋮---- +// In non-strict mode: handle special cases: +⋮---- +// response doesn't contain messageId (compliant with MOS version 2.8) +// we can assume it's the same as our sent message: +⋮---- +// response doesn't contain messageId (compliant with MOS version 2.8) +// we can assume it's the same as our sent message: +⋮---- +/** */ +private _onError(error: Error) +⋮---- +// dispatch error!!!!! +⋮---- +/** */ +private _onClose(hadError: boolean) +⋮---- +// this._readyToSendMessage = false +⋮---- +private _triggerQueueCleanup() +⋮---- +// in case we're in unsync with messages, prevent deadlock: +⋮---- +private debugTrace(...args: any[]) +⋮---- +// eslint-disable-next-line no-console + +================ +File: packages/connector/package.json +================ +{ + "name": "@mos-connection/connector", + "version": "4.2.2", + "description": "MOS compliant TCP/IP Socket connection.", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/nrkno/sofie-mos-connection.git" + }, + "bugs": { + "url": "https://github.com/nrkno/sofie-mos-connection/issues" + }, + "homepage": "https://github.com/nrkno/sofie-mos-connection#readme", + "scripts": { + "build": "run -T rimraf dist && run build:main", + "build:main": "run -T tsc -p tsconfig.build.json", + "test": "run -T jest", + "test:coverage": "yarn test --coverage", + "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", + "validate:dev-dependencies": "yarn npm audit ", + "license-validate": "yarn sofie-licensecheck" + }, + "files": [ + "/dist", + "/CHANGELOG.md", + "/README.md", + "/LICENSE" + ], + "dependencies": { + "@mos-connection/helper": "4.2.2", + "@mos-connection/model": "4.2.2", + "eventemitter3": "^5.0.1", + "iconv-lite": "^0.6.3", + "tslib": "^2.5.3", + "xml-js": "^1.6.11", + "xmlbuilder": "^15.1.1" + }, + "keywords": [ + "mos", + "broadcast", + "socket", + "typescript", + "javascript", + "open", + "source", + "automation", + "ncs", + "nrcs", + "rundown", + "tv-production", + "sofie" + ], + "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", + "engines": { + "node": ">=14.18.0" + }, + "lint-staged": { + "*.{css,json,md,scss}": [ + "run -T prettier" + ], + "*.{ts,tsx,js,jsx}": [ + "run -T eslint" + ] + }, + "devDependencies": { + "@sofie-automation/code-standard-preset": "*", + "typescript": "~4.9.5" + } +} + +================ +File: packages/quick-mos/package.json +================ +{ + "name": "quick-mos", + "version": "0.0.0", + "private": true, + "description": "Read rundowns from files, use mos-connection and send mos commands", + "main": "src/index.ts", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/nrkno/sofie-mos-connection.git" + }, + "homepage": "https://github.com/nrkno/sofie-mos-connection#readme", + "author": { + "name": "Johan Nyman", + "email": "johan@superfly.tv", + "url": "http://superfly.tv" + }, + "contributors": [], + "scripts": { + "build": "run -T rimraf dist && run build:main", + "buildstart": "run build && run start", + "buildinspect": "run build && run inspect", + "build:main": "run -T tsc -p tsconfig.json", + "test": "run -T jest", + "inspect": "node --inspect dist/index.js", + "start": "ts-node src/index.ts", + "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", + "validate:dev-dependencies": "yarn npm audit ", + "license-validate": "yarn sofie-licensecheck" + }, + "engines": { + "node": ">=14.18.0" + }, + "devDependencies": { + "@sofie-automation/code-standard-preset": "*", + "@types/node": "^20.10.5", + "@types/underscore": "^1.11.5", + "ts-node": "^10.9.1", + "typescript": "~4.9.5" + }, + "dependencies": { + "@mos-connection/connector": "4.2.2", + "@mos-connection/model": "4.2.2", + "chokidar": "^3.5.3", + "fast-clone": "^1.5.13", + "tslib": "^2.5.3", + "underscore": "^1.13.6" + }, + "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", + "lint-staged": { + "*.{css,json,md,scss}": [ + "run -T prettier" + ], + "*.{ts,tsx,js,jsx}": [ + "run -T eslint" + ] + }, + "files": [ + "/dist", + "/CHANGELOG.md", + "/README.md", + "/LICENSE" + ] +} + +================ +File: packages/connector/src/connection/NCSServerConnection.ts +================ +import { ConnectionType } from './socketConnection' +import { MosSocketClient, CallBackFunction, QueueMessage } from '../connection/mosSocketClient' +import { MosModel } from '@mos-connection/helper' +import { EventEmitter } from 'eventemitter3' +import { ParsedMosMessage } from './mosMessageParser' +⋮---- +export interface ClientDescription { + useHeartbeats: boolean + heartbeatConnected: boolean + client: MosSocketClient + clientDescription: MosModel.PortType +} +⋮---- +export interface INCSServerConnection { + on(event: 'rawMessage', listener: (type: string, message: string) => void): this +} +⋮---- +on(event: 'rawMessage', listener: (type: string, message: string) +⋮---- +export interface HandedOverQueue { + messages: QueueMessage[] + callbacks: { [messageId: string]: CallBackFunction } +} +export interface NCSServerConnectionEvents { + rawMessage: (...args: any[]) => void + warning: (str: string) => void + error: (err: Error) => void + info: (str: string) => void + connectionChanged: () => void +} +⋮---- +/** Handles connections to a NCS (server) */ +export class NCSServerConnection extends EventEmitter implements INCSServerConnection +⋮---- +// private _lastSeen: number +⋮---- +constructor( + id: string, + host: string, + mosID: string, + timeout: number | undefined, + heartbeatsInterval: number | undefined, + debug: boolean, + strict: boolean +) +get timeout(): number +/** Create a MOS client, which talks to */ +createClient(clientID: string, port: number, clientDescription: ConnectionType, useHeartbeats: boolean): void +⋮---- +/** */ +removeClient(clientID: string): void +⋮---- +/** */ +disableHeartbeats(): void +⋮---- +/** */ +enableHeartbeats(): void +⋮---- +/** */ +isHearbeatEnabled(): boolean +⋮---- +setAutoReconnectInterval(interval: number): void +⋮---- +connect(): void +⋮---- +// Connect client +⋮---- +// Send heartbeat and check connection +⋮---- +// Emit to _callbackOnConnectionChange +// if (this._callbackOnConnectionChange) this._callbackOnConnectionChange() +⋮---- +/** + * Sends a mos message. + * Returns a Promise which resolves when a MOS reply has been received. + */ +async executeCommand(message: MosModel.MosMessage): Promise +⋮---- +// Fill with clients +⋮---- +// Set mosID and ncsID +⋮---- +// Example: Port based on message type +⋮---- +public setDebug(debug: boolean): void +get connected(): boolean +getConnectedStatus(): +⋮---- +private _getClients(clientDescription: string): MosSocketClient[] +/** */ +get lowerPortClients(): MosSocketClient[] +⋮---- +/** */ +get upperPortClients(): MosSocketClient[] +⋮---- +/** */ +get queryPortClients(): MosSocketClient[] +get host(): string +get id(): string +⋮---- +handOverQueue(otherConnection: NCSServerConnection): void +⋮---- +// this._clients.forEach((client, id) => { +// // cmds[id] = client.client.handOverQueue() +// }) +⋮---- +receiveQueue(queue: +⋮---- +// @todo: keep order +// @todo: prevent callback-promise horror... +⋮---- +async dispose(): Promise +⋮---- +private _sendHeartBeats(): void +⋮---- +const triggerNextHeartBeat = () => +⋮---- +// probably a timeout +⋮---- +private debugTrace(...strs: any[]) +⋮---- +// eslint-disable-next-line no-console + +================ +File: packages/connector/src/MosConnection.ts +================ +import { Socket } from 'net' +import { ConnectionConfig } from './config/connectionConfig' +import { MosSocketServer } from './connection/mosSocketServer' +import { getMosTypes, IMOSAckStatus, IProfiles, MosTypes } from '@mos-connection/model' +import { MosDevice } from './MosDevice' +import { SocketServerEvent, SocketDescription, IncomingConnectionType } from './connection/socketConnection' +import { NCSServerConnection } from './connection/NCSServerConnection' +import { MosModel } from '@mos-connection/helper' +import { EventEmitter } from 'eventemitter3' +⋮---- +import { ParsedMosMessage, MosMessageParser } from './connection/mosMessageParser' +import { IConnectionConfig, IMosConnection, IMOSDeviceConnectionOptions } from './api' +import { PROFILE_VALIDNESS_CHECK_WAIT_TIME } from './lib' +⋮---- +export interface MosConnectionEvents { + // Note: These match the events defined in IMosConnection + rawMessage: (source: string, type: string, message: string) => void + info: (message: string, data?: any) => void + warning: (message: string) => void + error: (error: Error) => void +} +⋮---- +// Note: These match the events defined in IMosConnection +⋮---- +export class MosConnection extends EventEmitter implements IMosConnection +⋮---- +// private _isListening: Promise +⋮---- +/** */ +constructor(configOptions: IConnectionConfig) +⋮---- +// Setup utility functions for handling MosTypes: +⋮---- +/** + * Initiate the MosConnection, start accepting connections + */ +async init(): Promise +⋮---- +/** + * Establish a new connection to a MOS-device (NCS-server). When established, the new MOS-device will be emitted to this.onConnection() + * @param connectionOptions Connection options + */ +async connect(connectionOptions: IMOSDeviceConnectionOptions): Promise +⋮---- +// Connect to MOS-device: +⋮---- +// Create the secondary connection if configured +⋮---- +// For hot-standby, initially we need both connections to be able to establish +// so we can determine which one is active +⋮---- +// Check both connections on startup to determine which one to use +⋮---- +// Wait for initial connection attempts +⋮---- +// Handle that .openMediaHotStandby should not check for heartbeats on +// the secondary connection when the primary is connected +// And disable heartbeats on the primary when the primary is disconnected +⋮---- +// Initially disable heartbeats on secondary since primary is connected +⋮---- +// Primary is down, but secondary is available - use secondary +⋮---- +// Both are down initially, but try both +⋮---- +// Failover handling: +⋮---- +// Refresh connectionstatus +⋮---- +// Primary disconnected, enable secondary heartbeats +⋮---- +// Refresh connectionstatus +⋮---- +// Secondary is active when primary is down +⋮---- +// Both are down, try to connect to both +⋮---- +/** Callback is called when a new connection is established */ +onConnection(cb: (mosDevice: MosDevice) => void): void +/** True if mosConnection is listening for connections */ +get isListening(): boolean +⋮---- +/** TO BE IMPLEMENTED: True if mosConnection is mos-compliant */ +get isCompliant(): boolean +⋮---- +/** True if mosConnection is configured to accept connections */ +get acceptsConnections(): boolean +⋮---- +/** A list of the profiles mosConnection is currently configured to use */ +get profiles(): IProfiles +⋮---- +/** Close all connections and clear all data */ +async dispose(): Promise +/** Return a specific MOS-device */ +getDevice(id: string): MosDevice +/** Get a list of all MOS-devices */ +getDevices(): Array +disposeMosDevice(mosDevice: MosDevice): Promise +disposeMosDevice(myMosID: string, theirMosId0: string, theirMosId1: string | null): Promise +async disposeMosDevice( + myMosIDOrMosDevice: string | MosDevice, + theirMosId0?: string, + theirMosId1?: string | null +): Promise +⋮---- +// myMosID = myMosIDOrMosDevice +⋮---- +/** + * Do a check if the profile is valid. Throws if not. + * Optionally called after a mosConnection has been set up to ensure that all callbacks have been set up properly. + */ +checkProfileValidness(): void +⋮---- +/** TO BE IMPLEMENTED */ +get complianceText(): string +public setDebug(debug: boolean): void +private _registerMosDevice( + myMosID: string, + theirMosId0: string, + theirMosId1: string | null, + primary: NCSServerConnection | null, + secondary: NCSServerConnection | null +): MosDevice +⋮---- +// Add mosDevice to register: +⋮---- +// emit to .onConnection: +⋮---- +/** Set up TCP-server */ +private async _initiateIncomingConnections(): Promise +⋮---- +const initSocket = (port: number, portType: IncomingConnectionType) => +⋮---- +// handle error +⋮---- +const handleListen = async (socketServer: MosSocketServer) => +⋮---- +// All sockets are open and listening at this point +⋮---- +/** */ +private _registerIncomingClient(client: SocketDescription) +⋮---- +// messageParser.debug = this._debug +⋮---- +// Handle incoming data +⋮---- +// handles socket listeners +client.socket.on('close', (/*hadError: boolean*/) => { +⋮---- +const handleMessage = async (parsed: ParsedMosMessage, _messageString: string) => +⋮---- +const sendReply = (message: MosModel.MosMessage) => +⋮---- +// No MOS-device found in the register +// Register a new mosDevice to use for this connection: +⋮---- +// Setup a "primary" connection back to the mos-device, so that we can automatically +// send commands to it through the mosDevice +⋮---- +// Try to add the main mos message key to the error: +⋮---- +// Something went wrong +⋮---- +// Internal/parsing error +⋮---- +// Log error: +⋮---- +// reply with NACK: +// TODO: implement ACK +// https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS_Protocol_Version_2.8.5_Final.htm#mosAck +⋮---- +sendReply(msg) // TODO: Need tests +⋮---- +// No MOS-device found in the register +⋮---- +// We can't handle the message, reply with a NACK: +⋮---- +sendReply(msg) // TODO: Need tests +⋮---- +// Register this socket: +⋮---- +/** Close socket and clean up */ +private _disposeIncomingSocket(socketID: string) +/** Get new unique id */ +static get nextSocketID(): string +private debugTrace(...strs: any[]) +⋮---- +// eslint-disable-next-line no-console +⋮---- +private _scheduleCheckProfileValidness(orgStack: Error): void +⋮---- +// eslint-disable-next-line no-console +⋮---- +/** + * Checks that all callbacks have been set up properly, according to which MOS-profile have been set in the options. + * throws if something's wrong + */ +private _checkProfileValidness(orgStack: Error): void +⋮---- +const fixError = (message: string) => +⋮---- +// Change the stack of the error, so that it points to the original call to the MosDevice: + +================ +File: packages/connector/src/MosDevice.ts +================ +import { Socket } from 'net' +import { NCSServerConnection } from './connection/NCSServerConnection' +⋮---- +import { + IMOSObject, + IMOSRunningOrder, + IMOSRunningOrderBase, + IMOSRunningOrderStatus, + IMOSStoryStatus, + IMOSItemStatus, + IMOSROReadyToAir, + IMOSStoryAction, + IMOSItem, + IMOSROAction, + IMOSROStory, + IMOSItemAction, + IMOSROFullStory, + IMOSROAck, + IMOSAckStatus, + IMOSObjectStatus, + IMOSObjectList, + IMOSRequestObjectList, + IMOSListSearchableSchema, + IMOSAck, + MosItemReplaceOptions, + IMOSExternalMetaData, + IMOSListMachInfo, + IMOSDefaultActiveX, + IMOSString128, + IMOSTime, + getMosTypes, + MosTypes, + AnyXMLObject, + AnyXMLValueSingular, + IMOSObjectAirStatus, +} from '@mos-connection/model' +import { MosModel, MosReplyError } from '@mos-connection/helper' +import { IConnectionConfig, IMOSConnectionStatus, IMOSDevice } from './api' +import { PROFILE_VALIDNESS_CHECK_WAIT_TIME, has, safeStringify } from './lib' +import { ParsedMosMessage } from './connection/mosMessageParser' +import { XMLMosListSearchableSchema, XMLMosObjectList, isXMLObject } from '@mos-connection/helper/dist/mosModel' +⋮---- +export class MosDevice implements IMOSDevice +⋮---- +// private _host: string +⋮---- +} // Use same names as IProfiles? +⋮---- +/** If set, will do more checks that mos-protocol is properly implemented */ +⋮---- +// Callbacks for Profile 0: +⋮---- +// Callbacks for Profile 1: +⋮---- +// Callbacks for Profile 2: +⋮---- +// Callbacks for Profile 3: +⋮---- +// Callbacks for Profile 4: +⋮---- +constructor( + idPrimary: string, + idSecondary: string | null, + connectionConfig: IConnectionConfig, + primaryConnection: NCSServerConnection | null, + secondaryConnection: NCSServerConnection | null, + offSpecFailover: boolean, + strict: boolean +) +⋮---- +// this._id = this.mosTypes.mosString128.create(connectionConfig.mosID).toString() +⋮---- +// Add params to this in MosConnection/MosDevice +⋮---- +this.hwRev = this.mosTypes.mosString128.create('0.1') // empty string returnes +⋮---- +this.switchConnections() // and hope no current message goes lost +⋮---- +/** True if MOS-device has connection to server (can send messages) */ +get hasConnection(): boolean +/** Primary ID (probably the NCS-ID) */ +get idPrimary(): string +/** Secondary / Buddy ID (probably the MOS-ID) */ +get idSecondary(): string | null +/** Host name (IP-address) of the primary server */ +get primaryHost(): string | null +/** Name (ID) of the primary server */ +get primaryId(): string | null +/** Host name (IP-address) of the secondary (buddy) server */ +get secondaryHost(): string | null +/** Name (ID) of the secondary (buddy) server */ +get secondaryId(): string | null +⋮---- +connect(): void +async dispose(): Promise +⋮---- +async routeData(data: AnyXMLObject, port: MosModel.PortType): Promise +⋮---- +// Suppress console spam: +⋮---- +// this.debugTrace('keys', Object.keys(data)) +⋮---- +// Route and format data: +// Profile 0: ------------------------------------------------------------------------------------------------- +⋮---- +// send immediate reply on the same port: +⋮---- +// Profile 1: ------------------------------------------------------------------------------------------------- +⋮---- +// 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 +⋮---- +// const firstObject = mosObjects.shift() as IMOSObject +// const resp = new MosObj(firstObject) +// resolve(resp) +const sendNextObject = () => +⋮---- +// eslint-disable-next-line no-console +⋮---- +// eslint-disable-next-line no-console +⋮---- +// What this should contain isn't well defined in the protocol +⋮---- +// Profile 2: ------------------------------------------------------------------------------------------------- +// Translate deprecated messages into the functionally equivalent roElementActions: +⋮---- +// This is equivalent to inserting a story at the end of the running order +⋮---- +// From documentation: +// **Note**: If the second tag is blank move the story to the bottom of the Running Order. +⋮---- +// 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: +⋮---- +// Non strict mode: This is technically out of spec, but it's a common mistake, so we'll handle it like so: +⋮---- +storyID: roStorySwap.storyID, // an array +⋮---- +// element_target: { +// storyID: roStoryDelete.storyID[1], +// }, +⋮---- +// 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. +⋮---- +// Technically a no-op: +⋮---- +// 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) +⋮---- +// An additional validation checking the length of items can be added +⋮---- +// RO not found +⋮---- +resp.Stories = [] // dont return these (?) +⋮---- +// Profile 3: ------------------------------------------------------------------------------------------------- +⋮---- +// Profile 4: ------------------------------------------------------------------------------------------------- +⋮---- +// TODO: Use reject if function dont exists? Put Nack in ondata +⋮---- +// Unsupported messages: -------------------------------------------------------------------------------------- +⋮---- +ID: this.mosTypes.mosString128.create('0'), // TODO: Depends on type of message, needs logic +⋮---- +// ============================================================================================================ +// ========================== Profile 0 =================================================================== +// ============================================================================================================ +async requestMachineInfo(): Promise +⋮---- +onRequestMachineInfo(cb: () => Promise): void +⋮---- +onConnectionChange(cb: (connectionStatus: IMOSConnectionStatus) => void): void +⋮---- +getConnectionStatus(): IMOSConnectionStatus +⋮---- +// Deprecated methods: +/** @deprecated getMachineInfo is deprecated, use requestMachineInfo instead */ +async getMachineInfo(): Promise +/** @deprecated onGetMachineInfo is deprecated, use onRequestMachineInfo instead */ +onGetMachineInfo(cb: () => Promise): void +⋮---- +// ============================================================================================================ +// ========================== Profile 1 =================================================================== +// ============================================================================================================ +async sendMOSObject(obj: IMOSObject): Promise +⋮---- +onRequestMOSObject(cb: (objId: string) => Promise): void +⋮---- +async sendRequestMOSObject(objID: IMOSString128): Promise +⋮---- +onRequestAllMOSObjects(cb: () => Promise>): void +onMOSObjects(cb: (objs: IMOSObject[]) => Promise): void +⋮---- +async sendRequestAllMOSObjects(pause = 0): Promise +⋮---- +// 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 + */ +private async _sendAllMOSObjects(objs: IMOSObject[]): Promise +⋮---- +/** + * @deprecated getMOSObject is deprecated, use sendRequestMOSObject instead + */ +async getMOSObject(objId: IMOSString128): Promise +/** @deprecated getAllMOSObjects is deprecated, use sendRequestAllMOSObjects instead */ +async getAllMOSObjects(): Promise +⋮---- +// ============================================================================================================ +// ========================== Profile 2 =================================================================== +// ============================================================================================================ +onCreateRunningOrder(cb: (ro: IMOSRunningOrder) => Promise): void +async sendCreateRunningOrder(ro: IMOSRunningOrder): Promise +⋮---- +onReplaceRunningOrder(cb: (ro: IMOSRunningOrder) => Promise): void +async sendReplaceRunningOrder(ro: IMOSRunningOrder): Promise +⋮---- +onDeleteRunningOrder(cb: (runningOrderId: IMOSString128) => Promise): void +async sendDeleteRunningOrder(runningOrderId: IMOSString128): Promise +⋮---- +onRequestRunningOrder(cb: (runningOrderId: IMOSString128) => Promise): void +⋮---- +async sendRequestRunningOrder(runningOrderId: IMOSString128): Promise +/** + * @deprecated getRunningOrder is deprecated, use sendRequestRunningOrder instead + */ +async getRunningOrder(runningOrderId: IMOSString128): Promise +⋮---- +onMetadataReplace(cb: (metadata: IMOSRunningOrderBase) => Promise): void +async sendMetadataReplace(metadata: IMOSRunningOrderBase): Promise +⋮---- +onRunningOrderStatus(cb: (status: IMOSRunningOrderStatus) => Promise): void +⋮---- +onStoryStatus(cb: (status: IMOSStoryStatus) => Promise): void +onItemStatus(cb: (status: IMOSItemStatus) => Promise): void +⋮---- +/** @deprecated setRunningOrderStatus is deprecated, use sendRunningOrderStatus instead */ +async setRunningOrderStatus(status: IMOSRunningOrderStatus): Promise +/** @deprecated setStoryStatus is deprecated, use sendStoryStatus instead */ +async setStoryStatus(status: IMOSStoryStatus): Promise +/** @deprecated setItemStatus is deprecated, use sendItemStatus instead */ +async setItemStatus(status: IMOSItemStatus): Promise +⋮---- +async sendRunningOrderStatus(status: IMOSRunningOrderStatus): Promise +⋮---- +async sendStoryStatus(status: IMOSStoryStatus): Promise +async sendItemStatus(status: IMOSItemStatus): Promise +onReadyToAir(cb: (Action: IMOSROReadyToAir) => Promise): void +async sendReadyToAir(Action: IMOSROReadyToAir): Promise +onROInsertStories(cb: (Action: IMOSStoryAction, Stories: Array) => Promise): void +async sendROInsertStories(Action: IMOSStoryAction, Stories: Array): Promise +onROInsertItems(cb: (Action: IMOSItemAction, Items: Array) => Promise): void +async sendROInsertItems(Action: IMOSItemAction, Items: Array): Promise +onROReplaceStories(cb: (Action: IMOSStoryAction, Stories: Array) => Promise): void +async sendROReplaceStories(Action: IMOSStoryAction, Stories: Array): Promise +onROReplaceItems(cb: (Action: IMOSItemAction, Items: Array) => Promise): void +async sendROReplaceItems(Action: IMOSItemAction, Items: Array): Promise +onROMoveStories(cb: (Action: IMOSStoryAction, Stories: Array) => Promise): void +async sendROMoveStories(Action: IMOSStoryAction, Stories: Array): Promise +onROMoveItems(cb: (Action: IMOSItemAction, Items: Array) => Promise): void +async sendROMoveItems(Action: IMOSItemAction, Items: Array): Promise +onRODeleteStories(cb: (Action: IMOSROAction, Stories: Array) => Promise): void +async sendRODeleteStories(Action: IMOSROAction, Stories: Array): Promise +onRODeleteItems(cb: (Action: IMOSStoryAction, Items: Array) => Promise): void +async sendRODeleteItems(Action: IMOSStoryAction, Items: Array): Promise +onROSwapStories( + cb: (Action: IMOSROAction, StoryID0: IMOSString128, StoryID1: IMOSString128) => Promise +): void +async sendROSwapStories( + Action: IMOSROAction, + StoryID0: IMOSString128, + StoryID1: IMOSString128 +): Promise +onROSwapItems( + cb: (Action: IMOSStoryAction, ItemID0: IMOSString128, ItemID1: IMOSString128) => Promise +): void +async sendROSwapItems(Action: IMOSStoryAction, ItemID0: IMOSString128, ItemID1: IMOSString128): Promise +⋮---- +// ============================================================================================================ +// ========================== Profile 3 =================================================================== +// ============================================================================================================ +onObjectCreate(cb: (object: IMOSObject) => Promise): void +⋮---- +async sendObjectCreate(object: IMOSObject): Promise +⋮---- +onItemReplace(cb: (roID: IMOSString128, storyID: IMOSString128, item: IMOSItem) => Promise): void +⋮---- +async sendItemReplace(options: MosItemReplaceOptions): Promise +⋮---- +onRequestSearchableSchema(cb: (username: string) => Promise): void +⋮---- +async sendRequestSearchableSchema(username: string): Promise +⋮---- +onRequestObjectList(cb: (objList: IMOSRequestObjectList) => Promise): void +⋮---- +async sendRequestObjectList(reqObjList: IMOSRequestObjectList): Promise +⋮---- +onRequestObjectActionNew(cb: (obj: IMOSObject) => Promise): void +async sendRequestObjectActionNew(obj: IMOSObject): Promise +⋮---- +onRequestObjectActionUpdate(cb: (objId: IMOSString128, obj: IMOSObject) => Promise): void +async sendRequestObjectActionUpdate(objId: IMOSString128, obj: IMOSObject): Promise +onRequestObjectActionDelete(cb: (objId: IMOSString128) => Promise): void +async sendRequestObjectActionDelete(objId: IMOSString128): Promise +⋮---- +// Deprecated methods: +/** @deprecated onMosObjCreate is deprecated, use onObjectCreate instead */ +onMosObjCreate(cb: (object: IMOSObject) => Promise): void +/** @deprecated mosObjCreate is deprecated, use sendObjectCreate instead */ +async mosObjCreate(object: IMOSObject): Promise +/** @deprecated onMosItemReplace is deprecated, use onItemReplace instead */ +onMosItemReplace(cb: (roID: IMOSString128, storyID: IMOSString128, item: IMOSItem) => Promise): void +/** @deprecated mosItemReplace is deprecated, use sendItemReplace instead */ +async mosItemReplace(options: MosItemReplaceOptions): Promise +/** @deprecated onMosReqSearchableSchema is deprecated, use onRequestSearchableSchema instead */ +onMosReqSearchableSchema(cb: (username: string) => Promise): void +/** @deprecated mosRequestSearchableSchema is deprecated, use sendRequestSearchableSchema instead */ +async mosRequestSearchableSchema(username: string): Promise +/** @deprecated onMosReqObjectList is deprecated, use onRequestObjectList instead */ +onMosReqObjectList(cb: (objList: IMOSRequestObjectList) => Promise): void +/** @deprecated mosRequestObjectList is deprecated, use sendRequestObjectList instead */ +async mosRequestObjectList(reqObjList: IMOSRequestObjectList): Promise +/** @deprecated onMosReqObjectAction is deprecated, use onRequestObjectAction*** instead */ +onMosReqObjectAction(_cb: (action: string, obj: IMOSObject) => Promise): void +⋮---- +// ============================================================================================================ +// ========================== Profile 4 =================================================================== +// ============================================================================================================ +onRequestAllRunningOrders(cb: () => Promise): void +async sendRequestAllRunningOrders(): Promise> +onRunningOrderStory(cb: (story: IMOSROFullStory) => Promise): void +async sendRunningOrderStory(story: IMOSROFullStory): Promise +⋮---- +// Deprecated methods: +/** @deprecated onROReqAll is deprecated, use onRequestAllRunningOrders instead */ +onROReqAll(cb: () => Promise): void +/** @deprecated getAllRunningOrders is deprecated, use sendRequestAllRunningOrders instead */ +async getAllRunningOrders(): Promise> +/** @deprecated onROStory is deprecated, use onRunningOrderStory instead */ +onROStory(cb: (story: IMOSROFullStory) => Promise): void +/** @deprecated sendROStory is deprecated, use sendRunningOrderStory instead */ +async sendROStory(story: IMOSROFullStory): Promise +⋮---- +// ============================================================================================================= +// ///////////////////////////////////// End of Profile methods \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ +setDebug(debug: boolean): void +/** + * Do a check if the profile is valid. Throws if not. + * Optionally called after a mosDevice has been set up to ensure that all callbacks have been set up properly. + */ +checkProfileValidness(): void +⋮---- +/** Does a check if the specified profile is set, and throws otherwise */ +private checkProfile(methodName: string, profile: keyof IProfiles) +⋮---- +private async executeCommand(message: MosModel.MosMessage, resend?: boolean): Promise +private switchConnections(): void +⋮---- +// Switch: +⋮---- +otherConnection = currentConnection // former current connection +⋮---- +private async switchConnectionsAndExecuteCommand(message: MosModel.MosMessage): Promise +⋮---- +// @todo: we may deadlock if primary is down for us, but up for buddy +⋮---- +private _emitConnectionChange(): void +private handleParseReply(fcn: (strict: boolean) => T): T +⋮---- +// Try again in non-strict mode, to append a sidecar to the thrown error if possible: +⋮---- +// ignore error +⋮---- +private unknownReply(reply: MosReply) +private badRoAckReply(xmlRoAck: AnyXMLObject) +/** throws if there is an error */ +private _ensureReply(reply: ParsedMosMessage): MosReply +private _scheduleCheckProfileValidness(orgStack: Error): void +⋮---- +// eslint-disable-next-line no-console +⋮---- +/** + * Checks that all callbacks have been set up properly, according to which MOS-profile have been set in the options. + * throws if something's wrong + */ +private _checkProfileValidness(orgStack: Error): void +⋮---- +const fixError = (message: string) => +⋮---- +// Change the stack of the error, so that it points to the original call to the MosDevice: +⋮---- +/** For MOS-devices: Require a callback to have been set */ +const requireCallback = (profile: string, callbackName: string, method: (...args: any[]) => any) => +⋮---- +// @ts-expect-error no index signature +⋮---- +/** Check: Requires that a callback has been set */ +const requireMOSCallback = (profile: string, callbackName: string, method: (...args: any[]) => any) => +/** Require another profile to have been set */ +const requireProfile = (profile: number, requiredProfile: number) => +⋮---- +// @ts-expect-error no index signature +⋮---- +/* eslint-disable @typescript-eslint/unbound-method */ +⋮---- +// _callbackOnConnectionChange not required +⋮---- +private debugTrace(...strs: any[]) +⋮---- +// eslint-disable-next-line no-console +⋮---- +export interface IProfiles { + profile0: boolean + profile1: boolean + profile2: boolean + profile3: boolean + profile4: boolean + profile5: boolean + profile6: boolean + profile7: boolean +} +⋮---- +type MosReply = ParsedMosMessage + + + +================================================================ +End of Codebase +================================================================ diff --git a/yarn.lock b/yarn.lock index a00eb8ce..84f0d1ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -962,7 +962,7 @@ __metadata: languageName: node linkType: hard -"@mos-connection/connector@4.2.2, @mos-connection/connector@workspace:packages/connector": +"@mos-connection/connector@4.2.2, @mos-connection/connector@^4.2.2, @mos-connection/connector@workspace:packages/connector": version: 0.0.0-use.local resolution: "@mos-connection/connector@workspace:packages/connector" dependencies: @@ -6622,6 +6622,18 @@ __metadata: languageName: unknown linkType: soft +"mos-dummy-server@workspace:packages/mos-dummy-device": + version: 0.0.0-use.local + resolution: "mos-dummy-server@workspace:packages/mos-dummy-device" + dependencies: + "@mos-connection/connector": ^4.2.2 + "@types/node": ^20.10.5 + chokidar: ^3.5.3 + ts-node: ^10.9.2 + typescript: ^5.3.3 + languageName: unknown + linkType: soft + "mos-examples@workspace:packages/examples": version: 0.0.0-use.local resolution: "mos-examples@workspace:packages/examples" @@ -9153,6 +9165,44 @@ __metadata: languageName: node linkType: hard +"ts-node@npm:^10.9.2": + version: 10.9.2 + resolution: "ts-node@npm:10.9.2" + dependencies: + "@cspotcode/source-map-support": ^0.8.0 + "@tsconfig/node10": ^1.0.7 + "@tsconfig/node12": ^1.0.7 + "@tsconfig/node14": ^1.0.0 + "@tsconfig/node16": ^1.0.2 + acorn: ^8.4.1 + acorn-walk: ^8.1.1 + arg: ^4.1.0 + create-require: ^1.1.0 + diff: ^4.0.1 + make-error: ^1.1.1 + v8-compile-cache-lib: ^3.0.1 + yn: 3.1.1 + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + bin: + ts-node: dist/bin.js + ts-node-cwd: dist/bin-cwd.js + ts-node-esm: dist/bin-esm.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: fde256c9073969e234526e2cfead42591b9a2aec5222bac154b0de2fa9e4ceb30efcd717ee8bc785a56f3a119bdd5aa27b333d9dbec94ed254bd26f8944c67ac + languageName: node + linkType: hard + "tsconfig-paths@npm:^4.1.2": version: 4.2.0 resolution: "tsconfig-paths@npm:4.2.0" @@ -9296,6 +9346,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^5.3.3": + version: 5.8.2 + resolution: "typescript@npm:5.8.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 7f9e3d7ac15da6df713e439e785e51facd65d6450d5f51fab3e8d2f2e3f4eb317080d895480b8e305450cdbcb37e17383e8bf521e7395f8b556e2f2a4730ed86 + languageName: node + linkType: hard + "typescript@patch:typescript@^3 || ^4#~builtin, typescript@patch:typescript@~4.9.5#~builtin": version: 4.9.5 resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=289587" @@ -9306,6 +9366,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@^5.3.3#~builtin": + version: 5.8.2 + resolution: "typescript@patch:typescript@npm%3A5.8.2#~builtin::version=5.8.2&hash=5da071" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: a58d19ff9811c1764a299dd83ca20ed8020f0ab642906dafc880121b710751227201531fdc99878158205c356ac79679b0b61ac5b42eda0e28bfb180947a258d + languageName: node + linkType: hard + "uglify-js@npm:^3.1.4": version: 3.17.4 resolution: "uglify-js@npm:3.17.4" From 49f98b01409726c80e3d544e3189ed46be6d0851 Mon Sep 17 00:00:00 2001 From: olzzon Date: Mon, 31 Mar 2025 13:40:33 +0200 Subject: [PATCH 3/8] feat: initial sketch for mos-dummy-device --- packages/mos-dummy-device/README.md | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/packages/mos-dummy-device/README.md b/packages/mos-dummy-device/README.md index 49c6311e..08396b9e 100644 --- a/packages/mos-dummy-device/README.md +++ b/packages/mos-dummy-device/README.md @@ -11,42 +11,29 @@ A dummy MOS (Media Object Server) server for testing MOS clients with failover c - Command-line interface for manual testing ## Installation - -1. Clone this repository -2. Install dependencies: - -```bash -npm install -``` - -3. Build the project: +Install dependencies: ```bash -npm run build +yarn install ``` -## Usage - -### Starting the server +### Development mode +To run in development mode with automatic reloading: ```bash -npm start +yarn dev ``` -This will start the MOS server on the default ports (10540, 10541, 10542). - -### Development mode - -To run in development mode with automatic reloading: +you can add a --secondary flag to run a secondary server on a different port: ```bash -npm run dev +yarn dev --secondary ``` Or with file watching: ```bash -npm run watch +yarn run watch ``` ### Rundown Management From 8ba9774a29e851a85a675d506a65870678236e67 Mon Sep 17 00:00:00 2001 From: olzzon Date: Mon, 31 Mar 2025 15:50:15 +0200 Subject: [PATCH 4/8] fix: reference to secondary device when --secondary flag is used --- packages/mos-dummy-device/README.md | 26 ++++++++++++++ .../{defaultrundown.json => EXAMPLE_RO.json} | 0 packages/mos-dummy-device/src/index.ts | 35 +++++++++++++++---- 3 files changed, 55 insertions(+), 6 deletions(-) rename packages/mos-dummy-device/rundowns/{defaultrundown.json => EXAMPLE_RO.json} (100%) diff --git a/packages/mos-dummy-device/README.md b/packages/mos-dummy-device/README.md index 08396b9e..736ecb07 100644 --- a/packages/mos-dummy-device/README.md +++ b/packages/mos-dummy-device/README.md @@ -36,6 +36,32 @@ Or with file watching: yarn run watch ``` +### Run on same machine as client: +To run on same machine as client, remember to set the clients acceptsConnections to false (here shown in Sofie): + +```bash +mos: { + self: { + debug: debug, + // mosID: 'sofie.tv.automation', + mosID: 'N/A', // set by Core + acceptsConnections: false, // default:true + // accepsConnectionsFrom: ['127.0.0.1'], + profiles: { + '0': true, + '1': true, + '2': true, + '3': false, + '4': false, + '5': false, + '6': false, + '7': false, + }, + offspecFailover: true, + }, +``` + + ### Rundown Management Rundowns are stored as JSON files in the `rundowns` directory. The server watches this directory for changes: diff --git a/packages/mos-dummy-device/rundowns/defaultrundown.json b/packages/mos-dummy-device/rundowns/EXAMPLE_RO.json similarity index 100% rename from packages/mos-dummy-device/rundowns/defaultrundown.json rename to packages/mos-dummy-device/rundowns/EXAMPLE_RO.json diff --git a/packages/mos-dummy-device/src/index.ts b/packages/mos-dummy-device/src/index.ts index e11d0f88..b30591ab 100644 --- a/packages/mos-dummy-device/src/index.ts +++ b/packages/mos-dummy-device/src/index.ts @@ -29,6 +29,7 @@ class DummyMosServer { private rundowns: Map = new Map() private isServerOnline = true private mosTypes = getMosTypes(false) // Non-strict mode + private primaryDevice = false constructor(rundownsDir: string) { this.rundownsDir = rundownsDir @@ -54,10 +55,12 @@ class DummyMosServer { // Handle new MOS device connections private handleNewConnection(mosDevice: MosDevice): void { - console.log(`New connection from device: ${mosDevice.idPrimary}`) + console.log( + `New connection from device: ${this.primaryDevice ? mosDevice.idPrimary : mosDevice.idSecondary || ''}` + ) // Store reference to the device - this.devicesMap.set(mosDevice.idPrimary, mosDevice) + this.devicesMap.set(this.primaryDevice ? mosDevice.idPrimary : mosDevice.idSecondary || '', mosDevice) // Set up callbacks for Profile 0 mosDevice.onRequestMachineInfo(async () => { @@ -350,16 +353,36 @@ class DummyMosServer { return } - console.log(`Sending ${this.rundowns.size} rundowns to device: ${device.idPrimary}`) + console.log( + `Sending ${this.rundowns.size} rundowns to device: ${ + this.primaryDevice ? device.idPrimary : device.idSecondary || '' + }` + ) for (const [id, rundown] of this.rundowns) { try { - console.log(`Sending rundown to device ${device.idPrimary}: ${id}`) + console.log( + `Sending rundown to device ${ + this.primaryDevice ? device.idPrimary : device.idSecondary || '' + }: ${id}` + ) device .sendCreateRunningOrder(rundown) - .catch((err) => console.error(`Error sending rundown to device ${device.idPrimary}:`, err)) + .catch((err) => + console.error( + `Error sending rundown to device ${ + this.primaryDevice ? device.idPrimary : device.idSecondary || '' + }:`, + err + ) + ) } catch (error) { - console.error(`Error sending rundown to device ${device.idPrimary}:`, error) + console.error( + `Error sending rundown to device ${ + this.primaryDevice ? device.idPrimary : device.idSecondary || '' + }:`, + error + ) } } } From 160749945d00c6b1f9acf9fb8ad5b6a15745b2d6 Mon Sep 17 00:00:00 2001 From: olzzon Date: Tue, 8 Apr 2025 12:41:39 +0200 Subject: [PATCH 5/8] Revert "fix: Initial connection with hotStandby enabled, should always check both servers for availablity upon start." This reverts commit b5f46e88bd56ff988cb87e0914769143be85a4b8. --- packages/connector/src/MosConnection.ts | 98 ++++++++----------------- 1 file changed, 31 insertions(+), 67 deletions(-) diff --git a/packages/connector/src/MosConnection.ts b/packages/connector/src/MosConnection.ts index d9728faa..dcab6a88 100644 --- a/packages/connector/src/MosConnection.ts +++ b/packages/connector/src/MosConnection.ts @@ -130,12 +130,6 @@ export class MosConnection extends EventEmitter implements ) } - // Create the secondary connection if configured - const isHotStandby = connectionOptions.secondary?.openMediaHotStandby || false - // For hot-standby, initially we need both connections to be able to establish - // so we can determine which one is active - const useHeartbeatsOnSecondary = !isHotStandby || true - if (connectionOptions.secondary) { secondary = new NCSServerConnection( connectionOptions.secondary.id, @@ -164,13 +158,13 @@ export class MosConnection extends EventEmitter implements MosConnection.nextSocketID, connectionOptions.secondary.ports?.lower ?? MosConnection.CONNECTION_PORT_LOWER, 'lower', - useHeartbeatsOnSecondary + !connectionOptions.secondary.openMediaHotStandby ) secondary.createClient( MosConnection.nextSocketID, connectionOptions.secondary.ports?.upper ?? MosConnection.CONNECTION_PORT_UPPER, 'upper', - useHeartbeatsOnSecondary + !connectionOptions.secondary.openMediaHotStandby ) if (!connectionOptions.primary.dontUseQueryPort) { secondary.createClient( @@ -180,71 +174,41 @@ export class MosConnection extends EventEmitter implements false ) } - } - - if (isHotStandby && secondary) { - // Check both connections on startup to determine which one to use - this.emit('info', 'Initializing with OpenMedia hot-standby configuration') - - primary.connect() - secondary.connect() - // Wait for initial connection attempts - await new Promise((resolve) => setTimeout(resolve, 2000)) - - const primaryStatus = primary.getConnectedStatus() - const secondaryStatus = secondary.getConnectedStatus() - // Handle that .openMediaHotStandby should not check for heartbeats on // the secondary connection when the primary is connected // And disable heartbeats on the primary when the primary is disconnected - if (primaryStatus.connected) { - // Initially disable heartbeats on secondary since primary is connected + if (connectionOptions.secondary?.openMediaHotStandby) { + // Initially disable heartbeats on secondary since primary should be attempted first secondary.disableHeartbeats() primary.enableHeartbeats() - } else if (secondaryStatus.connected) { - // Primary is down, but secondary is available - use secondary - secondary.enableHeartbeats() - primary.disableHeartbeats() - } else { - // Both are down initially, but try both - this.emit('info', 'No connections established, enabling heartbeats on both') - primary.enableHeartbeats() - secondary.enableHeartbeats() - } - // Failover handling: - primary.on('connectionChanged', () => { - // Refresh connectionstatus - const status = primary.getConnectedStatus() - if (status.connected) { - this.emit('info', 'Primary connection established, switching to primary') - secondary?.disableHeartbeats() - primary.enableHeartbeats() - } else { - // Primary disconnected, enable secondary heartbeats - this.emit('info', 'Primary disconnected, enabling secondary') - secondary?.enableHeartbeats() - primary.disableHeartbeats() - } - }) - - secondary.on('connectionChanged', () => { - // Refresh connectionstatus - const primaryStatus = primary.getConnectedStatus() - const secondaryStatus = secondary.getConnectedStatus() - - if (!primaryStatus.connected && secondaryStatus.connected) { - // Secondary is active when primary is down - this.emit('info', 'Secondary active, primary down') - secondary.enableHeartbeats() - primary.disableHeartbeats() - } else if (!secondaryStatus.connected && !primaryStatus.connected) { - // Both are down, try to connect to both - this.emit('info', 'Both connections down, attempting reconnection to both') - primary.enableHeartbeats() - secondary.enableHeartbeats() - } - }) + primary.on('connectionChanged', () => { + if (primary.connected) { + secondary?.disableHeartbeats() + primary.enableHeartbeats() + } else { + secondary?.enableHeartbeats() + primary.disableHeartbeats() + } + }) + + // Handle secondary connection changes + setTimeout(() => { + secondary?.on('connectionChanged', () => { + if (!primary.connected) { + // Secondary is active + if (secondary?.connected) { + secondary.enableHeartbeats() + primary.disableHeartbeats() + } else { + // Secondary disconnected - try to re-enable primary + primary.enableHeartbeats() + secondary?.disableHeartbeats() + } + } + }) + }, 50) + } } return this._registerMosDevice( From 2d1a69ed1b4792ced5de1f84afc9034b0cb48035 Mon Sep 17 00:00:00 2001 From: olzzon Date: Thu, 10 Apr 2025 13:33:00 +0200 Subject: [PATCH 6/8] fix: in hotstandby - use secondary device upon start --- packages/connector/src/MosConnection.ts | 32 +- .../src/connection/NCSServerConnection.ts | 30 +- .../src/connection/mosSocketClient.ts | 4 + repomix-output.txt | 7165 ----------------- 4 files changed, 49 insertions(+), 7182 deletions(-) delete mode 100644 repomix-output.txt diff --git a/packages/connector/src/MosConnection.ts b/packages/connector/src/MosConnection.ts index dcab6a88..6f6f080c 100644 --- a/packages/connector/src/MosConnection.ts +++ b/packages/connector/src/MosConnection.ts @@ -40,6 +40,7 @@ export class MosConnection extends EventEmitter implements private _mosDevices: { [ncsID: string]: MosDevice } = {} private _initialized = false private _isListening = false + private _isOpenMediaHotStandby = false // private _isListening: Promise @@ -81,6 +82,7 @@ export class MosConnection extends EventEmitter implements */ async connect(connectionOptions: IMOSDeviceConnectionOptions): Promise { if (!this._initialized) throw Error('Not initialized, run .init() first!') + this._isOpenMediaHotStandby = connectionOptions.secondary?.openMediaHotStandby ?? false // Connect to MOS-device: const primary = new NCSServerConnection( @@ -90,9 +92,21 @@ export class MosConnection extends EventEmitter implements connectionOptions.primary.timeout, connectionOptions.primary.heartbeatInterval, this._debug, - this.mosTypes.strict + this.mosTypes.strict, + this._isOpenMediaHotStandby ) - let secondary: NCSServerConnection | null = null + const secondary = connectionOptions.secondary + ? new NCSServerConnection( + connectionOptions.secondary.id, + connectionOptions.secondary.host, + this._conf.mosID, + connectionOptions.secondary.timeout, + connectionOptions.secondary.heartbeatInterval, + this._debug, + this.mosTypes.strict, + this._isOpenMediaHotStandby + ) + : null this._ncsConnections[connectionOptions.primary.host] = primary primary.on('rawMessage', (type: string, message: string) => { @@ -130,16 +144,7 @@ export class MosConnection extends EventEmitter implements ) } - if (connectionOptions.secondary) { - secondary = new NCSServerConnection( - connectionOptions.secondary.id, - connectionOptions.secondary.host, - this._conf.mosID, - connectionOptions.secondary.timeout, - connectionOptions.secondary.heartbeatInterval, - this._debug, - this.mosTypes.strict - ) + if (secondary && connectionOptions.secondary) { this._ncsConnections[connectionOptions.secondary.host] = secondary secondary.on('rawMessage', (type: string, message: string) => { this.emit('rawMessage', 'secondary', type, message) @@ -506,7 +511,8 @@ export class MosConnection extends EventEmitter implements undefined, undefined, this._debug, - this.mosTypes.strict + this.mosTypes.strict, + this._isOpenMediaHotStandby ) this._ncsConnections[remoteAddress] = primary diff --git a/packages/connector/src/connection/NCSServerConnection.ts b/packages/connector/src/connection/NCSServerConnection.ts index 94c0ac09..e4901082 100644 --- a/packages/connector/src/connection/NCSServerConnection.ts +++ b/packages/connector/src/connection/NCSServerConnection.ts @@ -11,6 +11,7 @@ export interface ClientDescription { heartbeatConnected: boolean client: MosSocketClient clientDescription: MosModel.PortType + isConnected: () => boolean } export interface INCSServerConnection { @@ -47,6 +48,7 @@ export class NCSServerConnection extends EventEmitter private _heartBeatsTimer?: NodeJS.Timeout private _heartBeatsInterval: number + private _isOpenMediaHotStandby = false constructor( id: string, @@ -55,7 +57,8 @@ export class NCSServerConnection extends EventEmitter timeout: number | undefined, heartbeatsInterval: number | undefined, debug: boolean, - strict: boolean + strict: boolean, + isOpenMediaHotStandby: boolean ) { super() this._id = id @@ -66,6 +69,7 @@ export class NCSServerConnection extends EventEmitter this._connected = false this._debug = debug ?? false this._strict = strict ?? false + this._isOpenMediaHotStandby = isOpenMediaHotStandby ?? false } get timeout(): number { return this._timeout @@ -87,6 +91,9 @@ export class NCSServerConnection extends EventEmitter heartbeatConnected: false, client: client, clientDescription: clientDescription, + isConnected: () => { + return client.isConnected + }, } client.on('rawMessage', (type: string, message: string) => { this.emit('rawMessage', type, message) @@ -214,13 +221,28 @@ export class NCSServerConnection extends EventEmitter connected: false, status: 'Not connected', } + + // Check if we have any clients at all + const clientCount = Object.keys(this._clients).length + if (clientCount === 0) { + return { + connected: false, + status: 'No clients available', + } + } + + let isConnectedToSomeDevice = false + let notConnectedStatus: string | undefined = undefined Object.values(this._clients).forEach((client) => { - if (client.useHeartbeats && !client.heartbeatConnected) { + if (client.isConnected()) { + isConnectedToSomeDevice = true + } + if (client.useHeartbeats && !client.heartbeatConnected && !this._isOpenMediaHotStandby) { notConnectedStatus = `No heartbeats on port ${client.clientDescription}` } }) - if (!notConnectedStatus) { + if (!notConnectedStatus && isConnectedToSomeDevice) { return { connected: true, status: 'Connected', @@ -228,7 +250,7 @@ export class NCSServerConnection extends EventEmitter } else { return { connected: false, - status: notConnectedStatus, + status: notConnectedStatus || 'No heartbeats', } } } diff --git a/packages/connector/src/connection/mosSocketClient.ts b/packages/connector/src/connection/mosSocketClient.ts index b4e174ce..df2387b2 100644 --- a/packages/connector/src/connection/mosSocketClient.ts +++ b/packages/connector/src/connection/mosSocketClient.ts @@ -131,6 +131,10 @@ export class MosSocketClient extends EventEmitter { } } + get isConnected(): boolean { + return this._connected + } + /** */ disconnect(): void { this.dispose() diff --git a/repomix-output.txt b/repomix-output.txt deleted file mode 100644 index f3097fdc..00000000 --- a/repomix-output.txt +++ /dev/null @@ -1,7165 +0,0 @@ -This file is a merged representation of the entire codebase, combined into a single document by Repomix. -The content has been processed where content has been compressed (code blocks are separated by ⋮---- delimiter). - -================================================================ -File Summary -================================================================ - -Purpose: --------- -This file contains a packed representation of the entire repository's contents. -It is designed to be easily consumable by AI systems for analysis, code review, -or other automated processes. - -File Format: ------------- -The content is organized as follows: -1. This summary section -2. Repository information -3. Directory structure -4. Multiple file entries, each consisting of: - a. A separator line (================) - b. The file path (File: path/to/file) - c. Another separator line - d. The full contents of the file - e. A blank line - -Usage Guidelines: ------------------ -- This file should be treated as read-only. Any changes should be made to the - original repository files, not this packed version. -- When processing this file, use the file path to distinguish - between different files in the repository. -- Be aware that this file may contain sensitive information. Handle it with - the same level of security as you would the original repository. - -Notes: ------- -- Some files may have been excluded based on .gitignore rules and Repomix's configuration -- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files -- Files matching patterns in .gitignore are excluded -- Files matching default ignore patterns are excluded -- Content has been compressed - code blocks are separated by ⋮---- delimiter -- Files are sorted by Git change count (files with more changes are at the bottom) - -Additional Info: ----------------- - -================================================================ -Directory Structure -================================================================ -.github/ - ISSUE_TEMPLATE/ - bug_report.yml - config.yml - other.yml - question.yml - rfc.yml - workflows/ - audit.yaml - node.yaml - publish-prerelease.yaml - sonar.yaml - dependabot.yml - PULL_REQUEST_TEMPLATE.md -.husky/ - pre-commit -packages/ - connector/ - src/ - config/ - connectionConfig.ts - connection/ - mosMessageParser.ts - mosSocketClient.ts - mosSocketServer.ts - NCSServerConnection.ts - Server.ts - socketConnection.ts - api.ts - index.ts - lib.ts - MosConnection.ts - MosDevice.ts - .eslintrc.js - .prettierignore - jest.config.js - package.json - README.md - tsconfig.build.json - tsconfig.json - examples/ - src/ - client.ts - server.ts - .eslintrc.js - .prettierignore - jest.config.js - package.json - README.md - tsconfig.build.json - tsconfig.json - helper/ - src/ - mosModel/ - profile0/ - heartBeat.ts - index.ts - listMachInfo.ts - reqMachInfo.ts - xmlConversion.ts - profile1/ - index.ts - mosAck.ts - mosListAll.ts - mosObj.ts - reqMosObj.ts - reqMosObjAll.ts - xmlConversion.ts - profile2/ - index.ts - ROAck.ts - roActions.ts - roCreate.ts - roDelete.ts - roElementStat.ts - ROList.ts - ROListAll.ts - roMetadataReplace.ts - roReadyToAir.ts - roReplace.ts - roReq.ts - xmlConversion.ts - profile3/ - index.ts - mosItemReplace.ts - mosListSearchableSchema.ts - mosObjCreate.ts - mosObjList.ts - mosReqObjAction.ts - mosReqObjList.ts - mosReqSearchableSchema.ts - xmlConversion.ts - profile4/ - index.ts - roReqAll.ts - roStory.ts - xmlConversion.ts - index.ts - lib.ts - MosMessage.ts - ParseError.ts - parseMosTypes.ts - stringify/ - stringifyMosObject.ts - utils/ - ensureMethods.ts - Errors.ts - Utils.ts - index.ts - .eslintrc.js - .prettierignore - jest.config.js - package.json - README.md - tsconfig.build.json - tsconfig.json - model/ - src/ - mosTypes/ - lib.ts - mosDuration.ts - mosString128.ts - mosTime.ts - index.ts - model.ts - mosTypes.ts - xmlParse.ts - .eslintrc.js - .prettierignore - jest.config.js - package.json - README.md - tsconfig.build.json - tsconfig.json - mos-dummy-device/ - rundowns/ - defaultrundown.json - src/ - index.ts - package.json - README.md - tsconfig.json - quick-mos/ - input/ - runningorders/ - .gitignore - empty.ts - README.md - config.ts - util.ts - src/ - convertFromSofieSnapshot.ts - index.ts - mosDiff.ts - .eslintrc.js - jest.config.js - LICENSE - package.json - README.md - tsconfig.build.json - tsconfig.json -scripts/ - fix-version.js - reset.js - update-packages.mjs -.editorconfig -.eslintrc.js -.gitattributes -.gitignore -.prettierignore -.repomixignore -.yarnrc.yml -commonPackage.json -CONTRIBUTING.md -jest.config.base.js -lerna.json -LICENSE -package.json -README.md -sonar-project.properties -tsconfig.build.json -tsconfig.json - -================================================================ -Files -================================================================ - -================ -File: .github/ISSUE_TEMPLATE/bug_report.yml -================ -name: Bug Report 🐛 -description: Use this if you've found a bug -title: "Bug Report: [Short description of the bug]" -labels: - - 🐛bug - -body: - - type: markdown - attributes: - value: | - Before you post, be sure to read our Contribution guidelines: - https://nrkno.github.io/sofie-core/docs/for-developers/contribution-guidelines - - - type: textarea - attributes: - label: About me - description: Tell us who / which organization you are representing, and how the Sofie team will be able to contact you. - placeholder: Example "This RFC is posted on behalf of the NRK." - validations: - required: true - - - type: textarea - attributes: - label: Observed Behavior - description: What happened? - validations: - required: true - - - type: textarea - attributes: - label: Expected Behavior - description: What did you expect to happen? - validations: - required: true - - - type: input - attributes: - label: Version - description: What version of Sofie Core / Gateways / other components are you using? - validations: - required: true - - - type: textarea - attributes: - label: Severity / Impact - description: How big of an issue is this? How does this limit your operations? - placeholder: | - Examples: - * This is a blocker for us, we cannot use feature X until this is fixed. - * Not a big issue for us, but it would be nice to have this fixed. - -================ -File: .github/ISSUE_TEMPLATE/config.yml -================ -blank_issues_enabled: false - -================ -File: .github/ISSUE_TEMPLATE/other.yml -================ -name: Other Issue 🔧 -description: Use this for other issues -title: "Other: [Short description of the issue]" -labels: - - Other - -body: - - type: markdown - attributes: - value: | - Before you post, be sure to read our Contribution guidelines: - https://nrkno.github.io/sofie-core/docs/for-developers/contribution-guidelines - - - type: textarea - attributes: - label: About me - description: Tell us who / which organization you are representing, and how the Sofie team will be able to contact you. - placeholder: Example "This RFC is posted on behalf of the NRK." - validations: - required: true - - - type: textarea - attributes: - label: Issue - description: Describe the issue below - validations: - required: true - -================ -File: .github/ISSUE_TEMPLATE/question.yml -================ -name: Question ❓ -description: Use this if you have a question to the Sofie team -title: "Question: [Short summary of the question]" -labels: - - ❓ Question - -body: - - type: markdown - attributes: - value: | - Before you post, be sure to read our Contribution guidelines: - https://nrkno.github.io/sofie-core/docs/for-developers/contribution-guidelines - - - type: textarea - attributes: - label: About me - description: Tell us who / which organization you are representing, and how the Sofie team will be able to contact you. - placeholder: Example "This RFC is posted on behalf of the NRK." - validations: - required: true - - - type: textarea - attributes: - label: My Question - description: Write your question below - validations: - required: true - -================ -File: .github/ISSUE_TEMPLATE/rfc.yml -================ -name: Request for Comments ❗ -description: Use this to initiate a discussion about a new feature or a larger change -title: "RFC: [Short description of the feature/change]" -labels: - - RFC - - Contribution - -body: - - type: markdown - attributes: - value: | - Before you post, be sure to read our Contribution guidelines: - https://nrkno.github.io/sofie-core/docs/for-developers/contribution-guidelines - - - type: textarea - attributes: - label: About me - description: Tell us who / which organization you are representing, and how the Sofie team will be able to contact you. - placeholder: Example "This RFC is posted on behalf of the NRK." - validations: - required: true - - - type: textarea - attributes: - label: Use case - description: "Please write some background information here, such as: What is your use case? What problem are you trying to solve?" - validations: - required: true - - - type: textarea - attributes: - label: Proposal - description: Please describe your proposal here - validations: - required: true - - - type: textarea - attributes: - label: Process - description: Please don't touch this section, the Sofie team will update this as the RFC progresses. - value: | - The Sofie Team will evaluate this RFC and open up a discussion about it, usually within a week. - - [x] RFC created - - [ ] Sofie Team has evaluated the RFC - - [ ] A workshop has been planned - - [ ] RFC has been discussed in a workshop - - [ ] A conclusion has been reached, see comments in thread - -================ -File: .github/dependabot.yml -================ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - reviewers: - - "@nrkno/sofie-ops" - -================ -File: .github/PULL_REQUEST_TEMPLATE.md -================ - - -## About the Contributor - - - -## Type of Contribution - -This is a: - -Bug fix / Feature / Code improvement / Documentation improvement / Other (please specify) - - -## Current Behavior - - - -## New Behavior - - - -## Testing Instructions - - - -## Other Information - - - -## Status - - -- [ ] PR is ready to be reviewed. -- [ ] The functionality has been tested by the author. -- [ ] Relevant unit tests has been added / updated. -- [ ] Relevant documentation (code comments, [system documentation](https://nrkno.github.io/sofie-core/)) has been added / updated. - -================ -File: .husky/pre-commit -================ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -node scripts/update-packages.mjs - -yarn run lint-staged - -================ -File: packages/connector/src/config/connectionConfig.ts -================ -import { IProfiles } from '@mos-connection/model' -import { IConnectionConfig, IMOSDeviceConnectionOptions } from '../api' -⋮---- -/** */ -⋮---- -export class ConnectionConfig implements IConnectionConfig -⋮---- -// options for on-the-fly-created connections -⋮---- -constructor(init: IConnectionConfig) -⋮---- -/* tslint:disable */ -⋮---- -/* tslint:enable */ -⋮---- -/** */ -get profiles(): IProfiles -⋮---- -/** */ -set profiles(profileSupport: IProfiles) -⋮---- -// profile 0 is mandatory -⋮---- -// Profile 1 depends on 0 -⋮---- -// Profile 2 depends on 0 and 1 -⋮---- -// Profile 3 depends on 0, 1 and 2 -⋮---- -// Profile 4 depends on 0, 1 and 2 -⋮---- -// Profile 5 depends on 0, 1 and 2 -⋮---- -// Profile 6 depends on 0, 1 and 2 -⋮---- -// Profile 7 depends on 0, 1 and 2 -⋮---- -// must support at least one other profile, other than Profile 0 -⋮---- -// machineInfo: { -// manufacturer: "SuperFly.tv", -// model: "YAAS" -// //hwRev: , -// swRev: '0.0.1.0' -// DOM: '', // date of manufacture -// /*927748927 -// airchache.newscenter.com -// -// 2009-03-01T23:55:10 -// 2.8.2 -// */ -// } - -================ -File: packages/connector/src/connection/Server.ts -================ -import { Socket } from 'net' -import { ConnectionType, SocketDescription } from './socketConnection' -⋮---- -/** */ -export class Server -⋮---- -// private _connected: boolean -// private _lastSeen: number -⋮---- -/** */ -registerIncomingConnection(socketID: number, socket: Socket, portDescription: ConnectionType): void -⋮---- -/** */ -removeSocket(socketID: number): void -⋮---- -private _getSockets(portDescription: string): Socket[] -/** */ -get lowerPortSockets(): Socket[] -⋮---- -/** */ -get upperPortSockets(): Socket[] -⋮---- -/** */ -get queryPortSockets(): Socket[] - -================ -File: packages/connector/src/connection/socketConnection.ts -================ -import { Socket } from 'net' -⋮---- -/** */ -export enum SocketConnectionEvent { - CONNECTED = 'eventsocketconnectionconnected', - DISCONNECTED = 'eventsocketconnectiondisconnected', - DISPOSED = 'eventsocketconnectiondisposed', - TIMEOUT = 'eventsocketconnectiontimeout', - ALIVE = 'eventsocketconnectionalive', - REGISTER = 'eventsocketconnectionregister', - UNREGISTER = 'eventsocketconnectionunregister', -} -⋮---- -/** */ -export enum SocketServerEvent { - // LISTENING = 'eventsocketserverlistening', - // DISPOSED = 'eventsocketserverdisposed', - // ALIVE = 'eventsocketserveralive', - CLIENT_CONNECTED = 'eventsocketserverclientconnected', - ERROR = 'eventsocketserverclienterror', - CLOSE = 'eventsocketserverclientclose', -} -⋮---- -// LISTENING = 'eventsocketserverlistening', -// DISPOSED = 'eventsocketserverdisposed', -// ALIVE = 'eventsocketserveralive', -⋮---- -/** */ -export type ConnectionType = IncomingConnectionType | OutgoingConnectionType -export type IncomingConnectionType = 'lower' | 'upper' | 'query' -export type OutgoingConnectionType = 'lower' | 'upper' -⋮---- -/** */ -export type SocketDescription = { - socket: Socket - portDescription: ConnectionType -} - -================ -File: packages/connector/src/lib.ts -================ -/** Return true if the object has a property */ -export function has(obj: unknown, property: string): boolean -export function safeStringify(obj: unknown): string -⋮---- -export function isTestingWithJest(): boolean -⋮---- -// If running in Jest, wait a bit longer before checking the profile validity: - -================ -File: packages/connector/.eslintrc.js -================ - - -================ -File: packages/connector/.prettierignore -================ -package.json - -================ -File: packages/connector/jest.config.js -================ - - -================ -File: packages/connector/README.md -================ -# @mos-connection/connector - -This is the **connector** of the [MOS-connection](https://github.com/nrkno/sofie-mos-connection). - -It is a Node.js library used to connect to MOS services. - -Read the full documentation at the main repo [MOS-connection](https://github.com/nrkno/sofie-mos-connection). - -================ -File: packages/connector/tsconfig.build.json -================ -{ - "extends": "../../tsconfig.build.json", - "include": [ - "src/**/*.ts" - ], - "compilerOptions": { - "outDir": "./dist", - } -} - -================ -File: packages/connector/tsconfig.json -================ -{ - "extends": "../../tsconfig.json", - "include": [ - "src/**/*.ts" - ] -} - -================ -File: packages/examples/src/client.ts -================ -import { - MosConnection, - ConnectionConfig, - IMOSROAck, - IMOSROAction, - IMOSROReadyToAir, - IMOSROStory, - IMOSRunningOrder, - IMOSStoryAction, - MosDevice, - IMOSString128, -} from '@mos-connection/connector' -⋮---- -// mos.on('rawMessage', (_source, _type, _message) => { -// console.log('rawMessage', _source, _type, _message) -// }) -⋮---- -// console.log(dev) -⋮---- -const mosTypes = mosDevice.mosTypes // Could also be retrieved with getMosTypes(strict) -⋮---- -const sendInit = async () => -⋮---- -// trigger a re-send of those running orders: -// return dev.getRunningOrder(mosTypes.mosString128.create('696297DF-1568-4B36-B43B3B79514B40D4')) -⋮---- -// opTime?: mosTypes.mosTime.create(), -⋮---- -// To connect to a server on custom ports: -// ports: { -// lower: 11540, -// upper: 11541, -// query: 11542, -// }, - -================ -File: packages/examples/src/server.ts -================ -import { MosConnection, ConnectionConfig } from '@mos-connection/connector' -⋮---- -// To start the server on custom ports: -// ports: { -// lower: 11540, -// upper: 11541, -// query: 11542, -// }, -⋮---- -const mosTypes = mosDevice.mosTypes // Could also be retrieved with getMosTypes(strict) -⋮---- -// opTime?: mosTypes.mosTime.create(), - -================ -File: packages/examples/.eslintrc.js -================ - - -================ -File: packages/examples/.prettierignore -================ -package.json - -================ -File: packages/examples/jest.config.js -================ - - -================ -File: packages/examples/README.md -================ -# @mos-connection/connector - -This is the **connector** of the [MOS-connection](https://github.com/nrkno/sofie-mos-connection). - -It is a Node.js library used to connect to MOS services. - -Read the full documentation at the main repo [MOS-connection](https://github.com/nrkno/sofie-mos-connection). - -================ -File: packages/examples/tsconfig.build.json -================ -{ - "extends": "../../tsconfig.build.json", - "include": [ - "src/**/*.ts" - ], - "compilerOptions": { - "outDir": "./dist", - } -} - -================ -File: packages/examples/tsconfig.json -================ -{ - "extends": "../../tsconfig.json", - "include": [ - "src/**/*.ts" - ] -} - -================ -File: packages/helper/src/mosModel/profile0/index.ts -================ - - -================ -File: packages/helper/src/mosModel/profile0/listMachInfo.ts -================ -import { IMOSListMachInfo } from '@mos-connection/model' -import { MosMessage } from '../MosMessage' -⋮---- -import { XMLMosListMachInfo } from './xmlConversion' -⋮---- -export class ListMachineInfo extends MosMessage -⋮---- -/** */ -constructor(info: IMOSListMachInfo, port: 'upper' | 'lower', strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile0/reqMachInfo.ts -================ -import { MosMessage } from '../MosMessage' -⋮---- -export class ReqMachInfo extends MosMessage -⋮---- -/** */ -constructor(strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile1/index.ts -================ - - -================ -File: packages/helper/src/mosModel/profile1/mosAck.ts -================ -import { MosMessage } from '../MosMessage' -import { IMOSAck, IMOSAckStatus, IMOSString128 } from '@mos-connection/model' -import { addTextElementInternal } from '../../utils/Utils' -⋮---- -export class MOSAck extends MosMessage implements IMOSAck -⋮---- -Revision: number // max 999 -⋮---- -/** */ -constructor(ack: IMOSAck, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile1/mosListAll.ts -================ -import { MosMessage } from '../MosMessage' -⋮---- -import { IMOSObject } from '@mos-connection/model' -import { XMLMosObject } from '../profile1/xmlConversion' -⋮---- -export class MosListAll extends MosMessage -⋮---- -/** */ -constructor(objs: Array, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile1/mosObj.ts -================ -import { MosMessage } from '../MosMessage' -⋮---- -import { IMOSObject } from '@mos-connection/model' -import { XMLMosObject } from './xmlConversion' -⋮---- -export class MosObj extends MosMessage -⋮---- -/** */ -constructor(obj: IMOSObject, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile1/reqMosObj.ts -================ -import { MosMessage } from '../MosMessage' -import { IMOSString128 } from '@mos-connection/model' -import { addTextElementInternal } from '../../utils/Utils' -⋮---- -export class ReqMosObj extends MosMessage -⋮---- -/** */ -constructor(objId: IMOSString128, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile2/index.ts -================ - - -================ -File: packages/helper/src/mosModel/profile2/ROAck.ts -================ -import { MosMessage } from '../MosMessage' -import { IMOSROAck, IMOSROAckStory, IMOSString128 } from '@mos-connection/model' -import { addTextElementInternal } from '../../utils/Utils' -⋮---- -export class ROAck extends MosMessage implements IMOSROAck -⋮---- -/** */ -constructor(roAck: IMOSROAck, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement -⋮---- -// Loop over Stories, Items and Object - -================ -File: packages/helper/src/mosModel/profile2/roActions.ts -================ -import { MosMessage } from '../MosMessage' -import { - IMOSStoryAction, - IMOSROStory, - IMOSItemAction, - IMOSItem, - IMOSROAction, - IMOSString128, -} from '@mos-connection/model' -import { XMLROStory, XMLMosItem } from './xmlConversion' -import { addTextElementInternal } from '../../utils/Utils' -⋮---- -export abstract class MosSendMessage extends MosMessage -⋮---- -constructor(strict: boolean) -⋮---- -export class ROInsertStories extends MosSendMessage -⋮---- -constructor(private Action: IMOSStoryAction, private Stories: Array, strict: boolean) -get messageXMLBlocks(): XMLBuilder.XMLElement -⋮---- -export class ROInsertItems extends MosSendMessage -⋮---- -constructor(private Action: IMOSItemAction, private Items: Array, strict: boolean) -⋮---- -export class ROReplaceStories extends MosSendMessage -export class ROReplaceItems extends MosSendMessage -export class ROMoveStories extends MosSendMessage -⋮---- -constructor(private Action: IMOSStoryAction, private Stories: Array, strict: boolean) -⋮---- -export class ROMoveItems extends MosSendMessage -⋮---- -constructor(private Action: IMOSItemAction, private Items: Array, strict: boolean) -⋮---- -export class RODeleteStories extends MosSendMessage -⋮---- -constructor(private Action: IMOSROAction, private Stories: Array, strict: boolean) -⋮---- -export class RODeleteItems extends MosSendMessage -⋮---- -constructor(private Action: IMOSStoryAction, private Items: Array, strict: boolean) -⋮---- -export class ROSwapStories extends MosSendMessage -⋮---- -constructor( - private Action: IMOSROAction, - private StoryID0: IMOSString128, - private StoryID1: IMOSString128, - strict: boolean -) -⋮---- -export class ROSwapItems extends MosSendMessage -⋮---- -constructor( - private Action: IMOSStoryAction, - private ItemID0: IMOSString128, - private ItemID1: IMOSString128, - strict: boolean -) - -================ -File: packages/helper/src/mosModel/profile2/roCreate.ts -================ -import { MosMessage } from '../MosMessage' -import { IMOSRunningOrder } from '@mos-connection/model' -import { XMLROStory, XMLRunningOrderBase } from './xmlConversion' -⋮---- -export class ROCreate extends MosMessage -⋮---- -/** */ -constructor(private ro: IMOSRunningOrder, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement -protected fillXMLWithROData(root: XMLBuilder.XMLElement): void - -================ -File: packages/helper/src/mosModel/profile2/roDelete.ts -================ -import { MosMessage } from '../MosMessage' -import { IMOSString128 } from '@mos-connection/model' -import { addTextElementInternal } from '../../utils/Utils' -⋮---- -export class RODelete extends MosMessage -⋮---- -constructor(private roId: IMOSString128, strict: boolean) -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile2/ROList.ts -================ -import { MosMessage } from '../MosMessage' -import { IMOSRunningOrder } from '@mos-connection/model' -import { XMLROStory, XMLRunningOrderBase } from './xmlConversion' -⋮---- -export class ROList extends MosMessage -⋮---- -/** */ -constructor(private ro: IMOSRunningOrder, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement -⋮---- -protected fillXMLWithROData(root: XMLBuilder.XMLElement): void - -================ -File: packages/helper/src/mosModel/profile2/roMetadataReplace.ts -================ -import { IMOSRunningOrderBase } from '@mos-connection/model' -import { MosMessage } from '../MosMessage' -import { XMLRunningOrderBase } from './xmlConversion' -⋮---- -export class ROMetadataReplace extends MosMessage -⋮---- -constructor(private metadata: IMOSRunningOrderBase, strict: boolean) -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile2/roReadyToAir.ts -================ -import { MosMessage } from '../MosMessage' -import { IMOSString128 } from '@mos-connection/model' -import { addTextElementInternal } from '../../utils/Utils' -⋮---- -// https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReadyToAir -⋮---- -export interface ROReadyToAirOptions { - roId: IMOSString128 - roAir: 'READY' | 'NOT READY' -} -export class ROReadyToAir extends MosMessage -⋮---- -constructor(options: ROReadyToAirOptions, strict: boolean) -⋮---- -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile2/roReplace.ts -================ -import { IMOSRunningOrder } from '@mos-connection/model' -import { ROCreate } from './roCreate' -⋮---- -export class ROReplace extends ROCreate -⋮---- -constructor(ro: IMOSRunningOrder, strict: boolean) -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile2/roReq.ts -================ -import { MosMessage } from '../MosMessage' -import { IMOSString128 } from '@mos-connection/model' -import { addTextElementInternal } from '../../utils/Utils' -⋮---- -export class ROReq extends MosMessage -⋮---- -/** */ -constructor(roId: IMOSString128, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile3/mosItemReplace.ts -================ -import { MosItemReplaceOptions } from '@mos-connection/model' -import { MosMessage } from '../MosMessage' -⋮---- -import { XMLMosItem } from '../profile2/xmlConversion' -import { addTextElementInternal } from '../../utils/Utils' -⋮---- -export class MosItemReplace extends MosMessage -⋮---- -constructor(options: MosItemReplaceOptions, strict: boolean) -⋮---- -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile3/mosObjCreate.ts -================ -import { IMOSObject } from '@mos-connection/model' -import { MosMessage } from '../MosMessage' -⋮---- -import { XMLMosObject } from '../profile1/xmlConversion' -⋮---- -// https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjCreate -export class MosObjCreate extends MosMessage -⋮---- -constructor(object: IMOSObject, strict: boolean) -⋮---- -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile3/mosObjList.ts -================ -import { IMOSObjectList } from '@mos-connection/model' -import { MosMessage } from '../MosMessage' -⋮---- -import { XMLMosObjects } from '../profile1/xmlConversion' -import { addTextElementInternal } from '../../utils/Utils' -⋮---- -export class MosObjList extends MosMessage -⋮---- -constructor(options: IMOSObjectList, strict: boolean) -⋮---- -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile3/mosReqObjAction.ts -================ -import { IMOSObject, IMOSString128 } from '@mos-connection/model' -import { MosMessage } from '../MosMessage' -⋮---- -import { XMLMosObject } from '../profile1/xmlConversion' -export interface MosReqObjActionOptionsNew { - object: IMOSObject -} -export class MosReqObjActionNew extends MosMessage -⋮---- -constructor(options: MosReqObjActionOptionsNew, strict: boolean) -get messageXMLBlocks(): XMLBuilder.XMLElement -⋮---- -export interface MosReqObjActionOptionsUpdate { - object: IMOSObject - objectId: IMOSString128 -} -export class MosReqObjActionUpdate extends MosMessage -⋮---- -constructor(options: MosReqObjActionOptionsUpdate, strict: boolean) -⋮---- -export interface MosReqObjActionOptionsDelete { - objectId: IMOSString128 -} -export class MosReqObjActionDelete extends MosMessage -⋮---- -constructor(options: MosReqObjActionOptionsDelete, strict: boolean) - -================ -File: packages/helper/src/mosModel/profile3/mosReqSearchableSchema.ts -================ -import { MosMessage } from '../MosMessage' -⋮---- -export class MosReqSearchableSchema extends MosMessage -⋮---- -constructor(options: -⋮---- -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile4/index.ts -================ - - -================ -File: packages/helper/src/mosModel/profile4/roReqAll.ts -================ -import { MosMessage } from '../MosMessage' -⋮---- -export class ROReqAll extends MosMessage -⋮---- -/** */ -constructor(strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/.eslintrc.js -================ - - -================ -File: packages/helper/.prettierignore -================ -package.json - -================ -File: packages/helper/jest.config.js -================ - - -================ -File: packages/helper/README.md -================ -# @mos-connection/helper - -This is the **helper** of the [MOS-connection](https://github.com/nrkno/sofie-mos-connection). - -It is a library that provides various functions useful for those that prepare/handle data that will be sent to (or has been received by) the [MOS-connection](https://github.com/nrkno/sofie-mos-connection). - -Read the full documentation at the main repo [MOS-connection](https://github.com/nrkno/sofie-mos-connection). - -================ -File: packages/helper/tsconfig.build.json -================ -{ - "extends": "../../tsconfig.build.json", - "include": [ - "src/**/*.ts" - ], - "compilerOptions": { - "outDir": "./dist", - } -} - -================ -File: packages/helper/tsconfig.json -================ -{ - "extends": "../../tsconfig.json", - "include": [ - "src/**/*.ts" - ] -} - -================ -File: packages/model/.eslintrc.js -================ - - -================ -File: packages/model/.prettierignore -================ -package.json - -================ -File: packages/model/jest.config.js -================ - - -================ -File: packages/model/README.md -================ -# @mos-connection/model - -This is the **model** of the [MOS-connection](https://github.com/nrkno/sofie-mos-connection). - -It is a library that contains types and enums, useful for applications that handles MOS-data. - -Read the full documentation at the main repo [MOS-connection](https://github.com/nrkno/sofie-mos-connection). - -================ -File: packages/model/tsconfig.build.json -================ -{ - "extends": "../../tsconfig.build.json", - "include": [ - "src/**/*.ts" - ], - "compilerOptions": { - "outDir": "./dist", - } -} - -================ -File: packages/model/tsconfig.json -================ -{ - "extends": "../../tsconfig.json", - "include": [ - "src/**/*.ts" - ] -} - -================ -File: packages/mos-dummy-device/rundowns/defaultrundown.json -================ -{ - "ID": "EXAMPLE_RO", - "Slug": "Example Rundown", - "DefaultChannel": "A", - "Stories": [ - { - "ID": "EXAMPLE_RO_STORY_1", - "Slug": "Opening Story", - "Number": "1", - "Items": [ - { - "ID": "EXAMPLE_RO_STORY_1_ITEM_1", - "Slug": "Intro Graphic", - "ObjectID": "OBJ_INTRO_GRAPHIC", - "MOSID": "DUMMY.MOS.SERVER", - "ObjectSlug": "Opening Title Graphic", - "Duration": 500, - "TimeBase": 100 - }, - { - "ID": "EXAMPLE_RO_STORY_1_ITEM_2", - "Slug": "Presenter Introduction", - "ObjectID": "OBJ_PRESENTER_CAM1", - "MOSID": "DUMMY.MOS.SERVER", - "ObjectSlug": "Camera 1 - Wide", - "Duration": 1000, - "TimeBase": 100 - } - ] - }, - { - "ID": "EXAMPLE_RO_STORY_2", - "Slug": "News Story", - "Number": "2", - "Items": [ - { - "ID": "EXAMPLE_RO_STORY_2_ITEM_1", - "Slug": "News Intro", - "ObjectID": "OBJ_NEWS_GRAPHIC", - "MOSID": "DUMMY.MOS.SERVER", - "ObjectSlug": "News Title Card", - "Duration": 300, - "TimeBase": 100 - }, - { - "ID": "EXAMPLE_RO_STORY_2_ITEM_2", - "Slug": "News Package", - "ObjectID": "OBJ_NEWS_PKG_1", - "MOSID": "DUMMY.MOS.SERVER", - "ObjectSlug": "News Package Video", - "Duration": 3000, - "TimeBase": 100 - } - ] - }, - { - "ID": "EXAMPLE_RO_STORY_3", - "Slug": "Closing Story", - "Number": "3", - "Items": [ - { - "ID": "EXAMPLE_RO_STORY_3_ITEM_1", - "Slug": "Closing Remarks", - "ObjectID": "OBJ_PRESENTER_CAM2", - "MOSID": "DUMMY.MOS.SERVER", - "ObjectSlug": "Camera 2 - Close Up", - "Duration": 1500, - "TimeBase": 100 - }, - { - "ID": "EXAMPLE_RO_STORY_3_ITEM_2", - "Slug": "Outro Graphic", - "ObjectID": "OBJ_OUTRO_GRAPHIC", - "MOSID": "DUMMY.MOS.SERVER", - "ObjectSlug": "Closing Credits", - "Duration": 800, - "TimeBase": 100 - } - ] - } - ] - } - -================ -File: packages/mos-dummy-device/src/index.ts -================ -/* eslint-disable node/no-unpublished-import */ -import { MosConnection, ConnectionConfig, getMosTypes, IMOSRunningOrder, MosDevice } from '@mos-connection/connector' -⋮---- -// Define the server configuration -⋮---- -'0': true, // Profile 0 is mandatory -'1': true, // Basic object exchange -'2': true, // Running order/playlist exchange -'3': true, // Advanced object-based workflow -'4': true, // Advanced rundown functionality -⋮---- -lower: 10540, // Default MOS ports, can be customized -⋮---- -// Enable relay mode to create new MOS devices on-the-fly when receiving messages -⋮---- -// Class for the dummy MOS server -class DummyMosServer -⋮---- -private mosTypes = getMosTypes(false) // Non-strict mode -⋮---- -constructor(rundownsDir: string) -⋮---- -// Create the MOS connection -⋮---- -// Set up device connection callback -⋮---- -// Watch the rundowns directory for changes -⋮---- -// Load any existing rundowns -⋮---- -// Initialize and start the server -public async start(): Promise -⋮---- -// Handle new MOS device connections -private handleNewConnection(mosDevice: MosDevice): void -⋮---- -// Store reference to the device -⋮---- -// Set up callbacks for Profile 0 -⋮---- -// Set up callbacks for Profile 2 -⋮---- -// Set up Profile 4 callbacks -⋮---- -// Send all rundowns to the device on connection -⋮---- -// Load all rundowns from the rundowns directory -private loadRundowns(): void -⋮---- -// Ensure ID is set correctly -⋮---- -// Process and validate rundown (convert strings to MOS types) -⋮---- -// Add to rundowns map -⋮---- -// Watch the rundowns directory for changes -private setupRundownWatcher(): void -⋮---- -// Handle new rundown file -private handleRundownFileAdded(filePath: string): void -⋮---- -// Ensure ID is set correctly -⋮---- -// Process and validate rundown -⋮---- -// Add to rundowns map -⋮---- -// Send to all connected devices -⋮---- -// Handle rundown file changes -private handleRundownFileChanged(filePath: string): void -⋮---- -// Process and validate rundown -⋮---- -// Update in rundowns map -⋮---- -// Send to all connected devices -⋮---- -// Handle rundown file removals -private handleRundownFileRemoved(filePath: string): void -⋮---- -// Remove from rundowns map -⋮---- -// Notify all connected devices -⋮---- -// Process a rundown to ensure all required MOS types are properly formatted -private processRundown(rundown: IMOSRunningOrder): IMOSRunningOrder -⋮---- -// Deep copy to avoid modifying the original -⋮---- -// Convert string values to MOS types -⋮---- -// Process each story -⋮---- -// Process items in each story -⋮---- -// Send a running order to all connected devices -private broadcastRunningOrder(rundown: IMOSRunningOrder, isUpdate = false): void -⋮---- -// Notify all connected devices about a deleted running order -private broadcastRunningOrderDelete(roId: string): void -⋮---- -// Send all loaded rundowns to a newly connected device -private sendAllRunningOrders(device: MosDevice): void -⋮---- -// Simulate a server outage -public simulateOutage(durationMs: number = 5000): void -⋮---- -// Shutdown the server -public async shutdown(): Promise -⋮---- -// Main function -async function main() -⋮---- -// Create and start the MOS server -⋮---- -// Handle process termination -⋮---- -// Command line interface for manual testing -⋮---- -// Simple command processing -⋮---- -// Run the application - -================ -File: packages/mos-dummy-device/package.json -================ -{ - "name": "mos-dummy-server", - "version": "1.0.0", - "description": "A dummy MOS server for testing MOS clients with failover capabilities", - "main": "dist/index.js", - "scripts": { - "build": "tsc", - "start": "node dist/index.js", - "dev": "ts-node src/index.ts", - "watch": "nodemon --watch src -e ts --exec ts-node src/index.ts" - }, - "keywords": [ - "mos", - "broadcast", - "testing", - "dummy", - "failover" - ], - "author": "", - "license": "MIT", - "dependencies": { - "@mos-connection/connector": "^4.2.2", - "chokidar": "^3.5.3" - }, - "devDependencies": { - "@types/node": "^20.10.5", - "nodemon": "^3.0.2", - "ts-node": "^10.9.2", - "typescript": "^5.3.3" - } -} - -================ -File: packages/mos-dummy-device/README.md -================ -# MOS Dummy Server - -A dummy MOS (Media Object Server) server for testing MOS clients with failover capabilities. This server implements the MOS protocol and allows you to simulate connection issues and manage rundowns through JSON files. - -## Features - -- Full MOS protocol support (Profiles 0, 1, 2, 3, and 4) -- JSON-based rundown management -- Hot reloading of rundowns from the filesystem -- Simulation of server outages for failover testing -- Command-line interface for manual testing - -## Installation - -1. Clone this repository -2. Install dependencies: - -```bash -npm install -``` - -3. Build the project: - -```bash -npm run build -``` - -## Usage - -### Starting the server - -```bash -npm start -``` - -This will start the MOS server on the default ports (10540, 10541, 10542). - -### Development mode - -To run in development mode with automatic reloading: - -```bash -npm run dev -``` - -Or with file watching: - -```bash -npm run watch -``` - -### Rundown Management - -Rundowns are stored as JSON files in the `rundowns` directory. The server watches this directory for changes: - -- Adding a new `.json` file creates a new rundown -- Modifying a file updates the rundown -- Deleting a file removes the rundown - -### Rundown JSON Format - -Here's an example of a rundown JSON file: - -```json -{ - "ID": "EXAMPLE_RO", - "Slug": "Example Rundown", - "DefaultChannel": "A", - "Stories": [ - { - "ID": "EXAMPLE_RO_STORY_1", - "Slug": "Story 1", - "Number": "1", - "Items": [ - { - "ID": "EXAMPLE_RO_STORY_1_ITEM_1", - "Slug": "Item 1 in Story 1", - "ObjectID": "OBJ_EXAMPLE_RO_STORY_1_ITEM_1", - "MOSID": "DUMMY.MOS.SERVER", - "ObjectSlug": "Item 1 in Story 1", - "Duration": 1000, - "TimeBase": 100 - } - ] - } - ] -} -``` - -### Command-Line Interface - -While the server is running, you can use the following commands: - -- `outage [duration_ms]` - Simulate a server outage for the specified duration (default: 5000ms) -- `create [id] [title] [story_count]` - Create an example rundown -- `exit` - Shutdown the server and exit - -Example: `outage 10000` will simulate a 10-second outage. - -## Configuration - -You can modify the configuration in the `SERVER_CONFIG` object in `src/index.ts`: - -- `mosID`: The MOS ID of the server -- `acceptsConnections`: Whether to accept incoming connections -- `profiles`: The MOS profiles to support -- `debug`: Enable/disable debug logging -- `ports`: The ports to use for MOS communication - -## Testing Failover - -To test failover with your MOS client: - -1. Start the server -2. Connect your MOS client to the server -3. Use the `outage` command to simulate an outage -4. Observe how your client handles the disconnection and reconnection - -================ -File: packages/mos-dummy-device/tsconfig.json -================ -{ - "compilerOptions": { - "target": "es2020", - "module": "commonjs", - "lib": ["es2020"], - "declaration": true, - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "**/*.spec.ts"] - } - -================ -File: packages/quick-mos/input/runningorders/.gitignore -================ -*.ts -*.json - -!empty.ts - -================ -File: packages/quick-mos/input/runningorders/empty.ts -================ -import { IMOSScope } from '@mos-connection/model' -⋮---- -// DefaultChannel?: '' -// EditorialStart?: MosTime; -// EditorialDuration?: MosDuration; -// Trigger?: '' -// MacroIn?: '' -// MacroOut?: '' - -================ -File: packages/quick-mos/src/mosDiff.ts -================ -export interface ListEntry { - /** ID that uniquely identifies this entry */ - id: string - /** Hash or timestamp, that changes whenever the content has changed */ - changedHash: string - content: T -} -⋮---- -/** ID that uniquely identifies this entry */ -⋮---- -/** Hash or timestamp, that changes whenever the content has changed */ -⋮---- -export type Operation = OperationInsert | OperationUpdate | OperationRemove | OperationMove -export interface OperationBase { - type: OperationType -} -export enum OperationType { - INSERT = 'insert', - UPDATE = 'update', - REMOVE = 'remove', - MOVE = 'move', -} -export interface OperationInsert { - type: OperationType.INSERT - beforeId: string - inserts: { - id: string - changedHash: string - content: T - }[] -} -export interface OperationUpdate { - type: OperationType.UPDATE - id: string - changedHash: string - content: T -} -export interface OperationRemove { - type: OperationType.REMOVE - ids: string[] -} -export interface OperationMove { - type: OperationType.MOVE - ids: string[] - beforeId: string -} -/** Takes an old and a new list, and returns the operations needed to synk the two */ -export function diffLists(oldList: ListEntry[], newList: ListEntry[]): Operation[] -⋮---- -// Preparations: -⋮---- -// Find Inserted and Updated: -⋮---- -// Find removed: -⋮---- -// Apply Inserts, updates & removes on intermediary list: -/** Intermediate list. Start with the old order and gradually ends up in the ending order, - * as move operations are determined - */ -⋮---- -// Find Moved: -⋮---- -const getInterPrevId = (id: string): string => -⋮---- -// const prevEntry: ListEntry | undefined = newList[i - 1] -⋮---- -// const nextEntry: ListEntry | undefined = newList[i + 1] -⋮---- -// The new entry is not following after the same element as the one that was before the beginning of the move operation -⋮---- -// The new entry is following the element that was before the move operation started. -⋮---- -// Optimize: when the tail of the move-operation is in the original order, it can be replaced by another smaller move operation: -⋮---- -// The previous id is in the original order, continue -⋮---- -// Yes, we can replace a number of ids with another move operation. -⋮---- -currentMoveOperation.ids.length = i // remove entries after i -⋮---- -// Commit the move operation: -// currentMoveOperation.beforeId = nextId -⋮---- -// The new entry is not following the same entry as the old does -⋮---- -// No change -⋮---- -// todo? Optimize: when the tail of the operation will be on the end anyway: -⋮---- -export function applyOperations(oldList: ListEntry[], operations: Operation[]): ListEntry[] -⋮---- -let newList: ListEntry[] = oldList.slice() // clone -⋮---- -// Apply Inserts, updates & removes on intermediary list: -⋮---- -// insert last -⋮---- -// move last: -⋮---- -/** Find and extract entries from list, and return them */ -function extractList(list: T[], fcn: (value: T) => number): T[] -function makeDenseArray(sparse: Array): T[] - -================ -File: packages/quick-mos/.eslintrc.js -================ - - -================ -File: packages/quick-mos/jest.config.js -================ - - -================ -File: packages/quick-mos/LICENSE -================ -MIT License - -Copyright (c) 2020 Johan Nyman - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -================ -File: packages/quick-mos/README.md -================ -# Quick-MOS - -An application for quick simulation of a MOS server/NCS. - -## Usage - -- `yarn start` to start the application - -- The application will monitor the contents in the folder `/input` and send mos commands. -- Files and folders that begin with "\_" (underscore) will be ignored - -- Note: quickmos and mos-gateway must be run on different machines (or docker containers) as they both try to bind to the same ports. This is a limitation in the current implementation of mos-connection - -================ -File: packages/quick-mos/tsconfig.build.json -================ -{ - "extends": "../../tsconfig.build.json", - "include": [ - "src/**/*.ts", - "input/**/*.ts" - ], - "compilerOptions": { - "outDir": "./dist", - } -} - -================ -File: packages/quick-mos/tsconfig.json -================ -{ - "extends": "../../tsconfig.json", - "include": [ - "src/**/*.ts", - "input/**/*.ts" - ] -} - -================ -File: scripts/fix-version.js -================ -/* eslint-disable no-console */ -⋮---- -// This script fixes a few things after a release version has been bumped -⋮---- -async function run() { -await removeFile('packages/examples/CHANGELOG.md') -await fixPackageVersion('packages/examples/package.json') -⋮---- -await removeFile('packages/quick-mos/CHANGELOG.md') -await fixPackageVersion('packages/quick-mos/package.json') -⋮---- -await commit() -⋮---- -async function fixPackageVersion(filePath) { -filePath = path.resolve(filePath) -const packageJsonStr = await fs.promises.readFile(filePath, 'utf8') -const packageJson = JSON.parse(packageJsonStr) -⋮---- -await fs.promises.writeFile(filePath, JSON.stringify(packageJson, null, '\t') + '\n', 'utf8') -⋮---- -async function removeFile(filePath) { -⋮---- -await fs.promises.unlink(filePath) -⋮---- -// File does not exist, do nothing -⋮---- -async function commit() { -const latestGitCommit = cp.execSync('git log -1') -⋮---- -// get last git commit message -const message = latestGitCommit.toString().split('\n')[4].trim() -// Check that the last commit message is a version bump -if (message.match(/v\d+\.\d+.\d+/)) { -⋮---- -console.log('Amending last commit with changes...') -exec('git add .') -exec('git commit --amend --no-edit --no-verify') -⋮---- -// Move tag: -exec(`git tag -d ${tag}`) -exec(`git tag ${tag}`) -⋮---- -console.log('NOT committing changes!') -⋮---- -function exec(cmd) { -console.log(cmd) -cp.execSync(cmd) -⋮---- -run() -.then(() => console.log('Done')) -.catch(console.error) - -================ -File: scripts/reset.js -================ -/* eslint-disable node/no-unpublished-require, node/no-extraneous-require */ -⋮---- -const glob = promisify(require('glob')) -const rimraf = promisify(require('rimraf')) -⋮---- -/* - Removing all /node_modules and /dist folders -*/ -⋮---- -const basePath = process.cwd() -⋮---- -log('Gathering files to remove...') -⋮---- -// Remove things that arent used, to reduce file size: -const searchForFolder = async (name) => { -⋮---- -...(await glob(`${basePath}/${name}`)), -...(await glob(`${basePath}/*/${name}`)), -...(await glob(`${basePath}/*/*/${name}`)), -...(await glob(`${basePath}/*/*/*/${name}`)), -...(await glob(`${basePath}/*/*/*/*/${name}`)), -⋮---- -...(await searchForFolder('node_modules')), -...(await searchForFolder('dist')), -...(await searchForFolder('deploy')), -⋮---- -if (folder.match(resultingFolder)) { -⋮---- -resultingFolders.push(folder) -⋮---- -const rootNodeModules = await glob(`${basePath}/node_modules`) -if (rootNodeModules.length !== 1) throw new Error(`Wrong length of root node_modules (${rootNodeModule.length})`) -⋮---- -log(`Removing ${resultingFolders.length} files...`) -⋮---- -log(`Removing ${folder}`) -await rimraf(folder) -⋮---- -// Finally, remove root node_modules -⋮---- -log(`...done!`) -})().catch(log) -⋮---- -function log(...args) { -// eslint-disable-next-line no-console -console.log(...args) - -================ -File: scripts/update-packages.mjs -================ -// /* eslint-disable node/no-unpublished-require */ -⋮---- -const glob = promisify(globOrg) -⋮---- -const rootPackageStr = await fs.readFile(path.join(path.dirname(fileURLToPath(import.meta.url)), '../package.json')) -const rootPackage = JSON.parse(rootPackageStr.toString()) -⋮---- -/* - This script copies some common properties (from commonPackage.json) - into package.json of each of the packages. - */ -⋮---- -const extendPackageStr = await fs.readFile('commonPackage.json') -const extendPackage = JSON.parse(extendPackageStr) -delete extendPackage.description // don't copy this propery -⋮---- -const packageJsons = await glob(`${workspaceDef}/package.json`) -⋮---- -if (!packageJsonPath.match(/node_modules/)) { -const packageJsonStr = await fs.readFile(packageJsonPath) -const packageJson = JSON.parse(packageJsonStr) -⋮---- -const newPackageJson = deepExtend({}, packageJson, extendPackage) -⋮---- -if (!_.isEqual(newPackageJson, packageJson)) { -await fs.writeFile(packageJsonPath, JSON.stringify(newPackageJson, undefined, '\t') + '\n') -⋮---- -console.error(`Error when processing ${packageJsonPath}`) -⋮---- -console.log(`Updated package.json of ${count} packages`) -console.log(`You should commit these changes and run yarn install again.`) -process.exit(1) - -================ -File: .editorconfig -================ -[*] -indent_style = tab - -[*.{cs,js,ts,json}] -indent_size = 4 - -[*.{yml,yaml}] -indent_size = 2 -indent_style = space - -================ -File: .eslintrc.js -================ - - -================ -File: .gitattributes -================ -* text=auto eol=lf - -================ -File: .gitignore -================ -node_modules -dist -test -src/**.js - -/**/coverage -/docs -.nyc_output -*.log - -wallaby.conf.js - -.DS_Store -docs/ -.vscode/settings.json - -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks - -================ -File: .prettierignore -================ -package.json - -================ -File: .repomixignore -================ -__tests__/ -__mocks__/ -dist/ -yarn.lock -CHANGELOG.md - -================ -File: .yarnrc.yml -================ -nodeLinker: node-modules - -plugins: - - path: .yarn/plugins/@yarnpkg/plugin-version.cjs - spec: "@yarnpkg/plugin-version" - - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs - spec: "@yarnpkg/plugin-interactive-tools" - - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs - spec: "@yarnpkg/plugin-workspace-tools" - -yarnPath: .yarn/releases/yarn-3.6.0.cjs - -================ -File: commonPackage.json -================ -{ - "description": "The properties of this file are copied to all packages' package.json files by running `yarn postinstall`", - "scripts": { - "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", - "validate:dev-dependencies": "yarn npm audit ", - "license-validate": "yarn sofie-licensecheck" - }, - "devDependencies": { - "@sofie-automation/code-standard-preset": "*", - "typescript": "~4.9.5" - }, - "engines": { - "node": ">=14.18.0" - }, - "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", - "lint-staged": { - "*.{css,json,md,scss}": [ - "run -T prettier" - ], - "*.{ts,tsx,js,jsx}": [ - "run -T eslint" - ] - }, - "files": [ - "/dist", - "/CHANGELOG.md", - "/README.md", - "/LICENSE" - ] -} - -================ -File: CONTRIBUTING.md -================ -# How to Contribute to this Repository - -Before contributing to this specific repository, please read the [Contribution Guidelines](https://nrkno.github.io/sofie-core/docs/for-developers/contribution-guidelines) for the Sofie project. - - -## Branches -This repository uses the following branches: - -* **_master_** is our main branch. We consider it stable and it is used in production. -* The **_develop_** branch is our in-development branch. When a release is ready, we decide to “freeze” that branch and merge it into **_master_** branch. - -We encourage you to base your contributions on the latest **_develop_** branch. The [_Sofie Releases_](https://nrkno.github.io/sofie-core/releases) page collects the status and timeline of the releases. - -================ -File: jest.config.base.js -================ -// globals: { -// 'ts-jest': { -// tsconfig: 'tsconfig.json', -// }, -// }, -⋮---- -// verbose: true, - -================ -File: LICENSE -================ -MIT License (MIT) - -Copyright (c) 2023 Norsk rikskringkasting AS (NRK) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -================ -File: sonar-project.properties -================ -sonar.projectKey=nrkno_tv-automation-mos-connection -sonar.organization=nrkno - -# This is the name and version displayed in the SonarCloud UI. -#sonar.projectName=sofie-mos-connection -#sonar.projectVersion=1.0 - -# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. -#sonar.sources=. - -# Encoding of the source code. Default is default system encoding -#sonar.sourceEncoding=UTF-8 - -================ -File: tsconfig.build.json -================ -{ - "extends": "@sofie-automation/code-standard-preset/ts/tsconfig.lib", - "include": [ - "**/src/**/*.ts" - ], - "exclude": [ - "**/node_modules/**", - "**/*spec.ts", - "**/__tests__", - "**/__mocks__", - "**/scratch/**" - ], - "compilerOptions": { - "outDir": "./dist", - "baseUrl": "./", - "paths": { - "*": [ - "./node_modules/*" - ] - }, - "types": [ - "node" - ] - } -} - -================ -File: tsconfig.json -================ -{ - "extends": "./tsconfig.build.json", - "exclude": [ - "**/node_modules/**" - ], - "compilerOptions": { - "types": [ - "jest", - "node" - ] - } -} - -================ -File: .github/workflows/audit.yaml -================ -name: Audit dependencies - -on: - push: - branches: - - master - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -jobs: - validate-dependencies: - name: Validate production dependencies - runs-on: ubuntu-latest - continue-on-error: false - timeout-minutes: 5 - - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Use Node.js 16 - uses: actions/setup-node@v4 - with: - node-version: 16 - - name: Prepare Environment - run: | - yarn install - env: - CI: true - - name: Validate dependencies - run: | - yarn validate:dependencies - env: - CI: true - validate-all-dependencies: - name: Validate all dependencies - runs-on: ubuntu-latest - continue-on-error: true - timeout-minutes: 5 - - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Use Node.js 16 - uses: actions/setup-node@v4 - with: - node-version: 16 - - name: Prepare Environment - run: | - yarn install - env: - CI: true - - name: Validate dependencies - run: | - yarn validate:dev-dependencies - env: - CI: true - -================ -File: .github/workflows/sonar.yaml -================ -on: - # Trigger analysis when pushing in master or pull requests, and when creating - # a pull request. - push: - branches: - - master - pull_request: - types: [opened, synchronize, reopened] -name: SonarCloud -jobs: - sonarcloud: - name: SonarCloud - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'nrkno' }} - - steps: - - uses: actions/checkout@v4 - with: - # Disabling shallow clone is recommended for improving relevancy of reporting - fetch-depth: 0 - persist-credentials: false - - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: 16 - - name: Prepare Environment - run: | - yarn - env: - CI: true - - - name: SonarCloud Scan - uses: sonarsource/sonarcloud-github-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - -================ -File: packages/connector/src/connection/mosSocketServer.ts -================ -import { Server, Socket } from 'net' -import { EventEmitter } from 'eventemitter3' -import { IncomingConnectionType, SocketServerEvent } from './socketConnection' -⋮---- -export interface MosSocketServerEvents { - [SocketServerEvent.CLIENT_CONNECTED]: (options: { socket: Socket; portDescription: IncomingConnectionType }) => void - [SocketServerEvent.ERROR]: (err: Error) => void - [SocketServerEvent.CLOSE]: () => void -} -export class MosSocketServer extends EventEmitter -⋮---- -/** */ -constructor(port: number, description: IncomingConnectionType, debug: boolean) -async dispose(sockets: Socket[]): Promise -⋮---- -// close clients -⋮---- -// close server -⋮---- -// this._socketServer.on('close', resolve) -⋮---- -// close any server connections: -⋮---- -/** */ -async listen(): Promise -⋮---- -// already listening -⋮---- -// Listens and handles error and events -⋮---- -public setDebug(debug: boolean): void -get port(): number -get portDescription(): IncomingConnectionType -⋮---- -/** */ -private _onClientConnection(socket: Socket) -⋮---- -/** */ -private _onServerError(error: Error) -⋮---- -// @todo: implement -⋮---- -/** */ -private _onServerClose() -⋮---- -// @todo: implement -⋮---- -private debugTrace(...strs: any[]) -⋮---- -// eslint-disable-next-line no-console - -================ -File: packages/connector/src/index.ts -================ -// Backwards compatibility -import { xml2js, pad, addTextElement, xmlToObject } from '@mos-connection/helper' - -================ -File: packages/helper/src/mosModel/profile0/heartBeat.ts -================ -import { getMosTypes, IMOSTime } from '@mos-connection/model' -⋮---- -import { addTextElementInternal } from '../../utils/Utils' -import { MosMessage, PortType } from '../MosMessage' -⋮---- -export class HeartBeat extends MosMessage -⋮---- -/** */ -constructor(port: PortType, time: IMOSTime | undefined, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile1/reqMosObjAll.ts -================ -import { addTextElementInternal } from '../../utils/Utils' -import { MosMessage } from '../MosMessage' -⋮---- -export class ReqMosObjAll extends MosMessage -⋮---- -/** */ -constructor(pause: number, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile2/roElementStat.ts -================ -import { MosMessage } from '../MosMessage' -import { getMosTypes, IMOSObjectStatus, IMOSString128, IMOSTime } from '@mos-connection/model' -import { addTextElementInternal } from '../../utils/Utils' -⋮---- -export enum ROElementStatType { - RO = 'RO', - STORY = 'STORY', - ITEM = 'ITEM', -} -export interface ROElementStatOptions { - type: ROElementStatType - roId: IMOSString128 - storyId?: IMOSString128 - itemId?: IMOSString128 - objId?: IMOSString128 - itemChannel?: IMOSString128 - status: IMOSObjectStatus -} -export class ROElementStat extends MosMessage -⋮---- -/** */ -constructor(options: ROElementStatOptions, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile2/ROListAll.ts -================ -import { MosMessage } from '../MosMessage' -import { IMOSRunningOrder } from '@mos-connection/model' -import { XMLRunningOrderBase } from './xmlConversion' -⋮---- -export class ROListAll extends MosMessage -⋮---- -/** */ -constructor(list: IMOSRunningOrder[], strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile3/index.ts -================ - - -================ -File: packages/helper/src/mosModel/profile3/mosListSearchableSchema.ts -================ -import { MosMessage } from '../MosMessage' -⋮---- -import { IMOSListSearchableSchema } from '@mos-connection/model' -import { XMLMosListSearchableSchema } from './xmlConversion' -⋮---- -export class MosListSearchableSchema extends MosMessage -⋮---- -constructor(options: IMOSListSearchableSchema, strict: boolean) -⋮---- -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile3/mosReqObjList.ts -================ -import { IMOSRequestObjectList } from '@mos-connection/model' -import { MosMessage } from '../MosMessage' -⋮---- -import { XMLMosRequestObjectList } from './xmlConversion' -⋮---- -export class MosReqObjList extends MosMessage -⋮---- -constructor(options: IMOSRequestObjectList, strict: boolean) -⋮---- -get messageXMLBlocks(): XMLBuilder.XMLElement - -================ -File: packages/helper/src/mosModel/profile4/roStory.ts -================ -import { MosMessage } from '../MosMessage' -import { IMOSROFullStory, IMOSItem } from '@mos-connection/model' -import { XMLROStoryBase } from '../profile2/xmlConversion' -import { XMLMosExternalMetaData, XMLMosObjects, XMLObjectPaths } from '../profile1/xmlConversion' -import { addTextElementInternal } from '../../utils/Utils' -⋮---- -export class ROStory extends MosMessage -⋮---- -/** */ -constructor(private fullStory: IMOSROFullStory, strict: boolean) -⋮---- -/** */ -get messageXMLBlocks(): XMLBuilder.XMLElement -⋮---- -// TODO: mosAbstract?: string? -// TODO: Channel?: MosString128? -// TODO: MacroIn?: MosString128? -// TODO: MacroOut?: MosString128? -⋮---- -// Note: the is sent in roStorySend -⋮---- -// TODO: what is this? - -================ -File: packages/helper/src/stringify/stringifyMosObject.ts -================ -import { getMosTypes, IMOSDuration, IMOSString128, IMOSTime, MosTypes, stringifyMosType } from '@mos-connection/model' -⋮---- -/** - * Converts a MOS data object, - * replacing Mos-centric types with their stringified equivalents - */ -export function stringifyMosObject = { - [P in keyof T]: T[P] extends M[0] ? Replacement : T[P] extends object ? DeepReplace : T[P] -} -type Replacement = M extends any ? ([T] extends [M[0]] ? M[1] : never) : never -⋮---- -export type Stringified = DeepReplace< - Original, - [IMOSString128, string] | [IMOSDuration, string] | [IMOSTime, string] -> - -================ -File: packages/helper/src/utils/ensureMethods.ts -================ -import { AnyXMLObject, AnyXMLValue, AnyXMLValueSingular } from '@mos-connection/model' -import { assertStringLiteral, isEmpty } from '../mosModel/lib' -⋮---- -/** - * Ensures that the returned value is an array. - * If the input is not an array, it will be wrapped in an array. - */ -export function ensureArray(v: A | B | B[]): (A | B)[] -export function ensureArray(v: T | T[]): T[] -⋮---- -/** - * Ensures that the returned value is a string literal. - * If the input value is not of the correct type, will throw (if strict) or return the fallback value. - */ -export function ensureStringLiteral( - xmlValue: AnyXMLValue, - options: T[], - strict: boolean, - fallback: T -): T -/** - * Ensures that the returned value is a string. - * If the input value is not of the correct type, will throw (if strict) or return the fallback value. - */ -export function ensureString(value: AnyXMLValue, strict: boolean, fallback = ''): string -⋮---- -/** - * Ensures that the returned value is an object. - * If the input value is not of the correct type, will throw (if strict) or return the fallback value. - */ -export function ensureXMLObject(value: AnyXMLValue, strict: boolean, fallback: AnyXMLObject = -⋮---- -/** - * Ensures that the returned value is a singular value (ie a string or undefined). - * If the input value is not of the correct type, will throw (if strict) or return the fallback value. - */ -export function ensureSingular(value: AnyXMLValue, strict: boolean): AnyXMLValueSingular -⋮---- -// Quick-fix if it is in a xml element: -⋮---- -// Quick-fix for empty object -⋮---- -/** - * Ensures that the returned value is an array containing only singular values - * If the input value is not of the correct type, will throw (if strict) or return an empty array - */ -export function ensureSingularArray(value: AnyXMLValue, strict: boolean): AnyXMLValueSingular[] -/** - * Ensures that the returned value is an array containing only XMLObjects - * If the input value is not of the correct type, will throw (if strict) or return an empty array - */ -export function ensureXMLObjectArray(value: AnyXMLValue, strict: boolean): AnyXMLObject[] -⋮---- -export function isSingular(value: AnyXMLValue): value is AnyXMLValueSingular -export function isSingularArray(value: AnyXMLValue): value is AnyXMLValueSingular[] -export function isXMLObject(value: AnyXMLValue): value is AnyXMLObject -function isXMLTextElement(xml: any): xml is TextElement -interface TextElement { - $type: 'text' - $name: string - text: string -} - -================ -File: packages/helper/src/utils/Errors.ts -================ -/** An error that occurred while parsing a reply to a sent mos message */ -export class MosReplyError extends Error -⋮---- -constructor(orgError: unknown, public readonly parsedReply: unknown | undefined) - -================ -File: packages/model/src/mosTypes/lib.ts -================ -export function pad(n: string | number, width: number, z?: string): string - -================ -File: packages/quick-mos/input/runningorders/README.md -================ -Quick-MOS will monitor the contents of this folder. - -Put any RunningOrders that you want the Quick-MOS server to expose in here. - -Quick-MOS supports - -- ts files (see examples in the folder) -- json files (see examples in the folder) -- Sofie Playlist/Rundown Snapshots - -================ -File: packages/quick-mos/input/config.ts -================ -import { Config } from '../src/.' // eslint-disable-line node/no-unpublished-import -⋮---- -// This is the NCS-id, you might need to specify it in your mos-client that connects to Quick-MOS. -⋮---- -// ports: { -// Set these if you have a mos-client running on other ports than standard: -// lower: 11540, -// upper: 11541, -// query: 11542, -// }, -⋮---- -// Set these if you want quick-mos to run on other ports than standard: -⋮---- -// Set to true to turn on debug-logging: - -================ -File: packages/quick-mos/input/util.ts -================ -import { MosModel } from '@mos-connection/connector' -import type { IMOSROFullStory, IMOSString128 } from '@mos-connection/model' -⋮---- -export type NormalizeMosAttributes = { - [P in keyof T]: T[P] extends IMOSString128 - ? string - : T[P] extends IMOSString128 | undefined - ? string | undefined - : T[P] extends string | number | null | undefined - ? T[P] - : NormalizeMosAttributes -} -⋮---- -export function fixStoryBody( - stories: Array> -): Array> - -================ -File: packages/helper/src/mosModel/profile3/xmlConversion.ts -================ -import { - AnyXMLValue, - IMOSListSearchableSchema, - IMOSObjectList, - IMOSRequestObjectList, - IMOSSearchField, -} from '@mos-connection/model' -import { omitUndefined } from '../lib' -import { getParseMosTypes, getXMLAttributes } from '../parseMosTypes' -import { addTextElementInternal } from '../../utils/Utils' -import { XMLMosObjects } from '../profile1' -import { ParseError } from '../ParseError' -import { ensureXMLObject, ensureXMLObjectArray, isXMLObject } from '../../utils/ensureMethods' -/* eslint-disable @typescript-eslint/no-namespace */ -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSRequestObjectList -export function toXML(xml: XMLBuilder.XMLElement, objList: IMOSRequestObjectList, strict: boolean): void -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSObjectList -export function toXML(xml: XMLBuilder.XMLElement, objList: IMOSObjectList, strict: boolean): void -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSListSearchableSchema -export function toXML(xml: XMLBuilder.XMLElement, schema: IMOSListSearchableSchema, strict: boolean): void - -================ -File: packages/helper/src/mosModel/profile4/xmlConversion.ts -================ -import { IMOSROFullStoryBodyItem, IMOSROFullStory, AnyXMLValue } from '@mos-connection/model' -import { XMLROStory, XMLMosItem } from '../profile2/xmlConversion' -import { omitUndefined } from '../lib' -import { getParseMosTypes } from '../parseMosTypes' -import { ParseError } from '../ParseError' -import { ensureXMLObject, ensureXMLObjectArray } from '../../utils/ensureMethods' -⋮---- -/* eslint-disable @typescript-eslint/no-namespace */ -⋮---- -export function fromXML(path: string, xml0: AnyXMLValue, strict: boolean): IMOSROFullStory -⋮---- -function fromXMLStoryBody(xml: AnyXMLValue, strict: boolean): IMOSROFullStoryBodyItem[] -⋮---- -/* - // Not able to implement this currently, need to change {arrayNotation: true} in xml2json option - let elementKeys = Object.keys(xml) - elementKeys.forEach((key: string) => { - // let elements - let d = xml[key] - - if (!Array.isArray(d)) d = [d] - - d.forEach((el: any) => { - let bodyItem: IMOSROFullStoryBodyItem = { - Type: key, - Content: el - } - body.push(bodyItem) - }) - }) - */ -⋮---- -// Temporary implementation: - -================ -File: packages/helper/src/mosModel/index.ts -================ -import { AnyXMLObject } from '@mos-connection/model' -/** @deprecated use AnyXMLObject instead */ -export type AnyXML = AnyXMLObject // for backwards compatibility - -================ -File: packages/helper/src/mosModel/MosMessage.ts -================ -import { getMosTypes, MosTypes } from '@mos-connection/model' -⋮---- -import { addTextElementInternal } from '../utils/Utils' -⋮---- -export type PortType = 'upper' | 'lower' | 'query' -export abstract class MosMessage -⋮---- -constructor(public port: PortType, protected readonly strict: boolean) -⋮---- -private static getNewMessageID(): number -⋮---- -// increments and returns a signed 32-bit int counting from 1, resetting to 1 when wrapping -⋮---- -/** */ -prepare(messageID?: number): void -⋮---- -/** */ -get messageID(): number -⋮---- -/** */ -toString(): string -⋮---- -/** */ -protected abstract get messageXMLBlocks(): XMLBuilder.XMLElement -⋮---- -/** */ - -================ -File: packages/helper/src/mosModel/ParseError.ts -================ -/* - Typical use case: - function (xml) { - try { - // do something with xml.storyBody - } catch (e) { - throw ParseError.handleCaughtError('storyBody', e) - } - } - -*/ -⋮---- -export class ParseError -⋮---- -static handleCaughtError(basePath: string, e: unknown): ParseError -static isParseError(e: unknown): e is ParseError -static fromError(e: Error, path: string): ParseError -⋮---- -static handleError(func: () => T, path: string): T -⋮---- -private constructor(path: string, message: string, stack?: string) -⋮---- -this.message = '' // Updated in updateVars() -⋮---- -public addBreadcrumb(path: string): void -private updateVars() -⋮---- -toString(): string - -================ -File: packages/model/src/index.ts -================ - - -================ -File: packages/model/src/xmlParse.ts -================ -/** Parsed xml data objects */ -export type AnyXMLObject = { [key: string]: AnyXMLValue } -/** Parsed xml data values */ -export type AnyXMLValue = AnyXMLValueSingular | AnyXMLValueSingular[] | AnyXMLObject | AnyXMLObject[] -/** Parsed xml data values, singular */ -export type AnyXMLValueSingular = string | undefined - -================ -File: package.json -================ -{ - "name": "mos-connection-monorepo", - "version": "0.0.0", - "repository": "https://github.com/nrkno/tv-automation-mos-connection", - "private": true, - "workspaces": [ - "packages/*" - ], - "scripts": { - "prepare": "husky install", - "lerna": "lerna", - "postinstall": "node scripts/update-packages.mjs", - "ci": "run build && run lint && run test", - "release:set-version": "lerna version --exact --no-changelog --no-push --yes && yarn fix-version", - "release:bump-release": "lerna version --exact --conventional-commits --conventional-graduate --tag-version-prefix='v' --no-push --force-publish='*' && yarn fix-version", - "release:bump-prerelease": "lerna version --exact --conventional-commits --conventional-prerelease --tag-version-prefix='v' --no-push --force-publish='*' && yarn fix-version", - "release:bump-prerelease-ci": "lerna version --exact --conventional-commits --conventional-prerelease --tag-version-prefix='v' --no-push --force-publish='*'", - "fix-version": "node scripts/fix-version.js", - "setup": "lerna bootstrap", - "reset": "node scripts/reset.js", - "build": "lerna run build --stream", - "build:changed": "lerna run build --since head --exclude-dependents --stream", - "lint": "lerna exec --parallel -- run -T eslint . --ext .ts,.tsx", - "lintfix": "run lint --fix", - "lint:changed": "lerna exec --since origin/master --include-dependents -- run -T eslint . --ext .js,.jsx,.ts,.tsx", - "test": "lerna run test --stream", - "test:changed": "lerna run --since origin/master --include-dependents test", - "test:coverage": "lerna run test:coverage --stream", - "lint-staged": "./node_modules/.bin/lint-staged", - "prettier": "cd $INIT_CWD && \"$PROJECT_CWD/node_modules/.bin/prettier\"", - "eslint": "cd $INIT_CWD && \"$PROJECT_CWD/node_modules/.bin/eslint\"", - "validate:dependencies": "yarn lerna exec --parallel yarn validate:dependencies && yarn license-validate", - "validate:dev-dependencies": "yarn lerna exec --parallel yarn validate:dev-dependencies", - "license-validate": "yarn sofie-licensecheck --allowPackages \"mos-examples@0.0.0;quick-mos@0.0.0\"" - }, - "devDependencies": { - "@sofie-automation/code-standard-preset": "2.5.1", - "@types/jest": "^29.5.2", - "deep-extend": "^0.6.0", - "glob": "^7.2.3", - "jest": "^29.5.0", - "lerna": "^6.6.2", - "lodash": "^4.17.21", - "rimraf": "^5.0.1", - "ts-jest": "^29.1.0", - "typescript": "~4.9.5" - }, - "engines": { - "node": ">=14.18.0" - }, - "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", - "packageManager": "yarn@3.6.0" -} - -================ -File: README.md -================ -# MOS-Connection - -[![Tests](https://github.com/nrkno/sofie-mos-connection/actions/workflows/node.yaml/badge.svg)](https://github.com/nrkno/sofie-mos-connection/actions/workflows/node.yaml) -[![codecov](https://codecov.io/gh/nrkno/sofie-mos-connection/branch/master/graph/badge.svg?token=LQL02uXajF)](https://codecov.io/gh/nrkno/sofie-mos-connection) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nrkno_tv-automation-mos-connection&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=nrkno_tv-automation-mos-connection) - -[![@mos-connection/connector NPM Version](https://img.shields.io/npm/v/%40mos-connection%2Fconnector)](https://www.npmjs.com/package/@mos-connection/connector) -[![@mos-connection/helper NPM Version](https://img.shields.io/npm/v/%40mos-connection%2Fhelper)](https://www.npmjs.com/package/@mos-connection/helper) -[![@mos-connection/model NPM Version](https://img.shields.io/npm/v/%40mos-connection%2Fmodel)](https://www.npmjs.com/package/@mos-connection/model) - -_MOS-Connection_ is a Node.js library to communicate, using the [MOS Protocol](http://mosprotocol.com/), with systems such as Newsroom Computer Systems (NRCS) or other MOS devices. - -Features include: - -- Acting as a _MOS Device_ (ie a client), connecting to an NRCS. -- Acting as a _NRCS_ (ie a server), allowing other MOS Devices to connect to it. -- Helpful tools for developing _MOS Plugins_ and parsing MOS data. -- A simple tool for testing MOS connections, called _Quick-MOS_. - -This library is developed as a part of the [**Sofie** TV Automation System](https://github.com/nrkno/Sofie-TV-automation/). - -## General Sofie System Information - -- [_Sofie_ Documentation](https://nrkno.github.io/sofie-core/) -- [_Sofie_ Releases](https://nrkno.github.io/sofie-core/releases) -- [Contribution Guidelines](CONTRIBUTING.md) -- [License](LICENSE) - ---- - -## Packages - -MOS-Connection consists of 4 packages: - -- **@mos-connection/connector** is a Node.js library is used to connect to MOS devices or act as a MOS Server/NCS. - The `helper` and `model` functionality is included in this library as well. -- **@mos-connection/helper** is a library that provides various functions useful for those that prepare/handle data that will be sent to (or has been received by) the MOS-connection. - The `model` functionality is included in this library as well. -- **@mos-connection/model** is a library that contains types and enums, useful for applications that handles MOS-data. -- There is also a helper application **QuickMos** designed to be a minimal mock MOS server for testing client applications. - -## Getting Started - -### Installation - -```bash -npm install @mos-connection/connector -``` - -### Usage - -\_See more examples in the [examples](/packages/examples/src) folder, or the [QuickMos](/packages/quick-mos/src/index.ts) implementation! - -```typescript -import { MosConnection } from '@mos-connection/connector' - -let mos = new MosConnection(new ConnectionConfig({ - mosID: 'my.mos.application', - acceptsConnections: true, - profiles: { - '0': true, - '1': true, - '2': true, - '4': true - }, - openRelay: true - debug: false -})) -mos.onConnection((device: MosDevice) => { // called whenever there is a new connection to a mos-device - if (device.hasConnection) { // true if we can send messages to the mos-server - device.getMachineInfo().then((lm) => { - console.log('Machineinfo', lm) - }) - } - // Setup callbacks to pipe data: - device.onRequestMachineInfo(() => {}) - device.onCreateRunningOrder((ro) => {}) - device.onDeleteRunningOrder((RunningOrderID: MosString128) => {}) - device.onReadyToAir(() => {}) - // ... -}) -``` - -### Quick-MOS - -"Quick-MOS" is a simple MOS application that can be used to test the MOS-connection library. - -It reads data-files from disk and pretends to be an NRCS, so you can connect other MOS-clients to it. - -See [Quick-MOS](/packages/quick-mos/README.md) for more information. - -## MOS Support - -The MOS-Connection library currently supports the [MOS Protocol version **2.8.5**](https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS_Protocol_Version_2.8.5_Final.htm). - -### Supported MOS Profiles - -| Profile | Status | -| --------- | --------------------- | -| Profile 0 | Implemented | -| Profile 1 | Implemented | -| Profile 2 | Implemented | -| Profile 3 | Implemented | -| Profile 4 | Implemented | -| Profile 5 | Not implemented (yet) | -| Profile 6 | Not implemented (yet) | -| Profile 7 | Not implemented (yet) | - -Pull Requests for the remaining profiles would be happily accepted! - ---- - -_The NRK logo is a registered trademark of Norsk rikskringkasting AS. The license does not grant any right to use, in any way, any trademarks, service marks or logos of Norsk rikskringkasting AS._ - -================ -File: .github/workflows/publish-prerelease.yaml -================ -name: Publish prerelease - -on: - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -jobs: - test: - name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - node_version: ['14', '16', '18', '20'] - os: [ubuntu-latest] # [windows-latest, macOS-latest] - - timeout-minutes: 5 - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Use Node.js ${{ matrix.node_version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node_version }} - - name: Cache node_modules - uses: actions/cache@v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - - - name: Prepare Environment - run: | - yarn - yarn build - env: - CI: true - - name: Run unit tests - run: | - yarn test -- --maxWorkers=2 --forceExit - env: - CI: true - - prerelease: - name: Publish to NPM - runs-on: ubuntu-latest - timeout-minutes: 15 - - needs: - - test - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - persist-credentials: false - - name: Use Node.js 16 - uses: actions/setup-node@v4 - with: - node-version: 16 - - name: Check release is desired - id: do-publish - run: | - if [ -z "${{ secrets.NPM_TOKEN }}" ]; then - echo "No Token" - elif [[ "${{ github.ref }}" == "refs/heads/master" ]]; then - echo "Publish nightly" - echo "publish=nightly" >> $GITHUB_OUTPUT - else - echo "Publish experimental" - echo "publish=experimental" >> $GITHUB_OUTPUT - fi - - name: Prepare Environment - if: ${{ steps.do-publish.outputs.publish }} - run: | - yarn install - yarn build - env: - CI: true - - name: Bump version - if: ${{ steps.do-publish.outputs.publish }} - run: | - git config --global user.email "info@superfly.tv" - git config --global user.name "superflytvab" - - COMMIT_TIMESTAMP=$(git log -1 --pretty=format:%ct HEAD) - COMMIT_DATE=$(date -d @$COMMIT_TIMESTAMP +%Y%m%d-%H%M%S) - GIT_HASH=$(git rev-parse --short HEAD) - PRERELEASE_TAG=nightly-$(echo "${{ github.ref_name }}" | sed -r 's/[^a-z0-9]+/-/gi') - yarn release:bump-prerelease-ci --no-changelog --no-commit-hooks --preid "$PRERELEASE_TAG-$COMMIT_DATE-$GIT_HASH" prerelease --yes - env: - CI: true - - name: Publish to NPM - if: ${{ steps.do-publish.outputs.publish }} - run: | - echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc - yarn lerna publish from-package --tag-version-prefix='' --dist-tag ${{ steps.do-publish.outputs.publish }} --yes --no-verify-access - env: - CI: true - -================ -File: packages/connector/src/connection/mosMessageParser.ts -================ -import { EventEmitter } from 'eventemitter3' -import { MosModel, xml2js } from '@mos-connection/helper' -⋮---- -export interface MosMessageParserEvents { - message: (parsedData: ParsedMosMessage, messageString: string) => void -} -export class MosMessageParser extends EventEmitter -⋮---- -constructor(private description: string) -⋮---- -public parseMessage(messageString: string): void -⋮---- -// parse as many messages as possible from the data -⋮---- -// whitespace before a mos message is junk -⋮---- -// Nothing was plucked, so abort -⋮---- -private _tryParseData() -⋮---- -const startMatch = '' // -const endMatch = '' // -⋮---- -// No start tag, so looks like we have jibberish -⋮---- -// trim off anything before , as we'll never be able to parse that anyway. -⋮---- -// We have an end tag too -⋮---- -/** null = message is not complete */ -⋮---- -// fast-path: -⋮---- -// Figure out where in the message the end tag closes the start tag: -⋮---- -// Hmm, something is wrong, there should never be more end tags than start tags -⋮---- -// trim off anything before this end tag, we'll never be able to parse that anyway. -⋮---- -// We have a complete message, so pluck it out -⋮---- -const data = xml2js(messageString) as any as ParsedMosMessage // , { compact: true, trim: true, nativeType: true }) -⋮---- -// eslint-disable-next-line no-console -⋮---- -// eslint-disable-next-line no-console -⋮---- -// this.emit('error', e) -⋮---- -private debugTrace(str: string) -⋮---- -// Suppress console spam: -⋮---- -// eslint-disable-next-line no-console -⋮---- -/** Returns a list of indexes for the occurences of searchString in str */ -private indexesOf(str: string, searchString: string): number[] -⋮---- -// ^ Just to avoid an infinite loop -⋮---- -/** Definition of an incoming MOS Message */ -export interface ParsedMosMessage { - mos: { - ncsID: string - mosID: string - messageID?: string // Note: messageID is optional for some messages in older versions of the MOS Protocol - - [key: string]: MosModel.AnyXMLValue - } -} -⋮---- -messageID?: string // Note: messageID is optional for some messages in older versions of the MOS Protocol - -================ -File: packages/helper/src/mosModel/profile2/xmlConversion.ts -================ -import { - IMOSROStory, - IMOSItem, - IMOSROFullStory, - IMOSRunningOrderBase, - IMOSRunningOrder, - IMOSROAckStory, - IMOSROAckItem, - IMOSROAckObject, - AnyXMLValue, -} from '@mos-connection/model' -import { has, omitUndefined } from '../lib' -import { ensureArray, ensureXMLObject } from '../../utils/ensureMethods' -import { ROAck } from './ROAck' -import { XMLMosExternalMetaData, XMLMosObjects, XMLObjectPaths } from '../profile1/xmlConversion' -import { addTextElementInternal } from '../../utils/Utils' -import { getParseMosTypes } from '../parseMosTypes' -import { ParseError } from '../ParseError' -/* eslint-disable @typescript-eslint/no-namespace */ -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): ROAck -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSRunningOrderBase -export function toXML(xmlRo: XMLBuilder.XMLElement, ro: IMOSRunningOrderBase, strict: boolean): void -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSRunningOrder -⋮---- -export function fromXML(path: string, xmlStories: AnyXMLValue, strict: boolean): IMOSROStory[] -⋮---- -export function toXML( - xmlStory: XMLBuilder.XMLElement, - story: IMOSROStory | IMOSROFullStory, - strict: boolean -): void -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSROStory -⋮---- -// TODO: Add & test Number, ObjectID, MOSID, mosAbstract, Paths -// Channel, EditorialStart, EditorialDuration, UserTimingDuration, Trigger, MacroIn, MacroOut, MosExternalMetaData -// MosExternalMetaData: handleError(MOSExternalMetaData.fromXML(xml.mosExternalMetadata], 'mosExternalMetadata'). -⋮---- -// Note: the is sent in roStorySend -⋮---- -export function toXML(xmlRoot: XMLBuilder.XMLElement, story: IMOSROStory, strict: boolean): void -⋮---- -export function fromXML(path: string, xmlItems: AnyXMLValue, strict: boolean): Array -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSItem -⋮---- -// Note: the is sent in roStorySend -⋮---- -export function toXML(root: XMLBuilder.XMLElement, item: IMOSItem, strict: boolean): void -⋮---- -// TODO: MosObjects - -================ -File: packages/helper/src/utils/Utils.ts -================ -import { xml2js as xmlParser } from 'xml-js' -⋮---- -import { - AnyXMLValue, - getMosTypes, - IMOSDuration, - IMOSString128, - IMOSTime, - stringifyMosType, -} from '@mos-connection/model' -import { MosModel } from '..' -⋮---- -export function xml2js(messageString: string): MosModel.AnyXMLObject -⋮---- -nativeType: false, // we want to NOT auto-convert types, to avoid ambiguity -⋮---- -// common tags we typically want to know the order of the contents of: -⋮---- -/** - * Doing a post-order tree traversal we try to make the objectified tree as compact as possible. - * Whenever we find an "orderedTag" we keep the order of it's children. - * - * ps: post-order means we make a node's children as compact as possible first, and then try to make - * that node compact. - */ -const concatChildrenAndTraverseObject = (element: -⋮---- -// pure text node, hoist it up: -⋮---- -// leave it as is -⋮---- -// if the element name is contained in the set of orderedTag names we don't make it any more compact -⋮---- -// make array compact: -⋮---- -// might contain something useful like attributes -⋮---- -// all elements are unique -⋮---- -export function addTextElement( - root: XMLBuilder.XMLElement, - elementName: string, - text?: string | number | null | IMOSString128 | IMOSTime | IMOSDuration, - attributes?: { [key: string]: string }, - strict = true -): XMLBuilder.XMLElement -export function addTextElementInternal( - root: XMLBuilder.XMLElement, - elementName: string, - content: AnyXMLValue | number | null | IMOSString128 | IMOSTime | IMOSDuration, - attributes: { [key: string]: string | undefined } | undefined, - strict: boolean -): XMLBuilder.XMLElement -/** - * Utility-function to convert a XMLBuilder.XMLElement into the generic object which can be sent - * into the ***.fromXML(xml:any) methods in MosModel - */ -export function xmlToObject(root: XMLBuilder.XMLElement): any -⋮---- -// @ts-expect-error hack -⋮---- -// @ts-expect-error hack -⋮---- -// @ts-expect-error hack -⋮---- -obj[ch.name] = [obj[ch.name]] // make an array - -================ -File: packages/helper/src/index.ts -================ -// not addTextElementInternal - -================ -File: packages/model/src/mosTypes/mosString128.ts -================ -export interface IMOSString128 { - _mosString128: string - /** @deprecated use getMosTypes().mosString128.stringify() instead! */ - toString: never -} -⋮---- -/** @deprecated use getMosTypes().mosString128.stringify() instead! */ -⋮---- -export function create(anyValue: AnyValue, strict: boolean): IMOSString128 -⋮---- -// is empty? -⋮---- -export type AnyValue = string | { text: string; type: string } | IMOSString128 | undefined -⋮---- -export function validate(mosString128: IMOSString128, strict: boolean): void -export function valueOf(mosString128: IMOSString128): string -⋮---- -if (typeof mosString128 === 'string') return mosString128 // helpful hack -⋮---- -export function stringify(mosString128: IMOSString128): string -⋮---- -if (typeof mosString128 === 'string') return mosString128 // helpful hack -⋮---- -export function is(mosString128: IMOSString128 | any): mosString128 is IMOSString128 -export function fallback(): IMOSString128 - -================ -File: packages/model/src/model.ts -================ -import { IMOSTime, IMOSString128, IMOSDuration, IMOSExternalMetaData } from './mosTypes' -import { AnyXMLValue } from './xmlParse' -⋮---- -export interface IMOSROAction { - RunningOrderID: IMOSString128 -} -export interface IMOSStoryAction extends IMOSROAction { - StoryID: IMOSString128 -} -export interface IMOSItemAction extends IMOSStoryAction { - ItemID: IMOSString128 -} -export interface IMOSROReadyToAir { - ID: IMOSString128 - Status: IMOSObjectAirStatus -} -export interface IMOSRunningOrderStatus { - ID: IMOSString128 - Status: IMOSObjectStatus - Time: IMOSTime -} -export interface IMOSStoryStatus { - RunningOrderId: IMOSString128 - ID: IMOSString128 - Status: IMOSObjectStatus - Time: IMOSTime -} -export interface IMOSItemStatus { - RunningOrderId: IMOSString128 - StoryId: IMOSString128 - ID: IMOSString128 - Status: IMOSObjectStatus - Time: IMOSTime - ObjectId?: IMOSString128 - Channel?: IMOSString128 -} -export interface IMOSRunningOrderBase { - ID: IMOSString128 // running order id - Slug: IMOSString128 - DefaultChannel?: IMOSString128 - EditorialStart?: IMOSTime - EditorialDuration?: IMOSDuration - Trigger?: IMOSString128 - MacroIn?: IMOSString128 - MacroOut?: IMOSString128 - MosExternalMetaData?: Array -} -⋮---- -ID: IMOSString128 // running order id -⋮---- -export interface IMOSRunningOrder extends IMOSRunningOrderBase { - Stories: Array -} -export interface IMOSStory { - ID: IMOSString128 - Slug?: IMOSString128 - Number?: IMOSString128 - MosExternalMetaData?: Array -} -export interface IMOSROStory extends IMOSStory { - Items: Array -} -export interface IMOSROFullStory extends IMOSStory { - RunningOrderId: IMOSString128 - Body: Array -} -⋮---- -export type IMOSROFullStoryBodyItem = IMOSROFullStoryBodyStoryItem | IMOSROFullStoryBodyOtherItem -export type IMOSROFullStoryBodyStoryItem = { - itemType: 'storyItem' - Type: 'storyItem' - Content: IMOSItem // TODO: Make this stricter? IMOSItemObject?? -} -⋮---- -Content: IMOSItem // TODO: Make this stricter? IMOSItemObject?? -⋮---- -export type IMOSROFullStoryBodyOtherItem = { - itemType: 'other' - Type: string // e.g. 'p' - Content: AnyXMLValue -} -⋮---- -Type: string // e.g. 'p' -⋮---- -export interface IMOSItem { - ID: IMOSString128 - Slug?: IMOSString128 - ObjectSlug?: IMOSString128 - ObjectID: IMOSString128 - MOSID: string - mosAbstract?: string - Paths?: Array - Channel?: IMOSString128 - EditorialStart?: number - EditorialDuration?: number - Duration?: number - TimeBase?: number - UserTimingDuration?: number - Trigger?: AnyXMLValue - MacroIn?: IMOSString128 - MacroOut?: IMOSString128 - MosExternalMetaData?: Array - MosObjects?: Array -} -⋮---- -export interface IMOSAck { - ID: IMOSString128 - Revision: number // max 999 - Status: IMOSAckStatus - Description: IMOSString128 -} -⋮---- -Revision: number // max 999 -⋮---- -export interface IMOSROAck { - ID: IMOSString128 // Running order id - Status: IMOSString128 // OK or error desc - Stories: Array -} -⋮---- -ID: IMOSString128 // Running order id -Status: IMOSString128 // OK or error desc -⋮---- -export interface IMOSROAckStory { - ID: IMOSString128 // storyID - Items: Array -} -⋮---- -ID: IMOSString128 // storyID -⋮---- -export interface IMOSROAckItem { - ID: IMOSString128 - Channel: IMOSString128 - Objects: Array -} -⋮---- -export interface IMOSROAckObject { - Status: 'OK' | string - ID: IMOSString128 -} -⋮---- -export interface IProfiles { - [key: string]: boolean | undefined - '0': boolean - '1'?: boolean - '2'?: boolean - '3'?: boolean - '4'?: boolean - '5'?: boolean - '6'?: boolean - '7'?: boolean -} -⋮---- -/** https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObj */ -export interface IMOSObject { - ID?: IMOSString128 - Slug: IMOSString128 - MosAbstract?: AnyXMLValue // xml json - /** A stringified version of MosAbstract, where all HTML have been flattened */ - MosAbstractStr?: string - Group?: string - Type: IMOSObjectType - TimeBase: number - Revision?: number // max 999 - Duration: number - Status?: IMOSObjectStatus - AirStatus?: IMOSObjectAirStatus - Paths?: Array - CreatedBy?: IMOSString128 - Created?: IMOSTime - ChangedBy?: IMOSString128 // if not present, defaults to CreatedBy - Changed?: IMOSTime // if not present, defaults to Created - Description?: AnyXMLValue // xml json - /** A stringified version of Description, where all HTML have been flattened */ - DescriptionStr?: string - MosExternalMetaData?: Array - MosItemEditorProgID?: IMOSString128 -} -⋮---- -MosAbstract?: AnyXMLValue // xml json -/** A stringified version of MosAbstract, where all HTML have been flattened */ -⋮---- -Revision?: number // max 999 -⋮---- -ChangedBy?: IMOSString128 // if not present, defaults to CreatedBy -Changed?: IMOSTime // if not present, defaults to Created -Description?: AnyXMLValue // xml json -/** A stringified version of Description, where all HTML have been flattened */ -⋮---- -/** - * Returns selected object descriptions from a MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjList - */ -export interface IMOSObjectList { - username: string - queryID: string - listReturnStart: number - listReturnEnd: number - listReturnTotal: number - listReturnStatus?: string - list?: Array -} -⋮---- -/** - * mosReqObjList is a mechanism used by a NCS to retrieve only selected object descriptions from a MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObjList - */ -export interface IMOSRequestObjectList { - username: string - queryID: IMOSString128 - listReturnStart: number | null - listReturnEnd: number | null - generalSearch: IMOSString128 - mosSchema: string - searchGroups: Array<{ - searchFields: Array - }> -} -/** @see IMOSRequestObjectList */ -export interface IMOSSearchField { - XPath: string - sortByOrder?: number - sortType?: string -} -⋮---- -/** - * mosListSearchableSchema is a mechanism used by the MOS to send a pointer to a schema in which searchable fields are defined for the NCS device. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosListSearchableSchema - */ -export interface IMOSListSearchableSchema { - username: string - mosSchema: string -} -⋮---- -export enum IMOSObjectType { - STILL = 'STILL', - AUDIO = 'AUDIO', - VIDEO = 'VIDEO', - OTHER = 'OTHER', // unknown/not specified -} -⋮---- -OTHER = 'OTHER', // unknown/not specified -⋮---- -export enum IMOSObjectStatus { - NEW = 'NEW', - UPDATED = 'UPDATED', - MOVED = 'MOVED', - BUSY = 'BUSY', - DELETED = 'DELETED', - NCS_CTRL = 'NCS CTRL', - MANUAL_CTRL = 'MANUAL CTRL', - READY = 'READY', - NOT_READY = 'NOT READY', - PLAY = 'PLAY', - STOP = 'STOP', -} -⋮---- -export enum IMOSAckStatus { - ACK = 'ACK', - NACK = 'NACK', -} -⋮---- -export enum IMOSObjectAirStatus { - READY = 'READY', - NOT_READY = 'NOT READY', -} -⋮---- -export interface IMOSObjectPath { - Type: IMOSObjectPathType - Description: string - Target: string // Max 255 -} -⋮---- -Target: string // Max 255 -⋮---- -export enum IMOSObjectPathType { - PATH = 'PATH', - PROXY_PATH = 'PROXY PATH', - METADATA_PATH = 'METADATA PATH', -} -⋮---- -export interface MosItemReplaceOptions { - roID: IMOSString128 - storyID: IMOSString128 - item: IMOSItem -} -⋮---- -export enum IMOSListMachInfoDefaultActiveXMode { - MODALDIALOG = 'MODALDIALOG', - MODELESS = 'MODELESS', - CONTAINED = 'CONTAINED', - TOOLBAR = 'TOOLBAR', -} -⋮---- -export interface IMOSListMachInfo { - /** Used in MOS ActiveX messages. Manufacturer: Text description. 128 chars max. */ - manufacturer: IMOSString128 - /** Model: Text description. 128 chars max. */ - model: IMOSString128 - /** HW Revision: 128 chars max. */ - hwRev: IMOSString128 - /** Software Revision: (MOS) Text description. 128 chars max., example: '2.1.0.37' */ - swRev: IMOSString128 - /** Date of Manufacture. */ - DOM: IMOSString128 - /** Serial Number: text serial number. 128 chars max. ex: '927748927' */ - SN: IMOSString128 - /** Identification of a Machine: text. 128 chars max. */ - ID: IMOSString128 - /** Time: Time object changed status. Format is YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] */ - time: IMOSTime - /** Operational Time: date and time of last machine start. Format is YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] */ - opTime?: IMOSTime - /** MOS Revision: Text description. 128 chars max. */ - mosRev: IMOSString128 - - supportedProfiles: { - deviceType: 'NCS' | 'MOS' | 'N/A' - profile0?: boolean - profile1?: boolean - profile2?: boolean - profile3?: boolean - profile4?: boolean - profile5?: boolean - profile6?: boolean - profile7?: boolean - } - /** defaultActiveX contains tags that describe the correct settings for the ActiveX control (NOTE: no two elements can have the same value). */ - defaultActiveX?: Array - mosExternalMetaData?: Array -} -⋮---- -/** Used in MOS ActiveX messages. Manufacturer: Text description. 128 chars max. */ -⋮---- -/** Model: Text description. 128 chars max. */ -⋮---- -/** HW Revision: 128 chars max. */ -⋮---- -/** Software Revision: (MOS) Text description. 128 chars max., example: '2.1.0.37' */ -⋮---- -/** Date of Manufacture. */ -⋮---- -/** Serial Number: text serial number. 128 chars max. ex: '927748927' */ -⋮---- -/** Identification of a Machine: text. 128 chars max. */ -⋮---- -/** Time: Time object changed status. Format is YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] */ -⋮---- -/** Operational Time: date and time of last machine start. Format is YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] */ -⋮---- -/** MOS Revision: Text description. 128 chars max. */ -⋮---- -/** defaultActiveX contains tags that describe the correct settings for the ActiveX control (NOTE: no two elements can have the same value). */ -⋮---- -export interface IMOSDefaultActiveX { - /** Used in MOS ActiveX messages. How the ActiveX Plug-In window appears in the NCS Host window: MODALDIALOG, MODELESS, CONTAINED, TOOLBAR. */ - mode: IMOSListMachInfoDefaultActiveXMode - /** controlFileLocation is the file location for the default ActiveX control. */ - controlFileLocation: string - /** Defined by MOS 128 characters max */ - controlSlug: IMOSString128 - /** This value represents the key/classId key used to load the ActiveX from the registry., ex: "contained.containedCTRL.1" */ - controlName: string - /** This value represents the parameters that can be passed to an ActiveX. ex "URL=http:" */ - controlDefaultParams: string -} -⋮---- -/** Used in MOS ActiveX messages. How the ActiveX Plug-In window appears in the NCS Host window: MODALDIALOG, MODELESS, CONTAINED, TOOLBAR. */ -⋮---- -/** controlFileLocation is the file location for the default ActiveX control. */ -⋮---- -/** Defined by MOS 128 characters max */ -⋮---- -/** This value represents the key/classId key used to load the ActiveX from the registry., ex: "contained.containedCTRL.1" */ -⋮---- -/** This value represents the parameters that can be passed to an ActiveX. ex "URL=http:" */ - -================ -File: packages/model/src/mosTypes.ts -================ -import { AnyXMLValue } from './xmlParse' -⋮---- -/** - * Returns utility-functions for handling of MosTypes. - * @example - * const mosTypes = getMosTypes(true) - * const myString128 = mosTypes.mosString128.create('Hello world') - * const myString = mosTypes.mosString128.stringify(myString128) - * @param strict If true, creating out-of-spec values will throw an error (Example: Creating longer-than-128-characters long MosString128). - */ -export function getMosTypes(strict: boolean): MosTypes -export interface MosTypes { - strict: boolean - mosString128: MosType - mosDuration: MosType - mosTime: MosType -} -/** - * If value is a MosType, stringify it. - * Throw error otherwise - */ -export function stringifyMosType( -⋮---- -export interface MosType { - /** Creates a MosType using provided data. The MosType is then used in data sent into MOS-connection */ - create: (anyValue: CreateValue) => Serialized - /** (internal function) Validate the data. Throws if something is wrong with the data */ - validate: (value: Serialized) => void - /** Returns the value of the MosType */ - valueOf: (value: Serialized) => Value - /** Returns a stringified representation of the MosType */ - stringify: (value: Serialized) => string - /** Returns true if the provided data is of this MosType */ - is: (value: Serialized | any) => value is Serialized - - /** Returns a fallback value, used to replace missing or non-parsable data in non-strict mode */ - fallback: () => Serialized -} -⋮---- -/** Creates a MosType using provided data. The MosType is then used in data sent into MOS-connection */ -⋮---- -/** (internal function) Validate the data. Throws if something is wrong with the data */ -⋮---- -/** Returns the value of the MosType */ -⋮---- -/** Returns a stringified representation of the MosType */ -⋮---- -/** Returns true if the provided data is of this MosType */ -⋮---- -/** Returns a fallback value, used to replace missing or non-parsable data in non-strict mode */ -⋮---- -interface InternalMosType { - create: (anyValue: any, strict: boolean) => Serialized - validate: (value: Serialized, strict: boolean) => void - valueOf(value: Serialized): Value - stringify(value: Serialized): string - is(value: Serialized | any): value is Serialized - fallback(): Serialized -} -⋮---- -valueOf(value: Serialized): Value -stringify(value: Serialized): string -is(value: Serialized | any): value is Serialized -fallback(): Serialized -⋮---- -function getMosType( - mosType: InternalMosType, - strict: boolean -): MosType -⋮---- -export interface IMOSExternalMetaData { - MosScope?: IMOSScope - MosSchema: string - MosPayload: AnyXMLValue -} -export enum IMOSScope { - OBJECT = 'OBJECT', - STORY = 'STORY', - PLAYLIST = 'PLAYLIST', -} - -================ -File: packages/quick-mos/src/convertFromSofieSnapshot.ts -================ -import { getMosTypes, IMOSROFullStory, IMOSROStory, IMOSRunningOrder } from '@mos-connection/model' -⋮---- -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -⋮---- -export function convertFromSofieSnapshot( - filePath: string, - snapShotData: any -): -⋮---- -function fixStoryBody(stories: any[]) - -================ -File: packages/helper/src/mosModel/lib.ts -================ -import { AnyXMLValue } from '@mos-connection/model' -⋮---- -export function isEmpty(obj: unknown): boolean -⋮---- -/** Return true if the object has a property */ -export function has(obj: unknown, property: string): boolean -⋮---- -/** - * Asserts that a string type is of a certain literal. - * Example usage: const str = assertStringLiteral('foo', ['foo', 'bar']) // str is of type 'foo' | 'bar' - */ -export function assertStringLiteral(value: string, options: T[]): value is T -⋮---- -/** Type assertion */ -export function literal(o: T): T -⋮---- -/** - * Removes undefined properties from an object - */ -export function omitUndefined(obj: -export function flattenXMLText(xml: AnyXMLValue, strict: boolean): string -function flattenXMLTextInner(strings: string[], xml: AnyXMLValue, strict: boolean): void -⋮---- -// else - -================ -File: .github/workflows/node.yaml -================ -name: Node CI - Lint and Test - -on: - push: - branches: - - '**' - tags: - - 'v[0-9]+.[0-9]+.[0-9]+*' - pull_request: - -jobs: - lint: - name: Lint and Type check - runs-on: ubuntu-latest - timeout-minutes: 3 - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '16' - - name: Cache node_modules - uses: actions/cache@v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - - - name: Prepare Environment - run: yarn - env: - CI: true - - name: Type check - run: yarn build - env: - CI: true - - name: Lint - run: | - yarn lint - env: - CI: true - - test: - name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - node_version: ['14', '16', '18', '20'] - os: [ubuntu-latest] # [windows-latest, macOS-latest] - - timeout-minutes: 5 - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Use Node.js ${{ matrix.node_version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node_version }} - - name: Cache node_modules - uses: actions/cache@v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - - - name: Prepare Environment - run: | - yarn - yarn build - env: - CI: true - - name: Run unit tests - run: | - yarn test -- --maxWorkers=2 --forceExit --coverage - env: - CI: true - - name: Send coverage - uses: codecov/codecov-action@v5 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - validate-dependencies: - name: Validate production dependencies - runs-on: ubuntu-latest - continue-on-error: false - timeout-minutes: 5 - - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Use Node.js 16 - uses: actions/setup-node@v4 - with: - node-version: 16 - - name: Prepare Environment - run: | - yarn install - env: - CI: true - - name: Validate dependencies - run: | - yarn validate:dependencies - env: - CI: true - - release: - name: Release - runs-on: ubuntu-latest - timeout-minutes: 5 - - # only run for tags - if: contains(github.ref, 'refs/tags/') - - needs: - - test - - lint - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - persist-credentials: false - - name: Use Node.js 16 - uses: actions/setup-node@v4 - with: - node-version: 16 - - name: Check release is desired - id: do-publish - run: | - if [ -z "${{ secrets.NPM_TOKEN }}" ]; then - echo "No Token" - else - cd packages/connector - PACKAGE_NAME=@mos-connection/connector - PUBLISHED_VERSION=$(yarn npm info --json $PACKAGE_NAME | jq -c '.version' -r) - THIS_VERSION=$(node -p "require('./package.json').version") - # Simple bash helper to comapre version numbers - verlte() { - [ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ] - } - verlt() { - [ "$1" = "$2" ] && return 1 || verlte $1 $2 - } - if verlt $PUBLISHED_VERSION $THIS_VERSION - then - echo "Publishing latest" - echo "tag=latest" >> $GITHUB_OUTPUT - else - echo "Publishing hotfix" - echo "tag=hotfix" >> $GITHUB_OUTPUT - fi - - fi - - name: Prepare Environment - if: ${{ steps.do-publish.outputs.tag }} - run: | - yarn install - env: - CI: true - - name: Build - if: ${{ steps.do-publish.outputs.tag }} - run: | - yarn build - env: - CI: true - - name: Publish to NPM - if: ${{ steps.do-publish.outputs.tag }} - run: | - echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc - yarn lerna publish from-package --tag-version-prefix='' --dist-tag ${{ steps.do-publish.outputs.tag }} --yes --no-verify-access - env: - CI: true - -================ -File: packages/helper/src/mosModel/profile0/xmlConversion.ts -================ -import { - AnyXMLValue, - IMOSDefaultActiveX, - IMOSListMachInfo, - IMOSListMachInfoDefaultActiveXMode, - IMOSString128, -} from '@mos-connection/model' -import { has, omitUndefined } from '../lib' -import { ensureArray, ensureStringLiteral, ensureXMLObject } from '../../utils/ensureMethods' -import { addTextElementInternal } from '../../utils/Utils' -import { getParseMosTypes, getXMLAttributes } from '../parseMosTypes' -import { XMLMosExternalMetaData } from '../profile1' -import { ParseError } from '../ParseError' -⋮---- -/* eslint-disable @typescript-eslint/no-namespace */ -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSString128[] -⋮---- -/* eslint-disable @typescript-eslint/no-namespace */ -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSListMachInfo -export function toXML(xmlListMachInfo: XMLBuilder.XMLElement, info: IMOSListMachInfo, strict: boolean): void -⋮---- -export function fromXML( - path: string, - xmlSupportedProfiles: AnyXMLValue, - strict: boolean -): IMOSListMachInfo['supportedProfiles'] -⋮---- -// Note: .profiles are added below -⋮---- -// @ts-expect-error hack -⋮---- -export function toXML( - xml: XMLBuilder.XMLElement, - supportedProfiles: IMOSListMachInfo['supportedProfiles'], - strict: boolean -): void -⋮---- -// let p = addTextElement(root, 'supportedProfiles').att('deviceType', info.supportedProfiles.deviceType) -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSDefaultActiveX[] -export function toXML(xml: XMLBuilder.XMLElement, objs: IMOSDefaultActiveX[], strict: boolean): void - -================ -File: packages/helper/src/mosModel/profile1/xmlConversion.ts -================ -import { - IMOSObject, - IMOSAck, - IMOSAckStatus, - IMOSObjectPath, - IMOSObjectPathType, - IMOSExternalMetaData, - AnyXMLValue, - AnyXMLValueSingular, - IMOSObjectType, - IMOSObjectStatus, - IMOSObjectAirStatus, - IMOSScope, -} from '@mos-connection/model' -import { AnyXMLObject, flattenXMLText, has, isEmpty, literal, omitUndefined } from '../lib' -import { ensureArray, ensureXMLObject, ensureXMLObjectArray, isXMLObject } from '../../utils/ensureMethods' -import { addTextElementInternal } from '../../utils/Utils' -import { getParseMosTypes } from '../parseMosTypes' -import { ParseError } from '../ParseError' -⋮---- -/* eslint-disable @typescript-eslint/no-namespace */ -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSAck -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSObject[] -export function toXML(xml: XMLBuilder.XMLElement, objs: IMOSObject[], strict: boolean): void -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSObject -export function toXML(xml: XMLBuilder.XMLElement, obj: IMOSObject, strict: boolean): void -⋮---- -if (obj.Description) addTextElementInternal(xml, 'description', obj.Description, undefined, strict) // not handled (todo) -⋮---- -export function fromXML(path: string, xmlObjPaths: AnyXMLValue, strict: boolean): IMOSObjectPath[] -⋮---- -const xmlToArray = (obj: AnyXMLValue): IMOSObjectPath[] => -⋮---- -// Possible inputs: -/* - Single obj path - { - objPath: '41cfc641849c417eaa10e4f3c377f181', - techDescription: 'VIDEO' - } - - { - objPath: { - text: '41cfc641849c417eaa10e4f3c377f181', - techDescription: 'VIDEO' - }, - objProxyPath: { - text: 'http://server/proxy/clipe.wmv', - techDescription: 'WM9 750Kbps' - } - } - - { - objPath: { attributes: { techDescription: 'asdfasdf' }, text: 'asdfasdf' }, - objMetadataPath: { attributes: { techDescription: 'skdjhfb' }, text: '8372h4fv' } - } - { - objPath: [ - { - '$name': 'objPath', - '$type': 'text', - text: '41cfc641849c417eaa10e4f3c377f181', - techDescription: 'VIDEO' - }, - { - '$name': 'objPath', - '$type': 'text', - text: 'abc123.mp4', - techDescription: 'MYVIDEO' - } - ], - objProxyPath: { - '$name': 'objProxyPath', - '$type': 'text', - text: 'http://server/proxy/clipe.wmv', - techDescription: 'WM9 750Kbps' - } - } - */ -⋮---- -// When there is only one: ------------------------------------------- -⋮---- -// A single objPath -⋮---- -// A single objProxyPath -⋮---- -// A single objMetadataPath -⋮---- -// When there are multiple: ------------------------------------------- -⋮---- -export function toXML(xmlItem: XMLBuilder.XMLElement, paths: IMOSObjectPath[] | undefined, strict: boolean): void -⋮---- -export function fromXML(path: string, xml: AnyXMLValue, strict: boolean): IMOSExternalMetaData[] -⋮---- -export function toXML(xml: XMLBuilder.XMLElement, metadatas: IMOSExternalMetaData[]): void -⋮---- -/** Replace any empty objects with "" */ -function fixXMLMosPayload(value: AnyXMLValueSingular): AnyXMLValueSingular -function fixXMLMosPayload(value: AnyXMLObject): AnyXMLObject -function fixXMLMosPayload(value: AnyXMLValue): AnyXMLValue -⋮---- -if (isEmpty(value)) return '' // In xml, an empty tag results in an empty object - -================ -File: packages/helper/src/mosModel/parseMosTypes.ts -================ -import { MosTypes, getMosTypes, MosType, AnyXMLObject } from '@mos-connection/model' -import { AnyXMLValue } from './lib' -import { ParseError } from './ParseError' -import { ensureSingular } from '../utils/ensureMethods' -⋮---- -export function getParseMosTypes(strict: boolean): MosParseTypes -export type MosParseTypes = { - [key in keyof MosTypes]: MosTypes[key] extends MosType - ? MosTypeParse - : MosTypes[key] -} & { - string: MosTypeParse - stringEnum: MosTypeParse - number: MosTypeParse -} -interface MosTypeParse extends Omit, 'create'> { - /** - * Used to parse data that is optional. - * If the data is missing, undefined is returned. - */ - createOptional: (anyValue: CreateValue, path: string) => Serialized | undefined - /** - * Used to parse data that is required. - * If in strict mode, the data must be present and parsable, otherwise an error is thrown. - * If not in strict mode, a fallback value will be used. - */ - createRequired: (anyValue: CreateValue, path: string) => Serialized -} -⋮---- -/** - * Used to parse data that is optional. - * If the data is missing, undefined is returned. - */ -⋮---- -/** - * Used to parse data that is required. - * If in strict mode, the data must be present and parsable, otherwise an error is thrown. - * If not in strict mode, a fallback value will be used. - */ -⋮---- -function wrapParseMethods( - mosType: MosType, - valueIsSingular: boolean, - strict: boolean -): MosTypeParse -function wrapParseMethodCreateOptional( - mosType: MosType, - valueIsSingular: boolean, - strict: boolean -): MosTypeParse['createOptional'] -function wrapParseMethodCreateRequired( - mosType: MosType, - valueIsSingular: boolean, - strict: boolean -): MosTypeParse['createRequired'] -⋮---- -export function parseOptional( - parser: (value: V) => R, - valueIsSingular: boolean, - strict: boolean -): (value: V | AnyXMLValue, path: string) => R | undefined -⋮---- -// handle empty string: -⋮---- -// handle empty object (can happen when parsing an empty xml tag): -⋮---- -export function parseRequired( - parser: (value: V) => R, - fallback: () => R, - valueIsSingular: boolean, - strict: boolean -): (value: V | AnyXMLValue, path: string) => R -⋮---- -// handle empty string: -⋮---- -// handle empty object (can happen when parsing an empty xml tag): -⋮---- -// Something might be wrong. value is undefined, but should not be (?) -⋮---- -// This will throw if the mosType doesn't handle undefined: -⋮---- -function getSpecialMosTypes(strict: boolean) -export function getXMLAttributes(obj: AnyXMLObject): - -================ -File: packages/model/src/mosTypes/mosDuration.ts -================ -import { pad } from './lib' -⋮---- -export interface IMOSDuration { - _mosDuration: number // in seconds - /** @deprecated use getMosTypes().mosDuration.stringify() instead! */ - toString: never -} -⋮---- -_mosDuration: number // in seconds -/** @deprecated use getMosTypes().mosDuration.stringify() instead! */ -⋮---- -export function create(anyValue: AnyValue, strict: boolean): IMOSDuration -⋮---- -value = anyValue // seconds -⋮---- -export type AnyValue = string | number | IMOSDuration -export function validate(_mosDuration: IMOSDuration, _strict: boolean): void -⋮---- -// nothing -⋮---- -export function valueOf(mosDuration: IMOSDuration): number -⋮---- -if (typeof mosDuration === 'number') return mosDuration // helpful hack -⋮---- -export function stringify(mosDuration: IMOSDuration): string -⋮---- -if (typeof mosDuration === 'string') return mosDuration // helpful hack -⋮---- -export function is(mosDuration: IMOSDuration | any): mosDuration is IMOSDuration -export function fallback(): IMOSDuration - -================ -File: packages/quick-mos/src/index.ts -================ -/* eslint-disable node/no-unpublished-import */ -⋮---- -import { - MosConnection, - IMOSRunningOrder, - IMOSROStory, - IMOSROFullStory, - IConnectionConfig, - IMOSDeviceConnectionOptions, - MosDevice, - IMOSListMachInfo, - IMOSObjectAirStatus, - getMosTypes, -} from '@mos-connection/connector' -import { diffLists, ListEntry, OperationType } from './mosDiff' -⋮---- -import { convertFromSofieSnapshot } from './convertFromSofieSnapshot' -⋮---- -const DELAY_TIME = 300 // ms -⋮---- -export interface Config { - mosConnection: IConnectionConfig - devices: IMOSDeviceConnectionOptions[] -} -⋮---- -// @ts-expect-error just a stub, will be overwritten by /input/config.ts -⋮---- -function triggerReload() -function loadFile(requirePath: string): any -⋮---- -// eslint-disable-next-line @typescript-eslint/no-var-requires -⋮---- -async function reloadInner() -⋮---- -// Save the new config: -⋮---- -// Kill the old: -⋮---- -// Set up the new: -⋮---- -// opTime?: MosTime; -⋮---- -// profile3?: boolean; -// profile4?: boolean; -// profile5?: boolean; -// profile6?: boolean; -// profile7?: boolean; -⋮---- -// defaultActiveX?: Array; -// mosExternalMetaData?: Array; -⋮---- -// mosDevice.onRequestMOSObject((objId: string) => Promise): void; -// mosDevice.onRequestAllMOSObjects((pause: number) => Promise | IMOSAck>): void; -// mosDevice.onCreateRunningOrder((ro: IMOSRunningOrder) => Promise): void; -// mosDevice.onReplaceRunningOrder((ro: IMOSRunningOrder) => Promise): void; -// mosDevice.onDeleteRunningOrder((runningOrderId: MosString128) => Promise): void; -// mosDevice.onRequestRunningOrder((runningOrderId: MosString128) => Promise): void; -// mosDevice.onMetadataReplace((metadata: IMOSRunningOrderBase) => Promise): void; -// mosDevice.onRunningOrderStatus((status: IMOSRunningOrderStatus) => Promise): void; -// mosDevice.onStoryStatus((status: IMOSStoryStatus) => Promise): void; -// mosDevice.onItemStatus((status: IMOSItemStatus) => Promise): void; -// mosDevice.onReadyToAir((Action: IMOSROReadyToAir) => Promise): void; -// mosDevice.onROInsertStories((Action: IMOSStoryAction, Stories: Array) => Promise): void; -// mosDevice.onROInsertItems((Action: IMOSItemAction, Items: Array) => Promise): void; -// mosDevice.onROReplaceStories((Action: IMOSStoryAction, Stories: Array) => Promise): void; -// mosDevice.onROReplaceItems((Action: IMOSItemAction, Items: Array) => Promise): void; -// mosDevice.onROMoveStories((Action: IMOSStoryAction, Stories: Array) => Promise): void; -// mosDevice.onROMoveItems((Action: IMOSItemAction, Items: Array) => Promise): void; -// mosDevice.onRODeleteStories((Action: IMOSROAction, Stories: Array) => Promise): void; -// mosDevice.onRODeleteItems((Action: IMOSStoryAction, Items: Array) => Promise): void; -// mosDevice.onROSwapStories((Action: IMOSROAction, StoryID0: MosString128, StoryID1: MosString128) => Promise): void; -// mosDevice.onROSwapItems((Action: IMOSStoryAction, ItemID0: MosString128, ItemID1: MosString128) => Promise): void; -// mosDevice.onMosObjCreate((object: IMOSObject) => Promise): void; -// mosDevice.onMosItemReplace((roID: MosString128, storyID: MosString128, item: IMOSItem) => Promise): void; -// mosDevice.onMosReqSearchableSchema((username: string) => Promise): void; -// mosDevice.onMosReqObjectList((objList: IMosRequestObjectList) => Promise): void; -// mosDevice.onMosReqObjectAction((action: string, obj: IMOSObject) => Promise): void; -⋮---- -// mosDevice.onROStory((story: IMOSROFullStory) => Promise): void; -⋮---- -function refreshFiles() -⋮---- -// Check data -⋮---- -function fetchRunningOrders() -⋮---- -requirePath.match(/[/\\]_/) || // ignore and folders files that begin with "_" -requirePath.match(/[/\\]lib\.ts/) // ignore lib files -⋮---- -// Is a Sofie snapshot -⋮---- -function getAllFilesInDirectory(dir: string): string[] -⋮---- -// ------------ -⋮---- -type MOSCommand = () => Promise -class MOSMonitor -⋮---- -constructor(private mosDevice: MosDevice) -⋮---- -setTimeout(() => this.triggerRandomUpdate(), 10000) // startup delay -⋮---- -triggerRandomUpdate() -⋮---- -// only fire if the queue is sufficiently small (to avoid a backlog) -⋮---- -// send it -⋮---- -// run again -setTimeout(() => this.triggerRandomUpdate(), 500) // abritrary gap -⋮---- -// run again -setTimeout(() => this.triggerRandomUpdate(), 10000) // abritrary gap -⋮---- -onDeletedRunningOrder(roId: string) -⋮---- -// At the end, store the updated RO: -⋮---- -resendRunningOrder(roId: string): IMOSRunningOrder -onUpdatedRunningOrder(ro: IMOSRunningOrder, fullStories: IMOSROFullStory[], readyToAir: boolean | undefined): void -⋮---- -// compare with -⋮---- -// New RO -⋮---- -// nothing changed, do nothing -⋮---- -// Only RO metadata has changed -⋮---- -// Only Stories has changed -⋮---- -/* - // const addedGroups = this.groupIndexes(o.added) - _.each(o.added, (stories, beforeId) => { - // const index = parseInt(index0, 10) - }) - _.each(o.changed, c => { - this.commands.push(() => { - console.log('sendROReplaceStories', ro.ID) - const story = ro.Stories[c.id] - return this.mosDevice.sendROReplaceStories({ - RunningOrderID: ro.ID, - StoryID: mosTypes.mosString128.create(c.id) - }, [ c.story ]) - }) - }) - // Swap logic: - // if ( - // o.moved.length === 2 && - // o.moved[0].ids.length === 1 && - // o.moved[1].ids.length === 1 && - - // o.moved[0].beforeId - - // o.moved[0].beforeId === o.moved[1].oldIndex && - // o.moved[1].index === o.moved[0].oldIndex - // ) { - // this.commands.push(() => { - // console.log('sendROSwapStories', ro.ID) - // return this.mosDevice.sendROSwapStories({ - // RunningOrderID: ro.ID - // }, - // mosTypes.mosString128.create(o.moved[0].id), - // mosTypes.mosString128.create(o.moved[1].id) - // ) - // }) - // } else { - // const movedGroups = this.groupIndexes(o.moved) - // console.log('movedGroups', movedGroups) - _.each(o.moved, (m) => { - // const index = parseInt(index0, 10) - this.commands.push(() => { - console.log('sendROMoveStories', ro.ID, m.afterId, m.ids) - // const behindStory = index > 0 && ro.Stories[index - 1] - return this.mosDevice.sendROMoveStories({ - RunningOrderID: ro.ID, - StoryID: mosTypes.mosString128.create(m.afterId) - }, m.ids.map(m => mosTypes.mosString128.create(m))) - }) - }) - */ -⋮---- -// last resort: replace the whole rundown -⋮---- -// At the end, store the updated RO: -⋮---- -groupIndexes[] -static md5(str: string): string -⋮---- -private triggerCheckQueue() -⋮---- -this.commands.splice(0, 1) // remove the command from queue, as it has now been executed successfully -⋮---- -function fakeOnUpdatedRunningOrder(ro: IMOSRunningOrder, _fullStories: IMOSROFullStory[]): void -⋮---- -// compare with -⋮---- -// console.log('fakeOnUpdatedRunningOrder', roId) -⋮---- -// New RO -⋮---- -// console.log(_.omit(localRo.ro, 'MosExternalMetaData', 'Stories')) -// console.log(_.omit(ro, 'MosExternalMetaData', 'Stories')) -// console.log(metadataEqual) -// console.log(roStoriesEqual) -// console.log(roBaseDataEqual) -⋮---- -// nothing changed, do nothing -⋮---- -// Only RO metadata has changed -⋮---- -// this.commands.push(() => { -// return this.mosDevice.sendMetadataReplace(ro) -// }) -⋮---- -// Only Stories has changed -⋮---- -// const inserts = operation.inserts.map((i) => i.content) -⋮---- -// this.commands.push(() => { -// return this.mosDevice.sendROInsertStories({ -// RunningOrderID: ro.ID, -// StoryID: mosTypes.mosString128.create(operation.beforeId) -// }, inserts) -// }) -⋮---- -// const updatedStory = operation.content -⋮---- -// this.commands.push(() => { -// return this.mosDevice.sendROReplaceStories({ -// RunningOrderID: ro.ID, -// StoryID: mosTypes.mosString128.create(operation.id) -// }, [ updatedStory ]) -// }) -⋮---- -// this.commands.push(() => { -// return this.mosDevice.sendRODeleteStories({ -// RunningOrderID: ro.ID -// }, removeIds.map(id => mosTypes.mosString128.create(id))) -// }) -⋮---- -// this.commands.push(() => { -// return this.mosDevice.sendROMoveStories({ -// RunningOrderID: ro.ID, -// StoryID: mosTypes.mosString128.create(beforeId) -// }, moveIds.map(id => mosTypes.mosString128.create(id))) -// }) -⋮---- -// last resort: replace the whole rundown -⋮---- -// this.commands.push(() => { -// return this.mosDevice.sendReplaceRunningOrder(ro) -// }) -⋮---- -// At the end, store the updated RO: -⋮---- -// this.triggerCheckQueue() - -================ -File: packages/connector/src/api.ts -================ -import { - IProfiles, - IMOSListMachInfo, - IMOSObject, - MosItemReplaceOptions, - IMOSListSearchableSchema, - IMOSRequestObjectList, - IMOSObjectList, - IMOSAck, - IMOSRunningOrder, - IMOSItem, - IMOSItemAction, - IMOSItemStatus, - IMOSROAck, - IMOSROAction, - IMOSROFullStory, - IMOSROReadyToAir, - IMOSROStory, - IMOSRunningOrderBase, - IMOSRunningOrderStatus, - IMOSStoryAction, - IMOSStoryStatus, - IMOSString128, -} from '@mos-connection/model' -import { MosDevice } from './MosDevice' -⋮---- -/* - This file defines the API for the MOS-connection. - Most of these interfaces are derived from the MOS protocol. - https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm -*/ -export interface IMosConnection { - readonly isListening: boolean - - readonly acceptsConnections: boolean - readonly profiles: IProfiles - readonly isCompliant: boolean - readonly complianceText: string - - dispose: () => Promise - /* */ - connect: (connectionOptions: IMOSDeviceConnectionOptions) => Promise // resolved when connection has been made (before .onConnection is fired) - onConnection: (cb: (mosDevice: MosDevice) => void) => void - - on(event: 'error', listener: (error: Error) => void): this - on(event: 'warning', listener: (message: string) => void): this - on(event: 'info', listener: (message: string, data?: any) => void): this - on(event: 'rawMessage', listener: (source: string, type: string, message: string) => void): this -} -⋮---- -/* */ -connect: (connectionOptions: IMOSDeviceConnectionOptions) => Promise // resolved when connection has been made (before .onConnection is fired) -⋮---- -on(event: 'error', listener: (error: Error) -on(event: 'warning', listener: (message: string) -on(event: 'info', listener: (message: string, data?: any) -on(event: 'rawMessage', listener: (source: string, type: string, message: string) -⋮---- -export interface IMOSDevice - extends IMOSDeviceProfile0, - IMOSDeviceProfile1, - IMOSDeviceProfile2, - IMOSDeviceProfile3, - IMOSDeviceProfile4 { - idPrimary: string // unique id for this device and session - idSecondary: string | null // unique id for this device and session (buddy) -} -⋮---- -idPrimary: string // unique id for this device and session -idSecondary: string | null // unique id for this device and session (buddy) -⋮---- -/** - * Method definitions for Profile 1 - * see https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#Profile1 - */ -export interface IMOSDeviceProfile0 { - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * The reqMachInfo message is a method for an NCS or MOS to determine more information about its counterpart. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#reqMachInfo - */ - requestMachineInfo: () => Promise - /** - * Send message (as NCS) to a MOS device: - * The reqMachInfo message is a method for an NCS or MOS to determine more information about its counterpart. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#reqMachInfo - */ - onRequestMachineInfo: (cb: () => Promise) => void - - /** Assign callback for when the connection status changes. */ - onConnectionChange: (cb: (connectionStatus: IMOSConnectionStatus) => void) => void - /** Get the current connection status */ - getConnectionStatus: () => IMOSConnectionStatus - - // Deprecated methods: - /** @deprecated getMachineInfo is deprecated, use requestMachineInfo instead */ - getMachineInfo: () => Promise - /** @deprecated onGetMachineInfo is deprecated, use onRequestMachineInfo instead */ - onGetMachineInfo: (cb: () => Promise) => void -} -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * The reqMachInfo message is a method for an NCS or MOS to determine more information about its counterpart. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#reqMachInfo - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * The reqMachInfo message is a method for an NCS or MOS to determine more information about its counterpart. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#reqMachInfo - */ -⋮---- -/** Assign callback for when the connection status changes. */ -⋮---- -/** Get the current connection status */ -⋮---- -// Deprecated methods: -/** @deprecated getMachineInfo is deprecated, use requestMachineInfo instead */ -⋮---- -/** @deprecated onGetMachineInfo is deprecated, use onRequestMachineInfo instead */ -⋮---- -/** - * Method definitions for Profile 1 - * see https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#Profile1 - */ -export interface IMOSDeviceProfile1 { - /** - * Contains information that describes a unique MOS Object to the NCS. - * The NCS uses this information to search for and reference the MOS Object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObj - */ - sendMOSObject(obj: IMOSObject): Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Message used by the NCS to request the description of an object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObj - */ - onRequestMOSObject: (cb: (objId: string) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Message used by the NCS to request the description of an object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObj - */ - sendRequestMOSObject: (objId: IMOSString128) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Contains information that describes a unique MOS Object to the NCS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObj - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosListAll - */ - onMOSObjects: (cb: (objs: IMOSObject[]) => Promise) => void - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Method for the NCS to request the MOS to send it a mosObj message for every Object in the MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqAll - */ - onRequestAllMOSObjects: (cb: () => Promise>) => void - /** - * Method for the NCS to request the MOS to send it a mosObj message for every Object in the MOS. - * The replies will be sent to the callback set up in onMOSObjects() - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqAll - */ - sendRequestAllMOSObjects: ( - /** - * 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. - * @default 0 - */ - pause?: number - ) => Promise - - // Deprecated methods: - /** @deprecated getMOSObject is deprecated, use sendRequestMOSObject instead */ - getMOSObject: (objId: IMOSString128) => Promise - /** @deprecated getAllMOSObjects is deprecated, use sendRequestAllMOSObjects instead */ - getAllMOSObjects: () => Promise -} -⋮---- -/** - * Contains information that describes a unique MOS Object to the NCS. - * The NCS uses this information to search for and reference the MOS Object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObj - */ -sendMOSObject(obj: IMOSObject): Promise -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Message used by the NCS to request the description of an object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObj - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Message used by the NCS to request the description of an object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObj - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Contains information that describes a unique MOS Object to the NCS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObj - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosListAll - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Method for the NCS to request the MOS to send it a mosObj message for every Object in the MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqAll - */ -⋮---- -/** - * Method for the NCS to request the MOS to send it a mosObj message for every Object in the MOS. - * The replies will be sent to the callback set up in onMOSObjects() - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqAll - */ -⋮---- -/** - * 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. - * @default 0 - */ -⋮---- -// Deprecated methods: -/** @deprecated getMOSObject is deprecated, use sendRequestMOSObject instead */ -⋮---- -/** @deprecated getAllMOSObjects is deprecated, use sendRequestAllMOSObjects instead */ -⋮---- -/** - * Method definitions for Profile 2 - * see https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#Profile2 - */ -export interface IMOSDeviceProfile2 { - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Message received from the NCS to the MOS that defines a new Running Order. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roCreate - */ - onCreateRunningOrder: (cb: (ro: IMOSRunningOrder) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Message from the NCS to the MOS that defines a new Running Order. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roCreate - */ - sendCreateRunningOrder: (ro: IMOSRunningOrder) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Message received from the NCS to the MOS that defines a new Running Order, replacing an existing one. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReplace - */ - onReplaceRunningOrder: (cb: (ro: IMOSRunningOrder) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Message received from the NCS to the MOS that defines a new Running Order, replacing an existing one. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReplace - */ - sendReplaceRunningOrder: (ro: IMOSRunningOrder) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Deletes a Running order in the MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roDelete - */ - onDeleteRunningOrder: (cb: (runningOrderId: IMOSString128) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Deletes a Running order in the MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roDelete - */ - sendDeleteRunningOrder: (runningOrderId: IMOSString128) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Request for a complete build of a Running Order Playlist. NOTE: This message can be used by either NCS or MOS. - * A MOS can use this to "resync" its Playlist with the NCS Running Order or to obtain a full description of the Playlist at any time. - * An NCS can use this as a diagnostic tool to check the order of the Playlist constructed in the MOS versus the sequence of Items in the Running Order. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReq - * Response: https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roList - */ - onRequestRunningOrder: (cb: (runningOrderId: IMOSString128) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Request for a complete build of a Running Order Playlist. NOTE: This message can be used by either NCS or MOS. - * A MOS can use this to "resync" its Playlist with the NCS Running Order or to obtain a full description of the Playlist at any time. - * An NCS can use this as a diagnostic tool to check the order of the Playlist constructed in the MOS versus the sequence of Items in the Running Order. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReq - * Response: https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roList - */ - sendRequestRunningOrder: (runningOrderId: IMOSString128) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * The roMetadataReplace message allows metadata associated with a running order to be replaced without deleting the running order and sending the entire running order again. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roMetadataReplace - */ - onMetadataReplace: (cb: (metadata: IMOSRunningOrderBase) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * The roMetadataReplace message allows metadata associated with a running order to be replaced without deleting the running order and sending the entire running order again. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roMetadataReplace - */ - sendMetadataReplace: (metadata: IMOSRunningOrderBase) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * A method for the MOS to update the NCS on the status of a RO. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ - onRunningOrderStatus: (cb: (status: IMOSRunningOrderStatus) => Promise) => void // get roElementStat - /** - * Send message (as NCS) to a MOS device: - * A method for the MOS to update the NCS on the status of a RO. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ - sendRunningOrderStatus: (status: IMOSRunningOrderStatus) => Promise // send roElementStat - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * A method for the MOS to update the NCS on the status of a STORY. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ - onStoryStatus: (cb: (status: IMOSStoryStatus) => Promise) => void // get roElementStat - /** - * Send message (as NCS) to a MOS device: - * A method for the MOS to update the NCS on the status of a STORY. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ - sendStoryStatus: (status: IMOSStoryStatus) => Promise // send roElementStat - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * A method for the MOS to update the NCS on the status of an ITEM. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ - onItemStatus: (cb: (status: IMOSItemStatus) => Promise) => void // get roElementStat - /** - * Send message (as NCS) to a MOS device: - * A method for the MOS to update the NCS on the status of an ITEM. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ - sendItemStatus: (status: IMOSItemStatus) => Promise // send roElementStat - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * The roReadyToAir message allows the NCS to signal the MOS that a Running Order has been editorially approved ready for air. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReadyToAir - */ - onReadyToAir: (cb: (Action: IMOSROReadyToAir) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * The roReadyToAir message allows the NCS to signal the MOS that a Running Order has been editorially approved ready for air. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReadyToAir - */ - sendReadyToAir: (Action: IMOSROReadyToAir) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Insert one or more story into a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - onROInsertStories: (cb: (Action: IMOSStoryAction, Stories: Array) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Insert one or more story into a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - sendROInsertStories: (Action: IMOSStoryAction, Stories: Array) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Insert one or more Items into a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - onROInsertItems: (cb: (Action: IMOSItemAction, Items: Array) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Insert one or more Items into a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - sendROInsertItems: (Action: IMOSItemAction, Items: Array) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Replace (update) a Story with a new story. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - onROReplaceStories: (cb: (Action: IMOSStoryAction, Stories: Array) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Replace (update) a Story with a new story. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - sendROReplaceStories: (Action: IMOSStoryAction, Stories: Array) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Replace (update) an Item with a new Item. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - onROReplaceItems: (cb: (Action: IMOSItemAction, Items: Array) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Replace (update) an Item with a new Item. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - sendROReplaceItems: (Action: IMOSItemAction, Items: Array) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Move one or more Stories within a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - onROMoveStories: (cb: (Action: IMOSStoryAction, Stories: Array) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Move one or more Stories within a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - sendROMoveStories: (Action: IMOSStoryAction, Stories: Array) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Move one or more Items within a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - onROMoveItems: (cb: (Action: IMOSItemAction, Items: Array) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Move one or more Items within a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - sendROMoveItems: (Action: IMOSItemAction, Items: Array) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Delete one or more Stories within a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - onRODeleteStories: (cb: (Action: IMOSROAction, Stories: Array) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Delete one or more Stories within a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - sendRODeleteStories: (Action: IMOSROAction, Stories: Array) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Delete one or more Items within a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - onRODeleteItems: (cb: (Action: IMOSStoryAction, Items: Array) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * Delete one or more Items within a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - sendRODeleteItems: (Action: IMOSStoryAction, Items: Array) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Swap two Stories - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - onROSwapStories: ( - cb: (Action: IMOSROAction, StoryID0: IMOSString128, StoryID1: IMOSString128) => Promise - ) => void - /** - * Send message (as NCS) to a MOS device: - * Swap two Stories - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - sendROSwapStories: (Action: IMOSROAction, StoryID0: IMOSString128, StoryID1: IMOSString128) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Swap two Items - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - onROSwapItems: ( - cb: (Action: IMOSStoryAction, ItemID0: IMOSString128, ItemID1: IMOSString128) => Promise - ) => void - /** - * Send message (as NCS) to a MOS device: - * Swap two Items - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ - sendROSwapItems: (Action: IMOSStoryAction, ItemID0: IMOSString128, ItemID1: IMOSString128) => Promise - - // Deprecated methods: - /** @deprecated getRunningOrder is deprecated, use sendRequestRunningOrder instead */ - getRunningOrder: (runningOrderId: IMOSString128) => Promise - /** @deprecated setRunningOrderStatus is deprecated, use sendRunningOrderStatus instead */ - setRunningOrderStatus: (status: IMOSRunningOrderStatus) => Promise - /** @deprecated setStoryStatus is deprecated, use sendStoryStatus instead */ - setStoryStatus: (status: IMOSStoryStatus) => Promise - /** @deprecated setItemStatus is deprecated, use sendItemStatus instead */ - setItemStatus: (status: IMOSItemStatus) => Promise -} -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Message received from the NCS to the MOS that defines a new Running Order. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roCreate - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Message from the NCS to the MOS that defines a new Running Order. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roCreate - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Message received from the NCS to the MOS that defines a new Running Order, replacing an existing one. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReplace - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Message received from the NCS to the MOS that defines a new Running Order, replacing an existing one. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReplace - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Deletes a Running order in the MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roDelete - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Deletes a Running order in the MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roDelete - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Request for a complete build of a Running Order Playlist. NOTE: This message can be used by either NCS or MOS. - * A MOS can use this to "resync" its Playlist with the NCS Running Order or to obtain a full description of the Playlist at any time. - * An NCS can use this as a diagnostic tool to check the order of the Playlist constructed in the MOS versus the sequence of Items in the Running Order. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReq - * Response: https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roList - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Request for a complete build of a Running Order Playlist. NOTE: This message can be used by either NCS or MOS. - * A MOS can use this to "resync" its Playlist with the NCS Running Order or to obtain a full description of the Playlist at any time. - * An NCS can use this as a diagnostic tool to check the order of the Playlist constructed in the MOS versus the sequence of Items in the Running Order. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReq - * Response: https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roList - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * The roMetadataReplace message allows metadata associated with a running order to be replaced without deleting the running order and sending the entire running order again. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roMetadataReplace - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * The roMetadataReplace message allows metadata associated with a running order to be replaced without deleting the running order and sending the entire running order again. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roMetadataReplace - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * A method for the MOS to update the NCS on the status of a RO. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ -onRunningOrderStatus: (cb: (status: IMOSRunningOrderStatus) => Promise) => void // get roElementStat -/** - * Send message (as NCS) to a MOS device: - * A method for the MOS to update the NCS on the status of a RO. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ -sendRunningOrderStatus: (status: IMOSRunningOrderStatus) => Promise // send roElementStat -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * A method for the MOS to update the NCS on the status of a STORY. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ -onStoryStatus: (cb: (status: IMOSStoryStatus) => Promise) => void // get roElementStat -/** - * Send message (as NCS) to a MOS device: - * A method for the MOS to update the NCS on the status of a STORY. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ -sendStoryStatus: (status: IMOSStoryStatus) => Promise // send roElementStat -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * A method for the MOS to update the NCS on the status of an ITEM. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ -onItemStatus: (cb: (status: IMOSItemStatus) => Promise) => void // get roElementStat -/** - * Send message (as NCS) to a MOS device: - * A method for the MOS to update the NCS on the status of an ITEM. This allows the NCS to reflect the status of any element in the MOS Running Order in the NCS Running Order display. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.7.2_roElementStat_- - */ -sendItemStatus: (status: IMOSItemStatus) => Promise // send roElementStat -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * The roReadyToAir message allows the NCS to signal the MOS that a Running Order has been editorially approved ready for air. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReadyToAir - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * The roReadyToAir message allows the NCS to signal the MOS that a Running Order has been editorially approved ready for air. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReadyToAir - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Insert one or more story into a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Insert one or more story into a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Insert one or more Items into a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Insert one or more Items into a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Replace (update) a Story with a new story. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Replace (update) a Story with a new story. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Replace (update) an Item with a new Item. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Replace (update) an Item with a new Item. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Move one or more Stories within a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Move one or more Stories within a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Move one or more Items within a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Move one or more Items within a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Delete one or more Stories within a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Delete one or more Stories within a Running Order - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Delete one or more Items within a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Delete one or more Items within a Story - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Swap two Stories - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Swap two Stories - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * Swap two Items - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * Swap two Items - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roElementAction - */ -⋮---- -// Deprecated methods: -/** @deprecated getRunningOrder is deprecated, use sendRequestRunningOrder instead */ -⋮---- -/** @deprecated setRunningOrderStatus is deprecated, use sendRunningOrderStatus instead */ -⋮---- -/** @deprecated setStoryStatus is deprecated, use sendStoryStatus instead */ -⋮---- -/** @deprecated setItemStatus is deprecated, use sendItemStatus instead */ -⋮---- -/** - * Method definitions for Profile 3 - * see https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#Profile3 - */ -export interface IMOSDeviceProfile3 { - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosObjCreate allows an NCS to request the Media Object Server to create a Media Object with specific metadata associated with it. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjCreate - */ - onObjectCreate: (cb: (object: IMOSObject) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * mosObjCreate allows an NCS to request the Media Object Server to create a Media Object with specific metadata associated with it. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjCreate - */ - sendObjectCreate: (object: IMOSObject) => Promise - - /** - * Assign callback (as a NCS device) for when receiving message from MOS: - * This message allows a Media Object Server to replace an Item Reference in a Story with new metadata values and/or additional tags. - * The Story must be in a MOS Active PlayList. - * This message is initiated by the Media Object Server, rather than the NCS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosItemReplace - */ - onItemReplace: (cb: (roID: IMOSString128, storyID: IMOSString128, item: IMOSItem) => Promise) => void - /** - * Send message (as MOS) to a NCS: - * This message allows a Media Object Server to replace an Item Reference in a Story with new metadata values and/or additional tags. - * The Story must be in a MOS Active PlayList. - * This message is initiated by the Media Object Server, rather than the NCS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosItemReplace - */ - sendItemReplace: (options: MosItemReplaceOptions) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosReqSearchable Schema is a mechanism used by the NCS to request the MOS to send a pointer to a schema in which searchable fields are defined by the MOS device. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqSearchableSchema - */ - onRequestSearchableSchema: (cb: (username: string) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * mosReqSearchable Schema is a mechanism used by the NCS to request the MOS to send a pointer to a schema in which searchable fields are defined by the MOS device. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqSearchableSchema - */ - sendRequestSearchableSchema: (username: string) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosReqObjList is a mechanism used by a NCS to retrieve only selected object descriptions from a MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObjList - */ - onRequestObjectList: (cb: (objList: IMOSRequestObjectList) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * mosReqObjList is a mechanism used by a NCS to retrieve only selected object descriptions from a MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObjList - */ - sendRequestObjectList: (reqObjList: IMOSRequestObjectList) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ - onRequestObjectActionNew: (cb: (obj: IMOSObject) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ - sendRequestObjectActionNew: (obj: IMOSObject) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ - onRequestObjectActionUpdate: (cb: (objId: IMOSString128, obj: IMOSObject) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ - sendRequestObjectActionUpdate: (objId: IMOSString128, obj: IMOSObject) => Promise - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ - onRequestObjectActionDelete: (cb: (objId: IMOSString128) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ - sendRequestObjectActionDelete: (objId: IMOSString128) => Promise - - // Deprecated methods: - /** @deprecated onMosObjCreate is deprecated, use onObjectCreate instead */ - onMosObjCreate: (cb: (object: IMOSObject) => Promise) => void - /** @deprecated mosObjCreate is deprecated, use sendObjectCreate instead */ - mosObjCreate: (object: IMOSObject) => Promise - /** @deprecated onMosItemReplace is deprecated, use onItemReplace instead */ - onMosItemReplace: (cb: (roID: IMOSString128, storyID: IMOSString128, item: IMOSItem) => Promise) => void - /** @deprecated mosItemReplace is deprecated, use sendItemReplace instead */ - mosItemReplace: (options: MosItemReplaceOptions) => Promise - /** @deprecated onMosReqSearchableSchema is deprecated, use onRequestSearchableSchema instead */ - onMosReqSearchableSchema: (cb: (username: string) => Promise) => void - /** @deprecated mosRequestSearchableSchema is deprecated, use sendRequestSearchableSchema instead */ - mosRequestSearchableSchema: (username: string) => Promise - /** @deprecated onMosReqObjectList is deprecated, use onRequestObjectList instead */ - onMosReqObjectList: (cb: (objList: IMOSRequestObjectList) => Promise) => void - /** @deprecated mosRequestObjectList is deprecated, use sendRequestObjectList instead */ - mosRequestObjectList: (reqObjList: IMOSRequestObjectList) => Promise - /** @deprecated onMosReqObjectAction is deprecated, use onRequestObjectAction*** instead */ - onMosReqObjectAction: (cb: (action: string, obj: IMOSObject) => Promise) => void -} -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosObjCreate allows an NCS to request the Media Object Server to create a Media Object with specific metadata associated with it. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjCreate - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * mosObjCreate allows an NCS to request the Media Object Server to create a Media Object with specific metadata associated with it. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObjCreate - */ -⋮---- -/** - * Assign callback (as a NCS device) for when receiving message from MOS: - * This message allows a Media Object Server to replace an Item Reference in a Story with new metadata values and/or additional tags. - * The Story must be in a MOS Active PlayList. - * This message is initiated by the Media Object Server, rather than the NCS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosItemReplace - */ -⋮---- -/** - * Send message (as MOS) to a NCS: - * This message allows a Media Object Server to replace an Item Reference in a Story with new metadata values and/or additional tags. - * The Story must be in a MOS Active PlayList. - * This message is initiated by the Media Object Server, rather than the NCS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosItemReplace - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosReqSearchable Schema is a mechanism used by the NCS to request the MOS to send a pointer to a schema in which searchable fields are defined by the MOS device. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqSearchableSchema - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * mosReqSearchable Schema is a mechanism used by the NCS to request the MOS to send a pointer to a schema in which searchable fields are defined by the MOS device. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqSearchableSchema - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosReqObjList is a mechanism used by a NCS to retrieve only selected object descriptions from a MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObjList - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * mosReqObjList is a mechanism used by a NCS to retrieve only selected object descriptions from a MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqObjList - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * mosReqObjAction allows an NCS to request the Media Object Server to create, modify or delete a media object. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#_3.3.3_mosReqObjAction_%E2%80%93_NCS_request - */ -⋮---- -// Deprecated methods: -/** @deprecated onMosObjCreate is deprecated, use onObjectCreate instead */ -⋮---- -/** @deprecated mosObjCreate is deprecated, use sendObjectCreate instead */ -⋮---- -/** @deprecated onMosItemReplace is deprecated, use onItemReplace instead */ -⋮---- -/** @deprecated mosItemReplace is deprecated, use sendItemReplace instead */ -⋮---- -/** @deprecated onMosReqSearchableSchema is deprecated, use onRequestSearchableSchema instead */ -⋮---- -/** @deprecated mosRequestSearchableSchema is deprecated, use sendRequestSearchableSchema instead */ -⋮---- -/** @deprecated onMosReqObjectList is deprecated, use onRequestObjectList instead */ -⋮---- -/** @deprecated mosRequestObjectList is deprecated, use sendRequestObjectList instead */ -⋮---- -/** @deprecated onMosReqObjectAction is deprecated, use onRequestObjectAction*** instead */ -⋮---- -/** - * Method definitions for Profile 4 - * see https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#Profile4 - */ -export interface IMOSDeviceProfile4 { - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * roReqAll is a request for a description of all Running Orders known by a NCS from a MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReqAll - */ - onRequestAllRunningOrders: (cb: () => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * roReqAll is a request for a description of all Running Orders known by a NCS from a MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReqAll - */ - sendRequestAllRunningOrders: () => Promise> - - /** - * Assign callback (as a MOS device) for when receiving message from NCS: - * This message enables sending the body of story from the NCS to a Media Object Server. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roStorySend - */ - onRunningOrderStory: (cb: (story: IMOSROFullStory) => Promise) => void - /** - * Send message (as NCS) to a MOS device: - * This message enables sending the body of story from the NCS to a Media Object Server. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roStorySend - */ - sendRunningOrderStory: (story: IMOSROFullStory) => Promise - - // Deprecated methods: - /** @deprecated onROReqAll is deprecated, use onRequestAllRunningOrders instead */ - onROReqAll: (cb: () => Promise) => void - /** @deprecated getAllRunningOrders is deprecated, use sendRequestAllRunningOrders instead */ - getAllRunningOrders: () => Promise> // send roReqAll - /** @deprecated onROStory is deprecated, use onRunningOrderStory instead */ - onROStory: (cb: (story: IMOSROFullStory) => Promise) => void // roStorySend - /** @deprecated sendROStory is deprecated, use sendRunningOrderStory instead */ - sendROStory: (story: IMOSROFullStory) => Promise // roStorySend -} -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * roReqAll is a request for a description of all Running Orders known by a NCS from a MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReqAll - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * roReqAll is a request for a description of all Running Orders known by a NCS from a MOS. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roReqAll - */ -⋮---- -/** - * Assign callback (as a MOS device) for when receiving message from NCS: - * This message enables sending the body of story from the NCS to a Media Object Server. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roStorySend - */ -⋮---- -/** - * Send message (as NCS) to a MOS device: - * This message enables sending the body of story from the NCS to a Media Object Server. - * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#roStorySend - */ -⋮---- -// Deprecated methods: -/** @deprecated onROReqAll is deprecated, use onRequestAllRunningOrders instead */ -⋮---- -/** @deprecated getAllRunningOrders is deprecated, use sendRequestAllRunningOrders instead */ -getAllRunningOrders: () => Promise> // send roReqAll -/** @deprecated onROStory is deprecated, use onRunningOrderStory instead */ -onROStory: (cb: (story: IMOSROFullStory) => Promise) => void // roStorySend -/** @deprecated sendROStory is deprecated, use sendRunningOrderStory instead */ -sendROStory: (story: IMOSROFullStory) => Promise // roStorySend -⋮---- -// /** */ -export interface IMOSConnectionStatus { - PrimaryConnected: boolean - PrimaryStatus: string // if not connected this will contain human-readable error-message - SecondaryConnected: boolean - SecondaryStatus: string // if not connected this will contain human-readable error-message -} -⋮---- -PrimaryStatus: string // if not connected this will contain human-readable error-message -⋮---- -SecondaryStatus: string // if not connected this will contain human-readable error-message -⋮---- -/** Config object for creating a MOS-device */ -export interface IConnectionConfig { - /** The ID of this mos-device */ - mosID: string - /** Whether this mosConnection accepts new connections from othe MOS clients (ie acts as an NCS) */ - acceptsConnections: boolean - /** Only accept connections from this whitelist */ - accepsConnectionsFrom?: string[] - /** A list of which profile this mos device is to support */ - profiles: IProfiles - /** If true, this device is assumed to be an NCS (server). Defaults to a MOS (client). */ - isNCS?: boolean - /** Debugging-mode: logs raw mos-messages */ - debug?: boolean - offspecFailover?: boolean - /** If set to true, a strict check is performed to ensure that all required callbacks are set up for specified profiles */ - strict?: boolean - /** If set, overrides the standard port numbers */ - ports?: { - /** Set MOS Lower port (standard: 10540) */ - lower: number - /** Set MOS Upper port (standard: 10541) */ - upper: number - /** Set MOS Query port (standard: 10542) */ - query: number - } - /** When enabled, automatically create new mos-devices on-the-fly when receiving messages to unregistered MOS-ID:s */ - openRelay?: - | boolean - | { - // options for on-the-fly-created connections - options: IMOSDeviceConnectionOptions['primary'] - } -} -⋮---- -/** The ID of this mos-device */ -⋮---- -/** Whether this mosConnection accepts new connections from othe MOS clients (ie acts as an NCS) */ -⋮---- -/** Only accept connections from this whitelist */ -⋮---- -/** A list of which profile this mos device is to support */ -⋮---- -/** If true, this device is assumed to be an NCS (server). Defaults to a MOS (client). */ -⋮---- -/** Debugging-mode: logs raw mos-messages */ -⋮---- -/** If set to true, a strict check is performed to ensure that all required callbacks are set up for specified profiles */ -⋮---- -/** If set, overrides the standard port numbers */ -⋮---- -/** Set MOS Lower port (standard: 10540) */ -⋮---- -/** Set MOS Upper port (standard: 10541) */ -⋮---- -/** Set MOS Query port (standard: 10542) */ -⋮---- -/** When enabled, automatically create new mos-devices on-the-fly when receiving messages to unregistered MOS-ID:s */ -⋮---- -// options for on-the-fly-created connections -⋮---- -export interface IMOSDeviceConnectionOptions { - /** Connection options for the Primary NCS-server */ - primary: { - /** Name (NCS ID) of the NCS-server */ - id: string - /** Host address (IP-address) of the NCS-server */ - host: string // ip-addr - /** (Optional): Custom ports for communication */ - ports?: { - upper: number - lower: number - query: number - } - /** (Optional) Timeout for commands (ms) */ - timeout?: number - /** (Optional) Interval for sending of hearbeats (ms) */ - heartbeatInterval?: number - /** (Optional) Some server doesn't expose the Query port, which can cause connection-errors. - * Set this to true to not use that port (will cause some methods to stop working) - */ - dontUseQueryPort?: boolean - } - /** Connection options for the Secondary (Buddy) NCS-server */ - secondary?: { - /** Name (NCS ID) of the Buddy NCS-server */ - id: string - /** Host address (IP-address) of the NCS-server */ - host: string - /** (Optional): Custom ports for communication */ - ports?: { - upper: number - lower: number - query: number - } - /** (Optional) Timeout for commands (ms) */ - timeout?: number - /** (Optional) Interval for sending of hearbeats (ms) */ - heartbeatInterval?: number - - /** (Optional) Some server doesn't expose the Query port, which can cause connection-errors. - * Set this to true to not use that port (will cause some methods to stop working) - */ - dontUseQueryPort?: boolean - - /** (Optional) Treat the secondary server as a "hot standby". - * A "hot standby" is a server that is powered down / in standby while the primary server is up. - * When a server is a hot standby it is expected to be disconnected and hence we will not send - * heartbeat messages. The connection status will still be reported as disconnected however as we - * do not pretend to be connected to something that is powered down. - * - * (This was added to support the hot standby feature of OpenMedia NRCS.) - */ - openMediaHotStandby?: boolean - } -} -⋮---- -/** Connection options for the Primary NCS-server */ -⋮---- -/** Name (NCS ID) of the NCS-server */ -⋮---- -/** Host address (IP-address) of the NCS-server */ -host: string // ip-addr -/** (Optional): Custom ports for communication */ -⋮---- -/** (Optional) Timeout for commands (ms) */ -⋮---- -/** (Optional) Interval for sending of hearbeats (ms) */ -⋮---- -/** (Optional) Some server doesn't expose the Query port, which can cause connection-errors. - * Set this to true to not use that port (will cause some methods to stop working) - */ -⋮---- -/** Connection options for the Secondary (Buddy) NCS-server */ -⋮---- -/** Name (NCS ID) of the Buddy NCS-server */ -⋮---- -/** Host address (IP-address) of the NCS-server */ -⋮---- -/** (Optional): Custom ports for communication */ -⋮---- -/** (Optional) Timeout for commands (ms) */ -⋮---- -/** (Optional) Interval for sending of hearbeats (ms) */ -⋮---- -/** (Optional) Some server doesn't expose the Query port, which can cause connection-errors. - * Set this to true to not use that port (will cause some methods to stop working) - */ -⋮---- -/** (Optional) Treat the secondary server as a "hot standby". - * A "hot standby" is a server that is powered down / in standby while the primary server is up. - * When a server is a hot standby it is expected to be disconnected and hence we will not send - * heartbeat messages. The connection status will still be reported as disconnected however as we - * do not pretend to be connected to something that is powered down. - * - * (This was added to support the hot standby feature of OpenMedia NRCS.) - */ - -================ -File: packages/model/src/mosTypes/mosTime.ts -================ -import { pad } from './lib' -⋮---- -export interface IMOSTime { - _mosTime: number - _timezone: string - _timezoneOffset: number - - /** @deprecated use getMosTypes().mosTime.stringify() instead! */ - toString: never -} -⋮---- -/** @deprecated use getMosTypes().mosTime.stringify() instead! */ -⋮---- -export function create(timestamp: AnyValue, strict: boolean): IMOSTime -⋮---- -// let value: number -⋮---- -// create date from time-string or timestamp number -⋮---- -// formats: -// YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] -// Sun Feb 25 2018 08:59:08 GMT+0100 (CET) -// 2018-02-25T08:00:45.528Z -⋮---- -// parse out custom Z indicator (mos-centric) -⋮---- -// parse out custom timezones (mos local-local centric format) -⋮---- -// try to parse the time directly with Date, for Date-supported formats -⋮---- -export type AnyValue = Date | number | string | IMOSTime -⋮---- -export function validate(_mosDuration: IMOSTime, _strict: boolean): void -⋮---- -// nothing -⋮---- -export function valueOf(mosTime: IMOSTime): number -⋮---- -if (typeof mosTime === 'number') return mosTime // helpful hack -⋮---- -export function stringify(mosTime: IMOSTime): string -⋮---- -if (typeof mosTime === 'string') return mosTime // helpful hack -⋮---- -// Cheat a little bit to get the time-zone right: -// First add the local offset to get the Date to display the time in UTC, -// then add the timezoneOffset to get the time in the the correct time-zone: -⋮---- -export function is(mosTime: IMOSTime | any): mosTime is IMOSTime -export function fallback(): IMOSTime -⋮---- -function parseTimeOffset(timestamp: string): false | -⋮---- -/** */ -function parseMosCustomFormat(timestamp: string): - | false - | { - yy: string - mm: string - dd: string - hh: string - ii: string - ss: string - ms: string - timezoneIndicator: string - /** in minutes */ - timezoneOffset: number - } { - const timestampRegex = /(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)([,.](\d{3}))?(([+-Z])([:\d]+)?)?/i - const match = timestamp.match(timestampRegex) -if (match) -⋮---- -/** in minutes */ -⋮---- -const m = timezoneIndicator.match(/([+-])(\d+):(\d+)/) // +5:00, -05:00 - -================ -File: packages/examples/package.json -================ -{ - "name": "mos-examples", - "private": true, - "version": "0.0.0", - "description": "Examples for how to use MOS-connection", - "main": "dist/index.js", - "typings": "dist/index.d.ts", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/nrkno/sofie-mos-connection.git" - }, - "homepage": "https://github.com/nrkno/sofie-mos-connection#readme", - "scripts": { - "build": "run -T rimraf dist && run build:main", - "build:main": "run -T tsc -p tsconfig.build.json", - "__test": "run -T jest", - "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", - "validate:dev-dependencies": "yarn npm audit ", - "license-validate": "yarn sofie-licensecheck" - }, - "engines": { - "node": ">=14.18.0" - }, - "dependencies": { - "@mos-connection/connector": "4.2.2" - }, - "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", - "lint-staged": { - "*.{css,json,md,scss}": [ - "run -T prettier" - ], - "*.{ts,tsx,js,jsx}": [ - "run -T eslint" - ] - }, - "files": [ - "/dist", - "/CHANGELOG.md", - "/README.md", - "/LICENSE" - ], - "devDependencies": { - "@sofie-automation/code-standard-preset": "*", - "typescript": "~4.9.5" - } -} - -================ -File: packages/helper/package.json -================ -{ - "name": "@mos-connection/helper", - "version": "4.2.2", - "description": "Helper functions for the MOS-connection library", - "main": "dist/index.js", - "typings": "dist/index.d.ts", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/nrkno/sofie-mos-connection.git" - }, - "bugs": { - "url": "https://github.com/nrkno/sofie-mos-connection/issues" - }, - "homepage": "https://github.com/nrkno/sofie-mos-connection#readme", - "scripts": { - "build": "run -T rimraf dist && run build:main", - "build:main": "run -T tsc -p tsconfig.build.json", - "test": "run -T jest", - "test:coverage": "yarn test --coverage", - "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", - "validate:dev-dependencies": "yarn npm audit ", - "license-validate": "yarn sofie-licensecheck" - }, - "files": [ - "/dist", - "/CHANGELOG.md", - "/README.md", - "/LICENSE" - ], - "dependencies": { - "@mos-connection/model": "4.2.2", - "iconv-lite": "^0.6.3", - "tslib": "^2.5.3", - "xml-js": "^1.6.11", - "xmlbuilder": "^15.1.1" - }, - "keywords": [ - "mos", - "broadcast", - "socket", - "typescript", - "javascript", - "open", - "source", - "automation", - "ncs", - "nrcs", - "rundown", - "tv-production", - "sofie" - ], - "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", - "engines": { - "node": ">=14.18.0" - }, - "lint-staged": { - "*.{css,json,md,scss}": [ - "run -T prettier" - ], - "*.{ts,tsx,js,jsx}": [ - "run -T eslint" - ] - }, - "devDependencies": { - "@sofie-automation/code-standard-preset": "*", - "typescript": "~4.9.5" - } -} - -================ -File: packages/model/package.json -================ -{ - "name": "@mos-connection/model", - "version": "4.2.2", - "description": "Types and enums for the mos-connection library", - "main": "dist/index.js", - "typings": "dist/index.d.ts", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/nrkno/sofie-mos-connection.git" - }, - "homepage": "https://github.com/nrkno/sofie-mos-connection#readme", - "scripts": { - "build": "run -T rimraf dist && run build:main", - "build:main": "run -T tsc -p tsconfig.build.json", - "test": "run -T jest", - "test:coverage": "yarn test --coverage", - "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", - "validate:dev-dependencies": "yarn npm audit ", - "license-validate": "yarn sofie-licensecheck" - }, - "engines": { - "node": ">=14.18.0" - }, - "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", - "lint-staged": { - "*.{css,json,md,scss}": [ - "run -T prettier" - ], - "*.{ts,tsx,js,jsx}": [ - "run -T eslint" - ] - }, - "files": [ - "/dist", - "/CHANGELOG.md", - "/README.md", - "/LICENSE" - ], - "devDependencies": { - "@sofie-automation/code-standard-preset": "*", - "typescript": "~4.9.5" - } -} - -================ -File: lerna.json -================ -{ - "packages": [ - "packages/*" - ], - "version": "4.2.2", - "npmClient": "yarn", - "useWorkspaces": true -} - -================ -File: packages/connector/src/connection/mosSocketClient.ts -================ -import { EventEmitter } from 'eventemitter3' -import { Socket } from 'net' -import { SocketConnectionEvent } from './socketConnection' -import { MosModel } from '@mos-connection/helper' -import { DEFAULT_COMMAND_TIMEOUT, HandedOverQueue } from './NCSServerConnection' -⋮---- -import { ParsedMosMessage, MosMessageParser } from './mosMessageParser' -⋮---- -export type CallBackFunction = (data: { error: Error | string } | { reply: ParsedMosMessage }) => void -⋮---- -export interface QueueMessage { - time: number - msg: MosModel.MosMessage -} -⋮---- -export interface MosSocketClientEvents { - [SocketConnectionEvent.CONNECTED]: () => void - [SocketConnectionEvent.DISCONNECTED]: () => void - [SocketConnectionEvent.DISPOSED]: () => void - [SocketConnectionEvent.ALIVE]: () => void - // [SocketConnectionEvent.TIMEOUT]: () => void - // [SocketConnectionEvent.REGISTER]: () => void - // [SocketConnectionEvent.UNREGISTER]: () => void - - error: (error: Error) => void - warning: (warning: string) => void - rawMessage: (...args: any[]) => void -} -⋮---- -// [SocketConnectionEvent.TIMEOUT]: () => void -// [SocketConnectionEvent.REGISTER]: () => void -// [SocketConnectionEvent.UNREGISTER]: () => void -⋮---- -export class MosSocketClient extends EventEmitter -⋮---- -private _lingeringCallback: { [messageId: string]: CallBackFunction } = {} // for lingering messages -⋮---- -private _sentMessage: QueueMessage | null = null // sent message, waiting for reply -⋮---- -private _lingeringMessage: QueueMessage | null = null // sent message, NOT waiting for reply -// private _readyToSendMessage: boolean = true -⋮---- -// private _startingUp: boolean = true -⋮---- -/** */ -constructor(host: string, port: number, description: string, timeout: number, debug: boolean, strict: boolean) -⋮---- -/** */ -set autoReconnect(autoReconnect: boolean) -⋮---- -/** */ -set autoReconnectInterval(autoReconnectInterval: number) -⋮---- -/** */ -set autoReconnectAttempts(autoReconnectAttempts: number) -⋮---- -/** */ -connect(): void -⋮---- -// prevent manipulation of active socket -⋮---- -// throttling attempts -⋮---- -// !_lastReconnectionAttempt (means first attempt) OR time > _reconnectionDelay since last attempt -// recreate client if new attempt: -⋮---- -// (re)create client, either on first run or new attempt: -⋮---- -// connect: -⋮---- -// set timer to retry when needed: -⋮---- -/** */ -disconnect(): void -⋮---- -queueCommand(message: MosModel.MosMessage, cb: CallBackFunction, time?: number): void -⋮---- -// this.debugTrace('queueing', message.messageID, message.constructor.name ) -⋮---- -processQueue(): void -⋮---- -// Send the message: -⋮---- -// The queue is empty, do nothing -⋮---- -// Try again later: -⋮---- -/** - * Returns a queue of messages to be executed by a different connection. - * Will exclude hearbeats from the returned queue. The heartbeats must stay inside - * the internal queue because they are needed for the connection lifecycle. - */ -handOverQueue(): HandedOverQueue -⋮---- -// Temporary hack, to allow heartbeats to be received after a handover: -⋮---- -/** */ -get host(): string -⋮---- -/** */ -get port(): number -⋮---- -/** */ -dispose(): void -⋮---- -// this._readyToSendMessage = false -⋮---- -/** - * convenience wrapper to expose all logging calls to parent object - */ -log(...args: any[]): void -public setDebug(debug: boolean): void -/** */ -private set connected(connected: boolean) -⋮---- -/** */ -private get connected(): boolean -⋮---- -private _sendReply(messageId: number, response: -⋮---- -// this._onUnhandledCommandTimeout() -⋮---- -/** */ -private executeCommand(message: QueueMessage, isRetry?: boolean): void -⋮---- -// Command timeout: -⋮---- -/** */ -private _autoReconnectionAttempt(): void -⋮---- -// no reconnection if no valid reconnectionAttemps is set -⋮---- -// if current attempt is not less than max attempts -// reset reconnection behaviour -⋮---- -// new attempt if not allready connected -⋮---- -/** */ -private _clearConnectionAttemptTimer(): void -⋮---- -/** */ -private _onConnected() -⋮---- -/** */ -private _onData(data: Buffer) -⋮---- -private _handleMessage(parsedData: ParsedMosMessage, messageString: string) -⋮---- -// huh, we've got a reply to something we've not sent. -⋮---- -// error message? -⋮---- -// unknown message.. -⋮---- -// this._readyToSendMessage = true -⋮---- -private _getMessageId(parsedData: ParsedMosMessage, messageString: string): string | undefined -⋮---- -// If there is a messageID: -⋮---- -// In non-strict mode: handle special cases: -⋮---- -// response doesn't contain messageId (compliant with MOS version 2.8) -// we can assume it's the same as our sent message: -⋮---- -// response doesn't contain messageId (compliant with MOS version 2.8) -// we can assume it's the same as our sent message: -⋮---- -/** */ -private _onError(error: Error) -⋮---- -// dispatch error!!!!! -⋮---- -/** */ -private _onClose(hadError: boolean) -⋮---- -// this._readyToSendMessage = false -⋮---- -private _triggerQueueCleanup() -⋮---- -// in case we're in unsync with messages, prevent deadlock: -⋮---- -private debugTrace(...args: any[]) -⋮---- -// eslint-disable-next-line no-console - -================ -File: packages/connector/package.json -================ -{ - "name": "@mos-connection/connector", - "version": "4.2.2", - "description": "MOS compliant TCP/IP Socket connection.", - "main": "dist/index.js", - "typings": "dist/index.d.ts", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/nrkno/sofie-mos-connection.git" - }, - "bugs": { - "url": "https://github.com/nrkno/sofie-mos-connection/issues" - }, - "homepage": "https://github.com/nrkno/sofie-mos-connection#readme", - "scripts": { - "build": "run -T rimraf dist && run build:main", - "build:main": "run -T tsc -p tsconfig.build.json", - "test": "run -T jest", - "test:coverage": "yarn test --coverage", - "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", - "validate:dev-dependencies": "yarn npm audit ", - "license-validate": "yarn sofie-licensecheck" - }, - "files": [ - "/dist", - "/CHANGELOG.md", - "/README.md", - "/LICENSE" - ], - "dependencies": { - "@mos-connection/helper": "4.2.2", - "@mos-connection/model": "4.2.2", - "eventemitter3": "^5.0.1", - "iconv-lite": "^0.6.3", - "tslib": "^2.5.3", - "xml-js": "^1.6.11", - "xmlbuilder": "^15.1.1" - }, - "keywords": [ - "mos", - "broadcast", - "socket", - "typescript", - "javascript", - "open", - "source", - "automation", - "ncs", - "nrcs", - "rundown", - "tv-production", - "sofie" - ], - "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", - "engines": { - "node": ">=14.18.0" - }, - "lint-staged": { - "*.{css,json,md,scss}": [ - "run -T prettier" - ], - "*.{ts,tsx,js,jsx}": [ - "run -T eslint" - ] - }, - "devDependencies": { - "@sofie-automation/code-standard-preset": "*", - "typescript": "~4.9.5" - } -} - -================ -File: packages/quick-mos/package.json -================ -{ - "name": "quick-mos", - "version": "0.0.0", - "private": true, - "description": "Read rundowns from files, use mos-connection and send mos commands", - "main": "src/index.ts", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/nrkno/sofie-mos-connection.git" - }, - "homepage": "https://github.com/nrkno/sofie-mos-connection#readme", - "author": { - "name": "Johan Nyman", - "email": "johan@superfly.tv", - "url": "http://superfly.tv" - }, - "contributors": [], - "scripts": { - "build": "run -T rimraf dist && run build:main", - "buildstart": "run build && run start", - "buildinspect": "run build && run inspect", - "build:main": "run -T tsc -p tsconfig.json", - "test": "run -T jest", - "inspect": "node --inspect dist/index.js", - "start": "ts-node src/index.ts", - "validate:dependencies": "yarn npm audit --environment production && yarn license-validate", - "validate:dev-dependencies": "yarn npm audit ", - "license-validate": "yarn sofie-licensecheck" - }, - "engines": { - "node": ">=14.18.0" - }, - "devDependencies": { - "@sofie-automation/code-standard-preset": "*", - "@types/node": "^20.10.5", - "@types/underscore": "^1.11.5", - "ts-node": "^10.9.1", - "typescript": "~4.9.5" - }, - "dependencies": { - "@mos-connection/connector": "4.2.2", - "@mos-connection/model": "4.2.2", - "chokidar": "^3.5.3", - "fast-clone": "^1.5.13", - "tslib": "^2.5.3", - "underscore": "^1.13.6" - }, - "prettier": "@sofie-automation/code-standard-preset/.prettierrc.json", - "lint-staged": { - "*.{css,json,md,scss}": [ - "run -T prettier" - ], - "*.{ts,tsx,js,jsx}": [ - "run -T eslint" - ] - }, - "files": [ - "/dist", - "/CHANGELOG.md", - "/README.md", - "/LICENSE" - ] -} - -================ -File: packages/connector/src/connection/NCSServerConnection.ts -================ -import { ConnectionType } from './socketConnection' -import { MosSocketClient, CallBackFunction, QueueMessage } from '../connection/mosSocketClient' -import { MosModel } from '@mos-connection/helper' -import { EventEmitter } from 'eventemitter3' -import { ParsedMosMessage } from './mosMessageParser' -⋮---- -export interface ClientDescription { - useHeartbeats: boolean - heartbeatConnected: boolean - client: MosSocketClient - clientDescription: MosModel.PortType -} -⋮---- -export interface INCSServerConnection { - on(event: 'rawMessage', listener: (type: string, message: string) => void): this -} -⋮---- -on(event: 'rawMessage', listener: (type: string, message: string) -⋮---- -export interface HandedOverQueue { - messages: QueueMessage[] - callbacks: { [messageId: string]: CallBackFunction } -} -export interface NCSServerConnectionEvents { - rawMessage: (...args: any[]) => void - warning: (str: string) => void - error: (err: Error) => void - info: (str: string) => void - connectionChanged: () => void -} -⋮---- -/** Handles connections to a NCS (server) */ -export class NCSServerConnection extends EventEmitter implements INCSServerConnection -⋮---- -// private _lastSeen: number -⋮---- -constructor( - id: string, - host: string, - mosID: string, - timeout: number | undefined, - heartbeatsInterval: number | undefined, - debug: boolean, - strict: boolean -) -get timeout(): number -/** Create a MOS client, which talks to */ -createClient(clientID: string, port: number, clientDescription: ConnectionType, useHeartbeats: boolean): void -⋮---- -/** */ -removeClient(clientID: string): void -⋮---- -/** */ -disableHeartbeats(): void -⋮---- -/** */ -enableHeartbeats(): void -⋮---- -/** */ -isHearbeatEnabled(): boolean -⋮---- -setAutoReconnectInterval(interval: number): void -⋮---- -connect(): void -⋮---- -// Connect client -⋮---- -// Send heartbeat and check connection -⋮---- -// Emit to _callbackOnConnectionChange -// if (this._callbackOnConnectionChange) this._callbackOnConnectionChange() -⋮---- -/** - * Sends a mos message. - * Returns a Promise which resolves when a MOS reply has been received. - */ -async executeCommand(message: MosModel.MosMessage): Promise -⋮---- -// Fill with clients -⋮---- -// Set mosID and ncsID -⋮---- -// Example: Port based on message type -⋮---- -public setDebug(debug: boolean): void -get connected(): boolean -getConnectedStatus(): -⋮---- -private _getClients(clientDescription: string): MosSocketClient[] -/** */ -get lowerPortClients(): MosSocketClient[] -⋮---- -/** */ -get upperPortClients(): MosSocketClient[] -⋮---- -/** */ -get queryPortClients(): MosSocketClient[] -get host(): string -get id(): string -⋮---- -handOverQueue(otherConnection: NCSServerConnection): void -⋮---- -// this._clients.forEach((client, id) => { -// // cmds[id] = client.client.handOverQueue() -// }) -⋮---- -receiveQueue(queue: -⋮---- -// @todo: keep order -// @todo: prevent callback-promise horror... -⋮---- -async dispose(): Promise -⋮---- -private _sendHeartBeats(): void -⋮---- -const triggerNextHeartBeat = () => -⋮---- -// probably a timeout -⋮---- -private debugTrace(...strs: any[]) -⋮---- -// eslint-disable-next-line no-console - -================ -File: packages/connector/src/MosConnection.ts -================ -import { Socket } from 'net' -import { ConnectionConfig } from './config/connectionConfig' -import { MosSocketServer } from './connection/mosSocketServer' -import { getMosTypes, IMOSAckStatus, IProfiles, MosTypes } from '@mos-connection/model' -import { MosDevice } from './MosDevice' -import { SocketServerEvent, SocketDescription, IncomingConnectionType } from './connection/socketConnection' -import { NCSServerConnection } from './connection/NCSServerConnection' -import { MosModel } from '@mos-connection/helper' -import { EventEmitter } from 'eventemitter3' -⋮---- -import { ParsedMosMessage, MosMessageParser } from './connection/mosMessageParser' -import { IConnectionConfig, IMosConnection, IMOSDeviceConnectionOptions } from './api' -import { PROFILE_VALIDNESS_CHECK_WAIT_TIME } from './lib' -⋮---- -export interface MosConnectionEvents { - // Note: These match the events defined in IMosConnection - rawMessage: (source: string, type: string, message: string) => void - info: (message: string, data?: any) => void - warning: (message: string) => void - error: (error: Error) => void -} -⋮---- -// Note: These match the events defined in IMosConnection -⋮---- -export class MosConnection extends EventEmitter implements IMosConnection -⋮---- -// private _isListening: Promise -⋮---- -/** */ -constructor(configOptions: IConnectionConfig) -⋮---- -// Setup utility functions for handling MosTypes: -⋮---- -/** - * Initiate the MosConnection, start accepting connections - */ -async init(): Promise -⋮---- -/** - * Establish a new connection to a MOS-device (NCS-server). When established, the new MOS-device will be emitted to this.onConnection() - * @param connectionOptions Connection options - */ -async connect(connectionOptions: IMOSDeviceConnectionOptions): Promise -⋮---- -// Connect to MOS-device: -⋮---- -// Create the secondary connection if configured -⋮---- -// For hot-standby, initially we need both connections to be able to establish -// so we can determine which one is active -⋮---- -// Check both connections on startup to determine which one to use -⋮---- -// Wait for initial connection attempts -⋮---- -// Handle that .openMediaHotStandby should not check for heartbeats on -// the secondary connection when the primary is connected -// And disable heartbeats on the primary when the primary is disconnected -⋮---- -// Initially disable heartbeats on secondary since primary is connected -⋮---- -// Primary is down, but secondary is available - use secondary -⋮---- -// Both are down initially, but try both -⋮---- -// Failover handling: -⋮---- -// Refresh connectionstatus -⋮---- -// Primary disconnected, enable secondary heartbeats -⋮---- -// Refresh connectionstatus -⋮---- -// Secondary is active when primary is down -⋮---- -// Both are down, try to connect to both -⋮---- -/** Callback is called when a new connection is established */ -onConnection(cb: (mosDevice: MosDevice) => void): void -/** True if mosConnection is listening for connections */ -get isListening(): boolean -⋮---- -/** TO BE IMPLEMENTED: True if mosConnection is mos-compliant */ -get isCompliant(): boolean -⋮---- -/** True if mosConnection is configured to accept connections */ -get acceptsConnections(): boolean -⋮---- -/** A list of the profiles mosConnection is currently configured to use */ -get profiles(): IProfiles -⋮---- -/** Close all connections and clear all data */ -async dispose(): Promise -/** Return a specific MOS-device */ -getDevice(id: string): MosDevice -/** Get a list of all MOS-devices */ -getDevices(): Array -disposeMosDevice(mosDevice: MosDevice): Promise -disposeMosDevice(myMosID: string, theirMosId0: string, theirMosId1: string | null): Promise -async disposeMosDevice( - myMosIDOrMosDevice: string | MosDevice, - theirMosId0?: string, - theirMosId1?: string | null -): Promise -⋮---- -// myMosID = myMosIDOrMosDevice -⋮---- -/** - * Do a check if the profile is valid. Throws if not. - * Optionally called after a mosConnection has been set up to ensure that all callbacks have been set up properly. - */ -checkProfileValidness(): void -⋮---- -/** TO BE IMPLEMENTED */ -get complianceText(): string -public setDebug(debug: boolean): void -private _registerMosDevice( - myMosID: string, - theirMosId0: string, - theirMosId1: string | null, - primary: NCSServerConnection | null, - secondary: NCSServerConnection | null -): MosDevice -⋮---- -// Add mosDevice to register: -⋮---- -// emit to .onConnection: -⋮---- -/** Set up TCP-server */ -private async _initiateIncomingConnections(): Promise -⋮---- -const initSocket = (port: number, portType: IncomingConnectionType) => -⋮---- -// handle error -⋮---- -const handleListen = async (socketServer: MosSocketServer) => -⋮---- -// All sockets are open and listening at this point -⋮---- -/** */ -private _registerIncomingClient(client: SocketDescription) -⋮---- -// messageParser.debug = this._debug -⋮---- -// Handle incoming data -⋮---- -// handles socket listeners -client.socket.on('close', (/*hadError: boolean*/) => { -⋮---- -const handleMessage = async (parsed: ParsedMosMessage, _messageString: string) => -⋮---- -const sendReply = (message: MosModel.MosMessage) => -⋮---- -// No MOS-device found in the register -// Register a new mosDevice to use for this connection: -⋮---- -// Setup a "primary" connection back to the mos-device, so that we can automatically -// send commands to it through the mosDevice -⋮---- -// Try to add the main mos message key to the error: -⋮---- -// Something went wrong -⋮---- -// Internal/parsing error -⋮---- -// Log error: -⋮---- -// reply with NACK: -// TODO: implement ACK -// https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS_Protocol_Version_2.8.5_Final.htm#mosAck -⋮---- -sendReply(msg) // TODO: Need tests -⋮---- -// No MOS-device found in the register -⋮---- -// We can't handle the message, reply with a NACK: -⋮---- -sendReply(msg) // TODO: Need tests -⋮---- -// Register this socket: -⋮---- -/** Close socket and clean up */ -private _disposeIncomingSocket(socketID: string) -/** Get new unique id */ -static get nextSocketID(): string -private debugTrace(...strs: any[]) -⋮---- -// eslint-disable-next-line no-console -⋮---- -private _scheduleCheckProfileValidness(orgStack: Error): void -⋮---- -// eslint-disable-next-line no-console -⋮---- -/** - * Checks that all callbacks have been set up properly, according to which MOS-profile have been set in the options. - * throws if something's wrong - */ -private _checkProfileValidness(orgStack: Error): void -⋮---- -const fixError = (message: string) => -⋮---- -// Change the stack of the error, so that it points to the original call to the MosDevice: - -================ -File: packages/connector/src/MosDevice.ts -================ -import { Socket } from 'net' -import { NCSServerConnection } from './connection/NCSServerConnection' -⋮---- -import { - IMOSObject, - IMOSRunningOrder, - IMOSRunningOrderBase, - IMOSRunningOrderStatus, - IMOSStoryStatus, - IMOSItemStatus, - IMOSROReadyToAir, - IMOSStoryAction, - IMOSItem, - IMOSROAction, - IMOSROStory, - IMOSItemAction, - IMOSROFullStory, - IMOSROAck, - IMOSAckStatus, - IMOSObjectStatus, - IMOSObjectList, - IMOSRequestObjectList, - IMOSListSearchableSchema, - IMOSAck, - MosItemReplaceOptions, - IMOSExternalMetaData, - IMOSListMachInfo, - IMOSDefaultActiveX, - IMOSString128, - IMOSTime, - getMosTypes, - MosTypes, - AnyXMLObject, - AnyXMLValueSingular, - IMOSObjectAirStatus, -} from '@mos-connection/model' -import { MosModel, MosReplyError } from '@mos-connection/helper' -import { IConnectionConfig, IMOSConnectionStatus, IMOSDevice } from './api' -import { PROFILE_VALIDNESS_CHECK_WAIT_TIME, has, safeStringify } from './lib' -import { ParsedMosMessage } from './connection/mosMessageParser' -import { XMLMosListSearchableSchema, XMLMosObjectList, isXMLObject } from '@mos-connection/helper/dist/mosModel' -⋮---- -export class MosDevice implements IMOSDevice -⋮---- -// private _host: string -⋮---- -} // Use same names as IProfiles? -⋮---- -/** If set, will do more checks that mos-protocol is properly implemented */ -⋮---- -// Callbacks for Profile 0: -⋮---- -// Callbacks for Profile 1: -⋮---- -// Callbacks for Profile 2: -⋮---- -// Callbacks for Profile 3: -⋮---- -// Callbacks for Profile 4: -⋮---- -constructor( - idPrimary: string, - idSecondary: string | null, - connectionConfig: IConnectionConfig, - primaryConnection: NCSServerConnection | null, - secondaryConnection: NCSServerConnection | null, - offSpecFailover: boolean, - strict: boolean -) -⋮---- -// this._id = this.mosTypes.mosString128.create(connectionConfig.mosID).toString() -⋮---- -// Add params to this in MosConnection/MosDevice -⋮---- -this.hwRev = this.mosTypes.mosString128.create('0.1') // empty string returnes -⋮---- -this.switchConnections() // and hope no current message goes lost -⋮---- -/** True if MOS-device has connection to server (can send messages) */ -get hasConnection(): boolean -/** Primary ID (probably the NCS-ID) */ -get idPrimary(): string -/** Secondary / Buddy ID (probably the MOS-ID) */ -get idSecondary(): string | null -/** Host name (IP-address) of the primary server */ -get primaryHost(): string | null -/** Name (ID) of the primary server */ -get primaryId(): string | null -/** Host name (IP-address) of the secondary (buddy) server */ -get secondaryHost(): string | null -/** Name (ID) of the secondary (buddy) server */ -get secondaryId(): string | null -⋮---- -connect(): void -async dispose(): Promise -⋮---- -async routeData(data: AnyXMLObject, port: MosModel.PortType): Promise -⋮---- -// Suppress console spam: -⋮---- -// this.debugTrace('keys', Object.keys(data)) -⋮---- -// Route and format data: -// Profile 0: ------------------------------------------------------------------------------------------------- -⋮---- -// send immediate reply on the same port: -⋮---- -// Profile 1: ------------------------------------------------------------------------------------------------- -⋮---- -// 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 -⋮---- -// const firstObject = mosObjects.shift() as IMOSObject -// const resp = new MosObj(firstObject) -// resolve(resp) -const sendNextObject = () => -⋮---- -// eslint-disable-next-line no-console -⋮---- -// eslint-disable-next-line no-console -⋮---- -// What this should contain isn't well defined in the protocol -⋮---- -// Profile 2: ------------------------------------------------------------------------------------------------- -// Translate deprecated messages into the functionally equivalent roElementActions: -⋮---- -// This is equivalent to inserting a story at the end of the running order -⋮---- -// From documentation: -// **Note**: If the second tag is blank move the story to the bottom of the Running Order. -⋮---- -// 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: -⋮---- -// Non strict mode: This is technically out of spec, but it's a common mistake, so we'll handle it like so: -⋮---- -storyID: roStorySwap.storyID, // an array -⋮---- -// element_target: { -// storyID: roStoryDelete.storyID[1], -// }, -⋮---- -// 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. -⋮---- -// Technically a no-op: -⋮---- -// 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) -⋮---- -// An additional validation checking the length of items can be added -⋮---- -// RO not found -⋮---- -resp.Stories = [] // dont return these (?) -⋮---- -// Profile 3: ------------------------------------------------------------------------------------------------- -⋮---- -// Profile 4: ------------------------------------------------------------------------------------------------- -⋮---- -// TODO: Use reject if function dont exists? Put Nack in ondata -⋮---- -// Unsupported messages: -------------------------------------------------------------------------------------- -⋮---- -ID: this.mosTypes.mosString128.create('0'), // TODO: Depends on type of message, needs logic -⋮---- -// ============================================================================================================ -// ========================== Profile 0 =================================================================== -// ============================================================================================================ -async requestMachineInfo(): Promise -⋮---- -onRequestMachineInfo(cb: () => Promise): void -⋮---- -onConnectionChange(cb: (connectionStatus: IMOSConnectionStatus) => void): void -⋮---- -getConnectionStatus(): IMOSConnectionStatus -⋮---- -// Deprecated methods: -/** @deprecated getMachineInfo is deprecated, use requestMachineInfo instead */ -async getMachineInfo(): Promise -/** @deprecated onGetMachineInfo is deprecated, use onRequestMachineInfo instead */ -onGetMachineInfo(cb: () => Promise): void -⋮---- -// ============================================================================================================ -// ========================== Profile 1 =================================================================== -// ============================================================================================================ -async sendMOSObject(obj: IMOSObject): Promise -⋮---- -onRequestMOSObject(cb: (objId: string) => Promise): void -⋮---- -async sendRequestMOSObject(objID: IMOSString128): Promise -⋮---- -onRequestAllMOSObjects(cb: () => Promise>): void -onMOSObjects(cb: (objs: IMOSObject[]) => Promise): void -⋮---- -async sendRequestAllMOSObjects(pause = 0): Promise -⋮---- -// 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 - */ -private async _sendAllMOSObjects(objs: IMOSObject[]): Promise -⋮---- -/** - * @deprecated getMOSObject is deprecated, use sendRequestMOSObject instead - */ -async getMOSObject(objId: IMOSString128): Promise -/** @deprecated getAllMOSObjects is deprecated, use sendRequestAllMOSObjects instead */ -async getAllMOSObjects(): Promise -⋮---- -// ============================================================================================================ -// ========================== Profile 2 =================================================================== -// ============================================================================================================ -onCreateRunningOrder(cb: (ro: IMOSRunningOrder) => Promise): void -async sendCreateRunningOrder(ro: IMOSRunningOrder): Promise -⋮---- -onReplaceRunningOrder(cb: (ro: IMOSRunningOrder) => Promise): void -async sendReplaceRunningOrder(ro: IMOSRunningOrder): Promise -⋮---- -onDeleteRunningOrder(cb: (runningOrderId: IMOSString128) => Promise): void -async sendDeleteRunningOrder(runningOrderId: IMOSString128): Promise -⋮---- -onRequestRunningOrder(cb: (runningOrderId: IMOSString128) => Promise): void -⋮---- -async sendRequestRunningOrder(runningOrderId: IMOSString128): Promise -/** - * @deprecated getRunningOrder is deprecated, use sendRequestRunningOrder instead - */ -async getRunningOrder(runningOrderId: IMOSString128): Promise -⋮---- -onMetadataReplace(cb: (metadata: IMOSRunningOrderBase) => Promise): void -async sendMetadataReplace(metadata: IMOSRunningOrderBase): Promise -⋮---- -onRunningOrderStatus(cb: (status: IMOSRunningOrderStatus) => Promise): void -⋮---- -onStoryStatus(cb: (status: IMOSStoryStatus) => Promise): void -onItemStatus(cb: (status: IMOSItemStatus) => Promise): void -⋮---- -/** @deprecated setRunningOrderStatus is deprecated, use sendRunningOrderStatus instead */ -async setRunningOrderStatus(status: IMOSRunningOrderStatus): Promise -/** @deprecated setStoryStatus is deprecated, use sendStoryStatus instead */ -async setStoryStatus(status: IMOSStoryStatus): Promise -/** @deprecated setItemStatus is deprecated, use sendItemStatus instead */ -async setItemStatus(status: IMOSItemStatus): Promise -⋮---- -async sendRunningOrderStatus(status: IMOSRunningOrderStatus): Promise -⋮---- -async sendStoryStatus(status: IMOSStoryStatus): Promise -async sendItemStatus(status: IMOSItemStatus): Promise -onReadyToAir(cb: (Action: IMOSROReadyToAir) => Promise): void -async sendReadyToAir(Action: IMOSROReadyToAir): Promise -onROInsertStories(cb: (Action: IMOSStoryAction, Stories: Array) => Promise): void -async sendROInsertStories(Action: IMOSStoryAction, Stories: Array): Promise -onROInsertItems(cb: (Action: IMOSItemAction, Items: Array) => Promise): void -async sendROInsertItems(Action: IMOSItemAction, Items: Array): Promise -onROReplaceStories(cb: (Action: IMOSStoryAction, Stories: Array) => Promise): void -async sendROReplaceStories(Action: IMOSStoryAction, Stories: Array): Promise -onROReplaceItems(cb: (Action: IMOSItemAction, Items: Array) => Promise): void -async sendROReplaceItems(Action: IMOSItemAction, Items: Array): Promise -onROMoveStories(cb: (Action: IMOSStoryAction, Stories: Array) => Promise): void -async sendROMoveStories(Action: IMOSStoryAction, Stories: Array): Promise -onROMoveItems(cb: (Action: IMOSItemAction, Items: Array) => Promise): void -async sendROMoveItems(Action: IMOSItemAction, Items: Array): Promise -onRODeleteStories(cb: (Action: IMOSROAction, Stories: Array) => Promise): void -async sendRODeleteStories(Action: IMOSROAction, Stories: Array): Promise -onRODeleteItems(cb: (Action: IMOSStoryAction, Items: Array) => Promise): void -async sendRODeleteItems(Action: IMOSStoryAction, Items: Array): Promise -onROSwapStories( - cb: (Action: IMOSROAction, StoryID0: IMOSString128, StoryID1: IMOSString128) => Promise -): void -async sendROSwapStories( - Action: IMOSROAction, - StoryID0: IMOSString128, - StoryID1: IMOSString128 -): Promise -onROSwapItems( - cb: (Action: IMOSStoryAction, ItemID0: IMOSString128, ItemID1: IMOSString128) => Promise -): void -async sendROSwapItems(Action: IMOSStoryAction, ItemID0: IMOSString128, ItemID1: IMOSString128): Promise -⋮---- -// ============================================================================================================ -// ========================== Profile 3 =================================================================== -// ============================================================================================================ -onObjectCreate(cb: (object: IMOSObject) => Promise): void -⋮---- -async sendObjectCreate(object: IMOSObject): Promise -⋮---- -onItemReplace(cb: (roID: IMOSString128, storyID: IMOSString128, item: IMOSItem) => Promise): void -⋮---- -async sendItemReplace(options: MosItemReplaceOptions): Promise -⋮---- -onRequestSearchableSchema(cb: (username: string) => Promise): void -⋮---- -async sendRequestSearchableSchema(username: string): Promise -⋮---- -onRequestObjectList(cb: (objList: IMOSRequestObjectList) => Promise): void -⋮---- -async sendRequestObjectList(reqObjList: IMOSRequestObjectList): Promise -⋮---- -onRequestObjectActionNew(cb: (obj: IMOSObject) => Promise): void -async sendRequestObjectActionNew(obj: IMOSObject): Promise -⋮---- -onRequestObjectActionUpdate(cb: (objId: IMOSString128, obj: IMOSObject) => Promise): void -async sendRequestObjectActionUpdate(objId: IMOSString128, obj: IMOSObject): Promise -onRequestObjectActionDelete(cb: (objId: IMOSString128) => Promise): void -async sendRequestObjectActionDelete(objId: IMOSString128): Promise -⋮---- -// Deprecated methods: -/** @deprecated onMosObjCreate is deprecated, use onObjectCreate instead */ -onMosObjCreate(cb: (object: IMOSObject) => Promise): void -/** @deprecated mosObjCreate is deprecated, use sendObjectCreate instead */ -async mosObjCreate(object: IMOSObject): Promise -/** @deprecated onMosItemReplace is deprecated, use onItemReplace instead */ -onMosItemReplace(cb: (roID: IMOSString128, storyID: IMOSString128, item: IMOSItem) => Promise): void -/** @deprecated mosItemReplace is deprecated, use sendItemReplace instead */ -async mosItemReplace(options: MosItemReplaceOptions): Promise -/** @deprecated onMosReqSearchableSchema is deprecated, use onRequestSearchableSchema instead */ -onMosReqSearchableSchema(cb: (username: string) => Promise): void -/** @deprecated mosRequestSearchableSchema is deprecated, use sendRequestSearchableSchema instead */ -async mosRequestSearchableSchema(username: string): Promise -/** @deprecated onMosReqObjectList is deprecated, use onRequestObjectList instead */ -onMosReqObjectList(cb: (objList: IMOSRequestObjectList) => Promise): void -/** @deprecated mosRequestObjectList is deprecated, use sendRequestObjectList instead */ -async mosRequestObjectList(reqObjList: IMOSRequestObjectList): Promise -/** @deprecated onMosReqObjectAction is deprecated, use onRequestObjectAction*** instead */ -onMosReqObjectAction(_cb: (action: string, obj: IMOSObject) => Promise): void -⋮---- -// ============================================================================================================ -// ========================== Profile 4 =================================================================== -// ============================================================================================================ -onRequestAllRunningOrders(cb: () => Promise): void -async sendRequestAllRunningOrders(): Promise> -onRunningOrderStory(cb: (story: IMOSROFullStory) => Promise): void -async sendRunningOrderStory(story: IMOSROFullStory): Promise -⋮---- -// Deprecated methods: -/** @deprecated onROReqAll is deprecated, use onRequestAllRunningOrders instead */ -onROReqAll(cb: () => Promise): void -/** @deprecated getAllRunningOrders is deprecated, use sendRequestAllRunningOrders instead */ -async getAllRunningOrders(): Promise> -/** @deprecated onROStory is deprecated, use onRunningOrderStory instead */ -onROStory(cb: (story: IMOSROFullStory) => Promise): void -/** @deprecated sendROStory is deprecated, use sendRunningOrderStory instead */ -async sendROStory(story: IMOSROFullStory): Promise -⋮---- -// ============================================================================================================= -// ///////////////////////////////////// End of Profile methods \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ -setDebug(debug: boolean): void -/** - * Do a check if the profile is valid. Throws if not. - * Optionally called after a mosDevice has been set up to ensure that all callbacks have been set up properly. - */ -checkProfileValidness(): void -⋮---- -/** Does a check if the specified profile is set, and throws otherwise */ -private checkProfile(methodName: string, profile: keyof IProfiles) -⋮---- -private async executeCommand(message: MosModel.MosMessage, resend?: boolean): Promise -private switchConnections(): void -⋮---- -// Switch: -⋮---- -otherConnection = currentConnection // former current connection -⋮---- -private async switchConnectionsAndExecuteCommand(message: MosModel.MosMessage): Promise -⋮---- -// @todo: we may deadlock if primary is down for us, but up for buddy -⋮---- -private _emitConnectionChange(): void -private handleParseReply(fcn: (strict: boolean) => T): T -⋮---- -// Try again in non-strict mode, to append a sidecar to the thrown error if possible: -⋮---- -// ignore error -⋮---- -private unknownReply(reply: MosReply) -private badRoAckReply(xmlRoAck: AnyXMLObject) -/** throws if there is an error */ -private _ensureReply(reply: ParsedMosMessage): MosReply -private _scheduleCheckProfileValidness(orgStack: Error): void -⋮---- -// eslint-disable-next-line no-console -⋮---- -/** - * Checks that all callbacks have been set up properly, according to which MOS-profile have been set in the options. - * throws if something's wrong - */ -private _checkProfileValidness(orgStack: Error): void -⋮---- -const fixError = (message: string) => -⋮---- -// Change the stack of the error, so that it points to the original call to the MosDevice: -⋮---- -/** For MOS-devices: Require a callback to have been set */ -const requireCallback = (profile: string, callbackName: string, method: (...args: any[]) => any) => -⋮---- -// @ts-expect-error no index signature -⋮---- -/** Check: Requires that a callback has been set */ -const requireMOSCallback = (profile: string, callbackName: string, method: (...args: any[]) => any) => -/** Require another profile to have been set */ -const requireProfile = (profile: number, requiredProfile: number) => -⋮---- -// @ts-expect-error no index signature -⋮---- -/* eslint-disable @typescript-eslint/unbound-method */ -⋮---- -// _callbackOnConnectionChange not required -⋮---- -private debugTrace(...strs: any[]) -⋮---- -// eslint-disable-next-line no-console -⋮---- -export interface IProfiles { - profile0: boolean - profile1: boolean - profile2: boolean - profile3: boolean - profile4: boolean - profile5: boolean - profile6: boolean - profile7: boolean -} -⋮---- -type MosReply = ParsedMosMessage - - - -================================================================ -End of Codebase -================================================================ From 15080ca98dbfa9ea090a1fc02a2c2b9975b310aa Mon Sep 17 00:00:00 2001 From: olzzon Date: Thu, 10 Apr 2025 13:35:50 +0200 Subject: [PATCH 7/8] test: hotstandby - only one are connected at a time. --- .../src/__tests__/OpenMediaHotStandby.spec.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/connector/src/__tests__/OpenMediaHotStandby.spec.ts b/packages/connector/src/__tests__/OpenMediaHotStandby.spec.ts index 853d3ff9..af29ac99 100644 --- a/packages/connector/src/__tests__/OpenMediaHotStandby.spec.ts +++ b/packages/connector/src/__tests__/OpenMediaHotStandby.spec.ts @@ -128,7 +128,8 @@ describe('Hot Standby Feature', () => { if (primary && secondary) { expect(primary.getConnectedStatus().connected).toBe(true) - expect(secondary.getConnectedStatus().connected).toBe(true) + // Hot standby only connects one connection at a time: + expect(secondary.getConnectedStatus().connected).toBe(false) // Verify heartbeat states expect(primary.isHearbeatEnabled()).toBe(true) @@ -142,7 +143,7 @@ describe('Hot Standby Feature', () => { if (primary && secondary) { expect(primary.connected).toBe(true) - expect(secondary.connected).toBe(true) + expect(secondary.connected).toBe(false) // Disconnect primary connection: await discconnectPrimary() @@ -172,7 +173,8 @@ describe('Hot Standby Feature', () => { if (primary && secondary) { // Initially, primary should be connected and secondary should be connected but with heartbeats disabled expect(primary.connected).toBe(true) - expect(secondary.connected).toBe(true) + // Hot standby only connects one connection at a time: + expect(secondary.connected).toBe(false) expect(primary.isHearbeatEnabled()).toBe(true) expect(secondary.isHearbeatEnabled()).toBe(false) @@ -204,7 +206,8 @@ describe('Hot Standby Feature', () => { if (primary && secondary) { // Initially, both should be connected with primary heartbeats enabled and secondary disabled expect(primary.connected).toBe(true) - expect(secondary.connected).toBe(true) + // Hot standby only connects one connection at a time: + expect(secondary.connected).toBe(false) expect(primary.isHearbeatEnabled()).toBe(true) expect(secondary.isHearbeatEnabled()).toBe(false) @@ -234,9 +237,10 @@ describe('Hot Standby Feature', () => { expect(secondary).toBeTruthy() if (primary && secondary) { - // Initial setup - primary connected and secondary connected but with heartbeats disabled + // Initial setup - primary connected and secondary not but with heartbeats disabled expect(primary.connected).toBe(true) - expect(secondary.connected).toBe(true) + // Hot standby only connects one connection at a time: + expect(secondary.connected).toBe(false) // First disconnect primary to force secondary active await discconnectPrimary() From d3c2e7452bb6a2c82eef8963e47edd08b4a2a02d Mon Sep 17 00:00:00 2001 From: olzzon Date: Thu, 10 Apr 2025 13:42:52 +0200 Subject: [PATCH 8/8] feat: update mos-dummy-device to inform about quickMos for ingesting --- .repomixignore | 5 ----- packages/mos-dummy-device/README.md | 5 ++++- packages/mos-dummy-device/src/index.ts | 2 ++ 3 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 .repomixignore diff --git a/.repomixignore b/.repomixignore deleted file mode 100644 index 72aca00a..00000000 --- a/.repomixignore +++ /dev/null @@ -1,5 +0,0 @@ -__tests__/ -__mocks__/ -dist/ -yarn.lock -CHANGELOG.md \ No newline at end of file diff --git a/packages/mos-dummy-device/README.md b/packages/mos-dummy-device/README.md index 736ecb07..500826ac 100644 --- a/packages/mos-dummy-device/README.md +++ b/packages/mos-dummy-device/README.md @@ -1,7 +1,10 @@ -# MOS Dummy Server +# MOS Dummy Device A dummy MOS (Media Object Server) server for testing MOS clients with failover capabilities. This server implements the MOS protocol and allows you to simulate connection issues and manage rundowns through JSON files. +The mos dummy device is primary made for debugging MOS server connections and failovers. +For quick ingesting MOS rundowns use quick-mos + ## Features - MOS protocol support Profiles 0, 1, 2, 3, and 4 diff --git a/packages/mos-dummy-device/src/index.ts b/packages/mos-dummy-device/src/index.ts index b30591ab..5db4d747 100644 --- a/packages/mos-dummy-device/src/index.ts +++ b/packages/mos-dummy-device/src/index.ts @@ -348,6 +348,8 @@ class DummyMosServer { // Send all loaded rundowns to a newly connected device private sendAllRunningOrders(device: MosDevice): void { + // This part has not been implemented, quickTSR works for now + return if (this.rundowns.size === 0) { console.log('No rundowns to send to new device') return