@naanlang/naan 1.4.4 → 1.6.0

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.
Files changed (61) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +4 -4
  3. package/bin/index.js +25 -3
  4. package/dist/env_web.js +164 -22
  5. package/dist/naan.min.js +5 -5
  6. package/frameworks/browser/browser.nlg +2 -2
  7. package/frameworks/browser/https_request.nlg +2 -2
  8. package/frameworks/browser/sworker.js +28 -26
  9. package/frameworks/browser/terminals.nlg +342 -700
  10. package/frameworks/browser/worker.nlg +255 -0
  11. package/frameworks/browser/workers.nlg +5 -5
  12. package/frameworks/browser/ws_client.nlg +15 -10
  13. package/frameworks/client/apiclient.nlg +204 -19
  14. package/frameworks/client/client.nlg +22 -2
  15. package/frameworks/client/psm_client.nlg +12 -3
  16. package/frameworks/client/relaycon.nlg +87 -161
  17. package/frameworks/common/events.nlg +101 -0
  18. package/frameworks/common/repltools.nlg +80 -1
  19. package/frameworks/node/apiserver.nlg +39 -246
  20. package/frameworks/node/filesystem.nlg +39 -12
  21. package/frameworks/node/gitter.nlg +9 -7
  22. package/frameworks/node/http_request.nlg +34 -13
  23. package/frameworks/node/https_request.nlg +8 -3
  24. package/frameworks/node/node.nlg +2 -0
  25. package/frameworks/node/pty.nlg +346 -0
  26. package/frameworks/node/shell.nlg +4 -4
  27. package/frameworks/node/worker.nlg +409 -539
  28. package/frameworks/node/ws_client.nlg +2 -2
  29. package/frameworks/project/build.nlg +1 -1
  30. package/frameworks/project/projects.nlg +2 -1
  31. package/frameworks/running/debugnub.nlg +17 -4
  32. package/frameworks/running/executors.nlg +27 -274
  33. package/frameworks/running/instances.nlg +393 -0
  34. package/frameworks/running/remote_req.nlg +143 -0
  35. package/frameworks/running/remote_res.nlg +89 -0
  36. package/frameworks/running/taskexec.nlg +2 -2
  37. package/frameworks/service/imp.nlg +736 -0
  38. package/frameworks/service/model.nlg +71 -0
  39. package/frameworks/service/nubnode.nlg +105 -0
  40. package/frameworks/service/nubweb.nlg +106 -0
  41. package/frameworks/service/service.nlg +28 -0
  42. package/frameworks/storage/dbt_pouch.nlg +26 -13
  43. package/frameworks/storage/psm.nlg +39 -5
  44. package/frameworks/storage/psm_dbtables.nlg +10 -2
  45. package/lib/browser/env_web.js +171 -29
  46. package/lib/browser/env_webworker.js +91 -5
  47. package/lib/core/naanlib.js +5 -5
  48. package/lib/env_node.js +184 -8
  49. package/lib/env_nodeworker.js +74 -1
  50. package/package.json +1 -1
  51. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +2 -1
  52. package/plugins/serviceAws/aws_dynamo.nlg +1 -0
  53. package/plugins/serviceAws/aws_s3.nlg +3 -3
  54. package/plugins/serviceAws/aws_sqs.nlg +3 -1
  55. package/plugins/serviceAws/aws_utils.nlg +3 -1
  56. package/plugins/serviceAws/dbt_aws.nlg +22 -20
  57. package/plugins/serviceAws/psm_aws.nlg +2 -1
  58. package/plugins/serviceNexara/speechrec.nlg +10 -6
  59. package/test/test_02_context.nlg +24 -1
  60. package/test/test_03_jsinterop.nlg +32 -3
  61. package/plugins/serviceGC/russian_trusted_root_ca_pem.crt +0 -33
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2019-2024 by Richard C. Zulch
9
+ * Copyright (c) 2019-2026 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -15,363 +15,375 @@
15
15
  *
16
16
  */
17
17
 
18
- gWorkers;
18
+ gWorkerController = false;
19
19
 
20
20
 
21
21
  /*
22
- * workCurrent
22
+ * CurBot
23
23
  *
24
- * Attach a remote terminal interface to the current NodeJS thread.
24
+ * Attach a remote terminal interface to the current NodeJS thread.
25
+ *
26
+ * Options:
27
+ * {
28
+ * name: <string> // target name, e.g. "Play"
29
+ * type: <string> // target category, e.g. "Local"
30
+ * }
25
31
  *
26
32
  */
27
33
 
28
- closure workCurrent(workco, workerID, startup, local current) {
29
- global(js)
30
- current = require("../running/executors.nlg").ExecutorBase(workco, workerID, startup.name)
31
- current.workerID = workerID
32
- current.name = startup.name
33
- current.links = []
34
- current.termif = xnew({
34
+ closure CurBot(options, local curbot) {
35
+ global(App, js)
36
+ curbot = new(object, this)
37
+ curbot.topsubs = []
38
+ options = merge({
39
+ type: "Host"
40
+ }, options)
41
+ curbot.type = options.type
42
+ curbot.name = options.name
43
+
44
+ curbot.termif = xnew({
35
45
  textout: ONtextOut.proc
36
46
  debugtext: ONdebugText.proc
37
47
  status: ONstatus.proc
38
48
  debugout: ONdebugOut.proc
39
49
  messageout: ONmessageOut.proc
50
+ ioctl: ONioctl.proc
40
51
  })
41
- current.guid = 1
42
- current.host = js.t // our magic Naan controller reference
43
-
52
+ curbot.guid = 1
53
+ curbot.host = js.t // our magic Naan controller reference
54
+
55
+ curbot.instanceID = UUID()
56
+ curbot.sender = App.imp.register(curbot, {
57
+ name: curbot.name
58
+ type: curbot.type
59
+ instanceID: curbot.instanceID
60
+ services: ["naanide-server-main"]
61
+ })
62
+
63
+ // subscribe
64
+ //
65
+ // Add an InTop to our subscribers if not already known.
66
+ //
67
+ function subscribe(destID) {
68
+ if !curbot.topsubs.find(function(item) { item == destID }) {
69
+ curbot.topsubs.push(destID)
70
+ curbot.sender(destID, {
71
+ id: "workerDestID"
72
+ workerDestID: App.imp.us.instanceID // our remote responder
73
+ })
74
+ }
75
+ }
76
+
77
+ // unsubscribe
78
+ //
79
+ // Remove an InTop from our subscribers if present.
80
+ //
81
+ function unsubscribe(destID) {
82
+ curbot.topsubs = curbot.topsubs.filter(function(item) { item != destID })
83
+ }
84
+
85
+ // notifyAll
86
+ //
87
+ // Send the message to all our subscribers.
88
+ //
89
+ function notifyAll(message, exceptID, local topDestID) {
90
+ for topDestID in curbot.topsubs
91
+ if topDestID != exceptID
92
+ curbot.sender(topDestID, message)
93
+ }
94
+
95
+ // impReceive
96
+ //
97
+ // Received a payload.
98
+ //
99
+ curbot.impReceive = function impReceive(message, sourceID) {
100
+ // js.g.console.log("CurBot.impReceive(${sourceID}):\n", Dialect.print(new(message)))
101
+ if message.id == "destroy"
102
+ { }
103
+ else if message.id == "subscribe"
104
+ subscribe(sourceID)
105
+ else if message.id == "unsubscribe"
106
+ unsubscribe(sourceID)
107
+ else if message.id == "typed"
108
+ notifyAll(message, sourceID)
109
+ else {
110
+ message.data._guid = sourceID
111
+ curbot.host.DispatchMessage(message)
112
+ }
113
+ }
114
+
115
+ // impUnknown
116
+ //
117
+ // Received an unknown destination notice.
118
+ //
119
+ curbot.impUnknown = function impUnknown(unknownID, sourceID, gateway) {
120
+ if curbot.topsubs[unknownID] { // did not have courtesy to unsubscribe!
121
+ unsubscribe(unknownID)
122
+ return
123
+ }
124
+ if !gateway
125
+ debuglog("CurBot.impUnknownID:", unknownID, "sourceID:", sourceID)
126
+ }
127
+
44
128
  // replyHook
45
129
  //
46
130
  // Intercept the ReplyMessage function to see if we should handle it.
47
131
  //
48
132
  function replyHook(data) {
133
+ // js.g.console.log("CurBot.replyHook:\n", Dialect.print(new(data)))
134
+ // debuglog("CurBot.replyHook:\n", Dialect.print(new(data)))
135
+ /* ###
49
136
  if data._guid
50
137
  workco.uworkers[data._guid].msgport.postMessage({
51
138
  id: "targetreceive"
52
139
  data: data
53
140
  })
141
+ */
54
142
  }
55
- current.host.OnReplyHook(replyHook.proc)
143
+ curbot.host.OnReplyHook(replyHook.proc)
56
144
 
57
145
  // ONtextOut
146
+ //
58
147
  // Process console text output by Naan.
59
-
148
+ //
60
149
  function ONtextOut(text) {
61
- sendAll({
150
+ notifyAll({
62
151
  id: "textout",
63
152
  text: text,
64
153
  })
65
154
  }
66
-
155
+
67
156
  // ONdebugText
157
+ //
68
158
  // Process console debug text output by Naan.
69
-
159
+ //
70
160
  function ONdebugText(text, level) {
71
- sendAll({
161
+ notifyAll({
72
162
  id: "debugtext",
73
163
  text: text,
74
164
  level: level
75
165
  })
76
166
  }
77
-
167
+
78
168
  // ONstatus
169
+ //
79
170
  // Process status update output by Naan.
80
-
171
+ //
81
172
  function ONstatus(status) {
82
- sendAll({
173
+ notifyAll({
83
174
  id: "status",
84
175
  status: status
85
176
  })
86
177
  }
87
-
178
+
88
179
  // ONdebugOut
180
+ //
89
181
  // Process debugger output.
90
-
182
+ //
91
183
  function ONdebugOut(data) {
92
- sendAll({
184
+ notifyAll({
93
185
  id: "debugout",
94
186
  data: data
95
187
  })
96
188
  }
97
-
98
- // ONmessageOut
99
- // Process message output.
100
-
101
- function ONmessageOut(data) {
102
- if data._guid
103
- return // already replied
104
- current.execReceived(data)
105
- sendAll({
106
- id: "targetout",
107
- data: data
108
- })
109
- }
110
-
111
- // removeLink
112
- // Remove a link.
113
-
114
- function removeLink(link) {
115
- current.links = current.links.filter(function(item) {
116
- !(item.link eq link)
117
- })
118
- }
119
-
120
- // exitNotify
121
- // Notify all links that we have terminated.
122
-
123
- function exitNotify(exitCode, local link) {
124
- workco.uworkers[current.guid] = undefined
125
- for link in current.links
126
- link.exitWorker(exitCode)
127
- }
128
-
129
- // sendAll
130
- // Send a message to all links.
131
-
132
- function sendAll(msg, local link) {
133
- for link in current.links
134
- link.responder(msg)
135
- }
136
-
137
- //
138
- // attach
139
- //
140
- // Attach a link.
141
- //
142
-
143
- current.attach = function attach(link, responder) {
144
- removeLink(link)
145
- current.links.push({
146
- link: link // a link to us
147
- responder: responder // link's responder
148
- })
149
- if current.links.length == 1
150
- current.host.Attach(current.termif) // attached while one link is active
151
- }
152
-
153
- //
154
- // detach
155
- //
156
- // Detach a link.
157
- //
158
-
159
- current.detach = function detach(link) {
160
- removeLink(link)
161
- if current.links.length == 0
162
- current.host.Detach(current.termif) // attached while one link is active
163
- }
164
-
165
- //
166
- // target.PostMessage
167
- //
168
- // Post a message to the main thread.
169
- //
170
- current.PostMessage = function PostMessage(data) {
171
- js.t.DispatchMessage(data)
172
- list(false, { posted: true })
173
- }
174
189
 
190
+ // ONmessageOut
175
191
  //
176
- // msgin
192
+ // Process message output.
177
193
  //
178
- // Send a message to the host interface.
179
-
180
- current.msgin = function msgin(msg) {
181
- current.host.DispatchMessage(msg)
194
+ function ONmessageOut(data, local _guid) {
195
+ if data._guid {
196
+ _guid = data._guid
197
+ data._guid = undefined
198
+ curbot.sender(_guid, {
199
+ id: "targetout",
200
+ data: data
201
+ })
202
+ return
203
+ }
182
204
  }
183
205
 
206
+ // ONioctl
184
207
  //
185
- // echo
208
+ // Process ioctl.
186
209
  //
187
- // Echo a message to the *other* links.
188
-
189
- current.echo = function echo(msg, thislink, local linkref) {
190
- for linkref in current.links
191
- if !(linkref.link eq thislink)
192
- linkref.responder(msg)
210
+ function ONioctl(op, arg) {
211
+ notifyAll({
212
+ id: "ioctl",
213
+ op: op,
214
+ arg: arg
215
+ })
193
216
  }
194
217
 
195
- //
196
- // vitalUpdate
197
- //
198
- // Worker thread birth/death/enumeration.
218
+ curbot.host.Attach(curbot.termif)
199
219
 
200
- current.vitalUpdate = function vitalUpdate(msg) {
201
- sendAll(msg)
202
- }
220
+ curbot.subprocess = MakeSubProcessor(curbot) // Enable PTY subprocesses on our terminal
203
221
 
204
222
  // finis
205
- current
223
+
224
+ curbot
206
225
  };
207
226
 
208
227
 
209
228
  /*
210
- * WorkerConnect
229
+ * NowBot
211
230
  *
212
- * Return the existing web worker for a workerGUID, or false if it does not exist.
231
+ * Spawn a Naan worker in its own thread.
232
+ *
233
+ * Options:
234
+ * {
235
+ * name: <string> // target name, e.g. "Play"
236
+ * type: <string> // target category, e.g. "Local"
237
+ * startup: <dictionary> // startup options
238
+ * topOriginID: <string> // so top can route to us through IMP
239
+ * }
213
240
  *
214
241
  */
215
242
 
216
- function WorkerConnect(workerGUID) {
217
- gWorkers[workerGUID]
218
- }
219
-
243
+ closure NowBot(options, local nowbot, subChannel) {
244
+ global(App, js, threads, JSpath)
245
+ nowbot = new(object, this)
246
+ nowbot.topsubs = []
247
+ options = merge({
248
+ type: "node-worker"
249
+ }, options)
220
250
 
221
- /*
222
- * workMainProxy
223
- *
224
- * Make a nodejs proxy representing the main thread. This runs inside the worker and allows it
225
- * to do remote execution with Main.
226
- *
227
- */
251
+ // options = { execArgv: ["--inspect-brk"] } // uncomment for NodeJS debugging
252
+ nowbot.thread = xnew(threads.Worker, JSpath.resolve(js.d, "lib/node_worker.js"), options)
253
+ subChannel = xnew(threads.MessageChannel)
254
+ nowbot.thread.postMessage({ hereIsYourPort: subChannel.port1 }, [subChannel.port1])
255
+ nowbot.msgport = subChannel.port2
256
+
257
+ nowbot.instanceID = UUID()
258
+ nowbot.sender = App.imp.register(nowbot, {
259
+ name: options.name
260
+ type: options.type
261
+ instanceID: nowbot.instanceID
262
+ services: ["node-worker"]
263
+ })
228
264
 
229
- closure workMainProxy(track, name, options, local target) {
230
- global(js)
231
- target = require("../running/executors.nlg").ExecutorBase(track, "mainProxy", name)
232
- target.name = name
265
+ // If this NowBot is created remotely then we need to send a message from our new instanceID
266
+ // to the InTop origin so that it can send messages to us. The intermediate gateways save the
267
+ // route to allow replies.
268
+ if options.topOriginID && options.topOriginID != App.imp.us.instanceID
269
+ nowbot.sender(options.topOriginID) // create route top->bottom
270
+
271
+ // subscribe
272
+ //
273
+ // Add an InTop to our subscribers if not already known.
274
+ //
275
+ function subscribe(destID) {
276
+ if !nowbot.topsubs.find(function(item) { item == destID }) {
277
+ nowbot.topsubs.push(destID)
278
+ if nowbot.workerDestID {
279
+ App.imp.inform(nowbot.workerDestID, destID) // provide path to workerID
280
+ nowbot.sender(destID, { // send to new subscriber
281
+ id: "workerDestID"
282
+ workerDestID: nowbot.workerDestID
283
+ })
284
+ }
285
+ }
286
+ }
233
287
 
288
+ // unsubscribe
234
289
  //
235
- // target.PostMessage
236
- //
237
- // Post a message to the main thread.
290
+ // Remove an InTop from our subscribers if present.
238
291
  //
239
- target.PostMessage = function PostMessage(data) {
240
- js.t.DispatchMessage(data)
241
- list(false, { posted: true })
292
+ function unsubscribe(destID) {
293
+ nowbot.topsubs = nowbot.topsubs.filter(function(item) { item != destID })
242
294
  }
243
295
 
296
+ // notifyAll
244
297
  //
245
- // onreceive
246
- //
247
- // Process a received message from the main thread, typically in response.
298
+ // Send the message to all our subscribers.
248
299
  //
249
- function onreceive(data) {
250
- target.execReceived(data)
300
+ nowbot.notifyAll = function notifyAll(message, exceptID, local topDestID) {
301
+ for topDestID in nowbot.topsubs
302
+ if topDestID != exceptID
303
+ nowbot.sender(topDestID, message)
251
304
  }
252
-
253
- // destroy
305
+
306
+ // impReceive
254
307
  //
255
- // Destroy the execution instance.
308
+ // Received a payload.
256
309
  //
257
- target.destroy = function destroy() {
258
- target.execClosed(Error("instance destroyed"))
259
- track.delete(target)
260
- }
261
-
262
- js.t.OnReceive(onreceive.proc)
263
- track.add(target)
264
-
265
- // finis
266
-
267
- list(false, target)
268
- };
269
-
270
-
271
- /*
272
- * WorkerToMain
273
- *
274
- * Connect our worker to the main thread, returning a result tuple with an executor representing
275
- * Main. Use this to create a context object for Main, allowing remote evaluation.
276
- *
277
- */
278
-
279
- closure WorkerToMain(local nideRunning, track) {
280
- // mainProxy -- link tracker to main thread proxy
281
- function mainProxy(track, name, options) {
282
- workMainProxy(track, name, options)
310
+ nowbot.impReceive = function impReceive(message, sourceID) {
311
+ if message.id == "destroy"
312
+ terminate()
313
+ else if message.id == "subscribe"
314
+ subscribe(sourceID)
315
+ else if message.id == "unsubscribe"
316
+ unsubscribe(sourceID)
317
+ else if message.id == "typed"
318
+ notifyAll(message, sourceID)
319
+ else {
320
+ if message.id == "keyline" && nowbot.keyin
321
+ nowbot.keyin(message.text) // terminal keyboard -> pty subprocess
322
+ else
323
+ nowbot.msgport.postMessage(message)
324
+ }
283
325
  }
284
-
285
- if !js.g {
286
- error = Error("WorkerToMain only available in node workers")
287
- return (list(error))
326
+
327
+ // impUnknown
328
+ //
329
+ // Received an unknown destination notice.
330
+ //
331
+ nowbot.impUnknown = function impUnknown(unknownID, sourceID, gateway) {
332
+ if nowbot.topsubs[unknownID] { // did not have courtesy to unsubscribe!
333
+ unsubscribe(unknownID)
334
+ return
335
+ }
336
+ if !gateway
337
+ debuglog("NowBot.impUnknownID:", unknownID, "sourceID:", sourceID)
288
338
  }
289
- nideRunning = require("naanlib:frameworks/running/executors.nlg")
290
- track = nideRunning.ExecutorTracker()
291
- track.register(mainProxy, "proxy")
292
- track.spawn("proxy") // returns result tuple
293
- };
294
-
295
-
296
- /*
297
- * workThread
298
- *
299
- * Spawn a Naan worker in its own thread.
300
- *
301
- */
302
-
303
- closure workThread(workco, workerID, startup, guid
304
- local worker, options, subChannel, deathWatcher) {
305
- global(js, threads, JSpath)
306
- worker = require("../running/executors.nlg").ExecutorBase(workco, workerID, startup.name)
307
- worker.name = startup.name
308
- worker.workerID = workerID
309
- worker.links = []
310
- worker.guid = guid || UUID()
311
339
 
312
- // create our worker thread
313
-
314
- // options = { execArgv: ["--inspect-brk"] } // uncomment for NodeJS debugging
315
- worker.thread = xnew(threads.Worker, JSpath.resolve(js.d, "lib/node_worker.js"), options)
316
- subChannel = xnew(threads.MessageChannel)
317
- worker.thread.postMessage({ hereIsYourPort: subChannel.port1 }, [subChannel.port1])
318
- worker.msgport = subChannel.port2
319
-
320
- // removeLink
321
- // Remove a link.
322
-
323
- function removeLink(link) {
324
- worker.links = worker.links.filter(function(item) {
325
- !(item.link eq link)
340
+ // nowbot
341
+ //
342
+ // An exception occurred on the worker thread. Since Naan catches all exceptions this should
343
+ // not occur normally.
344
+ //
345
+ nowbot.thread.on("error", function onerror(error) {
346
+ debuglog("worker terminated with error", options.type, options.name, error)
347
+ notifyAll({
348
+ id: "terminated"
349
+ error: error
326
350
  })
327
- }
328
-
329
- // exitNotify
330
- // Notify all links that we have terminated.
331
-
332
- function exitNotify(exitCode, local link) {
333
- workco.uworkers[worker.guid] = undefined
334
- for link in worker.links
335
- link.exitWorker(exitCode)
336
- worker.links = []
337
- deathWatcher(workerID)
338
- }
339
-
340
- // sendAll
341
- // Send a message to all links.
351
+ destroy()
352
+ })
342
353
 
343
- function sendAll(msg, local link) {
344
- for link in worker.links
345
- link.responder(msg)
346
- }
347
-
354
+ // onexit
348
355
  //
349
- // onerror
356
+ // Note that the worker thread has terminated.
350
357
  //
351
- // An exception occurred on the worker thread. Since Naan catches all exceptions this should
352
- // not occur normally.
353
-
354
- worker.thread.on("error", function onerror(error) {
355
- debuglog("worker terminated with error", workerID, error)
356
- exitNotify(-1)
358
+ nowbot.thread.on("exit", function onexit(exitCode) {
359
+ notifyAll({
360
+ id: "terminated"
361
+ exitCode: exitCode
362
+ })
363
+ destroy()
357
364
  })
358
365
 
366
+ // destroy
359
367
  //
360
- // onexit
368
+ // Ensure the worker is destroyed.
361
369
  //
362
- // Note that the worker thread has terminated.
363
-
364
- worker.thread.on("exit", function onexit(exitCode) {
365
- exitNotify(exitCode)
366
- })
370
+ nowbot.destroy = function destroy() {
371
+ terminate()
372
+ nowbot.thread = false
373
+ nowbot.impconn.close()
374
+ nowbot.impconn = false
375
+ App.imp.unregister(nowbot.instanceID)
376
+ }
367
377
 
378
+ // NaaN message
368
379
  //
369
- // naanmsg
380
+ // A message has been received from NaaN running on the worker thread.
370
381
  //
371
- // A message has been received from Naan running on the worker thread.
372
-
373
- worker.msgport.on("message", function naanmsg(msg, local startmsg) {
374
- if msg.id == "loaded" {
382
+ nowbot.msgport.on("message", function (msg, local startmsg) {
383
+ if msg.id == "naan-imp-node" {
384
+ nowbot.impconn.dispatch(new(msg.data))
385
+ return
386
+ } else if msg.id == "loaded" {
375
387
  startmsg = {
376
388
  id: "start"
377
389
  state: false // resume state, if we have any
@@ -379,338 +391,197 @@ closure workThread(workco, workerID, startup, guid
379
391
  dirpath: js.d
380
392
  workerID: workerID
381
393
  workerGUID: worker.guid
394
+ metadata: {
395
+ mainInstanceID: App.imp.us.instanceID
396
+ name: options.name
397
+ }
382
398
  }
383
- if startup.initcmds
384
- startmsg.altcmd = startup.initcmds.concat("\n")
385
- if startup.dirpath
386
- startmsg.dirpath = startup.dirpath
387
- worker.msgport.postMessage(startmsg)
388
- worker.links[0].responder({ // only send this to first link
389
- id: "started" // don't want multiple initializers
390
- }) }
391
- else if msg.id == "targetsend" {
392
- msg.data._guid = worker.guid
399
+ if options.startup.initcmds
400
+ startmsg.altcmd = options.startup.initcmds.concat("\n")
401
+ if options.startup.dirpath
402
+ startmsg.dirpath = options.startup.dirpath
403
+ nowbot.msgport.postMessage(startmsg)
404
+ notifyAll({
405
+ id: "started"
406
+ })
407
+ } else if msg.id == "targetsend" {
408
+ msg.data._guid = nowbot.guid
393
409
  js.t.DispatchMessage({
394
410
  id: "targetin"
395
411
  data: msg.data
396
412
  }) }
397
- else {
398
- if msg.id == "status"
399
- worker.lastStatus = milliseconds()
400
- else if msg.id == "targetout"
401
- worker.execReceived(msg.data)
402
- sendAll(msg) } // default is just return message to the client
413
+ else if msg.id == "textout" && nowbot.naanin
414
+ nowbot.naanin(msg.text) // naan output -> pty subprocess
415
+ else
416
+ notifyAll(msg)
403
417
  })
404
418
 
405
- //
406
419
  // terminate
407
420
  //
408
- // Terminate this worker asap.
409
-
410
- worker.terminate = function terminate(local error, exitCode) {
411
- `(error, exitCode) = await(worker.thread.terminate())
412
- exitNotify(exitCode)
413
- }
414
-
415
- //
416
- // attach
417
- //
418
- // Attach a connection to our worker.
419
- //
420
-
421
- worker.attach = function attach(link, responder) {
422
- removeLink(link)
423
- worker.links.push({
424
- link: link // a link to us
425
- responder: responder // link's responder
426
- })
427
- }
428
-
421
+ // Terminate this worker asap.
429
422
  //
430
- // detach
431
- //
432
- // Detach a link from our worker.
433
- //
434
-
435
- worker.detach = function detach(link) {
436
- removeLink(link)
423
+ nowbot.terminate = function terminate(local error, exitCode) {
424
+ if nowbot.thread
425
+ `(error, exitCode) = await(nowbot.thread.terminate()) // notified by on-error and on-exit
426
+ else
427
+ list(false, { terminated: true }) // already terminated
437
428
  }
438
429
 
439
- //
440
430
  // PostMessage
441
431
  //
442
- // Post a message to the main thread.
432
+ // Post a message to the worker thread.
443
433
  //
444
- worker.PostMessage = function PostMessage(data) {
445
- worker.msgport.postMessage({ // send data to worker
434
+ nowbot.PostMessage = function PostMessage(data) {
435
+ nowbot.msgport.postMessage({ // send data to worker
446
436
  id: "targetin",
447
437
  data: data
448
438
  })
449
439
  list(false, { posted: true })
450
440
  }
451
441
 
452
- //
453
- // msgin
454
- //
455
- // Send a message to the worker thread.
456
-
457
- worker.msgin = function msgin(msg) {
458
- worker.msgport.postMessage(msg)
459
- }
460
-
461
- //
462
- // echo
463
- //
464
- // Echo a message to the *other* links.
465
-
466
- worker.echo = function echo(msg, thislink, local linkref) {
467
- for linkref in worker.links
468
- if !(linkref.link eq thislink)
469
- linkref.responder(msg)
470
- }
471
-
472
- //
473
- // vitalWatch
474
- //
475
- // Report our termination to the specified watcher.
476
-
477
- worker.vitalWatch = function vitalWatch(watcher) {
478
- deathWatcher = watcher
479
- }
442
+ nowbot.subprocess = MakeSubProcessor(nowbot) // Enable PTY subprocesses on our terminal
480
443
 
481
444
  // finis
482
- worker
445
+
446
+ nowbot
483
447
  };
484
448
 
485
449
 
486
450
  /*
487
- * workLink
451
+ * NobBot
488
452
  *
489
- * An object representing a specific connection and worker pair. Both workers and connections may
490
- * have multiple links. This allows a worker to communicate with multiple connections and allows
491
- * clients to create a new connection to talk with an existing worker.
453
+ * Create a remote interface like NowBot, but also provide messaging support for NodeWorkerNub inside
454
+ * the worker.
492
455
  *
493
456
  */
494
457
 
495
- closure workLink(workco, conn, worker, sender, local link) {
496
- global()
497
- link = new(object, this)
458
+ closure NobBot(options, local nobbot)
459
+ {
460
+ global(App)
461
+ nobbot = NowBot(options)
498
462
 
463
+ // informAll
499
464
  //
500
- // closeConn
501
- //
502
- // Note that the connection has closed.
465
+ // Inform sourceID path to all our subscribers.
503
466
  //
504
-
505
- link.closeConn = function closeConn() {
506
- worker.detach(link) // stop receiving updates from worker
507
- }
508
-
509
- //
510
- // exitWorker
511
- //
512
- // Note that the worker has exited.
513
- //
514
-
515
- link.exitWorker = function exitWorker(exitCode) {
516
- sender("terminated", [false, {
517
- workerID: worker.workerID // notify client they've lost a worker
518
- exitCode: exitCode
519
- }])
520
- workco.closeLink(link, conn, worker.workerID)
521
- worker.detach(link)
467
+ function informAll(sourceID, local topDestID) {
468
+ for topDestID in nobbot.topsubs
469
+ App.imp.inform(sourceID, topDestID)
522
470
  }
523
471
 
524
- // initialize
472
+ App.imp.addEventListener("active", function(event, info) {
473
+ if info.ourID == nobbot.instanceID {
474
+ nobbot.workerDestID = info.destID // handshake completed
475
+ informAll(nobbot.workerDestID) // provide path to workerID
476
+ nobbot.notifyAll({ // send to subscribers
477
+ id: "workerDestID"
478
+ workerDestID: nobbot.workerDestID
479
+ })
480
+ }
481
+ })
525
482
 
526
- worker.attach(link, function msgout(data) {
527
- sender("msgout", data)
483
+ nobbot.impconn = App.imp.responder(function(data) {
484
+ nobbot.msgport.postMessage({ // send to worker
485
+ id: "naan-imp-node" // scoped for NodeWorkerNub
486
+ data: data
487
+ })
488
+ list(false, { sent: true })
489
+ }, {
490
+ ourID: nobbot.instanceID
528
491
  })
529
- sender("linked", [false, {
530
- workerID: worker.workerID // notify client they've gained a worker
531
- }])
532
492
 
533
493
  // finis
534
494
 
535
- link
495
+ nobbot
536
496
  };
537
497
 
538
498
 
539
499
  /*
540
- * workController
500
+ * MakeSubProcessor
541
501
  *
542
- * Controller for workers. This knows about all the workers and links. If a connection dies then
543
- * this tears down all links from the connection. If a worker dies then this tears down all links to
544
- * that worker.
502
+ * Method PTY subprocess capability to CurBot and NowBot. They also need these:
503
+ *
504
+ * function impReceive(message, sourceID) { ...
505
+ * ...
506
+ * if message.id == "keyline" && nowbot.keyin
507
+ * execbot.keyin(message.text) // terminal keyboard -> pty subprocess
508
+ *
509
+ * function naanmsg(msg, local startmsg) { ...
510
+ * ...
511
+ * else if msg.id == "textout" && nowbot.naanin
512
+ * execbot.naanin(msg.text) // naan output -> pty subprocess
513
+ *
514
+ * subprocess
515
+ *
516
+ * Create a PTY subprocess and connect with the worker and terminal. You specify the path of
517
+ * the executable and the arguments. If no executable is specified then a shell is created. The
518
+ * return value is a result tuple with an object that can be used to kill or monitor the
519
+ * subprocess.
520
+ *
521
+ * The subprocess can be connected in a variety of ways:
522
+ *
523
+ * term-keyboard | term-display | worker-in | worker-out
524
+ * -------------------------------------|---------------|------------|-----------
525
+ * subprocess input keyboard | | | pipe
526
+ * subprocess output | display | tee | pipe | tee |
527
+ *
528
+ * Options:
529
+ * {
530
+ * input: false | "keyboard" | "pipe"
531
+ * output: false | "display" | "pipe" | "tee"
532
+ * spawn: <PtyManager.spawn options>
533
+ * }
545
534
  *
546
535
  */
547
536
 
548
- closure workController(api, local workco) {
549
- global(gWorkers)
550
- workco = new(object, this)
551
- workco.serverID = "Workers" // our serverID, used for communications
552
- workco.workers = { } // dictionary of workers by workerID
553
- workco.uworkers = { } // dictionary of workers by GUID
554
- workco.links = new(weakmap)
555
- api.Register(workco.serverID, workco)
556
-
557
- // addLinkForConnection
558
- // Add a link for a connection under its workerID.
559
-
560
- function addLinkForConnection(conn, workerID, link, local iddict) {
561
- iddict = workco.links[conn]
562
- if !iddict
563
- workco.links[conn] = iddict = { }
564
- iddict[workerID] = link
565
- }
566
-
567
- // deleteLinkForConnection
568
- // Remove a link for a connection under its workerID, or false.
569
-
570
- function deleteLinkForConnection(conn, workerID) {
571
- if workco.links[conn][workerID]
572
- workco.links[conn][workerID] = undefined
573
- }
574
-
575
- // makeSender
576
- // Sender factory for specified connection and message.
577
-
578
- closure makeSender(conn, message) {
579
- function sender(respOp, payload) {
580
- conn.send({
581
- clientID: message.clientID
582
- serverID: message.serverID
583
- workerID: message.workerID
584
- respOp: respOp
585
- payload: payload
586
- })
537
+ closure MakeSubProcessor(execbot) {
538
+ closure subprocess(
539
+ executable, args, options, local error, pty) {
540
+
541
+ // ptyData - text has been returned from the subprocess
542
+ function ptyData(text) {
543
+ if options.output == "display" || options.output == "tee"
544
+ notifyAll({ // send to display
545
+ id: "textout",
546
+ text: text
547
+ })
548
+ if options.output == "pipe" || options.output == "tee"
549
+ msg({ // send to NaaN worker
550
+ id: "keyline"
551
+ text: text
552
+ })
587
553
  }
588
- }
589
-
590
- // makeLink
591
- // Link the specified message, connection and worker.
592
-
593
- function makeLink(message, conn, local worker, link) {
594
- worker = workco.workers[message.workerID]
595
- link = workLink(workco, conn, worker, makeSender(conn, message))
596
- addLinkForConnection(conn, message.workerID, link)
597
- }
598
-
599
- // termination
600
- // Note that a worker has terminated.
601
-
602
- function termination(workerID) {
603
- workco.current.vitalUpdate({
604
- id: "termination", // note a thread has gone away
605
- workerID: workerID
606
- })
607
- workco.workers[workerID] = undefined // it's like this worker has never been
608
- }
609
554
 
610
- // spawn
611
- //
612
- // Spawn a new worker with the specified configuration dictionary and optional GUID.
613
- //
614
- workco.spawn = closure spawn(workerID, config, guid, local worker) {
615
- if !dictionary(gWorkers)
616
- gWorkers = {}
617
- worker = workco.workers[workerID]
618
- if !worker { // create worker if needed
619
- if workerID == "NideServer"
620
- worker = workco.current = workCurrent(workco, workerID, config)
621
- else
622
- worker = workThread(workco, workerID, config, guid)
623
- workco.uworkers[worker.guid] = worker
624
- workco.workers[workerID] = worker
625
- gWorkers[worker.guid] = worker }
626
- if workerID == "NideServer"
627
- workco.current.vitalUpdate({
628
- id: "workerlist", // send the initial list of workers
629
- workers: let(workers, workerID, worker) {
630
- workers = { }
631
- for `(workerID, worker) in workco.workers
632
- workers[workerID] = {
633
- name: worker.name
634
- }
635
- workers
636
- }()
637
- })
638
- else {
639
- worker.vitalWatch(termination)
640
- workco.current.vitalUpdate({
641
- id: "spawned", // note a new worker thread
642
- name: config.name
643
- workerID: workerID
644
- }) }
645
- list(false, worker)
646
- }
555
+ // ptyKilled - the subprocess has terminated
556
+ function ptyKilled(event) {
557
+ if execbot.keyin === keyin
558
+ execbot.keyin = false
559
+ if execbot.naanin === naanin
560
+ execbot.naanin = false
561
+ }
647
562
 
648
- // wsReceive
649
- //
650
- // Process a message from the websocket API.
651
- //
652
- workco.wsReceive = function wsReceive(message, conn, local worker, link, error) {
653
- if !message.workerID
654
- error = Error("workerID required", message.workerOp)
655
- else
656
- {
657
- worker = workco.workers[message.workerID]
658
- link = workco.links[conn][message.workerID]
659
- if message.workerOp == "msgin" {
660
- if worker && message.payload {
661
- if !link
662
- makeLink(message, conn) // make sure we have a link for responses
663
- worker.msgin(message.payload) } // send message to worker
664
- return }
665
- else if message.workerOp == "echo" {
666
- if worker && message.payload {
667
- if !link
668
- makeLink(message, conn) // make sure we have a link for responses
669
- worker.echo(message.payload, link) } // echo message to other links
670
- return }
671
- else if message.workerOp == "spawn" {
672
- spawn(message.workerID, message.payload, message.workerGUID)
673
- if !link
674
- makeLink(message, conn) // make sure we have a link for responses
675
- return }
676
- else if message.workerOp == "terminate" {
677
- if worker {
678
- if !link
679
- makeLink(message, conn) // make sure we have a link for responses
680
- worker.terminate() } // echo message to other links
681
- return }
682
- error = Error("invalid parameters", message.workerOp, message.workerID)
563
+ // keyin - the terminal has generated text for the subprocess
564
+ function keyin(text) {
565
+ pty.write(text)
683
566
  }
684
- ErrorDebuglog("workController.wsReceive error:", error)
685
- }
686
-
687
- //
688
- // wsClose
689
- //
690
- // Note that the connection with the client was closed.
691
- //
692
-
693
- workco.wsClose = function wsClose(conn, local workerID, link) {
694
- for workerID in workco.workers {
695
- link = workco.links[conn][workerID]
696
- if link
697
- link.closeConn()
698
- deleteLinkForConnection(conn, workerID)
567
+
568
+ // naanin - NaaN worker has generated text for the subprocess
569
+ function naanin(text) {
570
+ pty.write(text)
699
571
  }
700
- }
701
-
702
- //
703
- // closeLink
704
- //
705
- // Note that a link with the worker has closed.
706
- //
707
572
 
708
- workco.closeLink = function closeLink(link, conn, workerID) {
709
- deleteLinkForConnection(conn, workerID)
573
+ `(error, pty) = PtyManager().spawn(executable, args, {
574
+ ptyData: ptyData
575
+ ptyKilled: ptyKilled
576
+ }, options.spawn)
577
+ if error
578
+ return (list(error))
579
+ if options.input == "keyboard"
580
+ nowbot.keyin = keyin // replace existing
581
+ else if options.input == "pipe"
582
+ execbot.naanin = naanin // replace existing
583
+ list(false, pty)
710
584
  }
711
-
712
- // finis
713
- workco
714
585
  };
715
586
 
716
587
 
@@ -722,13 +593,12 @@ closure workController(api, local workco) {
722
593
  */
723
594
 
724
595
  function workInit(local manifest) {
725
- manifest = `(workCurrent, WorkerConnect, workMainProxy, WorkerToMain, workThread, workLink,
726
- workController, workInit)
596
+ manifest = `(CurBot, NowBot, NobBot, MakeSubProcessor, workInit)
727
597
  Naan.module.build(module.id, "worker", function(modobj, compobj) {
728
598
  require("./node.nlg")
729
599
  compobj.manifest = manifest
730
- modobj.exports.WorkerController = workController
731
- modobj.exports.WorkerConnect = WorkerConnect
732
- modobj.exports.WorkerToMain = WorkerToMain
600
+ modobj.exports.CurBot = CurBot
601
+ modobj.exports.NowBot = NowBot
602
+ modobj.exports.NobBot = NobBot
733
603
  })
734
604
  }();