New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@meyer/hyperdeck-emulator

Package Overview
Dependencies
Maintainers
2
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@meyer/hyperdeck-emulator - npm Package Compare versions

Comparing version 0.0.4-canary.38.80bd642 to 0.0.4-canary.40.e061397

316

dist/api.d.ts

@@ -1,39 +0,281 @@

export declare const paramsByKey: {
help: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
commands: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"device info": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"disk list": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
quit: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
ping: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
preview: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
play: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
playrange: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"playrange set": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"playrange clear": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"play on startup": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"play option": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
record: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"record spill": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
stop: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"clips count": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"clips get": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"clips add": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"clips remove": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"clips clear": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"transport info": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"slot info": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"slot select": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"slot unblock": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
"dynamic range": Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
notify: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
goto: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
jog: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
shuttle: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
remote: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
configuration: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
uptime: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
format: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
identify: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
watchdog: Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger">;
import { ArgKey, ArgsTypes } from './types';
interface Option<A extends Record<string, ArgKey> = Record<string, ArgKey>, R extends Record<string, ArgKey> = Record<string, ArgKey>> {
description: string;
arguments?: A;
returnValue: R;
}
declare type ParamMap = Record<string, Option>;
interface ParamInfo {
paramType: ArgKey;
/** The camelcase name we use everywhere */
paramName: string;
}
/** Internal container class that holds metadata about each HyperDeck event */
declare class HyperDeckAPI<P extends ParamMap = {}> {
readonly options: P;
constructor(options?: P);
addOption: <K extends string, A extends Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger" | "clips" | "slotstatus" | "transportstatus"> = {}, R extends Record<string, "string" | "number" | "boolean" | "timecode" | "videoformat" | "stopmode" | "goto" | "videoinput" | "audioinput" | "fileformat" | "audiocodec" | "timecodeinput" | "recordtrigger" | "clips" | "slotstatus" | "transportstatus"> = {}>(key: K | [K, ...string[]], option: Option<A, R>) => HyperDeckAPI<P & { [key in K]: Option<A, R>; }>;
/** Get a `Set` of param names keyed by function name */
getParamsByCommandName: () => { [K in keyof P]: Record<string, ParamInfo>; };
}
declare const api: HyperDeckAPI<{
help: Option<{}, {}>;
} & {
commands: Option<{}, {
commands: "string";
}>;
} & {
"device info": Option<{}, {
protocolVersion: "string";
model: "string";
slotCount: "string";
}>;
} & {
"disk list": Option<{
slotId: "number";
}, {
slotId: "number";
}>;
} & {
quit: Option<{}, {}>;
} & {
ping: Option<{}, {}>;
} & {
preview: Option<{
enable: "boolean";
}, {}>;
} & {
play: Option<{
speed: "number";
loop: "boolean";
singleClip: "boolean";
}, {}>;
} & {
playrange: Option<{}, {}>;
} & {
"playrange set": Option<{
clipId: "number";
in: "timecode";
out: "timecode";
timelineIn: "number";
timelineOut: "number";
}, {}>;
} & {
"playrange clear": Option<{}, {}>;
} & {
"play on startup": Option<{
enable: "boolean";
singleClip: "boolean";
}, {}>;
} & {
"play option": Option<{
stopMode: "stopmode";
}, {}>;
} & {
record: Option<{
name: "string";
}, {}>;
} & {
"record spill": Option<{
slotId: "number";
}, {}>;
} & {
stop: Option<{}, {}>;
} & {
"clips count": Option<{}, {
clipCount: "number";
}>;
} & {
"clips get": Option<{
clipId: "number";
count: "number";
version: "number";
}, {
clips: "clips";
}>;
} & {
"clips add": Option<{
name: "string";
clipId: "number";
in: "timecode";
out: "timecode";
}, {}>;
} & {
"clips remove": Option<{
clipId: "number";
}, {}>;
} & {
"clips clear": Option<{}, {}>;
} & {
"transport info": Option<{}, {
status: "transportstatus";
speed: "number";
slotId: "number";
clipId: "number";
singleClip: "boolean";
displayTimecode: "timecode";
timecode: "timecode";
videoFormat: "videoformat";
loop: "boolean";
}>;
} & {
"slot info": Option<{
slotId: "number";
}, {
slotId: "number";
status: "slotstatus";
volumeName: "string";
recordingTime: "timecode";
videoFormat: "videoformat";
}>;
} & {
"slot select": Option<{
slotId: "number";
videoFormat: "videoformat";
}, {}>;
} & {
"slot unblock": Option<{
slotId: "number";
}, {}>;
} & {
"dynamic range": Option<{
playbackOverride: "string";
}, {}>;
} & {
notify: Option<{
remote: "boolean";
transport: "boolean";
slot: "boolean";
configuration: "boolean";
droppedFrames: "boolean";
displayTimecode: "boolean";
timelinePosition: "boolean";
playrange: "boolean";
dynamicRange: "boolean";
}, {
remote: "boolean";
transport: "boolean";
slot: "boolean";
configuration: "boolean";
droppedFrames: "boolean";
displayTimecode: "boolean";
timelinePosition: "boolean";
playrange: "boolean";
dynamicRange: "boolean";
}>;
} & {
goto: Option<{
clipId: "number";
clip: "goto";
timeline: "goto";
timecode: "timecode";
slotId: "number";
}, {}>;
} & {
jog: Option<{
timecode: "timecode";
}, {}>;
} & {
shuttle: Option<{
speed: "number";
}, {}>;
} & {
remote: Option<{
enable: "boolean";
override: "boolean";
}, {}>;
} & {
configuration: Option<{
videoInput: "videoinput";
audioInput: "audioinput";
fileFormat: "fileformat";
audioCodec: "audiocodec";
timecodeInput: "timecodeinput";
timecodePreset: "timecode";
audioInputChannels: "number";
recordTrigger: "recordtrigger";
recordPrefix: "string";
appendTimestamp: "boolean";
}, {
videoInput: "videoinput";
audioInput: "audioinput";
fileFormat: "fileformat";
audioCodec: "audiocodec";
timecodeInput: "timecodeinput";
timecodePreset: "timecode";
audioInputChannels: "number";
recordTrigger: "recordtrigger";
recordPrefix: "string";
appendTimestamp: "boolean";
}>;
} & {
uptime: Option<{}, {
uptime: "number";
}>;
} & {
format: Option<{
prepare: "string";
confirm: "string";
}, {
token: "string";
}>;
} & {
identify: Option<{
enable: "boolean";
}, {}>;
} & {
watchdog: Option<{
period: "number";
}, {}>;
}>;
declare type CommandConfigs = {
[K in keyof typeof api['options']]: typeof api['options'][K];
};
export declare const paramsByCommandName: {
help: Record<string, ParamInfo>;
commands: Record<string, ParamInfo>;
"device info": Record<string, ParamInfo>;
"disk list": Record<string, ParamInfo>;
quit: Record<string, ParamInfo>;
ping: Record<string, ParamInfo>;
preview: Record<string, ParamInfo>;
play: Record<string, ParamInfo>;
playrange: Record<string, ParamInfo>;
"playrange set": Record<string, ParamInfo>;
"playrange clear": Record<string, ParamInfo>;
"play on startup": Record<string, ParamInfo>;
"play option": Record<string, ParamInfo>;
record: Record<string, ParamInfo>;
"record spill": Record<string, ParamInfo>;
stop: Record<string, ParamInfo>;
"clips count": Record<string, ParamInfo>;
"clips get": Record<string, ParamInfo>;
"clips add": Record<string, ParamInfo>;
"clips remove": Record<string, ParamInfo>;
"clips clear": Record<string, ParamInfo>;
"transport info": Record<string, ParamInfo>;
"slot info": Record<string, ParamInfo>;
"slot select": Record<string, ParamInfo>;
"slot unblock": Record<string, ParamInfo>;
"dynamic range": Record<string, ParamInfo>;
notify: Record<string, ParamInfo>;
goto: Record<string, ParamInfo>;
jog: Record<string, ParamInfo>;
shuttle: Record<string, ParamInfo>;
remote: Record<string, ParamInfo>;
configuration: Record<string, ParamInfo>;
uptime: Record<string, ParamInfo>;
format: Record<string, ParamInfo>;
identify: Record<string, ParamInfo>;
watchdog: Record<string, ParamInfo>;
};
export declare type CommandName = keyof CommandConfigs;
export declare type CommandParamsByCommandName = {
[K in CommandName]: ArgsTypes<NonNullable<CommandConfigs[K]['arguments']>>;
};
export declare type CommandResponsesByCommandName = {
[K in CommandName]: ArgsTypes<NonNullable<CommandConfigs[K]['returnValue']>>;
};
export declare function assertValidCommandName(value: any): asserts value is CommandName;
export {};
//# sourceMappingURL=api.d.ts.map

4

dist/formatClipsGetResponse.d.ts

@@ -1,3 +0,3 @@

import * as ResponseInterface from './types/ResponseInterface';
export declare const formatClipsGetResponse: (res: ResponseInterface.ClipsGet) => Record<string, string | number>;
import { CommandResponsesByCommandName } from './api';
export declare const formatClipsGetResponse: (res: CommandResponsesByCommandName['clips get']) => Record<string, string | number>;
//# sourceMappingURL=formatClipsGetResponse.d.ts.map

@@ -12,181 +12,35 @@ 'use strict';

function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
class FormattedError extends Error {
constructor(template, ...args) {
super(util.format(template, ...args));
this.template = template;
this.args = args;
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
return true;
} catch (e) {
return false;
function invariant(condition, message, ...args) {
if (!condition) {
throw new FormattedError(message, ...args);
}
}
function _construct(Parent, args, Class) {
if (_isNativeReflectConstruct()) {
_construct = Reflect.construct;
} else {
_construct = function _construct(Parent, args, Class) {
var a = [null];
a.push.apply(a, args);
var Constructor = Function.bind.apply(Parent, a);
var instance = new Constructor();
if (Class) _setPrototypeOf(instance, Class.prototype);
return instance;
};
}
class Timecode {
constructor(hh, mm, ss, ff) {
const timecode = [hh, mm, ss, ff].map(code => {
const codeInt = Math.floor(code);
!(codeInt === code && code >= 0 && code <= 99) ? invariant(false, 'Timecode params must be an integer between 0 and 99') : void 0; // turn the integer into a potentially zero-prefixed string
return _construct.apply(null, arguments);
}
return (codeInt + 100).toString().slice(-2);
}).join(':');
function _isNativeFunction(fn) {
return Function.toString.call(fn).indexOf("[native code]") !== -1;
}
function _wrapNativeSuper(Class) {
var _cache = typeof Map === "function" ? new Map() : undefined;
_wrapNativeSuper = function _wrapNativeSuper(Class) {
if (Class === null || !_isNativeFunction(Class)) return Class;
if (typeof Class !== "function") {
throw new TypeError("Super expression must either be null or a function");
}
if (typeof _cache !== "undefined") {
if (_cache.has(Class)) return _cache.get(Class);
_cache.set(Class, Wrapper);
}
function Wrapper() {
return _construct(Class, arguments, _getPrototypeOf(this).constructor);
}
Wrapper.prototype = Object.create(Class.prototype, {
constructor: {
value: Wrapper,
enumerable: false,
writable: true,
configurable: true
}
});
return _setPrototypeOf(Wrapper, Class);
};
return _wrapNativeSuper(Class);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
var it;
if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
return function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
this.toString = () => timecode;
}
it = o[Symbol.iterator]();
return it.next.bind(it);
}
// A type of promise-like that resolves synchronously and supports only one observer
const _iteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.iterator || (Symbol.iterator = Symbol("Symbol.iterator"))) : "@@iterator";
const _asyncIteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator"))) : "@@asyncIterator";
// Asynchronously call a function and send errors to recovery continuation
function _catch(body, recover) {
try {
var result = body();
} catch(e) {
return recover(e);
}
if (result && result.then) {
return result.then(void 0, recover);
}
return result;
}
function invariant(condition, message) {
if (!condition) {
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
args[_key - 2] = arguments[_key];
}
throw new Error(util.format.apply(util, [message].concat(args)));
}
}
var Timecode = function Timecode(hh, mm, ss, ff) {
var timecode = [hh, mm, ss, ff].map(function (code) {
var codeInt = Math.floor(code);
!(codeInt === code && code >= 0 && code <= 99) ? invariant(false, 'Timecode params must be an integer between 0 and 99') : void 0; // turn the integer into a potentially zero-prefixed string
return (codeInt + 100).toString().slice(-2);
}).join(':');
this.toString = function () {
return timecode;
};
};
Timecode.toTimecode = function (tcString) {
var bits = tcString.split(':');
Timecode.toTimecode = tcString => {
const bits = tcString.split(':');
!(bits.length === 4) ? invariant(false, 'Expected 4 bits, received %o bits', bits.length) : void 0;
var bitsInt = bits.map(function (bit) {
var bitInt = parseInt(bit, 10);
const bitsInt = bits.map(bit => {
const bitInt = parseInt(bit, 10);
!!isNaN(bitInt) ? invariant(false, 'bit `%s` is NaN', bit) : void 0;

@@ -198,3 +52,2 @@ return bitInt;

var _responseNamesByCode;
var ErrorCode;

@@ -250,40 +103,253 @@

var responseNamesByCode = (_responseNamesByCode = {}, _responseNamesByCode[AsynchronousCode.ConfigurationInfo] = 'configuration info', _responseNamesByCode[AsynchronousCode.ConnectionInfo] = 'connection info', _responseNamesByCode[AsynchronousCode.RemoteInfo] = 'remote info', _responseNamesByCode[AsynchronousCode.SlotInfo] = 'slot info', _responseNamesByCode[AsynchronousCode.TransportInfo] = 'transport info', _responseNamesByCode[ErrorCode.ConnectionRejected] = 'connection rejected', _responseNamesByCode[ErrorCode.DiskError] = 'disk error', _responseNamesByCode[ErrorCode.DiskFull] = 'disk full', _responseNamesByCode[ErrorCode.FormatNotPrepared] = 'format not prepared', _responseNamesByCode[ErrorCode.InternalError] = 'internal error', _responseNamesByCode[ErrorCode.InvalidCodec] = 'invalid codec', _responseNamesByCode[ErrorCode.InvalidFormat] = 'invalid format', _responseNamesByCode[ErrorCode.InvalidState] = 'invalid state', _responseNamesByCode[ErrorCode.InvalidToken] = 'invalid token', _responseNamesByCode[ErrorCode.InvalidValue] = 'invalid value', _responseNamesByCode[ErrorCode.NoDisk] = 'no disk', _responseNamesByCode[ErrorCode.NoInput] = 'no input', _responseNamesByCode[ErrorCode.OutOfRange] = 'out of range', _responseNamesByCode[ErrorCode.RemoteControlDisabled] = 'remote control disabled', _responseNamesByCode[ErrorCode.SyntaxError] = 'syntax error', _responseNamesByCode[ErrorCode.TimelineEmpty] = 'timeline empty', _responseNamesByCode[ErrorCode.Unsupported] = 'unsupported', _responseNamesByCode[ErrorCode.UnsupportedParameter] = 'unsupported parameter', _responseNamesByCode[SynchronousCode.ClipsCount] = 'clips count', _responseNamesByCode[SynchronousCode.ClipsInfo] = 'clips info', _responseNamesByCode[SynchronousCode.Configuration] = 'configuration', _responseNamesByCode[SynchronousCode.DeviceInfo] = 'device info', _responseNamesByCode[SynchronousCode.DiskList] = 'disk list', _responseNamesByCode[SynchronousCode.FormatReady] = 'format ready', _responseNamesByCode[SynchronousCode.Notify] = 'notify', _responseNamesByCode[SynchronousCode.OK] = 'ok', _responseNamesByCode[SynchronousCode.Remote] = 'remote', _responseNamesByCode[SynchronousCode.SlotInfo] = 'slot info', _responseNamesByCode[SynchronousCode.TransportInfo] = 'transport info', _responseNamesByCode[SynchronousCode.Uptime] = 'uptime', _responseNamesByCode);
const responseNamesByCode = {
[AsynchronousCode.ConfigurationInfo]: 'configuration info',
[AsynchronousCode.ConnectionInfo]: 'connection info',
[AsynchronousCode.RemoteInfo]: 'remote info',
[AsynchronousCode.SlotInfo]: 'slot info',
[AsynchronousCode.TransportInfo]: 'transport info',
[ErrorCode.ConnectionRejected]: 'connection rejected',
[ErrorCode.DiskError]: 'disk error',
[ErrorCode.DiskFull]: 'disk full',
[ErrorCode.FormatNotPrepared]: 'format not prepared',
[ErrorCode.InternalError]: 'internal error',
[ErrorCode.InvalidCodec]: 'invalid codec',
[ErrorCode.InvalidFormat]: 'invalid format',
[ErrorCode.InvalidState]: 'invalid state',
[ErrorCode.InvalidToken]: 'invalid token',
[ErrorCode.InvalidValue]: 'invalid value',
[ErrorCode.NoDisk]: 'no disk',
[ErrorCode.NoInput]: 'no input',
[ErrorCode.OutOfRange]: 'out of range',
[ErrorCode.RemoteControlDisabled]: 'remote control disabled',
[ErrorCode.SyntaxError]: 'syntax error',
[ErrorCode.TimelineEmpty]: 'timeline empty',
[ErrorCode.Unsupported]: 'unsupported',
[ErrorCode.UnsupportedParameter]: 'unsupported parameter',
[SynchronousCode.ClipsCount]: 'clips count',
[SynchronousCode.ClipsInfo]: 'clips info',
[SynchronousCode.Configuration]: 'configuration',
[SynchronousCode.DeviceInfo]: 'device info',
[SynchronousCode.DiskList]: 'disk list',
[SynchronousCode.FormatReady]: 'format ready',
[SynchronousCode.Notify]: 'notify',
[SynchronousCode.OK]: 'ok',
[SynchronousCode.Remote]: 'remote',
[SynchronousCode.SlotInfo]: 'slot info',
[SynchronousCode.TransportInfo]: 'transport info',
[SynchronousCode.Uptime]: 'uptime'
};
const slotStatus = {
empty: true,
mounting: true,
error: true,
mounted: true
};
const isSlotStatus = value => {
return typeof value === 'string' && slotStatus.hasOwnProperty(value);
};
const videoFormats = {
NTSC: true,
PAL: true,
NTSCp: true,
PALp: true,
'720p50': true,
'720p5994': true,
'720p60': true,
'1080p23976': true,
'1080p24': true,
'1080p25': true,
'1080p2997': true,
'1080p30': true,
'1080i50': true,
'1080i5994': true,
'1080i60': true,
'4Kp23976': true,
'4Kp24': true,
'4Kp25': true,
'4Kp2997': true,
'4Kp30': true,
'4Kp50': true,
'4Kp5994': true,
'4Kp60': true
};
const isClipV1 = value => {
return typeof value === 'object' && value !== null && typeof value.name === 'string';
};
const isVideoFormat = value => {
return typeof value === 'string' && videoFormats.hasOwnProperty(value);
};
const transportStatus = {
preview: true,
stopped: true,
play: true,
forward: true,
rewind: true,
jog: true,
shuttle: true,
record: true
};
const isTransportStatus = value => {
return typeof value === 'string' && transportStatus.hasOwnProperty(value);
};
const stopModes = {
lastframe: true,
nextframe: true,
black: true
};
const isStopMode = value => {
return typeof value === 'string' && stopModes.hasOwnProperty(value);
};
const videoInputs = {
SDI: true,
HDMI: true,
component: true
};
const isVideoInput = value => {
return typeof value === 'string' && videoInputs.hasOwnProperty(value);
};
const audioInputs = {
XLR: true,
RCA: true,
// TODO(meyer) verify this
embedded: true
};
const isAudioInput = value => {
return typeof value === 'string' && audioInputs.hasOwnProperty(value);
};
const audioCodecs = {
PCM: true,
AAC: true
};
const isAudioCodec = value => {
return typeof value === 'string' && audioCodecs.hasOwnProperty(value);
};
const timecodeInputs = {
external: true,
embedded: true,
preset: true,
clip: true
};
const isTimecodeInput = value => {
return typeof value === 'string' && timecodeInputs.hasOwnProperty(value);
};
const recordTriggers = {
none: true,
recordbit: true,
timecoderun: true
};
const isRecordTrigger = value => {
return typeof value === 'string' && recordTriggers.hasOwnProperty(value);
};
var CRLF = '\r\n';
function assertArrayOf(predicate, value, message) {
!Array.isArray(value) ? invariant(false, 'Expected an array') : void 0;
/** Convert `yourExampleKey` to `your example key` */
var camelcaseToSpaceCase = function camelcaseToSpaceCase(key) {
return key.replace(/([a-z])([A-Z]+)/g, '$1 $2').toLowerCase();
for (const item of value) {
!predicate(item) ? invariant(false, message) : void 0;
}
}
const getStringOrThrow = value => {
!(typeof value === 'string') ? invariant(false, 'Expected a string') : void 0;
return value;
};
/** Internal container class that holds metadata about each HyperDeck event */
const stringToValueFns = {
boolean: value => {
if (value === 'true') return true;
if (value === 'false') return false;
invariant(false, 'Unsupported value `%o` passed to `boolean`', value) ;
},
string: getStringOrThrow,
timecode: value => Timecode.toTimecode(getStringOrThrow(value)),
number: value => {
const valueNum = parseFloat(getStringOrThrow(value));
!!isNaN(valueNum) ? invariant(false, 'valueNum `%o` is NaN', value) : void 0;
return valueNum;
},
videoformat: value => {
!isVideoFormat(value) ? invariant(false, 'Unsupported video format: `%o`') : void 0;
return value;
},
stopmode: value => {
!isStopMode(value) ? invariant(false, 'Unsupported stopmode: `%o`', value) : void 0;
return value;
},
goto: value => {
if (value === 'start' || value === 'end') {
return value;
}
var HyperDeckAPI = function HyperDeckAPI(options) {
var _this = this;
const valueNum = parseInt(getStringOrThrow(value), 10);
if (options === void 0) {
options = {};
if (!isNaN(valueNum)) {
return valueNum;
} // TODO(meyer) validate further
return getStringOrThrow(value);
},
videoinput: value => {
!isVideoInput(value) ? invariant(false, 'Unsupported video input: `%o`', value) : void 0;
return value;
},
audioinput: value => {
!isAudioInput(value) ? invariant(false, 'Unsupported audio input: `%o`', value) : void 0;
return value;
},
fileformat: getStringOrThrow,
audiocodec: value => {
!isAudioCodec(value) ? invariant(false, 'Unsupported audio codec: `%o`', value) : void 0;
return value;
},
timecodeinput: value => {
!isTimecodeInput(value) ? invariant(false, 'Unsupported timecode input: `%o`', value) : void 0;
return value;
},
recordtrigger: value => {
!isRecordTrigger(value) ? invariant(false, 'Unsupported record trigger: `%o`', value) : void 0;
return value;
},
clips: value => {
assertArrayOf(isClipV1, value, 'Expected an array of clips');
return value;
},
slotstatus: value => {
!isSlotStatus(value) ? invariant(false, 'Unsupported slot status: `%o`', value) : void 0;
return value;
},
transportstatus: value => {
!isTransportStatus(value) ? invariant(false, 'Unsupported slot status: `%o`', value) : void 0;
return value;
}
};
this.options = options;
const CRLF = '\r\n';
this.addOption = function (key, option) {
var _Object$assign;
/** Convert `yourExampleKey` to `your example key` */
const camelcaseToSpaceCase = key => {
return key.replace(/([a-z])([A-Z]+)/g, '$1 $2').toLowerCase();
};
var k = Array.isArray(key) ? key[0] : key;
!!_this.options.hasOwnProperty(k) ? invariant(false, 'option already exists for key `%s`', k) : void 0; // NOTE: this mutates the original options object
// shouldn't be a problem since this is only used internally
/** Internal container class that holds metadata about each HyperDeck event */
Object.assign(_this.options, (_Object$assign = {}, _Object$assign[k] = option, _Object$assign));
return _this;
};
/** Get a `Set` of param names keyed by function name */
class HyperDeckAPI {
constructor( // public only because TS apparently strips types from private methods
options = {}) {
this.options = options;
this.addOption = (key, option) => {
const k = Array.isArray(key) ? key[0] : key;
!!this.options.hasOwnProperty(k) ? invariant(false, 'option already exists for key `%s`', k) : void 0; // NOTE: this mutates the original options object
// shouldn't be a problem since this is only used internally
this.getParamsByKey = function () {
return Object.entries(_this.options).reduce(function (prev, _ref) {
var commandName = _ref[0],
value = _ref[1];
Object.assign(this.options, {
[k]: option
});
return this;
};
/** Get a `Set` of param names keyed by function name */
this.getParamsByCommandName = () => Object.entries(this.options).reduce((prev, [commandName, value]) => {
if (!value.arguments) {

@@ -295,6 +361,7 @@ // we still want hasOwnProperty(key) to be true

prev[commandName] = Object.entries(value.arguments).reduce(function (argObj, _ref2) {
var argKey = _ref2[0],
argType = _ref2[1];
argObj[camelcaseToSpaceCase(argKey)] = argType;
prev[commandName] = Object.entries(value.arguments).reduce((argObj, [argKey, argType]) => {
argObj[camelcaseToSpaceCase(argKey)] = {
paramType: argType,
paramName: argKey
};
return argObj;

@@ -304,11 +371,21 @@ }, {});

}, {});
};
};
}
var api = /*#__PURE__*/new HyperDeckAPI().addOption(['help', '?'], {
description: 'Provides help text on all commands and parameters'
}
const api = /*#__PURE__*/new HyperDeckAPI().addOption(['help', '?'], {
description: 'Provides help text on all commands and parameters',
returnValue: {}
}).addOption('commands', {
description: 'return commands in XML format'
description: 'return commands in XML format',
returnValue: {
commands: 'string'
}
}).addOption('device info', {
description: 'return device information'
description: 'return device information',
returnValue: {
protocolVersion: 'string',
model: 'string',
slotCount: 'string'
}
}).addOption('disk list', {

@@ -318,7 +395,12 @@ description: 'query clip list on active disk',

slotId: 'number'
},
returnValue: {
slotId: 'number'
}
}).addOption('quit', {
description: 'disconnect ethernet control'
description: 'disconnect ethernet control',
returnValue: {}
}).addOption('ping', {
description: 'check device is responding'
description: 'check device is responding',
returnValue: {}
}).addOption('preview', {

@@ -328,3 +410,4 @@ description: 'switch to preview or output',

enable: 'boolean'
}
},
returnValue: {}
}).addOption('play', {

@@ -336,5 +419,8 @@ description: 'play from current timecode',

singleClip: 'boolean'
},
returnValue: {}
}).addOption('playrange', {
description: 'query playrange setting',
returnValue: {// TODO(meyer) this isn't accurate
}
}).addOption('playrange', {
description: 'query playrange setting'
}).addOption('playrange set', {

@@ -344,5 +430,5 @@ description: 'set play range to play clip {n} only',

// maybe number?
clipId: 'string',
clipId: 'number',
// description: 'set play range to play between timecode {inT} and timecode {outT}',
"in": 'timecode',
in: 'timecode',
out: 'timecode',

@@ -352,5 +438,7 @@ // 'set play range in units of frames between timeline position {in} and position {out} clear/reset play range°setting',

timelineOut: 'number'
}
},
returnValue: {}
}).addOption('playrange clear', {
description: 'clear/reset play range setting'
description: 'clear/reset play range setting',
returnValue: {}
}).addOption('play on startup', {

@@ -362,3 +450,5 @@ description: 'query unit play on startup state',

singleClip: 'boolean'
}
},
// TODO(meyer) verify that there's no return value
returnValue: {}
}).addOption('play option', {

@@ -368,3 +458,5 @@ description: 'query play options',

stopMode: 'stopmode'
}
},
// TODO(meyer)
returnValue: {}
}).addOption('record', {

@@ -374,3 +466,4 @@ description: 'record from current input',

name: 'string'
}
},
returnValue: {}
}).addOption('record spill', {

@@ -380,7 +473,13 @@ description: 'spill current recording to next slot',

slotId: 'number'
}
},
// TODO(meyer)
returnValue: {}
}).addOption('stop', {
description: 'stop playback or recording'
description: 'stop playback or recording',
returnValue: {}
}).addOption('clips count', {
description: 'query number of clips on timeline'
description: 'query number of clips on timeline',
returnValue: {
clipCount: 'number'
}
}).addOption('clips get', {

@@ -392,2 +491,5 @@ description: 'query all timeline clips',

version: 'number'
},
returnValue: {
clips: 'clips'
}

@@ -398,15 +500,30 @@ }).addOption('clips add', {

name: 'string',
clipId: 'string',
"in": 'timecode',
clipId: 'number',
in: 'timecode',
out: 'timecode'
}
},
returnValue: {}
}).addOption('clips remove', {
description: 'remove clip {n} from the timeline (invalidates clip ids following clip {n})',
arguments: {
clidId: 'string'
}
clipId: 'number'
},
// TODO(meyer) verify this
returnValue: {}
}).addOption('clips clear', {
description: 'empty timeline clip list'
description: 'empty timeline clip list',
returnValue: {}
}).addOption('transport info', {
description: 'query current activity'
description: 'query current activity',
returnValue: {
status: 'transportstatus',
speed: 'number',
slotId: 'number',
clipId: 'number',
singleClip: 'boolean',
displayTimecode: 'timecode',
timecode: 'timecode',
videoFormat: 'videoformat',
loop: 'boolean'
}
}).addOption('slot info', {

@@ -416,2 +533,9 @@ description: 'query active slot',

slotId: 'number'
},
returnValue: {
slotId: 'number',
status: 'slotstatus',
volumeName: 'string',
recordingTime: 'timecode',
videoFormat: 'videoformat'
}

@@ -423,3 +547,4 @@ }).addOption('slot select', {

videoFormat: 'videoformat'
}
},
returnValue: {}
}).addOption('slot unblock', {

@@ -429,3 +554,5 @@ description: 'unblock active slot',

slotId: 'number'
}
},
// TODO(meyer) verify this
returnValue: {}
}).addOption('dynamic range', {

@@ -436,3 +563,5 @@ description: 'query dynamic range settings',

playbackOverride: 'string'
}
},
// TODO(meyer)
returnValue: {}
}).addOption('notify', {

@@ -450,2 +579,13 @@ description: 'query notification status',

dynamicRange: 'boolean'
},
returnValue: {
remote: 'boolean',
transport: 'boolean',
slot: 'boolean',
configuration: 'boolean',
droppedFrames: 'boolean',
displayTimecode: 'boolean',
timelinePosition: 'boolean',
playrange: 'boolean',
dynamicRange: 'boolean'
}

@@ -455,3 +595,3 @@ }).addOption('goto', {

arguments: {
clipId: 'string',
clipId: 'number',
clip: 'goto',

@@ -461,3 +601,4 @@ timeline: 'goto',

slotId: 'number'
}
},
returnValue: {}
}).addOption('jog', {

@@ -467,3 +608,4 @@ description: 'jog forward or backward',

timecode: 'timecode'
}
},
returnValue: {}
}).addOption('shuttle', {

@@ -473,3 +615,4 @@ description: 'shuttle with speed',

speed: 'number'
}
},
returnValue: {}
}).addOption('remote', {

@@ -480,3 +623,5 @@ description: 'query unit remote control state',

override: 'boolean'
}
},
// TODO(meyer)
returnValue: {}
}).addOption('configuration', {

@@ -495,5 +640,20 @@ description: 'query configuration settings',

appendTimestamp: 'boolean'
},
returnValue: {
videoInput: 'videoinput',
audioInput: 'audioinput',
fileFormat: 'fileformat',
audioCodec: 'audiocodec',
timecodeInput: 'timecodeinput',
timecodePreset: 'timecode',
audioInputChannels: 'number',
recordTrigger: 'recordtrigger',
recordPrefix: 'string',
appendTimestamp: 'boolean'
}
}).addOption('uptime', {
description: 'return time since last boot'
description: 'return time since last boot',
returnValue: {
uptime: 'number'
}
}).addOption('format', {

@@ -504,2 +664,5 @@ description: 'prepare a disk formatting operation to filesystem {format}',

confirm: 'string'
},
returnValue: {
token: 'string'
}

@@ -510,3 +673,5 @@ }).addOption('identify', {

enable: 'boolean'
}
},
// TODO(meyer) verify
returnValue: {}
}).addOption('watchdog', {

@@ -516,8 +681,13 @@ description: 'client connection timeout',

period: 'number'
}
},
// TODO(meyer) verify
returnValue: {}
});
var paramsByKey = /*#__PURE__*/api.getParamsByKey();
const paramsByCommandName = /*#__PURE__*/api.getParamsByCommandName();
function assertValidCommandName(value) {
!(typeof value === 'string' && paramsByCommandName.hasOwnProperty(value)) ? invariant(false, 'Invalid command: `%o`', value) : void 0;
}
var MultilineParser = /*#__PURE__*/function () {
function MultilineParser(logger) {
class MultilineParser {
constructor(logger) {
this.linesQueue = [];

@@ -529,9 +699,7 @@ this.logger = logger.child({

var _proto = MultilineParser.prototype;
receivedString(data) {
const res = []; // add new lines to processing queue
_proto.receivedString = function receivedString(data) {
var res = []; // add new lines to processing queue
const newLines = data.split(CRLF); // remove the blank line at the end from the intentionally trailing \r\n
var newLines = data.split(CRLF); // remove the blank line at the end from the intentionally trailing \r\n
if (newLines.length > 0 && newLines[newLines.length - 1] === '') newLines.pop();

@@ -549,3 +717,3 @@ this.linesQueue = this.linesQueue.concat(newLines);

if (!this.linesQueue[0].includes(':') || this.linesQueue.length === 1 && this.linesQueue[0].includes(':')) {
var parsedResponse = this.parseResponse(this.linesQueue.splice(0, 1));
const parsedResponse = this.parseResponse(this.linesQueue.splice(0, 1));

@@ -559,3 +727,3 @@ if (parsedResponse) {

var endLine = this.linesQueue.indexOf('');
const endLine = this.linesQueue.indexOf('');

@@ -567,7 +735,7 @@ if (endLine === -1) {

var lines = this.linesQueue.splice(0, endLine + 1);
var r = this.parseResponse(lines);
const lines = this.linesQueue.splice(0, endLine + 1);
const parsedResponse = this.parseResponse(lines);
if (r) {
res.push(r);
if (parsedResponse) {
res.push(parsedResponse);
}

@@ -577,82 +745,111 @@ }

return res;
};
}
_proto.parseResponse = function parseResponse(responseLines) {
var lines = responseLines.map(function (l) {
return l.trim();
});
parseResponse(responseLines) {
try {
const lines = responseLines.map(l => l.trim());
const firstLine = lines[0];
if (lines.length === 1 && lines[0].includes(':')) {
var bits = lines[0].split(': ');
var msg = bits.shift();
!msg ? invariant(false, 'Unrecognised command') : void 0;
!paramsByKey.hasOwnProperty(msg) ? invariant(false, 'Invalid command: `%s`', msg) : void 0;
var params = {};
var paramNames = paramsByKey[msg];
var param = bits.shift();
!param ? invariant(false, 'No named parameters found') : void 0;
if (lines.length === 1) {
if (!firstLine.includes(':')) {
assertValidCommandName(firstLine);
return {
raw: lines.join(CRLF),
name: firstLine,
parameters: {}
};
} // single-line command with params
for (var i = 0; i < bits.length - 1; i++) {
var bit = bits[i];
var bobs = bit.split(' ');
var nextParam = '';
for (var _i = bobs.length - 1; _i >= 0; _i--) {
nextParam = (bobs.pop() + ' ' + nextParam).trim();
const bits = firstLine.split(': ');
const commandName = bits.shift();
assertValidCommandName(commandName);
const params = {};
const paramNames = paramsByCommandName[commandName];
let param = bits.shift();
!param ? "development" !== "production" ? invariant(false, 'No named parameters found') : invariant(false) : void 0;
if (paramNames.hasOwnProperty(nextParam)) {
break;
for (let i = 0; i < bits.length - 1; i++) {
const bit = bits[i];
const bobs = bit.split(' ');
let nextParam = '';
for (let i = bobs.length - 1; i >= 0; i--) {
nextParam = (bobs.pop() + ' ' + nextParam).trim();
if (paramNames.hasOwnProperty(nextParam)) {
break;
}
}
!(bobs.length > 0) ? "development" !== "production" ? invariant(false, 'Command malformed / paramName not recognised: `%s`', bit) : invariant(false) : void 0;
!paramNames.hasOwnProperty(param) ? "development" !== "production" ? invariant(false, 'Unsupported param: `%o`', param) : invariant(false) : void 0;
const value = bobs.join(' ');
const {
paramName,
paramType
} = paramNames[param];
const formatter = stringToValueFns[paramType];
params[paramName] = formatter(value);
param = nextParam;
}
!(bobs.length > 0) ? invariant(false, 'Command malformed / paramName not recognised: `%s`', bit) : void 0;
params[param] = bobs.join(' ');
param = nextParam;
!paramNames.hasOwnProperty(param) ? "development" !== "production" ? invariant(false, 'Unsupported param: `%o`', param) : invariant(false) : void 0;
const value = bits[bits.length - 1];
const {
paramName,
paramType
} = paramNames[param];
const formatter = stringToValueFns[paramType];
params[paramName] = formatter(value);
return {
raw: lines.join(CRLF),
name: commandName,
parameters: params
};
}
params[param] = bits[bits.length - 1];
return {
raw: lines.join(CRLF),
name: msg,
parameters: params
};
} else {
var headerMatch = lines[0].match(/(.+?)(:|)$/im);
!firstLine.endsWith(':') ? "development" !== "production" ? invariant(false, 'Expected a line ending in semicolon, received `%o`', firstLine) : invariant(false) : void 0; // remove the semicolon at the end of the command
if (!headerMatch) {
this.logger.error({
header: lines[0]
}, 'failed to parse header');
return null;
}
const commandName = firstLine.slice(0, -1);
assertValidCommandName(commandName);
const paramNames = paramsByCommandName[commandName];
const params = {};
var _msg = headerMatch[1];
var _params = {};
for (var _i2 = 1; _i2 < lines.length; _i2++) {
var lineMatch = lines[_i2].match(/^(.*?): (.*)$/im);
if (!lineMatch) {
this.logger.error({
line: lines[_i2]
}, 'failed to parse line');
continue;
}
_params[lineMatch[1]] = lineMatch[2];
for (const line of lines) {
const lineMatch = line.match(/^(.*?): (.*)$/im);
!lineMatch ? "development" !== "production" ? invariant(false, 'Failed to parse line: `%o`', line) : invariant(false) : void 0;
const param = lineMatch[1];
const value = lineMatch[2];
!paramNames.hasOwnProperty(param) ? "development" !== "production" ? invariant(false, 'Unsupported param: `%o`', param) : invariant(false) : void 0;
const {
paramName,
paramType
} = paramNames[param];
const formatter = stringToValueFns[paramType];
params[paramName] = formatter(value);
}
var res = {
const res = {
raw: lines.join(CRLF),
name: _msg,
parameters: _params
name: commandName,
parameters: params
};
return res;
} catch (err) {
if (err instanceof FormattedError) {
this.logger.error(err.template, ...err.args);
} else {
this.logger.error({
err: err + ''
}, 'parseResponse error');
}
return null;
}
};
}
return MultilineParser;
}();
}
var sanitiseMessage = function sanitiseMessage(input) {
const sanitiseMessage = input => {
return input.replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/:/g, '');

@@ -663,3 +860,3 @@ };

var messageForCode = function messageForCode(code, params) {
const messageForCode = (code, params) => {
if (typeof params === 'string') {

@@ -669,3 +866,3 @@ return code + ' ' + sanitiseMessage(params) + CRLF;

var firstLine = code + " " + responseNamesByCode[code]; // bail if no params
const firstLine = `${code} ${responseNamesByCode[code]}`; // bail if no params

@@ -677,6 +874,3 @@ if (!params) {

var paramEntries = Object.entries(params).filter(function (_ref) {
var value = _ref[1];
return value != null;
}); // bail if no params after filtering
const paramEntries = Object.entries(params).filter(([, value]) => value != null); // bail if no params after filtering

@@ -688,6 +882,4 @@ if (paramEntries.length === 0) {

return paramEntries.reduce(function (prev, _ref2) {
var key = _ref2[0],
value = _ref2[1];
var valueString;
return paramEntries.reduce((prev, [key, value]) => {
let valueString;

@@ -700,8 +892,10 @@ if (typeof value === 'string') {

valueString = value.toString();
} else if (value instanceof Timecode) {
valueString = value.toString();
} else {
invariant(false, 'Unhandled value type: `%s`', typeof value) ;
invariant(false, 'Unhandled value type for key `%s`: `%s`', key, Array.isArray(value) ? 'array' : typeof value) ;
} // convert camelCase keys to space-separated words
var formattedKey = camelcaseToSpaceCase(key);
const formattedKey = camelcaseToSpaceCase(key);
return prev + formattedKey + ': ' + valueString + CRLF;

@@ -711,80 +905,64 @@ }, firstLine + ':' + CRLF) + CRLF;

var HyperDeckSocket = /*#__PURE__*/function (_EventEmitter) {
_inheritsLoose(HyperDeckSocket, _EventEmitter);
function HyperDeckSocket(socket, logger, receivedCommand) {
var _this;
_this = _EventEmitter.call(this) || this;
_this.socket = socket;
_this.logger = logger;
_this.receivedCommand = receivedCommand;
_this.lastReceivedMS = -1;
_this.watchdogTimer = null;
_this.notifySettings = {
class HyperDeckSocket extends events.EventEmitter {
constructor(socket, logger, receivedCommand) {
super();
this.socket = socket;
this.logger = logger;
this.receivedCommand = receivedCommand;
this.lastReceivedMS = -1;
this.watchdogTimer = null;
this.notifySettings = {
configuration: false,
displayTimecode: false,
droppedFrames: false,
dynamicRange: false,
playrange: false,
remote: false,
slot: false,
transport: false,
remote: false,
configuration: false,
'dropped frames': false
timelinePosition: false,
transport: false
};
_this.parser = new MultilineParser(logger);
_this.socket.setEncoding('utf-8');
_this.socket.on('data', function (data) {
_this.onMessage(data);
this.parser = new MultilineParser(logger);
this.socket.setEncoding('utf-8');
this.socket.on('data', data => {
this.onMessage(data);
});
_this.socket.on('error', function (err) {
this.socket.on('error', err => {
logger.info({
err: err
err
}, 'error');
_this.socket.destroy();
_this.emit('disconnected');
this.socket.destroy();
this.emit('disconnected');
logger.info('manually disconnected');
});
_this.sendResponse(AsynchronousCode.ConnectionInfo, {
this.sendResponse(AsynchronousCode.ConnectionInfo, {
'protocol version': '1.11',
model: 'NodeJS HyperDeck Server Library'
});
return _this;
}
var _proto = HyperDeckSocket.prototype;
_proto.onMessage = function onMessage(data) {
var _this2 = this;
onMessage(data) {
this.logger.info({
data: data
}, '<-- received message from client');
data
}, '<--- received message from client');
this.lastReceivedMS = Date.now();
var cmds = this.parser.receivedString(data);
const cmds = this.parser.receivedString(data);
this.logger.info({
cmds: cmds
cmds
}, 'parsed commands');
var _loop = function _loop() {
var cmd = _step.value;
for (const cmd of cmds) {
// special cases
if (cmd.name === 'watchdog') {
if (_this2.watchdogTimer) clearInterval(_this2.watchdogTimer);
var watchdogCmd = cmd;
if (this.watchdogTimer) clearInterval(this.watchdogTimer);
const watchdogCmd = cmd;
if (watchdogCmd.parameters.period) {
_this2.watchdogTimer = setInterval(function () {
if (Date.now() - _this2.lastReceivedMS > Number(watchdogCmd.parameters.period)) {
_this2.socket.destroy();
this.watchdogTimer = setInterval(() => {
if (Date.now() - this.lastReceivedMS > Number(watchdogCmd.parameters.period)) {
this.socket.destroy();
this.emit('disconnected');
_this2.emit('disconnected');
if (_this2.watchdogTimer) {
clearInterval(_this2.watchdogTimer);
if (this.watchdogTimer) {
clearInterval(this.watchdogTimer);
}

@@ -795,72 +973,65 @@ }

} else if (cmd.name === 'notify') {
var notifyCmd = cmd;
const notifyCmd = cmd;
if (Object.keys(notifyCmd.parameters).length > 0) {
for (var _i = 0, _Object$keys = Object.keys(notifyCmd.parameters); _i < _Object$keys.length; _i++) {
var param = _Object$keys[_i];
if (_this2.notifySettings[param] !== undefined) {
_this2.notifySettings[param] = notifyCmd.parameters[param] === 'true';
for (const param of Object.keys(notifyCmd.parameters)) {
if (this.notifySettings[param] !== undefined) {
this.notifySettings[param] = notifyCmd.parameters[param] === true;
}
}
} else {
var settings = {};
const settings = {};
for (var _i2 = 0, _Object$keys2 = Object.keys(_this2.notifySettings); _i2 < _Object$keys2.length; _i2++) {
var key = _Object$keys2[_i2];
settings[key] = _this2.notifySettings[key] ? 'true' : 'false';
for (const key of Object.keys(this.notifySettings)) {
settings[key] = this.notifySettings[key] ? 'true' : 'false';
}
_this2.sendResponse(SynchronousCode.Notify, settings, cmd);
return "continue";
this.sendResponse(SynchronousCode.Notify, settings, cmd);
continue;
}
}
_this2.receivedCommand(cmd).then(function (codeOrObj) {
this.receivedCommand(cmd).then(codeOrObj => {
if (typeof codeOrObj === 'object') {
var _code = codeOrObj.code;
var paramsOrMessage = 'params' in codeOrObj && codeOrObj.params || 'message' in codeOrObj && codeOrObj.message || undefined;
return _this2.sendResponse(_code, paramsOrMessage, cmd);
const code = codeOrObj.code;
const paramsOrMessage = 'params' in codeOrObj && codeOrObj.params || 'message' in codeOrObj && codeOrObj.message || undefined;
return this.sendResponse(code, paramsOrMessage, cmd);
}
var code = codeOrObj;
const code = codeOrObj;
if (typeof code === 'number' && (ErrorCode[code] || SynchronousCode[code] || AsynchronousCode[code])) {
return _this2.sendResponse(code, undefined, cmd);
return this.sendResponse(code, undefined, cmd);
}
_this2.logger.error({
cmd: cmd,
codeOrObj: codeOrObj
this.logger.error({
cmd,
codeOrObj
}, 'codeOrObj was neither a ResponseCode nor a response object');
_this2.sendResponse(ErrorCode.InternalError, undefined, cmd);
this.sendResponse(ErrorCode.InternalError, undefined, cmd);
}, // not implemented by client code:
function () {
return _this2.sendResponse(ErrorCode.Unsupported, undefined, cmd);
});
};
() => this.sendResponse(ErrorCode.Unsupported, undefined, cmd));
}
}
for (var _iterator = _createForOfIteratorHelperLoose(cmds), _step; !(_step = _iterator()).done;) {
var _ret = _loop();
if (_ret === "continue") continue;
sendResponse(code, paramsOrMessage, cmd) {
try {
const responseText = messageForCode(code, paramsOrMessage);
const method = ErrorCode[code] ? 'error' : 'info';
this.logger[method]({
responseText,
cmd
}, '---> send response to client');
this.socket.write(responseText);
} catch (err) {
this.logger.error({
cmd
}, '-x-> Error sending response: %s', err);
}
};
}
_proto.sendResponse = function sendResponse(code, paramsOrMessage, cmd) {
var responseText = messageForCode(code, paramsOrMessage);
var method = ErrorCode[code] ? 'error' : 'info';
this.logger[method]({
responseText: responseText,
cmd: cmd
}, '--> send response to client');
this.socket.write(responseText);
};
_proto.notify = function notify(type, params) {
notify(type, params) {
this.logger.info({
type: type,
params: params
type,
params
}, 'notify');

@@ -878,21 +1049,26 @@

this.logger.error({
type: type,
params: params
type,
params
}, 'unhandled notify type');
}
};
}
return HyperDeckSocket;
}(events.EventEmitter);
}
var formatClipsGetResponse = function formatClipsGetResponse(res) {
var clipsCount = res.clips.length;
var response = {
clipsCount: clipsCount
const formatClipsGetResponse = res => {
if (!res.clips) {
return {
clipsCount: 0
};
}
const clipsCount = res.clips.length;
const response = {
clipsCount
};
for (var idx = 0; idx < clipsCount; idx++) {
var clip = res.clips[idx];
var clipKey = (idx + 1).toString();
response[clipKey] = clip.name + " " + clip.startT + " " + clip.duration;
for (let idx = 0; idx < clipsCount; idx++) {
const clip = res.clips[idx];
const clipKey = (idx + 1).toString();
response[clipKey] = `${clip.name} ${clip.startT} ${clip.duration}`;
}

@@ -903,499 +1079,135 @@

var UnimplementedError = /*#__PURE__*/function (_Error) {
_inheritsLoose(UnimplementedError, _Error);
class HyperDeckServer {
constructor(ip, logger = pino()) {
const _this = this;
function UnimplementedError() {
return _Error.apply(this, arguments) || this;
}
this.sockets = {};
this.commandHandlers = {};
return UnimplementedError;
}( /*#__PURE__*/_wrapNativeSuper(Error));
this.on = (key, handler) => {
!paramsByCommandName.hasOwnProperty(key) ? invariant(false, 'Invalid key: `%s`', key) : void 0;
!!this.commandHandlers.hasOwnProperty(key) ? invariant(false, 'Handler already registered for `%s`', key) : void 0;
this.commandHandlers[key] = handler;
};
var noop = function noop() {
try {
throw new UnimplementedError();
} catch (e) {
return Promise.reject(e);
}
};
var HyperDeckServer = /*#__PURE__*/function () {
function HyperDeckServer(ip, logger) {
var _this2 = this;
var _this = this;
if (logger === void 0) {
logger = pino();
}
this.sockets = {};
this.onDeviceInfo = noop;
this.onDiskList = noop;
this.onPreview = noop;
this.onPlay = noop;
this.onPlayrangeSet = noop;
this.onPlayrangeClear = noop;
this.onRecord = noop;
this.onStop = noop;
this.onClipsCount = noop;
this.onClipsGet = noop;
this.onClipsAdd = noop;
this.onClipsClear = noop;
this.onTransportInfo = noop;
this.onSlotInfo = noop;
this.onSlotSelect = noop;
this.onGoTo = noop;
this.onJog = noop;
this.onShuttle = noop;
this.onConfiguration = noop;
this.onUptime = noop;
this.onFormat = noop;
this.onIdentify = noop;
this.onWatchdog = noop;
this.receivedCommand = function (cmd) {
try {
// TODO(meyer) more sophisticated debouncing
return Promise.resolve(new Promise(function (resolve) {
return setTimeout(function () {
return resolve();
}, 200);
})).then(function () {
var _exit = false;
return Promise.resolve(new Promise(resolve => setTimeout(() => resolve(), 200))).then(function () {
_this.logger.info({
cmd: cmd
}, '<-- ' + cmd.name);
cmd
}, 'receivedCommand %s', cmd.name);
return _catch(function () {
function _temp44(_result) {
var _exit2 = false;
if (_exit) return _result;
if (cmd.name === 'remote') {
return {
code: SynchronousCode.Remote,
params: {
enabled: true,
override: false
}
};
} // implemented in socket.ts
function _temp42(_result2) {
var _exit3 = false;
if (_exit2) return _result2;
function _temp40(_result3) {
var _exit4 = false;
if (_exit3) return _result3;
if (cmd.name === 'notify' || cmd.name === 'watchdog' || cmd.name === 'ping') {
return SynchronousCode.OK;
}
function _temp38(_result4) {
var _exit5 = false;
if (_exit4) return _result4;
const handler = _this.commandHandlers[cmd.name];
function _temp36(_result5) {
var _exit6 = false;
if (_exit5) return _result5;
if (!handler) {
_this.logger.error({
cmd
}, 'unimplemented');
function _temp34(_result6) {
var _exit7 = false;
if (_exit6) return _result6;
return ErrorCode.Unsupported;
}
function _temp32(_result7) {
var _exit8 = false;
if (_exit7) return _result7;
return Promise.resolve(handler(cmd)).then(function (response) {
const result = {
name: cmd.name,
response
};
function _temp30(_result8) {
var _exit9 = false;
if (_exit8) return _result8;
if (result.name === 'clips add' || result.name === 'clips clear' || result.name === 'goto' || result.name === 'identify' || result.name === 'jog' || result.name === 'play' || result.name === 'playrange clear' || result.name === 'playrange set' || result.name === 'preview' || result.name === 'record' || result.name === 'shuttle' || result.name === 'slot select' || result.name === 'stop') {
return SynchronousCode.OK;
}
function _temp28(_result9) {
var _exit10 = false;
if (_exit9) return _result9;
if (result.name === 'device info') {
return {
code: SynchronousCode.DeviceInfo,
params: result.response
};
}
function _temp26(_result10) {
var _exit11 = false;
if (_exit10) return _result10;
if (result.name === 'disk list') {
return {
code: SynchronousCode.DiskList,
params: result.response
};
}
function _temp24(_result11) {
var _exit12 = false;
if (_exit11) return _result11;
if (result.name === 'clips count') {
return {
code: SynchronousCode.ClipsCount,
params: result.response
};
}
function _temp22(_result12) {
var _exit13 = false;
if (_exit12) return _result12;
if (result.name === 'clips get') {
return {
code: SynchronousCode.ClipsInfo,
params: formatClipsGetResponse(result.response)
};
}
function _temp20(_result13) {
var _exit14 = false;
if (_exit13) return _result13;
if (result.name === 'transport info') {
return {
code: SynchronousCode.TransportInfo,
params: result.response
};
}
function _temp18(_result14) {
var _exit15 = false;
if (_exit14) return _result14;
if (result.name === 'slot info') {
return {
code: SynchronousCode.SlotInfo,
params: result.response
};
}
function _temp16(_result15) {
var _exit16 = false;
if (_exit15) return _result15;
function _temp14(_result16) {
var _exit17 = false;
if (_exit16) return _result16;
function _temp12(_result17) {
var _exit18 = false;
if (_exit17) return _result17;
function _temp10(_result18) {
var _exit19 = false;
if (_exit18) return _result18;
function _temp8(_result19) {
var _exit20 = false;
if (_exit19) return _result19;
function _temp6(_result20) {
var _exit21 = false;
if (_exit20) return _result20;
function _temp4(_result21) {
var _exit22 = false;
if (_exit21) return _result21;
function _temp2(_result22) {
if (_exit22) return _result22;
if (cmd.name === 'watchdog') {
// implemented in socket.ts
return SynchronousCode.OK;
}
if (cmd.name === 'ping') {
// implemented in socket.ts
return SynchronousCode.OK;
}
!false ? "development" !== "production" ? invariant(false, 'Unhandled command name: `%s`', cmd.name) : invariant(false) : void 0;
}
var _temp = function () {
if (cmd.name === 'identify') {
return Promise.resolve(_this.onIdentify(cmd)).then(function () {
_exit22 = true;
return SynchronousCode.OK;
});
}
}();
return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
}
var _temp3 = function () {
if (cmd.name === 'format') {
return Promise.resolve(_this.onFormat(cmd)).then(function (res) {
if (res) {
_exit21 = true;
return {
code: SynchronousCode.FormatReady,
params: res
};
}
_exit21 = true;
return SynchronousCode.OK;
});
}
}();
return _temp3 && _temp3.then ? _temp3.then(_temp4) : _temp4(_temp3);
}
var _temp5 = function () {
if (cmd.name === 'uptime') {
return Promise.resolve(_this.onUptime(cmd)).then(function (res) {
_exit20 = true;
return {
code: SynchronousCode.Uptime,
params: res
};
});
}
}();
return _temp5 && _temp5.then ? _temp5.then(_temp6) : _temp6(_temp5);
}
if (cmd.name === 'remote') {
return {
code: SynchronousCode.Remote,
params: {
enabled: true,
override: false
}
};
}
var _temp7 = function () {
if (cmd.name === 'configuration') {
return Promise.resolve(_this.onConfiguration(cmd)).then(function (res) {
if (res) {
_exit19 = true;
return {
code: SynchronousCode.Configuration,
params: res
};
}
_exit19 = true;
return SynchronousCode.OK;
});
}
}();
return _temp7 && _temp7.then ? _temp7.then(_temp8) : _temp8(_temp7);
}
var _temp9 = function () {
if (cmd.name === 'shuttle') {
return Promise.resolve(_this.onShuttle(cmd)).then(function () {
_exit18 = true;
return SynchronousCode.OK;
});
}
}();
return _temp9 && _temp9.then ? _temp9.then(_temp10) : _temp10(_temp9);
}
var _temp11 = function () {
if (cmd.name === 'jog') {
return Promise.resolve(_this.onJog(cmd)).then(function () {
_exit17 = true;
return SynchronousCode.OK;
});
}
}();
return _temp11 && _temp11.then ? _temp11.then(_temp12) : _temp12(_temp11);
}
if (cmd.name === 'notify') {
// implemented in socket.ts
return SynchronousCode.OK;
}
var _temp13 = function () {
if (cmd.name === 'go to') {
return Promise.resolve(_this.onGoTo(cmd)).then(function () {
_exit16 = true;
return SynchronousCode.OK;
});
}
}();
return _temp13 && _temp13.then ? _temp13.then(_temp14) : _temp14(_temp13);
}
var _temp15 = function () {
if (cmd.name === 'slot select') {
return Promise.resolve(_this.onSlotSelect(cmd)).then(function () {
_exit15 = true;
return SynchronousCode.OK;
});
}
}();
return _temp15 && _temp15.then ? _temp15.then(_temp16) : _temp16(_temp15);
}
var _temp17 = function () {
if (cmd.name === 'slot info') {
return Promise.resolve(_this.onSlotInfo(cmd)).then(function (res) {
_exit14 = true;
return {
code: SynchronousCode.SlotInfo,
params: res
};
});
}
}();
return _temp17 && _temp17.then ? _temp17.then(_temp18) : _temp18(_temp17);
}
var _temp19 = function () {
if (cmd.name === 'transport info') {
return Promise.resolve(_this.onTransportInfo(cmd)).then(function (res) {
_exit13 = true;
return {
code: SynchronousCode.TransportInfo,
params: res
};
});
}
}();
return _temp19 && _temp19.then ? _temp19.then(_temp20) : _temp20(_temp19);
}
var _temp21 = function () {
if (cmd.name === 'clips clear') {
return Promise.resolve(_this.onClipsClear(cmd)).then(function () {
_exit12 = true;
return SynchronousCode.OK;
});
}
}();
return _temp21 && _temp21.then ? _temp21.then(_temp22) : _temp22(_temp21);
}
var _temp23 = function () {
if (cmd.name === 'clips add') {
return Promise.resolve(_this.onClipsAdd(cmd)).then(function () {
_exit11 = true;
return SynchronousCode.OK;
});
}
}();
return _temp23 && _temp23.then ? _temp23.then(_temp24) : _temp24(_temp23);
}
var _temp25 = function () {
if (cmd.name === 'clips get') {
return Promise.resolve(_this.onClipsGet(cmd).then(formatClipsGetResponse)).then(function (res) {
_exit10 = true;
return {
code: SynchronousCode.ClipsInfo,
params: res
};
});
}
}();
return _temp25 && _temp25.then ? _temp25.then(_temp26) : _temp26(_temp25);
}
var _temp27 = function () {
if (cmd.name === 'clips count') {
return Promise.resolve(_this.onClipsCount(cmd)).then(function (res) {
_exit9 = true;
return {
code: SynchronousCode.ClipsCount,
params: res
};
});
}
}();
return _temp27 && _temp27.then ? _temp27.then(_temp28) : _temp28(_temp27);
}
var _temp29 = function () {
if (cmd.name === 'stop') {
return Promise.resolve(_this.onStop(cmd)).then(function () {
_exit8 = true;
return SynchronousCode.OK;
});
}
}();
return _temp29 && _temp29.then ? _temp29.then(_temp30) : _temp30(_temp29);
}
var _temp31 = function () {
if (cmd.name === 'record') {
return Promise.resolve(_this.onRecord(cmd)).then(function () {
_exit7 = true;
return SynchronousCode.OK;
});
}
}();
return _temp31 && _temp31.then ? _temp31.then(_temp32) : _temp32(_temp31);
}
var _temp33 = function () {
if (cmd.name === 'playrange clear') {
return Promise.resolve(_this.onPlayrangeClear(cmd)).then(function () {
_exit6 = true;
return SynchronousCode.OK;
});
}
}();
return _temp33 && _temp33.then ? _temp33.then(_temp34) : _temp34(_temp33);
}
var _temp35 = function () {
if (cmd.name === 'playrange set') {
return Promise.resolve(_this.onPlayrangeSet(cmd)).then(function () {
_exit5 = true;
return SynchronousCode.OK;
});
}
}();
return _temp35 && _temp35.then ? _temp35.then(_temp36) : _temp36(_temp35);
}
var _temp37 = function () {
if (cmd.name === 'play') {
return Promise.resolve(_this.onPlay(cmd)).then(function () {
_exit4 = true;
return SynchronousCode.OK;
});
}
}();
return _temp37 && _temp37.then ? _temp37.then(_temp38) : _temp38(_temp37);
}
var _temp39 = function () {
if (cmd.name === 'preview') {
return Promise.resolve(_this.onPreview(cmd)).then(function () {
_exit3 = true;
return SynchronousCode.OK;
});
}
}();
return _temp39 && _temp39.then ? _temp39.then(_temp40) : _temp40(_temp39);
if (result.name === 'configuration') {
if (result) {
return {
code: SynchronousCode.Configuration,
params: result.response
};
}
var _temp41 = function () {
if (cmd.name === 'disk list') {
return Promise.resolve(_this.onDiskList(cmd)).then(function (res) {
_exit2 = true;
return {
code: SynchronousCode.DiskList,
params: res
};
});
}
}();
return SynchronousCode.OK;
}
return _temp41 && _temp41.then ? _temp41.then(_temp42) : _temp42(_temp41);
if (result.name === 'uptime') {
return {
code: SynchronousCode.Uptime,
params: result.response
};
}
var _temp43 = function () {
if (cmd.name === 'device info') {
return Promise.resolve(_this.onDeviceInfo(cmd)).then(function (res) {
_exit = true;
return {
code: SynchronousCode.DeviceInfo,
params: res
};
});
if (result.name === 'format') {
if (result) {
return {
code: SynchronousCode.FormatReady,
params: result.response
};
}
}();
return _temp43 && _temp43.then ? _temp43.then(_temp44) : _temp44(_temp43);
}, function (err) {
if (err instanceof UnimplementedError) {
_this.logger.error({
cmd: cmd
}, 'unimplemented');
return ErrorCode.Unsupported;
return SynchronousCode.OK;
}
_this.logger.error({
cmd: cmd,
err: err.message
}, 'unhandled command name');
cmd,
res: result
}, 'Unsupported command');
return ErrorCode.InternalError;
return ErrorCode.Unsupported;
});

@@ -1411,29 +1223,17 @@ });

});
this.server = net.createServer(function (socket) {
_this2.logger.info('connection');
var socketId = Math.random().toString(35).substr(-6);
var socketLogger = _this2.logger.child({
this.server = net.createServer(socket => {
this.logger.info('connection');
const socketId = Math.random().toString(35).substr(-6);
const socketLogger = this.logger.child({
name: 'HyperDeck socket ' + socketId
});
_this2.sockets[socketId] = new HyperDeckSocket(socket, socketLogger, function (cmd) {
return _this2.receivedCommand(cmd);
});
_this2.sockets[socketId].on('disconnected', function () {
this.sockets[socketId] = new HyperDeckSocket(socket, socketLogger, this.receivedCommand);
this.sockets[socketId].on('disconnected', () => {
socketLogger.info('disconnected');
delete _this2.sockets[socketId];
delete this.sockets[socketId];
});
});
this.server.on('listening', function () {
return _this2.logger.info('listening');
});
this.server.on('close', function () {
return _this2.logger.info('connection closed');
});
this.server.on('error', function (err) {
return _this2.logger.error('server error:', err);
});
this.server.on('listening', () => this.logger.info('listening'));
this.server.on('close', () => this.logger.info('connection closed'));
this.server.on('error', err => this.logger.error('server error:', err));
this.server.maxConnections = 1;

@@ -1443,35 +1243,24 @@ this.server.listen(9993, ip);

var _proto = HyperDeckServer.prototype;
_proto.close = function close() {
close() {
this.server.unref();
};
}
_proto.notifySlot = function notifySlot(params) {
notifySlot(params) {
this.notify('slot', params);
};
}
_proto.notifyTransport = function notifyTransport(params) {
notifyTransport(params) {
this.notify('transport', params);
};
}
_proto.notify = function notify(type, params) {
for (var _i = 0, _Object$keys = Object.keys(this.sockets); _i < _Object$keys.length; _i++) {
var id = _Object$keys[_i];
notify(type, params) {
for (const id of Object.keys(this.sockets)) {
this.sockets[id].notify(type, params);
}
};
}
return HyperDeckServer;
}();
}
var ResponseInterface = {
__proto__: null
};
exports.HyperDeckServer = HyperDeckServer;
exports.ResponseInterface = ResponseInterface;
exports.Timecode = Timecode;
//# sourceMappingURL=hyperdeck-emulator.cjs.development.js.map

@@ -1,2 +0,2 @@

"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0});var n=require("events"),t=e(require("util")),r=require("net"),o=e(require("pino"));function i(e,n){e.prototype=Object.create(n.prototype),e.prototype.constructor=e,e.__proto__=n}function a(e){return(a=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function s(e,n){return(s=Object.setPrototypeOf||function(e,n){return e.__proto__=n,e})(e,n)}function u(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function c(e,n,t){return(c=u()?Reflect.construct:function(e,n,t){var r=[null];r.push.apply(r,n);var o=new(Function.bind.apply(e,r));return t&&s(o,t.prototype),o}).apply(null,arguments)}function l(e){var n="function"==typeof Map?new Map:void 0;return(l=function(e){if(null===e||-1===Function.toString.call(e).indexOf("[native code]"))return e;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==n){if(n.has(e))return n.get(e);n.set(e,t)}function t(){return c(e,arguments,a(this).constructor)}return t.prototype=Object.create(e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),s(t,e)})(e)}function f(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=new Array(n);t<n;t++)r[t]=e[t];return r}function p(e,n){if(!e){for(var r=arguments.length,o=new Array(r>2?r-2:0),i=2;i<r;i++)o[i-2]=arguments[i];throw new Error(t.format.apply(t,[n].concat(o)))}}"undefined"!=typeof Symbol&&(Symbol.iterator||(Symbol.iterator=Symbol("Symbol.iterator"))),"undefined"!=typeof Symbol&&(Symbol.asyncIterator||(Symbol.asyncIterator=Symbol("Symbol.asyncIterator")));var d,m,h,v,g=function(e,n,t,r){var o=[e,n,t,r].map((function(e){var n=Math.floor(e);return n===e&&e>=0&&e<=99||p(!1),(n+100).toString().slice(-2)})).join(":");this.toString=function(){return o}};g.toTimecode=function(e){var n=e.split(":");4!==n.length&&p(!1);var t=n.map((function(e){var n=parseInt(e,10);return isNaN(n)&&p(!1),n}));return new g(t[0],t[1],t[2],t[3])},function(e){e[e.SyntaxError=100]="SyntaxError",e[e.UnsupportedParameter=101]="UnsupportedParameter",e[e.InvalidValue=102]="InvalidValue",e[e.Unsupported=103]="Unsupported",e[e.DiskFull=104]="DiskFull",e[e.NoDisk=105]="NoDisk",e[e.DiskError=106]="DiskError",e[e.TimelineEmpty=107]="TimelineEmpty",e[e.InternalError=108]="InternalError",e[e.OutOfRange=109]="OutOfRange",e[e.NoInput=110]="NoInput",e[e.RemoteControlDisabled=111]="RemoteControlDisabled",e[e.ConnectionRejected=120]="ConnectionRejected",e[e.InvalidState=150]="InvalidState",e[e.InvalidCodec=151]="InvalidCodec",e[e.InvalidFormat=160]="InvalidFormat",e[e.InvalidToken=161]="InvalidToken",e[e.FormatNotPrepared=162]="FormatNotPrepared"}(m||(m={})),function(e){e[e.OK=200]="OK",e[e.SlotInfo=202]="SlotInfo",e[e.DeviceInfo=204]="DeviceInfo",e[e.ClipsInfo=205]="ClipsInfo",e[e.DiskList=206]="DiskList",e[e.TransportInfo=208]="TransportInfo",e[e.Notify=209]="Notify",e[e.Remote=210]="Remote",e[e.Configuration=211]="Configuration",e[e.ClipsCount=214]="ClipsCount",e[e.Uptime=215]="Uptime",e[e.FormatReady=216]="FormatReady"}(h||(h={})),function(e){e[e.ConnectionInfo=500]="ConnectionInfo",e[e.SlotInfo=502]="SlotInfo",e[e.TransportInfo=508]="TransportInfo",e[e.RemoteInfo=510]="RemoteInfo",e[e.ConfigurationInfo=511]="ConfigurationInfo"}(v||(v={}));var y=((d={})[v.ConfigurationInfo]="configuration info",d[v.ConnectionInfo]="connection info",d[v.RemoteInfo]="remote info",d[v.SlotInfo]="slot info",d[v.TransportInfo]="transport info",d[m.ConnectionRejected]="connection rejected",d[m.DiskError]="disk error",d[m.DiskFull]="disk full",d[m.FormatNotPrepared]="format not prepared",d[m.InternalError]="internal error",d[m.InvalidCodec]="invalid codec",d[m.InvalidFormat]="invalid format",d[m.InvalidState]="invalid state",d[m.InvalidToken]="invalid token",d[m.InvalidValue]="invalid value",d[m.NoDisk]="no disk",d[m.NoInput]="no input",d[m.OutOfRange]="out of range",d[m.RemoteControlDisabled]="remote control disabled",d[m.SyntaxError]="syntax error",d[m.TimelineEmpty]="timeline empty",d[m.Unsupported]="unsupported",d[m.UnsupportedParameter]="unsupported parameter",d[h.ClipsCount]="clips count",d[h.ClipsInfo]="clips info",d[h.Configuration]="configuration",d[h.DeviceInfo]="device info",d[h.DiskList]="disk list",d[h.FormatReady]="format ready",d[h.Notify]="notify",d[h.OK]="ok",d[h.Remote]="remote",d[h.SlotInfo]="slot info",d[h.TransportInfo]="transport info",d[h.Uptime]="uptime",d),b=function(e){return e.replace(/([a-z])([A-Z]+)/g,"$1 $2").toLowerCase()},O=(new function(e){var n=this;void 0===e&&(e={}),this.options=e,this.addOption=function(e,t){var r,o=Array.isArray(e)?e[0]:e;return n.options.hasOwnProperty(o)&&p(!1),Object.assign(n.options,((r={})[o]=t,r)),n},this.getParamsByKey=function(){return Object.entries(n.options).reduce((function(e,n){var t=n[0],r=n[1];return r.arguments?(e[t]=Object.entries(r.arguments).reduce((function(e,n){var t=n[1];return e[b(n[0])]=t,e}),{}),e):(e[t]={},e)}),{})}}).addOption(["help","?"],{description:"Provides help text on all commands and parameters"}).addOption("commands",{description:"return commands in XML format"}).addOption("device info",{description:"return device information"}).addOption("disk list",{description:"query clip list on active disk",arguments:{slotId:"number"}}).addOption("quit",{description:"disconnect ethernet control"}).addOption("ping",{description:"check device is responding"}).addOption("preview",{description:"switch to preview or output",arguments:{enable:"boolean"}}).addOption("play",{description:"play from current timecode",arguments:{speed:"number",loop:"boolean",singleClip:"boolean"}}).addOption("playrange",{description:"query playrange setting"}).addOption("playrange set",{description:"set play range to play clip {n} only",arguments:{clipId:"string",in:"timecode",out:"timecode",timelineIn:"number",timelineOut:"number"}}).addOption("playrange clear",{description:"clear/reset play range setting"}).addOption("play on startup",{description:"query unit play on startup state",arguments:{enable:"boolean",singleClip:"boolean"}}).addOption("play option",{description:"query play options",arguments:{stopMode:"stopmode"}}).addOption("record",{description:"record from current input",arguments:{name:"string"}}).addOption("record spill",{description:"spill current recording to next slot",arguments:{slotId:"number"}}).addOption("stop",{description:"stop playback or recording"}).addOption("clips count",{description:"query number of clips on timeline"}).addOption("clips get",{description:"query all timeline clips",arguments:{clipId:"number",count:"number",version:"number"}}).addOption("clips add",{description:"append a clip to timeline",arguments:{name:"string",clipId:"string",in:"timecode",out:"timecode"}}).addOption("clips remove",{description:"remove clip {n} from the timeline (invalidates clip ids following clip {n})",arguments:{clidId:"string"}}).addOption("clips clear",{description:"empty timeline clip list"}).addOption("transport info",{description:"query current activity"}).addOption("slot info",{description:"query active slot",arguments:{slotId:"number"}}).addOption("slot select",{description:"switch to specified slot",arguments:{slotId:"number",videoFormat:"videoformat"}}).addOption("slot unblock",{description:"unblock active slot",arguments:{slotId:"number"}}).addOption("dynamic range",{description:"query dynamic range settings",arguments:{playbackOverride:"string"}}).addOption("notify",{description:"query notification status",arguments:{remote:"boolean",transport:"boolean",slot:"boolean",configuration:"boolean",droppedFrames:"boolean",displayTimecode:"boolean",timelinePosition:"boolean",playrange:"boolean",dynamicRange:"boolean"}}).addOption("goto",{description:"go forward or backward within a clip or timeline",arguments:{clipId:"string",clip:"goto",timeline:"goto",timecode:"timecode",slotId:"number"}}).addOption("jog",{description:"jog forward or backward",arguments:{timecode:"timecode"}}).addOption("shuttle",{description:"shuttle with speed",arguments:{speed:"number"}}).addOption("remote",{description:"query unit remote control state",arguments:{enable:"boolean",override:"boolean"}}).addOption("configuration",{description:"query configuration settings",arguments:{videoInput:"videoinput",audioInput:"audioinput",fileFormat:"fileformat",audioCodec:"audiocodec",timecodeInput:"timecodeinput",timecodePreset:"timecode",audioInputChannels:"number",recordTrigger:"recordtrigger",recordPrefix:"string",appendTimestamp:"boolean"}}).addOption("uptime",{description:"return time since last boot"}).addOption("format",{description:"prepare a disk formatting operation to filesystem {format}",arguments:{prepare:"string",confirm:"string"}}).addOption("identify",{description:"identify the device",arguments:{enable:"boolean"}}).addOption("watchdog",{description:"client connection timeout",arguments:{period:"number"}}).getParamsByKey(),I=function(){function e(e){this.linesQueue=[],this.logger=e.child({name:"MultilineParser"})}var n=e.prototype;return n.receivedString=function(e){var n=[],t=e.split("\r\n");for(t.length>0&&""===t[t.length-1]&&t.pop(),this.linesQueue=this.linesQueue.concat(t);this.linesQueue.length>0;)if(""!==this.linesQueue[0])if(!this.linesQueue[0].includes(":")||1===this.linesQueue.length&&this.linesQueue[0].includes(":")){var r=this.parseResponse(this.linesQueue.splice(0,1));r&&n.push(r)}else{var o=this.linesQueue.indexOf("");if(-1===o)break;var i=this.linesQueue.splice(0,o+1),a=this.parseResponse(i);a&&n.push(a)}else this.linesQueue.shift();return n},n.parseResponse=function(e){var n=e.map((function(e){return e.trim()}));if(1===n.length&&n[0].includes(":")){var t=n[0].split(": "),r=t.shift();r||p(!1),O.hasOwnProperty(r)||p(!1);var o={},i=O[r],a=t.shift();a||p(!1);for(var s=0;s<t.length-1;s++){for(var u=t[s].split(" "),c="",l=u.length-1;l>=0&&(c=(u.pop()+" "+c).trim(),!i.hasOwnProperty(c));l--);u.length>0||p(!1),o[a]=u.join(" "),a=c}return o[a]=t[t.length-1],{raw:n.join("\r\n"),name:r,parameters:o}}var f=n[0].match(/(.+?)(:|)$/im);if(!f)return this.logger.error({header:n[0]},"failed to parse header"),null;for(var d=f[1],m={},h=1;h<n.length;h++){var v=n[h].match(/^(.*?): (.*)$/im);v?m[v[1]]=v[2]:this.logger.error({line:n[h]},"failed to parse line")}return{raw:n.join("\r\n"),name:d,parameters:m}},e}(),S=function(e){function n(n,t,r){var o;return(o=e.call(this)||this).socket=n,o.logger=t,o.receivedCommand=r,o.lastReceivedMS=-1,o.watchdogTimer=null,o.notifySettings={slot:!1,transport:!1,remote:!1,configuration:!1,"dropped frames":!1},o.parser=new I(t),o.socket.setEncoding("utf-8"),o.socket.on("data",(function(e){o.onMessage(e)})),o.socket.on("error",(function(e){t.info({err:e},"error"),o.socket.destroy(),o.emit("disconnected"),t.info("manually disconnected")})),o.sendResponse(v.ConnectionInfo,{"protocol version":"1.11",model:"NodeJS HyperDeck Server Library"}),o}i(n,e);var t=n.prototype;return t.onMessage=function(e){var n=this;this.logger.info({data:e},"<-- received message from client"),this.lastReceivedMS=Date.now();var t=this.parser.receivedString(e);this.logger.info({cmds:t},"parsed commands");for(var r,o=function(){var e=r.value;if("watchdog"===e.name){n.watchdogTimer&&clearInterval(n.watchdogTimer);var t=e;t.parameters.period&&(n.watchdogTimer=setInterval((function(){Date.now()-n.lastReceivedMS>Number(t.parameters.period)&&(n.socket.destroy(),n.emit("disconnected"),n.watchdogTimer&&clearInterval(n.watchdogTimer))}),1e3*Number(t.parameters.period)))}else if("notify"===e.name){var o=e;if(!(Object.keys(o.parameters).length>0)){for(var i={},a=0,s=Object.keys(n.notifySettings);a<s.length;a++){var u=s[a];i[u]=n.notifySettings[u]?"true":"false"}return n.sendResponse(h.Notify,i,e),"continue"}for(var c=0,l=Object.keys(o.parameters);c<l.length;c++){var f=l[c];void 0!==n.notifySettings[f]&&(n.notifySettings[f]="true"===o.parameters[f])}}n.receivedCommand(e).then((function(t){return"object"==typeof t?n.sendResponse(t.code,"params"in t&&t.params||"message"in t&&t.message||void 0,e):"number"==typeof t&&(m[t]||h[t]||v[t])?n.sendResponse(t,void 0,e):(n.logger.error({cmd:e,codeOrObj:t},"codeOrObj was neither a ResponseCode nor a response object"),void n.sendResponse(m.InternalError,void 0,e))}),(function(){return n.sendResponse(m.Unsupported,void 0,e)}))},i=function(e,n){var t;if("undefined"==typeof Symbol||null==e[Symbol.iterator]){if(Array.isArray(e)||(t=function(e,n){if(e){if("string"==typeof e)return f(e,void 0);var t=Object.prototype.toString.call(e).slice(8,-1);return"Object"===t&&e.constructor&&(t=e.constructor.name),"Map"===t||"Set"===t?Array.from(e):"Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?f(e,void 0):void 0}}(e))){t&&(e=t);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}return(t=e[Symbol.iterator]()).next.bind(t)}(t);!(r=i()).done;)o()},t.sendResponse=function(e,n,t){var r=function(e,n){if("string"==typeof n)return e+" "+n.replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/:/g,"")+"\r\n";var t=e+" "+y[e];if(!n)return t+"\r\n";var r=Object.entries(n).filter((function(e){return null!=e[1]}));return 0===r.length?t+"\r\n":r.reduce((function(e,n){var t,r=n[0],o=n[1];return"string"==typeof o?t=o:"boolean"==typeof o?t=o?"true":"false":"number"==typeof o?t=o.toString():p(!1),e+b(r)+": "+t+"\r\n"}),t+":\r\n")+"\r\n"}(e,n);this.logger[m[e]?"error":"info"]({responseText:r,cmd:t},"--\x3e send response to client"),this.socket.write(r)},t.notify=function(e,n){this.logger.info({type:e,params:n},"notify"),"configuration"===e&&this.notifySettings.configuration?this.sendResponse(v.ConfigurationInfo,n):"remote"===e&&this.notifySettings.remote?this.sendResponse(v.RemoteInfo,n):"slot"===e&&this.notifySettings.slot?this.sendResponse(v.SlotInfo,n):"transport"===e&&this.notifySettings.transport?this.sendResponse(v.TransportInfo,n):this.logger.error({type:e,params:n},"unhandled notify type")},n}(n.EventEmitter),C=function(e){for(var n=e.clips.length,t={clipsCount:n},r=0;r<n;r++){var o=e.clips[r];t[(r+1).toString()]=o.name+" "+o.startT+" "+o.duration}return t},k=function(e){function n(){return e.apply(this,arguments)||this}return i(n,e),n}(l(Error)),P=function(){try{throw new k}catch(e){return Promise.reject(e)}};exports.HyperDeckServer=function(){function e(e,n){var t=this,i=this;void 0===n&&(n=o()),this.sockets={},this.onDeviceInfo=P,this.onDiskList=P,this.onPreview=P,this.onPlay=P,this.onPlayrangeSet=P,this.onPlayrangeClear=P,this.onRecord=P,this.onStop=P,this.onClipsCount=P,this.onClipsGet=P,this.onClipsAdd=P,this.onClipsClear=P,this.onTransportInfo=P,this.onSlotInfo=P,this.onSlotSelect=P,this.onGoTo=P,this.onJog=P,this.onShuttle=P,this.onConfiguration=P,this.onUptime=P,this.onFormat=P,this.onIdentify=P,this.onWatchdog=P,this.receivedCommand=function(e){try{return Promise.resolve(new Promise((function(e){return setTimeout((function(){return e()}),200)}))).then((function(){var n=!1;return i.logger.info({cmd:e},"<-- "+e.name),function(t,r){try{var o=function(){function t(t){var r=!1;if(n)return t;function o(n){var t=!1;if(r)return n;function o(n){var r=!1;if(t)return n;function o(n){var t=!1;if(r)return n;function o(n){var r=!1;if(t)return n;function o(n){var t=!1;if(r)return n;function o(n){var r=!1;if(t)return n;function o(n){var t=!1;if(r)return n;function o(n){var r=!1;if(t)return n;function o(n){var t=!1;if(r)return n;function o(n){var r=!1;if(t)return n;function o(n){var t=!1;if(r)return n;function o(n){var r=!1;if(t)return n;function o(n){var t=!1;if(r)return n;function o(n){var r=!1;if(t)return n;function o(n){var t=!1;if(r)return n;function o(n){var r=!1;if(t)return n;function o(n){var t=!1;if(r)return n;function o(n){var r=!1;if(t)return n;function o(n){var t=!1;if(r)return n;function o(n){var r=!1;if(t)return n;function o(n){return r?n:"watchdog"===e.name||"ping"===e.name?h.OK:void p(!1)}var a=function(){if("identify"===e.name)return Promise.resolve(i.onIdentify(e)).then((function(){return r=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("format"===e.name)return Promise.resolve(i.onFormat(e)).then((function(e){return e?(t=!0,{code:h.FormatReady,params:e}):(t=!0,h.OK)}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("uptime"===e.name)return Promise.resolve(i.onUptime(e)).then((function(e){return r=!0,{code:h.Uptime,params:e}}))}();return a&&a.then?a.then(o):o(a)}if("remote"===e.name)return{code:h.Remote,params:{enabled:!0,override:!1}};var a=function(){if("configuration"===e.name)return Promise.resolve(i.onConfiguration(e)).then((function(e){return e?(t=!0,{code:h.Configuration,params:e}):(t=!0,h.OK)}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("shuttle"===e.name)return Promise.resolve(i.onShuttle(e)).then((function(){return r=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("jog"===e.name)return Promise.resolve(i.onJog(e)).then((function(){return t=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}if("notify"===e.name)return h.OK;var a=function(){if("go to"===e.name)return Promise.resolve(i.onGoTo(e)).then((function(){return r=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("slot select"===e.name)return Promise.resolve(i.onSlotSelect(e)).then((function(){return t=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("slot info"===e.name)return Promise.resolve(i.onSlotInfo(e)).then((function(e){return r=!0,{code:h.SlotInfo,params:e}}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("transport info"===e.name)return Promise.resolve(i.onTransportInfo(e)).then((function(e){return t=!0,{code:h.TransportInfo,params:e}}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("clips clear"===e.name)return Promise.resolve(i.onClipsClear(e)).then((function(){return r=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("clips add"===e.name)return Promise.resolve(i.onClipsAdd(e)).then((function(){return t=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("clips get"===e.name)return Promise.resolve(i.onClipsGet(e).then(C)).then((function(e){return r=!0,{code:h.ClipsInfo,params:e}}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("clips count"===e.name)return Promise.resolve(i.onClipsCount(e)).then((function(e){return t=!0,{code:h.ClipsCount,params:e}}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("stop"===e.name)return Promise.resolve(i.onStop(e)).then((function(){return r=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("record"===e.name)return Promise.resolve(i.onRecord(e)).then((function(){return t=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("playrange clear"===e.name)return Promise.resolve(i.onPlayrangeClear(e)).then((function(){return r=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("playrange set"===e.name)return Promise.resolve(i.onPlayrangeSet(e)).then((function(){return t=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("play"===e.name)return Promise.resolve(i.onPlay(e)).then((function(){return r=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("preview"===e.name)return Promise.resolve(i.onPreview(e)).then((function(){return t=!0,h.OK}))}();return a&&a.then?a.then(o):o(a)}var a=function(){if("disk list"===e.name)return Promise.resolve(i.onDiskList(e)).then((function(e){return r=!0,{code:h.DiskList,params:e}}))}();return a&&a.then?a.then(o):o(a)}var r=function(){if("device info"===e.name)return Promise.resolve(i.onDeviceInfo(e)).then((function(e){return n=!0,{code:h.DeviceInfo,params:e}}))}();return r&&r.then?r.then(t):t(r)}()}catch(e){return r(e)}return o&&o.then?o.then(void 0,r):o}(0,(function(n){return n instanceof k?(i.logger.error({cmd:e},"unimplemented"),m.Unsupported):(i.logger.error({cmd:e,err:n.message},"unhandled command name"),m.InternalError)}))}))}catch(e){return Promise.reject(e)}},this.logger=n.child({name:"HyperDeck Emulator"}),this.server=r.createServer((function(e){t.logger.info("connection");var n=Math.random().toString(35).substr(-6),r=t.logger.child({name:"HyperDeck socket "+n});t.sockets[n]=new S(e,r,(function(e){return t.receivedCommand(e)})),t.sockets[n].on("disconnected",(function(){r.info("disconnected"),delete t.sockets[n]}))})),this.server.on("listening",(function(){return t.logger.info("listening")})),this.server.on("close",(function(){return t.logger.info("connection closed")})),this.server.on("error",(function(e){return t.logger.error("server error:",e)})),this.server.maxConnections=1,this.server.listen(9993,e)}var n=e.prototype;return n.close=function(){this.server.unref()},n.notifySlot=function(e){this.notify("slot",e)},n.notifyTransport=function(e){this.notify("transport",e)},n.notify=function(e,n){for(var t=0,r=Object.keys(this.sockets);t<r.length;t++)this.sockets[r[t]].notify(e,n)},e}(),exports.ResponseInterface={__proto__:null},exports.Timecode=g;
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0});var t,n,o,r=require("events"),i=e(require("util")),s=require("net"),a=e(require("pino"));class p extends Error{constructor(e,...t){super(i.format(e,...t)),this.template=e,this.args=t}}function d(e,t,...n){if(!e)throw new p(t,...n)}class l{constructor(e,t,n,o){const r=[e,t,n,o].map(e=>{const t=Math.floor(e);return t===e&&e>=0&&e<=99||d(!1),(t+100).toString().slice(-2)}).join(":");this.toString=()=>r}}l.toTimecode=e=>{const t=e.split(":");4!==t.length&&d(!1);const n=t.map(e=>{const t=parseInt(e,10);return isNaN(t)&&d(!1),t});return new l(n[0],n[1],n[2],n[3])},function(e){e[e.SyntaxError=100]="SyntaxError",e[e.UnsupportedParameter=101]="UnsupportedParameter",e[e.InvalidValue=102]="InvalidValue",e[e.Unsupported=103]="Unsupported",e[e.DiskFull=104]="DiskFull",e[e.NoDisk=105]="NoDisk",e[e.DiskError=106]="DiskError",e[e.TimelineEmpty=107]="TimelineEmpty",e[e.InternalError=108]="InternalError",e[e.OutOfRange=109]="OutOfRange",e[e.NoInput=110]="NoInput",e[e.RemoteControlDisabled=111]="RemoteControlDisabled",e[e.ConnectionRejected=120]="ConnectionRejected",e[e.InvalidState=150]="InvalidState",e[e.InvalidCodec=151]="InvalidCodec",e[e.InvalidFormat=160]="InvalidFormat",e[e.InvalidToken=161]="InvalidToken",e[e.FormatNotPrepared=162]="FormatNotPrepared"}(t||(t={})),function(e){e[e.OK=200]="OK",e[e.SlotInfo=202]="SlotInfo",e[e.DeviceInfo=204]="DeviceInfo",e[e.ClipsInfo=205]="ClipsInfo",e[e.DiskList=206]="DiskList",e[e.TransportInfo=208]="TransportInfo",e[e.Notify=209]="Notify",e[e.Remote=210]="Remote",e[e.Configuration=211]="Configuration",e[e.ClipsCount=214]="ClipsCount",e[e.Uptime=215]="Uptime",e[e.FormatReady=216]="FormatReady"}(n||(n={})),function(e){e[e.ConnectionInfo=500]="ConnectionInfo",e[e.SlotInfo=502]="SlotInfo",e[e.TransportInfo=508]="TransportInfo",e[e.RemoteInfo=510]="RemoteInfo",e[e.ConfigurationInfo=511]="ConfigurationInfo"}(o||(o={}));const c={[o.ConfigurationInfo]:"configuration info",[o.ConnectionInfo]:"connection info",[o.RemoteInfo]:"remote info",[o.SlotInfo]:"slot info",[o.TransportInfo]:"transport info",[t.ConnectionRejected]:"connection rejected",[t.DiskError]:"disk error",[t.DiskFull]:"disk full",[t.FormatNotPrepared]:"format not prepared",[t.InternalError]:"internal error",[t.InvalidCodec]:"invalid codec",[t.InvalidFormat]:"invalid format",[t.InvalidState]:"invalid state",[t.InvalidToken]:"invalid token",[t.InvalidValue]:"invalid value",[t.NoDisk]:"no disk",[t.NoInput]:"no input",[t.OutOfRange]:"out of range",[t.RemoteControlDisabled]:"remote control disabled",[t.SyntaxError]:"syntax error",[t.TimelineEmpty]:"timeline empty",[t.Unsupported]:"unsupported",[t.UnsupportedParameter]:"unsupported parameter",[n.ClipsCount]:"clips count",[n.ClipsInfo]:"clips info",[n.Configuration]:"configuration",[n.DeviceInfo]:"device info",[n.DiskList]:"disk list",[n.FormatReady]:"format ready",[n.Notify]:"notify",[n.OK]:"ok",[n.Remote]:"remote",[n.SlotInfo]:"slot info",[n.TransportInfo]:"transport info",[n.Uptime]:"uptime"},u={empty:!0,mounting:!0,error:!0,mounted:!0},m={NTSC:!0,PAL:!0,NTSCp:!0,PALp:!0,"720p50":!0,"720p5994":!0,"720p60":!0,"1080p23976":!0,"1080p24":!0,"1080p25":!0,"1080p2997":!0,"1080p30":!0,"1080i50":!0,"1080i5994":!0,"1080i60":!0,"4Kp23976":!0,"4Kp24":!0,"4Kp25":!0,"4Kp2997":!0,"4Kp30":!0,"4Kp50":!0,"4Kp5994":!0,"4Kp60":!0},f=e=>"object"==typeof e&&null!==e&&"string"==typeof e.name,g={preview:!0,stopped:!0,play:!0,forward:!0,rewind:!0,jog:!0,shuttle:!0,record:!0},h={lastframe:!0,nextframe:!0,black:!0},y={SDI:!0,HDMI:!0,component:!0},b={XLR:!0,RCA:!0,embedded:!0},v={PCM:!0,AAC:!0},I={external:!0,embedded:!0,preset:!0,clip:!0},O={none:!0,recordbit:!0,timecoderun:!0},C=e=>("string"!=typeof e&&d(!1),e),k={boolean:e=>"true"===e||"false"!==e&&void d(!1),string:C,timecode:e=>l.toTimecode(C(e)),number:e=>{const t=parseFloat(C(e));return isNaN(t)&&d(!1),t},videoformat:e=>((e=>"string"==typeof e&&m.hasOwnProperty(e))(e)||d(!1),e),stopmode:e=>((e=>"string"==typeof e&&h.hasOwnProperty(e))(e)||d(!1),e),goto:e=>{if("start"===e||"end"===e)return e;const t=parseInt(C(e),10);return isNaN(t)?C(e):t},videoinput:e=>((e=>"string"==typeof e&&y.hasOwnProperty(e))(e)||d(!1),e),audioinput:e=>((e=>"string"==typeof e&&b.hasOwnProperty(e))(e)||d(!1),e),fileformat:C,audiocodec:e=>((e=>"string"==typeof e&&v.hasOwnProperty(e))(e)||d(!1),e),timecodeinput:e=>((e=>"string"==typeof e&&I.hasOwnProperty(e))(e)||d(!1),e),recordtrigger:e=>((e=>"string"==typeof e&&O.hasOwnProperty(e))(e)||d(!1),e),clips:e=>(function(e,t,n){Array.isArray(t)||d(!1);for(const n of t)e(n)||d(!1)}(f,e),e),slotstatus:e=>((e=>"string"==typeof e&&u.hasOwnProperty(e))(e)||d(!1),e),transportstatus:e=>((e=>"string"==typeof e&&g.hasOwnProperty(e))(e)||d(!1),e)},w=e=>e.replace(/([a-z])([A-Z]+)/g,"$1 $2").toLowerCase();class R{constructor(e={}){this.options=e,this.addOption=(e,t)=>{const n=Array.isArray(e)?e[0]:e;return this.options.hasOwnProperty(n)&&d(!1),Object.assign(this.options,{[n]:t}),this},this.getParamsByCommandName=()=>Object.entries(this.options).reduce((e,[t,n])=>n.arguments?(e[t]=Object.entries(n.arguments).reduce((e,[t,n])=>(e[w(t)]={paramType:n,paramName:t},e),{}),e):(e[t]={},e),{})}}const P=(new R).addOption(["help","?"],{description:"Provides help text on all commands and parameters",returnValue:{}}).addOption("commands",{description:"return commands in XML format",returnValue:{commands:"string"}}).addOption("device info",{description:"return device information",returnValue:{protocolVersion:"string",model:"string",slotCount:"string"}}).addOption("disk list",{description:"query clip list on active disk",arguments:{slotId:"number"},returnValue:{slotId:"number"}}).addOption("quit",{description:"disconnect ethernet control",returnValue:{}}).addOption("ping",{description:"check device is responding",returnValue:{}}).addOption("preview",{description:"switch to preview or output",arguments:{enable:"boolean"},returnValue:{}}).addOption("play",{description:"play from current timecode",arguments:{speed:"number",loop:"boolean",singleClip:"boolean"},returnValue:{}}).addOption("playrange",{description:"query playrange setting",returnValue:{}}).addOption("playrange set",{description:"set play range to play clip {n} only",arguments:{clipId:"number",in:"timecode",out:"timecode",timelineIn:"number",timelineOut:"number"},returnValue:{}}).addOption("playrange clear",{description:"clear/reset play range setting",returnValue:{}}).addOption("play on startup",{description:"query unit play on startup state",arguments:{enable:"boolean",singleClip:"boolean"},returnValue:{}}).addOption("play option",{description:"query play options",arguments:{stopMode:"stopmode"},returnValue:{}}).addOption("record",{description:"record from current input",arguments:{name:"string"},returnValue:{}}).addOption("record spill",{description:"spill current recording to next slot",arguments:{slotId:"number"},returnValue:{}}).addOption("stop",{description:"stop playback or recording",returnValue:{}}).addOption("clips count",{description:"query number of clips on timeline",returnValue:{clipCount:"number"}}).addOption("clips get",{description:"query all timeline clips",arguments:{clipId:"number",count:"number",version:"number"},returnValue:{clips:"clips"}}).addOption("clips add",{description:"append a clip to timeline",arguments:{name:"string",clipId:"number",in:"timecode",out:"timecode"},returnValue:{}}).addOption("clips remove",{description:"remove clip {n} from the timeline (invalidates clip ids following clip {n})",arguments:{clipId:"number"},returnValue:{}}).addOption("clips clear",{description:"empty timeline clip list",returnValue:{}}).addOption("transport info",{description:"query current activity",returnValue:{status:"transportstatus",speed:"number",slotId:"number",clipId:"number",singleClip:"boolean",displayTimecode:"timecode",timecode:"timecode",videoFormat:"videoformat",loop:"boolean"}}).addOption("slot info",{description:"query active slot",arguments:{slotId:"number"},returnValue:{slotId:"number",status:"slotstatus",volumeName:"string",recordingTime:"timecode",videoFormat:"videoformat"}}).addOption("slot select",{description:"switch to specified slot",arguments:{slotId:"number",videoFormat:"videoformat"},returnValue:{}}).addOption("slot unblock",{description:"unblock active slot",arguments:{slotId:"number"},returnValue:{}}).addOption("dynamic range",{description:"query dynamic range settings",arguments:{playbackOverride:"string"},returnValue:{}}).addOption("notify",{description:"query notification status",arguments:{remote:"boolean",transport:"boolean",slot:"boolean",configuration:"boolean",droppedFrames:"boolean",displayTimecode:"boolean",timelinePosition:"boolean",playrange:"boolean",dynamicRange:"boolean"},returnValue:{remote:"boolean",transport:"boolean",slot:"boolean",configuration:"boolean",droppedFrames:"boolean",displayTimecode:"boolean",timelinePosition:"boolean",playrange:"boolean",dynamicRange:"boolean"}}).addOption("goto",{description:"go forward or backward within a clip or timeline",arguments:{clipId:"number",clip:"goto",timeline:"goto",timecode:"timecode",slotId:"number"},returnValue:{}}).addOption("jog",{description:"jog forward or backward",arguments:{timecode:"timecode"},returnValue:{}}).addOption("shuttle",{description:"shuttle with speed",arguments:{speed:"number"},returnValue:{}}).addOption("remote",{description:"query unit remote control state",arguments:{enable:"boolean",override:"boolean"},returnValue:{}}).addOption("configuration",{description:"query configuration settings",arguments:{videoInput:"videoinput",audioInput:"audioinput",fileFormat:"fileformat",audioCodec:"audiocodec",timecodeInput:"timecodeinput",timecodePreset:"timecode",audioInputChannels:"number",recordTrigger:"recordtrigger",recordPrefix:"string",appendTimestamp:"boolean"},returnValue:{videoInput:"videoinput",audioInput:"audioinput",fileFormat:"fileformat",audioCodec:"audiocodec",timecodeInput:"timecodeinput",timecodePreset:"timecode",audioInputChannels:"number",recordTrigger:"recordtrigger",recordPrefix:"string",appendTimestamp:"boolean"}}).addOption("uptime",{description:"return time since last boot",returnValue:{uptime:"number"}}).addOption("format",{description:"prepare a disk formatting operation to filesystem {format}",arguments:{prepare:"string",confirm:"string"},returnValue:{token:"string"}}).addOption("identify",{description:"identify the device",arguments:{enable:"boolean"},returnValue:{}}).addOption("watchdog",{description:"client connection timeout",arguments:{period:"number"},returnValue:{}}).getParamsByCommandName();function T(e){"string"==typeof e&&P.hasOwnProperty(e)||d(!1)}class S{constructor(e){this.linesQueue=[],this.logger=e.child({name:"MultilineParser"})}receivedString(e){const t=[],n=e.split("\r\n");for(n.length>0&&""===n[n.length-1]&&n.pop(),this.linesQueue=this.linesQueue.concat(n);this.linesQueue.length>0;){if(""===this.linesQueue[0]){this.linesQueue.shift();continue}if(!this.linesQueue[0].includes(":")||1===this.linesQueue.length&&this.linesQueue[0].includes(":")){const e=this.parseResponse(this.linesQueue.splice(0,1));e&&t.push(e);continue}const e=this.linesQueue.indexOf("");if(-1===e)break;const n=this.linesQueue.splice(0,e+1),o=this.parseResponse(n);o&&t.push(o)}return t}parseResponse(e){try{const t=e.map(e=>e.trim()),n=t[0];if(1===t.length){if(!n.includes(":"))return T(n),{raw:t.join("\r\n"),name:n,parameters:{}};const e=n.split(": "),o=e.shift();T(o);const r={},i=P[o];let s=e.shift();s||d(!1);for(let t=0;t<e.length-1;t++){const n=e[t].split(" ");let o="";for(let e=n.length-1;e>=0&&(o=(n.pop()+" "+o).trim(),!i.hasOwnProperty(o));e--);n.length>0||d(!1),i.hasOwnProperty(s)||d(!1);const a=n.join(" "),{paramName:p,paramType:l}=i[s];r[p]=(0,k[l])(a),s=o}i.hasOwnProperty(s)||d(!1);const a=e[e.length-1],{paramName:p,paramType:l}=i[s];return r[p]=(0,k[l])(a),{raw:t.join("\r\n"),name:o,parameters:r}}n.endsWith(":")||d(!1);const o=n.slice(0,-1);T(o);const r=P[o],i={};for(const e of t){const t=e.match(/^(.*?): (.*)$/im);t||d(!1);const n=t[1],o=t[2];r.hasOwnProperty(n)||d(!1);const{paramName:s,paramType:a}=r[n];i[s]=(0,k[a])(o)}return{raw:t.join("\r\n"),name:o,parameters:i}}catch(e){return e instanceof p?this.logger.error(e.template,...e.args):this.logger.error({err:e+""},"parseResponse error"),null}}}class V extends r.EventEmitter{constructor(e,t,n){super(),this.socket=e,this.logger=t,this.receivedCommand=n,this.lastReceivedMS=-1,this.watchdogTimer=null,this.notifySettings={configuration:!1,displayTimecode:!1,droppedFrames:!1,dynamicRange:!1,playrange:!1,remote:!1,slot:!1,timelinePosition:!1,transport:!1},this.parser=new S(t),this.socket.setEncoding("utf-8"),this.socket.on("data",e=>{this.onMessage(e)}),this.socket.on("error",e=>{t.info({err:e},"error"),this.socket.destroy(),this.emit("disconnected"),t.info("manually disconnected")}),this.sendResponse(o.ConnectionInfo,{"protocol version":"1.11",model:"NodeJS HyperDeck Server Library"})}onMessage(e){this.logger.info({data:e},"<--- received message from client"),this.lastReceivedMS=Date.now();const r=this.parser.receivedString(e);this.logger.info({cmds:r},"parsed commands");for(const e of r){if("watchdog"===e.name){this.watchdogTimer&&clearInterval(this.watchdogTimer);const t=e;t.parameters.period&&(this.watchdogTimer=setInterval(()=>{Date.now()-this.lastReceivedMS>Number(t.parameters.period)&&(this.socket.destroy(),this.emit("disconnected"),this.watchdogTimer&&clearInterval(this.watchdogTimer))},1e3*Number(t.parameters.period)))}else if("notify"===e.name){const t=e;if(!(Object.keys(t.parameters).length>0)){const t={};for(const e of Object.keys(this.notifySettings))t[e]=this.notifySettings[e]?"true":"false";this.sendResponse(n.Notify,t,e);continue}for(const e of Object.keys(t.parameters))void 0!==this.notifySettings[e]&&(this.notifySettings[e]=!0===t.parameters[e])}this.receivedCommand(e).then(r=>"object"==typeof r?this.sendResponse(r.code,"params"in r&&r.params||"message"in r&&r.message||void 0,e):"number"==typeof r&&(t[r]||n[r]||o[r])?this.sendResponse(r,void 0,e):(this.logger.error({cmd:e,codeOrObj:r},"codeOrObj was neither a ResponseCode nor a response object"),void this.sendResponse(t.InternalError,void 0,e)),()=>this.sendResponse(t.Unsupported,void 0,e))}}sendResponse(e,n,o){try{const r=((e,t)=>{if("string"==typeof t)return e+" "+t.replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/:/g,"")+"\r\n";const n=`${e} ${c[e]}`;if(!t)return n+"\r\n";const o=Object.entries(t).filter(([,e])=>null!=e);return 0===o.length?n+"\r\n":o.reduce((e,[t,n])=>{let o;return"string"==typeof n?o=n:"boolean"==typeof n?o=n?"true":"false":"number"==typeof n||n instanceof l?o=n.toString():d(!1),e+w(t)+": "+o+"\r\n"},n+":\r\n")+"\r\n"})(e,n);this.logger[t[e]?"error":"info"]({responseText:r,cmd:o},"---\x3e send response to client"),this.socket.write(r)}catch(e){this.logger.error({cmd:o},"-x-> Error sending response: %s",e)}}notify(e,t){this.logger.info({type:e,params:t},"notify"),"configuration"===e&&this.notifySettings.configuration?this.sendResponse(o.ConfigurationInfo,t):"remote"===e&&this.notifySettings.remote?this.sendResponse(o.RemoteInfo,t):"slot"===e&&this.notifySettings.slot?this.sendResponse(o.SlotInfo,t):"transport"===e&&this.notifySettings.transport?this.sendResponse(o.TransportInfo,t):this.logger.error({type:e,params:t},"unhandled notify type")}}const N=e=>{if(!e.clips)return{clipsCount:0};const t=e.clips.length,n={clipsCount:t};for(let o=0;o<t;o++){const t=e.clips[o];n[(o+1).toString()]=`${t.name} ${t.startT} ${t.duration}`}return n};exports.HyperDeckServer=class{constructor(e,o=a()){const r=this;this.sockets={},this.commandHandlers={},this.on=(e,t)=>{P.hasOwnProperty(e)||d(!1),this.commandHandlers.hasOwnProperty(e)&&d(!1),this.commandHandlers[e]=t},this.receivedCommand=function(e){try{return Promise.resolve(new Promise(e=>setTimeout(()=>e(),200))).then((function(){if(r.logger.info({cmd:e},"receivedCommand %s",e.name),"remote"===e.name)return{code:n.Remote,params:{enabled:!0,override:!1}};if("notify"===e.name||"watchdog"===e.name||"ping"===e.name)return n.OK;const o=r.commandHandlers[e.name];return o?Promise.resolve(o(e)).then((function(o){const i={name:e.name,response:o};return"clips add"===i.name||"clips clear"===i.name||"goto"===i.name||"identify"===i.name||"jog"===i.name||"play"===i.name||"playrange clear"===i.name||"playrange set"===i.name||"preview"===i.name||"record"===i.name||"shuttle"===i.name||"slot select"===i.name||"stop"===i.name?n.OK:"device info"===i.name?{code:n.DeviceInfo,params:i.response}:"disk list"===i.name?{code:n.DiskList,params:i.response}:"clips count"===i.name?{code:n.ClipsCount,params:i.response}:"clips get"===i.name?{code:n.ClipsInfo,params:N(i.response)}:"transport info"===i.name?{code:n.TransportInfo,params:i.response}:"slot info"===i.name?{code:n.SlotInfo,params:i.response}:"configuration"===i.name?i?{code:n.Configuration,params:i.response}:n.OK:"uptime"===i.name?{code:n.Uptime,params:i.response}:"format"===i.name?i?{code:n.FormatReady,params:i.response}:n.OK:(r.logger.error({cmd:e,res:i},"Unsupported command"),t.Unsupported)})):(r.logger.error({cmd:e},"unimplemented"),t.Unsupported)}))}catch(e){return Promise.reject(e)}},this.logger=o.child({name:"HyperDeck Emulator"}),this.server=s.createServer(e=>{this.logger.info("connection");const t=Math.random().toString(35).substr(-6),n=this.logger.child({name:"HyperDeck socket "+t});this.sockets[t]=new V(e,n,this.receivedCommand),this.sockets[t].on("disconnected",()=>{n.info("disconnected"),delete this.sockets[t]})}),this.server.on("listening",()=>this.logger.info("listening")),this.server.on("close",()=>this.logger.info("connection closed")),this.server.on("error",e=>this.logger.error("server error:",e)),this.server.maxConnections=1,this.server.listen(9993,e)}close(){this.server.unref()}notifySlot(e){this.notify("slot",e)}notifyTransport(e){this.notify("transport",e)}notify(e,t){for(const n of Object.keys(this.sockets))this.sockets[n].notify(e,t)}},exports.Timecode=l;
//# sourceMappingURL=hyperdeck-emulator.cjs.production.min.js.map

@@ -6,181 +6,35 @@ import { EventEmitter } from 'events';

function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
class FormattedError extends Error {
constructor(template, ...args) {
super(util.format(template, ...args));
this.template = template;
this.args = args;
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
return true;
} catch (e) {
return false;
function invariant(condition, message, ...args) {
if (!condition) {
throw new FormattedError(message, ...args);
}
}
function _construct(Parent, args, Class) {
if (_isNativeReflectConstruct()) {
_construct = Reflect.construct;
} else {
_construct = function _construct(Parent, args, Class) {
var a = [null];
a.push.apply(a, args);
var Constructor = Function.bind.apply(Parent, a);
var instance = new Constructor();
if (Class) _setPrototypeOf(instance, Class.prototype);
return instance;
};
}
class Timecode {
constructor(hh, mm, ss, ff) {
const timecode = [hh, mm, ss, ff].map(code => {
const codeInt = Math.floor(code);
!(codeInt === code && code >= 0 && code <= 99) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Timecode params must be an integer between 0 and 99') : invariant(false) : void 0; // turn the integer into a potentially zero-prefixed string
return _construct.apply(null, arguments);
}
return (codeInt + 100).toString().slice(-2);
}).join(':');
function _isNativeFunction(fn) {
return Function.toString.call(fn).indexOf("[native code]") !== -1;
}
function _wrapNativeSuper(Class) {
var _cache = typeof Map === "function" ? new Map() : undefined;
_wrapNativeSuper = function _wrapNativeSuper(Class) {
if (Class === null || !_isNativeFunction(Class)) return Class;
if (typeof Class !== "function") {
throw new TypeError("Super expression must either be null or a function");
}
if (typeof _cache !== "undefined") {
if (_cache.has(Class)) return _cache.get(Class);
_cache.set(Class, Wrapper);
}
function Wrapper() {
return _construct(Class, arguments, _getPrototypeOf(this).constructor);
}
Wrapper.prototype = Object.create(Class.prototype, {
constructor: {
value: Wrapper,
enumerable: false,
writable: true,
configurable: true
}
});
return _setPrototypeOf(Wrapper, Class);
};
return _wrapNativeSuper(Class);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
var it;
if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
return function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
this.toString = () => timecode;
}
it = o[Symbol.iterator]();
return it.next.bind(it);
}
// A type of promise-like that resolves synchronously and supports only one observer
const _iteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.iterator || (Symbol.iterator = Symbol("Symbol.iterator"))) : "@@iterator";
const _asyncIteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator"))) : "@@asyncIterator";
// Asynchronously call a function and send errors to recovery continuation
function _catch(body, recover) {
try {
var result = body();
} catch(e) {
return recover(e);
}
if (result && result.then) {
return result.then(void 0, recover);
}
return result;
}
function invariant(condition, message) {
if (!condition) {
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
args[_key - 2] = arguments[_key];
}
throw new Error(util.format.apply(util, [message].concat(args)));
}
}
var Timecode = function Timecode(hh, mm, ss, ff) {
var timecode = [hh, mm, ss, ff].map(function (code) {
var codeInt = Math.floor(code);
!(codeInt === code && code >= 0 && code <= 99) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Timecode params must be an integer between 0 and 99') : invariant(false) : void 0; // turn the integer into a potentially zero-prefixed string
return (codeInt + 100).toString().slice(-2);
}).join(':');
this.toString = function () {
return timecode;
};
};
Timecode.toTimecode = function (tcString) {
var bits = tcString.split(':');
Timecode.toTimecode = tcString => {
const bits = tcString.split(':');
!(bits.length === 4) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Expected 4 bits, received %o bits', bits.length) : invariant(false) : void 0;
var bitsInt = bits.map(function (bit) {
var bitInt = parseInt(bit, 10);
const bitsInt = bits.map(bit => {
const bitInt = parseInt(bit, 10);
!!isNaN(bitInt) ? process.env.NODE_ENV !== "production" ? invariant(false, 'bit `%s` is NaN', bit) : invariant(false) : void 0;

@@ -192,3 +46,2 @@ return bitInt;

var _responseNamesByCode;
var ErrorCode;

@@ -244,40 +97,253 @@

var responseNamesByCode = (_responseNamesByCode = {}, _responseNamesByCode[AsynchronousCode.ConfigurationInfo] = 'configuration info', _responseNamesByCode[AsynchronousCode.ConnectionInfo] = 'connection info', _responseNamesByCode[AsynchronousCode.RemoteInfo] = 'remote info', _responseNamesByCode[AsynchronousCode.SlotInfo] = 'slot info', _responseNamesByCode[AsynchronousCode.TransportInfo] = 'transport info', _responseNamesByCode[ErrorCode.ConnectionRejected] = 'connection rejected', _responseNamesByCode[ErrorCode.DiskError] = 'disk error', _responseNamesByCode[ErrorCode.DiskFull] = 'disk full', _responseNamesByCode[ErrorCode.FormatNotPrepared] = 'format not prepared', _responseNamesByCode[ErrorCode.InternalError] = 'internal error', _responseNamesByCode[ErrorCode.InvalidCodec] = 'invalid codec', _responseNamesByCode[ErrorCode.InvalidFormat] = 'invalid format', _responseNamesByCode[ErrorCode.InvalidState] = 'invalid state', _responseNamesByCode[ErrorCode.InvalidToken] = 'invalid token', _responseNamesByCode[ErrorCode.InvalidValue] = 'invalid value', _responseNamesByCode[ErrorCode.NoDisk] = 'no disk', _responseNamesByCode[ErrorCode.NoInput] = 'no input', _responseNamesByCode[ErrorCode.OutOfRange] = 'out of range', _responseNamesByCode[ErrorCode.RemoteControlDisabled] = 'remote control disabled', _responseNamesByCode[ErrorCode.SyntaxError] = 'syntax error', _responseNamesByCode[ErrorCode.TimelineEmpty] = 'timeline empty', _responseNamesByCode[ErrorCode.Unsupported] = 'unsupported', _responseNamesByCode[ErrorCode.UnsupportedParameter] = 'unsupported parameter', _responseNamesByCode[SynchronousCode.ClipsCount] = 'clips count', _responseNamesByCode[SynchronousCode.ClipsInfo] = 'clips info', _responseNamesByCode[SynchronousCode.Configuration] = 'configuration', _responseNamesByCode[SynchronousCode.DeviceInfo] = 'device info', _responseNamesByCode[SynchronousCode.DiskList] = 'disk list', _responseNamesByCode[SynchronousCode.FormatReady] = 'format ready', _responseNamesByCode[SynchronousCode.Notify] = 'notify', _responseNamesByCode[SynchronousCode.OK] = 'ok', _responseNamesByCode[SynchronousCode.Remote] = 'remote', _responseNamesByCode[SynchronousCode.SlotInfo] = 'slot info', _responseNamesByCode[SynchronousCode.TransportInfo] = 'transport info', _responseNamesByCode[SynchronousCode.Uptime] = 'uptime', _responseNamesByCode);
const responseNamesByCode = {
[AsynchronousCode.ConfigurationInfo]: 'configuration info',
[AsynchronousCode.ConnectionInfo]: 'connection info',
[AsynchronousCode.RemoteInfo]: 'remote info',
[AsynchronousCode.SlotInfo]: 'slot info',
[AsynchronousCode.TransportInfo]: 'transport info',
[ErrorCode.ConnectionRejected]: 'connection rejected',
[ErrorCode.DiskError]: 'disk error',
[ErrorCode.DiskFull]: 'disk full',
[ErrorCode.FormatNotPrepared]: 'format not prepared',
[ErrorCode.InternalError]: 'internal error',
[ErrorCode.InvalidCodec]: 'invalid codec',
[ErrorCode.InvalidFormat]: 'invalid format',
[ErrorCode.InvalidState]: 'invalid state',
[ErrorCode.InvalidToken]: 'invalid token',
[ErrorCode.InvalidValue]: 'invalid value',
[ErrorCode.NoDisk]: 'no disk',
[ErrorCode.NoInput]: 'no input',
[ErrorCode.OutOfRange]: 'out of range',
[ErrorCode.RemoteControlDisabled]: 'remote control disabled',
[ErrorCode.SyntaxError]: 'syntax error',
[ErrorCode.TimelineEmpty]: 'timeline empty',
[ErrorCode.Unsupported]: 'unsupported',
[ErrorCode.UnsupportedParameter]: 'unsupported parameter',
[SynchronousCode.ClipsCount]: 'clips count',
[SynchronousCode.ClipsInfo]: 'clips info',
[SynchronousCode.Configuration]: 'configuration',
[SynchronousCode.DeviceInfo]: 'device info',
[SynchronousCode.DiskList]: 'disk list',
[SynchronousCode.FormatReady]: 'format ready',
[SynchronousCode.Notify]: 'notify',
[SynchronousCode.OK]: 'ok',
[SynchronousCode.Remote]: 'remote',
[SynchronousCode.SlotInfo]: 'slot info',
[SynchronousCode.TransportInfo]: 'transport info',
[SynchronousCode.Uptime]: 'uptime'
};
const slotStatus = {
empty: true,
mounting: true,
error: true,
mounted: true
};
const isSlotStatus = value => {
return typeof value === 'string' && slotStatus.hasOwnProperty(value);
};
const videoFormats = {
NTSC: true,
PAL: true,
NTSCp: true,
PALp: true,
'720p50': true,
'720p5994': true,
'720p60': true,
'1080p23976': true,
'1080p24': true,
'1080p25': true,
'1080p2997': true,
'1080p30': true,
'1080i50': true,
'1080i5994': true,
'1080i60': true,
'4Kp23976': true,
'4Kp24': true,
'4Kp25': true,
'4Kp2997': true,
'4Kp30': true,
'4Kp50': true,
'4Kp5994': true,
'4Kp60': true
};
const isClipV1 = value => {
return typeof value === 'object' && value !== null && typeof value.name === 'string';
};
const isVideoFormat = value => {
return typeof value === 'string' && videoFormats.hasOwnProperty(value);
};
const transportStatus = {
preview: true,
stopped: true,
play: true,
forward: true,
rewind: true,
jog: true,
shuttle: true,
record: true
};
const isTransportStatus = value => {
return typeof value === 'string' && transportStatus.hasOwnProperty(value);
};
const stopModes = {
lastframe: true,
nextframe: true,
black: true
};
const isStopMode = value => {
return typeof value === 'string' && stopModes.hasOwnProperty(value);
};
const videoInputs = {
SDI: true,
HDMI: true,
component: true
};
const isVideoInput = value => {
return typeof value === 'string' && videoInputs.hasOwnProperty(value);
};
const audioInputs = {
XLR: true,
RCA: true,
// TODO(meyer) verify this
embedded: true
};
const isAudioInput = value => {
return typeof value === 'string' && audioInputs.hasOwnProperty(value);
};
const audioCodecs = {
PCM: true,
AAC: true
};
const isAudioCodec = value => {
return typeof value === 'string' && audioCodecs.hasOwnProperty(value);
};
const timecodeInputs = {
external: true,
embedded: true,
preset: true,
clip: true
};
const isTimecodeInput = value => {
return typeof value === 'string' && timecodeInputs.hasOwnProperty(value);
};
const recordTriggers = {
none: true,
recordbit: true,
timecoderun: true
};
const isRecordTrigger = value => {
return typeof value === 'string' && recordTriggers.hasOwnProperty(value);
};
var CRLF = '\r\n';
function assertArrayOf(predicate, value, message) {
!Array.isArray(value) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Expected an array') : invariant(false) : void 0;
/** Convert `yourExampleKey` to `your example key` */
var camelcaseToSpaceCase = function camelcaseToSpaceCase(key) {
return key.replace(/([a-z])([A-Z]+)/g, '$1 $2').toLowerCase();
for (const item of value) {
!predicate(item) ? process.env.NODE_ENV !== "production" ? invariant(false, message) : invariant(false) : void 0;
}
}
const getStringOrThrow = value => {
!(typeof value === 'string') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Expected a string') : invariant(false) : void 0;
return value;
};
/** Internal container class that holds metadata about each HyperDeck event */
const stringToValueFns = {
boolean: value => {
if (value === 'true') return true;
if (value === 'false') return false;
process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported value `%o` passed to `boolean`', value) : invariant(false) ;
},
string: getStringOrThrow,
timecode: value => Timecode.toTimecode(getStringOrThrow(value)),
number: value => {
const valueNum = parseFloat(getStringOrThrow(value));
!!isNaN(valueNum) ? process.env.NODE_ENV !== "production" ? invariant(false, 'valueNum `%o` is NaN', value) : invariant(false) : void 0;
return valueNum;
},
videoformat: value => {
!isVideoFormat(value) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported video format: `%o`') : invariant(false) : void 0;
return value;
},
stopmode: value => {
!isStopMode(value) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported stopmode: `%o`', value) : invariant(false) : void 0;
return value;
},
goto: value => {
if (value === 'start' || value === 'end') {
return value;
}
var HyperDeckAPI = function HyperDeckAPI(options) {
var _this = this;
const valueNum = parseInt(getStringOrThrow(value), 10);
if (options === void 0) {
options = {};
if (!isNaN(valueNum)) {
return valueNum;
} // TODO(meyer) validate further
return getStringOrThrow(value);
},
videoinput: value => {
!isVideoInput(value) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported video input: `%o`', value) : invariant(false) : void 0;
return value;
},
audioinput: value => {
!isAudioInput(value) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported audio input: `%o`', value) : invariant(false) : void 0;
return value;
},
fileformat: getStringOrThrow,
audiocodec: value => {
!isAudioCodec(value) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported audio codec: `%o`', value) : invariant(false) : void 0;
return value;
},
timecodeinput: value => {
!isTimecodeInput(value) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported timecode input: `%o`', value) : invariant(false) : void 0;
return value;
},
recordtrigger: value => {
!isRecordTrigger(value) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported record trigger: `%o`', value) : invariant(false) : void 0;
return value;
},
clips: value => {
assertArrayOf(isClipV1, value, 'Expected an array of clips');
return value;
},
slotstatus: value => {
!isSlotStatus(value) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported slot status: `%o`', value) : invariant(false) : void 0;
return value;
},
transportstatus: value => {
!isTransportStatus(value) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported slot status: `%o`', value) : invariant(false) : void 0;
return value;
}
};
this.options = options;
const CRLF = '\r\n';
this.addOption = function (key, option) {
var _Object$assign;
/** Convert `yourExampleKey` to `your example key` */
const camelcaseToSpaceCase = key => {
return key.replace(/([a-z])([A-Z]+)/g, '$1 $2').toLowerCase();
};
var k = Array.isArray(key) ? key[0] : key;
!!_this.options.hasOwnProperty(k) ? process.env.NODE_ENV !== "production" ? invariant(false, 'option already exists for key `%s`', k) : invariant(false) : void 0; // NOTE: this mutates the original options object
// shouldn't be a problem since this is only used internally
/** Internal container class that holds metadata about each HyperDeck event */
Object.assign(_this.options, (_Object$assign = {}, _Object$assign[k] = option, _Object$assign));
return _this;
};
/** Get a `Set` of param names keyed by function name */
class HyperDeckAPI {
constructor( // public only because TS apparently strips types from private methods
options = {}) {
this.options = options;
this.addOption = (key, option) => {
const k = Array.isArray(key) ? key[0] : key;
!!this.options.hasOwnProperty(k) ? process.env.NODE_ENV !== "production" ? invariant(false, 'option already exists for key `%s`', k) : invariant(false) : void 0; // NOTE: this mutates the original options object
// shouldn't be a problem since this is only used internally
this.getParamsByKey = function () {
return Object.entries(_this.options).reduce(function (prev, _ref) {
var commandName = _ref[0],
value = _ref[1];
Object.assign(this.options, {
[k]: option
});
return this;
};
/** Get a `Set` of param names keyed by function name */
this.getParamsByCommandName = () => Object.entries(this.options).reduce((prev, [commandName, value]) => {
if (!value.arguments) {

@@ -289,6 +355,7 @@ // we still want hasOwnProperty(key) to be true

prev[commandName] = Object.entries(value.arguments).reduce(function (argObj, _ref2) {
var argKey = _ref2[0],
argType = _ref2[1];
argObj[camelcaseToSpaceCase(argKey)] = argType;
prev[commandName] = Object.entries(value.arguments).reduce((argObj, [argKey, argType]) => {
argObj[camelcaseToSpaceCase(argKey)] = {
paramType: argType,
paramName: argKey
};
return argObj;

@@ -298,11 +365,21 @@ }, {});

}, {});
};
};
}
var api = /*#__PURE__*/new HyperDeckAPI().addOption(['help', '?'], {
description: 'Provides help text on all commands and parameters'
}
const api = /*#__PURE__*/new HyperDeckAPI().addOption(['help', '?'], {
description: 'Provides help text on all commands and parameters',
returnValue: {}
}).addOption('commands', {
description: 'return commands in XML format'
description: 'return commands in XML format',
returnValue: {
commands: 'string'
}
}).addOption('device info', {
description: 'return device information'
description: 'return device information',
returnValue: {
protocolVersion: 'string',
model: 'string',
slotCount: 'string'
}
}).addOption('disk list', {

@@ -312,7 +389,12 @@ description: 'query clip list on active disk',

slotId: 'number'
},
returnValue: {
slotId: 'number'
}
}).addOption('quit', {
description: 'disconnect ethernet control'
description: 'disconnect ethernet control',
returnValue: {}
}).addOption('ping', {
description: 'check device is responding'
description: 'check device is responding',
returnValue: {}
}).addOption('preview', {

@@ -322,3 +404,4 @@ description: 'switch to preview or output',

enable: 'boolean'
}
},
returnValue: {}
}).addOption('play', {

@@ -330,5 +413,8 @@ description: 'play from current timecode',

singleClip: 'boolean'
},
returnValue: {}
}).addOption('playrange', {
description: 'query playrange setting',
returnValue: {// TODO(meyer) this isn't accurate
}
}).addOption('playrange', {
description: 'query playrange setting'
}).addOption('playrange set', {

@@ -338,5 +424,5 @@ description: 'set play range to play clip {n} only',

// maybe number?
clipId: 'string',
clipId: 'number',
// description: 'set play range to play between timecode {inT} and timecode {outT}',
"in": 'timecode',
in: 'timecode',
out: 'timecode',

@@ -346,5 +432,7 @@ // 'set play range in units of frames between timeline position {in} and position {out} clear/reset play range°setting',

timelineOut: 'number'
}
},
returnValue: {}
}).addOption('playrange clear', {
description: 'clear/reset play range setting'
description: 'clear/reset play range setting',
returnValue: {}
}).addOption('play on startup', {

@@ -356,3 +444,5 @@ description: 'query unit play on startup state',

singleClip: 'boolean'
}
},
// TODO(meyer) verify that there's no return value
returnValue: {}
}).addOption('play option', {

@@ -362,3 +452,5 @@ description: 'query play options',

stopMode: 'stopmode'
}
},
// TODO(meyer)
returnValue: {}
}).addOption('record', {

@@ -368,3 +460,4 @@ description: 'record from current input',

name: 'string'
}
},
returnValue: {}
}).addOption('record spill', {

@@ -374,7 +467,13 @@ description: 'spill current recording to next slot',

slotId: 'number'
}
},
// TODO(meyer)
returnValue: {}
}).addOption('stop', {
description: 'stop playback or recording'
description: 'stop playback or recording',
returnValue: {}
}).addOption('clips count', {
description: 'query number of clips on timeline'
description: 'query number of clips on timeline',
returnValue: {
clipCount: 'number'
}
}).addOption('clips get', {

@@ -386,2 +485,5 @@ description: 'query all timeline clips',

version: 'number'
},
returnValue: {
clips: 'clips'
}

@@ -392,15 +494,30 @@ }).addOption('clips add', {

name: 'string',
clipId: 'string',
"in": 'timecode',
clipId: 'number',
in: 'timecode',
out: 'timecode'
}
},
returnValue: {}
}).addOption('clips remove', {
description: 'remove clip {n} from the timeline (invalidates clip ids following clip {n})',
arguments: {
clidId: 'string'
}
clipId: 'number'
},
// TODO(meyer) verify this
returnValue: {}
}).addOption('clips clear', {
description: 'empty timeline clip list'
description: 'empty timeline clip list',
returnValue: {}
}).addOption('transport info', {
description: 'query current activity'
description: 'query current activity',
returnValue: {
status: 'transportstatus',
speed: 'number',
slotId: 'number',
clipId: 'number',
singleClip: 'boolean',
displayTimecode: 'timecode',
timecode: 'timecode',
videoFormat: 'videoformat',
loop: 'boolean'
}
}).addOption('slot info', {

@@ -410,2 +527,9 @@ description: 'query active slot',

slotId: 'number'
},
returnValue: {
slotId: 'number',
status: 'slotstatus',
volumeName: 'string',
recordingTime: 'timecode',
videoFormat: 'videoformat'
}

@@ -417,3 +541,4 @@ }).addOption('slot select', {

videoFormat: 'videoformat'
}
},
returnValue: {}
}).addOption('slot unblock', {

@@ -423,3 +548,5 @@ description: 'unblock active slot',

slotId: 'number'
}
},
// TODO(meyer) verify this
returnValue: {}
}).addOption('dynamic range', {

@@ -430,3 +557,5 @@ description: 'query dynamic range settings',

playbackOverride: 'string'
}
},
// TODO(meyer)
returnValue: {}
}).addOption('notify', {

@@ -444,2 +573,13 @@ description: 'query notification status',

dynamicRange: 'boolean'
},
returnValue: {
remote: 'boolean',
transport: 'boolean',
slot: 'boolean',
configuration: 'boolean',
droppedFrames: 'boolean',
displayTimecode: 'boolean',
timelinePosition: 'boolean',
playrange: 'boolean',
dynamicRange: 'boolean'
}

@@ -449,3 +589,3 @@ }).addOption('goto', {

arguments: {
clipId: 'string',
clipId: 'number',
clip: 'goto',

@@ -455,3 +595,4 @@ timeline: 'goto',

slotId: 'number'
}
},
returnValue: {}
}).addOption('jog', {

@@ -461,3 +602,4 @@ description: 'jog forward or backward',

timecode: 'timecode'
}
},
returnValue: {}
}).addOption('shuttle', {

@@ -467,3 +609,4 @@ description: 'shuttle with speed',

speed: 'number'
}
},
returnValue: {}
}).addOption('remote', {

@@ -474,3 +617,5 @@ description: 'query unit remote control state',

override: 'boolean'
}
},
// TODO(meyer)
returnValue: {}
}).addOption('configuration', {

@@ -489,5 +634,20 @@ description: 'query configuration settings',

appendTimestamp: 'boolean'
},
returnValue: {
videoInput: 'videoinput',
audioInput: 'audioinput',
fileFormat: 'fileformat',
audioCodec: 'audiocodec',
timecodeInput: 'timecodeinput',
timecodePreset: 'timecode',
audioInputChannels: 'number',
recordTrigger: 'recordtrigger',
recordPrefix: 'string',
appendTimestamp: 'boolean'
}
}).addOption('uptime', {
description: 'return time since last boot'
description: 'return time since last boot',
returnValue: {
uptime: 'number'
}
}).addOption('format', {

@@ -498,2 +658,5 @@ description: 'prepare a disk formatting operation to filesystem {format}',

confirm: 'string'
},
returnValue: {
token: 'string'
}

@@ -504,3 +667,5 @@ }).addOption('identify', {

enable: 'boolean'
}
},
// TODO(meyer) verify
returnValue: {}
}).addOption('watchdog', {

@@ -510,8 +675,13 @@ description: 'client connection timeout',

period: 'number'
}
},
// TODO(meyer) verify
returnValue: {}
});
var paramsByKey = /*#__PURE__*/api.getParamsByKey();
const paramsByCommandName = /*#__PURE__*/api.getParamsByCommandName();
function assertValidCommandName(value) {
!(typeof value === 'string' && paramsByCommandName.hasOwnProperty(value)) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Invalid command: `%o`', value) : invariant(false) : void 0;
}
var MultilineParser = /*#__PURE__*/function () {
function MultilineParser(logger) {
class MultilineParser {
constructor(logger) {
this.linesQueue = [];

@@ -523,9 +693,7 @@ this.logger = logger.child({

var _proto = MultilineParser.prototype;
receivedString(data) {
const res = []; // add new lines to processing queue
_proto.receivedString = function receivedString(data) {
var res = []; // add new lines to processing queue
const newLines = data.split(CRLF); // remove the blank line at the end from the intentionally trailing \r\n
var newLines = data.split(CRLF); // remove the blank line at the end from the intentionally trailing \r\n
if (newLines.length > 0 && newLines[newLines.length - 1] === '') newLines.pop();

@@ -543,3 +711,3 @@ this.linesQueue = this.linesQueue.concat(newLines);

if (!this.linesQueue[0].includes(':') || this.linesQueue.length === 1 && this.linesQueue[0].includes(':')) {
var parsedResponse = this.parseResponse(this.linesQueue.splice(0, 1));
const parsedResponse = this.parseResponse(this.linesQueue.splice(0, 1));

@@ -553,3 +721,3 @@ if (parsedResponse) {

var endLine = this.linesQueue.indexOf('');
const endLine = this.linesQueue.indexOf('');

@@ -561,7 +729,7 @@ if (endLine === -1) {

var lines = this.linesQueue.splice(0, endLine + 1);
var r = this.parseResponse(lines);
const lines = this.linesQueue.splice(0, endLine + 1);
const parsedResponse = this.parseResponse(lines);
if (r) {
res.push(r);
if (parsedResponse) {
res.push(parsedResponse);
}

@@ -571,82 +739,111 @@ }

return res;
};
}
_proto.parseResponse = function parseResponse(responseLines) {
var lines = responseLines.map(function (l) {
return l.trim();
});
parseResponse(responseLines) {
try {
const lines = responseLines.map(l => l.trim());
const firstLine = lines[0];
if (lines.length === 1 && lines[0].includes(':')) {
var bits = lines[0].split(': ');
var msg = bits.shift();
!msg ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unrecognised command') : invariant(false) : void 0;
!paramsByKey.hasOwnProperty(msg) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Invalid command: `%s`', msg) : invariant(false) : void 0;
var params = {};
var paramNames = paramsByKey[msg];
var param = bits.shift();
!param ? process.env.NODE_ENV !== "production" ? invariant(false, 'No named parameters found') : invariant(false) : void 0;
if (lines.length === 1) {
if (!firstLine.includes(':')) {
assertValidCommandName(firstLine);
return {
raw: lines.join(CRLF),
name: firstLine,
parameters: {}
};
} // single-line command with params
for (var i = 0; i < bits.length - 1; i++) {
var bit = bits[i];
var bobs = bit.split(' ');
var nextParam = '';
for (var _i = bobs.length - 1; _i >= 0; _i--) {
nextParam = (bobs.pop() + ' ' + nextParam).trim();
const bits = firstLine.split(': ');
const commandName = bits.shift();
assertValidCommandName(commandName);
const params = {};
const paramNames = paramsByCommandName[commandName];
let param = bits.shift();
!param ? process.env.NODE_ENV !== "production" ? invariant(false, 'No named parameters found') : invariant(false) : void 0;
if (paramNames.hasOwnProperty(nextParam)) {
break;
for (let i = 0; i < bits.length - 1; i++) {
const bit = bits[i];
const bobs = bit.split(' ');
let nextParam = '';
for (let i = bobs.length - 1; i >= 0; i--) {
nextParam = (bobs.pop() + ' ' + nextParam).trim();
if (paramNames.hasOwnProperty(nextParam)) {
break;
}
}
!(bobs.length > 0) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Command malformed / paramName not recognised: `%s`', bit) : invariant(false) : void 0;
!paramNames.hasOwnProperty(param) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported param: `%o`', param) : invariant(false) : void 0;
const value = bobs.join(' ');
const {
paramName,
paramType
} = paramNames[param];
const formatter = stringToValueFns[paramType];
params[paramName] = formatter(value);
param = nextParam;
}
!(bobs.length > 0) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Command malformed / paramName not recognised: `%s`', bit) : invariant(false) : void 0;
params[param] = bobs.join(' ');
param = nextParam;
!paramNames.hasOwnProperty(param) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported param: `%o`', param) : invariant(false) : void 0;
const value = bits[bits.length - 1];
const {
paramName,
paramType
} = paramNames[param];
const formatter = stringToValueFns[paramType];
params[paramName] = formatter(value);
return {
raw: lines.join(CRLF),
name: commandName,
parameters: params
};
}
params[param] = bits[bits.length - 1];
return {
raw: lines.join(CRLF),
name: msg,
parameters: params
};
} else {
var headerMatch = lines[0].match(/(.+?)(:|)$/im);
!firstLine.endsWith(':') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Expected a line ending in semicolon, received `%o`', firstLine) : invariant(false) : void 0; // remove the semicolon at the end of the command
if (!headerMatch) {
this.logger.error({
header: lines[0]
}, 'failed to parse header');
return null;
}
const commandName = firstLine.slice(0, -1);
assertValidCommandName(commandName);
const paramNames = paramsByCommandName[commandName];
const params = {};
var _msg = headerMatch[1];
var _params = {};
for (var _i2 = 1; _i2 < lines.length; _i2++) {
var lineMatch = lines[_i2].match(/^(.*?): (.*)$/im);
if (!lineMatch) {
this.logger.error({
line: lines[_i2]
}, 'failed to parse line');
continue;
}
_params[lineMatch[1]] = lineMatch[2];
for (const line of lines) {
const lineMatch = line.match(/^(.*?): (.*)$/im);
!lineMatch ? process.env.NODE_ENV !== "production" ? invariant(false, 'Failed to parse line: `%o`', line) : invariant(false) : void 0;
const param = lineMatch[1];
const value = lineMatch[2];
!paramNames.hasOwnProperty(param) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported param: `%o`', param) : invariant(false) : void 0;
const {
paramName,
paramType
} = paramNames[param];
const formatter = stringToValueFns[paramType];
params[paramName] = formatter(value);
}
var res = {
const res = {
raw: lines.join(CRLF),
name: _msg,
parameters: _params
name: commandName,
parameters: params
};
return res;
} catch (err) {
if (err instanceof FormattedError) {
this.logger.error(err.template, ...err.args);
} else {
this.logger.error({
err: err + ''
}, 'parseResponse error');
}
return null;
}
};
}
return MultilineParser;
}();
}
var sanitiseMessage = function sanitiseMessage(input) {
const sanitiseMessage = input => {
return input.replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/:/g, '');

@@ -657,3 +854,3 @@ };

var messageForCode = function messageForCode(code, params) {
const messageForCode = (code, params) => {
if (typeof params === 'string') {

@@ -663,3 +860,3 @@ return code + ' ' + sanitiseMessage(params) + CRLF;

var firstLine = code + " " + responseNamesByCode[code]; // bail if no params
const firstLine = `${code} ${responseNamesByCode[code]}`; // bail if no params

@@ -671,6 +868,3 @@ if (!params) {

var paramEntries = Object.entries(params).filter(function (_ref) {
var value = _ref[1];
return value != null;
}); // bail if no params after filtering
const paramEntries = Object.entries(params).filter(([, value]) => value != null); // bail if no params after filtering

@@ -682,6 +876,4 @@ if (paramEntries.length === 0) {

return paramEntries.reduce(function (prev, _ref2) {
var key = _ref2[0],
value = _ref2[1];
var valueString;
return paramEntries.reduce((prev, [key, value]) => {
let valueString;

@@ -694,8 +886,10 @@ if (typeof value === 'string') {

valueString = value.toString();
} else if (value instanceof Timecode) {
valueString = value.toString();
} else {
process.env.NODE_ENV !== "production" ? invariant(false, 'Unhandled value type: `%s`', typeof value) : invariant(false) ;
process.env.NODE_ENV !== "production" ? invariant(false, 'Unhandled value type for key `%s`: `%s`', key, Array.isArray(value) ? 'array' : typeof value) : invariant(false) ;
} // convert camelCase keys to space-separated words
var formattedKey = camelcaseToSpaceCase(key);
const formattedKey = camelcaseToSpaceCase(key);
return prev + formattedKey + ': ' + valueString + CRLF;

@@ -705,80 +899,64 @@ }, firstLine + ':' + CRLF) + CRLF;

var HyperDeckSocket = /*#__PURE__*/function (_EventEmitter) {
_inheritsLoose(HyperDeckSocket, _EventEmitter);
function HyperDeckSocket(socket, logger, receivedCommand) {
var _this;
_this = _EventEmitter.call(this) || this;
_this.socket = socket;
_this.logger = logger;
_this.receivedCommand = receivedCommand;
_this.lastReceivedMS = -1;
_this.watchdogTimer = null;
_this.notifySettings = {
class HyperDeckSocket extends EventEmitter {
constructor(socket, logger, receivedCommand) {
super();
this.socket = socket;
this.logger = logger;
this.receivedCommand = receivedCommand;
this.lastReceivedMS = -1;
this.watchdogTimer = null;
this.notifySettings = {
configuration: false,
displayTimecode: false,
droppedFrames: false,
dynamicRange: false,
playrange: false,
remote: false,
slot: false,
transport: false,
remote: false,
configuration: false,
'dropped frames': false
timelinePosition: false,
transport: false
};
_this.parser = new MultilineParser(logger);
_this.socket.setEncoding('utf-8');
_this.socket.on('data', function (data) {
_this.onMessage(data);
this.parser = new MultilineParser(logger);
this.socket.setEncoding('utf-8');
this.socket.on('data', data => {
this.onMessage(data);
});
_this.socket.on('error', function (err) {
this.socket.on('error', err => {
logger.info({
err: err
err
}, 'error');
_this.socket.destroy();
_this.emit('disconnected');
this.socket.destroy();
this.emit('disconnected');
logger.info('manually disconnected');
});
_this.sendResponse(AsynchronousCode.ConnectionInfo, {
this.sendResponse(AsynchronousCode.ConnectionInfo, {
'protocol version': '1.11',
model: 'NodeJS HyperDeck Server Library'
});
return _this;
}
var _proto = HyperDeckSocket.prototype;
_proto.onMessage = function onMessage(data) {
var _this2 = this;
onMessage(data) {
this.logger.info({
data: data
}, '<-- received message from client');
data
}, '<--- received message from client');
this.lastReceivedMS = Date.now();
var cmds = this.parser.receivedString(data);
const cmds = this.parser.receivedString(data);
this.logger.info({
cmds: cmds
cmds
}, 'parsed commands');
var _loop = function _loop() {
var cmd = _step.value;
for (const cmd of cmds) {
// special cases
if (cmd.name === 'watchdog') {
if (_this2.watchdogTimer) clearInterval(_this2.watchdogTimer);
var watchdogCmd = cmd;
if (this.watchdogTimer) clearInterval(this.watchdogTimer);
const watchdogCmd = cmd;
if (watchdogCmd.parameters.period) {
_this2.watchdogTimer = setInterval(function () {
if (Date.now() - _this2.lastReceivedMS > Number(watchdogCmd.parameters.period)) {
_this2.socket.destroy();
this.watchdogTimer = setInterval(() => {
if (Date.now() - this.lastReceivedMS > Number(watchdogCmd.parameters.period)) {
this.socket.destroy();
this.emit('disconnected');
_this2.emit('disconnected');
if (_this2.watchdogTimer) {
clearInterval(_this2.watchdogTimer);
if (this.watchdogTimer) {
clearInterval(this.watchdogTimer);
}

@@ -789,72 +967,65 @@ }

} else if (cmd.name === 'notify') {
var notifyCmd = cmd;
const notifyCmd = cmd;
if (Object.keys(notifyCmd.parameters).length > 0) {
for (var _i = 0, _Object$keys = Object.keys(notifyCmd.parameters); _i < _Object$keys.length; _i++) {
var param = _Object$keys[_i];
if (_this2.notifySettings[param] !== undefined) {
_this2.notifySettings[param] = notifyCmd.parameters[param] === 'true';
for (const param of Object.keys(notifyCmd.parameters)) {
if (this.notifySettings[param] !== undefined) {
this.notifySettings[param] = notifyCmd.parameters[param] === true;
}
}
} else {
var settings = {};
const settings = {};
for (var _i2 = 0, _Object$keys2 = Object.keys(_this2.notifySettings); _i2 < _Object$keys2.length; _i2++) {
var key = _Object$keys2[_i2];
settings[key] = _this2.notifySettings[key] ? 'true' : 'false';
for (const key of Object.keys(this.notifySettings)) {
settings[key] = this.notifySettings[key] ? 'true' : 'false';
}
_this2.sendResponse(SynchronousCode.Notify, settings, cmd);
return "continue";
this.sendResponse(SynchronousCode.Notify, settings, cmd);
continue;
}
}
_this2.receivedCommand(cmd).then(function (codeOrObj) {
this.receivedCommand(cmd).then(codeOrObj => {
if (typeof codeOrObj === 'object') {
var _code = codeOrObj.code;
var paramsOrMessage = 'params' in codeOrObj && codeOrObj.params || 'message' in codeOrObj && codeOrObj.message || undefined;
return _this2.sendResponse(_code, paramsOrMessage, cmd);
const code = codeOrObj.code;
const paramsOrMessage = 'params' in codeOrObj && codeOrObj.params || 'message' in codeOrObj && codeOrObj.message || undefined;
return this.sendResponse(code, paramsOrMessage, cmd);
}
var code = codeOrObj;
const code = codeOrObj;
if (typeof code === 'number' && (ErrorCode[code] || SynchronousCode[code] || AsynchronousCode[code])) {
return _this2.sendResponse(code, undefined, cmd);
return this.sendResponse(code, undefined, cmd);
}
_this2.logger.error({
cmd: cmd,
codeOrObj: codeOrObj
this.logger.error({
cmd,
codeOrObj
}, 'codeOrObj was neither a ResponseCode nor a response object');
_this2.sendResponse(ErrorCode.InternalError, undefined, cmd);
this.sendResponse(ErrorCode.InternalError, undefined, cmd);
}, // not implemented by client code:
function () {
return _this2.sendResponse(ErrorCode.Unsupported, undefined, cmd);
});
};
() => this.sendResponse(ErrorCode.Unsupported, undefined, cmd));
}
}
for (var _iterator = _createForOfIteratorHelperLoose(cmds), _step; !(_step = _iterator()).done;) {
var _ret = _loop();
if (_ret === "continue") continue;
sendResponse(code, paramsOrMessage, cmd) {
try {
const responseText = messageForCode(code, paramsOrMessage);
const method = ErrorCode[code] ? 'error' : 'info';
this.logger[method]({
responseText,
cmd
}, '---> send response to client');
this.socket.write(responseText);
} catch (err) {
this.logger.error({
cmd
}, '-x-> Error sending response: %s', err);
}
};
}
_proto.sendResponse = function sendResponse(code, paramsOrMessage, cmd) {
var responseText = messageForCode(code, paramsOrMessage);
var method = ErrorCode[code] ? 'error' : 'info';
this.logger[method]({
responseText: responseText,
cmd: cmd
}, '--> send response to client');
this.socket.write(responseText);
};
_proto.notify = function notify(type, params) {
notify(type, params) {
this.logger.info({
type: type,
params: params
type,
params
}, 'notify');

@@ -872,21 +1043,26 @@

this.logger.error({
type: type,
params: params
type,
params
}, 'unhandled notify type');
}
};
}
return HyperDeckSocket;
}(EventEmitter);
}
var formatClipsGetResponse = function formatClipsGetResponse(res) {
var clipsCount = res.clips.length;
var response = {
clipsCount: clipsCount
const formatClipsGetResponse = res => {
if (!res.clips) {
return {
clipsCount: 0
};
}
const clipsCount = res.clips.length;
const response = {
clipsCount
};
for (var idx = 0; idx < clipsCount; idx++) {
var clip = res.clips[idx];
var clipKey = (idx + 1).toString();
response[clipKey] = clip.name + " " + clip.startT + " " + clip.duration;
for (let idx = 0; idx < clipsCount; idx++) {
const clip = res.clips[idx];
const clipKey = (idx + 1).toString();
response[clipKey] = `${clip.name} ${clip.startT} ${clip.duration}`;
}

@@ -897,499 +1073,135 @@

var UnimplementedError = /*#__PURE__*/function (_Error) {
_inheritsLoose(UnimplementedError, _Error);
class HyperDeckServer {
constructor(ip, logger = pino()) {
const _this = this;
function UnimplementedError() {
return _Error.apply(this, arguments) || this;
}
this.sockets = {};
this.commandHandlers = {};
return UnimplementedError;
}( /*#__PURE__*/_wrapNativeSuper(Error));
this.on = (key, handler) => {
!paramsByCommandName.hasOwnProperty(key) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Invalid key: `%s`', key) : invariant(false) : void 0;
!!this.commandHandlers.hasOwnProperty(key) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Handler already registered for `%s`', key) : invariant(false) : void 0;
this.commandHandlers[key] = handler;
};
var noop = function noop() {
try {
throw new UnimplementedError();
} catch (e) {
return Promise.reject(e);
}
};
var HyperDeckServer = /*#__PURE__*/function () {
function HyperDeckServer(ip, logger) {
var _this2 = this;
var _this = this;
if (logger === void 0) {
logger = pino();
}
this.sockets = {};
this.onDeviceInfo = noop;
this.onDiskList = noop;
this.onPreview = noop;
this.onPlay = noop;
this.onPlayrangeSet = noop;
this.onPlayrangeClear = noop;
this.onRecord = noop;
this.onStop = noop;
this.onClipsCount = noop;
this.onClipsGet = noop;
this.onClipsAdd = noop;
this.onClipsClear = noop;
this.onTransportInfo = noop;
this.onSlotInfo = noop;
this.onSlotSelect = noop;
this.onGoTo = noop;
this.onJog = noop;
this.onShuttle = noop;
this.onConfiguration = noop;
this.onUptime = noop;
this.onFormat = noop;
this.onIdentify = noop;
this.onWatchdog = noop;
this.receivedCommand = function (cmd) {
try {
// TODO(meyer) more sophisticated debouncing
return Promise.resolve(new Promise(function (resolve) {
return setTimeout(function () {
return resolve();
}, 200);
})).then(function () {
var _exit = false;
return Promise.resolve(new Promise(resolve => setTimeout(() => resolve(), 200))).then(function () {
_this.logger.info({
cmd: cmd
}, '<-- ' + cmd.name);
cmd
}, 'receivedCommand %s', cmd.name);
return _catch(function () {
function _temp44(_result) {
var _exit2 = false;
if (_exit) return _result;
if (cmd.name === 'remote') {
return {
code: SynchronousCode.Remote,
params: {
enabled: true,
override: false
}
};
} // implemented in socket.ts
function _temp42(_result2) {
var _exit3 = false;
if (_exit2) return _result2;
function _temp40(_result3) {
var _exit4 = false;
if (_exit3) return _result3;
if (cmd.name === 'notify' || cmd.name === 'watchdog' || cmd.name === 'ping') {
return SynchronousCode.OK;
}
function _temp38(_result4) {
var _exit5 = false;
if (_exit4) return _result4;
const handler = _this.commandHandlers[cmd.name];
function _temp36(_result5) {
var _exit6 = false;
if (_exit5) return _result5;
if (!handler) {
_this.logger.error({
cmd
}, 'unimplemented');
function _temp34(_result6) {
var _exit7 = false;
if (_exit6) return _result6;
return ErrorCode.Unsupported;
}
function _temp32(_result7) {
var _exit8 = false;
if (_exit7) return _result7;
return Promise.resolve(handler(cmd)).then(function (response) {
const result = {
name: cmd.name,
response
};
function _temp30(_result8) {
var _exit9 = false;
if (_exit8) return _result8;
if (result.name === 'clips add' || result.name === 'clips clear' || result.name === 'goto' || result.name === 'identify' || result.name === 'jog' || result.name === 'play' || result.name === 'playrange clear' || result.name === 'playrange set' || result.name === 'preview' || result.name === 'record' || result.name === 'shuttle' || result.name === 'slot select' || result.name === 'stop') {
return SynchronousCode.OK;
}
function _temp28(_result9) {
var _exit10 = false;
if (_exit9) return _result9;
if (result.name === 'device info') {
return {
code: SynchronousCode.DeviceInfo,
params: result.response
};
}
function _temp26(_result10) {
var _exit11 = false;
if (_exit10) return _result10;
if (result.name === 'disk list') {
return {
code: SynchronousCode.DiskList,
params: result.response
};
}
function _temp24(_result11) {
var _exit12 = false;
if (_exit11) return _result11;
if (result.name === 'clips count') {
return {
code: SynchronousCode.ClipsCount,
params: result.response
};
}
function _temp22(_result12) {
var _exit13 = false;
if (_exit12) return _result12;
if (result.name === 'clips get') {
return {
code: SynchronousCode.ClipsInfo,
params: formatClipsGetResponse(result.response)
};
}
function _temp20(_result13) {
var _exit14 = false;
if (_exit13) return _result13;
if (result.name === 'transport info') {
return {
code: SynchronousCode.TransportInfo,
params: result.response
};
}
function _temp18(_result14) {
var _exit15 = false;
if (_exit14) return _result14;
if (result.name === 'slot info') {
return {
code: SynchronousCode.SlotInfo,
params: result.response
};
}
function _temp16(_result15) {
var _exit16 = false;
if (_exit15) return _result15;
function _temp14(_result16) {
var _exit17 = false;
if (_exit16) return _result16;
function _temp12(_result17) {
var _exit18 = false;
if (_exit17) return _result17;
function _temp10(_result18) {
var _exit19 = false;
if (_exit18) return _result18;
function _temp8(_result19) {
var _exit20 = false;
if (_exit19) return _result19;
function _temp6(_result20) {
var _exit21 = false;
if (_exit20) return _result20;
function _temp4(_result21) {
var _exit22 = false;
if (_exit21) return _result21;
function _temp2(_result22) {
if (_exit22) return _result22;
if (cmd.name === 'watchdog') {
// implemented in socket.ts
return SynchronousCode.OK;
}
if (cmd.name === 'ping') {
// implemented in socket.ts
return SynchronousCode.OK;
}
!false ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unhandled command name: `%s`', cmd.name) : invariant(false) : void 0;
}
var _temp = function () {
if (cmd.name === 'identify') {
return Promise.resolve(_this.onIdentify(cmd)).then(function () {
_exit22 = true;
return SynchronousCode.OK;
});
}
}();
return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
}
var _temp3 = function () {
if (cmd.name === 'format') {
return Promise.resolve(_this.onFormat(cmd)).then(function (res) {
if (res) {
_exit21 = true;
return {
code: SynchronousCode.FormatReady,
params: res
};
}
_exit21 = true;
return SynchronousCode.OK;
});
}
}();
return _temp3 && _temp3.then ? _temp3.then(_temp4) : _temp4(_temp3);
}
var _temp5 = function () {
if (cmd.name === 'uptime') {
return Promise.resolve(_this.onUptime(cmd)).then(function (res) {
_exit20 = true;
return {
code: SynchronousCode.Uptime,
params: res
};
});
}
}();
return _temp5 && _temp5.then ? _temp5.then(_temp6) : _temp6(_temp5);
}
if (cmd.name === 'remote') {
return {
code: SynchronousCode.Remote,
params: {
enabled: true,
override: false
}
};
}
var _temp7 = function () {
if (cmd.name === 'configuration') {
return Promise.resolve(_this.onConfiguration(cmd)).then(function (res) {
if (res) {
_exit19 = true;
return {
code: SynchronousCode.Configuration,
params: res
};
}
_exit19 = true;
return SynchronousCode.OK;
});
}
}();
return _temp7 && _temp7.then ? _temp7.then(_temp8) : _temp8(_temp7);
}
var _temp9 = function () {
if (cmd.name === 'shuttle') {
return Promise.resolve(_this.onShuttle(cmd)).then(function () {
_exit18 = true;
return SynchronousCode.OK;
});
}
}();
return _temp9 && _temp9.then ? _temp9.then(_temp10) : _temp10(_temp9);
}
var _temp11 = function () {
if (cmd.name === 'jog') {
return Promise.resolve(_this.onJog(cmd)).then(function () {
_exit17 = true;
return SynchronousCode.OK;
});
}
}();
return _temp11 && _temp11.then ? _temp11.then(_temp12) : _temp12(_temp11);
}
if (cmd.name === 'notify') {
// implemented in socket.ts
return SynchronousCode.OK;
}
var _temp13 = function () {
if (cmd.name === 'go to') {
return Promise.resolve(_this.onGoTo(cmd)).then(function () {
_exit16 = true;
return SynchronousCode.OK;
});
}
}();
return _temp13 && _temp13.then ? _temp13.then(_temp14) : _temp14(_temp13);
}
var _temp15 = function () {
if (cmd.name === 'slot select') {
return Promise.resolve(_this.onSlotSelect(cmd)).then(function () {
_exit15 = true;
return SynchronousCode.OK;
});
}
}();
return _temp15 && _temp15.then ? _temp15.then(_temp16) : _temp16(_temp15);
}
var _temp17 = function () {
if (cmd.name === 'slot info') {
return Promise.resolve(_this.onSlotInfo(cmd)).then(function (res) {
_exit14 = true;
return {
code: SynchronousCode.SlotInfo,
params: res
};
});
}
}();
return _temp17 && _temp17.then ? _temp17.then(_temp18) : _temp18(_temp17);
}
var _temp19 = function () {
if (cmd.name === 'transport info') {
return Promise.resolve(_this.onTransportInfo(cmd)).then(function (res) {
_exit13 = true;
return {
code: SynchronousCode.TransportInfo,
params: res
};
});
}
}();
return _temp19 && _temp19.then ? _temp19.then(_temp20) : _temp20(_temp19);
}
var _temp21 = function () {
if (cmd.name === 'clips clear') {
return Promise.resolve(_this.onClipsClear(cmd)).then(function () {
_exit12 = true;
return SynchronousCode.OK;
});
}
}();
return _temp21 && _temp21.then ? _temp21.then(_temp22) : _temp22(_temp21);
}
var _temp23 = function () {
if (cmd.name === 'clips add') {
return Promise.resolve(_this.onClipsAdd(cmd)).then(function () {
_exit11 = true;
return SynchronousCode.OK;
});
}
}();
return _temp23 && _temp23.then ? _temp23.then(_temp24) : _temp24(_temp23);
}
var _temp25 = function () {
if (cmd.name === 'clips get') {
return Promise.resolve(_this.onClipsGet(cmd).then(formatClipsGetResponse)).then(function (res) {
_exit10 = true;
return {
code: SynchronousCode.ClipsInfo,
params: res
};
});
}
}();
return _temp25 && _temp25.then ? _temp25.then(_temp26) : _temp26(_temp25);
}
var _temp27 = function () {
if (cmd.name === 'clips count') {
return Promise.resolve(_this.onClipsCount(cmd)).then(function (res) {
_exit9 = true;
return {
code: SynchronousCode.ClipsCount,
params: res
};
});
}
}();
return _temp27 && _temp27.then ? _temp27.then(_temp28) : _temp28(_temp27);
}
var _temp29 = function () {
if (cmd.name === 'stop') {
return Promise.resolve(_this.onStop(cmd)).then(function () {
_exit8 = true;
return SynchronousCode.OK;
});
}
}();
return _temp29 && _temp29.then ? _temp29.then(_temp30) : _temp30(_temp29);
}
var _temp31 = function () {
if (cmd.name === 'record') {
return Promise.resolve(_this.onRecord(cmd)).then(function () {
_exit7 = true;
return SynchronousCode.OK;
});
}
}();
return _temp31 && _temp31.then ? _temp31.then(_temp32) : _temp32(_temp31);
}
var _temp33 = function () {
if (cmd.name === 'playrange clear') {
return Promise.resolve(_this.onPlayrangeClear(cmd)).then(function () {
_exit6 = true;
return SynchronousCode.OK;
});
}
}();
return _temp33 && _temp33.then ? _temp33.then(_temp34) : _temp34(_temp33);
}
var _temp35 = function () {
if (cmd.name === 'playrange set') {
return Promise.resolve(_this.onPlayrangeSet(cmd)).then(function () {
_exit5 = true;
return SynchronousCode.OK;
});
}
}();
return _temp35 && _temp35.then ? _temp35.then(_temp36) : _temp36(_temp35);
}
var _temp37 = function () {
if (cmd.name === 'play') {
return Promise.resolve(_this.onPlay(cmd)).then(function () {
_exit4 = true;
return SynchronousCode.OK;
});
}
}();
return _temp37 && _temp37.then ? _temp37.then(_temp38) : _temp38(_temp37);
}
var _temp39 = function () {
if (cmd.name === 'preview') {
return Promise.resolve(_this.onPreview(cmd)).then(function () {
_exit3 = true;
return SynchronousCode.OK;
});
}
}();
return _temp39 && _temp39.then ? _temp39.then(_temp40) : _temp40(_temp39);
if (result.name === 'configuration') {
if (result) {
return {
code: SynchronousCode.Configuration,
params: result.response
};
}
var _temp41 = function () {
if (cmd.name === 'disk list') {
return Promise.resolve(_this.onDiskList(cmd)).then(function (res) {
_exit2 = true;
return {
code: SynchronousCode.DiskList,
params: res
};
});
}
}();
return SynchronousCode.OK;
}
return _temp41 && _temp41.then ? _temp41.then(_temp42) : _temp42(_temp41);
if (result.name === 'uptime') {
return {
code: SynchronousCode.Uptime,
params: result.response
};
}
var _temp43 = function () {
if (cmd.name === 'device info') {
return Promise.resolve(_this.onDeviceInfo(cmd)).then(function (res) {
_exit = true;
return {
code: SynchronousCode.DeviceInfo,
params: res
};
});
if (result.name === 'format') {
if (result) {
return {
code: SynchronousCode.FormatReady,
params: result.response
};
}
}();
return _temp43 && _temp43.then ? _temp43.then(_temp44) : _temp44(_temp43);
}, function (err) {
if (err instanceof UnimplementedError) {
_this.logger.error({
cmd: cmd
}, 'unimplemented');
return ErrorCode.Unsupported;
return SynchronousCode.OK;
}
_this.logger.error({
cmd: cmd,
err: err.message
}, 'unhandled command name');
cmd,
res: result
}, 'Unsupported command');
return ErrorCode.InternalError;
return ErrorCode.Unsupported;
});

@@ -1405,29 +1217,17 @@ });

});
this.server = createServer(function (socket) {
_this2.logger.info('connection');
var socketId = Math.random().toString(35).substr(-6);
var socketLogger = _this2.logger.child({
this.server = createServer(socket => {
this.logger.info('connection');
const socketId = Math.random().toString(35).substr(-6);
const socketLogger = this.logger.child({
name: 'HyperDeck socket ' + socketId
});
_this2.sockets[socketId] = new HyperDeckSocket(socket, socketLogger, function (cmd) {
return _this2.receivedCommand(cmd);
});
_this2.sockets[socketId].on('disconnected', function () {
this.sockets[socketId] = new HyperDeckSocket(socket, socketLogger, this.receivedCommand);
this.sockets[socketId].on('disconnected', () => {
socketLogger.info('disconnected');
delete _this2.sockets[socketId];
delete this.sockets[socketId];
});
});
this.server.on('listening', function () {
return _this2.logger.info('listening');
});
this.server.on('close', function () {
return _this2.logger.info('connection closed');
});
this.server.on('error', function (err) {
return _this2.logger.error('server error:', err);
});
this.server.on('listening', () => this.logger.info('listening'));
this.server.on('close', () => this.logger.info('connection closed'));
this.server.on('error', err => this.logger.error('server error:', err));
this.server.maxConnections = 1;

@@ -1437,33 +1237,23 @@ this.server.listen(9993, ip);

var _proto = HyperDeckServer.prototype;
_proto.close = function close() {
close() {
this.server.unref();
};
}
_proto.notifySlot = function notifySlot(params) {
notifySlot(params) {
this.notify('slot', params);
};
}
_proto.notifyTransport = function notifyTransport(params) {
notifyTransport(params) {
this.notify('transport', params);
};
}
_proto.notify = function notify(type, params) {
for (var _i = 0, _Object$keys = Object.keys(this.sockets); _i < _Object$keys.length; _i++) {
var id = _Object$keys[_i];
notify(type, params) {
for (const id of Object.keys(this.sockets)) {
this.sockets[id].notify(type, params);
}
};
}
return HyperDeckServer;
}();
}
var ResponseInterface = {
__proto__: null
};
export { HyperDeckServer, ResponseInterface, Timecode };
export { HyperDeckServer, Timecode };
//# sourceMappingURL=hyperdeck-emulator.esm.js.map

@@ -1,6 +0,3 @@

import { DeserializedCommand } from './types';
import * as ResponseInterface from './types/ResponseInterface';
import * as DeserializedCommands from './types/DeserializedCommands';
import { CommandHandler } from './types';
import pino from 'pino';
declare type Handler<C extends DeserializedCommand, R extends any> = (command: C) => Promise<R>;
export declare class HyperDeckServer {

@@ -10,25 +7,2 @@ private logger;

private server;
onDeviceInfo: Handler<DeserializedCommand, ResponseInterface.DeviceInfo>;
onDiskList: Handler<DeserializedCommand, ResponseInterface.DiskList>;
onPreview: Handler<DeserializedCommands.PreviewCommand, void>;
onPlay: Handler<DeserializedCommands.PlayCommand, void>;
onPlayrangeSet: Handler<DeserializedCommands.PlayrangeSetCommand, void>;
onPlayrangeClear: Handler<DeserializedCommand, void>;
onRecord: Handler<DeserializedCommands.RecordCommand, void>;
onStop: Handler<DeserializedCommand, void>;
onClipsCount: Handler<DeserializedCommand, ResponseInterface.ClipsCount>;
onClipsGet: Handler<DeserializedCommands.ClipsGetCommand, ResponseInterface.ClipsGet>;
onClipsAdd: Handler<DeserializedCommands.ClipsAddCommand, void>;
onClipsClear: Handler<DeserializedCommand, void>;
onTransportInfo: Handler<DeserializedCommand, ResponseInterface.TransportInfo>;
onSlotInfo: Handler<DeserializedCommands.SlotInfoCommand, ResponseInterface.SlotInfo>;
onSlotSelect: Handler<DeserializedCommands.SlotSelectCommand, void>;
onGoTo: Handler<DeserializedCommands.GoToCommand, void>;
onJog: Handler<DeserializedCommands.JogCommand, void>;
onShuttle: Handler<DeserializedCommands.ShuttleCommand, void>;
onConfiguration: Handler<DeserializedCommands.ConfigurationCommand, ResponseInterface.Configuration>;
onUptime: Handler<DeserializedCommand, ResponseInterface.Uptime>;
onFormat: Handler<DeserializedCommands.FormatCommand, ResponseInterface.Format>;
onIdentify: Handler<DeserializedCommands.IdentifyCommand, void>;
onWatchdog: Handler<DeserializedCommands.WatchdogCommand, void>;
constructor(ip?: string, logger?: pino.Logger);

@@ -39,5 +13,6 @@ close(): void;

private notify;
private commandHandlers;
on: <T extends "goto" | "help" | "commands" | "device info" | "disk list" | "quit" | "preview" | "play" | "playrange" | "playrange set" | "playrange clear" | "play on startup" | "play option" | "record" | "record spill" | "stop" | "clips count" | "clips get" | "clips add" | "clips remove" | "clips clear" | "transport info" | "slot info" | "slot select" | "slot unblock" | "dynamic range" | "jog" | "shuttle" | "configuration" | "uptime" | "format" | "identify">(key: T, handler: CommandHandler<T>) => void;
private receivedCommand;
}
export {};
//# sourceMappingURL=HyperDeckServer.d.ts.map
/// <reference types="node" />
import type { Socket } from 'net';
import { EventEmitter } from 'events';
import { DeserializedCommand, ErrorCode, NotifyType, ResponseCode } from './types';
import { DeserializedCommand, ErrorCode, NotifyType, ResponseCode, TypesByStringKey } from './types';
import type { Logger } from 'pino';

@@ -25,3 +25,3 @@ interface ResponseWithMessage {

private onMessage;
sendResponse(code: ResponseCode, paramsOrMessage?: Record<string, unknown> | string, cmd?: DeserializedCommand): void;
sendResponse(code: ResponseCode, paramsOrMessage?: Record<string, TypesByStringKey[keyof TypesByStringKey]> | string, cmd?: DeserializedCommand): void;
notify(type: NotifyType, params: Record<string, string>): void;

@@ -28,0 +28,0 @@ }

@@ -1,4 +0,3 @@

export * from './HyperDeckServer';
export * from './Timecode';
export * as ResponseInterface from './types/ResponseInterface';
export { HyperDeckServer } from './HyperDeckServer';
export { Timecode } from './Timecode';
//# sourceMappingURL=index.d.ts.map

@@ -0,2 +1,7 @@

export declare class FormattedError extends Error {
constructor(template: string, ...args: any[]);
template: string;
args: any[];
}
export declare function invariant(condition: any, message: string, ...args: any[]): asserts condition;
//# sourceMappingURL=invariant.d.ts.map
import { ResponseCode } from './types';
import { Timecode } from './Timecode';
/** For a given code, generate the response message that will be sent to the ATEM */
export declare const messageForCode: (code: ResponseCode, params?: string | Record<string, unknown> | undefined) => string;
export declare const messageForCode: (code: ResponseCode, params?: string | Record<string, string | number | boolean | Timecode | import("./types").ClipV1[]> | undefined) => string;
//# sourceMappingURL=messageForCode.d.ts.map

@@ -1,6 +0,6 @@

import type { DeserializedCommand } from './types';
import { DeserializedCommand } from './types';
import type { Logger } from 'pino';
export declare class MultilineParser {
private linesQueue;
private logger;
private linesQueue;
constructor(logger: Logger);

@@ -7,0 +7,0 @@ receivedString(data: string): DeserializedCommand[];

import { Timecode } from './Timecode';
import type { CommandName, CommandParamsByCommandName, CommandResponsesByCommandName } from './api';
export interface NotificationConfig {

@@ -8,7 +9,18 @@ transport: boolean;

}
export interface DeserializedCommand {
raw: string;
name: string;
parameters: Record<string, string | undefined>;
}
export declare type DeserializedCommandsByName = {
[K in CommandName]: {
raw: string;
name: K;
parameters: CommandParamsByCommandName[K];
};
};
export declare type DeserializedCommand = DeserializedCommandsByName[CommandName];
export declare type CommandHandler<T extends CommandName> = (cmd: DeserializedCommandsByName[T]['parameters']) => Promise<CommandResponsesByCommandName[T]>;
export declare type ResponsesByCommandName = {
[K in CommandName]: {
name: K;
response: CommandResponsesByCommandName[K];
};
};
export declare type CommandResponse = ResponsesByCommandName[CommandName];
export declare type ResponseCode = ErrorCode | SynchronousCode | AsynchronousCode;

@@ -91,2 +103,15 @@ export declare enum ErrorCode {

};
export interface ClipV1 {
name: string;
startT: Timecode;
duration: Timecode;
}
export declare const isClipV1: (value: any) => value is ClipV1;
export interface ClipV2 {
startT: Timecode;
duration: number;
inT: Timecode;
outT: Timecode;
name: string;
}
export declare type VideoFormat = keyof typeof videoFormats;

@@ -105,3 +130,3 @@ export declare const isVideoFormat: (value: any) => value is "NTSC" | "PAL" | "NTSCp" | "PALp" | "720p50" | "720p5994" | "720p60" | "1080p23976" | "1080p24" | "1080p25" | "1080p2997" | "1080p30" | "1080i50" | "1080i5994" | "1080i60" | "4Kp23976" | "4Kp24" | "4Kp25" | "4Kp2997" | "4Kp30" | "4Kp50" | "4Kp5994" | "4Kp60";

export declare type TransportStatus = keyof typeof transportStatus;
export declare const isTransportStatus: (value: any) => value is "preview" | "stopped" | "play" | "forward" | "rewind" | "jog" | "shuttle" | "record";
export declare const isTransportStatus: (value: any) => value is "preview" | "play" | "record" | "jog" | "shuttle" | "stopped" | "forward" | "rewind";
export declare const stopModes: {

@@ -151,2 +176,5 @@ lastframe: boolean;

export declare type ArgKey = keyof TypesByStringKey;
export declare type ArgsTypes<T extends Record<string, ArgKey>> = {
[K in keyof T]?: TypesByStringKey[T[K]];
};
export interface TypesByStringKey {

@@ -166,6 +194,9 @@ boolean: boolean;

recordtrigger: RecordTrigger;
clips: ClipV1[];
slotstatus: SlotStatus;
transportstatus: TransportStatus;
}
export declare const stringToValueFns: {
[K in keyof TypesByStringKey]: (value: string) => TypesByStringKey[K];
[K in keyof TypesByStringKey]: (value: unknown) => TypesByStringKey[K];
};
//# sourceMappingURL=types.d.ts.map
{
"name": "@meyer/hyperdeck-emulator",
"version": "0.0.4-canary.38.80bd642",
"version": "0.0.4-canary.40.e061397",
"description": "Typescript Node.js library for emulating a Blackmagic Hyperdeck",

@@ -71,2 +71,3 @@ "main": "dist/index.js",

"devDependencies": {
"@babel/preset-env": "^7.10.3",
"@types/jest": "^26.0.3",

@@ -73,0 +74,0 @@ "@types/npm-packlist": "^1.1.1",

@@ -53,82 +53,46 @@ import type { Socket } from 'net';

expect((socket.write as jest.Mock).mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"500 connection info:
protocol version: 1.11
model: NodeJS HyperDeck Server Library
Array [
Array [
"500 connection info:
protocol version: 1.11
model: NodeJS HyperDeck Server Library
",
],
Array [
"108 internal error
",
],
]
`);
",
],
]
`);
expect(logger.getLoggedOutput()).toMatchInlineSnapshot(`
Array [
Object {
"level": 30,
"msg": "connection",
},
Object {
"cmd": undefined,
"level": 30,
"msg": "--> send response to client",
"responseText": "500 connection info:
protocol version: 1.11
model: NodeJS HyperDeck Server Library
Array [
Object {
"level": 30,
"msg": "connection",
},
Object {
"cmd": undefined,
"level": 30,
"msg": "---> send response to client",
"responseText": "500 connection info:
protocol version: 1.11
model: NodeJS HyperDeck Server Library
",
},
Object {
"data": "banana",
"level": 30,
"msg": "<-- received message from client",
},
Object {
"cmds": Array [
Object {
"name": "banana",
"parameters": Object {},
"raw": "banana",
},
],
"level": 30,
"msg": "parsed commands",
},
Object {
"cmd": Object {
"name": "banana",
"parameters": Object {},
"raw": "banana",
},
"level": 30,
"msg": "<-- banana",
},
Object {
"cmd": Object {
"name": "banana",
"parameters": Object {},
"raw": "banana",
},
"err": "Unhandled command name: \`banana\`",
"level": 50,
"msg": "unhandled command name",
},
Object {
"cmd": Object {
"name": "banana",
"parameters": Object {},
"raw": "banana",
},
"level": 50,
"msg": "--> send response to client",
"responseText": "108 internal error
",
},
]
`);
",
},
Object {
"data": "banana",
"level": 30,
"msg": "<--- received message from client",
},
Object {
"level": 50,
"msg": "Invalid command: \`'banana'\`",
},
Object {
"cmds": Array [],
"level": 30,
"msg": "parsed commands",
},
]
`);
});
});

@@ -7,5 +7,5 @@ import { messageForCode } from '../messageForCode';

expect(messageForCode(ErrorCode.ConnectionRejected)).toMatchInlineSnapshot(`
"120 connection rejected
"
`);
"120 connection rejected
"
`);

@@ -18,21 +18,21 @@ expect(

).toMatchInlineSnapshot(`
"500 connection info:
param1: wow
param2: ok
"500 connection info:
param1: wow
param2: ok
"
`);
"
`);
expect(messageForCode(SynchronousCode.OK, 'okie dokie')).toMatchInlineSnapshot(`
"200 okie dokie
"
`);
"200 okie dokie
"
`);
});
it('filters out null and undefined values', () => {
expect(messageForCode(SynchronousCode.OK, { param1: null, param2: undefined }))
expect(messageForCode(SynchronousCode.OK, { param1: null, param2: undefined } as any))
.toMatchInlineSnapshot(`
"200 ok
"
`);
"200 ok
"
`);
});

@@ -43,9 +43,9 @@

.toMatchInlineSnapshot(`
"200 ok:
param1: 1234
param2: true
param3: false
"200 ok:
param1: 1234
param2: true
param3: false
"
`);
"
`);
});

@@ -55,5 +55,5 @@

expect(() =>
messageForCode(SynchronousCode.OK, { param1: { hmmm: true } })
).toThrowErrorMatchingInlineSnapshot(`"Unhandled value type: \`object\`"`);
messageForCode(SynchronousCode.OK, { param1: { hmmm: true } as any })
).toThrowErrorMatchingInlineSnapshot(`"Unhandled value type for key \`param1\`: \`object\`"`);
});
});

@@ -70,6 +70,2 @@ import packlist = require('npm-packlist');

- dist/types.d.ts.map
- dist/types/DeserializedCommands.d.ts
- dist/types/DeserializedCommands.d.ts.map
- dist/types/ResponseInterface.d.ts
- dist/types/ResponseInterface.d.ts.map
- dist/utils.d.ts

@@ -93,4 +89,2 @@ - dist/utils.d.ts.map

- src/types.ts
- src/types/DeserializedCommands.ts
- src/types/ResponseInterface.ts
- src/utils.ts

@@ -97,0 +91,0 @@ "

@@ -16,10 +16,10 @@ import { MultilineParser } from '../MultilineParser';

expect(parser.parse('play')).toMatchInlineSnapshot(`
Array [
Object {
"name": "play",
"parameters": Object {},
"raw": "play",
},
]
`);
Array [
Object {
"name": "play",
"parameters": Object {},
"raw": "play",
},
]
`);
expect(parser.getLoggedOutput()).toEqual([]);

@@ -31,53 +31,68 @@ });

expect(parser.parse('play' + CRLF + 'stop' + CRLF + 'play')).toMatchInlineSnapshot(`
Array [
Object {
"name": "play",
"parameters": Object {},
"raw": "play",
},
Object {
"name": "stop",
"parameters": Object {},
"raw": "stop",
},
Object {
"name": "play",
"parameters": Object {},
"raw": "play",
},
]
`);
Array [
Object {
"name": "play",
"parameters": Object {},
"raw": "play",
},
Object {
"name": "stop",
"parameters": Object {},
"raw": "stop",
},
Object {
"name": "play",
"parameters": Object {},
"raw": "play",
},
]
`);
expect(parser.getLoggedOutput()).toEqual([]);
});
it('does not validate commands with that do not have params', () => {
it('validates commands with that do not have params', () => {
const parser = getParser();
expect(parser.parse('banana')).toMatchInlineSnapshot(`
Array [
Object {
"name": "banana",
"parameters": Object {},
"raw": "banana",
},
]
`);
expect(parser.getLoggedOutput()).toEqual([]);
expect(parser.parse('banana')).toMatchInlineSnapshot(`Array []`);
expect(parser.getLoggedOutput()).toMatchInlineSnapshot(`
Array [
Object {
"level": 50,
"msg": "Invalid command: \`'banana'\`",
},
]
`);
});
it('throws an error when it receives an invalid command', () => {
expect(() =>
getParser().parse(
const parser = getParser();
expect(
parser.parse(
'notifyyyy: transporttttt: true slottttttttt: true remoteeeeee: true configurationnnn: false'
)
).toThrowErrorMatchingInlineSnapshot(`"Invalid command: \`notifyyyy\`"`);
).toEqual([]);
expect(parser.getLoggedOutput()).toMatchInlineSnapshot(`
Array [
Object {
"level": 50,
"msg": "Invalid command: \`'notifyyyy'\`",
},
]
`);
});
it('throws an error when it receives a valid command with invalid params', () => {
expect(() =>
getParser().parse(
const parser = getParser();
expect(
parser.parse(
'notify: transporttttt: true slottttttttt: true remoteeeeee: true configurationnnn: false'
)
).toThrowErrorMatchingInlineSnapshot(
`"Command malformed / paramName not recognised: \`true slottttttttt\`"`
);
).toMatchInlineSnapshot(`Array []`);
expect(parser.getLoggedOutput()).toMatchInlineSnapshot(`
Array [
Object {
"level": 50,
"msg": "Command malformed / paramName not recognised: \`true slottttttttt\`",
},
]
`);
});

@@ -89,104 +104,101 @@

.toMatchInlineSnapshot(`
Array [
Object {
"name": "notify",
"parameters": Object {
"configuration": "false",
"remote": "true",
"slot": "true",
"transport": "true",
},
"raw": "notify: transport: true slot: true remote: true configuration: false",
},
]
`);
Array [
Object {
"name": "notify",
"parameters": Object {
"configuration": false,
"remote": true,
"slot": true,
"transport": true,
},
"raw": "notify: transport: true slot: true remote: true configuration: false",
},
]
`);
expect(parser.parse('configuration: video input: SDI audio input: XLR')).toMatchInlineSnapshot(`
Array [
Object {
"name": "configuration",
"parameters": Object {
"audio input": "XLR",
"video input": "SDI",
},
"raw": "configuration: video input: SDI audio input: XLR",
},
]
`);
Array [
Object {
"name": "configuration",
"parameters": Object {
"audioInput": "XLR",
"videoInput": "SDI",
},
"raw": "configuration: video input: SDI audio input: XLR",
},
]
`);
expect(parser.parse('slot select: slot id: 2 video format: NTSC')).toMatchInlineSnapshot(`
Array [
Object {
"name": "slot select",
"parameters": Object {
"slot id": "2",
"video format": "NTSC",
},
"raw": "slot select: slot id: 2 video format: NTSC",
},
]
`);
Array [
Object {
"name": "slot select",
"parameters": Object {
"slotId": 2,
"videoFormat": "NTSC",
},
"raw": "slot select: slot id: 2 video format: NTSC",
},
]
`);
expect(parser.parse('preview: enable: true')).toMatchInlineSnapshot(`
Array [
Object {
"name": "preview",
"parameters": Object {
"enable": "true",
},
"raw": "preview: enable: true",
},
]
`);
Array [
Object {
"name": "preview",
"parameters": Object {
"enable": true,
},
"raw": "preview: enable: true",
},
]
`);
expect(parser.parse('play on startup: single clip: true')).toMatchInlineSnapshot(`
Array [
Object {
"name": "play on startup",
"parameters": Object {
"single clip": "true",
},
"raw": "play on startup: single clip: true",
},
]
`);
Array [
Object {
"name": "play on startup",
"parameters": Object {
"singleClip": true,
},
"raw": "play on startup: single clip: true",
},
]
`);
expect(parser.parse('clips get: clip id: example clip id')).toMatchInlineSnapshot(`
Array [
Object {
"name": "clips get",
"parameters": Object {
"clip id": "example clip id",
},
"raw": "clips get: clip id: example clip id",
},
]
`);
expect(parser.parse('clips get: clip id: example clip id')).toMatchInlineSnapshot(`Array []`);
expect(parser.parse('playrange set: clip id: 12345')).toMatchInlineSnapshot(`
Array [
Object {
"name": "playrange set",
"parameters": Object {
"clip id": "12345",
},
"raw": "playrange set: clip id: 12345",
},
]
`);
Array [
Object {
"name": "playrange set",
"parameters": Object {
"clipId": 12345,
},
"raw": "playrange set: clip id: 12345",
},
]
`);
expect(parser.parse('shuttle: speed: -1600')).toMatchInlineSnapshot(`
Array [
Object {
"name": "shuttle",
"parameters": Object {
"speed": "-1600",
},
"raw": "shuttle: speed: -1600",
},
]
`);
Array [
Object {
"name": "shuttle",
"parameters": Object {
"speed": -1600,
},
"raw": "shuttle: speed: -1600",
},
]
`);
expect(parser.getLoggedOutput()).toEqual([]);
expect(parser.getLoggedOutput()).toMatchInlineSnapshot(`
Array [
Object {
"level": 50,
"msg": "valueNum \`'example clip id'\` is NaN",
},
]
`);
});
});
import { invariant } from './invariant';
import { camelcaseToSpaceCase } from './utils';
import { ArgKey } from './types';
import { ArgKey, ArgsTypes } from './types';
interface Option<T extends Record<string, ArgKey> = Record<string, ArgKey>> {
interface Option<
A extends Record<string, ArgKey> = Record<string, ArgKey>,
R extends Record<string, ArgKey> = Record<string, ArgKey>
> {
description: string;
arguments?: T;
arguments?: A;
returnValue: R;
}

@@ -12,10 +16,23 @@

interface ParamInfo {
paramType: ArgKey;
/** The camelcase name we use everywhere */
paramName: string;
}
/** Internal container class that holds metadata about each HyperDeck event */
class HyperDeckAPI<T extends ParamMap = {}> {
constructor(private readonly options: T = {} as any) {}
class HyperDeckAPI<P extends ParamMap = {}> {
constructor(
// public only because TS apparently strips types from private methods
public readonly options: P = {} as any
) {}
public addOption = <K extends string, R extends Record<string, ArgKey> = {}>(
public addOption = <
K extends string,
A extends Record<string, ArgKey> = {},
R extends Record<string, ArgKey> = {}
>(
key: K | [K, ...string[]],
option: Option<R>
): HyperDeckAPI<T & { [key in K]: Option<R> }> => {
option: Option<A, R>
): HyperDeckAPI<P & { [key in K]: Option<A, R> }> => {
const k = Array.isArray(key) ? key[0] : key;

@@ -30,4 +47,4 @@ invariant(!this.options.hasOwnProperty(k), 'option already exists for key `%s`', k);

/** Get a `Set` of param names keyed by function name */
public getParamsByKey = (): { [K in keyof T]: Record<string, ArgKey> } =>
Object.entries(this.options).reduce<Record<string, Record<string, ArgKey>>>(
public getParamsByCommandName = (): { [K in keyof P]: Record<string, ParamInfo> } =>
Object.entries(this.options).reduce<Record<string, Record<string, ParamInfo>>>(
(prev, [commandName, value]) => {

@@ -39,5 +56,8 @@ if (!value.arguments) {

}
prev[commandName] = Object.entries(value.arguments).reduce<Record<string, ArgKey>>(
prev[commandName] = Object.entries(value.arguments).reduce<Record<string, ParamInfo>>(
(argObj, [argKey, argType]) => {
argObj[camelcaseToSpaceCase(argKey)] = argType;
argObj[camelcaseToSpaceCase(argKey)] = {
paramType: argType,
paramName: argKey,
};
return argObj;

@@ -56,8 +76,17 @@ },

description: 'Provides help text on all commands and parameters',
returnValue: {},
})
.addOption('commands', {
description: 'return commands in XML format',
returnValue: {
commands: 'string',
},
})
.addOption('device info', {
description: 'return device information',
returnValue: {
protocolVersion: 'string',
model: 'string',
slotCount: 'string',
},
})

@@ -69,8 +98,14 @@ .addOption('disk list', {

},
returnValue: {
slotId: 'number',
// TODO(meyer) array of clips
},
})
.addOption('quit', {
description: 'disconnect ethernet control',
returnValue: {},
})
.addOption('ping', {
description: 'check device is responding',
returnValue: {},
})

@@ -82,2 +117,3 @@ .addOption('preview', {

},
returnValue: {},
})

@@ -91,5 +127,9 @@ .addOption('play', {

},
returnValue: {},
})
.addOption('playrange', {
description: 'query playrange setting',
returnValue: {
// TODO(meyer) this isn't accurate
},
})

@@ -100,3 +140,3 @@ .addOption('playrange set', {

// maybe number?
clipId: 'string',
clipId: 'number',
// description: 'set play range to play between timecode {inT} and timecode {outT}',

@@ -109,5 +149,7 @@ in: 'timecode',

},
returnValue: {},
})
.addOption('playrange clear', {
description: 'clear/reset play range setting',
returnValue: {},
})

@@ -121,2 +163,4 @@ .addOption('play on startup', {

},
// TODO(meyer) verify that there's no return value
returnValue: {},
})

@@ -128,2 +172,4 @@ .addOption('play option', {

},
// TODO(meyer)
returnValue: {},
})

@@ -135,2 +181,3 @@ .addOption('record', {

},
returnValue: {},
})

@@ -142,8 +189,14 @@ .addOption('record spill', {

},
// TODO(meyer)
returnValue: {},
})
.addOption('stop', {
description: 'stop playback or recording',
returnValue: {},
})
.addOption('clips count', {
description: 'query number of clips on timeline',
returnValue: {
clipCount: 'number',
},
})

@@ -157,2 +210,5 @@ .addOption('clips get', {

},
returnValue: {
clips: 'clips',
},
})

@@ -163,6 +219,7 @@ .addOption('clips add', {

name: 'string',
clipId: 'string',
clipId: 'number',
in: 'timecode',
out: 'timecode',
},
returnValue: {},
})

@@ -172,10 +229,24 @@ .addOption('clips remove', {

arguments: {
clidId: 'string',
clipId: 'number',
},
// TODO(meyer) verify this
returnValue: {},
})
.addOption('clips clear', {
description: 'empty timeline clip list',
returnValue: {},
})
.addOption('transport info', {
description: 'query current activity',
returnValue: {
status: 'transportstatus',
speed: 'number',
slotId: 'number',
clipId: 'number',
singleClip: 'boolean',
displayTimecode: 'timecode',
timecode: 'timecode',
videoFormat: 'videoformat',
loop: 'boolean',
},
})

@@ -187,2 +258,9 @@ .addOption('slot info', {

},
returnValue: {
slotId: 'number',
status: 'slotstatus',
volumeName: 'string',
recordingTime: 'timecode',
videoFormat: 'videoformat',
},
})

@@ -195,2 +273,3 @@ .addOption('slot select', {

},
returnValue: {},
})

@@ -202,2 +281,4 @@ .addOption('slot unblock', {

},
// TODO(meyer) verify this
returnValue: {},
})

@@ -210,2 +291,4 @@ .addOption('dynamic range', {

},
// TODO(meyer)
returnValue: {},
})

@@ -225,2 +308,13 @@ .addOption('notify', {

},
returnValue: {
remote: 'boolean',
transport: 'boolean',
slot: 'boolean',
configuration: 'boolean',
droppedFrames: 'boolean',
displayTimecode: 'boolean',
timelinePosition: 'boolean',
playrange: 'boolean',
dynamicRange: 'boolean',
},
})

@@ -230,3 +324,3 @@ .addOption('goto', {

arguments: {
clipId: 'string',
clipId: 'number',
clip: 'goto',

@@ -237,2 +331,3 @@ timeline: 'goto',

},
returnValue: {},
})

@@ -244,2 +339,3 @@ .addOption('jog', {

},
returnValue: {},
})

@@ -251,2 +347,3 @@ .addOption('shuttle', {

},
returnValue: {},
})

@@ -259,2 +356,4 @@ .addOption('remote', {

},
// TODO(meyer)
returnValue: {},
})

@@ -275,5 +374,20 @@ .addOption('configuration', {

},
returnValue: {
videoInput: 'videoinput',
audioInput: 'audioinput',
fileFormat: 'fileformat',
audioCodec: 'audiocodec',
timecodeInput: 'timecodeinput',
timecodePreset: 'timecode',
audioInputChannels: 'number',
recordTrigger: 'recordtrigger',
recordPrefix: 'string',
appendTimestamp: 'boolean',
},
})
.addOption('uptime', {
description: 'return time since last boot',
returnValue: {
uptime: 'number',
},
})

@@ -286,2 +400,5 @@ .addOption('format', {

},
returnValue: {
token: 'string',
},
})

@@ -293,2 +410,4 @@ .addOption('identify', {

},
// TODO(meyer) verify
returnValue: {},
})

@@ -300,4 +419,26 @@ .addOption('watchdog', {

},
// TODO(meyer) verify
returnValue: {},
});
export const paramsByKey = api.getParamsByKey();
type CommandConfigs = { [K in keyof typeof api['options']]: typeof api['options'][K] };
export const paramsByCommandName = api.getParamsByCommandName();
export type CommandName = keyof CommandConfigs;
export type CommandParamsByCommandName = {
[K in CommandName]: ArgsTypes<NonNullable<CommandConfigs[K]['arguments']>>;
};
export type CommandResponsesByCommandName = {
[K in CommandName]: ArgsTypes<NonNullable<CommandConfigs[K]['returnValue']>>;
};
export function assertValidCommandName(value: any): asserts value is CommandName {
invariant(
typeof value === 'string' && paramsByCommandName.hasOwnProperty(value),
'Invalid command: `%o`',
value
);
}

@@ -1,6 +0,12 @@

import * as ResponseInterface from './types/ResponseInterface';
import { CommandResponsesByCommandName } from './api';
export const formatClipsGetResponse = (
res: ResponseInterface.ClipsGet
res: CommandResponsesByCommandName['clips get']
): Record<string, string | number> => {
if (!res.clips) {
return {
clipsCount: 0,
};
}
const clipsCount = res.clips.length;

@@ -7,0 +13,0 @@

import { HyperDeckSocket } from './HyperDeckSocket';
import type { ReceivedCommandCallback } from './HyperDeckSocket';
import { DeserializedCommand, SynchronousCode, ErrorCode, NotifyType } from './types';
import * as ResponseInterface from './types/ResponseInterface';
import * as DeserializedCommands from './types/DeserializedCommands';
import { formatClipsGetResponse } from './formatClipsGetResponse';
import { SynchronousCode, ErrorCode, NotifyType, CommandHandler, CommandResponse } from './types';
import { createServer, Server } from 'net';
import pino from 'pino';
import { CommandName, paramsByCommandName } from './api';
import { formatClipsGetResponse } from './formatClipsGetResponse';
import { invariant } from './invariant';
type Handler<C extends DeserializedCommand, R extends any> = (command: C) => Promise<R>;
const internalCommands = {
remote: true,
notify: true,
watchdog: true,
ping: true,
};
class UnimplementedError extends Error {}
type SupportedCommands = Exclude<CommandName, keyof typeof internalCommands>;
const noop = async () => {
throw new UnimplementedError();
};
export class HyperDeckServer {

@@ -24,29 +24,2 @@ private logger: pino.Logger;

onDeviceInfo: Handler<DeserializedCommand, ResponseInterface.DeviceInfo> = noop;
onDiskList: Handler<DeserializedCommand, ResponseInterface.DiskList> = noop;
onPreview: Handler<DeserializedCommands.PreviewCommand, void> = noop;
onPlay: Handler<DeserializedCommands.PlayCommand, void> = noop;
onPlayrangeSet: Handler<DeserializedCommands.PlayrangeSetCommand, void> = noop;
onPlayrangeClear: Handler<DeserializedCommand, void> = noop;
onRecord: Handler<DeserializedCommands.RecordCommand, void> = noop;
onStop: Handler<DeserializedCommand, void> = noop;
onClipsCount: Handler<DeserializedCommand, ResponseInterface.ClipsCount> = noop;
onClipsGet: Handler<DeserializedCommands.ClipsGetCommand, ResponseInterface.ClipsGet> = noop;
onClipsAdd: Handler<DeserializedCommands.ClipsAddCommand, void> = noop;
onClipsClear: Handler<DeserializedCommand, void> = noop;
onTransportInfo: Handler<DeserializedCommand, ResponseInterface.TransportInfo> = noop;
onSlotInfo: Handler<DeserializedCommands.SlotInfoCommand, ResponseInterface.SlotInfo> = noop;
onSlotSelect: Handler<DeserializedCommands.SlotSelectCommand, void> = noop;
onGoTo: Handler<DeserializedCommands.GoToCommand, void> = noop;
onJog: Handler<DeserializedCommands.JogCommand, void> = noop;
onShuttle: Handler<DeserializedCommands.ShuttleCommand, void> = noop;
onConfiguration: Handler<
DeserializedCommands.ConfigurationCommand,
ResponseInterface.Configuration
> = noop;
onUptime: Handler<DeserializedCommand, ResponseInterface.Uptime> = noop;
onFormat: Handler<DeserializedCommands.FormatCommand, ResponseInterface.Format> = noop;
onIdentify: Handler<DeserializedCommands.IdentifyCommand, void> = noop;
onWatchdog: Handler<DeserializedCommands.WatchdogCommand, void> = noop;
constructor(ip?: string, logger = pino()) {

@@ -61,5 +34,3 @@ this.logger = logger.child({ name: 'HyperDeck Emulator' });

this.sockets[socketId] = new HyperDeckSocket(socket, socketLogger, (cmd) =>
this.receivedCommand(cmd)
);
this.sockets[socketId] = new HyperDeckSocket(socket, socketLogger, this.receivedCommand);

@@ -97,160 +68,113 @@ this.sockets[socketId].on('disconnected', () => {

private receivedCommand: ReceivedCommandCallback = async (cmd) => {
// TODO(meyer) more sophisticated debouncing
await new Promise((resolve) => setTimeout(() => resolve(), 200));
private commandHandlers: { [K in CommandName]?: CommandHandler<K> } = {};
this.logger.info({ cmd }, '<-- ' + cmd.name);
try {
if (cmd.name === 'device info') {
const res = await this.onDeviceInfo(cmd);
return { code: SynchronousCode.DeviceInfo, params: res };
}
public on = <T extends SupportedCommands>(key: T, handler: CommandHandler<T>): void => {
invariant(paramsByCommandName.hasOwnProperty(key), 'Invalid key: `%s`', key);
if (cmd.name === 'disk list') {
const res = await this.onDiskList(cmd);
return { code: SynchronousCode.DiskList, params: res };
}
invariant(
!this.commandHandlers.hasOwnProperty(key),
'Handler already registered for `%s`',
key
);
if (cmd.name === 'preview') {
await this.onPreview(cmd);
return SynchronousCode.OK;
}
this.commandHandlers[key] = handler as any;
};
if (cmd.name === 'play') {
await this.onPlay(cmd);
return SynchronousCode.OK;
}
private receivedCommand: ReceivedCommandCallback = async (cmd) => {
// TODO(meyer) more sophisticated debouncing
await new Promise((resolve) => setTimeout(() => resolve(), 200));
if (cmd.name === 'playrange set') {
await this.onPlayrangeSet(cmd);
return SynchronousCode.OK;
}
this.logger.info({ cmd }, 'receivedCommand %s', cmd.name);
if (cmd.name === 'playrange clear') {
await this.onPlayrangeClear(cmd);
return SynchronousCode.OK;
}
if (cmd.name === 'remote') {
return {
code: SynchronousCode.Remote,
params: {
enabled: true,
override: false,
},
};
}
if (cmd.name === 'record') {
await this.onRecord(cmd);
return SynchronousCode.OK;
}
// implemented in socket.ts
if (cmd.name === 'notify' || cmd.name === 'watchdog' || cmd.name === 'ping') {
return SynchronousCode.OK;
}
if (cmd.name === 'stop') {
await this.onStop(cmd);
return SynchronousCode.OK;
}
const handler = this.commandHandlers[cmd.name];
if (!handler) {
this.logger.error({ cmd }, 'unimplemented');
return ErrorCode.Unsupported;
}
if (cmd.name === 'clips count') {
const res = await this.onClipsCount(cmd);
return { code: SynchronousCode.ClipsCount, params: res };
}
const response = await handler(cmd);
if (cmd.name === 'clips get') {
const res = await this.onClipsGet(cmd).then(formatClipsGetResponse);
return { code: SynchronousCode.ClipsInfo, params: res };
}
const result: CommandResponse = {
name: cmd.name,
response,
} as any;
if (cmd.name === 'clips add') {
await this.onClipsAdd(cmd);
return SynchronousCode.OK;
}
if (
result.name === 'clips add' ||
result.name === 'clips clear' ||
result.name === 'goto' ||
result.name === 'identify' ||
result.name === 'jog' ||
result.name === 'play' ||
result.name === 'playrange clear' ||
result.name === 'playrange set' ||
result.name === 'preview' ||
result.name === 'record' ||
result.name === 'shuttle' ||
result.name === 'slot select' ||
result.name === 'stop'
) {
return SynchronousCode.OK;
}
if (cmd.name === 'clips clear') {
await this.onClipsClear(cmd);
return SynchronousCode.OK;
}
if (result.name === 'device info') {
return { code: SynchronousCode.DeviceInfo, params: result.response };
}
if (cmd.name === 'transport info') {
const res = await this.onTransportInfo(cmd);
return { code: SynchronousCode.TransportInfo, params: res };
}
if (result.name === 'disk list') {
return { code: SynchronousCode.DiskList, params: result.response };
}
if (cmd.name === 'slot info') {
const res = await this.onSlotInfo(cmd);
return { code: SynchronousCode.SlotInfo, params: res };
}
if (result.name === 'clips count') {
return { code: SynchronousCode.ClipsCount, params: result.response };
}
if (cmd.name === 'slot select') {
await this.onSlotSelect(cmd);
return SynchronousCode.OK;
}
if (result.name === 'clips get') {
return { code: SynchronousCode.ClipsInfo, params: formatClipsGetResponse(result.response) };
}
if (cmd.name === 'notify') {
// implemented in socket.ts
return SynchronousCode.OK;
}
if (result.name === 'transport info') {
return { code: SynchronousCode.TransportInfo, params: result.response };
}
if (cmd.name === 'go to') {
await this.onGoTo(cmd);
return SynchronousCode.OK;
}
if (result.name === 'slot info') {
return { code: SynchronousCode.SlotInfo, params: result.response };
}
if (cmd.name === 'jog') {
await this.onJog(cmd);
return SynchronousCode.OK;
if (result.name === 'configuration') {
if (result) {
return { code: SynchronousCode.Configuration, params: result.response };
}
return SynchronousCode.OK;
}
if (cmd.name === 'shuttle') {
await this.onShuttle(cmd);
return SynchronousCode.OK;
}
if (result.name === 'uptime') {
return { code: SynchronousCode.Uptime, params: result.response };
}
if (cmd.name === 'remote') {
return {
code: SynchronousCode.Remote,
params: {
enabled: true,
override: false,
},
};
if (result.name === 'format') {
if (result) {
return { code: SynchronousCode.FormatReady, params: result.response };
}
return SynchronousCode.OK;
}
if (cmd.name === 'configuration') {
const res = await this.onConfiguration(cmd);
if (res) {
return { code: SynchronousCode.Configuration, params: res };
}
return SynchronousCode.OK;
}
if (cmd.name === 'uptime') {
const res = await this.onUptime(cmd);
return { code: SynchronousCode.Uptime, params: res };
}
if (cmd.name === 'format') {
const res = await this.onFormat(cmd);
if (res) {
return { code: SynchronousCode.FormatReady, params: res };
}
return SynchronousCode.OK;
}
if (cmd.name === 'identify') {
await this.onIdentify(cmd);
return SynchronousCode.OK;
}
if (cmd.name === 'watchdog') {
// implemented in socket.ts
return SynchronousCode.OK;
}
if (cmd.name === 'ping') {
// implemented in socket.ts
return SynchronousCode.OK;
}
invariant(false, 'Unhandled command name: `%s`', cmd.name);
} catch (err) {
if (err instanceof UnimplementedError) {
this.logger.error({ cmd }, 'unimplemented');
return ErrorCode.Unsupported;
}
this.logger.error({ cmd, err: err.message }, 'unhandled command name');
return ErrorCode.InternalError;
}
this.logger.error({ cmd, res: result }, 'Unsupported command');
return ErrorCode.Unsupported;
};
}

@@ -10,4 +10,5 @@ import type { Socket } from 'net';

ResponseCode,
DeserializedCommandsByName,
TypesByStringKey,
} from './types';
import * as DeserializedCommands from './types/DeserializedCommands';
import { MultilineParser } from './MultilineParser';

@@ -64,12 +65,19 @@ import type { Logger } from 'pino';

private notifySettings = {
private notifySettings: Record<
keyof DeserializedCommandsByName['notify']['parameters'],
boolean
> = {
configuration: false,
displayTimecode: false,
droppedFrames: false,
dynamicRange: false,
playrange: false,
remote: false,
slot: false,
timelinePosition: false,
transport: false,
remote: false,
configuration: false,
'dropped frames': false,
};
private onMessage(data: string): void {
this.logger.info({ data }, '<-- received message from client');
this.logger.info({ data }, '<--- received message from client');

@@ -86,3 +94,3 @@ this.lastReceivedMS = Date.now();

const watchdogCmd = cmd as DeserializedCommands.WatchdogCommand;
const watchdogCmd = cmd as DeserializedCommandsByName['watchdog'];
if (watchdogCmd.parameters.period) {

@@ -100,3 +108,3 @@ this.watchdogTimer = setInterval(() => {

} else if (cmd.name === 'notify') {
const notifyCmd = cmd as DeserializedCommands.NotifyCommand;
const notifyCmd = cmd as DeserializedCommandsByName['notify'];

@@ -108,3 +116,3 @@ if (Object.keys(notifyCmd.parameters).length > 0) {

if (this.notifySettings[param] !== undefined) {
this.notifySettings[param] = notifyCmd.parameters[param] === 'true';
this.notifySettings[param] = notifyCmd.parameters[param] === true;
}

@@ -159,9 +167,13 @@ }

code: ResponseCode,
paramsOrMessage?: Record<string, unknown> | string,
paramsOrMessage?: Record<string, TypesByStringKey[keyof TypesByStringKey]> | string,
cmd?: DeserializedCommand
): void {
const responseText = messageForCode(code, paramsOrMessage);
const method = ErrorCode[code] ? 'error' : 'info';
this.logger[method]({ responseText, cmd }, '--> send response to client');
this.socket.write(responseText);
try {
const responseText = messageForCode(code, paramsOrMessage);
const method = ErrorCode[code] ? 'error' : 'info';
this.logger[method]({ responseText, cmd }, '---> send response to client');
this.socket.write(responseText);
} catch (err) {
this.logger.error({ cmd }, '-x-> Error sending response: %s', err);
}
}

@@ -168,0 +180,0 @@

@@ -1,3 +0,2 @@

export * from './HyperDeckServer';
export * from './Timecode';
export * as ResponseInterface from './types/ResponseInterface';
export { HyperDeckServer } from './HyperDeckServer';
export { Timecode } from './Timecode';
import util from 'util';
export class FormattedError extends Error {
constructor(template: string, ...args: any[]) {
super(util.format(template, ...args));
this.template = template;
this.args = args;
}
public template: string;
public args: any[];
}
export function invariant(condition: any, message: string, ...args: any[]): asserts condition {
if (!condition) {
throw new Error(util.format(message, ...args));
throw new FormattedError(message, ...args);
}
}
import { CRLF } from './constants';
import { ResponseCode, responseNamesByCode } from './types';
import { ResponseCode, responseNamesByCode, TypesByStringKey } from './types';
import { invariant } from './invariant';
import { camelcaseToSpaceCase } from './utils';
import { Timecode } from './Timecode';

@@ -14,3 +15,3 @@ // escape CR/LF and remove colons

code: ResponseCode,
params?: Record<string, unknown> | string
params?: Record<string, TypesByStringKey[keyof TypesByStringKey]> | string
): string => {

@@ -47,4 +48,11 @@ if (typeof params === 'string') {

valueString = value.toString();
} else if (value instanceof Timecode) {
valueString = value.toString();
} else {
invariant(false, 'Unhandled value type: `%s`', typeof value);
invariant(
false,
'Unhandled value type for key `%s`: `%s`',
key,
Array.isArray(value) ? 'array' : typeof value
);
}

@@ -51,0 +59,0 @@

@@ -1,10 +0,10 @@

import type { DeserializedCommand } from './types';
import { DeserializedCommand, stringToValueFns } from './types';
import { CRLF } from './constants';
import type { Logger } from 'pino';
import { invariant } from './invariant';
import { paramsByKey } from './api';
import { invariant, FormattedError } from './invariant';
import { paramsByCommandName, assertValidCommandName } from './api';
export class MultilineParser {
private linesQueue: string[] = [];
private logger: Logger;
private linesQueue: string[] = [];

@@ -52,5 +52,5 @@ constructor(logger: Logger) {

const lines = this.linesQueue.splice(0, endLine + 1);
const r = this.parseResponse(lines);
if (r) {
res.push(r);
const parsedResponse = this.parseResponse(lines);
if (parsedResponse) {
res.push(parsedResponse);
}

@@ -63,60 +63,92 @@ }

private parseResponse(responseLines: string[]): DeserializedCommand | null {
const lines = responseLines.map((l) => l.trim());
try {
const lines = responseLines.map((l) => l.trim());
const firstLine = lines[0];
if (lines.length === 1 && lines[0].includes(':')) {
const bits = lines[0].split(': ');
if (lines.length === 1) {
if (!firstLine.includes(':')) {
assertValidCommandName(firstLine);
return {
raw: lines.join(CRLF),
name: firstLine,
parameters: {},
} as DeserializedCommand;
}
const msg = bits.shift() as keyof typeof paramsByKey;
invariant(msg, 'Unrecognised command');
invariant(paramsByKey.hasOwnProperty(msg), 'Invalid command: `%s`', msg);
// single-line command with params
const params: Record<string, string> = {};
const paramNames = paramsByKey[msg];
let param = bits.shift();
invariant(param, 'No named parameters found');
const bits = firstLine.split(': ');
for (let i = 0; i < bits.length - 1; i++) {
const bit = bits[i];
const bobs = bit.split(' ');
const commandName = bits.shift();
assertValidCommandName(commandName);
let nextParam = '';
for (let i = bobs.length - 1; i >= 0; i--) {
nextParam = (bobs.pop() + ' ' + nextParam).trim();
if (paramNames.hasOwnProperty(nextParam)) {
break;
const params: Record<string, any> = {};
const paramNames = paramsByCommandName[commandName];
let param = bits.shift();
invariant(param, 'No named parameters found');
for (let i = 0; i < bits.length - 1; i++) {
const bit = bits[i];
const bobs = bit.split(' ');
let nextParam = '';
for (let i = bobs.length - 1; i >= 0; i--) {
nextParam = (bobs.pop() + ' ' + nextParam).trim();
if (paramNames.hasOwnProperty(nextParam)) {
break;
}
}
invariant(bobs.length > 0, 'Command malformed / paramName not recognised: `%s`', bit);
invariant(paramNames.hasOwnProperty(param), 'Unsupported param: `%o`', param);
const value = bobs.join(' ');
const { paramName, paramType } = paramNames[param];
const formatter = stringToValueFns[paramType];
params[paramName] = formatter(value);
param = nextParam;
}
invariant(bobs.length > 0, 'Command malformed / paramName not recognised: `%s`', bit);
invariant(paramNames.hasOwnProperty(param), 'Unsupported param: `%o`', param);
params[param] = bobs.join(' ');
param = nextParam;
}
const value = bits[bits.length - 1];
const { paramName, paramType } = paramNames[param];
params[param] = bits[bits.length - 1];
const formatter = stringToValueFns[paramType];
params[paramName] = formatter(value);
return {
raw: lines.join(CRLF),
name: msg,
parameters: params,
};
} else {
const headerMatch = lines[0].match(/(.+?)(:|)$/im);
if (!headerMatch) {
this.logger.error({ header: lines[0] }, 'failed to parse header');
return null;
return {
raw: lines.join(CRLF),
name: commandName,
parameters: params,
} as DeserializedCommand;
}
const msg = headerMatch[1];
invariant(
firstLine.endsWith(':'),
'Expected a line ending in semicolon, received `%o`',
firstLine
);
const params: Record<string, string> = {};
// remove the semicolon at the end of the command
const commandName = firstLine.slice(0, -1);
for (let i = 1; i < lines.length; i++) {
const lineMatch = lines[i].match(/^(.*?): (.*)$/im);
if (!lineMatch) {
this.logger.error({ line: lines[i] }, 'failed to parse line');
continue;
}
assertValidCommandName(commandName);
params[lineMatch[1]] = lineMatch[2];
const paramNames = paramsByCommandName[commandName];
const params: Record<string, any> = {};
for (const line of lines) {
const lineMatch = line.match(/^(.*?): (.*)$/im);
invariant(lineMatch, 'Failed to parse line: `%o`', line);
const param = lineMatch[1];
const value = lineMatch[2];
invariant(paramNames.hasOwnProperty(param), 'Unsupported param: `%o`', param);
const { paramName, paramType } = paramNames[param];
const formatter = stringToValueFns[paramType];
params[paramName] = formatter(value);
}

@@ -126,8 +158,17 @@

raw: lines.join(CRLF),
name: msg,
name: commandName,
parameters: params,
};
} as DeserializedCommand;
return res;
} catch (err) {
if (err instanceof FormattedError) {
this.logger.error(err.template, ...err.args);
} else {
this.logger.error({ err: err + '' }, 'parseResponse error');
}
return null;
}
}
}
import { invariant } from './invariant';
import { Timecode } from './Timecode';
import type { CommandName, CommandParamsByCommandName, CommandResponsesByCommandName } from './api';

@@ -11,8 +12,25 @@ export interface NotificationConfig {

export interface DeserializedCommand {
raw: string;
name: string;
parameters: Record<string, string | undefined>;
}
export type DeserializedCommandsByName = {
[K in CommandName]: {
raw: string;
name: K;
parameters: CommandParamsByCommandName[K];
};
};
export type DeserializedCommand = DeserializedCommandsByName[CommandName];
export type CommandHandler<T extends CommandName> = (
cmd: DeserializedCommandsByName[T]['parameters']
) => Promise<CommandResponsesByCommandName[T]>;
export type ResponsesByCommandName = {
[K in CommandName]: {
name: K;
response: CommandResponsesByCommandName[K];
};
};
export type CommandResponse = ResponsesByCommandName[CommandName];
export type ResponseCode = ErrorCode | SynchronousCode | AsynchronousCode;

@@ -143,2 +161,20 @@

export interface ClipV1 {
name: string;
startT: Timecode;
duration: Timecode;
}
export const isClipV1 = (value: any): value is ClipV1 => {
return typeof value === 'object' && value !== null && typeof value.name === 'string';
};
export interface ClipV2 {
startT: Timecode;
duration: number;
inT: Timecode;
outT: Timecode;
name: string;
}
export type VideoFormat = keyof typeof videoFormats;

@@ -251,2 +287,6 @@

export type ArgsTypes<T extends Record<string, ArgKey>> = {
[K in keyof T]?: TypesByStringKey[T[K]];
};
export interface TypesByStringKey {

@@ -266,7 +306,26 @@ boolean: boolean;

recordtrigger: RecordTrigger;
clips: ClipV1[];
slotstatus: SlotStatus;
transportstatus: TransportStatus;
}
function assertArrayOf<T>(
predicate: (v: any) => v is T,
value: any,
message: string
): asserts value is T[] {
invariant(Array.isArray(value), 'Expected an array');
for (const item of value) {
invariant(predicate(item), message);
}
}
const getStringOrThrow = (value: any): string => {
invariant(typeof value === 'string', 'Expected a string');
return value;
};
export const stringToValueFns: {
/** Coerce string to the correct type or throw if the string cannot be converted. */
[K in keyof TypesByStringKey]: (value: string) => TypesByStringKey[K];
[K in keyof TypesByStringKey]: (value: unknown) => TypesByStringKey[K];
} = {

@@ -278,6 +337,6 @@ boolean: (value) => {

},
string: (value) => value,
timecode: (value) => Timecode.toTimecode(value),
string: getStringOrThrow,
timecode: (value) => Timecode.toTimecode(getStringOrThrow(value)),
number: (value) => {
const valueNum = parseFloat(value);
const valueNum = parseFloat(getStringOrThrow(value));
invariant(!isNaN(valueNum), 'valueNum `%o` is NaN', value);

@@ -298,3 +357,3 @@ return valueNum;

}
const valueNum = parseInt(value, 10);
const valueNum = parseInt(getStringOrThrow(value), 10);
if (!isNaN(valueNum)) {

@@ -304,3 +363,3 @@ return valueNum;

// TODO(meyer) validate further
return value;
return getStringOrThrow(value);
},

@@ -315,3 +374,3 @@ videoinput: (value) => {

},
fileformat: (value) => value,
fileformat: getStringOrThrow,
audiocodec: (value) => {

@@ -329,2 +388,14 @@ invariant(isAudioCodec(value), 'Unsupported audio codec: `%o`', value);

},
clips: (value) => {
assertArrayOf(isClipV1, value, 'Expected an array of clips');
return value;
},
slotstatus: (value) => {
invariant(isSlotStatus(value), 'Unsupported slot status: `%o`', value);
return value;
},
transportstatus: (value) => {
invariant(isTransportStatus(value), 'Unsupported slot status: `%o`', value);
return value;
},
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc