@naanlang/naan 1.0.2 → 1.0.3

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 (35) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +3 -3
  3. package/bin/index.js +2 -2
  4. package/dist/env_web.js +149 -20
  5. package/dist/naan.min.js +5 -5
  6. package/frameworks/browser/sworker.js +309 -91
  7. package/frameworks/browser/terminals.nlg +22 -9
  8. package/frameworks/browser/workers.nlg +19 -11
  9. package/frameworks/client/apiclient.nlg +17 -4
  10. package/frameworks/client/psm_client.nlg +116 -53
  11. package/frameworks/common/common.nlg +12 -2
  12. package/frameworks/node/apiserver.nlg +25 -13
  13. package/frameworks/node/filesystem.nlg +173 -30
  14. package/frameworks/node/psm_server.nlg +95 -32
  15. package/frameworks/project/build.nlg +40 -23
  16. package/frameworks/project/projects.nlg +47 -26
  17. package/frameworks/running/debugnub.nlg +2 -5
  18. package/frameworks/storage/csv.nlg +120 -0
  19. package/frameworks/storage/dbt_pouch.nlg +41 -25
  20. package/frameworks/storage/psm.nlg +108 -28
  21. package/frameworks/storage/psm_dbtables.nlg +7 -3
  22. package/frameworks/storage/resources.nlg +30 -2
  23. package/lib/browser/env_web.js +147 -18
  24. package/lib/browser/env_webworker.js +1 -1
  25. package/lib/browser/require.js +1 -1
  26. package/lib/core/naanlib.js +5 -5
  27. package/package.json +1 -1
  28. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +1 -1
  29. package/plugins/serviceAws/aws_dynamo.nlg +625 -141
  30. package/plugins/serviceAws/aws_dynextra.nlg +294 -0
  31. package/plugins/serviceAws/dbt_aws.nlg +31 -11
  32. package/plugins/serviceAws/psm_aws.nlg +42 -26
  33. package/plugins/serviceAws/serviceAws.nlg +1 -0
  34. package/plugins/serviceGitHub/psm_github.nlg +41 -25
  35. package/plugins/serviceGitLab/psm_gitlab.nlg +41 -25
@@ -4,66 +4,319 @@
4
4
  *
5
5
  * Nide service worker script.
6
6
  *
7
+ * The goal of this service worker is to 1) cache fetch requests for static content, and 2) relay
8
+ * fetch requests in the /run/ URL path namespace to instances of the IDE that can provide it. The
9
+ * identifier for the various browser windows is by "clientId", which is a GUID reported by the
10
+ * service worker and message channel APIs. Here we use the term clientId to refer to windows that
11
+ * consume data in the /run/ path, i.e. the IDE targets, and the term sourceId to refer to windows
12
+ * that provide data, i.e. the IDE instances.
13
+ * When the service worker is first executed it sends messages to all known windows requesting
14
+ * that they establish a message channel and forward the service worker's port to it. This secondary
15
+ * channel is used to communicate fetch requests from the service worker to the source IDE and then
16
+ * get the response. If an IDE is executed subsequently then it registers itself by sending the port
17
+ * unilatterally, instead of waiting to be requested.
18
+ * The service worker gets a fetch request when a non-cached resource is requested by a client
19
+ * window. If the URL path is not within /run/ then this executes a normal network request. IF the
20
+ * URL path is within /run/ then this asks the IDE source(s) to fetch it. Initially every IDE source
21
+ * is sent the request, but when one provides a 200 response then this stores that source port under
22
+ * the clientId in fetchPorts so that subsequent requests can go only to the correct IDE. This works
23
+ * great until an IDE goes away or becomes unresponsive.
24
+ * Unfortunately there is no event that tells us when a window is closed or goes away. The first
25
+ * indication of trouble is when an IDE fails to respond within a reasonable amount of time, which
26
+ * could be anything. We can enumerates the valid clients, which gives us a chance to validate our
27
+ * knowledge of which ones are responding. If a request takes longer than a second to respond then
28
+ * this executes a validate operation, which eliminates any clients that have gone away and rejects
29
+ * their pending operations.
30
+ * One potential problem is stale caches, where a new publisher (e.g. NaanIDE) or new version
31
+ * arises but this worker is still serving old data. The solution is that every publisher provides
32
+ * its ID and version along with the mesage port. If that is different from the previous version then
33
+ * the old caches are cleared and data re-fetched.
34
+ *
7
35
  * column positioning: // // !
8
36
  *
9
- * Copyright (c) 2020 by Richard C. Zulch
37
+ * Copyright (c) 2020-2022 by Richard C. Zulch
10
38
  *
11
39
  */
12
40
 
13
- var CurrentCacheName = "Naan IDE 1.0.2-1"
41
+ var CurrentCacheName = "Naanlang 1.0.3-1";
14
42
 
15
43
 
16
44
  //
17
45
  // locals
18
46
  //
19
47
 
20
- var fetchQueue = [];
21
- var fetchNextSeq = 1;
22
- var msgport;
48
+ var fetchQueue = []; // queue for outstanding requests to sources
49
+ var fetchNextSeq = 1; // sequence number for source requests
50
+ var msgPorts = {}; // message ports keyed by sourceId
51
+ var fetchPorts = {}; // message ports keyed by clientId
52
+ var waitingForInit = []; // initialization functions, or false after done
23
53
 
24
54
 
25
55
  /*
26
- * message event
56
+ * initialize
27
57
  *
28
- * Received when someone sends us a.
58
+ * Initiate listening for incoming messages from our clients. This waits for any client responses
59
+ * and then quits. Any clients that want to participate after that must unilaterally send a port for
60
+ * communication.
29
61
  *
30
62
  */
31
63
 
32
- var promiseport = new Promise(function (resolve, reject) {
64
+ (function initialize() {
65
+ if (!waitingForInit)
66
+ return; // already initialized
67
+ var pending = 0;
68
+
69
+ // doneInit
70
+ //
71
+ // Call this to resolve any waiting promises and release them for attempting to fetch from the
72
+ // IDEs (sources.)
73
+ //
74
+ function doneInit() {
75
+ if (!waitingForInit)
76
+ return; // already done
77
+ var waiters = waitingForInit;
78
+ waitingForInit = false; // no more waiters allowed
79
+ for (var waiter in waiters)
80
+ waiter(); // call each waiter
81
+ }
82
+
83
+ // self.onmessage events
84
+ //
85
+ // This receives channel port messages from the IDEs as they either 1) respond to a request to
86
+ // register, or 2) spontaneously register themselves as they notice that we're running.
87
+ //
33
88
  self.addEventListener('message', function(event) {
34
- console.log("[1.0.2-1] port received");
35
- msgport = event.data.hereIsYourPort;
89
+ var msgport = event.data.hereIsYourPort; // message port of a client
90
+ var pubID = event.data.hereIsMyID;
91
+ var pubVersion = event.data.hereIsMyVersion;
92
+ var sourceId = event.source.id; // client id of sender of message
93
+ msgPorts[sourceId] = msgport;
94
+ var newCacheName = "Naanlang".concat("-", pubID, "-", pubVersion);
95
+ console.log("[1.0.3-1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
+ if (CurrentCacheName != newCacheName) {
97
+ for (var srcid in msgPorts)
98
+ msgPorts[srcid].postMessage({
99
+ id: "upgrade",
100
+ previous: "1.0.3-1",
101
+ version: pubVersion
102
+ });
103
+ }
104
+ ClearCaches(newCacheName)
105
+ .then(Reaper); // clean up obsolete info
106
+
107
+ // msgport.onmessage events
108
+ //
109
+ // This receives channel port messages that are responding to our fetch requests. The IDE
110
+ // can also send a text message for logging, but that is used only for debugging.
111
+ //
36
112
  msgport.onmessage = function(msg) {
37
113
  msg = msg.data;
38
114
  if (msg.id == "response")
39
- postResponse(msg);
115
+ processResponse(msg);
40
116
  else if (msg.id == "text")
41
- console.log("[1.0.2-1] msg received:", msg.text);
117
+ console.log("[1.0.3-1] msg received:", msg.text);
42
118
  };
43
- /*
44
- // don't clutter the log
45
- msgport.postMessage({
46
- id: "text",
47
- text: "port received by 1.0.2-1",
48
- });
49
- */
50
- resolve(msgport);
51
-
119
+
120
+ // send text to IDE log
52
121
  //
53
- // postResponse
122
+ // This is only for debugging.
54
123
  //
55
-
56
- function postResponse(msg) {
57
- var fqdex, response, item;
58
- for (fqdex = 0; fqdex < fetchQueue.length; ++fqdex)
59
- if (fetchQueue[fqdex].seq === msg.seq) {
60
- item = fetchQueue.splice(fqdex, 1);
61
- item[0].resolve(new Response(msg.body, msg.init));
62
- break;
63
- }
124
+ /*
125
+ // don't clutter the log
126
+ msgport.postMessage({
127
+ id: "text",
128
+ text: "port received by 1.0.3-1",
129
+ });
130
+ */
131
+ if (--pending === 0)
132
+ doneInit(); // we finished all known clients
133
+ });
134
+
135
+ // initialize known IDE instances.
136
+ //
137
+ // This sends a port request to each known IDE instance. Fetches that occur prior to completion
138
+ // of initialization will wait for these instances to respond. A timeout prevents one errant IDE
139
+ // from hanging all requests, by releasing the fetches even before all source IDEs respond.
140
+ //
141
+ self.clients.matchAll({
142
+ includeUncontrolled: true
143
+ }).then(function(clientList) {
144
+ clientList.every(function(client) {
145
+ console.log("[1.0.3-1] requesting new msgport for", client.id);
146
+ ++pending;
147
+ client.postMessage({ // tell client(s) we need this fetch source
148
+ msg: "Naan_need_fetch_port",
149
+ });
150
+ });
151
+ setTimeout(doneInit, 1000); // release fetches
152
+ });
153
+
154
+ // processResponse
155
+ //
156
+ // An IDE source has responded to our fetch request. If this was successful then we record the
157
+ // IDE (sourceId) so that subsequent requests from that target (clientId) will go to the same
158
+ // source.
159
+ //
160
+ function processResponse(msg) {
161
+ var fqdex, response, item;
162
+ for (fqdex = 0; fqdex < fetchQueue.length; ++fqdex)
163
+ if (fetchQueue[fqdex].seq === msg.seq) {
164
+ item = fetchQueue.splice(fqdex, 1)[0];
165
+ if (item.clientId && msg.init.status == 200)
166
+ fetchPorts[item.clientId] = item.msgport; // successful lookup
167
+ item.resolve(new Response(msg.body, msg.init));
168
+ break;
169
+ }
170
+ }
171
+ })();
172
+
173
+
174
+ /*
175
+ * Reaper
176
+ *
177
+ * Periodic cleanup returning a promise. This removes anything that is obsolete and resolve all
178
+ * associated transactions.
179
+ *
180
+ */
181
+
182
+ function Reaper() {
183
+ var promise = self.clients.matchAll({
184
+ includeUncontrolled: true
185
+ }).then(function(clientList) { // both sources and clients
186
+ var clients = {};
187
+ for (var clidex in clientList)
188
+ clients[clientList[clidex].id] = clientList[clidex];
189
+ for (var sourceId in msgPorts)
190
+ if (!clients[sourceId]) {
191
+ console.log("[1.0.3-1] source gone:", sourceId);
192
+ delete msgPorts[sourceId]; // no longer a source
193
+ }
194
+ for (var clientId in fetchPorts)
195
+ if (!clients[clientId]) {
196
+ console.log("[1.0.3-1] client gone:", clientId);
197
+ delete fetchPorts[clientId]; // no longer a client
198
+ }
199
+ for (var fqdex = 0; fqdex < fetchQueue.length; ++fqdex) {
200
+ var item = fetchQueue[fqdex];
201
+ while (item.clientId && !clients[item.clientId]
202
+ || !Object.values(msgPorts).includes(item.msgport)) {
203
+ fetchQueue.splice(fqdex, 1); // either client or source missing
204
+ item.resolve(new Response(undefined, {
205
+ status: 404,
206
+ statusText: "Source Disappeared" // if client is gone this disappears too
207
+ }));
208
+ if (fqdex >= fetchQueue.length)
209
+ break; // we've run out of transactions
210
+ item = fetchQueue[fqdex];
211
+ }
64
212
  }
65
213
  });
66
- });
214
+ return (promise);
215
+ }
216
+
217
+
218
+ /*
219
+ * ClearCaches
220
+ *
221
+ * Clear obsolete caches in favor of the new name, returning a promise.
222
+ *
223
+ */
224
+
225
+ function ClearCaches(newCacheName) {
226
+ if (CurrentCacheName == newCacheName)
227
+ return (Promise.resolve()); // no change
228
+ CurrentCacheName = newCacheName;
229
+ var promise = caches.keys().then(function(cacheNames) { // delete old cache entries
230
+ return (Promise.all(
231
+ cacheNames.map(function(cacheName) {
232
+ if (cacheName !== CurrentCacheName) {
233
+ console.log('[1.0.3-1] deleting old cache:', cacheName);
234
+ return (caches.delete(cacheName));
235
+ }
236
+ })
237
+ ));
238
+ }).then(function() { // claim all clients
239
+ console.log('[1.0.3-1] claiming clients for version', CurrentCacheName);
240
+ return (self.clients.claim());
241
+ });
242
+ return (promise);
243
+ }
244
+
245
+
246
+ /*
247
+ * GetClientResponse
248
+ *
249
+ * Get message ports from the current clients if they can supply contents for the specified URL.
250
+ * This returns a promise that is resolved after the msgPorts object is updated. Note that the
251
+ * clientId argument is the requester, but the client that actually has the available data will
252
+ * often be a different window.
253
+ * On the first request from a given client, every source is requested to respond to the URL
254
+ * pathname, and the first one that does is designated the official source for that client. While
255
+ * asking all the sources, this checks every second to see if any of them have gone away so that it
256
+ * does not hang waiting. A source can be unresponsive and delay the fetch forever, e.g. in the
257
+ * debugger, but if the window has closed then it will get reaped and the fetch will complete.
258
+ * On subsequent requests, to a known source, this does not have a timeout. If the source goes
259
+ * away then the fetch will not complete, but the client is screwed anyway in that case. Eventually
260
+ * the reaper will be called for other reasons and it will clean things up then.
261
+ *
262
+ */
263
+
264
+ function GetClientResponse(event, urlpath) {
265
+
266
+ // delegate
267
+ //
268
+ // Delegate to a source window and return a promise
269
+ //
270
+ function delegate(msgport, clientId) {
271
+ return (new Promise(function(resolve, reject) {
272
+ var seqno = fetchNextSeq++;
273
+ fetchQueue.push({
274
+ seq: seqno,
275
+ msgport: msgport,
276
+ clientId: clientId,
277
+ resolve: resolve
278
+ });
279
+ msgport.postMessage({
280
+ id: "fetch",
281
+ seq: seqno,
282
+ version: "1.0.3-1",
283
+ request: {
284
+ method: event.request.method,
285
+ url: event.request.url
286
+ },
287
+ });
288
+ }));
289
+ }
290
+
291
+ var clientId;
292
+ if (event.clientId !== "") {
293
+ clientId = event.clientId;
294
+ if (fetchPorts[clientId])
295
+ return (delegate(fetchPorts[clientId], false));
296
+ }
297
+ else
298
+ clientId = event.resultingClientId; // new window
299
+ var pending = [];
300
+ var sourceIds = Object.getOwnPropertyNames(msgPorts);
301
+ for (var mdex = 0; mdex < sourceIds.length; ++mdex)
302
+ {
303
+ var sourceId = sourceIds[mdex];
304
+ pending.push(delegate(msgPorts[sourceId], clientId));
305
+ }
306
+ if (pending.length === 0)
307
+ return (Promise.resolve(new Response(undefined, {
308
+ status: 404,
309
+ statusText: "No Sources"
310
+ })));
311
+ var toid = setInterval(Reaper, 1000); // reap every second while pending
312
+ return (Promise.all(pending).then(function(responses) {
313
+ clearInterval(toid); // all completed
314
+ for (var idex in responses)
315
+ if (responses[idex].status == 200)
316
+ return (responses[idex]); // this is the one we want
317
+ return (responses[0]); // fall back to first
318
+ }));
319
+ }
67
320
 
68
321
 
69
322
  /*
@@ -74,7 +327,7 @@ var promiseport = new Promise(function (resolve, reject) {
74
327
  */
75
328
 
76
329
  self.addEventListener('install', function(event) {
77
- console.log("[1.0.2-1] install");
330
+ console.log("[1.0.3-1] install");
78
331
  self.skipWaiting();
79
332
  });
80
333
 
@@ -95,53 +348,30 @@ self.addEventListener('fetch', function(event) {
95
348
  var tid;
96
349
  var promise;
97
350
  var url = new URL(event.request.url);
98
- var nocache = event.method != "GET" || url.search.length > 0;
99
- if (nocache && url.pathname.startsWith("/run/")) {
100
- if (msgport)
101
- promise = Promise.resolve(msgport);
102
- else {
103
- promise = self.clients.matchAll({
104
- includeUncontrolled: true
105
- }).then(function(clientList) {
106
- console.log("[1.0.2-1] requesting new msgport");
107
- var urls = clientList.map(function(client) {
108
- client.postMessage({ // tell client(s) we need a fetch source
109
- msg: "Naan_need_fetch_port"
110
- });
111
- return (client.url);
351
+ var nocache = event.request.method != "GET"
352
+ || url.search.length !== 0
353
+ || event.request.headers.get('range');
354
+ if (url.pathname.startsWith("/run/")) {
355
+ nocache = true;
356
+ if (waitingForInit) {
357
+ promise = new Promise(function(resolve, reject) {
358
+ waitingForInit.push(function(){
359
+ resolve(); // init was complete
112
360
  });
113
- console.log('[1.0.2-1] matching clients:', urls.join(', '));
114
- return (promiseport);
361
+ }).then(function(){
362
+ return (GetClientResponse(event, url.pathname)); // then get the response
115
363
  });
116
- }
117
- promise = promise.then(function(mp) { return new Promise(function(resolve, reject) {
118
- /*
119
- tid = setTimeout( function() {
120
- reject("timeout!");
121
- }, 250); */
122
- var seqno = fetchNextSeq++;
123
- if (msgport) {
124
- fetchQueue.push({
125
- seq: seqno,
126
- resolve: resolve
127
- });
128
- msgport.postMessage({
129
- id: "fetch",
130
- seq: seqno,
131
- version: "1.0.2-1",
132
- request: {
133
- method: event.request.method,
134
- url: event.request.url
135
- },
136
- });
137
- } else {
138
- console.log("[1.0.2-1] fetch has no msgport");
139
- return (reject("fatal delegation error"));
140
- }
141
- }) });
364
+ } else
365
+ promise = GetClientResponse(event, url.pathname);
142
366
  }
143
367
  else
144
- promise = fetch(event.request);
368
+ promise = fetch(event.request).catch(function (e) {
369
+ console.log("[1.0.3-1] fetch failed", e);
370
+ return (new Response(undefined, {
371
+ status: 404,
372
+ statusText: "Fetch Failed"
373
+ }));
374
+ });
145
375
  return (promise.then(function(response) {
146
376
  // delete tid timer ###
147
377
  if (!nocache) {
@@ -165,28 +395,16 @@ self.addEventListener('fetch', function(event) {
165
395
  */
166
396
 
167
397
  self.addEventListener('activate', function(event) {
168
- console.log("[1.0.2-1] activate");
398
+ console.log("[1.0.3-1] activate");
169
399
  self.clients.matchAll({ // for debugging, list controlled clients
170
400
  includeUncontrolled: true
171
401
  }).then(function(clientList) {
172
402
  var urls = clientList.map(function(client) {
173
403
  return (client.url);
174
404
  });
175
- console.log('[1.0.2-1] matching clients:', urls.join(', '));
405
+ console.log('[1.0.3-1] matching clients:', urls.join(', '));
176
406
  });
177
- var promise = caches.keys().then(function(cacheNames) { // delete old cache entries
178
- return (Promise.all(
179
- cacheNames.map(function(cacheName) {
180
- if (cacheName !== CurrentCacheName) {
181
- console.log('[1.0.2-1] deleting old cache:', cacheName);
182
- return (caches.delete(cacheName));
183
- }
184
- })
185
- ));
186
- }).then(function() { // claim all clients
187
- console.log('[1.0.2-1] claiming clients for version', CurrentCacheName);
188
- return (self.clients.claim());
189
- });
407
+ var promise = ClearCaches(CurrentCacheName);
190
408
  if (event.waitUntil)
191
409
  event.waitUntil(promise);
192
410
  });
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2019-2021 by Richard C. Zulch
9
+ * Copyright (c) 2019-2022 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -170,6 +170,7 @@ closure TermHost(track, api, name, workerID, options,
170
170
 
171
171
  target.sendControl = function sendControl(workerOp, payload) {
172
172
  target.ws.send(js.w.JSON.stringify({
173
+ guid: api.guid
173
174
  clientID: clientID
174
175
  serverID: serverID
175
176
  workerID: target.workerID
@@ -355,7 +356,7 @@ closure TermHost(track, api, name, workerID, options,
355
356
  */
356
357
 
357
358
  closure TermLocal(track, name, workerID, options,
358
- local target, nlregex)
359
+ local target, nlregex, listener)
359
360
  {
360
361
  if workerID == "NaanIDE" {
361
362
  if !js.t
@@ -363,8 +364,6 @@ closure TermLocal(track, name, workerID, options,
363
364
  termInstallVirtualWatcher(track, js.t)
364
365
  return (termIDE(track, name, workerID, js.t)) // connect to IDE execution instance itself
365
366
  }
366
- if not js.w.Worker
367
- return (list(Error("Local environment does not support workers")))
368
367
  if dictionary(options)
369
368
  options = new(options)
370
369
  else
@@ -372,13 +371,27 @@ closure TermLocal(track, name, workerID, options,
372
371
  target = runningExecutors.ExecutorBase(track, "Local", name)
373
372
  nlregex = RegExp("\\n", "g")
374
373
  target.workerID = workerID
375
- target.worker = xnew(js.w.Worker, "env_webworker.js")
376
374
  target.msgQueue = []
375
+ if workerID == "NaanIDE-Debug" {
376
+ target.worker = js.w.open(js.w.location.href, "_blank")
377
+ target.worker.addEventListener("beforeunload", function(e) { // our target is closing
378
+ destroy()
379
+ })
380
+ js.w.addEventListener("beforeunload", function(e) { // we are closing
381
+ // ### tell target we are closing (if needed)
382
+ })
383
+ listener = js.w
384
+ } else {
385
+ if not js.w.Worker
386
+ return (list(Error("Local environment does not support workers")))
387
+ target.worker = xnew(js.w.Worker, "env_webworker.js")
388
+ listener = target.worker
389
+ }
377
390
 
378
391
  // onmessage
379
392
  // A message was received from the worker.
380
393
 
381
- target.worker.onmessage = function(e, msg) {
394
+ listener.addEventListener("message", function(e, msg) {
382
395
  msg = new(e.data)
383
396
  if msg.id != "targetout" && msg.id != "loaded" {
384
397
  if msg.id == "status"
@@ -388,14 +401,14 @@ closure TermLocal(track, name, workerID, options,
388
401
  target.msgQueue.push(msg)
389
402
  return } }
390
403
  target.doMessage(msg)
391
- }
404
+ })
392
405
 
393
406
  // onerror
394
407
  // The worker had an error.
395
408
 
396
- target.worker.onerror = function(e) {
409
+ target.worker.addEventListener("error", function(e) {
397
410
  target.execClosed(Error("instance error", e.message))
398
- }
411
+ })
399
412
 
400
413
  //
401
414
  // oninterrupt
@@ -18,7 +18,7 @@
18
18
  *
19
19
  */
20
20
 
21
- closure ServiceWorker(local serv, callback) {
21
+ closure ServiceWorker(pubID, pubVersion, local serv, callback) {
22
22
  serv = new(object, this)
23
23
 
24
24
  //
@@ -32,7 +32,11 @@ closure ServiceWorker(local serv, callback) {
32
32
  mchan = xnew(js.w.MessageChannel)
33
33
  serv.msgport = mchan.port2
34
34
  serv.msgport.onmessage = workerMsg.proc
35
- serv.sw.postMessage({ hereIsYourPort: mchan.port1 }, [mchan.port1])
35
+ serv.sw.postMessage({
36
+ hereIsYourPort: mchan.port1
37
+ hereIsMyID: pubID
38
+ hereIsMyVersion: pubVersion
39
+ }, [mchan.port1])
36
40
  }
37
41
 
38
42
  //
@@ -50,7 +54,11 @@ closure ServiceWorker(local serv, callback) {
50
54
  request: msg.request
51
55
  version: msg.version
52
56
  })
53
- } else if msg.id == "text"
57
+ } else if msg.id == "upgrade" {
58
+ if msg.version != msg.previous // same version: new install
59
+ debuglog("service worker version upgrade to:", msg.version, "from", msg.previous)
60
+ }
61
+ else if msg.id == "text"
54
62
  debuglog("sw[".concat(serv.scope, "]: "), msg.text) // handy remote debug capability
55
63
  }
56
64
 
@@ -174,10 +182,10 @@ closure ServiceWorker(local serv, callback) {
174
182
  *
175
183
  */
176
184
 
177
- closure ScopedServiceWorker(local scoper, result) {
185
+ closure ScopedServiceWorker(pubID, pubVersion, local scoper, result) {
178
186
  scoper = new(object, ScopedServiceWorker)
179
187
  scoper.scopes = {}
180
- scoper.serv = ServiceWorker()
188
+ scoper.serv = ServiceWorker(pubID, pubVersion)
181
189
  result = scoper.serv.register("/", closure(error, data, local response, filepath, scope) {
182
190
  if data.message == "fetch" {
183
191
  response = {
@@ -201,7 +209,7 @@ closure ScopedServiceWorker(local scoper, result) {
201
209
  filepath = xnew(js.w.URL, data.request.url).pathname
202
210
  filepath = DecodeURIComponent(filepath) // "/run/<scope>/path..."
203
211
  filepath = filepath.slice(4) // "/<scope>/path..."
204
- error = true
212
+ error = true // stays true iff not in scope
205
213
  for scope in scoper.scopes {
206
214
  if filepath.startsWith(scope) {
207
215
  filepath = filepath.slice(scope.length+1) // remove scope including "/"
@@ -209,8 +217,6 @@ closure ScopedServiceWorker(local scoper, result) {
209
217
  break
210
218
  }
211
219
  }
212
- if error === true
213
- debuglog("ScopedServiceWorker: couldn't find scope", scope, "in", totuple(scoper.scopes))
214
220
 
215
221
  // read resource failed
216
222
  if error {
@@ -353,12 +359,14 @@ closure TrackedScope(track, scope, fs, rootpath, local trope, result) {
353
359
  /*
354
360
  * TrackServiceWorkers
355
361
  *
356
- * Configure service workers to be tracked in the execution tracker.
362
+ * Configure service workers to be tracked in the execution tracker. The pubID identifies us as
363
+ * the publisher for this service worker, and the version allows it to clear its cache when versions
364
+ * change.
357
365
  *
358
366
  */
359
367
 
360
- closure TrackServiceWorkers(track, local error) {
361
- `(error, serviceWorker) = ScopedServiceWorker()
368
+ closure TrackServiceWorkers(track, pubID, pubVersion, local error) {
369
+ `(error, serviceWorker) = ScopedServiceWorker(pubID, pubVersion)
362
370
  if error
363
371
  ErrorDebugLog("TrackServiceWorkers: cannot load service worker", error)
364
372
  track.register(TrackedScope, "V-Site")