forked from svt/bridge
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcommands.js
170 lines (149 loc) · 4.25 KB
/
commands.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// SPDX-FileCopyrightText: 2022 Sveriges Television AB
//
// SPDX-License-Identifier: MIT
const random = require('./random')
const DIController = require('../shared/DIController')
const NoLocalHandlerError = require('./error/NoLocalHandlerError')
const InvalidArgumentError = require('./error/InvalidArgumentError')
/**
* Create a handler touple
* from a function and a
* return indicator
*
* @typedef {{
* call: (...any) => Promise.<any> | void,
* returns: Boolean
* }} HandlerTouple
*
* @param { (...any) => Promise.<any> | void } fn
* @param { Boolean } returns
* @returns {{ HandlerTouple }}
*/
function handlerFactory (fn, returns) {
return {
call: fn,
returns
}
}
class Commands {
#props
#handlers = new Map()
constructor (props) {
this.#props = props
this.#setup()
}
#setup () {
/*
Handle incoming messages and
call the registered handler
with the provided arguments
This code is also responsible
for executing any transactions
if the handler provides a
return value
*/
this.#props.Transport.onMessage(async message => {
const handler = this.#handlers.get(message.command)
if (!handler) return
const args = message.args || []
if (!handler.returns) {
handler.call(...args)
} else {
const transaction = args.shift()
try {
const res = await handler.call(...args)
this.executeRawCommand(transaction, res)
} catch (err) {
this.executeRawCommand(transaction, undefined, {
message: err.message,
cause: err.cause,
stack: err.stack,
name: err.name
})
}
}
})
}
/**
* Execute a command
* @param { String } command A command to execute
* @param { ...any } args Any arguments to pass to the handler, must be serializable
* @returns { Promise.<any> }
*/
executeCommand (command, ...args) {
return new Promise((resolve, reject) => {
const transactionId = random.string(12)
const transaction = `transaction:${transactionId}:${command}`
this.registerCommand(transaction, (res, err) => {
this.removeCommand(transaction)
if (err) {
const error = new Error(err.message)
error.stack = err.stack
error.cause = err.cause
error.name = err.name
return reject(error)
}
resolve(res)
}, false)
this.executeRawCommand(command, transaction, ...args)
})
}
/**
* Execute a command without
* creating a transaction
*
* No return values are available
* from commands called with this
* function, use executeCommand if
* return values are required
* @param { String } command The command to execute
* @param { ...any } args Arguments to pass to the command
*/
executeRawCommand (command, ...args) {
this.#props.Transport.send({ command, args: [...args] })
}
/**
* Register a command
* with a handler
* @param { String } command The command to register, should be scoped
* @param {
* (...Any) => Promise.<Any> | (...Any) => Void
* } handler A handler to invoke whenever
* the command is run
* @param { Boolean } returns Indicate whether or not
* the handler returns a value,
* defaults to true
*/
registerCommand (command, handler, returns = true) {
if (typeof handler !== 'function') {
throw new InvalidArgumentError('Parameter \'handler\' must be a function')
}
this.#handlers.set(command, handlerFactory(handler, returns))
this.#props.Transport.send({
command: 'commands.registerCommand',
args: [command]
})
}
/**
* Remove a command
* and remove its handler
* @param { String } command The command to remove
*/
removeCommand (command) {
/*
A plugin can only remove
its own commands
*/
if (!this.#handlers.has(command)) {
throw new NoLocalHandlerError('Command cannot be removeed as it wasn\'t created by this plugin')
}
this.#handlers.delete(command)
this.#props.Transport.send({
command: 'commands.removeCommand',
args: [command]
})
}
}
DIController.main.register('Commands', Commands, [
'Transport'
])