@naanlang/naan 1.6.0 → 1.7.1

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 (54) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +1 -1
  3. package/bin/index.js +2 -2
  4. package/dist/env_web.js +3 -32
  5. package/dist/naan.min.js +10 -10
  6. package/frameworks/browser/sworker.js +23 -23
  7. package/frameworks/browser/terminals.nlg +161 -212
  8. package/frameworks/browser/worker.nlg +78 -30
  9. package/frameworks/browser/workers.nlg +1 -1
  10. package/frameworks/browser/ws_client.nlg +23 -87
  11. package/frameworks/client/apiclient.nlg +91 -58
  12. package/frameworks/client/client.nlg +22 -2
  13. package/frameworks/client/psm_client.nlg +10 -14
  14. package/frameworks/client/relaycon.nlg +1 -1
  15. package/frameworks/common/common.nlg +13 -9
  16. package/frameworks/node/apiserver.nlg +1 -2
  17. package/frameworks/node/node.nlg +1 -0
  18. package/frameworks/node/pty.nlg +21 -21
  19. package/frameworks/node/shell.nlg +26 -3
  20. package/frameworks/node/worker.nlg +166 -84
  21. package/frameworks/node/ws_client.nlg +25 -84
  22. package/frameworks/project/build.nlg +83 -32
  23. package/frameworks/project/projects.nlg +2 -2
  24. package/frameworks/running/debugcom.nlg +20 -3
  25. package/frameworks/running/debugnub.nlg +16 -7
  26. package/frameworks/running/executors.nlg +36 -26
  27. package/frameworks/running/instances.nlg +34 -22
  28. package/frameworks/running/remote_req.nlg +6 -9
  29. package/frameworks/running/remote_res.nlg +2 -2
  30. package/frameworks/running/sourcecode.nlg +3 -2
  31. package/frameworks/running/taskexec.nlg +14 -57
  32. package/frameworks/service/imp.nlg +37 -5
  33. package/frameworks/service/nubnode.nlg +39 -8
  34. package/frameworks/service/nubweb.nlg +28 -10
  35. package/frameworks/storage/file_manager.nlg +3 -2
  36. package/lib/browser/env_web.js +10 -39
  37. package/lib/browser/env_webworker.js +1 -30
  38. package/lib/core/naanlib.js +10 -10
  39. package/lib/env_node.js +5 -22
  40. package/lib/env_nodeworker.js +8 -43
  41. package/lib/node_server_init.nlg +149 -0
  42. package/lib/node_worker.js +36 -0
  43. package/lib/node_worker_init.nlg +43 -0
  44. package/package.json +1 -1
  45. package/plugins/openSearch/openSearch.nlg +3 -2
  46. package/plugins/openSearch/os_dynamo.nlg +3 -2
  47. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +44 -2
  48. package/plugins/serviceAws/aws_dynamo.nlg +144 -63
  49. package/plugins/serviceAws/aws_utils.nlg +6 -11
  50. package/plugins/serviceAws/serviceAws.nlg +3 -2
  51. package/test/test_01_core.nlg +2 -2
  52. package/test/test_02_context.nlg +8 -8
  53. package/test/test_03_jsinterop.nlg +3 -3
  54. package/test/test_07_lingo.nlg +1 -1
@@ -2,7 +2,7 @@
2
2
  * worker.nlg
3
3
  * Naanlib/frameworks/browser
4
4
  *
5
- * Manage NaaN instances.
5
+ * Manage web-based NaaN instances.
6
6
  *
7
7
  * NaaN instances are managed by a top/bottom pair of of objects. InBot (INstance BOTtom) objects
8
8
  * reside in the same JavaScript environment (node/browser) as their worker and communicate with the
@@ -24,7 +24,8 @@
24
24
  *
25
25
  * Create a remote interface for a single execution instance, returning the object. This provides
26
26
  * instance access to zero or more InTops via IMP connections. The tops subscribe to receive
27
- * notifications/responses.
27
+ * notifications/responses. This runs in the main thread and communicates with the worker via the
28
+ * standard JS messaging interface.
28
29
  *
29
30
  * Options:
30
31
  * {
@@ -32,7 +33,6 @@
32
33
  * type: <string> // target category, e.g. "Local"
33
34
  * startup: <dictionary> // startup options
34
35
  * workerID: <string> // initialize worker with this
35
- * workerGUID: <string> // initialize worker with this
36
36
  * topOriginID: <string> // so top can route to us through IMP
37
37
  * }
38
38
  *
@@ -49,7 +49,7 @@ closure InBot(worker, options, local inbot)
49
49
  inbot.workerID = options.workerID
50
50
  inbot.worker = worker
51
51
 
52
- inbot.instanceID = UUID()
52
+ inbot.instanceID = App.imp.getUUID("InBot(${options.name}) instanceID")
53
53
  inbot.sender = App.imp.register(inbot, {
54
54
  name: options.name
55
55
  type: options.type
@@ -133,9 +133,9 @@ closure InBot(worker, options, local inbot)
133
133
  // A message was received from the worker.
134
134
  //
135
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)
136
+ msg = e.data
137
+ if msg.scope == "naan-imp-out" {
138
+ inbot.impconn.dispatch(new(msg.data))
139
139
  return
140
140
  }
141
141
  if msg.scope != "naan-env-web"
@@ -148,10 +148,10 @@ closure InBot(worker, options, local inbot)
148
148
  state: false // resume state, if we have any
149
149
  altcmd: options.startup.initcmds
150
150
  workerID: options.workerID
151
- workerGUID: options.workerGUID
152
151
  metadata: {
153
152
  mainInstanceID: App.imp.us.instanceID
154
153
  name: options.name
154
+ maintext: options.startup.maintext.concat("\n")
155
155
  }
156
156
  })
157
157
  inbot.started = true
@@ -164,15 +164,29 @@ closure InBot(worker, options, local inbot)
164
164
  // The worker had an error.
165
165
  //
166
166
  inbot.worker.addEventListener("error", function(e) {
167
- debuglog("worker error:", e.message)
167
+ debuglog("worker error:", e.message, "in", e.filename, "at", e.lineno)
168
168
  notifyAll({
169
169
  id: "error"
170
- data: e.message
170
+ data: {
171
+ message: e.message
172
+ filename: e.filename
173
+ lineno: e.lineno
174
+ }
171
175
  })
172
176
  inbot.worker = false
173
177
  destroy()
174
178
  })
175
179
 
180
+ // close
181
+ //
182
+ // Close and remove our IMP resources, but leave the worker alone.
183
+ //
184
+ inbot.close = function close() {
185
+ inbot.impconn.destroy()
186
+ inbot.impconn = false
187
+ App.imp.unregister(inbot.instanceID)
188
+ }
189
+
176
190
  // destroy
177
191
  //
178
192
  // Ensure the worker is destroyed and notify everyone.
@@ -185,13 +199,9 @@ closure InBot(worker, options, local inbot)
185
199
  notifyAll({
186
200
  id: "terminated"
187
201
  })
188
- inbot.impconn.close()
189
- inbot.impconn = false
190
- App.imp.unregister(inbot.instanceID)
202
+ close()
191
203
  }
192
204
 
193
- // register our connects *after* the receivers are configured above
194
-
195
205
  // finis
196
206
 
197
207
  inbot
@@ -199,41 +209,77 @@ closure InBot(worker, options, local inbot)
199
209
 
200
210
 
201
211
  /*
202
- * NubBot
212
+ * MewBot
213
+ *
214
+ * Create a remote InBot interface for the Main WEb browser thread.
215
+ *
216
+ */
217
+
218
+ closure MewBot(exec, options, local mewbot, inbot_impReceive)
219
+ {
220
+ global(App)
221
+ mewbot = InBot(exec, options)
222
+ mewbot.dispatcher = require("naanlib:frameworks/running/taskexec.nlg").TaskDispatcher()
223
+ inbot_impReceive = mewbot.impReceive
224
+
225
+ // impReceive
226
+ //
227
+ // Received a message from InTop.
228
+ //
229
+ mewbot.impReceive = function impReceive(message, sourceID) {
230
+ if message.id == "targetin"
231
+ mewbot.dispatcher(message.data, function(reply) {
232
+ mewbot.sender(sourceID, {
233
+ id: "targetout",
234
+ data: reply
235
+ })
236
+ })
237
+ else
238
+ inbot_impReceive(message, sourceID)
239
+ }
240
+
241
+ // finis
242
+
243
+ mewbot
244
+ }
245
+
246
+
247
+ /*
248
+ * WowBot
203
249
  *
204
- * Create a remote interface like InBot, but also provide messaging support for WebWorkerNub inside
205
- * the worker.
250
+ * Create a remote InBot interface for WebWorkerNub running in the worker. This forwards remoting
251
+ * messages to the worker so they can be handled in the correct place.
206
252
  *
207
253
  */
208
254
 
209
- closure NubBot(worker, options, local nubbot)
255
+ closure WowBot(worker, options, local wowbot)
210
256
  {
211
257
  global(App)
212
- nubbot = InBot(worker, options)
258
+ wowbot = InBot(worker, options)
213
259
 
214
260
  App.imp.addEventListener("active", function(event, info) {
215
- if info.ourID == nubbot.instanceID {
216
- nubbot.workerDestID = info.destID // handshake completed
217
- nubbot.notifyAll({ // send to tops
261
+ if info.ourID == wowbot.instanceID {
262
+ wowbot.workerDestID = info.destID // handshake completed
263
+ wowbot.notifyAll({ // send to tops
218
264
  id: "workerDestID"
219
- workerDestID: nubbot.workerDestID
265
+ workerDestID: wowbot.workerDestID
220
266
  })
221
267
  }
222
268
  })
223
269
 
224
- nubbot.impconn = App.imp.responder(function(data) {
225
- nubbot.worker.postMessage({ // send to worker
226
- scope: "naan-imp-web" // scoped for WorkerNub
270
+ wowbot.impconn = App.imp.responder(function(data) {
271
+ wowbot.worker.postMessage({ // send IMP messages to worker
272
+ scope: "naan-imp-in" // scoped for WebWorkerNub
227
273
  data: data
228
274
  })
229
275
  list(false, { sent: true })
230
276
  }, {
231
- ourID: nubbot.instanceID
277
+ ourID: wowbot.instanceID
232
278
  })
233
279
 
234
280
  // finis
235
281
 
236
- nubbot
282
+ wowbot
237
283
  };
238
284
 
239
285
 
@@ -245,11 +291,13 @@ closure NubBot(worker, options, local nubbot)
245
291
  */
246
292
 
247
293
  function workInit(local manifest) {
248
- manifest = `(InBot, NubBot, workInit)
294
+ manifest = `(InBot, MewBot, WowBot, workInit)
249
295
 
250
296
  Naan.module.build(module.id, "worker", function(modobj, compobj) {
251
297
  require("./browser.nlg")
252
298
  compobj.manifest = manifest
253
299
  modobj.exports.InBot = InBot
300
+ modobj.exports.MewBot = MewBot
301
+ modobj.exports.WowBot = WowBot
254
302
  })
255
303
  } ();
@@ -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
  }
@@ -13,107 +13,43 @@
13
13
  /*
14
14
  * WebSocket
15
15
  *
16
- * Create a WebSocket client connection object.
16
+ * Create a WebSocket client connection object, returning a tuple.
17
17
  *
18
- * Optional, default Host:
19
- * host: <string> // host:port
18
+ * Required:
19
+ * urlstr: <string>
20
20
  *
21
21
  * Options:
22
22
  * {
23
23
  * wss: <boolean> // true to use secure connection
24
+ * query: <string> // query string starting with ?
24
25
  * onopen: <proc>(e) // connection has opened
25
26
  * onmessage: <proc>(e, message) // message received
26
27
  * onerror: <proc>(e) // error occurred
27
28
  * onclose: <proc>(e) // connection has closed
28
- * query: <string> // query string starting with ? for insecure connections
29
- * querys: <string> // query string starting with ? for secure connections
30
29
  * }
31
30
  *
32
31
  */
33
32
 
34
- closure WebSocket(host, options, local wsock) {
35
- global(window)
36
- wsock = new(object, this)
37
- options = new(options) || { }
38
- if !host {
39
- host = window.location.host
40
- options.wss = window.location.protocol == "https:"
33
+ closure WebSocket(urlstr, options, local url, ws) {
34
+ try {
35
+ url = xnew(js.w.URL, urlstr)
36
+ } catch {
37
+ return (list(Error("invalid URL ${urlstr}", exception)))
41
38
  }
42
-
43
- // connect
44
- //
45
- // Connect with the host if not already connected, returning a result tuple.
46
- //
47
- wsock.connect = closure connect(local url, pending) {
48
- if wsock.ws
49
- return (list(false, { open: true }))
50
- if options.wss
51
- url = "wss://${host}${options.querys||''}"
52
- else
53
- url = "ws://${host}${options.query||''}"
54
- wsock.ws = xnew(window.WebSocket, url)
55
- pending = new(nonce)
56
-
57
- // onopen
58
- // The WebSocket connection is now open.
59
- wsock.ws.onopen = closure onopen(e) {
60
- pending.signal(list(false, { ok: true }))
61
- options.onopen(e)
62
- }
63
-
64
- // onmessage
65
- // A message was received from the worker via WebSockets.
66
- wsock.ws.onmessage = closure onmessage(e, local message) {
67
- try {
68
- message = new(JSONparse(e.data))
69
- } catch {
70
- debuglog("can't decode incoming ws message:", exception)
71
- options.onerror(Error("can't decode incoming message:", exception))
72
- return
73
- }
74
- options.onmessage(e, message)
75
- }
76
-
77
- // onerror
78
- // An error occurred on the WebSocket connection.
79
- wsock.ws.onerror = closure onerror(e, local error) {
80
- error = Error("websocket failed", e)
81
- options.onerror(e, error)
82
- pending.signal(list(error)) // ensure connect completes
83
- }
84
-
85
- // onclose
86
- // The WebSocket connection was closed.
87
- wsock.ws.onclose = closure onclose(e) {
88
- options.onclose(e, e.code, e.reason)
89
- pending.signal(list(Error("connection closed"))) // ensure connect completes
90
- wsock.ws = false
91
- }
92
-
93
- pending.wait()
94
- }
95
-
96
- // send
97
- //
98
- // Send a message dictionary.
99
- //
100
- wsock.send = function send(message) {
101
- wsock.ws.send(JSONstringify(message))
102
- }
103
-
104
- // close
105
- //
106
- // Close the connection.
107
- //
108
- wsock.close = function close(reason) {
109
- if !reason
110
- reason = 1000 // normal close
111
- wsock.ws.close(reason)
112
- }
113
-
114
- // finis
115
-
116
- wsock
39
+ options = merge({
40
+ query: url.search || ''
41
+ wss: url.protocol == "https:"
42
+ }, options)
43
+ if options.wss
44
+ url = "wss://${url.host}${options.query}"
45
+ else
46
+ url = "ws://${url.host}${options.query}"
47
+ ws = xnew(window.WebSocket, url)
48
+ ws.onopen = options.onopen
49
+ ws.onmessage = options.onmessage
50
+ ws.onerror = options.onerror
51
+ ws.onclose = options.onclose
52
+ list(false, ws)
117
53
  };
118
54
 
119
55
 
@@ -19,7 +19,7 @@ serviceMod;;
19
19
  /*
20
20
  * APIClient
21
21
  *
22
- * Make an API client object.
22
+ * Make an API client object. If you want websockets, and you do, call connect() on the object.
23
23
  *
24
24
  * Options:
25
25
  * {
@@ -46,18 +46,20 @@ closure APIClient(imp, options, local client, url, guid) {
46
46
  client.requester = clirRequester()
47
47
 
48
48
  //
49
- // wsopen
49
+ // connect
50
50
  //
51
51
 
52
- client.wsopen = function wsopen(error) {
53
- if !client.wesc {
52
+ client.connect = function connect(error) {
53
+ if !client.wesc
54
54
  client.wesc = WebSocketClient(options, imp)
55
- `(error) = client.wesc.connect()
56
- if error
57
- ErrorDebuglog("APIClient: cannot connect to", url)
55
+ `(error) = client.wesc.connect()
56
+ if error {
57
+ ErrorDebuglog("APIClient: cannot connect to", url, error)
58
+ return (list(error))
58
59
  }
60
+ client.serverID = client.wesc.serverID // serverID
61
+ list(false, { connected: true })
59
62
  }
60
- wsopen()
61
63
 
62
64
  // clientRequest() - append our guid to the headers
63
65
 
@@ -178,10 +180,12 @@ closure APIClient(imp, options, local client, url, guid) {
178
180
  // Close down the client and release all resources.
179
181
  //
180
182
  client.destroy = closure destroy() {
181
- debuglog("client destroy websocket")
183
+ debuglog("API client destroyed") // ### not pretty, but uncommon
182
184
  client.wesc.destroy()
183
185
  client.wesc = false
184
186
  }
187
+
188
+ // finis
185
189
 
186
190
  client
187
191
  };
@@ -197,104 +201,129 @@ closure APIClient(imp, options, local client, url, guid) {
197
201
  * {
198
202
  * url: <string> // hostname or URL of the remote server
199
203
  * guid: <string> // shared secret for access to remote server
200
- * instanceID: <string> // UUID for our client; can be persisted by caller
201
204
  * }
202
205
  *
203
206
  */
204
207
 
205
- closure WebSocketClient(options, imp, local wesc, ws) {
208
+ closure WebSocketClient(options, imp, local wesc, ws, pending) {
206
209
  global(js)
207
210
  wesc = new(object, this)
211
+
212
+
213
+ // reconnect
214
+ //
215
+ // Attempt to reconnect if needed, returning false on success or an error tuple otherwise.
216
+ //
217
+ function reconnect() {
218
+ if !ws || wesc.fatal || pending {
219
+ connect() // try to connect
220
+ if wesc.fatal
221
+ return (list(wesc.fatal))
222
+ }
223
+ false
224
+ }
208
225
 
209
226
  // connect
210
227
  //
211
228
  // Establish a websocket connection with the host.
212
229
  //
213
- wesc.connect = closure connect(local url, pending) {
214
- if ws && !wesc.fatal
215
- return(list(false, { connected: true }))
216
- if wesc.fatal
217
- debuglog("WebSocketClient reopen")
218
- wesc.fatal = false
219
- try {
220
- url = xnew(js.w.URL, options.url)
221
- } catch {
222
- return(list(Error("invalid URL ${options.url}", exception)))
230
+ wesc.connect = closure connect(local url, result, error) {
231
+ if ws {
232
+ if pending
233
+ return (pending.wait()) // connect in process
234
+ if !wesc.fatal
235
+ return(list(false, { connected: true })) // already open
223
236
  }
224
- if url.protocol == "https:"
225
- url.protocol = "wss:"
226
- else
227
- url.protocol = "ws:"
228
- ws = xnew(js.w.WebSocket, url.href)
229
- pending = new(nonce)
230
-
237
+
231
238
  // onopen
232
239
  // The WebSocket connection is now open.
233
- ws.onopen = function onopen(e, local error) {
234
- wesc.impconn = imp.requester(function(data) {
235
- if wesc.fatal // raw send function
236
- return (list(wesc.fatal))
237
- ws.send(js.w.JSON.stringify(data))
240
+ function onopen(e) {
241
+ if wesc.impconn {
242
+ result = wesc.impconn.reopen()
243
+ pending.signal(result)
244
+ return
245
+ }
246
+ wesc.impconn = imp.requester(function(data, local error, text) {
247
+ if !pending && reconnect() // !pending means not opening/reopening
248
+ return // failed to reconnect
249
+ `(error, text) = JsonStringify(data)
250
+ if error
251
+ return (list(error))
252
+ ws.send(text)
238
253
  list(false, { sent: true })
239
254
  }, {
240
255
  secret: options.guid
241
256
  label: "api-server"
242
- wesc: wesc
243
257
  })
244
- if error
245
- pending.signal(list(error))
246
- else
258
+ wesc.impconn.addEventListener("active", function (event, info) {
259
+ wesc.serverID = info.destID // serverID we connected to
247
260
  pending.signal(list(false, { ok: true }))
261
+ pending = false
262
+ })
248
263
  }
249
-
264
+
250
265
  // onmessage
251
266
  // A message was received via WebSockets.
252
- ws.onmessage = function onmessage(e, local error, message) {
267
+ function onmessage(e, local error, message) {
253
268
  `(error, message) = JsonParse(e.data)
254
269
  if !error {
255
270
  message = new(message)
256
271
  if wesc.impconn.dispatch(message)
257
272
  return
258
273
  }
259
- debuglog("socket message:", e.data)
274
+ pending.signal(list(Error("incompatible responder")))
275
+ pending = false
276
+ debuglog("socket message:", e.data) // unexpected incoming data
260
277
  }
261
-
278
+
262
279
  // onerror
263
280
  // An error occurred on the WebSocket connection.
264
281
  // This can happen in normal operation when the connection times out.
265
- ws.onerror = function onerror(e) {
282
+ function onerror(e) {
266
283
  if !wesc.fatal
267
284
  wesc.fatal = Error("websocket failed", e)
268
285
  pending.signal(list(wesc.fatal))
286
+ pending = false
269
287
  }
270
288
 
271
289
  // onclose
272
290
  // The WebSocket connection was closed.
273
- ws.onclose = function onclose(e) {
291
+ function onclose(e) {
274
292
  // if e.code != 1000 && e.code != 1001 && e.code != 1005 && e.code != 1006
275
- debuglog("WebSocketClient: closed:", e.code, e.reason)
293
+ debuglog("WebSocketClient: closed:", e.code, e.reason) // ### not pretty to see this
276
294
  ws = false
277
295
  if !wesc.fatal
278
296
  wesc.fatal = Error("websocket closed", e.code, e.reason)
279
297
  pending.signal(list(wesc.fatal))
280
- wesc.impconn.close({
281
- code: e.code
282
- reason: e.reason
283
- })
284
- wesc.impconn = false
298
+ pending = false
285
299
  }
286
300
 
287
- pending.wait()
301
+ `(error, ws) = call(clirWebsocket(), options.url, {
302
+ onopen: onopen.proc
303
+ onmessage: onmessage.proc
304
+ onerror: onerror.proc
305
+ onclose: onclose.proc
306
+ })
307
+ if error
308
+ return (list(wesc.fatal = error)) // failed to even get started
309
+
310
+ if wesc.fatal
311
+ debuglog("WebSocketClient reopen") // ### not pretty to see this
312
+ wesc.fatal = false
313
+ pending = new(nonce)
314
+ result = pending.wait(10000, list(Error("IMP connection timeout")))
315
+ pending = false
316
+ result
288
317
  }
289
-
318
+
290
319
  // close
291
320
  //
292
321
  // Close the connection, but can be opened again with connect().
293
322
  //
294
323
  wesc.close = function close() {
295
- if !ws
296
- return
297
- ws.close(1000) // 1000: normal close
324
+ if ws
325
+ ws.close(1000) // 1000: normal close
326
+ ws = false
298
327
  }
299
328
 
300
329
  // destroy
@@ -303,23 +332,27 @@ closure WebSocketClient(options, imp, local wesc, ws) {
303
332
  //
304
333
  wesc.destroy = function destroy() {
305
334
  close()
335
+ wesc.impconn.destroy()
336
+ wesc.impconn = false
306
337
  }
307
338
 
308
339
  // send
309
340
  //
310
- // Send a message.
341
+ // Send a message, returning result tuple.
311
342
  //
312
343
  wesc.send = closure send(data) {
313
- if wesc.fatal
314
- return (wesc.fatal)
344
+ if reconnect()
345
+ return // failed to reconnect
315
346
  wesc.impconn.send(data)
316
347
  }
317
348
 
318
349
  // sendRaw
319
350
  //
320
- // Send a message with reconnect but no routing.
351
+ // Send a message with reconnect but no routing. Returns a result tuple.
321
352
  //
322
353
  wesc.sendRaw = closure sendRaw(data) {
354
+ if reconnect()
355
+ return // failed to reconnect
323
356
  ws.send(data)
324
357
  }
325
358
 
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2020-2025 by Richard C. Zulch
9
+ * Copyright (c) 2020-2026 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -31,6 +31,26 @@ function clirRequester() {
31
31
  };
32
32
 
33
33
 
34
+ /*
35
+ * clirWebsocket
36
+ *
37
+ * Return an appropriate Websocket client based on our environment.
38
+ *
39
+ */
40
+
41
+ clirWebsocketApi = false;
42
+
43
+ function clirWebsocket() {
44
+ if !clirWebsocketApi {
45
+ if js.w
46
+ clirWebsocketApi = require("naanlib:frameworks/browser/ws_client.nlg").WebSocket
47
+ else
48
+ clirWebsocketApi = require("naanlib:frameworks/node/ws_client.nlg").WebSocket
49
+ }
50
+ clirWebsocketApi
51
+ };
52
+
53
+
34
54
  /*
35
55
  * clirInit
36
56
  *
@@ -40,7 +60,7 @@ function clirRequester() {
40
60
  */
41
61
 
42
62
  function clirInit(local manifest) {
43
- manifest = `(clirRequester, clirInit)
63
+ manifest = `(clirRequester, clirWebsocket, clirInit)
44
64
 
45
65
  Naan.module.build(module.id, "client", function(modobj, compobj) {
46
66
  require("../common/").LiveImport()