@naanlang/naan 1.5.0 → 1.7.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 (62) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +2 -2
  3. package/bin/index.js +7 -3
  4. package/dist/env_web.js +162 -48
  5. package/dist/naan.min.js +11 -11
  6. package/frameworks/browser/https_request.nlg +2 -2
  7. package/frameworks/browser/sworker.js +28 -26
  8. package/frameworks/browser/terminals.nlg +295 -737
  9. package/frameworks/browser/worker.nlg +295 -0
  10. package/frameworks/browser/workers.nlg +6 -6
  11. package/frameworks/browser/ws_client.nlg +15 -10
  12. package/frameworks/client/apiclient.nlg +211 -9
  13. package/frameworks/client/psm_client.nlg +10 -14
  14. package/frameworks/client/relaycon.nlg +87 -161
  15. package/frameworks/common/common.nlg +2 -0
  16. package/frameworks/common/events.nlg +101 -0
  17. package/frameworks/common/repltools.nlg +80 -1
  18. package/frameworks/node/apiserver.nlg +38 -246
  19. package/frameworks/node/filesystem.nlg +31 -6
  20. package/frameworks/node/http_request.nlg +34 -13
  21. package/frameworks/node/https_request.nlg +8 -3
  22. package/frameworks/node/node.nlg +3 -0
  23. package/frameworks/node/pty.nlg +346 -0
  24. package/frameworks/node/shell.nlg +26 -3
  25. package/frameworks/node/worker.nlg +504 -560
  26. package/frameworks/node/ws_client.nlg +2 -2
  27. package/frameworks/project/build.nlg +56 -14
  28. package/frameworks/project/projects.nlg +1 -1
  29. package/frameworks/running/debugcom.nlg +20 -3
  30. package/frameworks/running/debugnub.nlg +33 -11
  31. package/frameworks/running/executors.nlg +40 -285
  32. package/frameworks/running/instances.nlg +394 -0
  33. package/frameworks/running/remote_req.nlg +139 -0
  34. package/frameworks/running/remote_res.nlg +89 -0
  35. package/frameworks/running/sourcecode.nlg +3 -2
  36. package/frameworks/running/taskexec.nlg +16 -59
  37. package/frameworks/service/imp.nlg +751 -0
  38. package/frameworks/service/model.nlg +71 -0
  39. package/frameworks/service/nubnode.nlg +136 -0
  40. package/frameworks/service/nubweb.nlg +124 -0
  41. package/frameworks/service/service.nlg +28 -0
  42. package/frameworks/storage/file_manager.nlg +3 -2
  43. package/lib/browser/env_web.js +169 -55
  44. package/lib/browser/env_webworker.js +89 -31
  45. package/lib/core/naanlib.js +11 -11
  46. package/lib/env_node.js +186 -27
  47. package/lib/env_nodeworker.js +82 -44
  48. package/lib/node_server_init.nlg +149 -0
  49. package/lib/node_worker.js +36 -0
  50. package/lib/node_worker_init.nlg +43 -0
  51. package/package.json +1 -1
  52. package/plugins/openSearch/openSearch.nlg +3 -2
  53. package/plugins/openSearch/os_dynamo.nlg +3 -2
  54. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +44 -2
  55. package/plugins/serviceAws/aws_dynamo.nlg +144 -63
  56. package/plugins/serviceAws/aws_utils.nlg +6 -11
  57. package/plugins/serviceAws/serviceAws.nlg +3 -2
  58. package/plugins/serviceNexara/speechrec.nlg +10 -6
  59. package/test/test_01_core.nlg +2 -2
  60. package/test/test_02_context.nlg +32 -9
  61. package/test/test_03_jsinterop.nlg +3 -3
  62. package/test/test_07_lingo.nlg +1 -1
@@ -0,0 +1,751 @@
1
+ /*
2
+ * imp.nlg
3
+ * Naanlib/frameworks/service/imp
4
+ *
5
+ * Instance Message Processor (IMP)
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2026 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * Imp
16
+ *
17
+ * The IMP network processes messages amongst instances, ensuring that any two instances can
18
+ * communicate with each other through intermediaries if not directly. Each IMP has a unique UUID
19
+ * that identifies it to the rest of the network.
20
+ *
21
+ * us:
22
+ * {
23
+ * name: <string> // name string for our instance
24
+ * type: <string> // type string for our instance
25
+ * instanceID: <string> // our UUID, which should be persistent
26
+ * meta: <dictionary> // data about our IMP, e.g. IP address or host name
27
+ * }
28
+ *
29
+ */
30
+
31
+ closure Imp(us, local imp) {
32
+ global()
33
+ imp = new(object, this)
34
+ imp.us = us = merge({ // start with rando defaults
35
+ name: "imp-generic"
36
+ type: "imp-type"
37
+ }, us)
38
+ if !us.instanceID
39
+ us.instanceID = UUID()
40
+ imp.direct = { } // direct-connected destinations
41
+ imp.pipes = { } // piped connections to destinations
42
+ imp.gateways = { } // routing gateways for next hop
43
+ imp.discovering = [] // nonces for current discoveries
44
+ imp.unknowns = { } // unknown ID notices we have sent
45
+
46
+ // send_hello
47
+ // Send an hello request to the other side.
48
+ function send_hello(destID, local dest, message) {
49
+ dest = imp.pipes[destID]
50
+ message = {
51
+ id: "imp-hello"
52
+ from: us.instanceID
53
+ secret: dest.secret
54
+ name: us.name
55
+ type: us.type
56
+ meta: us.meta || undefined
57
+ }
58
+ if !dest.idsymbol
59
+ message.to = destID // must match
60
+ dest.sendconn(message)
61
+ dest.state = `hello
62
+ }
63
+
64
+ // validate_destination
65
+ // Check the handshake destination, returning true iff unique
66
+ function validate_destination(fakeID, realID, local dest) {
67
+ if fakeID == realID
68
+ return (true)
69
+ dest = imp.pipes[realID]
70
+ if dest && dest.state != `closed
71
+ return (false)
72
+ dest = imp.pipes[fakeID]
73
+ if !dest.idsymbol
74
+ return (false)
75
+ assign(dest.idsymbol, realID)
76
+ dest.idsymbol = undefined
77
+ dest.destID = realID
78
+ imp.pipes[realID] = dest
79
+ imp.pipes[fakeID] = undefined
80
+ return (true)
81
+ }
82
+
83
+ // disp_hello
84
+ // Dispatch an hello request from destID.
85
+ function disp_hello(message, destID, local dest, reject) {
86
+ if imp.direct[message.from] { // wasteful, I think
87
+ debuglog("Imp.disp_hello: ignoring hello from direct:", message.from)
88
+ return
89
+ }
90
+ if message.to && message.to != us.instanceID { // required uuid mismatch
91
+ debuglog("Imp.disp_hello: ignoring hello from ${message.from} to ${message.to}, we are ${us.instanceID}")
92
+ return
93
+ }
94
+ if message.secret != imp.pipes[destID].secret { // invalid hello secret
95
+ debuglog("Imp.disp_hello: ${message.from}: incoming secret ${message.secret} doesn't match our secret")
96
+ return
97
+ }
98
+ dest = imp.pipes[message.from]
99
+ if destID != message.from { // educate pipe on his real destID
100
+ dest = imp.pipes[destID]
101
+ if !dest || !dest.idsymbol {
102
+ debuglog("Imp.disp_hello: hello ${message.from} sequencing error")
103
+ return
104
+ }
105
+ if validate_destination(destID, message.from) {
106
+ destID = message.from
107
+ if imp.gateways[destID] {
108
+ debuglog("Imp.disp_hello: pipe ${destID} promoted from indirect ${imp.gateways[destID]}")
109
+ imp.gateways[destID] = undefined
110
+ }
111
+ } else
112
+ reject = "duplicate"
113
+ }
114
+ dest.name = message.name
115
+ dest.type = message.type
116
+ dest.meta = message.meta || undefined
117
+ dest.sendconn({
118
+ id: "imp-active"
119
+ from: us.instanceID
120
+ to: message.from
121
+ name: us.name
122
+ type: us.type
123
+ meta: us.meta
124
+ defaultID: dest.info.defaultID || undefined
125
+ reject: reject || undefined
126
+ })
127
+ dest.state = `active // responder now active
128
+ dest.handshake.signal(list(false, destID))
129
+ }
130
+
131
+ // disp_active
132
+ // Dispatch an active response from the other side.
133
+ function disp_active(message, destID, local dest) {
134
+ if message.to != us.instanceID
135
+ return // somehow not for us
136
+ dest = imp.pipes[destID]
137
+ if dest.state != `hello {
138
+ debuglog("Imp.disp_active: wrong sequence for state", dest.state)
139
+ return
140
+ }
141
+ if message.reject { // we were rejected
142
+ debuglog("Imp.disp_active: duplicate requesterID", message.to, "(${message.name} / ${message.type})")
143
+ dest.handshake.signal(list(Error("duplicate requesterID", message.to)))
144
+ return
145
+ }
146
+ if !validate_destination(destID, message.from) { // we are rejecting them
147
+ debuglog("Imp.disp_active: duplicate responderID", message.from, "(${message.name} / ${message.type})")
148
+ dest.handshake.signal(list(Error("duplicate responderID", message.from)))
149
+ return
150
+ }
151
+ destID = message.from
152
+ dest.name = message.name
153
+ dest.type = message.type
154
+ dest.meta = message.meta || undefined
155
+ dest.defaultID = message.defaultID
156
+ dest.state = `active // both sides now active
157
+ if message.defaultID
158
+ imp.gateways[message.defaultID] = destID // how to get there
159
+ dest.handshake.signal(list(false, destID))
160
+ }
161
+
162
+ // send_direct
163
+ //
164
+ // Send a message to a registered direct-connect destination.
165
+ //
166
+ function send_direct(dest, sourceID, destID, message) {
167
+ if message.pay
168
+ dest.obj.impReceive(message.pay, sourceID) // payload received
169
+ else if message.unknownID
170
+ dest.obj.impUnknown(message.unknownID, sourceID, message.gateway) // unknown destID reported
171
+ else if message.found
172
+ disc_done(message) // complete waiting discovery requests
173
+ else
174
+ { } // empty message; just a ping
175
+ }
176
+
177
+ // send_unknown
178
+ //
179
+ // Send a message notifying about an unknown destID. This is done only once per id/sender.
180
+ //
181
+ function send_unknown(sourceID, unknownID, message, local key) {
182
+ key = "${sourceID}+${unknownID}"
183
+ if imp.unknowns[key]
184
+ return
185
+ imp.unknowns[key] = 1
186
+ if !message.unknownID { // if not already sending unknown...
187
+ message = new(message)
188
+ message.pay = undefined
189
+ message.unknownID = unknownID
190
+ if send_message(us.instanceID, sourceID, message).0
191
+ debuglog("Imp.send_unknown: silent unknown destID", unknownID) // log instead of silence
192
+ }
193
+ }
194
+
195
+ // send_message
196
+ //
197
+ // Send a message from any sourceID to any destID, returning a result tuple. If the destination
198
+ // is unknown then the message is returned to the sender with no payload and `unknownID defined,
199
+ // and the sender is the IMP node that first noticed the destID was undefined.
200
+ //
201
+ closure send_message(sourceID, destID, message, local dest, hopID) {
202
+ if sourceID == destID || !sourceID || !destID || !message {
203
+ debuglog("Imp.send_message: invalid parameters", sourceID, destID, message)
204
+ return (list(Error("invalid send parameters"))) // invalid
205
+ }
206
+ dest = imp.direct[destID]
207
+ if dest { // send to direct connect
208
+ if imp.direct[sourceID] || us.instanceID == sourceID { // direct -> direct
209
+ message = JSONparse(JSONstringify(message)) // pretend normal comm link
210
+ future(function() { send_direct(dest, sourceID, destID, message) }, 0)
211
+ } else
212
+ send_direct(dest, sourceID, destID, message) // incoming from pipe
213
+ return (list(false, { ok: true }))
214
+ } else if message.to == us.instanceID {
215
+ disc_done(message) // complete waiting discovery requests
216
+ return (list(false, { ok: true }))
217
+ }
218
+ hopID = destID
219
+ while !(dest = imp.pipes[hopID])
220
+ if !(hopID = imp.gateways[hopID]) // gateway knowledge is relative
221
+ break // chain is incomplete
222
+ if !dest {
223
+ if imp.gateways[destID] { // previously routed
224
+ message.gateway = imp.gateways[destID]
225
+ imp.gateways[destID] = undefined
226
+ }
227
+ send_unknown(sourceID, destID, message)
228
+ return (list(Error("unknown destination", destID)))
229
+ }
230
+ dest.sendconn(merge(message, { // send it out pipe link
231
+ id: "imp-xfer"
232
+ from: sourceID
233
+ to: destID
234
+ }))
235
+ return (list(false, { ok: true }))
236
+ }
237
+
238
+ // disp_xfer
239
+ // Dispatch on xfer packet received from the other side.
240
+ function disp_xfer(message, peerID, local peer) {
241
+ peer = imp.pipes[peerID]
242
+ if peer.state != `active
243
+ return // not active
244
+ check_gateway(message.from, peerID) // update our gateway knowledge
245
+ if message.discover {
246
+ if message.to == us.instanceID || imp.direct[message.to] { // we are handling this
247
+ if message.to == us.instanceID
248
+ message.found = disc_dests(message.discover) // querying this IMP
249
+ else
250
+ message.found = [] // querying a leaf
251
+ message.to = message.from
252
+ message.from = us.instanceID
253
+ message.discover = undefined // remove from response
254
+ }
255
+ } else if message.found // filter and forward results
256
+ message.found = disc_redirect(message.found)
257
+ else if message.unknownID
258
+ imp.gateways[message.unknownID] = undefined // cleanup for obsolete connection
259
+ send_message(message.from, message.to, message)
260
+ }
261
+
262
+ // dispatch
263
+ //
264
+ // Handle an incoming message over pipe, returning true iff it is our message.
265
+ //
266
+ function dispatch(message, destID, local proc) {
267
+ if !message
268
+ return (false)
269
+ if !imp.pipes[destID] {
270
+ debuglog("Imp.dispatch: ${destID} invalid")
271
+ return (false) // not open
272
+ }
273
+ proc = {
274
+ "imp-hello": disp_hello
275
+ "imp-active": disp_active
276
+ "imp-xfer": disp_xfer
277
+ }[message.id]
278
+ if proc {
279
+ call(proc, message, destID)
280
+ true
281
+ }
282
+ }
283
+
284
+ // check_gateway
285
+ // Update our gateway with new information.
286
+ function check_gateway(fromID, peerID, local hopID) {
287
+ if fromID == peerID
288
+ return
289
+ hopID = imp.gateways[fromID]
290
+ if !hopID || hopID != peerID {
291
+ if hopID != peerID && false // ### logging disabled
292
+ debuglog("Imp.check_gateway: fromID ${fromID} hopID ${hopID} now ${peerID}")
293
+ imp.gateways[fromID] = peerID // our gateway to fromID is peerID
294
+ }
295
+ }
296
+
297
+ // disc_dests
298
+ // Return an array of our discovery destinations.
299
+ function disc_dests(local output, uuid, dest) {
300
+ output = []
301
+ for `(uuid, dest) in imp.direct
302
+ output.push({
303
+ destID: uuid
304
+ name: dest.name
305
+ type: dest.type
306
+ meta: dest.meta || undefined
307
+ services: dest.info.services
308
+ direct: us.instanceID // direct IMP owner
309
+ })
310
+ for `(uuid, dest) in imp.pipes
311
+ output.push({
312
+ destID: uuid
313
+ name: dest.name
314
+ type: dest.type
315
+ meta: dest.meta || undefined
316
+ services: dest.info.services
317
+ pipe: us.instanceID
318
+ })
319
+ output
320
+ }
321
+
322
+ // disc_redirect
323
+ // Update an array of destination descriptors to prefer ours
324
+ function disc_redirect(found, local output, item) {
325
+ output = []
326
+ for item in found {
327
+ if item.direct // don't change direct
328
+ { }
329
+ else if imp.direct[item.destID]
330
+ || imp.pipes[item.destID] && !item.direct && !imp.gateways[item.destID]
331
+ item.route = us.instanceID // we are better route
332
+ output.push(item)
333
+ check_gateway(item.destID, item.route || item.direct || item.pipe) // ensure we can route to each one
334
+ }
335
+ output
336
+ }
337
+
338
+ // disc_done
339
+ // Complete any nonces for a discovery that has finished (response or unknown)
340
+ function disc_done(message, local waiter) {
341
+ for waiter in imp.discovering {
342
+ if waiter.destID == message.from
343
+ waiter.signal(list(false, message.found))
344
+ else if waiter.destID == message.unknownID
345
+ waiter.signal(list(Error("unknown destination:", waiter.destID)))
346
+ else
347
+ continue
348
+ waiter.complete = true
349
+ }
350
+ imp.discovering = imp.discovering.filter(function(waiter) {
351
+ !waiter.complete
352
+ })
353
+ }
354
+
355
+ // pipe_start
356
+ //
357
+ // Start a connection for either requester or responder.
358
+ //
359
+ // A pipe connection has begun. The IMP will use the specified sender, and the returned
360
+ // dispatcher should be called for incoming messages. The dispatcher returns true if the message
361
+ // is recognized as an IMP message, or false otherwise. Call the dispatcher with message false
362
+ // (no arguments) to destroy and forget the connection.
363
+ //
364
+ // We may not know the destID of the far end until we complete the hello/active handshake. There
365
+ // can also be multiple handshakes proceeding in parallel, in the case of a server responder.
366
+ // This makes a temporary destID and then replaces it after the real destID becomes known.
367
+ //
368
+ closure pipe_start(conn_sender, info, initiator, local destID, dest, service) {
369
+
370
+ // dispatcher - dispatch a message
371
+ function dispatcher(message) {
372
+ dispatch(message, destID)
373
+ }
374
+
375
+ // sender - send a message, resturning result tuple
376
+ function sender(message, senderID, local result) {
377
+ if !imp.pipes[destID]
378
+ return (list(Error("connection destroyed"))) // cannot open
379
+ if !senderID
380
+ senderID = us.instanceID
381
+ result = wait() // wait for handshake to complete
382
+ if result.0
383
+ return (result)
384
+ send_message(senderID, destID, {
385
+ pay: message
386
+ })
387
+ }
388
+
389
+ // wait - wait for handshake and return destID in result tuple
390
+ function wait() {
391
+ dest.handshake.wait()
392
+ }
393
+
394
+ // open - the underlying connection has opened/reopened
395
+ function open() {
396
+ dest.handshake.reset()
397
+ dest.state = `connected
398
+ future(function() {
399
+ wait()
400
+ info.destID = destID
401
+ info.defaultID = dest.defaultID || undefined
402
+ info.name = dest.name
403
+ info.type = dest.type
404
+ info.meta = dest.meta
405
+ dest.conn.dispatchEvent("active", info)
406
+ imp.dispatchEvent("active", info)
407
+ }, 0)
408
+ if initiator
409
+ send_hello(destID) // start the handshake
410
+ dest.conn.dispatchEvent("opening", info)
411
+ imp.dispatchEvent("opening", info)
412
+ }
413
+
414
+ // close - the underlying connection has closed
415
+ function close() {
416
+ dest.handshake.signal(list(Error("connection closed")))
417
+ dest.state = `closed
418
+ if !initiator
419
+ dest.destID = undefined // ### reset for non-persistent clients
420
+ dest.conn.dispatchEvent("closed", info)
421
+ imp.dispatchEvent("closed", info)
422
+ }
423
+
424
+ // destroy - close and forget the connection
425
+ function destroy() {
426
+ if dest.state != `closed
427
+ close()
428
+ imp.pipes[destID] = undefined
429
+ dest.conn.dispatchEvent("destroyed", info)
430
+ imp.dispatchEvent("destroyed", info)
431
+ }
432
+
433
+ if info
434
+ info = new(info)
435
+ else
436
+ info = { }
437
+ dest = {
438
+ conn: ImpConn(dispatcher, sender, wait, open, close, destroy)
439
+ handshake: new(nonce)
440
+ sendconn: conn_sender
441
+ state: `closed
442
+ secret: info.secret || undefined
443
+ info: info
444
+ }
445
+ dest.info.secret = undefined // no need to spread it around
446
+ if initiator
447
+ service = "imp-pipe-req"
448
+ else
449
+ service = "imp-pipe-res"
450
+ if array(dest.info.services)
451
+ dest.info.services.unshift(service)
452
+ else
453
+ dest.info.services = [ service ]
454
+ destID = info.destID
455
+ if !destID {
456
+ destID = UUID() // temporary destID to be updated
457
+ dest.idsymbol = quote(destID)
458
+ }
459
+ imp.pipes[destID] = dest
460
+ if !info.delayOpen
461
+ open() // open immediately
462
+
463
+ dest.conn
464
+ }
465
+
466
+ // requester
467
+ //
468
+ // Start a requester connection and initiate the handshake. The IMP will use the specified
469
+ // sender, and returns an ImpConn object. The destID can be omitted to update the actual value
470
+ // from the far end. Specifying destID makes it a requirement, so the connection request will
471
+ // be ignored if wrong. This will use an existing connection to the same destination if known.
472
+ //
473
+ // info:
474
+ // {
475
+ // secret: <string> // must match responder
476
+ // [destID: <string>] // if specified, must match responder
477
+ // delayOpen: <boolean> // true to start closed; open() required later
478
+ // }
479
+ //
480
+ imp.requester = function requester(sender, info, local dest) {
481
+ if info.destID && (dest = imp.pipes[info.destID])
482
+ return (dest.conn)
483
+ imp.dispatchEvent("requester", info)
484
+ pipe_start(sender, info, true)
485
+ }
486
+
487
+ // responder
488
+ //
489
+ // Start a responder connection and wait for the handshake. The IMP will use the specified
490
+ // sender and returns an ImpConn object. When a responder for the same destination already exists
491
+ // then this new responder becomes the default, with a new connection object. Here be dragons.
492
+ //
493
+ // info:
494
+ // {
495
+ // secret: <string> // must match responder
496
+ // [defaultID: <string>] // optional default direct object provided to requester
497
+ // delayOpen: <boolean> // true to start closed; open() required later
498
+ // }
499
+ //
500
+ imp.responder = function responder(sender, info) {
501
+ imp.dispatchEvent("responder", info)
502
+ pipe_start(sender, info)
503
+ }
504
+
505
+ // register
506
+ //
507
+ // Register a directly-connected destination. The destobj may have two methods invoked:
508
+ // obj.impReceive(payload, sourceID) // received payload from sourceID
509
+ // obj.impUnknown(unknownID, sourceID) // recipient unknownID failed by sourceID
510
+ // The return value is a custom sender function for sending a payload to a destination:
511
+ // sender(destID, payload) // send payload to destID
512
+ //
513
+ // info:
514
+ // {
515
+ // instanceID: <string> // direct object ID
516
+ // name: <string>
517
+ // type: <string>
518
+ // meta: <dictionary>
519
+ // services: [<string>, ... ] // services provided by this destination
520
+ // }
521
+ //
522
+ imp.register = function register(destobj, info, local instanceID) {
523
+ if !string(info.name, info.type)
524
+ debuglog("Imp.register: info.name and info.type must be strings")
525
+ if info.meta && !dictionary(info.meta)
526
+ debuglog("Imp.register: info.meta must be a dictionary if specified")
527
+ instanceID = info.instanceID
528
+ if !string(instanceID) || imp.direct[instanceID] {
529
+ instanceID = UUID()
530
+ debuglog("Imp.register: instanceID ${info.instanceID} invalid or in use - replaced with ${instanceID}")
531
+ }
532
+ imp.direct[info.instanceID] = {
533
+ destID: info.instanceID
534
+ name: info.name
535
+ type: info.type
536
+ meta: info.meta || undefined
537
+ obj: destobj
538
+ info: info
539
+ }
540
+ imp.dispatchEvent("registered", info, destobj)
541
+ closure (sourceID) { // return a custom sender function
542
+ function sender(destID, payload) {
543
+ if !payload
544
+ payload = undefined // just a ping
545
+ send_message(sourceID, destID, {
546
+ pay: payload
547
+ })
548
+ }
549
+ } (info.instanceID)
550
+ }
551
+
552
+ // unregister
553
+ //
554
+ // Unregister a directly-connected destination.
555
+ //
556
+ imp.unregister = function unregister(uuid, local dest) {
557
+ dest = imp.direct[uuid]
558
+ if !dest {
559
+ debuglog("Imp.unregister: ${uuid} does not exist")
560
+ return
561
+ }
562
+ imp.dispatchEvent("unregistered", dest.info, dest.obj)
563
+ imp.direct[uuid] = undefined
564
+ }
565
+
566
+ // registered
567
+ //
568
+ // Return the registered direct object for a uuid, or false.
569
+ //
570
+ imp.registered = function registered(uuid) {
571
+ imp.direct[uuid]
572
+ }
573
+
574
+ // discover
575
+ //
576
+ // Discover the available destinations at a certain point. The result is an array of destination
577
+ // descriptors:
578
+ //
579
+ // [{
580
+ // destID: uuid
581
+ // name: dest.name
582
+ // type: dest.type
583
+ // meta: dest.meta
584
+ // direct: ownerID // direct IMP owner, or
585
+ // pipe: destID // pipe IMP, but not both
586
+ // }...]
587
+ //
588
+ imp.discover = closure discover(destID, local error, pending, timeout, result) {
589
+ if !destID || destID == us.instanceID
590
+ return (list(false, disc_dests(1))) // report this IMP itself
591
+ if imp.direct[destID]
592
+ return (list(false, [])) // direct objects don't report
593
+ `(error) = send_message(us.instanceID, destID, {
594
+ discover: 1
595
+ })
596
+ if error
597
+ return (list(error))
598
+ pending = imp.discovering.find(function(waiter) { waiter.destID == destID })
599
+ if !pending { // need new discovery for destID
600
+ pending = new(nonce)
601
+ pending.destID = destID
602
+ imp.discovering.push(pending)
603
+ timeout = future(function() {
604
+ imp.discovering = imp.discovering.filter(function(waiter) {
605
+ waiter.destID !== pending.destID
606
+ })
607
+ pending.signal(list(Error("discovery timeout:", destID)))
608
+ }, 5000)
609
+ }
610
+ result = pending.wait()
611
+ timeout.cancel()
612
+ result
613
+ }
614
+
615
+ // inform
616
+ //
617
+ // Inform the destination that the source is available via the current IMP. This explicit path
618
+ // routing is needed when you want the destination to be able to send to the source. For example,
619
+ // you might follow this by enclosing the sourceID in a payload sent to the destination. The
620
+ // sourceID must be known to this IMP. The result is an error tuple.
621
+ //
622
+ imp.inform = closure inform(sourceID, destID) {
623
+ if !imp.direct[sourceID] && !imp.pipes[sourceID] && !imp.gateways[sourceID]
624
+ return (list(Error("unknown sourceID:", sourceID)))
625
+ send_message(sourceID, destID, { }) // just a ping
626
+ }
627
+
628
+ // check_us
629
+ // Report anything wrong with our identity.
630
+ function check_str(field) {
631
+ if !string(us[field]) || us[field].length == 0
632
+ debuglog("Imp: invalid us.${field}:", us[field])
633
+ }
634
+
635
+ check_us(`name)
636
+ check_us(`type)
637
+ check_us(`instanceID)
638
+ Events(imp)
639
+
640
+ // finis
641
+
642
+ imp
643
+ };
644
+
645
+
646
+ /*
647
+ * ImpConn
648
+ *
649
+ * Imp connection objects.
650
+ *
651
+ */
652
+
653
+ closure ImpConn(dispatcher, sender, wait, open, close, destroy,
654
+ local impco) {
655
+ global()
656
+ impco = new(object, this)
657
+
658
+ // reopen
659
+ //
660
+ // Reopen the connection with a delay to let events process.
661
+ //
662
+ impco.reopen = closure reopen() {
663
+ close()
664
+ sleep(10)
665
+ open()
666
+ }
667
+
668
+ impco.dispatch = dispatcher.proc
669
+ impco.send = sender.proc
670
+ impco.wait = wait.proc
671
+ impco.open = open.proc
672
+ impco.close = close.proc
673
+ impco.destroy = destroy.proc
674
+ Events(impco)
675
+
676
+ // finis
677
+
678
+ impco
679
+ };
680
+
681
+
682
+ /*
683
+ * DebugImp
684
+ *
685
+ * Make a debugging IMP that optionally logs activity. Set imp.debug on and off as you wish.
686
+ *
687
+ */
688
+
689
+ closure DebugImp(us, local imp) {
690
+ imp = Imp(us)
691
+ // imp.debug = true
692
+
693
+ if imp.debug {
694
+ debuglog("new Imp: ${imp.us.instanceID} (${imp.us.name} :${imp.us.type})")
695
+ if imp.us.meta
696
+ debuglog(" meta:", Dialect.print(new(imp.us.meta)))
697
+ }
698
+ imp.addEventListener("active", function(event, info) {
699
+ if imp.debug {
700
+ debuglog("Imp: active connection with ${info.destID} (${info.name} :${info.type})")
701
+ if info.defaultID
702
+ debuglog(" redirected to ${info.defaultID}")
703
+ if info.meta
704
+ debuglog(" meta:", Dialect.print(new(info.meta)))
705
+ }
706
+ })
707
+ imp.addEventListener("opening", function(event, info) {
708
+ if imp.debug
709
+ debuglog("Imp: opening ${info.destID || "unknown"}")
710
+ })
711
+ imp.addEventListener("closed", function(event, info) {
712
+ if imp.debug
713
+ debuglog("Imp: closed ${info.destID} (${info.name} :${info.type})")
714
+ })
715
+ imp.addEventListener("destroyed", function(event, info) {
716
+ if imp.debug
717
+ debuglog("Imp: destroyed ${info.destID} (${info.name} :${info.type})")
718
+ })
719
+ imp.addEventListener("registered", function(event, info) {
720
+ if imp.debug
721
+ debuglog("Imp: registered ${info.instanceID} (${info.name} :${info.type})")
722
+ })
723
+ imp.addEventListener("unregistered", function(event, info) {
724
+ if imp.debug
725
+ debuglog("Imp: unregistered ${info.instanceID} (${info.name} :${info.type})")
726
+ })
727
+
728
+ // finis
729
+
730
+ imp
731
+ };
732
+
733
+
734
+ /*
735
+ * impInit
736
+ *
737
+ * Initialize the IMP component.
738
+ *
739
+ */
740
+
741
+ function impInit(local manifest) {
742
+ manifest = `(Imp, ImpConn, DebugImp, impInit)
743
+
744
+ Naan.module.build(module.id, "imp", function(modobj, compobj) {
745
+ require("./service.nlg")
746
+ require("../common/events.nlg")
747
+ compobj.manifest = manifest
748
+ module.exports.Imp = Imp
749
+ module.exports.DebugImp = DebugImp
750
+ })
751
+ } ();