Skip to content

Commit

Permalink
feat: make event types be strongly defined
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Sep 23, 2023
1 parent fb7a34b commit 93e7573
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 73 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"source"
],
"dependencies": {
"eventemitter3": "^4.0.7",
"tslib": "^2.6.2"
},
"prettier": "@sofie-automation/code-standard-preset/.prettierrc.json",
Expand Down
19 changes: 3 additions & 16 deletions src/asyncHandlers/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
import { ResponseMessage } from '../message'
import { IHandler } from './iHandler'
import { AsynchronousCode } from '../codes'
import { ConfigurationChangeResponse } from '../events'

export interface ConfigurationChangeResponse {
audioInput?: 'embedded' | 'XLR' | 'RCA'
videoInput?: 'SDI' | 'HDMI' | 'component' | string
fileFormat?: string
// v1.11:
audioCodec?: 'PCM' | 'AAC'
timecodeInput?: 'external' | 'embedded' | 'preset' | 'clip'
timecodePreset?: string
audioInputChannels?: number
recordTrigger?: 'none' | 'recordbit' | 'timecoderun'
recordPrefix?: string
appendTimestamp?: boolean
}

export class ConfigurationChange implements IHandler {
export class ConfigurationChange implements IHandler<'notify.configuration'> {
responseCode = AsynchronousCode.Configuration
eventName = 'notify.configuration'
eventName = 'notify.configuration' as const

deserialize(msg: ResponseMessage): ConfigurationChangeResponse {
const res: ConfigurationChangeResponse = {
Expand Down
9 changes: 3 additions & 6 deletions src/asyncHandlers/displayTimecode.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { ResponseMessage } from '../message'
import { IHandler } from './iHandler'
import { AsynchronousCode } from '../codes'
import { DisplayTimecodeChangeResponse } from '../events'

export interface DisplayTimecodeChangeResponse {
displayTimecode: string
}

export class DisplayTimecodeChange implements IHandler {
export class DisplayTimecodeChange implements IHandler<'notify.displayTimecode'> {
responseCode = AsynchronousCode.DisplayTimecode
eventName = 'notify.displayTimecode'
eventName = 'notify.displayTimecode' as const

deserialize(msg: ResponseMessage): DisplayTimecodeChangeResponse {
const res: DisplayTimecodeChangeResponse = {
Expand Down
7 changes: 4 additions & 3 deletions src/asyncHandlers/iHandler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { HyperdeckAsyncEvents } from '../events'
import { AsynchronousCode } from '../codes'
import { ResponseMessage } from '../message'

export interface IHandler {
export interface IHandler<TEvent extends keyof HyperdeckAsyncEvents> {
responseCode: AsynchronousCode
eventName: string
eventName: TEvent

deserialize(msg: ResponseMessage): any
deserialize(msg: ResponseMessage): HyperdeckAsyncEvents[TEvent][0]
}
9 changes: 3 additions & 6 deletions src/asyncHandlers/remoteInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ import { ResponseMessage } from '../message'
import { parseBool } from '../util'
import { IHandler } from './iHandler'
import { AsynchronousCode } from '../codes'
import { RemoteInfoChangeResponse } from '../events'

export interface RemoteInfoChangeResponse {
enabled?: boolean
}

export class RemoteInfoChange implements IHandler {
export class RemoteInfoChange implements IHandler<'notify.remote'> {
responseCode = AsynchronousCode.RemoteInfo
eventName = 'notify.remote'
eventName = 'notify.remote' as const

deserialize(msg: ResponseMessage): RemoteInfoChangeResponse {
const res: RemoteInfoChangeResponse = {
Expand Down
15 changes: 4 additions & 11 deletions src/asyncHandlers/slotInfo.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import { SlotId, VideoFormat, SlotStatus } from '../enums'
import { VideoFormat, SlotStatus } from '../enums'
import { ResponseMessage } from '../message'
import { parseIntIfDefined } from '../util'
import { IHandler } from './iHandler'
import { AsynchronousCode } from '../codes'
import { SlotInfoChangeResponse } from '../events'

export interface SlotInfoChangeResponse {
slotId: SlotId
status?: SlotStatus
volumeName?: string
recordingTime?: number
videoFormat?: VideoFormat
}

export class SlotInfoChange implements IHandler {
export class SlotInfoChange implements IHandler<'notify.slot'> {
responseCode = AsynchronousCode.SlotInfo
eventName = 'notify.slot'
eventName = 'notify.slot' as const

deserialize(msg: ResponseMessage): SlotInfoChangeResponse {
const res: SlotInfoChangeResponse = {
Expand Down
9 changes: 3 additions & 6 deletions src/asyncHandlers/timelinePosition.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { ResponseMessage } from '../message'
import { IHandler } from './iHandler'
import { AsynchronousCode } from '../codes'
import { TimelinePositionChangeResponse } from '../events'

export interface TimelinePositionChangeResponse {
timelinePosition: string
}

export class TimelinePositionChange implements IHandler {
export class TimelinePositionChange implements IHandler<'notify.timelinePosition'> {
responseCode = AsynchronousCode.TimelinePosition
eventName = 'notify.timelinePosition'
eventName = 'notify.timelinePosition' as const

deserialize(msg: ResponseMessage): TimelinePositionChangeResponse {
const res: TimelinePositionChangeResponse = {
Expand Down
19 changes: 4 additions & 15 deletions src/asyncHandlers/transportInfo.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
import { TransportStatus, SlotId, VideoFormat } from '../enums'
import { TransportStatus, VideoFormat } from '../enums'
import { ResponseMessage } from '../message'
import { parseIdOrNone, parseIntIfDefined, parseBool } from '../util'
import { IHandler } from './iHandler'
import { AsynchronousCode } from '../codes'
import { TransportInfoChangeResponse } from '../events'

export interface TransportInfoChangeResponse {
status?: TransportStatus
speed?: number
slotId?: SlotId | null
clipId?: number | null
singleClip?: boolean
displayTimecode?: string
timecode?: string
videoFormat?: VideoFormat
loop?: boolean
}

export class TransportInfoChange implements IHandler {
export class TransportInfoChange implements IHandler<'notify.transport'> {
responseCode = AsynchronousCode.TransportInfo
eventName = 'notify.transport'
eventName = 'notify.transport' as const

deserialize(msg: ResponseMessage): TransportInfoChangeResponse {
const res: TransportInfoChangeResponse = {
Expand Down
63 changes: 63 additions & 0 deletions src/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ConnectionInfoResponse } from './commands/connect'
import { SlotId, SlotStatus, TransportStatus, VideoFormat } from './enums'

export interface HyperdeckEvents extends HyperdeckAsyncEvents {
error: [message: string, error: Error | unknown]
disconnected: []
connected: [info: ConnectionInfoResponse]
}

export interface HyperdeckAsyncEvents {
'notify.displayTimecode': [DisplayTimecodeChangeResponse]
'notify.configuration': [ConfigurationChangeResponse]
'notify.transport': [TransportInfoChangeResponse]
'notify.timelinePosition': [TimelinePositionChangeResponse]
'notify.slot': [SlotInfoChangeResponse]
'notify.remote': [RemoteInfoChangeResponse]
}

export interface DisplayTimecodeChangeResponse {
displayTimecode: string
}

export interface ConfigurationChangeResponse {
audioInput?: 'embedded' | 'XLR' | 'RCA'
videoInput?: 'SDI' | 'HDMI' | 'component' | string
fileFormat?: string
// v1.11:
audioCodec?: 'PCM' | 'AAC'
timecodeInput?: 'external' | 'embedded' | 'preset' | 'clip'
timecodePreset?: string
audioInputChannels?: number
recordTrigger?: 'none' | 'recordbit' | 'timecoderun'
recordPrefix?: string
appendTimestamp?: boolean
}

export interface TransportInfoChangeResponse {
status?: TransportStatus
speed?: number
slotId?: SlotId | null
clipId?: number | null
singleClip?: boolean
displayTimecode?: string
timecode?: string
videoFormat?: VideoFormat
loop?: boolean
}

export interface TimelinePositionChangeResponse {
timelinePosition: string
}

export interface SlotInfoChangeResponse {
slotId: SlotId
status?: SlotStatus
volumeName?: string
recordingTime?: number
videoFormat?: VideoFormat
}

export interface RemoteInfoChangeResponse {
enabled?: boolean
}
20 changes: 10 additions & 10 deletions src/hyperdeck.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { EventEmitter } from 'events'
import { Socket } from 'net'

import EventEmitter = require('eventemitter3')
import { ResponseCodeType, GetResponseCodeType, AsynchronousCode } from './codes'
import { AbstractCommand } from './commands'
import * as AsyncHandlers from './asyncHandlers'
import { ResponseMessage } from './message'
import { DummyConnectCommand, WatchdogPeriodCommand, PingCommand, QuitCommand } from './commands/internal'
import { buildMessageStr, MultilineParser } from './parser'
import { HyperdeckAsyncEvents, HyperdeckEvents } from './events'

export interface HyperdeckOptions {
pingPeriod?: number // set to 0 to disable
Expand Down Expand Up @@ -34,7 +34,7 @@ class QueuedCommand<TResponse> {
}
}

export class Hyperdeck extends EventEmitter {
export class Hyperdeck extends EventEmitter<HyperdeckEvents> {
DEFAULT_PORT = 9993
RECONNECT_INTERVAL = 5000
DEBUG = false
Expand All @@ -47,7 +47,7 @@ export class Hyperdeck extends EventEmitter {
private _pingPeriod = 5000
private _pingInterval: NodeJS.Timer | null = null
private _lastCommandTime = 0
private _asyncHandlers: { [key: number]: AsyncHandlers.IHandler } = {}
private _asyncHandlers: { [key: number]: AsyncHandlers.IHandler<keyof HyperdeckAsyncEvents> } = {}
private _parser: MultilineParser

private _connectionActive = false // True when connected/connecting/reconnecting
Expand All @@ -73,7 +73,7 @@ export class Hyperdeck extends EventEmitter {
this.socket.setEncoding('utf8')
this.socket.on('error', (e) => {
if (this._connectionActive) {
this.emit('error', e)
this.emit('error', 'socket error', e)
}
})
this.socket.on('close', () => {
Expand All @@ -91,7 +91,7 @@ export class Hyperdeck extends EventEmitter {

for (const h in AsyncHandlers) {
try {
const handler: AsyncHandlers.IHandler = new (AsyncHandlers as any)[h]()
const handler: AsyncHandlers.IHandler<keyof HyperdeckAsyncEvents> = new (AsyncHandlers as any)[h]()
this._asyncHandlers[handler.responseCode] = handler
} catch (e) {
// ignore as likely not a class
Expand Down Expand Up @@ -189,7 +189,7 @@ export class Hyperdeck extends EventEmitter {
if (this.connected)
this._performPing().catch((e) => {
if (this._connectionActive) {
this.emit('error', e)
this.emit('error', 'ping failure', e)
}
})
}, this._pingPeriod)
Expand All @@ -199,9 +199,9 @@ export class Hyperdeck extends EventEmitter {

return c
})
.then((c) => {
.then((info) => {
this._connected = true
this.emit('connected', c)
this.emit('connected', info)
})
.catch((e) => {
this._connected = false
Expand Down Expand Up @@ -320,7 +320,7 @@ export class Hyperdeck extends EventEmitter {

const h = this._asyncHandlers[msg.code]
if (h) {
this.emit(h.eventName, h.deserialize(msg))
this.emit(h.eventName, h.deserialize(msg) as any)
} else {
this._log('unknown async response:', msg)
}
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1668,6 +1668,11 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==

eventemitter3@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==

execa@^5.0.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
Expand Down

0 comments on commit 93e7573

Please sign in to comment.