@lls/offline 24.27.0 → 24.28.0-12ba1b40

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.
@@ -1,3 +1,21 @@
1
+ // src/utils/stream.ts
2
+ var parseRange = (range, size) => {
3
+ const parts = range.replace("bytes=", "").split("-");
4
+ let start = Number.parseInt(parts[0], 10);
5
+ let end = Number.parseInt(parts[1], 10);
6
+ if (isNaN(start)) {
7
+ start = size - end;
8
+ end = size - 1;
9
+ } else if (isNaN(end)) {
10
+ end = size - 1;
11
+ }
12
+ if (end > size - 1) {
13
+ end = size - 1;
14
+ }
15
+ const chunkSize = end - start + 1;
16
+ return { start, end, chunkSize };
17
+ };
18
+
1
19
  // src/utils/worker.ts
2
20
  var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
3
21
  const dic = {};
@@ -20,7 +38,18 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
20
38
  return ({ ports, ...arg } = {}) => {
21
39
  return new Promise((resolve, reject) => {
22
40
  const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
23
- dic[eventId] = { resolve, reject };
41
+ let resolved = false;
42
+ const timeout = setTimeout(() => {
43
+ console.warn("slow request for", methodName, arg);
44
+ }, 3e3);
45
+ const doResolve = (arg2) => {
46
+ if (!resolved) {
47
+ resolved = true;
48
+ clearTimeout(timeout);
49
+ resolve(arg2);
50
+ }
51
+ };
52
+ dic[eventId] = { resolve: doResolve, reject };
24
53
  offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
25
54
  });
26
55
  };
@@ -77,7 +106,7 @@ var cocoreFetchPartialPageScreen = async (_ev, { offlineWorkerSync: offlineWorke
77
106
  const key = `gqls/${zipSlugName(variables.slugs[0] || "")}.json`;
78
107
  const entryId = knownLinks2.get(key);
79
108
  if (!entryId) {
80
- throw new Error(`slug not found ${key}`);
109
+ throw new Error(`slug not found ${key}, links size: ${knownLinks2.size}`);
81
110
  }
82
111
  const gqlPage = await offlineWorkerSync2.loadPageContent({ entryId });
83
112
  return { data: gqlPage };
@@ -202,22 +231,90 @@ var coreFetchPagesApi = async (ev, { offlineWorkerSync: offlineWorkerSync2, vari
202
231
  }
203
232
  }
204
233
  };
234
+ var cocoreFetchPrimaryMethod = async (_ev, { offlineWorkerSync: offlineWorkerSync2, variables }) => {
235
+ const uri = variables.uris?.[0];
236
+ if (!uri) {
237
+ throw new Error("invalid uri");
238
+ }
239
+ const method = await offlineWorkerSync2.readZipEntry({ bookId: uri, entryName: "gqls/method.json", asJson: true });
240
+ return {
241
+ data: method
242
+ };
243
+ };
244
+ var cocoreCreateLexicalFlower = async (_ev, { variables }) => {
245
+ const flower = variables.flower;
246
+ if (!flower) {
247
+ throw new Error("invalid flower");
248
+ }
249
+ const back = {
250
+ viewer: {
251
+ createLexicalFlower: {
252
+ ...flower,
253
+ id: Date.now(),
254
+ user: null,
255
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
256
+ }
257
+ }
258
+ };
259
+ return {
260
+ data: back
261
+ };
262
+ };
263
+ var cocoreUpdateLexicalFlower = async (_ev, { variables }) => {
264
+ const flower = variables.flower;
265
+ if (!flower) {
266
+ throw new Error("invalid flower");
267
+ }
268
+ const back = {
269
+ viewer: {
270
+ updateLexicalFlower: {
271
+ ...flower,
272
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
273
+ }
274
+ }
275
+ };
276
+ return {
277
+ data: back
278
+ };
279
+ };
280
+ var cocoreDeleteLexicalFlower = async (_ev, { variables }) => {
281
+ const flower = variables.flower;
282
+ if (!flower) {
283
+ throw new Error("invalid flower");
284
+ }
285
+ const back = {
286
+ viewer: {
287
+ deleteLexicalFlower: flower
288
+ }
289
+ };
290
+ return {
291
+ data: back
292
+ };
293
+ };
205
294
  var handles = {
206
295
  coreFetchBookDrawerScreen,
207
296
  cocoreFetchBookScreen,
208
297
  coreFetchPagesApi,
209
298
  cocoreFetchPartialPageScreen,
210
- cocoreFetchPartialPageSiblingsScreen
299
+ cocoreFetchPartialPageSiblingsScreen,
300
+ cocoreFetchPrimaryMethod,
301
+ cocoreCreateLexicalFlower,
302
+ cocoreUpdateLexicalFlower,
303
+ cocoreDeleteLexicalFlower
211
304
  };
212
305
  var canHandleScreenCall = (url) => new RegExp(Object.keys(handles).join("|")).test(url);
213
306
  var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, knownLinks: knownLinks2 }) => {
214
307
  try {
215
- const variables = (
216
- // @ts-expect-error TODO: handle variables from body(stream) properly!!
217
- ev.request.body?.variables || JSON.parse(new URLSearchParams(new URL(ev.request.url).search).get("variables") || "{}")
218
- );
219
- console.debug("variables are", variables, "vs", ev.request.body);
308
+ let variables;
309
+ const searchParamsVariables = new URLSearchParams(new URL(ev.request.url).search).get("variables");
310
+ if (searchParamsVariables) {
311
+ variables = JSON.parse(searchParamsVariables);
312
+ } else {
313
+ const body = await new Response(ev.request.body).json();
314
+ variables = body.variables;
315
+ }
220
316
  const key = ev.request.url.match(Object.keys(handles).join("|"))?.[0];
317
+ console.debug("screencall", key, "qs", variables, "vs body", ev.request.body);
221
318
  if (!key) {
222
319
  throw new Error("unknown ev");
223
320
  }
@@ -238,17 +335,39 @@ var setKnownLinks = (knownLinks2, { entryNames, bookId }) => {
238
335
  // src/worker/fetch-worker.ts
239
336
  var STORAGE = "cache";
240
337
  var CONF_ENV = "development";
241
- var fetchAsset = async (_ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
338
+ var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
242
339
  try {
243
- const buf = await offlineWorkerSync2.readZipEntry(entryId);
340
+ const [buf, maybeRangeHeaders] = await offlineWorkerSync2.readZipEntry(entryId).then((buf2) => {
341
+ const range = ev.request.headers.get("Range");
342
+ if (!ev.request.url.endsWith(".mp4") && !ev.request.url.endsWith(".webm")) {
343
+ return [buf2, {}];
344
+ }
345
+ if (!range) {
346
+ return [
347
+ buf2,
348
+ {
349
+ "Accept-Ranges": "bytes",
350
+ "Content-Range": `bytes ${0}-${buf2.byteLength}/${buf2.byteLength}`
351
+ }
352
+ ];
353
+ }
354
+ const { start, end, chunkSize } = parseRange(range, buf2.byteLength);
355
+ const rangeHeaders = {
356
+ "Content-Length": chunkSize,
357
+ "Accept-Ranges": "bytes",
358
+ "Content-Range": `bytes ${start}-${end}/${buf2.byteLength}`
359
+ };
360
+ return [buf2.slice(start, end + 1), rangeHeaders];
361
+ });
244
362
  return new Response(buf, {
245
363
  headers: {
246
364
  "Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
247
365
  "Content-Length": buf.byteLength,
248
- "Content-Type": mimeType
366
+ "Content-Type": mimeType,
367
+ ...maybeRangeHeaders
249
368
  },
250
369
  // @ts-ignore: poor ts typing
251
- status: 200,
370
+ status: maybeRangeHeaders["Content-Range"] ? 206 : 200,
252
371
  statusText: "OK"
253
372
  });
254
373
  } catch (e) {
@@ -305,23 +424,30 @@ var putInCache = async (request, response) => {
305
424
  const cache = await openCache(CONF_ENV);
306
425
  await cache.put(request, response);
307
426
  };
427
+ var tryOnline = async (request, event) => {
428
+ const responseFromNetwork = await fetch(request);
429
+ if (isValidZipName(request.url) && !isValidS3Zip(responseFromNetwork)) {
430
+ throw new Error(`zip is invalid: got ${responseFromNetwork.headers.get("Content-Length")} bytes`);
431
+ }
432
+ const cached = request.method === "GET" || request.method === "OPTIONS" ? putInCache(request, responseFromNetwork.clone()) : Promise.resolve();
433
+ if (request.url.endsWith(".zip")) {
434
+ await cached;
435
+ event.waitUntil(Promise.resolve());
436
+ } else {
437
+ event.waitUntil(cached);
438
+ }
439
+ return responseFromNetwork;
440
+ };
441
+ var tryOffline = async (request, _event) => {
442
+ const over = /\/\?story=basic/.test(request.url) ? request.url.replace(/\/\?story=.*$/, "") : request;
443
+ const responseFromCache = await caches.match(over);
444
+ return responseFromCache;
445
+ };
308
446
  var onlineFirst = async (request, event) => {
309
447
  try {
310
- const responseFromNetwork = await fetch(request);
311
- if (isValidZipName(request.url) && !isValidS3Zip(responseFromNetwork)) {
312
- throw new Error(`zip is invalid: got ${responseFromNetwork.headers.get("Content-Length")} bytes`);
313
- }
314
- const cached = request.method === "HEAD" ? Promise.resolve() : putInCache(request, responseFromNetwork.clone());
315
- if (request.url.endsWith(".zip")) {
316
- await cached;
317
- event.waitUntil(Promise.resolve());
318
- } else {
319
- event.waitUntil(cached);
320
- }
321
- return responseFromNetwork;
448
+ return await tryOnline(request, event);
322
449
  } catch (e) {
323
- const over = /\/\?story=basic/.test(request.url) ? request.url.replace(/\/\?story=.*$/, "") : request;
324
- const responseFromCache = await caches.match(over);
450
+ const responseFromCache = await tryOffline(request, event);
325
451
  if (!responseFromCache) {
326
452
  return new Response(
327
453
  `FAILED fetch ${e && typeof e === "object" && "message" in e ? e.message : e?.toString?.()}`,
@@ -343,18 +469,23 @@ swSelf.addEventListener("fetch", (ev) => {
343
469
  const extension = url.match(/\.[^.]+$/)?.[0];
344
470
  const mimeType = extension && extension in ACCEPTED_EXTENSIONS ? ACCEPTED_EXTENSIONS[extension] : "";
345
471
  const isValidScreenCall = canHandleScreenCall(url);
346
- if (STORAGE !== "cache" && url.endsWith(".zip")) {
472
+ if (url.endsWith(".zip")) {
473
+ if (STORAGE === "cache") {
474
+ return ev.respondWith(onlineFirst(ev.request, ev));
475
+ }
347
476
  return;
348
477
  }
349
478
  if (!mimeType && !isValidScreenCall) {
350
- if (!/\.(css|tsx?|jsx?|woff2|fr|manifest)(\??|$)|@(vite|react|id)/.test(url)) {
479
+ if (!/\.(css|tsx?|[mj]sx?|woff2|wasm|fr|(web)?manifest)(\??|$)|@(vite|react|id)|(:6019\/?$)/.test(url)) {
351
480
  console.debug("no ext", extension || url);
352
481
  }
353
482
  return ev.respondWith(onlineFirst(ev.request, ev));
354
483
  }
484
+ if (/http:\/\/localhost:\d+\/(favicon\.svg|assets\/)/.test(url)) {
485
+ return;
486
+ }
355
487
  const entryId = knownLinks.get(url.replace(/https:\/\/.*?\//, "")) || knownLinks.get(`medias/${url.replace(/https:\/\/.*?\//, "")}`);
356
488
  if (isValidScreenCall) {
357
- console.debug("screencall!");
358
489
  return ev.respondWith(fetchScreenCall(ev, { offlineWorkerSync, knownLinks }));
359
490
  }
360
491
  if (entryId) {
@@ -363,6 +494,7 @@ swSelf.addEventListener("fetch", (ev) => {
363
494
  if (/lls|lelivrescolaire/.test(url)) {
364
495
  console.info("no link for", url.replace(/https:\/\/.*?\//, ""), `medias/${url.replace(/https:\/\/.*?\//, "")}`);
365
496
  }
497
+ console.debug("unintercepted url", url);
366
498
  return;
367
499
  });
368
500
  swSelf.addEventListener("install", (_event) => {
@@ -383,7 +515,10 @@ var initChannel = async (__, { ports }) => {
383
515
  );
384
516
  offlineWorkerSync = sender;
385
517
  offlineWorker.onmessage = handler.handle;
386
- offlineWorkerSync.on("setKnownLinks", setKnownLinks.bind(null, knownLinks));
518
+ offlineWorkerSync.on("setKnownLinks", ({ entryNames, bookId, eventId }) => {
519
+ setKnownLinks(knownLinks, { entryNames, bookId });
520
+ offlineWorker.postMessage({ methodName: "setKnownLinks:back", eventId });
521
+ });
387
522
  offlineWorker.postMessage({ methodName: "sendAppReady" });
388
523
  };
389
524
  var knownLinks = /* @__PURE__ */ new Map();
@@ -31,7 +31,18 @@ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
31
31
  return ({ ports, ...arg } = {}) => {
32
32
  return new Promise((resolve, reject) => {
33
33
  const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
34
- dic[eventId] = { resolve, reject };
34
+ let resolved = false;
35
+ const timeout = setTimeout(() => {
36
+ console.warn("slow request for", methodName, arg);
37
+ }, 3e3);
38
+ const doResolve = (arg2) => {
39
+ if (!resolved) {
40
+ resolved = true;
41
+ clearTimeout(timeout);
42
+ resolve(arg2);
43
+ }
44
+ };
45
+ dic[eventId] = { resolve: doResolve, reject };
35
46
  offlineWorker.postMessage({ eventId, methodName, arg }, ports);
36
47
  });
37
48
  };
@@ -84,16 +95,6 @@ var combineHandlers = (...handlers) => {
84
95
  };
85
96
  };
86
97
  var openCache = async (mode) => caches.open(`LLS_OFFLINE_${mode}`);
87
- var concatArrayBuffers = (buffers) => {
88
- const totalLength = buffers.reduce((sum, buf) => sum + buf.byteLength, 0);
89
- const concatenated = new Uint8Array(totalLength);
90
- let offset = 0;
91
- for (const buffer of buffers) {
92
- concatenated.set(new Uint8Array(buffer), offset);
93
- offset += buffer.byteLength;
94
- }
95
- return concatenated.buffer;
96
- };
97
98
  var isStoryZip = (name) => /story-.*?\.zip/.test(name);
98
99
  var isValidZipName = (name) => {
99
100
  return /lecture-cp\.zip/.test(name) || isStoryZip(name);
@@ -170,615 +171,72 @@ var loadPreload = (json) => {
170
171
  });
171
172
  };
172
173
 
173
- // ../../node_modules/.pnpm/fflate@0.8.2/node_modules/fflate/esm/browser.js
174
- var u8 = Uint8Array;
175
- var u16 = Uint16Array;
176
- var i32 = Int32Array;
177
- var fleb = new u8([
178
- 0,
179
- 0,
180
- 0,
181
- 0,
182
- 0,
183
- 0,
184
- 0,
185
- 0,
186
- 1,
187
- 1,
188
- 1,
189
- 1,
190
- 2,
191
- 2,
192
- 2,
193
- 2,
194
- 3,
195
- 3,
196
- 3,
197
- 3,
198
- 4,
199
- 4,
200
- 4,
201
- 4,
202
- 5,
203
- 5,
204
- 5,
205
- 5,
206
- 0,
207
- /* unused */
208
- 0,
209
- 0,
210
- /* impossible */
211
- 0
212
- ]);
213
- var fdeb = new u8([
214
- 0,
215
- 0,
216
- 0,
217
- 0,
218
- 1,
219
- 1,
220
- 2,
221
- 2,
222
- 3,
223
- 3,
224
- 4,
225
- 4,
226
- 5,
227
- 5,
228
- 6,
229
- 6,
230
- 7,
231
- 7,
232
- 8,
233
- 8,
234
- 9,
235
- 9,
236
- 10,
237
- 10,
238
- 11,
239
- 11,
240
- 12,
241
- 12,
242
- 13,
243
- 13,
244
- /* unused */
245
- 0,
246
- 0
247
- ]);
248
- var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
249
- var freb = function(eb, start) {
250
- var b = new u16(31);
251
- for (var i = 0; i < 31; ++i) {
252
- b[i] = start += 1 << eb[i - 1];
253
- }
254
- var r = new i32(b[30]);
255
- for (var i = 1; i < 30; ++i) {
256
- for (var j = b[i]; j < b[i + 1]; ++j) {
257
- r[j] = j - b[i] << 5 | i;
258
- }
259
- }
260
- return { b, r };
261
- };
262
- var _a = freb(fleb, 2);
263
- var fl = _a.b;
264
- var revfl = _a.r;
265
- fl[28] = 258, revfl[258] = 28;
266
- var _b = freb(fdeb, 0);
267
- var fd = _b.b;
268
- var revfd = _b.r;
269
- var rev = new u16(32768);
270
- for (i = 0; i < 32768; ++i) {
271
- x = (i & 43690) >> 1 | (i & 21845) << 1;
272
- x = (x & 52428) >> 2 | (x & 13107) << 2;
273
- x = (x & 61680) >> 4 | (x & 3855) << 4;
274
- rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
275
- }
276
- var x;
277
- var i;
278
- var hMap = function(cd, mb, r) {
279
- var s = cd.length;
280
- var i = 0;
281
- var l = new u16(mb);
282
- for (; i < s; ++i) {
283
- if (cd[i])
284
- ++l[cd[i] - 1];
285
- }
286
- var le = new u16(mb);
287
- for (i = 1; i < mb; ++i) {
288
- le[i] = le[i - 1] + l[i - 1] << 1;
289
- }
290
- var co;
291
- if (r) {
292
- co = new u16(1 << mb);
293
- var rvb = 15 - mb;
294
- for (i = 0; i < s; ++i) {
295
- if (cd[i]) {
296
- var sv = i << 4 | cd[i];
297
- var r_1 = mb - cd[i];
298
- var v = le[cd[i] - 1]++ << r_1;
299
- for (var m = v | (1 << r_1) - 1; v <= m; ++v) {
300
- co[rev[v] >> rvb] = sv;
301
- }
302
- }
303
- }
304
- } else {
305
- co = new u16(s);
306
- for (i = 0; i < s; ++i) {
307
- if (cd[i]) {
308
- co[i] = rev[le[cd[i] - 1]++] >> 15 - cd[i];
309
- }
310
- }
311
- }
312
- return co;
313
- };
314
- var flt = new u8(288);
315
- for (i = 0; i < 144; ++i)
316
- flt[i] = 8;
317
- var i;
318
- for (i = 144; i < 256; ++i)
319
- flt[i] = 9;
320
- var i;
321
- for (i = 256; i < 280; ++i)
322
- flt[i] = 7;
323
- var i;
324
- for (i = 280; i < 288; ++i)
325
- flt[i] = 8;
326
- var i;
327
- var fdt = new u8(32);
328
- for (i = 0; i < 32; ++i)
329
- fdt[i] = 5;
330
- var i;
331
- var flrm = /* @__PURE__ */ hMap(flt, 9, 1);
332
- var fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
333
- var max = function(a2) {
334
- var m = a2[0];
335
- for (var i = 1; i < a2.length; ++i) {
336
- if (a2[i] > m)
337
- m = a2[i];
338
- }
339
- return m;
340
- };
341
- var bits = function(d, p, m) {
342
- var o = p / 8 | 0;
343
- return (d[o] | d[o + 1] << 8) >> (p & 7) & m;
344
- };
345
- var bits16 = function(d, p) {
346
- var o = p / 8 | 0;
347
- return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7);
348
- };
349
- var shft = function(p) {
350
- return (p + 7) / 8 | 0;
351
- };
352
- var slc = function(v, s, e) {
353
- if (s == null || s < 0)
354
- s = 0;
355
- if (e == null || e > v.length)
356
- e = v.length;
357
- return new u8(v.subarray(s, e));
358
- };
359
- var ec = [
360
- "unexpected EOF",
361
- "invalid block type",
362
- "invalid length/literal",
363
- "invalid distance",
364
- "stream finished",
365
- "no stream handler",
366
- ,
367
- "no callback",
368
- "invalid UTF-8 data",
369
- "extra field too long",
370
- "date not in range 1980-2099",
371
- "filename too long",
372
- "stream finishing",
373
- "invalid zip data"
374
- // determined by unknown compression method
375
- ];
376
- var err = function(ind, msg, nt) {
377
- var e = new Error(msg || ec[ind]);
378
- e.code = ind;
379
- if (Error.captureStackTrace)
380
- Error.captureStackTrace(e, err);
381
- if (!nt)
382
- throw e;
383
- return e;
384
- };
385
- var inflt = function(dat, st, buf, dict) {
386
- var sl = dat.length, dl = dict ? dict.length : 0;
387
- if (!sl || st.f && !st.l)
388
- return buf || new u8(0);
389
- var noBuf = !buf;
390
- var resize = noBuf || st.i != 2;
391
- var noSt = st.i;
392
- if (noBuf)
393
- buf = new u8(sl * 3);
394
- var cbuf = function(l2) {
395
- var bl = buf.length;
396
- if (l2 > bl) {
397
- var nbuf = new u8(Math.max(bl * 2, l2));
398
- nbuf.set(buf);
399
- buf = nbuf;
400
- }
401
- };
402
- var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;
403
- var tbts = sl * 8;
404
- do {
405
- if (!lm) {
406
- final = bits(dat, pos, 1);
407
- var type = bits(dat, pos + 1, 3);
408
- pos += 3;
409
- if (!type) {
410
- var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
411
- if (t > sl) {
412
- if (noSt)
413
- err(0);
414
- break;
415
- }
416
- if (resize)
417
- cbuf(bt + l);
418
- buf.set(dat.subarray(s, t), bt);
419
- st.b = bt += l, st.p = pos = t * 8, st.f = final;
420
- continue;
421
- } else if (type == 1)
422
- lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
423
- else if (type == 2) {
424
- var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
425
- var tl = hLit + bits(dat, pos + 5, 31) + 1;
426
- pos += 14;
427
- var ldt = new u8(tl);
428
- var clt = new u8(19);
429
- for (var i = 0; i < hcLen; ++i) {
430
- clt[clim[i]] = bits(dat, pos + i * 3, 7);
431
- }
432
- pos += hcLen * 3;
433
- var clb = max(clt), clbmsk = (1 << clb) - 1;
434
- var clm = hMap(clt, clb, 1);
435
- for (var i = 0; i < tl; ) {
436
- var r = clm[bits(dat, pos, clbmsk)];
437
- pos += r & 15;
438
- var s = r >> 4;
439
- if (s < 16) {
440
- ldt[i++] = s;
441
- } else {
442
- var c = 0, n = 0;
443
- if (s == 16)
444
- n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];
445
- else if (s == 17)
446
- n = 3 + bits(dat, pos, 7), pos += 3;
447
- else if (s == 18)
448
- n = 11 + bits(dat, pos, 127), pos += 7;
449
- while (n--)
450
- ldt[i++] = c;
174
+ // src/utils/zipDecoder.ts
175
+ var getEOCDStart = (buf) => {
176
+ for (let i = buf.length - 1; i > 0; --i) {
177
+ if (buf[i] === 6) {
178
+ if (buf[i - 1] === 5) {
179
+ if (buf[i - 2] === 75) {
180
+ if (buf[i - 3] === 80) {
181
+ return i - 3;
451
182
  }
452
183
  }
453
- var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
454
- lbt = max(lt);
455
- dbt = max(dt);
456
- lm = hMap(lt, lbt, 1);
457
- dm = hMap(dt, dbt, 1);
458
- } else
459
- err(1);
460
- if (pos > tbts) {
461
- if (noSt)
462
- err(0);
463
- break;
464
184
  }
465
185
  }
466
- if (resize)
467
- cbuf(bt + 131072);
468
- var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
469
- var lpos = pos;
470
- for (; ; lpos = pos) {
471
- var c = lm[bits16(dat, pos) & lms], sym = c >> 4;
472
- pos += c & 15;
473
- if (pos > tbts) {
474
- if (noSt)
475
- err(0);
476
- break;
477
- }
478
- if (!c)
479
- err(2);
480
- if (sym < 256)
481
- buf[bt++] = sym;
482
- else if (sym == 256) {
483
- lpos = pos, lm = null;
484
- break;
485
- } else {
486
- var add = sym - 254;
487
- if (sym > 264) {
488
- var i = sym - 257, b = fleb[i];
489
- add = bits(dat, pos, (1 << b) - 1) + fl[i];
490
- pos += b;
491
- }
492
- var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
493
- if (!d)
494
- err(3);
495
- pos += d & 15;
496
- var dt = fd[dsym];
497
- if (dsym > 3) {
498
- var b = fdeb[dsym];
499
- dt += bits16(dat, pos) & (1 << b) - 1, pos += b;
500
- }
501
- if (pos > tbts) {
502
- if (noSt)
503
- err(0);
504
- break;
505
- }
506
- if (resize)
507
- cbuf(bt + 131072);
508
- var end = bt + add;
509
- if (bt < dt) {
510
- var shift = dl - dt, dend = Math.min(dt, end);
511
- if (shift + bt < 0)
512
- err(3);
513
- for (; bt < dend; ++bt)
514
- buf[bt] = dict[shift + bt];
515
- }
516
- for (; bt < end; ++bt)
517
- buf[bt] = buf[bt - dt];
518
- }
519
- }
520
- st.l = lm, st.p = lpos, st.b = bt, st.f = final;
521
- if (lm)
522
- final = 1, st.m = lbt, st.d = dm, st.n = dbt;
523
- } while (!final);
524
- return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
525
- };
526
- var et = /* @__PURE__ */ new u8(0);
527
- var b2 = function(d, b) {
528
- return d[b] | d[b + 1] << 8;
529
- };
530
- var b4 = function(d, b) {
531
- return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24) >>> 0;
532
- };
533
- var b8 = function(d, b) {
534
- return b4(d, b) + b4(d, b + 4) * 4294967296;
535
- };
536
- var Inflate = /* @__PURE__ */ function() {
537
- function Inflate2(opts, cb) {
538
- if (typeof opts == "function")
539
- cb = opts, opts = {};
540
- this.ondata = cb;
541
- var dict = opts && opts.dictionary && opts.dictionary.subarray(-32768);
542
- this.s = { i: 0, b: dict ? dict.length : 0 };
543
- this.o = new u8(32768);
544
- this.p = new u8(0);
545
- if (dict)
546
- this.o.set(dict);
547
186
  }
548
- Inflate2.prototype.e = function(c) {
549
- if (!this.ondata)
550
- err(5);
551
- if (this.d)
552
- err(4);
553
- if (!this.p.length)
554
- this.p = c;
555
- else if (c.length) {
556
- var n = new u8(this.p.length + c.length);
557
- n.set(this.p), n.set(c, this.p.length), this.p = n;
558
- }
559
- };
560
- Inflate2.prototype.c = function(final) {
561
- this.s.i = +(this.d = final || false);
562
- var bts = this.s.b;
563
- var dt = inflt(this.p, this.s, this.o);
564
- this.ondata(slc(dt, bts, this.s.b), this.d);
565
- this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length;
566
- this.p = slc(this.p, this.s.p / 8 | 0), this.s.p &= 7;
567
- };
568
- Inflate2.prototype.push = function(chunk, final) {
569
- this.e(chunk), this.c(final);
570
- };
571
- return Inflate2;
572
- }();
573
- var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder();
574
- var tds = 0;
575
- try {
576
- td.decode(et, { stream: true });
577
- tds = 1;
578
- } catch (e) {
579
- }
580
- var dutf8 = function(d) {
581
- for (var r = "", i = 0; ; ) {
582
- var c = d[i++];
583
- var eb = (c > 127) + (c > 223) + (c > 239);
584
- if (i + eb > d.length)
585
- return { s: r, r: slc(d, i - 1) };
586
- if (!eb)
587
- r += String.fromCharCode(c);
588
- else if (eb == 3) {
589
- c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | d[i++] & 63) - 65536, r += String.fromCharCode(55296 | c >> 10, 56320 | c & 1023);
590
- } else if (eb & 1)
591
- r += String.fromCharCode((c & 31) << 6 | d[i++] & 63);
592
- else
593
- r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | d[i++] & 63);
594
- }
595
- };
596
- function strFromU8(dat, latin1) {
597
- if (latin1) {
598
- var r = "";
599
- for (var i = 0; i < dat.length; i += 16384)
600
- r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384));
601
- return r;
602
- } else if (td) {
603
- return td.decode(dat);
604
- } else {
605
- var _a2 = dutf8(dat), s = _a2.s, r = _a2.r;
606
- if (r.length)
607
- err(8);
608
- return s;
609
- }
610
- }
611
- var z64e = function(d, b) {
612
- for (; b2(d, b) != 1; b += 4 + b2(d, b + 2))
613
- ;
614
- return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)];
187
+ throw new Error("failed to read EOCDstart");
615
188
  };
616
- var UnzipPassThrough = /* @__PURE__ */ function() {
617
- function UnzipPassThrough2() {
618
- }
619
- UnzipPassThrough2.prototype.push = function(data, final) {
620
- this.ondata(null, data, final);
621
- };
622
- UnzipPassThrough2.compression = 0;
623
- return UnzipPassThrough2;
624
- }();
625
- var UnzipInflate = /* @__PURE__ */ function() {
626
- function UnzipInflate2() {
627
- var _this = this;
628
- this.i = new Inflate(function(dat, final) {
629
- _this.ondata(null, dat, final);
630
- });
189
+ var readIndex = (buf, offset, nBytes) => {
190
+ let idx = 0;
191
+ for (let i = 0; i < nBytes; ++i) {
192
+ const next = buf[offset + i] * 256 ** i;
193
+ idx += next;
631
194
  }
632
- UnzipInflate2.prototype.push = function(data, final) {
633
- try {
634
- this.i.push(data, final);
635
- } catch (e) {
636
- this.ondata(e, null, final);
637
- }
638
- };
639
- UnzipInflate2.compression = 8;
640
- return UnzipInflate2;
641
- }();
642
- var Unzip = /* @__PURE__ */ function() {
643
- function Unzip2(cb) {
644
- this.onfile = cb;
645
- this.k = [];
646
- this.o = {
647
- 0: UnzipPassThrough
195
+ return idx;
196
+ };
197
+ var getSOCDStart = (buf, eocdIdx) => {
198
+ return readIndex(buf, eocdIdx + 16, 4);
199
+ };
200
+ var readEntryNames = (buf, socdIdx, nRecords) => {
201
+ const records = Array.from({ length: nRecords });
202
+ for (let i = 0; i < nRecords; ++i) {
203
+ const n = readIndex(buf, socdIdx + 28, 2);
204
+ const m = readIndex(buf, socdIdx + 30, 2);
205
+ const k = readIndex(buf, socdIdx + 32, 2);
206
+ records[i] = {
207
+ sofh: readIndex(buf, socdIdx + 42, 4),
208
+ name: buf.slice(socdIdx + 46, socdIdx + 46 + n)
648
209
  };
649
- this.p = et;
210
+ socdIdx += 46 + n + m + k;
650
211
  }
651
- Unzip2.prototype.push = function(chunk, final) {
652
- var _this = this;
653
- if (!this.onfile)
654
- err(5);
655
- if (!this.p)
656
- err(4);
657
- if (this.c > 0) {
658
- var len = Math.min(this.c, chunk.length);
659
- var toAdd = chunk.subarray(0, len);
660
- this.c -= len;
661
- if (this.d)
662
- this.d.push(toAdd, !this.c);
663
- else
664
- this.k[0].push(toAdd);
665
- chunk = chunk.subarray(len);
666
- if (chunk.length)
667
- return this.push(chunk, final);
668
- } else {
669
- var f = 0, i = 0, is = void 0, buf = void 0;
670
- if (!this.p.length)
671
- buf = chunk;
672
- else if (!chunk.length)
673
- buf = this.p;
674
- else {
675
- buf = new u8(this.p.length + chunk.length);
676
- buf.set(this.p), buf.set(chunk, this.p.length);
677
- }
678
- var l = buf.length, oc = this.c, add = oc && this.d;
679
- var _loop_2 = function() {
680
- var _a2;
681
- var sig = b4(buf, i);
682
- if (sig == 67324752) {
683
- f = 1, is = i;
684
- this_1.d = null;
685
- this_1.c = 0;
686
- var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28);
687
- if (l > i + 30 + fnl + es) {
688
- var chks_3 = [];
689
- this_1.k.unshift(chks_3);
690
- f = 2;
691
- var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22);
692
- var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u);
693
- if (sc_1 == 4294967295) {
694
- _a2 = dd ? [-2] : z64e(buf, i), sc_1 = _a2[0], su_1 = _a2[1];
695
- } else if (dd)
696
- sc_1 = -1;
697
- i += es;
698
- this_1.c = sc_1;
699
- var d_1;
700
- var file_1 = {
701
- name: fn_1,
702
- compression: cmp_1,
703
- start: function() {
704
- if (!file_1.ondata)
705
- err(5);
706
- if (!sc_1)
707
- file_1.ondata(null, et, true);
708
- else {
709
- var ctr = _this.o[cmp_1];
710
- if (!ctr)
711
- file_1.ondata(err(14, "unknown compression type " + cmp_1, 1), null, false);
712
- d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1);
713
- d_1.ondata = function(err2, dat3, final2) {
714
- file_1.ondata(err2, dat3, final2);
715
- };
716
- for (var _i = 0, chks_4 = chks_3; _i < chks_4.length; _i++) {
717
- var dat2 = chks_4[_i];
718
- d_1.push(dat2, false);
719
- }
720
- if (_this.k[0] == chks_3 && _this.c)
721
- _this.d = d_1;
722
- else
723
- d_1.push(et, true);
724
- }
725
- },
726
- terminate: function() {
727
- if (d_1 && d_1.terminate)
728
- d_1.terminate();
729
- }
730
- };
731
- if (sc_1 >= 0)
732
- file_1.size = sc_1, file_1.originalSize = su_1;
733
- this_1.onfile(file_1);
734
- }
735
- return "break";
736
- } else if (oc) {
737
- if (sig == 134695760) {
738
- is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0;
739
- return "break";
740
- } else if (sig == 33639248) {
741
- is = i -= 4, f = 3, this_1.c = 0;
742
- return "break";
743
- }
744
- }
745
- };
746
- var this_1 = this;
747
- for (; i < l - 4; ++i) {
748
- var state_1 = _loop_2();
749
- if (state_1 === "break")
750
- break;
751
- }
752
- this.p = et;
753
- if (oc < 0) {
754
- var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 134695760 && 4)) : buf.subarray(0, i);
755
- if (add)
756
- add.push(dat, !!f);
757
- else
758
- this.k[+(f == 2)].push(dat);
759
- }
760
- if (f & 2)
761
- return this.push(buf.subarray(i), final);
762
- this.p = buf.subarray(i);
763
- }
764
- if (final) {
765
- if (this.c)
766
- err(13);
767
- this.p = null;
768
- }
769
- };
770
- Unzip2.prototype.register = function(decoder) {
771
- this.o[decoder.compression] = decoder;
772
- };
773
- return Unzip2;
774
- }();
212
+ return records;
213
+ };
214
+ var readRecordData = (buf, { sofh }) => {
215
+ const dataSize = readIndex(buf, sofh + 22, 4);
216
+ const n = readIndex(buf, sofh + 26, 2);
217
+ const m = readIndex(buf, sofh + 28, 2);
218
+ return buf.slice(sofh + 30 + n + m, sofh + 30 + n + m + dataSize);
219
+ };
220
+ var getHeaders = (buf) => {
221
+ const eocdIdx = getEOCDStart(buf);
222
+ const nbRecords = readIndex(buf, eocdIdx + 10, 2);
223
+ const socdIdx = getSOCDStart(buf, eocdIdx);
224
+ const records = readEntryNames(buf, socdIdx, nbRecords);
225
+ return records;
226
+ };
775
227
 
776
228
  // src/worker/wwZip.ts
777
229
  var key = new Uint8Array([..."4c6520636f6e74656e7520646573206c".matchAll(/../g)].map(([c]) => Number(`0x${c}`)));
778
230
  var iv = new Uint8Array(16).fill(0);
779
231
  async function decryptData(encryptedData, key2, iv2) {
780
- const cryptoKey = await crypto.subtle.importKey("raw", key2.buffer, { name: "AES-CBC" }, false, ["decrypt"]);
781
- const decryptedData = await crypto.subtle.decrypt({ name: "AES-CBC", iv: iv2.buffer }, cryptoKey, encryptedData);
232
+ const cryptoKey = await crypto.subtle.importKey("raw", key2.buffer, { name: "AES-CBC" }, false, [
233
+ "decrypt"
234
+ ]);
235
+ const decryptedData = await crypto.subtle.decrypt(
236
+ { name: "AES-CBC", iv: iv2.buffer },
237
+ cryptoKey,
238
+ encryptedData
239
+ );
782
240
  return decryptedData;
783
241
  }
784
242
  var BOOKS = {};
@@ -802,12 +260,14 @@ var readZipEntry = async ({
802
260
  const originalName = manifest.medias[zipName];
803
261
  overridenName = `medias/${originalName}`;
804
262
  } else {
805
- console.error("any media should exist in manifest", zipName, entryName, bookId);
263
+ if (entryName !== "manifest.json") {
264
+ console.error("any media should exist in manifest", zipName, entryName, bookId);
265
+ }
806
266
  }
807
267
  }
808
268
  const bookEntry = BOOKS[bookId].map.get(overridenName);
809
269
  if (!bookEntry) {
810
- console.info("no bookEntry", entryName, "found", overridenName);
270
+ console.info("no bookEntry", entryName, "overridingName:", overridenName, "for book", bookId);
811
271
  return null;
812
272
  }
813
273
  if (!bookEntry.decoded) {
@@ -820,43 +280,19 @@ var hasZip = (bookId) => {
820
280
  };
821
281
  var loadZip = async (buf, bookId) => {
822
282
  const m = /* @__PURE__ */ new Map();
823
- const unzipper = new Unzip();
824
- unzipper.register(UnzipInflate);
825
- unzipper.onfile = (file) => {
826
- if (!file.name.includes(".")) {
827
- return;
283
+ const headers = getHeaders(buf);
284
+ for (const header of headers) {
285
+ const name = new TextDecoder().decode(header.name);
286
+ if (!name.includes(".")) {
287
+ continue;
828
288
  }
829
- m.set(file.name, {
289
+ m.set(name, {
830
290
  decoded: null,
831
291
  zipEntry: {
832
- getData: () => {
833
- return new Promise((resolve) => {
834
- const buffers = [];
835
- file.ondata = (err2, data, final) => {
836
- if (err2) {
837
- console.error("failed to read zip data", file.name, err2);
838
- }
839
- if (data.byteLength) {
840
- buffers.push(data);
841
- }
842
- if (final) {
843
- return resolve(concatArrayBuffers(buffers));
844
- }
845
- };
846
- file.start();
847
- });
848
- }
292
+ getData: async () => readRecordData(buf, header).buffer
849
293
  }
850
294
  });
851
- };
852
- const chunkSize = 32 * 1024 * 1024;
853
- const nChunks = Math.max(1, Math.floor(buf.byteLength / chunkSize));
854
- const sleep = new Promise((resolve) => setTimeout(resolve, 0));
855
- for (let i = 0; i < nChunks - 1; ++i) {
856
- unzipper.push(buf.slice(i * chunkSize, (i + 1) * chunkSize));
857
- await sleep;
858
295
  }
859
- unzipper.push(buf.slice((nChunks - 1) * chunkSize, buf.byteLength), true);
860
296
  BOOKS[bookId] = {
861
297
  map: m,
862
298
  manifest: null
@@ -886,7 +322,8 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
886
322
  return back;
887
323
  }
888
324
  if (STORAGE === "cache") {
889
- const response = await caches.match(zipUrl);
325
+ const cache = await openCache(CONF_ENV);
326
+ const response = await cache.match(zipUrl);
890
327
  if (!response) {
891
328
  throw new Error(`missing zip ${zipUrl}`);
892
329
  }
@@ -910,7 +347,11 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
910
347
  const buf = await readArrayBuffer();
911
348
  if (CHECK_UPDATES) {
912
349
  const response = await fetch(zipUrl, { method: "HEAD" });
913
- const remoteSize = Number.parseInt(response.headers.get("content-length") || "0", 10);
350
+ let remoteSize = Number.parseInt(response.headers.get("content-length") ?? "");
351
+ if (!remoteSize) {
352
+ console.info("remoteSize not found. will skip update. Is the server up at", zipUrl);
353
+ remoteSize = buf.byteLength;
354
+ }
914
355
  if (remoteSize !== buf.byteLength) {
915
356
  const error = `new ${fileName} available, refetching (opfs:${buf.byteLength} vs remote:${remoteSize})`;
916
357
  console.debug(error);
@@ -918,6 +359,7 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
918
359
  throw new Error(`new ${fileName} available, refetching`);
919
360
  }
920
361
  }
362
+ self.postMessage({ methodName: "opfsProgress", arg: { total: buf.byteLength, bytes: buf.byteLength } });
921
363
  return buf;
922
364
  } catch (e) {
923
365
  if (e instanceof Error && e.message !== "forceRefresh" && !e.message.includes("refetching")) {
@@ -947,6 +389,7 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
947
389
  self.postMessage({ methodName: "opfsProgress", arg: { total: contentLength, bytes: position } });
948
390
  }
949
391
  }
392
+ console.debug("zip loaded:", arrayBuffer.byteLength);
950
393
  if (STORAGE === "native") {
951
394
  await sender.nativeWriteFile({ fname: fileName, buffer: arrayBuffer });
952
395
  } else if (STORAGE === "opfs") {
@@ -991,8 +434,15 @@ var loadBook2 = async ({ id: bookId, forceRefresh }) => {
991
434
  tryCatch(async () => {
992
435
  const buf = await fetchZipFile(`${ZIP_BASE_URL}/${bookId}.zip`, { forceRefresh });
993
436
  try {
437
+ console.time("loadzip");
994
438
  const entryNames = await loadZip(new Uint8Array(buf), bookId);
995
- fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId } });
439
+ console.timeEnd("loadzip");
440
+ const eventId = `setKnownLinks:${Date.now()}-${Math.random().toFixed(5)}`;
441
+ const responseForEventId = new Promise((resolve, reject) => {
442
+ dicFetchWorkerCallback[eventId] = { resolve, reject };
443
+ });
444
+ fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId, eventId } });
445
+ await responseForEventId;
996
446
  if (isOldZip(bookId)) {
997
447
  const json = await readZipEntry({
998
448
  entryName: `preload/${bookId}.json`,
@@ -1000,6 +450,13 @@ var loadBook2 = async ({ id: bookId, forceRefresh }) => {
1000
450
  asJson: true
1001
451
  });
1002
452
  loadBook(json);
453
+ } else {
454
+ try {
455
+ const { createdAt } = await readZipEntry({ bookId, entryName: "manifest.json", asJson: true });
456
+ console.debug("zipversion", createdAt);
457
+ } catch (e) {
458
+ console.debug("failed to read manifest.json", e);
459
+ }
1003
460
  }
1004
461
  return true;
1005
462
  } catch (e) {
@@ -1029,6 +486,14 @@ var loadPageContent2 = async ({
1029
486
  return await loadPageContent({ chapterId, json });
1030
487
  };
1031
488
  var fetchWorker;
489
+ var dicFetchWorkerCallback = {};
490
+ var handleFetchWorkerCallback = ({ data: { eventId } }) => {
491
+ if (dicFetchWorkerCallback[eventId]) {
492
+ dicFetchWorkerCallback[eventId].resolve();
493
+ } else {
494
+ console.debug("unknown eventId", eventId);
495
+ }
496
+ };
1032
497
  var initChannel = (__, { ports }) => {
1033
498
  fetchWorker = ports[0];
1034
499
  const { canHandle, handle } = makeSyncHandler(fetchWorker, {
@@ -1043,6 +508,14 @@ var initChannel = (__, { ports }) => {
1043
508
  self.postMessage({ methodName: "ready" });
1044
509
  return;
1045
510
  }
511
+ if (dataObj.data?.methodName === "setKnownLinks:back") {
512
+ if (typeof dataObj?.data?.eventId !== "string") {
513
+ console.debug("missing eventId to get response back for setKnownLinks:back");
514
+ return;
515
+ }
516
+ handleFetchWorkerCallback(dataObj);
517
+ return;
518
+ }
1046
519
  if (canHandle(dataObj.data?.methodName || "")) {
1047
520
  return handle(dataObj);
1048
521
  }
@@ -1116,13 +589,18 @@ var loadPreload2 = async () => {
1116
589
  console.error("failed to load preload", e);
1117
590
  }
1118
591
  };
592
+ var reset = async () => {
593
+ await (await navigator.storage.getDirectory()).remove({ recursive: true });
594
+ console.info("opfs cleared");
595
+ };
1119
596
  var mainHandle = makeSyncHandler(self, {
1120
597
  initChannel,
1121
598
  loadPreload: loadPreload2,
1122
599
  readZipEntry,
1123
600
  loadBooks,
1124
601
  loadBook: loadBook2,
1125
- configure
602
+ configure,
603
+ reset
1126
604
  });
1127
605
  var { handler, ...sender } = makeSyncWorker(self, ["nativeWriteFile", "nativeReadFile", "nativeListZips"]);
1128
606
  self.onmessage = combineHandlers(mainHandle, handler);
package/lib/index.js CHANGED
@@ -31,7 +31,18 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
31
31
  return ({ ports, ...arg } = {}) => {
32
32
  return new Promise((resolve, reject) => {
33
33
  const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
34
- dic[eventId] = { resolve, reject };
34
+ let resolved = false;
35
+ const timeout = setTimeout(() => {
36
+ console.warn("slow request for", methodName, arg);
37
+ }, 3e3);
38
+ const doResolve = (arg2) => {
39
+ if (!resolved) {
40
+ resolved = true;
41
+ clearTimeout(timeout);
42
+ resolve(arg2);
43
+ }
44
+ };
45
+ dic[eventId] = { resolve: doResolve, reject };
35
46
  offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
36
47
  });
37
48
  };
@@ -82,13 +93,14 @@ var combineHandlers = (...handlers) => {
82
93
  console.error("no handlers matched", methodName);
83
94
  };
84
95
  };
96
+ var clearCache = async (mode) => caches.delete(`LLS_OFFLINE_${mode}`);
85
97
 
86
98
  // src/hooks/useOfflineWorker.ts
87
99
  import { useCallback, useEffect, useState } from "react";
88
100
  var offlineWorker = new Worker("/src/worker/offline-worker.js", { type: "module" });
89
101
  var { handler, ...offlineWorkerSync } = makeSyncWorker(
90
102
  offlineWorker,
91
- ["initChannel", "readZipEntry", "configure", "loadPreload", "loadBooks", "loadBook"],
103
+ ["initChannel", "readZipEntry", "configure", "loadPreload", "loadBooks", "loadBook", "reset"],
92
104
  "app:offlineWorker"
93
105
  );
94
106
  var fetchWorkerPromise = new Promise(
@@ -160,6 +172,7 @@ var installWorker = async ({
160
172
  setBooksLoaded();
161
173
  };
162
174
  var useOfflineWorker_default = ({
175
+ skipLoadBooks,
163
176
  primaryOnly = false,
164
177
  nativeReadFile,
165
178
  nativeWriteFile,
@@ -173,6 +186,7 @@ var useOfflineWorker_default = ({
173
186
  useEffect(() => {
174
187
  const fn = async () => {
175
188
  await installWorker({
189
+ skipLoadBooks,
176
190
  primaryOnly,
177
191
  nativeReadFile,
178
192
  nativeWriteFile,
@@ -187,19 +201,69 @@ var useOfflineWorker_default = ({
187
201
  setReady(true);
188
202
  };
189
203
  fn();
190
- }, [primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, storage, zipBaseUrl, checkUpdates, confEnv]);
204
+ }, [
205
+ skipLoadBooks,
206
+ primaryOnly,
207
+ nativeReadFile,
208
+ nativeWriteFile,
209
+ nativeListZips,
210
+ storage,
211
+ zipBaseUrl,
212
+ checkUpdates,
213
+ confEnv
214
+ ]);
191
215
  const loadBook = useCallback(
192
- async ({ id, forceRefresh }, cbk) => {
193
- offlineWorkerSync.on("opfsProgress", cbk);
194
- return await offlineWorkerSync.loadBook({ id, forceRefresh });
216
+ async ({ forceRefresh, ...idOrUri }, cbk) => {
217
+ cbk && offlineWorkerSync.on("opfsProgress", cbk);
218
+ return await offlineWorkerSync.loadBook({ id: "id" in idOrUri ? idOrUri.id : idOrUri.uri, forceRefresh });
195
219
  },
196
220
  []
197
221
  );
222
+ const reset = useCallback(async () => {
223
+ await offlineWorkerSync.reset();
224
+ await clearCache(confEnv || "development");
225
+ console.debug("reset: keys:", await caches.keys());
226
+ }, [confEnv]);
198
227
  return {
199
228
  ready,
200
- loadBook
229
+ loadBook,
230
+ reset
201
231
  };
202
232
  };
233
+
234
+ // src/store/index.ts
235
+ import { persistReducer } from "redux-persist";
236
+ var persistEntitiesReducer = (entitiesReducer, CONF_ENV) => {
237
+ const getKey = (key) => `store:${CONF_ENV}:${key}`;
238
+ return persistReducer(
239
+ {
240
+ key: "entities",
241
+ storage: {
242
+ getItem: async (key) => localStorage.getItem(getKey(key)) || "{}",
243
+ setItem: async (key, item) => localStorage.setItem(getKey(key), item),
244
+ removeItem: async (key) => localStorage.removeItem(getKey(key))
245
+ },
246
+ whitelist: ["lexicalFlowers"],
247
+ version: 1,
248
+ debug: true,
249
+ throttle: 1e3
250
+ // lexicalFlowers is small, we may persist it fast
251
+ },
252
+ entitiesReducer
253
+ );
254
+ };
255
+
256
+ // src/store/offlinePersist.tsx
257
+ import { persistStore } from "redux-persist";
258
+ import { PersistGate } from "redux-persist/integration/react";
259
+ import { useMemo } from "react";
260
+ import { jsx } from "react/jsx-runtime";
261
+ var OfflinePersist = ({ store, children }) => {
262
+ const persistor = useMemo(() => persistStore(store), [store]);
263
+ return /* @__PURE__ */ jsx(PersistGate, { loading: null, persistor, children });
264
+ };
203
265
  export {
266
+ OfflinePersist,
267
+ persistEntitiesReducer,
204
268
  useOfflineWorker_default as useOfflineWorker
205
269
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lls/offline",
3
- "version": "24.27.0",
3
+ "version": "24.28.0-12ba1b40",
4
4
  "description": "offline",
5
5
  "main": "./lib/index.js",
6
6
  "module": "./lib/index.js",
@@ -48,14 +48,15 @@
48
48
  "nock": "14.0.5",
49
49
  "react": "18.3.1",
50
50
  "react-dom": "18.3.1",
51
+ "redux": "5.0.1",
51
52
  "typescript": "5.8.3",
52
53
  "vite": "7.0.7",
53
54
  "vite-tsconfig-paths": "5.1.4",
54
55
  "zod": "3.25.50",
55
- "@lls/vitescripts": "24.27.0"
56
+ "@lls/vitescripts": "24.28.0-12ba1b40"
56
57
  },
57
58
  "dependencies": {
58
- "fflate": "0.8.2"
59
+ "redux-persist": "6.0.0"
59
60
  },
60
61
  "scripts": {
61
62
  "start": "pnpm ladle serve",