@naanlang/naan 1.3.1 → 1.4.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 (44) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +3 -3
  3. package/bin/index.js +2 -2
  4. package/dist/naan.min.js +4 -4
  5. package/frameworks/browser/sworker.js +23 -23
  6. package/frameworks/browser/terminals.nlg +26 -11
  7. package/frameworks/browser/workers.nlg +3 -0
  8. package/frameworks/browser/ws_client.nlg +1 -1
  9. package/frameworks/client/psm_client.nlg +1 -1
  10. package/frameworks/client/relaycon.nlg +2 -1
  11. package/frameworks/common/common.nlg +6 -10
  12. package/frameworks/common/preferences.nlg +1 -1
  13. package/frameworks/node/apiserver.nlg +3 -3
  14. package/frameworks/node/https_request.nlg +97 -12
  15. package/frameworks/node/node.nlg +1 -0
  16. package/frameworks/node/worker.nlg +103 -45
  17. package/frameworks/project/build.nlg +12 -7
  18. package/frameworks/project/jszip.js +0 -0
  19. package/frameworks/project/projects.nlg +7 -3
  20. package/frameworks/running/executors.nlg +64 -28
  21. package/frameworks/running/running.nlg +85 -3
  22. package/frameworks/running/taskexec.nlg +24 -4
  23. package/frameworks/storage/dbt_pouch.nlg +1 -1
  24. package/frameworks/storage/psm.nlg +11 -1
  25. package/lib/browser/env_web.js +6 -6
  26. package/lib/browser/env_webworker.js +2 -0
  27. package/lib/core/naanlib.js +4 -4
  28. package/lib/env_nodeworker.js +2 -0
  29. package/package.json +1 -1
  30. package/plugins/gitClient/gitutils.nlg +2 -2
  31. package/plugins/nodeLib/node_https.nlg +2 -2
  32. package/plugins/serviceAws/dbt_aws.nlg +3 -0
  33. package/plugins/serviceAws/lambda_naan_control.nlg +3 -3
  34. package/plugins/serviceGC/gc_api.nlg +263 -0
  35. package/plugins/serviceGC/russian_trusted_root_ca_pem.crt +33 -0
  36. package/plugins/serviceGC/serviceGC.nlg +31 -0
  37. package/plugins/serviceGitHub/gitops.nlg +4 -3
  38. package/plugins/serviceGitHub/psm_github.nlg +1 -1
  39. package/plugins/serviceGitLab/gitops.nlg +2 -1
  40. package/plugins/serviceGitLab/psm_gitlab.nlg +1 -1
  41. package/plugins/serviceYC/yc_speechrec.nlg +138 -0
  42. package/plugins/serviceYC/yc_vm.nlg +1 -1
  43. package/test/test_02_context.nlg +1 -1
  44. package/test/test_07_lingo.nlg +3 -3
@@ -36,7 +36,7 @@
36
36
  *
37
37
  */
38
38
 
39
- var CurrentCacheName = "Naanlang-1.3.1+1";
39
+ var CurrentCacheName = "Naanlang-1.4.0+1";
40
40
 
41
41
 
42
42
  //
@@ -92,18 +92,18 @@ var terminated; // flag that this service worker
92
92
  var pubVersion = event.data.hereIsMyVersion;
93
93
  var sourceId = event.source.id; // client id of sender of message
94
94
  msgPorts[sourceId] = msgport;
95
- console.log("[1.3.1+1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
- if (pubVersion != "1.3.1+1") {
95
+ console.log("[1.4.0+1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
+ if (pubVersion != "1.4.0+1") {
97
97
  if (upgradeMsgPorts) { // if not activated
98
- console.log("[1.3.1+1] update pending for", sourceId);
98
+ console.log("[1.4.0+1] update pending for", sourceId);
99
99
  upgradeMsgPorts[sourceId] = msgport;
100
100
  }
101
101
  else {
102
102
  msgport.postMessage({ // notify new version available
103
103
  id: "upgrade",
104
- version: "1.3.1+1"
104
+ version: "1.4.0+1"
105
105
  });
106
- console.log("[1.3.1+1] update sent for:", sourceId);
106
+ console.log("[1.4.0+1] update sent for:", sourceId);
107
107
  }
108
108
  }
109
109
  Reaper(); // clean up obsolete info
@@ -134,10 +134,10 @@ var terminated; // flag that this service worker
134
134
  msgport.onmessage = function(msg) {
135
135
  msg = msg.data;
136
136
  if (msg.id == "skipWaiting") { // try to activate this SW now
137
- console.log("[1.3.1+1] attempting skipWaiting");
137
+ console.log("[1.4.0+1] attempting skipWaiting");
138
138
  self.skipWaiting();
139
139
  } else if (msg.id == "terminate") { // termiante this SW now
140
- console.log("[1.3.1+1] terminated");
140
+ console.log("[1.4.0+1] terminated");
141
141
  terminate();
142
142
  } else if (msg.id == "abortURL") { // abort an I/O transaction
143
143
  var href = new URL(msg.url).href;
@@ -150,7 +150,7 @@ var terminated; // flag that this service worker
150
150
  } else if (msg.id == "response") // response from fetch request
151
151
  processResponse(msg);
152
152
  else if (msg.id == "text") // just log some text
153
- console.log("[1.3.1+1] msg received:", msg.text);
153
+ console.log("[1.4.0+1] msg received:", msg.text);
154
154
  };
155
155
 
156
156
  // send text to IDE log
@@ -161,7 +161,7 @@ var terminated; // flag that this service worker
161
161
  // don't clutter the log
162
162
  msgport.postMessage({
163
163
  id: "text",
164
- text: "port received by 1.3.1+1",
164
+ text: "port received by 1.4.0+1",
165
165
  });
166
166
  */
167
167
  if (--pending === 0)
@@ -178,7 +178,7 @@ var terminated; // flag that this service worker
178
178
  includeUncontrolled: true
179
179
  }).then(function(clientList) {
180
180
  clientList.every(function(client) {
181
- console.log("[1.3.1+1] requesting new msgport for", client.id);
181
+ console.log("[1.4.0+1] requesting new msgport for", client.id);
182
182
  ++pending;
183
183
  client.postMessage({ // tell client(s) we need this fetch source
184
184
  msg: "Naan_need_fetch_port",
@@ -224,12 +224,12 @@ function Reaper() {
224
224
  clients[clientList[clidex].id] = clientList[clidex];
225
225
  for (var sourceId in msgPorts)
226
226
  if (!clients[sourceId]) {
227
- console.log("[1.3.1+1] source gone:", sourceId);
227
+ console.log("[1.4.0+1] source gone:", sourceId);
228
228
  delete msgPorts[sourceId]; // no longer a source
229
229
  }
230
230
  for (var clientId in fetchPorts)
231
231
  if (!clients[clientId]) {
232
- console.log("[1.3.1+1] client gone:", clientId);
232
+ console.log("[1.4.0+1] client gone:", clientId);
233
233
  delete fetchPorts[clientId]; // no longer a client
234
234
  }
235
235
  for (var fqdex = 0; fqdex < fetchQueue.length; ++fqdex) {
@@ -263,16 +263,16 @@ function ClearCaches() {
263
263
  return (Promise.all(
264
264
  cacheNames.map(function(cacheName) {
265
265
  if (cacheName != CurrentCacheName) {
266
- console.log('[1.3.1+1] deleting old cache:', cacheName);
266
+ console.log('[1.4.0+1] deleting old cache:', cacheName);
267
267
  return (caches.delete(cacheName));
268
268
  }
269
269
  })
270
270
  ));
271
271
  }).then(function() { // claim all clients
272
- console.log('[1.3.1+1] claiming clients');
272
+ console.log('[1.4.0+1] claiming clients');
273
273
  return (self.clients.claim());
274
274
  }).then(function() {
275
- console.log('[1.3.1+1] clients claimed');
275
+ console.log('[1.4.0+1] clients claimed');
276
276
  return (Promise.resolve(true));
277
277
  });
278
278
  return (promise);
@@ -315,7 +315,7 @@ function GetClientResponse(event, urlpath) {
315
315
  msgport.postMessage({
316
316
  id: "fetch",
317
317
  seq: seqno,
318
- version: "1.3.1+1",
318
+ version: "1.4.0+1",
319
319
  request: {
320
320
  method: event.request.method,
321
321
  url: event.request.url
@@ -363,7 +363,7 @@ function GetClientResponse(event, urlpath) {
363
363
  */
364
364
 
365
365
  self.addEventListener('install', function(event) {
366
- console.log("[1.3.1+1] install");
366
+ console.log("[1.4.0+1] install");
367
367
  self.skipWaiting();
368
368
  });
369
369
 
@@ -376,20 +376,20 @@ self.addEventListener('install', function(event) {
376
376
  */
377
377
 
378
378
  self.addEventListener('activate', function(event) {
379
- console.log("[1.3.1+1] activate");
379
+ console.log("[1.4.0+1] activate");
380
380
  self.clients.matchAll({ // for debugging, list controlled clients
381
381
  includeUncontrolled: true
382
382
  }).then(function(clientList) {
383
383
  var urls = clientList.map(function(client) {
384
384
  return (client.url);
385
385
  });
386
- console.log('[1.3.1+1] matching clients:', urls.join(', '));
386
+ console.log('[1.4.0+1] matching clients:', urls.join(', '));
387
387
  });
388
388
  var promise = ClearCaches().then(function() {
389
389
  for (var sourceId in upgradeMsgPorts) {
390
390
  upgradeMsgPorts[sourceId].postMessage({ // notify new version available
391
391
  id: "upgrade",
392
- version: "1.3.1+1"
392
+ version: "1.4.0+1"
393
393
  });
394
394
  }
395
395
  upgradeMsgPorts = false;
@@ -421,7 +421,7 @@ self.addEventListener('fetch', function(event) {
421
421
  var promise;
422
422
  var url = new URL(request.url);
423
423
  var nocache = request.method != "GET"
424
- || url.search.length !== 0 && url.searchParams.get("naanver") !== "ecae8a62f4a0ae0408f1cbd16082d4ea"
424
+ || url.search.length !== 0 && url.searchParams.get("naanver") !== "fae76573fc7eeb3020b11e0d88fe69d4"
425
425
  || request.headers.get('range');
426
426
  if (url.pathname.startsWith("/run/")) {
427
427
  nocache = true;
@@ -455,7 +455,7 @@ self.addEventListener('fetch', function(event) {
455
455
  statusText: "Request Cancelled"
456
456
  }));
457
457
  }
458
- console.log("[1.3.1+1] fetch failed", request.url, e);
458
+ console.log("[1.4.0+1] fetch failed", request.url, e);
459
459
  return (new Response(undefined, {
460
460
  status: 404,
461
461
  statusText: "Fetch Failed"
@@ -15,7 +15,7 @@
15
15
  *
16
16
  */
17
17
 
18
- workers;
18
+ gWorkers;
19
19
 
20
20
 
21
21
  /*
@@ -154,7 +154,7 @@ closure TermHost(track, api, name, options, local target, nlregex, xtarg)
154
154
  // An error occurred on the WebSocket connection.
155
155
  target.ws.onerror = function onerror(e) {
156
156
  pending.signal(list(Error("websocket failed", e)))
157
- debuglog("ws error:", e)
157
+ debuglog("ws error")
158
158
  }
159
159
 
160
160
  // onclose
@@ -431,9 +431,9 @@ closure TermHost(track, api, name, options, local target, nlregex, xtarg)
431
431
 
432
432
  closure TermLocal(track, name, options, local target, nlregex, listener)
433
433
  {
434
- global(js, window, workers)
435
- if !dictionary(workers)
436
- workers = {}
434
+ global(js, window, gWorkers)
435
+ if !dictionary(gWorkers)
436
+ gWorkers = {}
437
437
  options = merge({
438
438
  type: "Local"
439
439
  }, options)
@@ -456,14 +456,15 @@ closure TermLocal(track, name, options, local target, nlregex, listener)
456
456
  // ### tell target we are closing (if needed)
457
457
  })
458
458
  listener = window
459
+ target.guid = 1
459
460
  } else {
460
461
  if not window.Worker
461
462
  return (list(Error("Local environment does not support workers")))
462
463
  target.worker = xnew(window.Worker, "env_webworker.js")
463
464
  listener = target.worker
464
465
  target.guid = UUID()
465
- workers[target.guid] = target
466
466
  }
467
+ gWorkers[target.guid] = target
467
468
 
468
469
  // onmessage
469
470
  // A message was received from the worker.
@@ -551,6 +552,8 @@ closure TermLocal(track, name, options, local target, nlregex, listener)
551
552
  id: "start",
552
553
  state: false // resume state, if we have any
553
554
  altcmd: options.startup.initcmds
555
+ workerID: target.workerID
556
+ workerGUID: target.guid
554
557
  })
555
558
  target.started = true
556
559
  } else if msg.id == "targetsend" {
@@ -667,10 +670,21 @@ closure TermLocal(track, name, options, local target, nlregex, listener)
667
670
  };
668
671
 
669
672
 
673
+ /*
674
+ * WorkerConnect
675
+ *
676
+ * Return the existing web worker for a workerGUID, or false if it does not exist.
677
+ *
678
+ */
679
+
680
+ function WorkerConnect(workerGUID) {
681
+ gWorkers[workerGUID]
682
+ };
683
+
684
+
670
685
  /*
671
686
  * termMainProxy
672
687
  *
673
- * column positioning: // // !
674
688
  * Make a browser proxy representing the main thread. This runs inside the worker and allows it
675
689
  * to do remote execution with Main.
676
690
  *
@@ -769,7 +783,7 @@ closure findIDEtarget(track, name, workerID, naancont, local target) {
769
783
  */
770
784
 
771
785
  closure termIDE(track, name, workerID, naancont, title, local target, connected) {
772
- global(workers)
786
+ global(js, gWorkers)
773
787
  target = findIDEtarget(track, name, workerID, naancont)
774
788
  if target { // new naancont on existing target
775
789
  target.updateController(naancont)
@@ -790,7 +804,7 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
790
804
  //
791
805
  function replyHook(data) {
792
806
  if data._guid
793
- workers[data._guid].worker.postMessage({
807
+ gWorkers[data._guid].worker.postMessage({
794
808
  id: "targetreceive"
795
809
  data: data
796
810
  })
@@ -969,14 +983,15 @@ closure termInstallVirtualWatcher(track, naancont) {
969
983
  */
970
984
 
971
985
  function termInit(local manifest) {
972
- manifest = `(TermHost, TermLocal, termMainProxy, WorkerToMain, findIDEtarget, termIDE,
973
- termInstallVirtualWatcher, termInit)
986
+ manifest = `(TermHost, TermLocal, WorkerConnect, termMainProxy, WorkerToMain, findIDEtarget,
987
+ termIDE, termInstallVirtualWatcher, termInit)
974
988
 
975
989
  Naan.module.build(module.id, "terminals", function(modobj, compobj) {
976
990
  require("./browser.nlg")
977
991
  compobj.manifest = manifest
978
992
  modobj.exports.TermLocal = TermLocal
979
993
  modobj.exports.TermHost = TermHost
994
+ modobj.exports.WorkerConnect = WorkerConnect
980
995
  modobj.exports.WorkerToMain = WorkerToMain
981
996
  })
982
997
  } ();
@@ -371,10 +371,13 @@ closure TrackedScope(track, scope, fs, rootpath, local trope) {
371
371
  "jpg": "image/jpeg",
372
372
  "js": "text/javascript",
373
373
  "json": "application/json",
374
+ "mov": "video/mp4",
374
375
  "mp3": "audio/mpeg",
375
376
  "mp4": "video/mp4",
376
377
  "nlg": "text/plain",
377
378
  "npk": "application/json",
379
+ "ogg": "video/ogg",
380
+ "png": "image/png",
378
381
  "svg": "image/svg+xml",
379
382
  "text": "text/plain",
380
383
  "txt": "text/plain",
@@ -51,7 +51,7 @@ closure WebSocket(options, local wsock) {
51
51
 
52
52
  // onopen
53
53
  // The WebSocket connection is now open.
54
- wsock.ws.onopen = closure onopen(e, local spawner) {
54
+ wsock.ws.onopen = closure onopen(e) {
55
55
  pending.signal(list(false, { ok: true }))
56
56
  options.onopen(e)
57
57
  }
@@ -430,7 +430,7 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
430
430
  */
431
431
 
432
432
  closure psmcFsConnector(psm, api, local connClassID, connector, watch) {
433
- global()
433
+ global(App)
434
434
  connClassID = "NodeFS"
435
435
  connector = new(object, this)
436
436
  connector.psm = psm
@@ -43,7 +43,7 @@ wsMod;; // websocket module
43
43
 
44
44
  closure RelayCon(host, secret, options, local relay, nlregex)
45
45
  {
46
- global(execsMod, wsMod)
46
+ global(App, execsMod, wsMod)
47
47
  relay = new(object, this)
48
48
  options = merge({
49
49
  name: "NaanIDE Server"
@@ -301,6 +301,7 @@ function hocoInit(local manifest) {
301
301
  manifest = `(RelayCon, hocoInit)
302
302
 
303
303
  Naan.module.build(module.id, "relaycon", function(modobj, compobj) {
304
+ require("./client.nlg")
304
305
  execsMod = require("../running/executors.nlg")
305
306
  if js.w
306
307
  wsMod = require("../browser/ws_client.nlg")
@@ -83,6 +83,7 @@ function ContentTypeFromFileExt(filepath, local mimeType, encoding) {
83
83
  "nlg": "text/plain",
84
84
  "npk": "application/json",
85
85
  "ogg": "video/ogg",
86
+ "png": "image/png",
86
87
  "svg": "image/svg+xml",
87
88
  "text": "text/plain",
88
89
  "txt": "text/plain",
@@ -192,20 +193,15 @@ function JsonStringify(data) {
192
193
 
193
194
  jsScripts = { }; // script values become false on reload
194
195
 
195
- closure JsLoadScript(libpath, globalID, local savens, fpath, hostpath, pending, scriptTag) {
196
- global(jsScripts, js, JSpath)
196
+ closure JsLoadScript(libpath, globalID, local savens, hostpath, pending, scriptTag) {
197
+ global(jsScripts, JSpath, js)
197
198
  if jsScripts[libpath]
198
199
  return // already loaded
199
200
  savens = nsactive()
200
- if libpath.startsWith("/")
201
- fpath = libpath
202
- else
203
- fpath = "/".concat(libpath)
204
- hostpath = js.w.location.origin.concat(JSpath.dirname(js.w.location.pathname))
201
+ hostpath = js.w.location.origin.concat(JSpath.join(JSpath.dirname(js.w.location.pathname), libpath))
205
202
  if js.s.importScripts && tostring(js.s.importScripts) == "[Function importScripts]" {
206
- fpath = hostpath.concat(fpath)
207
203
  try {
208
- js.s.importScripts(fpath) // load in worker context
204
+ js.s.importScripts(hostpath) // load in worker context
209
205
  jsScripts[libpath] = js.s[globalID]
210
206
  } catch {
211
207
  debuglog("JsLoadScript:", libpath, "failed", exception)
@@ -214,7 +210,7 @@ closure JsLoadScript(libpath, globalID, local savens, fpath, hostpath, pending,
214
210
  } else if js.w { // load in browser context
215
211
  pending = new(nonce)
216
212
  scriptTag = js.w.document.createElement("script")
217
- scriptTag.src = hostpath.concat(fpath, requireQuery())
213
+ scriptTag.src = hostpath.concat(requireQuery())
218
214
  scriptTag.onload = function (event) {
219
215
  jsScripts[libpath] = js.w[globalID]
220
216
  pending.signal(js.w[globalID])
@@ -88,7 +88,7 @@ closure Preferences(nicc, dbname, local prefs, error, data) {
88
88
  folder = "plugins/"
89
89
  path = folder.concat("plugins.cfg")
90
90
  pending = new(nonce)
91
- nicc.api.readFile(path, function(error, req, event, local config) {
91
+ nicc.api.readFile(path, function(error, req, event) {
92
92
  if error
93
93
  ErrorDebuglog("Can't read ".concat(path), error)
94
94
  else
@@ -30,7 +30,7 @@
30
30
  */
31
31
 
32
32
  closure APIServer(options, local server, chroot) {
33
- global(JSpath, js, bodyParser, express, psmServerMod, workerMod)
33
+ global(App, JSpath, js, bodyParser, express, psmServerMod, workerMod)
34
34
  apisLoadLibs()
35
35
  server = new(object, this)
36
36
  server.app = express()
@@ -116,7 +116,7 @@ closure APIServer(options, local server, chroot) {
116
116
  return (list(error))
117
117
  }
118
118
  server.hostfs = hostfs
119
- workerMod.WorkerController(server)
119
+ server.workco = workerMod.WorkerController(server)
120
120
  list(false, port)
121
121
  }
122
122
 
@@ -550,7 +550,7 @@ closure WebSocketServer(server, local websocks) {
550
550
  */
551
551
 
552
552
  closure WesCon(server, websocks, socket, request, local wescon) {
553
- global()
553
+ global(js)
554
554
  wescon = new(object, this)
555
555
  wescon.socket = socket
556
556
  wescon.info = {
@@ -31,7 +31,8 @@
31
31
  * key: <data>
32
32
  * passphrase: <data> // optional
33
33
  * }
34
- * allowSelfSigned: <boolean> // allow a self-signed certificate at the servera
34
+ * allowSelfSigned: <boolean> // allow a self-signed certificate at the server
35
+ * streamHook: <proc> // called for incoming chunks of text/event-stream
35
36
  * debug: <boolean> // log errors and status results
36
37
  * }
37
38
  *
@@ -100,11 +101,26 @@ closure httpsApiReqOnce(url, options,
100
101
  pending = new(nonce)
101
102
  chunks = []
102
103
  request = nodeHttps.request(url, reqOptions, closure (response) {
104
+ // test if response is a SSE stream
105
+ function stream() { response.headers["content-type"].startsWith("text/event-stream") }
106
+
103
107
  response.on("data", function (chunk) {
104
- chunks.push(chunk)
108
+ if options.streamHook && stream()
109
+ call(options.streamHook, chunk) // got some data
110
+ else
111
+ chunks.push(chunk)
105
112
  })
106
- response.on("end", function (local content, error, headers) {
107
- content = js.g.Buffer.concat(chunks)
113
+ response.on("end", function (local state, content, error, headers) {
114
+ state = {
115
+ headers: response.headers
116
+ status: response.statusCode
117
+ contentType: response.headers["content-type"]
118
+ complete: response.complete
119
+ }
120
+ if options.streamHook && stream()
121
+ call(options.streamHook, false, state) // no more data
122
+ else
123
+ content = js.g.Buffer.concat(chunks)
108
124
  if response.statusCode < 200 || response.statusCode >= 299 { // 299 is "cancelled"
109
125
  if options.debug
110
126
  debuglog("HttpsApiRequest status:", url, response.statusCode, "message:", content)
@@ -120,7 +136,7 @@ closure httpsApiReqOnce(url, options,
120
136
  error = Error("HttpsApiRequest decode:", url, error)
121
137
  else
122
138
  content = new(content)
123
- if array(content) { // deencapsulate to get API result
139
+ if array(content) { // naan: decapsulate to get API result
124
140
  content = totuple(content)
125
141
  error = content.0
126
142
  content = content.1
@@ -129,11 +145,7 @@ closure httpsApiReqOnce(url, options,
129
145
  }
130
146
  else
131
147
  error = Error("HttpsApiRequest terminated prematurely:", url, response.statusCode)
132
- pending.signal(list(error, content, {
133
- headers: response.headers
134
- status: response.statusCode
135
- contentType: response.headers["content-type"]
136
- }))
148
+ pending.signal(list(error, content, state))
137
149
  })
138
150
  })
139
151
  request.on("error", function hre(error) {
@@ -149,6 +161,66 @@ closure httpsApiReqOnce(url, options,
149
161
  };
150
162
 
151
163
 
164
+ /*
165
+ * sseParserFactory
166
+ *
167
+ * Returns an Server-Sent Events parser that calls the specified message procedure each time an event
168
+ * is recognized. Call the returned parser object's write method to add data for parsing.
169
+ *
170
+ */
171
+
172
+ closure sseParserFactory(message, local parser) {
173
+ global()
174
+ parser = new(object, this)
175
+ parser.buffer = ""
176
+ parser.eomrx = RegExp("(?:\\r(?!\\n)|\\r\\n|\\n){2}")
177
+ parser.eolrx = RegExp("(?:\\r(?!\\n)|\\r\\n|\\n){1}")
178
+ parser.divrx = RegExp("([^:]*)[:] ?(.*)")
179
+
180
+ // write
181
+ //
182
+ // Add a string to the parser input, calling message for any completed messages.
183
+ //
184
+ parser.write = function write(text,
185
+ local messages, msg, output, line, found, field, value) {
186
+ parser.buffer = parser.buffer.concat(text)
187
+ messages = parser.buffer.split(parser.eomrx)
188
+ loop {
189
+ msg = messages.shift()
190
+ if messages.length == 0 {
191
+ parser.buffer = msg
192
+ break
193
+ }
194
+ output = { }
195
+ for line in msg.split(parser.eolrx) {
196
+ found = line.match(parser.divrx)
197
+ if found {
198
+ field = found.1
199
+ value = found.2
200
+ } else {
201
+ field = line
202
+ value = ""
203
+ }
204
+ if field == "data" {
205
+ if output.data
206
+ output.data = output.data.concat("\n", value)
207
+ else
208
+ output.data = value
209
+ }
210
+ else if field == "event"
211
+ output.type = value
212
+ else if field == "id"
213
+ output.lastEventID = value
214
+ }
215
+ call(message, output)
216
+ }
217
+ }
218
+
219
+ // finis
220
+ parser
221
+ };
222
+
223
+
152
224
  /*
153
225
  * HttpsApiRequest
154
226
  *
@@ -157,11 +229,24 @@ closure httpsApiReqOnce(url, options,
157
229
  * Options:
158
230
  * {
159
231
  * retrier: function(error) { error.status == 502 } // retry 502 errors
232
+ * sseHook: function(msg) { } // process SSE messages
160
233
  * }
161
234
  *
162
235
  */
163
236
 
164
- closure HttpsApiRequest(url, options, local result) {
237
+ closure HttpsApiRequest(url, options, local parser, decoder, result) {
238
+ if options.sseHook {
239
+ parser = sseParserFactory(options.sseHook)
240
+ decoder = xnew(stringDecoder.StringDecoder, "utf8")
241
+ options = merge(options, {
242
+ streamHook: function(chunk) {
243
+ if chunk
244
+ parser.write(decoder.write(chunk)) // data arrived
245
+ else
246
+ parser.write(decoder.end()) // end of data
247
+ }
248
+ })
249
+ }
165
250
  loop {
166
251
  result = httpsApiReqOnce(url, options)
167
252
  if result.0 && result.0.status { // check for retries
@@ -182,7 +267,7 @@ closure HttpsApiRequest(url, options, local result) {
182
267
  */
183
268
 
184
269
  function https_nodeInit(local manifest) {
185
- manifest = `(httpsApiReqOnce, HttpsApiRequest, https_nodeInit)
270
+ manifest = `(httpsApiReqOnce, sseParserFactory, HttpsApiRequest, https_nodeInit)
186
271
 
187
272
  Naan.module.build(module.id, "https_request", function(modobj, compobj) {
188
273
  require("./node.nlg")
@@ -133,6 +133,7 @@ function nodrInit(local manifest) {
133
133
  nodeHttps = js.r("https")
134
134
  nodeUrl= js.r("url")
135
135
  threads = js.r("worker_threads")
136
+ stringDecoder = js.r("string_decoder")
136
137
  }()
137
138
  module.reload = nodrReload
138
139
  module.exports.NaanlangDir = NaanlangDir