@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.
- package/LICENSE.md +2 -2
- package/README.md +2 -2
- package/bin/index.js +7 -3
- package/dist/env_web.js +162 -48
- package/dist/naan.min.js +11 -11
- package/frameworks/browser/https_request.nlg +2 -2
- package/frameworks/browser/sworker.js +28 -26
- package/frameworks/browser/terminals.nlg +295 -737
- package/frameworks/browser/worker.nlg +295 -0
- package/frameworks/browser/workers.nlg +6 -6
- package/frameworks/browser/ws_client.nlg +15 -10
- package/frameworks/client/apiclient.nlg +211 -9
- package/frameworks/client/psm_client.nlg +10 -14
- package/frameworks/client/relaycon.nlg +87 -161
- package/frameworks/common/common.nlg +2 -0
- package/frameworks/common/events.nlg +101 -0
- package/frameworks/common/repltools.nlg +80 -1
- package/frameworks/node/apiserver.nlg +38 -246
- package/frameworks/node/filesystem.nlg +31 -6
- package/frameworks/node/http_request.nlg +34 -13
- package/frameworks/node/https_request.nlg +8 -3
- package/frameworks/node/node.nlg +3 -0
- package/frameworks/node/pty.nlg +346 -0
- package/frameworks/node/shell.nlg +26 -3
- package/frameworks/node/worker.nlg +504 -560
- package/frameworks/node/ws_client.nlg +2 -2
- package/frameworks/project/build.nlg +56 -14
- package/frameworks/project/projects.nlg +1 -1
- package/frameworks/running/debugcom.nlg +20 -3
- package/frameworks/running/debugnub.nlg +33 -11
- package/frameworks/running/executors.nlg +40 -285
- package/frameworks/running/instances.nlg +394 -0
- package/frameworks/running/remote_req.nlg +139 -0
- package/frameworks/running/remote_res.nlg +89 -0
- package/frameworks/running/sourcecode.nlg +3 -2
- package/frameworks/running/taskexec.nlg +16 -59
- package/frameworks/service/imp.nlg +751 -0
- package/frameworks/service/model.nlg +71 -0
- package/frameworks/service/nubnode.nlg +136 -0
- package/frameworks/service/nubweb.nlg +124 -0
- package/frameworks/service/service.nlg +28 -0
- package/frameworks/storage/file_manager.nlg +3 -2
- package/lib/browser/env_web.js +169 -55
- package/lib/browser/env_webworker.js +89 -31
- package/lib/core/naanlib.js +11 -11
- package/lib/env_node.js +186 -27
- package/lib/env_nodeworker.js +82 -44
- package/lib/node_server_init.nlg +149 -0
- package/lib/node_worker.js +36 -0
- package/lib/node_worker_init.nlg +43 -0
- package/package.json +1 -1
- package/plugins/openSearch/openSearch.nlg +3 -2
- package/plugins/openSearch/os_dynamo.nlg +3 -2
- package/plugins/serviceAws/aws_cloudwatchlogs.nlg +44 -2
- package/plugins/serviceAws/aws_dynamo.nlg +144 -63
- package/plugins/serviceAws/aws_utils.nlg +6 -11
- package/plugins/serviceAws/serviceAws.nlg +3 -2
- package/plugins/serviceNexara/speechrec.nlg +10 -6
- package/test/test_01_core.nlg +2 -2
- package/test/test_02_context.nlg +32 -9
- package/test/test_03_jsinterop.nlg +3 -3
- package/test/test_07_lingo.nlg +1 -1
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* worker.nlg
|
|
3
|
+
* Naanlib/frameworks/browser
|
|
4
|
+
*
|
|
5
|
+
* Manage web-based NaaN instances.
|
|
6
|
+
*
|
|
7
|
+
* NaaN instances are managed by a top/bottom pair of of objects. InBot (INstance BOTtom) objects
|
|
8
|
+
* reside in the same JavaScript environment (node/browser) as their worker and communicate with the
|
|
9
|
+
* appropriate env_xxx.js adapter for NaaN in that environment.
|
|
10
|
+
*
|
|
11
|
+
* Zero or more InTop objects connect to each InBot object via the IMP network, providing remote
|
|
12
|
+
* evaluation and terminal access services. Each InTop supports only one terminal simultaneously, but
|
|
13
|
+
* they can be attached and detached at any time.
|
|
14
|
+
*
|
|
15
|
+
* column positioning: // // !
|
|
16
|
+
*
|
|
17
|
+
* Copyright (c) 2026 by Richard C. Zulch
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
* InBot
|
|
24
|
+
*
|
|
25
|
+
* Create a remote interface for a single execution instance, returning the object. This provides
|
|
26
|
+
* instance access to zero or more InTops via IMP connections. The tops subscribe to receive
|
|
27
|
+
* notifications/responses. This runs in the main thread and communicates with the worker via the
|
|
28
|
+
* standard JS messaging interface.
|
|
29
|
+
*
|
|
30
|
+
* Options:
|
|
31
|
+
* {
|
|
32
|
+
* name: <string> // target name, e.g. "Play"
|
|
33
|
+
* type: <string> // target category, e.g. "Local"
|
|
34
|
+
* startup: <dictionary> // startup options
|
|
35
|
+
* workerID: <string> // initialize worker with this
|
|
36
|
+
* topOriginID: <string> // so top can route to us through IMP
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
closure InBot(worker, options, local inbot)
|
|
42
|
+
{
|
|
43
|
+
global(App)
|
|
44
|
+
inbot = new(object, this)
|
|
45
|
+
inbot.topsubs = []
|
|
46
|
+
options = merge({
|
|
47
|
+
type: "Local"
|
|
48
|
+
}, options)
|
|
49
|
+
inbot.workerID = options.workerID
|
|
50
|
+
inbot.worker = worker
|
|
51
|
+
|
|
52
|
+
inbot.instanceID = UUID()
|
|
53
|
+
inbot.sender = App.imp.register(inbot, {
|
|
54
|
+
name: options.name
|
|
55
|
+
type: options.type
|
|
56
|
+
instanceID: inbot.instanceID
|
|
57
|
+
services: ["naan-worker"]
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// If this InBot is created remotely then we need to send a message from our new instanceID
|
|
61
|
+
// to the InTop origin so that it can send messages to us. The intermediate gateways save the
|
|
62
|
+
// route to allow replies.
|
|
63
|
+
if options.topOriginID && options.topOriginID != App.imp.us.instanceID
|
|
64
|
+
inbot.sender(options.topOriginID) // create route top->bottom
|
|
65
|
+
|
|
66
|
+
// subscribe
|
|
67
|
+
//
|
|
68
|
+
// Add an InTop to our subscribers if not already known.
|
|
69
|
+
//
|
|
70
|
+
function subscribe(destID) {
|
|
71
|
+
if !inbot.topsubs.find(function(item) { item == destID }) {
|
|
72
|
+
inbot.topsubs.push(destID)
|
|
73
|
+
if inbot.workerDestID
|
|
74
|
+
inbot.sender(destID, {
|
|
75
|
+
id: "workerDestID"
|
|
76
|
+
workerDestID: inbot.workerDestID
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// unsubscribe
|
|
82
|
+
//
|
|
83
|
+
// Remove an InTop from our subscribers if present.
|
|
84
|
+
//
|
|
85
|
+
function unsubscribe(destID) {
|
|
86
|
+
inbot.topsubs = inbot.topsubs.filter(function(item) { item != destID })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// notifyAll
|
|
90
|
+
//
|
|
91
|
+
// Send the message to all our subscribers.
|
|
92
|
+
//
|
|
93
|
+
inbot.notifyAll = function notifyAll(message, exceptID, local topDestID) {
|
|
94
|
+
for topDestID in inbot.topsubs
|
|
95
|
+
if topDestID != exceptID
|
|
96
|
+
inbot.sender(topDestID, message)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// impUnknown
|
|
100
|
+
//
|
|
101
|
+
// Received an unknown destination notice from the other side.
|
|
102
|
+
//
|
|
103
|
+
inbot.impUnknown = function impUnknown(unknownID, sourceID, gateway) {
|
|
104
|
+
if inbot.topsubs[unknownID] { // did not have courtesy to unsubscribe!
|
|
105
|
+
unsubscribe(unknownID)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
if !gateway
|
|
109
|
+
debuglog("InBot.impUnknownID:", unknownID, "sourceID:", sourceID)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// impReceive
|
|
113
|
+
//
|
|
114
|
+
// Received a message from InTop.
|
|
115
|
+
//
|
|
116
|
+
inbot.impReceive = function impReceive(message, sourceID) {
|
|
117
|
+
if message.id == "destroy"
|
|
118
|
+
destroy() // told to destroy worker
|
|
119
|
+
else if message.id == "subscribe"
|
|
120
|
+
subscribe(sourceID)
|
|
121
|
+
else if message.id == "unsubscribe"
|
|
122
|
+
unsubscribe(sourceID)
|
|
123
|
+
else if message.id == "typed"
|
|
124
|
+
notifyAll(message, sourceID)
|
|
125
|
+
else { // relay to worker
|
|
126
|
+
message.scope = "naan-env-web"
|
|
127
|
+
inbot.worker.postMessage(message)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// onmessage
|
|
132
|
+
//
|
|
133
|
+
// A message was received from the worker.
|
|
134
|
+
//
|
|
135
|
+
inbot.worker.addEventListener("message", function(e, local msg) {
|
|
136
|
+
msg = new(e.data)
|
|
137
|
+
if msg.scope == "naan-imp-web" {
|
|
138
|
+
inbot.impconn.dispatch(msg.data)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if msg.scope != "naan-env-web"
|
|
142
|
+
return
|
|
143
|
+
if msg.id == "loaded" {
|
|
144
|
+
if !inbot.started
|
|
145
|
+
inbot.worker.postMessage({
|
|
146
|
+
scope: "naan-env-web"
|
|
147
|
+
id: "start",
|
|
148
|
+
state: false // resume state, if we have any
|
|
149
|
+
altcmd: options.startup.initcmds
|
|
150
|
+
workerID: options.workerID
|
|
151
|
+
metadata: {
|
|
152
|
+
mainInstanceID: App.imp.us.instanceID
|
|
153
|
+
name: options.name
|
|
154
|
+
maintext: options.startup.maintext.concat("\n")
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
inbot.started = true
|
|
158
|
+
}
|
|
159
|
+
notifyAll(msg)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// onerror
|
|
163
|
+
//
|
|
164
|
+
// The worker had an error.
|
|
165
|
+
//
|
|
166
|
+
inbot.worker.addEventListener("error", function(e) {
|
|
167
|
+
debuglog("worker error:", e.message, "in", e.filename, "at", e.lineno)
|
|
168
|
+
notifyAll({
|
|
169
|
+
id: "error"
|
|
170
|
+
data: {
|
|
171
|
+
message: e.message
|
|
172
|
+
filename: e.filename
|
|
173
|
+
lineno: e.lineno
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
inbot.worker = false
|
|
177
|
+
destroy()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// destroy
|
|
181
|
+
//
|
|
182
|
+
// Ensure the worker is destroyed and notify everyone.
|
|
183
|
+
//
|
|
184
|
+
inbot.destroy = function destroy() {
|
|
185
|
+
if inbot.worker {
|
|
186
|
+
inbot.worker.terminate() // kill without saving
|
|
187
|
+
inbot.worker = false
|
|
188
|
+
}
|
|
189
|
+
notifyAll({
|
|
190
|
+
id: "terminated"
|
|
191
|
+
})
|
|
192
|
+
inbot.impconn.destroy()
|
|
193
|
+
inbot.impconn = false
|
|
194
|
+
App.imp.unregister(inbot.instanceID)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// finis
|
|
198
|
+
|
|
199
|
+
inbot
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
/*
|
|
204
|
+
* MewBot
|
|
205
|
+
*
|
|
206
|
+
* Create a remote InBot interface for the Main WEb browser thread.
|
|
207
|
+
*
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
closure MewBot(exec, options, local mewbot, inbot_impReceive)
|
|
211
|
+
{
|
|
212
|
+
global(App)
|
|
213
|
+
mewbot = InBot(exec, options)
|
|
214
|
+
mewbot.dispatcher = require("naanlib:frameworks/running/taskexec.nlg").TaskDispatcher()
|
|
215
|
+
inbot_impReceive = mewbot.impReceive
|
|
216
|
+
|
|
217
|
+
// impReceive
|
|
218
|
+
//
|
|
219
|
+
// Received a message from InTop.
|
|
220
|
+
//
|
|
221
|
+
mewbot.impReceive = function impReceive(message, sourceID) {
|
|
222
|
+
if message.id == "targetin"
|
|
223
|
+
mewbot.dispatcher(message.data, function(reply) {
|
|
224
|
+
mewbot.sender(sourceID, {
|
|
225
|
+
id: "targetout",
|
|
226
|
+
data: reply
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
else
|
|
230
|
+
inbot_impReceive(message, sourceID)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// finis
|
|
234
|
+
|
|
235
|
+
mewbot
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
/*
|
|
240
|
+
* WowBot
|
|
241
|
+
*
|
|
242
|
+
* Create a remote InBot interface for WebWorkerNub running in the worker. This forwards remoting
|
|
243
|
+
* messages to the worker so they can be handled in the correct place.
|
|
244
|
+
*
|
|
245
|
+
*/
|
|
246
|
+
|
|
247
|
+
closure WowBot(worker, options, local wowbot)
|
|
248
|
+
{
|
|
249
|
+
global(App)
|
|
250
|
+
wowbot = InBot(worker, options)
|
|
251
|
+
|
|
252
|
+
App.imp.addEventListener("active", function(event, info) {
|
|
253
|
+
if info.ourID == wowbot.instanceID {
|
|
254
|
+
wowbot.workerDestID = info.destID // handshake completed
|
|
255
|
+
wowbot.notifyAll({ // send to tops
|
|
256
|
+
id: "workerDestID"
|
|
257
|
+
workerDestID: wowbot.workerDestID
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
wowbot.impconn = App.imp.responder(function(data) {
|
|
263
|
+
wowbot.worker.postMessage({ // send IMP messages to worker
|
|
264
|
+
scope: "naan-imp-web" // scoped for WebWorkerNub
|
|
265
|
+
data: data
|
|
266
|
+
})
|
|
267
|
+
list(false, { sent: true })
|
|
268
|
+
}, {
|
|
269
|
+
ourID: wowbot.instanceID
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
// finis
|
|
273
|
+
|
|
274
|
+
wowbot
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
/*
|
|
279
|
+
* workInit
|
|
280
|
+
*
|
|
281
|
+
* Initialize the component.
|
|
282
|
+
*
|
|
283
|
+
*/
|
|
284
|
+
|
|
285
|
+
function workInit(local manifest) {
|
|
286
|
+
manifest = `(InBot, MewBot, WowBot, workInit)
|
|
287
|
+
|
|
288
|
+
Naan.module.build(module.id, "worker", function(modobj, compobj) {
|
|
289
|
+
require("./browser.nlg")
|
|
290
|
+
compobj.manifest = manifest
|
|
291
|
+
modobj.exports.InBot = InBot
|
|
292
|
+
modobj.exports.MewBot = MewBot
|
|
293
|
+
modobj.exports.WowBot = WowBot
|
|
294
|
+
})
|
|
295
|
+
} ();
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* column positioning: // // !
|
|
8
8
|
*
|
|
9
|
-
* Copyright (c) 2020-
|
|
9
|
+
* Copyright (c) 2020-2026 by Richard C. Zulch
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -314,7 +314,7 @@ closure ScopedServiceWorker(pubID, pubVersion, callback, local scoper, result) {
|
|
|
314
314
|
//
|
|
315
315
|
// Destroy the service worker.
|
|
316
316
|
|
|
317
|
-
|
|
317
|
+
scoper.destroy = function destroy() {
|
|
318
318
|
scoper.serv.unregister()
|
|
319
319
|
scoper.serv = false
|
|
320
320
|
}
|
|
@@ -427,22 +427,22 @@ closure TrackServiceWorkers(track, pubID, pubVersion, callback, local error) {
|
|
|
427
427
|
`(error, serviceWorker) = ScopedServiceWorker(pubID, pubVersion, callback)
|
|
428
428
|
if error
|
|
429
429
|
ErrorDebugLog("TrackServiceWorkers: cannot load service worker", error)
|
|
430
|
-
track.register(
|
|
430
|
+
track.register("V-Site", TrackedScope)
|
|
431
431
|
serviceWorker
|
|
432
432
|
};
|
|
433
433
|
|
|
434
434
|
|
|
435
435
|
/*
|
|
436
|
-
*
|
|
436
|
+
* worksInit
|
|
437
437
|
*
|
|
438
438
|
* Initialize the debugger page.
|
|
439
439
|
*
|
|
440
440
|
*/
|
|
441
441
|
|
|
442
|
-
function
|
|
442
|
+
function worksInit(local manifest) {
|
|
443
443
|
|
|
444
444
|
manifest = `(ServiceWorker, ScopedServiceWorker, findTrackedScope, TrackedScope,
|
|
445
|
-
TrackServiceWorkers,
|
|
445
|
+
TrackServiceWorkers, worksInit)
|
|
446
446
|
|
|
447
447
|
Naan.module.build(module.id, "workers", function(modobj, compobj) {
|
|
448
448
|
require("./browser.nlg")
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* column positioning: // // !
|
|
7
7
|
*
|
|
8
|
-
* Copyright (c) 2023-
|
|
8
|
+
* Copyright (c) 2023-2026 by Richard C. Zulch
|
|
9
9
|
*
|
|
10
10
|
*/
|
|
11
11
|
|
|
@@ -13,11 +13,14 @@
|
|
|
13
13
|
/*
|
|
14
14
|
* WebSocket
|
|
15
15
|
*
|
|
16
|
-
* Create a WebSocket client connection object
|
|
16
|
+
* Create a WebSocket client connection object.
|
|
17
|
+
*
|
|
18
|
+
* Optional, default Host:
|
|
19
|
+
* host: <string> // host:port
|
|
17
20
|
*
|
|
18
21
|
* Options:
|
|
19
22
|
* {
|
|
20
|
-
*
|
|
23
|
+
* wss: <boolean> // true to use secure connection
|
|
21
24
|
* onopen: <proc>(e) // connection has opened
|
|
22
25
|
* onmessage: <proc>(e, message) // message received
|
|
23
26
|
* onerror: <proc>(e) // error occurred
|
|
@@ -28,12 +31,14 @@
|
|
|
28
31
|
*
|
|
29
32
|
*/
|
|
30
33
|
|
|
31
|
-
closure WebSocket(options, local wsock) {
|
|
34
|
+
closure WebSocket(host, options, local wsock) {
|
|
32
35
|
global(window)
|
|
33
36
|
wsock = new(object, this)
|
|
34
37
|
options = new(options) || { }
|
|
35
|
-
if !
|
|
36
|
-
|
|
38
|
+
if !host {
|
|
39
|
+
host = window.location.host
|
|
40
|
+
options.wss = window.location.protocol == "https:"
|
|
41
|
+
}
|
|
37
42
|
|
|
38
43
|
// connect
|
|
39
44
|
//
|
|
@@ -42,10 +47,10 @@ closure WebSocket(options, local wsock) {
|
|
|
42
47
|
wsock.connect = closure connect(local url, pending) {
|
|
43
48
|
if wsock.ws
|
|
44
49
|
return (list(false, { open: true }))
|
|
45
|
-
if
|
|
46
|
-
url = "wss://${
|
|
50
|
+
if options.wss
|
|
51
|
+
url = "wss://${host}${options.querys||''}"
|
|
47
52
|
else
|
|
48
|
-
url = "ws://${
|
|
53
|
+
url = "ws://${host}${options.query||''}"
|
|
49
54
|
wsock.ws = xnew(window.WebSocket, url)
|
|
50
55
|
pending = new(nonce)
|
|
51
56
|
|
|
@@ -58,7 +63,7 @@ closure WebSocket(options, local wsock) {
|
|
|
58
63
|
|
|
59
64
|
// onmessage
|
|
60
65
|
// A message was received from the worker via WebSockets.
|
|
61
|
-
wsock.ws.onmessage = closure onmessage(e, message) {
|
|
66
|
+
wsock.ws.onmessage = closure onmessage(e, local message) {
|
|
62
67
|
try {
|
|
63
68
|
message = new(JSONparse(e.data))
|
|
64
69
|
} catch {
|
|
@@ -2,43 +2,65 @@
|
|
|
2
2
|
* apiclient.nlg
|
|
3
3
|
* Naanlib/frameworks/client
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Access to Nide API with specified HTTP client.
|
|
6
6
|
*
|
|
7
7
|
* column positioning: // // !
|
|
8
8
|
*
|
|
9
|
-
* Copyright (c) 2017-
|
|
9
|
+
* Copyright (c) 2017-2026 by Richard C. Zulch
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
// Globals
|
|
15
|
+
|
|
16
|
+
serviceMod;;
|
|
17
|
+
|
|
18
|
+
|
|
14
19
|
/*
|
|
15
20
|
* APIClient
|
|
16
21
|
*
|
|
17
|
-
*
|
|
22
|
+
* Make an API client object. If you want websockets, and you do, call connect() on the object.
|
|
18
23
|
*
|
|
19
24
|
* Options:
|
|
20
25
|
* {
|
|
21
26
|
* url: <string> // hostname or URL of the remote server
|
|
22
27
|
* guid: <string> // shared secret for access to remote server
|
|
23
|
-
* instanceID: <string> // optional UUID for our client; can be persisted by caller
|
|
24
28
|
* }
|
|
25
29
|
*
|
|
26
30
|
*/
|
|
27
31
|
|
|
28
|
-
closure APIClient(options, local client, url, guid) {
|
|
29
|
-
global(App)
|
|
32
|
+
closure APIClient(imp, options, local client, url, guid) {
|
|
33
|
+
global(App, serviceMod)
|
|
30
34
|
client = new(object, this)
|
|
35
|
+
options = merge({
|
|
36
|
+
}, options)
|
|
31
37
|
url = options.url
|
|
32
38
|
if !url.match(RegExp("^https?:\/\/", "i"))
|
|
33
39
|
url = "http://${url}" // a protocol is required
|
|
34
40
|
if url.slice(-1) != "/"
|
|
35
41
|
url = url.concat("/") // must end in "/"
|
|
36
42
|
client.url = url
|
|
43
|
+
options.url = url
|
|
37
44
|
client.guid = options.guid
|
|
38
|
-
client.instanceID = options.instanceID || UUID() // our client's unique ID
|
|
39
45
|
client.notifyAfter = 0
|
|
40
46
|
client.requester = clirRequester()
|
|
41
47
|
|
|
48
|
+
//
|
|
49
|
+
// connect
|
|
50
|
+
//
|
|
51
|
+
|
|
52
|
+
client.connect = function connect(error) {
|
|
53
|
+
if !client.wesc
|
|
54
|
+
client.wesc = WebSocketClient(options, imp)
|
|
55
|
+
`(error) = client.wesc.connect()
|
|
56
|
+
if error {
|
|
57
|
+
ErrorDebuglog("APIClient: cannot connect to", url, error)
|
|
58
|
+
return (list(error))
|
|
59
|
+
}
|
|
60
|
+
client.serverID = client.wesc.serverID // serverID
|
|
61
|
+
list(false, { connected: true })
|
|
62
|
+
}
|
|
63
|
+
|
|
42
64
|
// clientRequest() - append our guid to the headers
|
|
43
65
|
|
|
44
66
|
closure clientRequest(url, reqops) {
|
|
@@ -152,24 +174,204 @@ closure APIClient(options, local client, url, guid) {
|
|
|
152
174
|
else
|
|
153
175
|
callback(error, content)
|
|
154
176
|
}
|
|
177
|
+
|
|
178
|
+
// destroy
|
|
179
|
+
//
|
|
180
|
+
// Close down the client and release all resources.
|
|
181
|
+
//
|
|
182
|
+
client.destroy = closure destroy() {
|
|
183
|
+
debuglog("API client destroyed") // ### not pretty, but uncommon
|
|
184
|
+
client.wesc.destroy()
|
|
185
|
+
client.wesc = false
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// finis
|
|
155
189
|
|
|
156
190
|
client
|
|
157
191
|
};
|
|
158
192
|
|
|
159
193
|
|
|
194
|
+
/*
|
|
195
|
+
* WebSocketClient
|
|
196
|
+
*
|
|
197
|
+
* WebSocket client for websocket communications with a server. The Instance Messaage Processor (IMP)
|
|
198
|
+
* handles routing between instances using intermediaries.
|
|
199
|
+
*
|
|
200
|
+
* Options:
|
|
201
|
+
* {
|
|
202
|
+
* url: <string> // hostname or URL of the remote server
|
|
203
|
+
* guid: <string> // shared secret for access to remote server
|
|
204
|
+
* }
|
|
205
|
+
*
|
|
206
|
+
*/
|
|
207
|
+
|
|
208
|
+
closure WebSocketClient(options, imp, local wesc, ws, pending) {
|
|
209
|
+
global(js)
|
|
210
|
+
wesc = new(object, this)
|
|
211
|
+
|
|
212
|
+
// reconnect
|
|
213
|
+
//
|
|
214
|
+
// Attempt to reconnect if needed, returning false on success or an error tuple otherwise.
|
|
215
|
+
//
|
|
216
|
+
function reconnect() {
|
|
217
|
+
if !ws || wesc.fatal || pending {
|
|
218
|
+
connect() // try to connect
|
|
219
|
+
if wesc.fatal
|
|
220
|
+
return (list(wesc.fatal))
|
|
221
|
+
}
|
|
222
|
+
false
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// connect
|
|
226
|
+
//
|
|
227
|
+
// Establish a websocket connection with the host.
|
|
228
|
+
//
|
|
229
|
+
wesc.connect = closure connect(local url, result) {
|
|
230
|
+
if ws {
|
|
231
|
+
if pending
|
|
232
|
+
return (pending.wait()) // connect in process
|
|
233
|
+
if !wesc.fatal
|
|
234
|
+
return(list(false, { connected: true })) // already open
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
url = xnew(js.w.URL, options.url)
|
|
238
|
+
} catch {
|
|
239
|
+
return(list(Error("invalid URL ${options.url}", exception)))
|
|
240
|
+
}
|
|
241
|
+
if url.protocol == "https:"
|
|
242
|
+
url.protocol = "wss:"
|
|
243
|
+
else
|
|
244
|
+
url.protocol = "ws:"
|
|
245
|
+
ws = xnew(js.w.WebSocket, url.href)
|
|
246
|
+
if wesc.fatal
|
|
247
|
+
debuglog("WebSocketClient reopen") // ### not pretty to see this
|
|
248
|
+
wesc.fatal = false
|
|
249
|
+
pending = new(nonce)
|
|
250
|
+
|
|
251
|
+
// onopen
|
|
252
|
+
// The WebSocket connection is now open.
|
|
253
|
+
ws.onopen = function onopen(e) {
|
|
254
|
+
if wesc.impconn {
|
|
255
|
+
result = wesc.impconn.reopen()
|
|
256
|
+
pending.signal(result)
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
wesc.impconn = imp.requester(function(data) {
|
|
260
|
+
if !pending && reconnect() // !pending means not opening/reopening
|
|
261
|
+
return // failed to reconnect
|
|
262
|
+
ws.send(js.w.JSON.stringify(data))
|
|
263
|
+
list(false, { sent: true })
|
|
264
|
+
}, {
|
|
265
|
+
secret: options.guid
|
|
266
|
+
label: "api-server"
|
|
267
|
+
})
|
|
268
|
+
wesc.impconn.addEventListener("active", function (event, info) {
|
|
269
|
+
wesc.serverID = info.destID // serverID we connected to
|
|
270
|
+
pending.signal(list(false, { ok: true }))
|
|
271
|
+
pending = false
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// onmessage
|
|
276
|
+
// A message was received via WebSockets.
|
|
277
|
+
ws.onmessage = function onmessage(e, local error, message) {
|
|
278
|
+
`(error, message) = JsonParse(e.data)
|
|
279
|
+
if !error {
|
|
280
|
+
message = new(message)
|
|
281
|
+
if wesc.impconn.dispatch(message)
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
pending.signal(list(Error("incompatible responder")))
|
|
285
|
+
pending = false
|
|
286
|
+
debuglog("socket message:", e.data) // unexpected incoming data
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// onerror
|
|
290
|
+
// An error occurred on the WebSocket connection.
|
|
291
|
+
// This can happen in normal operation when the connection times out.
|
|
292
|
+
ws.onerror = function onerror(e) {
|
|
293
|
+
if !wesc.fatal
|
|
294
|
+
wesc.fatal = Error("websocket failed", e)
|
|
295
|
+
pending.signal(list(wesc.fatal))
|
|
296
|
+
pending = false
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// onclose
|
|
300
|
+
// The WebSocket connection was closed.
|
|
301
|
+
ws.onclose = function onclose(e) {
|
|
302
|
+
// if e.code != 1000 && e.code != 1001 && e.code != 1005 && e.code != 1006
|
|
303
|
+
debuglog("WebSocketClient: closed:", e.code, e.reason) // ### not pretty to see this
|
|
304
|
+
ws = false
|
|
305
|
+
if !wesc.fatal
|
|
306
|
+
wesc.fatal = Error("websocket closed", e.code, e.reason)
|
|
307
|
+
pending.signal(list(wesc.fatal))
|
|
308
|
+
pending = false
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
result = pending.wait(10000, list(Error("IMP connection timeout")))
|
|
312
|
+
pending = false
|
|
313
|
+
result
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// close
|
|
317
|
+
//
|
|
318
|
+
// Close the connection, but can be opened again with connect().
|
|
319
|
+
//
|
|
320
|
+
wesc.close = function close() {
|
|
321
|
+
if ws
|
|
322
|
+
ws.close(1000) // 1000: normal close
|
|
323
|
+
ws = false
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// destroy
|
|
327
|
+
//
|
|
328
|
+
// Destroy the connection permanently.
|
|
329
|
+
//
|
|
330
|
+
wesc.destroy = function destroy() {
|
|
331
|
+
close()
|
|
332
|
+
wesc.impconn.destroy()
|
|
333
|
+
wesc.impconn = false
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// send
|
|
337
|
+
//
|
|
338
|
+
// Send a message, returning result tuple.
|
|
339
|
+
//
|
|
340
|
+
wesc.send = closure send(data) {
|
|
341
|
+
if reconnect()
|
|
342
|
+
return // failed to reconnect
|
|
343
|
+
wesc.impconn.send(data)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// sendRaw
|
|
347
|
+
//
|
|
348
|
+
// Send a message with reconnect but no routing. Returns a result tuple.
|
|
349
|
+
//
|
|
350
|
+
wesc.sendRaw = closure sendRaw(data) {
|
|
351
|
+
if reconnect()
|
|
352
|
+
return // failed to reconnect
|
|
353
|
+
ws.send(data)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// finis
|
|
357
|
+
wesc
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
|
|
160
361
|
/*
|
|
161
362
|
* apicInit
|
|
162
363
|
*
|
|
163
|
-
*
|
|
364
|
+
* Initialize the apiclient component.
|
|
164
365
|
*
|
|
165
366
|
*/
|
|
166
367
|
|
|
167
368
|
function apicInit(local manifest) {
|
|
168
369
|
|
|
169
|
-
manifest = `(APIClient, apicInit)
|
|
370
|
+
manifest = `(APIClient, WebSocketClient, apicInit)
|
|
170
371
|
|
|
171
372
|
Naan.module.build(module.id, "apiclient", function(modobj, compobj) {
|
|
172
373
|
Naan.module.require("./client.nlg")
|
|
374
|
+
serviceMod = Naan.module.require("naanlib:frameworks/service/imp.nlg")
|
|
173
375
|
compobj.manifest = manifest
|
|
174
376
|
modobj.exports.APIClient = APIClient
|
|
175
377
|
})
|