signalk-automation-plugin 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/plugin/index.js CHANGED
@@ -14,8 +14,14 @@ function sendJson (res, body, status) {
14
14
 
15
15
  function readJson (req) {
16
16
  return new Promise((resolve, reject) => {
17
- if (req.body && typeof req.body === 'object' && !Buffer.isBuffer(req.body)) {
18
- resolve(req.body)
17
+ const parsed = req.body
18
+ const hasParsed =
19
+ parsed &&
20
+ typeof parsed === 'object' &&
21
+ !Buffer.isBuffer(parsed) &&
22
+ Object.keys(parsed).length > 0
23
+ if (hasParsed || req.readableEnded) {
24
+ resolve(hasParsed ? parsed : (parsed && typeof parsed === 'object' && !Buffer.isBuffer(parsed) ? parsed : {}))
19
25
  return
20
26
  }
21
27
  let raw = ''
@@ -27,7 +33,7 @@ function readJson (req) {
27
33
  })
28
34
  req.on('end', () => {
29
35
  if (!raw) {
30
- resolve({})
36
+ resolve(parsed && typeof parsed === 'object' && !Buffer.isBuffer(parsed) ? parsed : {})
31
37
  return
32
38
  }
33
39
  try {
@@ -88,6 +94,8 @@ module.exports = function (app) {
88
94
  started: false,
89
95
  automations: [],
90
96
  helpers: [],
97
+ zones: [],
98
+ switches: [],
91
99
  errors: [],
92
100
  yaml: '',
93
101
  commits: []
@@ -110,6 +118,19 @@ module.exports = function (app) {
110
118
  try {
111
119
  const body = await readJson(req)
112
120
  const snap = rt.activateCommit(body.sha)
121
+ rewireAfterYamlChange()
122
+ sendJson(res, snap)
123
+ } catch (err) {
124
+ sendJson(res, { error: err.message }, 400)
125
+ }
126
+ }
127
+
128
+ function handleReload (req, res) {
129
+ const rt = requireRuntime(res)
130
+ if (!rt) return
131
+ try {
132
+ const snap = rt.reloadWorkingTree()
133
+ rewireAfterYamlChange()
113
134
  sendJson(res, snap)
114
135
  } catch (err) {
115
136
  sendJson(res, { error: err.message }, 400)
@@ -120,9 +141,37 @@ module.exports = function (app) {
120
141
  const rt = requireRuntime(res)
121
142
  if (!rt) return
122
143
  try {
144
+ const auto = rt.doc.automations.find((a) => a.id === id)
145
+ if (!auto) {
146
+ sendJson(res, { error: 'Unknown automation: ' + id }, 404)
147
+ return
148
+ }
123
149
  const body = await readJson(req)
150
+ if (!Object.prototype.hasOwnProperty.call(body, 'enabled')) {
151
+ throw new Error('enabled is required')
152
+ }
124
153
  rt.setEnabled(id, body.enabled)
125
- sendJson(res, { id, enabled: rt.isEnabled({ id, enabled: true }) && body.enabled })
154
+ sendJson(res, { id, enabled: rt.isEnabled(auto) })
155
+ } catch (err) {
156
+ sendJson(res, { error: err.message }, 400)
157
+ }
158
+ }
159
+
160
+ async function handleVerbose (req, res, id) {
161
+ const rt = requireRuntime(res)
162
+ if (!rt) return
163
+ try {
164
+ const auto = rt.doc.automations.find((a) => a.id === id)
165
+ if (!auto) {
166
+ sendJson(res, { error: 'Unknown automation: ' + id }, 404)
167
+ return
168
+ }
169
+ const body = await readJson(req)
170
+ if (!Object.prototype.hasOwnProperty.call(body, 'verbose')) {
171
+ throw new Error('verbose is required')
172
+ }
173
+ rt.setVerbose(id, body.verbose)
174
+ sendJson(res, { id, verbose: rt.isVerbose(auto) })
126
175
  } catch (err) {
127
176
  sendJson(res, { error: err.message }, 400)
128
177
  }
@@ -133,7 +182,7 @@ module.exports = function (app) {
133
182
  if (!rt) return
134
183
  try {
135
184
  const body = await readJson(req)
136
- const value = rt.setHelper(id, body.value)
185
+ const value = await rt.setHelper(id, body.value)
137
186
  emitHelper(id, value)
138
187
  sendJson(res, { id, value })
139
188
  } catch (err) {
@@ -141,11 +190,11 @@ module.exports = function (app) {
141
190
  }
142
191
  }
143
192
 
144
- function handleHelperReset (req, res, id) {
193
+ async function handleHelperReset (req, res, id) {
145
194
  const rt = requireRuntime(res)
146
195
  if (!rt) return
147
196
  try {
148
- const value = rt.resetHelper(id)
197
+ const value = await rt.resetHelper(id)
149
198
  emitHelper(id, value)
150
199
  sendJson(res, { id, value: value == null ? null : value })
151
200
  } catch (err) {
@@ -153,25 +202,55 @@ module.exports = function (app) {
153
202
  }
154
203
  }
155
204
 
205
+ async function handleRun (req, res, id) {
206
+ const rt = requireRuntime(res)
207
+ if (!rt) return
208
+ try {
209
+ const record = await rt.runNow(id)
210
+ sendJson(res, record)
211
+ } catch (err) {
212
+ sendJson(res, { error: err.message }, 404)
213
+ }
214
+ }
215
+
156
216
  plugin.registerWithRouter = function (router) {
217
+ const write = typeof router.access === 'function' ? router.access('readwrite') : router
157
218
  router.get('/status', (req, res) => sendJson(res, snapshot()))
158
219
  router.get('/yaml', (req, res) => sendJson(res, { yaml: snapshot().yaml, sha: snapshot().activeSha }))
159
220
  router.get('/commits', (req, res) => sendJson(res, { commits: snapshot().commits, activeSha: snapshot().activeSha }))
221
+ router.get('/diff', (req, res) => {
222
+ const rt = requireRuntime(res)
223
+ if (!rt) return
224
+ try {
225
+ sendJson(res, rt.diffCommit(queryParam(req, 'sha')))
226
+ } catch (err) {
227
+ sendJson(res, { error: err.message }, 400)
228
+ }
229
+ })
160
230
  router.get('/automations/:id/traces', (req, res) => {
161
231
  const rt = requireRuntime(res)
162
232
  if (!rt) return
163
233
  sendJson(res, { id: req.params.id, traces: rt.traces(req.params.id) })
164
234
  })
165
- router.post('/live', (req, res) => {
235
+ write.post('/live', (req, res) => {
166
236
  handleLive(req, res)
167
237
  })
168
- router.post('/automations/:id/enabled', (req, res) => {
238
+ write.post('/reload', (req, res) => {
239
+ handleReload(req, res)
240
+ })
241
+ write.post('/automations/:id/enabled', (req, res) => {
169
242
  handleEnabled(req, res, req.params.id)
170
243
  })
171
- router.put('/helpers/:id', (req, res) => {
244
+ write.post('/automations/:id/verbose', (req, res) => {
245
+ handleVerbose(req, res, req.params.id)
246
+ })
247
+ write.post('/automations/:id/run', (req, res) => {
248
+ handleRun(req, res, req.params.id)
249
+ })
250
+ write.put('/helpers/:id', (req, res) => {
172
251
  handleHelperPut(req, res, req.params.id)
173
252
  })
174
- router.post('/helpers/:id/reset', (req, res) => {
253
+ write.post('/helpers/:id/reset', (req, res) => {
175
254
  handleHelperReset(req, res, req.params.id)
176
255
  })
177
256
  }
@@ -185,6 +264,17 @@ module.exports = function (app) {
185
264
  router.get(prefix + '/commits', (req, res) =>
186
265
  sendJson(res, { commits: snapshot().commits, activeSha: snapshot().activeSha })
187
266
  )
267
+ router.get(prefix + '/diff', (req, res) => {
268
+ if (!runtime) {
269
+ sendJson(res, { error: 'plugin not started' }, 409)
270
+ return
271
+ }
272
+ try {
273
+ sendJson(res, runtime.diffCommit(queryParam(req, 'sha')))
274
+ } catch (err) {
275
+ sendJson(res, { error: err.message }, 400)
276
+ }
277
+ })
188
278
  router.get(prefix + '/automations/:id/traces', (req, res) => {
189
279
  if (!runtime) {
190
280
  sendJson(res, { error: 'plugin not started' }, 409)
@@ -217,14 +307,38 @@ module.exports = function (app) {
217
307
 
218
308
  function putPath (p, value) {
219
309
  return new Promise((resolve, reject) => {
220
- if (typeof app.putSelfPath === 'function') {
221
- app.putSelfPath(p, value, (err) => (err ? reject(err) : resolve()))
310
+ if (typeof app.putSelfPath !== 'function') {
311
+ app.handleMessage(PLUGIN_ID, {
312
+ updates: [{ values: [{ path: p, value }] }]
313
+ })
314
+ resolve()
222
315
  return
223
316
  }
224
- app.handleMessage(PLUGIN_ID, {
225
- updates: [{ values: [{ path: p, value }] }]
226
- })
227
- resolve()
317
+ let settled = false
318
+ const finish = (err) => {
319
+ if (settled) return
320
+ settled = true
321
+ if (err) reject(err)
322
+ else resolve()
323
+ }
324
+ const fromReply = (reply) => {
325
+ if (reply == null) return finish(null)
326
+ if (reply instanceof Error) return finish(reply)
327
+ const code = reply.statusCode
328
+ if (typeof code === 'number' && code >= 400) {
329
+ finish(new Error(reply.message || ('PUT failed (' + code + ') for ' + p)))
330
+ return
331
+ }
332
+ finish(null)
333
+ }
334
+ try {
335
+ const ret = app.putSelfPath(p, value, fromReply)
336
+ if (ret && typeof ret.then === 'function') {
337
+ ret.then(fromReply, finish)
338
+ }
339
+ } catch (err) {
340
+ finish(err)
341
+ }
228
342
  })
229
343
  }
230
344
 
@@ -265,57 +379,90 @@ module.exports = function (app) {
265
379
  scriptTimeoutSeconds: options.scriptTimeoutSeconds,
266
380
  put: putPath,
267
381
  notify,
268
- log: { debug: app.debug ? app.debug.bind(app) : () => {}, error: app.error ? app.error.bind(app) : console.error }
382
+ log: {
383
+ debug: app.debug ? app.debug.bind(app) : () => {},
384
+ error: app.error ? app.error.bind(app) : console.error,
385
+ info: (msg) => {
386
+ if (app.debug) app.debug(msg)
387
+ console.log('[signalk-automation-plugin] ' + msg)
388
+ }
389
+ }
269
390
  })
270
391
  runtime.load()
392
+ runtime.seedFromSelf()
393
+ rewireAfterYamlChange()
394
+ runtime.evaluateOnStart().catch((err) => app.error && app.error(err))
395
+
396
+ timers.push(setInterval(() => {
397
+ runtime.tickSchedule().catch((err) => app.error && app.error(err))
398
+ }, 15000))
399
+ }
400
+
401
+ function clearSubscriptions () {
402
+ unsubscribes.splice(0).forEach((f) => {
403
+ try {
404
+ f()
405
+ } catch (_) {}
406
+ })
407
+ }
271
408
 
409
+ function emitAllHelpers () {
410
+ if (!runtime) return
272
411
  for (const [id, value] of Object.entries(runtime.helperValues)) {
273
412
  emitHelper(id, value)
274
413
  if (typeof app.registerPutHandler === 'function') {
275
414
  app.registerPutHandler('vessels.self', helpers.helperPath(id), (context, p, v, cb) => {
276
- try {
277
- const next = runtime.setHelper(id, v)
278
- emitHelper(id, next)
279
- if (cb) cb({ state: 'COMPLETED' })
280
- } catch (err) {
281
- if (cb) cb({ state: 'COMPLETED', statusCode: 400, message: err.message })
282
- }
415
+ Promise.resolve(runtime.setHelper(id, v))
416
+ .then((next) => {
417
+ emitHelper(id, next)
418
+ if (cb) cb({ state: 'COMPLETED' })
419
+ })
420
+ .catch((err) => {
421
+ if (cb) cb({ state: 'COMPLETED', statusCode: 400, message: err.message })
422
+ })
423
+ return { state: 'PENDING' }
283
424
  })
284
425
  }
285
426
  }
427
+ }
286
428
 
429
+ function subscribePaths () {
430
+ if (!runtime || !app.subscriptionmanager) return
287
431
  const paths = require('../lib/engine').collectPaths(runtime.doc)
288
- if (paths.length && app.subscriptionmanager) {
289
- app.subscriptionmanager.subscribe(
290
- {
291
- context: 'vessels.self',
292
- subscribe: paths.map((p) => ({ path: p }))
293
- },
294
- unsubscribes,
295
- (err) => app.error && app.error(err),
296
- (delta) => {
297
- const src = delta && delta.updates && delta.updates[0] && delta.updates[0].$source
298
- ;(delta.updates || []).forEach((u) => {
299
- const source = u.$source || src
300
- ;(u.values || []).forEach((v) => {
301
- runtime.handlePathChange(v.path, v.value, source).catch((err) => {
302
- if (app.error) app.error(err)
303
- })
432
+ if (!paths.length) return
433
+ app.subscriptionmanager.subscribe(
434
+ {
435
+ context: 'vessels.self',
436
+ subscribe: paths.map((p) => ({ path: p }))
437
+ },
438
+ unsubscribes,
439
+ (err) => app.error && app.error(err),
440
+ (delta) => {
441
+ const src = delta && delta.updates && delta.updates[0] && delta.updates[0].$source
442
+ ;(delta.updates || []).forEach((u) => {
443
+ const source = u.$source || src
444
+ ;(u.values || []).forEach((v) => {
445
+ runtime.handlePathChange(v.path, v.value, source).catch((err) => {
446
+ if (app.error) app.error(err)
304
447
  })
305
448
  })
306
- }
307
- )
308
- }
449
+ })
450
+ }
451
+ )
452
+ }
309
453
 
310
- timers.push(setInterval(() => {
311
- runtime.tickSchedule().catch((err) => app.error && app.error(err))
312
- }, 15000))
454
+ function setStatusFromRuntime () {
455
+ if (!app.setPluginStatus || !runtime) return
456
+ const n = runtime.doc.automations.length
457
+ const err = runtime.doc.errors.length
458
+ app.setPluginStatus(n + ' automations' + (err ? ', ' + err + ' YAML error(s)' : ''))
459
+ }
313
460
 
314
- if (app.setPluginStatus) {
315
- const n = runtime.doc.automations.length
316
- const err = runtime.doc.errors.length
317
- app.setPluginStatus(n + ' automations' + (err ? ', ' + err + ' YAML error(s)' : ''))
318
- }
461
+ function rewireAfterYamlChange () {
462
+ clearSubscriptions()
463
+ emitAllHelpers()
464
+ subscribePaths()
465
+ setStatusFromRuntime()
319
466
  }
320
467
 
321
468
  plugin.stop = function () {
@@ -336,5 +483,11 @@ function fsMkdir (dir) {
336
483
  fs.mkdirSync(dir, { recursive: true })
337
484
  }
338
485
 
486
+ function queryParam (req, name) {
487
+ if (req.query && req.query[name] != null) return String(req.query[name])
488
+ const raw = String(req.url || '').split('?')[1] || ''
489
+ return new URLSearchParams(raw).get(name) || ''
490
+ }
491
+
339
492
  module.exports.app = 'app'
340
493
  module.exports.options = 'options'