@sasjs/cli 6.25.0 → 6.26.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.
- package/build/commands/build/internal/mockSite/assets/persist.js +165 -26
- package/build/commands/build/internal/mockSite/assets/sw.js +419 -63
- package/build/commands/build/internal/mockSite/mockSite.d.ts +5 -0
- package/build/commands/build/internal/mockSite/mockSite.js +49 -6
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +150 -0
- package/build/commands/build/internal/mockSite/spec/persist.spec.js +335 -15
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +522 -18
- package/package.json +5 -5
|
@@ -3,29 +3,40 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The service worker hosts the runtime but may be terminated between requests,
|
|
5
5
|
* so it holds no durable state. On boot this script reads the persisted drive
|
|
6
|
-
* and hands
|
|
7
|
-
* files it changed and
|
|
6
|
+
* and identity and hands them to the worker; after every service run the worker
|
|
7
|
+
* sends back the files it changed and the identity store's snapshot when a route
|
|
8
|
+
* changed it, and both are written here.
|
|
8
9
|
*/
|
|
9
10
|
;(function () {
|
|
10
11
|
const DB_NAME = 'sasjs-mock-host'
|
|
11
|
-
const
|
|
12
|
+
const FILES = 'files'
|
|
13
|
+
/**
|
|
14
|
+
* The identity the visitor last had: the fixture the worker's store reported
|
|
15
|
+
* and the account the visitor chose to view the app as. One row, because the
|
|
16
|
+
* two are written together - a subject without the fixture it names would be a
|
|
17
|
+
* boot signed in as an account the store no longer holds.
|
|
18
|
+
*/
|
|
19
|
+
const IDENTITY = 'identity'
|
|
20
|
+
const IDENTITY_KEY = 'current'
|
|
12
21
|
|
|
13
22
|
const openDb = () =>
|
|
14
23
|
new Promise((resolve, reject) => {
|
|
15
|
-
const req = indexedDB.open(DB_NAME,
|
|
24
|
+
const req = indexedDB.open(DB_NAME, 2)
|
|
16
25
|
req.onupgradeneeded = () => {
|
|
17
26
|
const db = req.result
|
|
18
|
-
if (!db.objectStoreNames.contains(
|
|
27
|
+
if (!db.objectStoreNames.contains(FILES)) db.createObjectStore(FILES)
|
|
28
|
+
if (!db.objectStoreNames.contains(IDENTITY))
|
|
29
|
+
db.createObjectStore(IDENTITY)
|
|
19
30
|
}
|
|
20
31
|
req.onsuccess = () => resolve(req.result)
|
|
21
32
|
req.onerror = () => reject(req.error)
|
|
22
33
|
})
|
|
23
34
|
|
|
24
|
-
const withStore = async (mode, run) => {
|
|
35
|
+
const withStore = async (mode, run, name = FILES) => {
|
|
25
36
|
const db = await openDb()
|
|
26
37
|
return new Promise((resolve, reject) => {
|
|
27
|
-
const tx = db.transaction(
|
|
28
|
-
const store = tx.objectStore(
|
|
38
|
+
const tx = db.transaction(name, mode)
|
|
39
|
+
const store = tx.objectStore(name)
|
|
29
40
|
let result
|
|
30
41
|
try {
|
|
31
42
|
result = run(store)
|
|
@@ -44,15 +55,15 @@
|
|
|
44
55
|
try {
|
|
45
56
|
const db = await openDb()
|
|
46
57
|
const keysReq = db
|
|
47
|
-
.transaction(
|
|
48
|
-
.objectStore(
|
|
58
|
+
.transaction(FILES, 'readonly')
|
|
59
|
+
.objectStore(FILES)
|
|
49
60
|
.getAllKeys()
|
|
50
61
|
keysReq.onsuccess = () => {
|
|
51
62
|
const keys = keysReq.result
|
|
52
63
|
if (!keys.length) return resolve({})
|
|
53
64
|
const valsReq = db
|
|
54
|
-
.transaction(
|
|
55
|
-
.objectStore(
|
|
65
|
+
.transaction(FILES, 'readonly')
|
|
66
|
+
.objectStore(FILES)
|
|
56
67
|
.getAll()
|
|
57
68
|
valsReq.onsuccess = () => {
|
|
58
69
|
const files = {}
|
|
@@ -89,6 +100,36 @@
|
|
|
89
100
|
|
|
90
101
|
const clearAll = () => withStore('readwrite', (store) => store.clear())
|
|
91
102
|
|
|
103
|
+
/**
|
|
104
|
+
* The stored identity, `null` when there is none. A row whose shape is not the
|
|
105
|
+
* one this script writes is ignored rather than handed on: the worker refuses a
|
|
106
|
+
* fixture it cannot read by name, and re-sending a value that is not a row at
|
|
107
|
+
* all would blame the fixture for a corrupt database.
|
|
108
|
+
*/
|
|
109
|
+
const readIdentity = () =>
|
|
110
|
+
new Promise(async (resolve, reject) => {
|
|
111
|
+
try {
|
|
112
|
+
const db = await openDb()
|
|
113
|
+
const req = db
|
|
114
|
+
.transaction(IDENTITY, 'readonly')
|
|
115
|
+
.objectStore(IDENTITY)
|
|
116
|
+
.get(IDENTITY_KEY)
|
|
117
|
+
req.onsuccess = () => {
|
|
118
|
+
const row = req.result
|
|
119
|
+
resolve(row && typeof row === 'object' && row.fixture ? row : null)
|
|
120
|
+
}
|
|
121
|
+
req.onerror = () => reject(req.error)
|
|
122
|
+
} catch (e) {
|
|
123
|
+
reject(e)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const writeIdentity = (row) =>
|
|
128
|
+
withStore('readwrite', (store) => store.put(row, IDENTITY_KEY), IDENTITY)
|
|
129
|
+
|
|
130
|
+
const clearIdentity = () =>
|
|
131
|
+
withStore('readwrite', (store) => store.delete(IDENTITY_KEY), IDENTITY)
|
|
132
|
+
|
|
92
133
|
/**
|
|
93
134
|
* The build this site is, as the page can see it: mock-config.json carries the
|
|
94
135
|
* stamp the build wrote over its host code and the seed it lays out. Read here
|
|
@@ -191,13 +232,29 @@
|
|
|
191
232
|
})
|
|
192
233
|
})
|
|
193
234
|
|
|
194
|
-
// Persist whatever the worker reports as changed
|
|
235
|
+
// Persist whatever the worker reports as changed: drive files, and the
|
|
236
|
+
// identity store's snapshot when a route created, changed or removed anything.
|
|
195
237
|
navigator.serviceWorker.addEventListener('message', (ev) => {
|
|
196
238
|
const data = ev.data || {}
|
|
197
|
-
if (data.type === 'sasjs-persist')
|
|
239
|
+
if (data.type === 'sasjs-persist') {
|
|
198
240
|
writeFiles(data.files).catch((e) =>
|
|
199
241
|
console.error('[sasjs-mock] persist failed', e)
|
|
200
242
|
)
|
|
243
|
+
if (data.identity)
|
|
244
|
+
readIdentity()
|
|
245
|
+
.then((row) =>
|
|
246
|
+
// `undefined`, not null: no stored subject means the fixture's own
|
|
247
|
+
// administrator, which is not the same as the anonymous caller a
|
|
248
|
+
// null would name
|
|
249
|
+
writeIdentity({
|
|
250
|
+
fixture: data.identity,
|
|
251
|
+
subject: row ? row.subject : undefined
|
|
252
|
+
})
|
|
253
|
+
)
|
|
254
|
+
.catch((e) =>
|
|
255
|
+
console.error('[sasjs-mock] identity persist failed', e)
|
|
256
|
+
)
|
|
257
|
+
}
|
|
201
258
|
if (data.type === 'sasjs-host-error')
|
|
202
259
|
console.error('[sasjs-mock] ' + data.path + ': ' + data.message)
|
|
203
260
|
})
|
|
@@ -206,7 +263,52 @@
|
|
|
206
263
|
readAll,
|
|
207
264
|
writeFiles,
|
|
208
265
|
clear: clearAll,
|
|
266
|
+
identity: readIdentity,
|
|
209
267
|
snapshot: () => askWorker({ type: 'sasjs-snapshot' }, 'sasjs-snapshot'),
|
|
268
|
+
/**
|
|
269
|
+
* Views the app as another account: `as('sasjsuser')` for a restricted user,
|
|
270
|
+
* `as()` or `as('admin')` to return to the fixture's administrator.
|
|
271
|
+
*
|
|
272
|
+
* The worker issues a token pair for the account and answers as it from then
|
|
273
|
+
* on; the subject and the fixture are stored here so a reload stays that
|
|
274
|
+
* account, and the app is restarted so every screen redraws its menus and
|
|
275
|
+
* refusals. The pair is returned for a caller who wants to send it as a
|
|
276
|
+
* bearer token - nothing signs it, and no password is checked.
|
|
277
|
+
*/
|
|
278
|
+
as: async (username) => {
|
|
279
|
+
const answer = await askWorker(
|
|
280
|
+
{ type: 'sasjs-as', username },
|
|
281
|
+
'sasjs-as-ack'
|
|
282
|
+
)
|
|
283
|
+
if (answer.error) throw new Error(answer.error)
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
await writeIdentity({
|
|
287
|
+
fixture: answer.identity,
|
|
288
|
+
subject: answer.username
|
|
289
|
+
})
|
|
290
|
+
} catch (e) {
|
|
291
|
+
// the subject still applies to this run; it just will not outlive it
|
|
292
|
+
console.error('[sasjs-mock] could not store the subject: ' + e)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const pair = {
|
|
296
|
+
username: answer.username,
|
|
297
|
+
accessToken: answer.accessToken,
|
|
298
|
+
refreshToken: answer.refreshToken
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// a task, so the caller's promise resolves with the pair first
|
|
302
|
+
setTimeout(() => {
|
|
303
|
+
try {
|
|
304
|
+
window.location.reload()
|
|
305
|
+
} catch (e) {
|
|
306
|
+
/* a page that cannot reload keeps the subject it already has */
|
|
307
|
+
}
|
|
308
|
+
}, 0)
|
|
309
|
+
|
|
310
|
+
return pair
|
|
311
|
+
},
|
|
210
312
|
/**
|
|
211
313
|
* The drive total in bytes, measured by the worker: it owns the stored
|
|
212
314
|
* representation, so the figure stays the one `statSync` reports rather than
|
|
@@ -249,20 +351,62 @@
|
|
|
249
351
|
return Object.keys(files).length
|
|
250
352
|
},
|
|
251
353
|
reset: async () => {
|
|
252
|
-
await askWorker({ type: 'sasjs-reset' }, 'sasjs-reset-ack')
|
|
354
|
+
const answer = await askWorker({ type: 'sasjs-reset' }, 'sasjs-reset-ack')
|
|
355
|
+
if (answer.error) throw new Error(answer.error)
|
|
253
356
|
await clearAll()
|
|
357
|
+
await clearIdentity()
|
|
254
358
|
return true
|
|
255
359
|
}
|
|
256
360
|
}
|
|
257
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Boots the worker with what is stored, and recovers when the stored identity
|
|
364
|
+
* is one this build cannot read.
|
|
365
|
+
*
|
|
366
|
+
* A fixture outlives the build that wrote it, exactly as the drive does, and
|
|
367
|
+
* the worker refuses one whose format stamp it does not speak - by name, and
|
|
368
|
+
* with `errorKind: 'identity'`. Only that answer drops the stored row: the
|
|
369
|
+
* row holds the accounts the visitor created as well as the fixture, so a
|
|
370
|
+
* host-side failure (a drive that could not be read) leaves it alone.
|
|
371
|
+
*/
|
|
372
|
+
const boot = async (files, stored) => {
|
|
373
|
+
const loading = Object.keys(files).length > 0
|
|
374
|
+
const type = loading ? 'sasjs-load' : 'sasjs-seed'
|
|
375
|
+
const ack = loading ? 'sasjs-load-ack' : 'sasjs-seed-ack'
|
|
376
|
+
|
|
377
|
+
const answer = await askWorker(
|
|
378
|
+
{
|
|
379
|
+
type,
|
|
380
|
+
files,
|
|
381
|
+
identity: stored ? stored.fixture : undefined,
|
|
382
|
+
subject: stored ? stored.subject : undefined
|
|
383
|
+
},
|
|
384
|
+
ack
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
if (answer.error && stored && answer.errorKind === 'identity') {
|
|
388
|
+
console.info('[sasjs-mock] ' + answer.error)
|
|
389
|
+
try {
|
|
390
|
+
await clearIdentity()
|
|
391
|
+
} catch (e) {
|
|
392
|
+
console.error('[sasjs-mock] could not clear the stored identity: ' + e)
|
|
393
|
+
}
|
|
394
|
+
return await askWorker({ type, files }, ack)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return answer
|
|
398
|
+
}
|
|
399
|
+
|
|
258
400
|
window.__sasjsMockReady = (async () => {
|
|
259
401
|
await navigator.serviceWorker.ready
|
|
260
402
|
const controller = await waitForController()
|
|
261
403
|
if (!controller) return { error: 'no service worker controller' }
|
|
262
404
|
|
|
263
405
|
let files = {}
|
|
406
|
+
let stored = null
|
|
264
407
|
try {
|
|
265
408
|
files = await readAll()
|
|
409
|
+
stored = await readIdentity()
|
|
266
410
|
} catch (e) {
|
|
267
411
|
console.error('[sasjs-mock] could not read storage: ' + e)
|
|
268
412
|
}
|
|
@@ -287,23 +431,18 @@
|
|
|
287
431
|
)
|
|
288
432
|
try {
|
|
289
433
|
await clearAll()
|
|
434
|
+
// the identity is part of what this build ships, so a visitor who kept
|
|
435
|
+
// the previous build's fixture would never see a changed
|
|
436
|
+
// `mock-identity.json`: it goes with the drive, as it does on reset
|
|
437
|
+
await clearIdentity()
|
|
290
438
|
} catch (e) {
|
|
291
439
|
console.error('[sasjs-mock] could not clear storage: ' + e)
|
|
292
440
|
}
|
|
293
441
|
files = {}
|
|
442
|
+
stored = null
|
|
294
443
|
}
|
|
295
444
|
|
|
296
|
-
|
|
297
|
-
window.__sasjsMockBoot = await askWorker(
|
|
298
|
-
{ type: 'sasjs-load', files },
|
|
299
|
-
'sasjs-load-ack'
|
|
300
|
-
)
|
|
301
|
-
} else {
|
|
302
|
-
window.__sasjsMockBoot = await askWorker(
|
|
303
|
-
{ type: 'sasjs-seed' },
|
|
304
|
-
'sasjs-seed-ack'
|
|
305
|
-
)
|
|
306
|
-
}
|
|
445
|
+
window.__sasjsMockBoot = await boot(files, stored)
|
|
307
446
|
if (window.__sasjsMockBoot && !window.__sasjsMockBoot.error)
|
|
308
447
|
keepBuildStamp(stamp)
|
|
309
448
|
return window.__sasjsMockBoot
|