@naanlang/naan 1.2.1 → 1.3.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 (38) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +1 -1
  3. package/bin/index.js +5 -2
  4. package/dist/env_web.js +12 -2
  5. package/dist/naan.min.js +4 -4
  6. package/frameworks/browser/https_request.nlg +2 -0
  7. package/frameworks/browser/sworker.js +23 -23
  8. package/frameworks/browser/terminals.nlg +123 -21
  9. package/frameworks/browser/workers.nlg +4 -1
  10. package/frameworks/browser/ws_client.nlg +5 -3
  11. package/frameworks/client/apiclient.nlg +8 -6
  12. package/frameworks/client/relaycon.nlg +3 -1
  13. package/frameworks/common/common.nlg +16 -3
  14. package/frameworks/node/apiserver.nlg +205 -5
  15. package/frameworks/node/worker.nlg +112 -10
  16. package/frameworks/node/ws_client.nlg +5 -3
  17. package/frameworks/project/build.nlg +5 -1
  18. package/frameworks/project/projects.nlg +1 -1
  19. package/frameworks/running/executors.nlg +3 -1
  20. package/frameworks/running/taskexec.nlg +5 -1
  21. package/lib/browser/env_web.js +18 -8
  22. package/lib/browser/env_webworker.js +22 -3
  23. package/lib/core/naanlib.js +4 -4
  24. package/lib/env_node.js +15 -4
  25. package/lib/env_nodeworker.js +21 -1
  26. package/package.json +8 -5
  27. package/plugins/serviceAws/aws/aws-sdk-node.min.js +2 -2
  28. package/plugins/serviceAws/aws/aws-sdk.min.js +3 -2
  29. package/plugins/serviceAws/aws_dynamo.nlg +19 -19
  30. package/plugins/serviceYC/generic_api.yaml +36 -0
  31. package/plugins/serviceYC/naanide-relay-index.js +148 -0
  32. package/plugins/serviceYC/serviceYC.nlg +60 -0
  33. package/plugins/serviceYC/yc_apigateway.nlg +205 -0
  34. package/plugins/serviceYC/yc_function.nlg +163 -0
  35. package/plugins/serviceYC/yc_vm.nlg +207 -0
  36. package/test/test_03_jsinterop.nlg +2 -2
  37. package/test/test_04_numerics.nlg +170 -2
  38. package/test/test_07_lingo.nlg +1 -1
@@ -16,6 +16,17 @@
16
16
  *
17
17
  * Make an API server object.
18
18
  *
19
+ * Options:
20
+ * {
21
+ * name: <string> // server name (default "NaanServer")
22
+ * abspath: <string> // absolute pathname for web root
23
+ * relpath: <string> // web root pth relative to js.d
24
+ * fixedPort: <number> // fixed
25
+ * basePort: <number> // starting point for search
26
+ * gateway: <string> // websocket gateway URL
27
+ * statics: [<string>, ...] // search paths for static content
28
+ * }
29
+ *
19
30
  */
20
31
 
21
32
  closure APIServer(options, local server, chroot) {
@@ -30,7 +41,10 @@ closure APIServer(options, local server, chroot) {
30
41
  relpath: ""
31
42
  name: "NaanServer"
32
43
  }, options)
33
- chroot = JSpath.join(js.d, options.relpath)
44
+ if options.abspath
45
+ chroot = options.abspath
46
+ else
47
+ chroot = JSpath.join(js.d, options.relpath)
34
48
  server.root = chroot
35
49
 
36
50
  // updatePort
@@ -83,7 +97,16 @@ closure APIServer(options, local server, chroot) {
83
97
  if server.guid
84
98
  server.oururl = server.oururl.concat("?guid=", server.guid)
85
99
  App.api = server // used remotely by client/relaycon.nlg
86
- server.ws = WebSocketServer(server)
100
+ if options.gateway { // processes at /wsgw endpoint
101
+ server.ws = WebSocketGatewayServer()
102
+ `(error) = server.ws.open(server, options.gateway)
103
+ if error {
104
+ error = Error("APIServer: cannot establish websocket gateway", options.gateway, error)
105
+ ErrorDebuglog(error)
106
+ return (list(error))
107
+ }
108
+ } else // default is native websocket protocols
109
+ server.ws = WebSocketServer(server)
87
110
  WesRelay(server) // registers as subsystem
88
111
  server.psm = psmServerMod.MakeServerPSM(server, options.name)
89
112
  `(error, hostfs) = server.psm.conns.NodeFS.connect("Host", "NideFS", list(""))
@@ -267,7 +290,7 @@ closure APIServer(options, local server, chroot) {
267
290
 
268
291
  server.app.all("/psm", closure(req, res, local guid) {
269
292
  guid = req.get("x-naanlang-api-guid")
270
- if server.guid && guid != server.guid && req.method !== "OPTIONS"
293
+ if server.guid && guid != server.guid
271
294
  res.status(403).send("unauthorized")
272
295
  else if !server.hostfs
273
296
  res.status(503).send("host filesystem not available")
@@ -275,12 +298,187 @@ closure APIServer(options, local server, chroot) {
275
298
  server.hostfs.api(req, res)
276
299
  })
277
300
 
301
+ // "/wsgw"
302
+
303
+ server.app.all("/wsgw", closure(req, res) {
304
+ if !server.ws.requested
305
+ res.status(503).send("websocket gateway not available")
306
+ else
307
+ server.ws.requested(req, res)
308
+ res.set('Content-Type', 'application/json')
309
+ res.status(200).send("")
310
+ })
311
+
278
312
  // finis
279
313
 
280
314
  server
281
315
  };
282
316
 
283
317
 
318
+ /*
319
+ * WebSocketGatewayServer
320
+ *
321
+ * Process websocket operations forwarded by an API Gateway. This connects to the specified gateway
322
+ * component to process the details.
323
+ *
324
+ * Gateway handler methods:
325
+ * handler = Gateway(server) -- constructor, returns gateway object
326
+ * `(error) = open(wsgate) -- initialize for any API Gateway
327
+ * `(error, data) = requested(req, res) -- process incoming requests
328
+ * `(error) = send(connID, message) -- send a message
329
+ * close() -- close the gateway for all connections
330
+ *
331
+ */
332
+
333
+ closure WebSocketGatewayServer(local wsgate) {
334
+ wsgate = new(object, this)
335
+ wsgate.conns = {} // our connections indexed by connID
336
+ wsgate.subsystems = { } // registered subsystems
337
+
338
+ // open
339
+ //
340
+ // Open the server with the specified gateway.
341
+ //
342
+ wsgate.open = closure open(server, gatewayPath) {
343
+ try {
344
+ wsgate.handler = require(gatewayPath).Gateway(server)
345
+ result = wsgate.handler.open()
346
+ if result.0
347
+ return (list(result.0))
348
+ } catch {
349
+ return (list(exception))
350
+ }
351
+ wsgate.guid = server.guid
352
+ list(false, { ok: true })
353
+ }
354
+
355
+ // requested
356
+ //
357
+ // We have an incoming request translated from WS by the API Gateway. This finds out from our
358
+ // gateway handler the client this pertains to and connects/disconnects/messages as appropriate.
359
+ //
360
+ wsgate.requested = closure requested(req, res, local error, data, conn, message) {
361
+ // reqOpen
362
+ // Get our connection or make one.
363
+ function reqOpen(connID) {
364
+ conn = wsgate.conns[connID]
365
+ if !conn {
366
+ conn = WebSocketConn(wsgate, connID)
367
+ wsgate.conns[connID] = conn
368
+ for serverID in wsgate.subsystems
369
+ wsgate.subsystems[serverID].wsOpen(conn)
370
+ }
371
+ conn
372
+ }
373
+
374
+ `(error, data) = wsgate.handler.requested(req, res)
375
+ if data.op == "connect"
376
+ conn = reqOpen(data.connID)
377
+ else if data.op == "message"
378
+ reqOpen(data.connID).msgin(data.message)
379
+ else if data.op == "disconnect" {
380
+ conn = wsgate.conns[data.connID]
381
+ wsgate.conns[data.connID] = undefined
382
+ if conn {
383
+ conn.close(data.code, data.reason)
384
+ for serverID in wsgate.subsystems
385
+ wsgate.subsystems[serverID].wsClose(conn)
386
+ }
387
+ }
388
+ }
389
+
390
+ // close
391
+ //
392
+ // Respond to server closing.
393
+ //
394
+ wsgate.close = function close(conn, local serverID) {
395
+ wsgate.handler.close()
396
+ wsgate.handler = false
397
+ for serverID in wsgate.subsystems
398
+ wsgate.subsystems[serverID].wsClose()
399
+ }
400
+
401
+ // finis
402
+ wsgate
403
+ };
404
+
405
+
406
+ /*
407
+ * WebSocketConn
408
+ *
409
+ * WebSocketConn objects represent a connection with a particular client. These are created when
410
+ * a new client connects. The protocol is JSON with the top level being a dictionary having the
411
+ * following static keys specified by the client:
412
+ * serverID: defined by the server, this string defines where requests should be routed.
413
+ * clientID: defined by the client, this string defines where replies should be sent.
414
+ * These are class (code) designators. Further keys can be defined to identify a specific instance on
415
+ * the client or server.
416
+ *
417
+ */
418
+
419
+ closure WebSocketConn(wsgate, connID, local wsconn) {
420
+ global()
421
+ wsconn = new(object, this)
422
+ wsconn.info = {
423
+ destID: UUID()
424
+ }
425
+
426
+ // msgin
427
+ //
428
+ // This finds the appropriate target for an incoming message, which can be a relay message that
429
+ // we send on to the next node on the network.
430
+ //
431
+ wsconn.msgin = closure msgin(message, local error) {
432
+ if wsgate.guid && message.guid != wsgate.guid
433
+ error = Error("unauthorized")
434
+ else if message.init {
435
+ if message.init.destID {
436
+ wsconn.relay = true
437
+ wsgate.subsystems.Relay.wsChangeDestID(conn, wsconn.info.destID, message.init.destID)
438
+ }
439
+ wsconn.info = merge(wsconn.info, message.init)
440
+ }
441
+ else if message.respOp && wsgate.subsystems.Relay
442
+ wsgate.subsystems.Relay.wsReceive(message, wsconn) // with respOp, must be a relay
443
+ else if message.clientID && wsgate.subsystems[message.serverID]
444
+ wsgate.subsystems[message.serverID].wsReceive(message, wsconn)
445
+ else
446
+ error = Error("can't route message")
447
+ if error
448
+ ErrorDebuglog("WebSocketConn.msgin error:", error)
449
+ }
450
+
451
+ // on close
452
+ //
453
+ // Connection has closed.
454
+ //
455
+ wsconn.close = closure close(code, reason) {
456
+ if code != 1000 && code != 1001 && code != 1005 && code != 1006
457
+ debuglog("WebSocketConn: connection has closed:", code, reason, JSONstringify(wsconn.info))
458
+ wsconn.closed = {
459
+ code: code
460
+ reason: reason
461
+ }
462
+ }
463
+
464
+ // send
465
+ //
466
+ // Send a dictionary, which must have a clientID defined.
467
+ //
468
+ wsconn.send = function send(message) {
469
+ if !message.clientID || !message.serverID
470
+ throw("WebSocketConn.send: client and server IDs required")
471
+ if wsgate.closed
472
+ list(Error("cannot send - websocket closed"))
473
+ wsgate.handler.send(connID, JSONstringify(message))
474
+ list(false, { ok: true })
475
+ }
476
+
477
+ // finis
478
+ wsconn
479
+ };
480
+
481
+
284
482
  /*
285
483
  * WebSocketServer
286
484
  *
@@ -322,8 +520,9 @@ closure WebSocketServer(server, local websocks) {
322
520
  })
323
521
 
324
522
  // closeConn
523
+ //
325
524
  // respond to connection closing
326
-
525
+ //
327
526
  websocks.closeConn = function closeConn(conn, local serverID) {
328
527
  websocks.conns = websocks.conns.filter(function(item) {
329
528
  item !== conn
@@ -641,7 +840,8 @@ function apisLoadLibs() {
641
840
  };
642
841
 
643
842
  function ApisInit(local manifest) {
644
- manifest = `(APIServer, WebSocketServer, WesCon, WesRelay, apisLoadLibs, ApisInit)
843
+ manifest = `(APIServer, WebSocketGatewayServer, WebSocketConn, WebSocketServer, WesCon, WesRelay,
844
+ apisLoadLibs, ApisInit)
645
845
 
646
846
  Naan.module.build(module.id, "apiserver", function(modobj, compobj) {
647
847
  require("./node.nlg")
@@ -18,7 +18,7 @@
18
18
  *
19
19
  */
20
20
 
21
- closure workCurrent(workerID, startup, local current) {
21
+ closure workCurrent(workco, workerID, startup, local current) {
22
22
  global()
23
23
  current = new(object, this)
24
24
  current.workerID = workerID
@@ -31,9 +31,22 @@ closure workCurrent(workerID, startup, local current) {
31
31
  debugout: ONdebugOut.proc
32
32
  messageout: ONmessageOut.proc
33
33
  })
34
+ current.guid = 1
35
+ current.host = js.t // our magic Naan controller reference
34
36
 
35
- current.host = js.t // our magic Naan controller reference
36
-
37
+ // replyHook
38
+ //
39
+ // Intercept the ReplyMessage function to see if we should handle it.
40
+ //
41
+ function replyHook(data) {
42
+ if data._guid
43
+ workco.uworkers[data._guid].msgport.postMessage({
44
+ id: "targetreceive"
45
+ data: data
46
+ })
47
+ }
48
+ current.host.OnReplyHook(replyHook.proc)
49
+
37
50
  // ONtextOut
38
51
  // Process console text output by Naan.
39
52
 
@@ -79,6 +92,8 @@ closure workCurrent(workerID, startup, local current) {
79
92
  // Process message output.
80
93
 
81
94
  function ONmessageOut(data) {
95
+ if data._guid
96
+ return // already replied
82
97
  sendAll({
83
98
  id: "targetout",
84
99
  data: data
@@ -98,6 +113,7 @@ closure workCurrent(workerID, startup, local current) {
98
113
  // Notify all links that we have terminated.
99
114
 
100
115
  function exitNotify(exitCode, local link) {
116
+ workco.uworkers[current.guid] = undefined
101
117
  for link in current.links
102
118
  link.exitWorker(exitCode)
103
119
  }
@@ -157,7 +173,7 @@ closure workCurrent(workerID, startup, local current) {
157
173
  if !(linkref.link eq thislink)
158
174
  linkref.responder(msg)
159
175
  }
160
-
176
+
161
177
  //
162
178
  // vitalUpdate
163
179
  //
@@ -172,6 +188,81 @@ closure workCurrent(workerID, startup, local current) {
172
188
  };
173
189
 
174
190
 
191
+ /*
192
+ * workMainProxy
193
+ *
194
+ * Make a nodejs proxy representing the main thread. This runs inside the worker and allows it
195
+ * to do remote execution with Main.
196
+ *
197
+ */
198
+
199
+ closure workMainProxy(track, name, options, local target) {
200
+ global(js)
201
+ target = require("../running/executors.nlg").ExecutorBase(track, "mainProxy", name)
202
+ target.name = name
203
+
204
+ //
205
+ // target.PostMessage
206
+ //
207
+ // Post a message to the main thread.
208
+ //
209
+ target.PostMessage = function PostMessage(data) {
210
+ js.t.DispatchMessage(data)
211
+ list(false, { posted: true })
212
+ }
213
+
214
+ //
215
+ // onreceive
216
+ //
217
+ // Process a received message from the main thread, typically in response.
218
+ //
219
+ function onreceive(data) {
220
+ target.execReceived(data)
221
+ }
222
+
223
+ // destroy
224
+ //
225
+ // Destroy the execution instance.
226
+ //
227
+ target.destroy = function destroy() {
228
+ target.execClosed(Error("instance destroyed"))
229
+ track.delete(target)
230
+ }
231
+
232
+ js.t.OnReceive(onreceive.proc)
233
+ track.add(target)
234
+
235
+ // finis
236
+
237
+ list(false, target)
238
+ };
239
+
240
+
241
+ /*
242
+ * WorkerToMain
243
+ *
244
+ * Connect our worker to the main thread, returning a result tuple with an executor representing
245
+ * Main. Use this to create a context object for Main, allowing remote evaluation.
246
+ *
247
+ */
248
+
249
+ closure WorkerToMain(local nideRunning, track) {
250
+ // mainProxy -- link tracker to main thread proxy
251
+ function mainProxy(track, name, options) {
252
+ workMainProxy(track, name, options)
253
+ }
254
+
255
+ if !js.g {
256
+ error = Error("WorkerToMain only available in node workers")
257
+ return (list(error))
258
+ }
259
+ nideRunning = require("naanlib:frameworks/running/executors.nlg")
260
+ track = nideRunning.ExecutorTracker()
261
+ track.register(mainProxy, "proxy")
262
+ track.spawn("proxy") // returns result tuple
263
+ };
264
+
265
+
175
266
  /*
176
267
  * workThread
177
268
  *
@@ -179,12 +270,13 @@ closure workCurrent(workerID, startup, local current) {
179
270
  *
180
271
  */
181
272
 
182
- closure workThread(workerID, startup, local worker, options, subChannel, deathWatcher) {
273
+ closure workThread(workco, workerID, startup, local worker, options, subChannel, deathWatcher) {
183
274
  global(threads, JSpath)
184
275
  worker = new(object, this)
185
276
  worker.name = startup.name
186
277
  worker.workerID = workerID
187
278
  worker.links = []
279
+ worker.guid = UUID()
188
280
 
189
281
  // create our worker thread
190
282
 
@@ -207,6 +299,7 @@ closure workThread(workerID, startup, local worker, options, subChannel, deathWa
207
299
  // Notify all links that we have terminated.
208
300
 
209
301
  function exitNotify(exitCode, local link) {
302
+ workco.uworkers[worker.guid] = undefined
210
303
  for link in worker.links
211
304
  link.exitWorker(exitCode)
212
305
  worker.links = []
@@ -262,6 +355,12 @@ closure workThread(workerID, startup, local worker, options, subChannel, deathWa
262
355
  worker.links[0].responder({ // only send this to first link
263
356
  id: "started" // don't want multiple initializers
264
357
  }) }
358
+ else if msg.id == "targetsend" {
359
+ msg.data._guid = worker.guid
360
+ js.t.DispatchMessage({
361
+ id: "targetin"
362
+ data: msg.data
363
+ }) }
265
364
  else {
266
365
  if msg.id == "status"
267
366
  worker.lastStatus = milliseconds()
@@ -403,6 +502,7 @@ closure workController(api, local workco) {
403
502
  workco = new(object, this)
404
503
  workco.serverID = "Workers" // our serverID, used for communications
405
504
  workco.workers = { } // dictionary of workers by workerID
505
+ workco.uworkers = { } // dictionary of workers by GUID
406
506
  workco.links = new(weakmap)
407
507
  api.Register(workco.serverID, workco)
408
508
 
@@ -487,9 +587,10 @@ closure workController(api, local workco) {
487
587
  else if message.workerOp == "spawn" {
488
588
  if !worker { // create worker if needed
489
589
  if message.workerID == "NideServer"
490
- worker = workco.current = workCurrent(message.workerID, message.payload)
590
+ worker = workco.current = workCurrent(workco, message.workerID, message.payload)
491
591
  else
492
- worker = workThread(message.workerID, message.payload)
592
+ worker = workThread(workco, message.workerID, message.payload)
593
+ workco.uworkers[worker.guid] = worker
493
594
  workco.workers[message.workerID] = worker }
494
595
  if !link
495
596
  makeLink(message, conn) // make sure we have a link for responses
@@ -521,7 +622,7 @@ closure workController(api, local workco) {
521
622
  return } }
522
623
  error = Error("invalid parameters", message.workerOp, message.workerID)
523
624
  }
524
- call(makeSender(conn, message), "error", [error])
625
+ ErrorDebuglog("workController.wsReceive error:", error)
525
626
  }
526
627
 
527
628
  //
@@ -562,12 +663,13 @@ closure workController(api, local workco) {
562
663
  */
563
664
 
564
665
  function workInit(local manifest) {
565
-
566
- manifest = `(workCurrent, workThread, workLink, workController, workInit)
666
+ manifest = `(workCurrent, workMainProxy, WorkerToMain, workThread, workLink, workController,
667
+ workInit)
567
668
 
568
669
  Naan.module.build(module.id, "worker", function(modobj, compobj) {
569
670
  require("./node.nlg")
570
671
  compobj.manifest = manifest
571
672
  modobj.exports.WorkerController = workController
673
+ modobj.exports.WorkerToMain = WorkerToMain
572
674
  })
573
675
  }();
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * column positioning: // // !
7
7
  *
8
- * Copyright (c) 2023 by Richard C. Zulch
8
+ * Copyright (c) 2023-2024 by Richard C. Zulch
9
9
  *
10
10
  */
11
11
 
@@ -25,6 +25,8 @@
25
25
  * onmessage: <proc>(e, message) // message received
26
26
  * onerror: <proc>(e) // error occurred
27
27
  * 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
28
30
  * }
29
31
  *
30
32
  */
@@ -45,9 +47,9 @@ closure WebSocket(host, options, local wsock) {
45
47
  if error
46
48
  return (list(error))
47
49
  if options.wss
48
- url = "wss://${host}"
50
+ url = "wss://${host}${options.querys||''}"
49
51
  else
50
- url = "ws://${host}"
52
+ url = "ws://${host}${options.query||''}"
51
53
  wsock.ws = xnew(ws.default, url)
52
54
  pending = new(nonce)
53
55
 
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2019-2023 by Richard C. Zulch
9
+ * Copyright (c) 2019-2024 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -728,6 +728,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, track, local build, gro
728
728
  tasks: group.tasks
729
729
  readpath: infile,
730
730
  writepath: outfile
731
+ follow: group.follow || undefined
731
732
  }
732
733
  if group.input.startsWith("@")
733
734
  entry.insource = JSpath.join(group.input.substring(1), group.sources[sdex])
@@ -982,6 +983,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, track, local build, gro
982
983
  overwrite: true // overwrite files
983
984
  force: true // ignore modify date
984
985
  idmode: true // preserve ids and mode
986
+ follow: entry.follow // optional follow links
985
987
  })
986
988
  if error // deepcopy returns a list of errors
987
989
  error = error.0 // but we're only doing one item
@@ -1296,6 +1298,8 @@ closure Builder(rules, operation, fs, srcpath, destpath, track, local build, gro
1296
1298
  //
1297
1299
  build.increment = closure increment(newnum, local error, filecon, buildnum) {
1298
1300
  build.buildnum = undefined
1301
+ if !build.buildnopath
1302
+ return (list(false, { version: false }))
1299
1303
  `(error, filecon) = fs.readFile(build.buildnopath)
1300
1304
  if !error
1301
1305
  buildnum = toint(filecon.trim())
@@ -296,7 +296,7 @@ closure projConnector(projman, projtype, local connector, watch) {
296
296
  *
297
297
  * Nide.proj/
298
298
  * nide_cliser.cfg - JSON definition of the project (see below)
299
- * NaanIDE_version.txt - [default] substitution file defining 1.2.1+1 etc.
299
+ * NaanIDE_version.txt - [default] substitution file defining 1.3.1+1 etc.
300
300
  * NaanIDE_version_builds.txt - [default] contains current build number as decimal string
301
301
  * build/ - [optional] default build output location
302
302
  * NaanIDE.lic - [optional] defines Zulch Laboratories, Inc. and other license info
@@ -56,9 +56,11 @@ closure ExecutorBase(track, type, name, local exec) {
56
56
 
57
57
  exec.execReceived = closure execReceived(data, local pending) {
58
58
  pending = exec.pending[data.xid]
59
- if !pending {
59
+ if !pending { // not waiting on message
60
60
  if data.xmsg == "xready"
61
61
  exec.pending.ready = true
62
+ if data.xmsg == "xattention"
63
+ { }
62
64
  return
63
65
  }
64
66
  exec.pending[data.xid] = undefined
@@ -28,12 +28,16 @@ closure TaskDispatcher(replier, local contexts, gohome, util) {
28
28
  reply = {
29
29
  xmsg: "error"
30
30
  xid: data.xid
31
+ _guid: data._guid || undefined
31
32
  error: "unknown operation"
32
33
  }
33
34
  try {
34
35
  restorens = makeActivator()
35
36
  if data.xmsg == "xstart"
36
- reply = { xmsg: "xready" } // ready to accept messages
37
+ reply = { // ready to accept messages
38
+ xmsg: "xready"
39
+ _guid: data._guid || undefined
40
+ }
37
41
  else if data.xmsg == "register" {
38
42
  //
39
43
  // register a new context to default namespace
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2022 by Richard C. Zulch
9
+ * Copyright (c) 2017-2024 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -223,7 +223,17 @@ exports.NaanControllerWeb = function() {
223
223
  }, 1);
224
224
  };
225
225
 
226
+ var replyHook;
227
+ this.OnReplyHook = function OnReplyHook(proc) {
228
+ replyHook = proc;
229
+ return (proc);
230
+ };
231
+
226
232
  this.ReplyMessage = function ReplyMessage(data) { // "remote" -> "local"
233
+ if (replyHook)
234
+ setTimeout(function() {
235
+ replyHook(data);
236
+ }, 1);
227
237
  termlist.forEach(function(term) {
228
238
  if (term.OnMessageOut)
229
239
  term.OnMessageOut(data);
@@ -526,7 +536,7 @@ exports.NaanControllerWeb = function() {
526
536
  statedoc.curversion = kStateCurrentVersion;
527
537
  statedoc.firstver = kStateFirstVersion;
528
538
  statedoc.licensee = "MIT-License";
529
- statedoc.verstring = "1.2.1+1";
539
+ statedoc.verstring = "1.3.1+1";
530
540
  statedoc.date = new Date().toISOString();
531
541
  statedoc.prefs = prefs;
532
542
  statedoc.naan = naanlib.saveState(false); // true to optimize, which is a bit slower
@@ -544,7 +554,7 @@ exports.NaanControllerWeb = function() {
544
554
  || statedoc.firstver > kStateCurrentVersion
545
555
  || statedoc.curversion < kStateFirstVersion
546
556
  || statedoc.licensee != "MIT-License"
547
- || statedoc.verstring != "1.2.1+1")
557
+ || statedoc.verstring != "1.3.1+1")
548
558
  {
549
559
  localStorage.removeItem("NaanState_Hosted");
550
560
  return (false);
@@ -603,7 +613,7 @@ exports.NaanControllerWeb = function() {
603
613
  op: "VsiteOpen",
604
614
  name: vsiteName,
605
615
  naancont: contSelf,
606
- title: vsiteName + " 1.2.1+1"
616
+ title: vsiteName + " 1.3.1+1"
607
617
  });
608
618
  }
609
619
  } catch (e) {
@@ -617,7 +627,7 @@ exports.NaanControllerWeb = function() {
617
627
  op: "VsiteClose",
618
628
  name: vsiteName,
619
629
  naancont: contSelf,
620
- title: vsiteName + " 1.2.1+1"
630
+ title: vsiteName + " 1.3.1+1"
621
631
  });
622
632
  termTextOut("\x1b[90m\x1b[3m".concat("\nwindow closed", "\x1b[0m\n"));
623
633
  }
@@ -630,10 +640,10 @@ exports.NaanControllerWeb = function() {
630
640
  if (querystrings["restart"] || !loadLocal())
631
641
  { // don't load state or can't load state
632
642
  naanlib.banner();
633
- var hostpath = naanlib.js.r("path").dirname(window.location.href);
643
+ var hostpath = window.location.origin.concat(naanlib.js.r("path").dirname(window.location.pathname));
634
644
  naanlib.start({
635
- cmd: 'App.version = "1.2.1+1";;\r\n'
636
- + 'App.cache = "f22e60a842b7a65ac309da378d469eed";;\r\n'
645
+ cmd: 'App.version = "1.3.1+1";;\r\n'
646
+ + 'App.cache = "1f0eccb08fd1a57ede0bd91dc0e934cc";;\r\n'
637
647
  + 'Naan.module.requireQuery({ naanver: App.cache });;\r\n'
638
648
  + 'Naan.module.webparse("naan_init.nlg", "' + hostpath + '", { naanver: App.cache });;\r\n'
639
649
  });