@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,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-2023 by Richard C. Zulch
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
- worker.destroy = function destroy() {
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(TrackedScope, "V-Site")
430
+ track.register("V-Site", TrackedScope)
431
431
  serviceWorker
432
432
  };
433
433
 
434
434
 
435
435
  /*
436
- * workInit
436
+ * worksInit
437
437
  *
438
438
  * Initialize the debugger page.
439
439
  *
440
440
  */
441
441
 
442
- function workInit(local manifest) {
442
+ function worksInit(local manifest) {
443
443
 
444
444
  manifest = `(ServiceWorker, ScopedServiceWorker, findTrackedScope, TrackedScope,
445
- TrackServiceWorkers, workInit)
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-2024 by Richard C. Zulch
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 using the existing HTTPS connection.
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
- * host: <string> // host:port
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 !options.host
36
- options.host = window.location.host
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 window.location.protocol == "https:"
46
- url = "wss://${options.host}${options.querys||''}"
50
+ if options.wss
51
+ url = "wss://${host}${options.querys||''}"
47
52
  else
48
- url = "ws://${options.host}${options.query||''}"
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
- * Access to Nide API with specified HTTP client.
5
+ * Access to Nide API with specified HTTP client.
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2025 by Richard C. Zulch
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
- * Make an API client object.
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
- * Initialize the apic component.
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
  })