@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.
- package/LICENSE.md +2 -2
- package/README.md +4 -4
- package/bin/index.js +25 -3
- package/dist/env_web.js +164 -22
- package/dist/naan.min.js +5 -5
- package/frameworks/browser/browser.nlg +2 -2
- package/frameworks/browser/https_request.nlg +2 -2
- package/frameworks/browser/sworker.js +28 -26
- package/frameworks/browser/terminals.nlg +342 -700
- package/frameworks/browser/worker.nlg +255 -0
- package/frameworks/browser/workers.nlg +5 -5
- package/frameworks/browser/ws_client.nlg +15 -10
- package/frameworks/client/apiclient.nlg +204 -19
- package/frameworks/client/client.nlg +22 -2
- package/frameworks/client/psm_client.nlg +12 -3
- package/frameworks/client/relaycon.nlg +87 -161
- package/frameworks/common/events.nlg +101 -0
- package/frameworks/common/repltools.nlg +80 -1
- package/frameworks/node/apiserver.nlg +39 -246
- package/frameworks/node/filesystem.nlg +39 -12
- package/frameworks/node/gitter.nlg +9 -7
- package/frameworks/node/http_request.nlg +34 -13
- package/frameworks/node/https_request.nlg +8 -3
- package/frameworks/node/node.nlg +2 -0
- package/frameworks/node/pty.nlg +346 -0
- package/frameworks/node/shell.nlg +4 -4
- package/frameworks/node/worker.nlg +409 -539
- package/frameworks/node/ws_client.nlg +2 -2
- package/frameworks/project/build.nlg +1 -1
- package/frameworks/project/projects.nlg +2 -1
- package/frameworks/running/debugnub.nlg +17 -4
- package/frameworks/running/executors.nlg +27 -274
- package/frameworks/running/instances.nlg +393 -0
- package/frameworks/running/remote_req.nlg +143 -0
- package/frameworks/running/remote_res.nlg +89 -0
- package/frameworks/running/taskexec.nlg +2 -2
- package/frameworks/service/imp.nlg +736 -0
- package/frameworks/service/model.nlg +71 -0
- package/frameworks/service/nubnode.nlg +105 -0
- package/frameworks/service/nubweb.nlg +106 -0
- package/frameworks/service/service.nlg +28 -0
- package/frameworks/storage/dbt_pouch.nlg +26 -13
- package/frameworks/storage/psm.nlg +39 -5
- package/frameworks/storage/psm_dbtables.nlg +10 -2
- package/lib/browser/env_web.js +171 -29
- package/lib/browser/env_webworker.js +91 -5
- package/lib/core/naanlib.js +5 -5
- package/lib/env_node.js +184 -8
- package/lib/env_nodeworker.js +74 -1
- package/package.json +1 -1
- package/plugins/serviceAws/aws_cloudwatchlogs.nlg +2 -1
- package/plugins/serviceAws/aws_dynamo.nlg +1 -0
- package/plugins/serviceAws/aws_s3.nlg +3 -3
- package/plugins/serviceAws/aws_sqs.nlg +3 -1
- package/plugins/serviceAws/aws_utils.nlg +3 -1
- package/plugins/serviceAws/dbt_aws.nlg +22 -20
- package/plugins/serviceAws/psm_aws.nlg +2 -1
- package/plugins/serviceNexara/speechrec.nlg +10 -6
- package/test/test_02_context.nlg +24 -1
- package/test/test_03_jsinterop.nlg +32 -3
- 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
|
+
} ();
|