@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
@@ -0,0 +1,736 @@
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
+ imp.dispatchEvent("active", info)
406
+ }, 0)
407
+ if initiator
408
+ send_hello(destID) // start the handshake
409
+ imp.dispatchEvent("opening", info)
410
+ }
411
+
412
+ // close - the underlying connection has closed
413
+ function close() {
414
+ dest.handshake.signal(list(Error("connection closed")))
415
+ dest.state = `closed
416
+ if !initiator
417
+ dest.destID = undefined // ### reset for non-persistent clients
418
+ imp.dispatchEvent("closed", info)
419
+ }
420
+
421
+ // destroy - close and forget the connection
422
+ function destroy() {
423
+ if dest.state != `closed
424
+ close()
425
+ imp.pipes[destID] = undefined
426
+ imp.dispatchEvent("destroyed", info)
427
+ }
428
+
429
+ if info
430
+ info = new(info)
431
+ else
432
+ info = { }
433
+ dest = {
434
+ conn: ImpConn(dispatcher, sender, wait, open, close, destroy)
435
+ handshake: new(nonce)
436
+ sendconn: conn_sender
437
+ state: `closed
438
+ secret: info.secret || undefined
439
+ info: info
440
+ }
441
+ dest.info.secret = undefined // no need to spread it around
442
+ if initiator
443
+ service = "imp-pipe-req"
444
+ else
445
+ service = "imp-pipe-res"
446
+ if array(dest.info.services)
447
+ dest.info.services.unshift(service)
448
+ else
449
+ dest.info.services = [ service ]
450
+ destID = info.destID
451
+ if !destID {
452
+ destID = UUID() // temporary destID to be updated
453
+ dest.idsymbol = quote(destID)
454
+ }
455
+ imp.pipes[destID] = dest
456
+ if !info.delayOpen
457
+ open() // open immediately
458
+
459
+ dest.conn
460
+ }
461
+
462
+ // requester
463
+ //
464
+ // Start a requester connection and initiate the handshake. The IMP will use the specified
465
+ // sender, and returns an ImpConn object. The destID can be omitted to update the actual value
466
+ // from the far end. Specifying destID makes it a requirement, so the connection request will
467
+ // be ignored if wrong. This will use an existing connection to the same destination if known.
468
+ //
469
+ // info:
470
+ // {
471
+ // secret: <string> // must match responder
472
+ // [destID: <string>] // if specified, must match responder
473
+ // delayOpen: <boolean> // true to start closed; open() required later
474
+ // }
475
+ //
476
+ imp.requester = function requester(sender, info, local dest) {
477
+ if info.destID && (dest = imp.pipes[info.destID])
478
+ return (dest.conn)
479
+ imp.dispatchEvent("requester", info)
480
+ pipe_start(sender, info, true)
481
+ }
482
+
483
+ // responder
484
+ //
485
+ // Start a responder connection and wait for the handshake. The IMP will use the specified
486
+ // sender and returns an ImpConn object. When a responder for the same destination already exists
487
+ // then this new responder becomes the default, with a new connection object. Here be dragons.
488
+ //
489
+ // info:
490
+ // {
491
+ // secret: <string> // must match responder
492
+ // [defaultID: <string>] // optional default direct object provided to requester
493
+ // delayOpen: <boolean> // true to start closed; open() required later
494
+ // }
495
+ //
496
+ imp.responder = function responder(sender, info) {
497
+ imp.dispatchEvent("responder", info)
498
+ pipe_start(sender, info)
499
+ }
500
+
501
+ // register
502
+ //
503
+ // Register a directly-connected destination. The destobj may have two methods invoked:
504
+ // obj.impReceive(payload, sourceID) // received payload from sourceID
505
+ // obj.impUnknown(unknownID, sourceID) // recipient unknownID failed by sourceID
506
+ // The return value is a custom sender function for sending a payload to a destination:
507
+ // sender(destID, payload) // send payload to destID
508
+ //
509
+ // info:
510
+ // {
511
+ // instanceID: <string> // direct object ID
512
+ // name: <string>
513
+ // type: <string>
514
+ // meta: <dictionary>
515
+ // services: [<string>, ... ] // services provided by this destination
516
+ // }
517
+ //
518
+ imp.register = function register(destobj, info, local instanceID) {
519
+ if !string(info.name, info.type)
520
+ debuglog("Imp.register: info.name and info.type must be strings")
521
+ if info.meta && !dictionary(info.meta)
522
+ debuglog("Imp.register: info.meta must be a dictionary if specified")
523
+ instanceID = info.instanceID
524
+ if !string(instanceID) || imp.direct[instanceID] {
525
+ instanceID = UUID()
526
+ debuglog("Imp.register: instanceID ${info.instanceID} invalid or in use - replaced with ${instanceID}")
527
+ }
528
+ imp.direct[info.instanceID] = {
529
+ destID: info.instanceID
530
+ name: info.name
531
+ type: info.type
532
+ meta: info.meta || undefined
533
+ obj: destobj
534
+ info: info
535
+ }
536
+ imp.dispatchEvent("registered", info, destobj)
537
+ closure (sourceID) { // return a custom sender function
538
+ function sender(destID, payload) {
539
+ if !payload
540
+ payload = undefined // just a ping
541
+ send_message(sourceID, destID, {
542
+ pay: payload
543
+ })
544
+ }
545
+ } (info.instanceID)
546
+ }
547
+
548
+ // unregister
549
+ //
550
+ // Unregister a directly-connected destination.
551
+ //
552
+ imp.unregister = function unregister(uuid, local dest) {
553
+ dest = imp.direct[uuid]
554
+ if !dest {
555
+ debuglog("Imp.unregister: ${uuid} does not exist")
556
+ return
557
+ }
558
+ imp.dispatchEvent("unregistered", dest.info, dest.obj)
559
+ imp.direct[uuid] = undefined
560
+ }
561
+
562
+ // registered
563
+ //
564
+ // Return the registered direct object for a uuid, or false.
565
+ //
566
+ imp.registered = function registered(uuid) {
567
+ imp.direct[uuid]
568
+ }
569
+
570
+ // discover
571
+ //
572
+ // Discover the available destinations at a certain point. The result is an array of destination
573
+ // descriptors:
574
+ //
575
+ // [{
576
+ // destID: uuid
577
+ // name: dest.name
578
+ // type: dest.type
579
+ // meta: dest.meta
580
+ // direct: ownerID // direct IMP owner, or
581
+ // pipe: destID // pipe IMP, but not both
582
+ // }...]
583
+ //
584
+ imp.discover = closure discover(destID, local error, pending, timeout, result) {
585
+ if !destID || destID == us.instanceID
586
+ return (list(false, disc_dests(1))) // report this IMP itself
587
+ if imp.direct[destID]
588
+ return (list(false, [])) // direct objects don't report
589
+ `(error) = send_message(us.instanceID, destID, {
590
+ discover: 1
591
+ })
592
+ if error
593
+ return (list(error))
594
+ pending = imp.discovering.find(function(waiter) { waiter.destID == destID })
595
+ if !pending { // need new discovery for destID
596
+ pending = new(nonce)
597
+ pending.destID = destID
598
+ imp.discovering.push(pending)
599
+ timeout = future(function() {
600
+ imp.discovering = imp.discovering.filter(function(waiter) {
601
+ waiter.destID !== pending.destID
602
+ })
603
+ pending.signal(list(Error("discovery timeout:", destID)))
604
+ }, 5000)
605
+ }
606
+ result = pending.wait()
607
+ timeout.cancel()
608
+ result
609
+ }
610
+
611
+ // inform
612
+ //
613
+ // Inform the destination that the source is available via the current IMP. This explicit path
614
+ // routing is needed when you want the destination to be able to send to the source. For example,
615
+ // you might follow this by enclosing the sourceID in a payload sent to the destination. The
616
+ // sourceID must be known to this IMP. The result is an error tuple.
617
+ //
618
+ imp.inform = closure inform(sourceID, destID) {
619
+ if !imp.direct[sourceID] && !imp.pipes[sourceID] && !imp.gateways[sourceID]
620
+ return (list(Error("unknown sourceID:", sourceID)))
621
+ send_message(sourceID, destID, { }) // just a ping
622
+ }
623
+
624
+ // check_us
625
+ // Report anything wrong with our identity.
626
+ function check_str(field) {
627
+ if !string(us[field]) || us[field].length == 0
628
+ debuglog("Imp: invalid us.${field}:", us[field])
629
+ }
630
+
631
+ check_us(`name)
632
+ check_us(`type)
633
+ check_us(`instanceID)
634
+ Events(imp)
635
+
636
+ // finis
637
+
638
+ imp
639
+ };
640
+
641
+
642
+ /*
643
+ * ImpConn
644
+ *
645
+ * Imp connection objects.
646
+ *
647
+ */
648
+
649
+ closure ImpConn(dispatcher, sender, wait, open, close, destroy,
650
+ local impco) {
651
+ global()
652
+ impco = new(object, this)
653
+
654
+ impco.dispatch = dispatcher.proc
655
+ impco.send = sender.proc
656
+ impco.wait = wait.proc
657
+ impco.open = open.proc
658
+ impco.close = close.proc
659
+ impco.destroy = destroy.proc
660
+
661
+ // finis
662
+
663
+ impco
664
+ };
665
+
666
+
667
+ /*
668
+ * DebugImp
669
+ *
670
+ * Make a debugging IMP that optionally logs activity. Set imp.debug on and off as you wish.
671
+ *
672
+ */
673
+
674
+ closure DebugImp(us, local imp) {
675
+ imp = Imp(us)
676
+ // imp.debug = true
677
+
678
+ if imp.debug {
679
+ debuglog("new Imp: ${imp.us.instanceID} (${imp.us.name} :${imp.us.type})")
680
+ if imp.us.meta
681
+ debuglog(" meta:", Dialect.print(new(imp.us.meta)))
682
+ }
683
+ imp.addEventListener("active", function(event, info) {
684
+ if imp.debug {
685
+ debuglog("Imp: active connection with ${info.destID} (${info.name} :${info.type})")
686
+ if info.defaultID
687
+ debuglog(" redirected to ${info.defaultID}")
688
+ if info.meta
689
+ debuglog(" meta:", Dialect.print(new(info.meta)))
690
+ }
691
+ })
692
+ imp.addEventListener("opening", function(event, info) {
693
+ if imp.debug
694
+ debuglog("Imp: opening ${info.destID || "unknown"}")
695
+ })
696
+ imp.addEventListener("closed", function(event, info) {
697
+ if imp.debug
698
+ debuglog("Imp: closed ${info.destID} (${info.name} :${info.type})")
699
+ })
700
+ imp.addEventListener("destroyed", function(event, info) {
701
+ if imp.debug
702
+ debuglog("Imp: destroyed ${info.destID} (${info.name} :${info.type})")
703
+ })
704
+ imp.addEventListener("registered", function(event, info) {
705
+ if imp.debug
706
+ debuglog("Imp: registered ${info.instanceID} (${info.name} :${info.type})")
707
+ })
708
+ imp.addEventListener("unregistered", function(event, info) {
709
+ if imp.debug
710
+ debuglog("Imp: unregistered ${info.instanceID} (${info.name} :${info.type})")
711
+ })
712
+
713
+ // finis
714
+
715
+ imp
716
+ };
717
+
718
+
719
+ /*
720
+ * impInit
721
+ *
722
+ * Initialize the IMP component.
723
+ *
724
+ */
725
+
726
+ function impInit(local manifest) {
727
+ manifest = `(Imp, ImpConn, DebugImp, impInit)
728
+
729
+ Naan.module.build(module.id, "imp", function(modobj, compobj) {
730
+ require("./service.nlg")
731
+ require("../common/events.nlg")
732
+ compobj.manifest = manifest
733
+ module.exports.Imp = Imp
734
+ module.exports.DebugImp = DebugImp
735
+ })
736
+ } ();