@lls/offline 24.28.0-fd6a3fbf → 24.29.0-038ff270

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.
@@ -17,10 +17,10 @@ var parseRange = (range, size) => {
17
17
  };
18
18
 
19
19
  // src/utils/worker.ts
20
- var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
20
+ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
21
21
  const dic = {};
22
22
  const cbks = {};
23
- const fn = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
23
+ const fn = ({ source, data: { eventId, methodName, results, error, arg } = {} }) => {
24
24
  if (eventId && eventId in dic) {
25
25
  if (error) {
26
26
  dic[eventId].reject(error);
@@ -28,11 +28,11 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
28
28
  dic[eventId].resolve(results);
29
29
  }
30
30
  } else {
31
- if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
31
+ if (methodName && !(methodName in cbks)) {
32
32
  console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
33
33
  }
34
34
  }
35
- methodName && cbks[methodName]?.(arg);
35
+ methodName && cbks[methodName]?.({ source, ...arg });
36
36
  };
37
37
  const makeFn = (methodName) => {
38
38
  return ({ ports, ...arg } = {}) => {
@@ -40,7 +40,7 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
40
40
  const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
41
41
  let resolved = false;
42
42
  const timeout = setTimeout(() => {
43
- console.warn("slow request for", methodName, arg);
43
+ console.warn("slow request for", methodName, arg, eventId);
44
44
  }, 3e3);
45
45
  const doResolve = (arg2) => {
46
46
  if (!resolved) {
@@ -50,11 +50,16 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
50
50
  }
51
51
  };
52
52
  dic[eventId] = { resolve: doResolve, reject };
53
- offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
53
+ if (ports) {
54
+ offlineWorker.postMessage({ eventId, methodName, arg }, ports);
55
+ } else {
56
+ offlineWorker.postMessage({ eventId, methodName, arg });
57
+ }
54
58
  });
55
59
  };
56
60
  };
57
- const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
61
+ const fns = Object.fromEntries(methodNames.map((name) => [name, makeFn(name)]));
62
+ const obj = Object.assign(fns, {
58
63
  on: (key, fn2) => {
59
64
  cbks[key] = fn2;
60
65
  }
@@ -71,26 +76,38 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
71
76
  };
72
77
  var a = (a2) => a2;
73
78
  var makeSyncHandler = a(
74
- (sender, services) => ({
79
+ (services) => ({
75
80
  canHandle: (methodName) => {
76
81
  return methodName in services;
77
82
  },
78
- handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
83
+ handle: async ({ data: { eventId, methodName, arg } = {}, source, ports, target }) => {
84
+ const emitter = source?.postMessage && source || target?.postMessage && target || self;
79
85
  try {
80
86
  if (typeof methodName === "string" && methodName in services && services[methodName]) {
81
87
  const results = await services[methodName](arg, { ports });
82
- sender.postMessage({ eventId, methodName, results });
88
+ emitter.postMessage({ eventId, methodName, results });
83
89
  return;
84
90
  }
85
91
  throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
86
92
  } catch (e) {
87
93
  console.debug("failed", e);
88
- sender.postMessage({ eventId, methodName, error: e });
94
+ emitter.postMessage({ eventId, methodName, error: e });
89
95
  }
90
96
  }
91
97
  })
92
98
  );
93
99
  var jsonToBuf = (json) => new TextEncoder().encode(JSON.stringify(json));
100
+ var combineHandlers = (...handlers) => {
101
+ return (message) => {
102
+ const methodName = String("methodName" in message.data ? message.data.methodName : "");
103
+ for (const h of handlers) {
104
+ if (h.canHandle(methodName)) {
105
+ return h.handle(message);
106
+ }
107
+ }
108
+ console.error("no handlers matched", methodName);
109
+ };
110
+ };
94
111
  var openCache = async (mode) => caches.open(`LLS_OFFLINE_${mode}`);
95
112
  var isStoryZip = (name) => /story-.*?\.zip/.test(name);
96
113
  var isValidZipName = (name) => {
@@ -339,7 +356,7 @@ var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorke
339
356
  try {
340
357
  const [buf, maybeRangeHeaders] = await offlineWorkerSync2.readZipEntry(entryId).then((buf2) => {
341
358
  const range = ev.request.headers.get("Range");
342
- if (!ev.request.url.endsWith(".mp4") && !ev.request.url.endsWith(".webm")) {
359
+ if (!hasStreamingExtension(ev.request.url)) {
343
360
  return [buf2, {}];
344
361
  }
345
362
  if (!range) {
@@ -420,6 +437,7 @@ var ACCEPTED_EXTENSIONS = {
420
437
  ".riv": "application/octet-stream",
421
438
  ".pdf": "application/pdf"
422
439
  };
440
+ var hasStreamingExtension = (str) => /\.(mp3|mp4|webm|m4a)$/.test(str);
423
441
  var putInCache = async (request, response) => {
424
442
  const cache = await openCache(CONF_ENV);
425
443
  await cache.put(request, response);
@@ -429,7 +447,7 @@ var tryOnline = async (request, event) => {
429
447
  if (isValidZipName(request.url) && !isValidS3Zip(responseFromNetwork)) {
430
448
  throw new Error(`zip is invalid: got ${responseFromNetwork.headers.get("Content-Length")} bytes`);
431
449
  }
432
- const cached = request.method === "HEAD" ? Promise.resolve() : putInCache(request, responseFromNetwork.clone());
450
+ const cached = request.method === "GET" || request.method === "OPTIONS" ? putInCache(request, responseFromNetwork.clone()) : Promise.resolve();
433
451
  if (request.url.endsWith(".zip")) {
434
452
  await cached;
435
453
  event.waitUntil(Promise.resolve());
@@ -450,7 +468,7 @@ var onlineFirst = async (request, event) => {
450
468
  const responseFromCache = await tryOffline(request, event);
451
469
  if (!responseFromCache) {
452
470
  return new Response(
453
- `FAILED fetch ${e && typeof e === "object" && "message" in e ? e.message : e?.toString?.()}`,
471
+ `FAILED onlineFirst fetch ${e && typeof e === "object" && "message" in e ? e.message : e?.toString?.()} | ${event.request.url}`,
454
472
  {
455
473
  // @ts-ignore: poor ts typing
456
474
  ok: false,
@@ -475,6 +493,9 @@ swSelf.addEventListener("fetch", (ev) => {
475
493
  }
476
494
  return;
477
495
  }
496
+ if (/api(-dev|-preprod)?\.lelivrescolaire\.fr\/?$/.test(url) && ev.request.method === "HEAD") {
497
+ return;
498
+ }
478
499
  if (!mimeType && !isValidScreenCall) {
479
500
  if (!/\.(css|tsx?|[mj]sx?|woff2|wasm|fr|(web)?manifest)(\??|$)|@(vite|react|id)|(:6019\/?$)/.test(url)) {
480
501
  console.debug("no ext", extension || url);
@@ -503,23 +524,23 @@ swSelf.addEventListener("install", (_event) => {
503
524
  swSelf.addEventListener("activate", (event) => {
504
525
  event.waitUntil(swSelf.clients.claim());
505
526
  });
506
- var offlineWorker;
527
+ var setKnownLinks2 = (arg) => setKnownLinks(knownLinks, arg);
507
528
  var offlineWorkerSync;
508
529
  var initChannel = async (__, { ports }) => {
509
530
  console.info(`FETCH WORKER innitChannel v${/* @__PURE__ */ new Date()}`);
510
- offlineWorker = ports[0];
511
- const { handler, ...sender } = makeSyncWorker(
531
+ const offlineWorker = ports[0];
532
+ const { handler: syncHandler, ...sender } = makeSyncWorker(
512
533
  offlineWorker,
513
- ["readZipEntry", "loadBook", "slugToFileMeta", "loadPageContent", "searchBooks"],
534
+ ["readZipEntry", "loadBook", "slugToFileMeta", "loadPageContent", "searchBooks", "ensureAppReady"],
514
535
  "fetchworker"
515
536
  );
537
+ const onHandler = makeSyncHandler({ setKnownLinks: setKnownLinks2 });
516
538
  offlineWorkerSync = sender;
517
- offlineWorker.onmessage = handler.handle;
518
- offlineWorkerSync.on("setKnownLinks", ({ entryNames, bookId, eventId }) => {
519
- setKnownLinks(knownLinks, { entryNames, bookId });
520
- offlineWorker.postMessage({ methodName: "setKnownLinks:back", eventId });
521
- });
522
- offlineWorker.postMessage({ methodName: "sendAppReady" });
539
+ offlineWorker.onmessage = combineHandlers(syncHandler, onHandler);
540
+ offlineWorker.onclose = () => {
541
+ console.error("socket to ow has been closed!");
542
+ };
543
+ await offlineWorkerSync.ensureAppReady();
523
544
  };
524
545
  var knownLinks = /* @__PURE__ */ new Map();
525
546
  var configure = ({
@@ -536,28 +557,4 @@ var configure = ({
536
557
  }
537
558
  console.info("sw::configure", { STORAGE, CONF_ENV });
538
559
  };
539
- swSelf.onmessage = async ({
540
- data: { methodName, arg } = {},
541
- ports
542
- }) => {
543
- const services = {
544
- initChannel,
545
- configure
546
- };
547
- try {
548
- if (methodName === "configure") {
549
- await services[methodName](arg);
550
- return;
551
- }
552
- if (methodName === "initChannel") {
553
- if (!ports.length) {
554
- throw new Error("fetchworker::missing port!");
555
- }
556
- await services[methodName](arg, { ports });
557
- return;
558
- }
559
- throw new Error(`fetchworker:invalid methodName ${Object.keys(services)} ${methodName}`);
560
- } catch (e) {
561
- console.error("failed", methodName, arg, e);
562
- }
563
- };
560
+ swSelf.onmessage = combineHandlers(makeSyncHandler({ initChannel, configure }));
@@ -13,7 +13,7 @@ var tryCatch = (fn) => {
13
13
  var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
14
14
  const dic = {};
15
15
  const cbks = {};
16
- const fn = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
16
+ const fn = ({ source, data: { eventId, methodName, results, error, arg } = {} }) => {
17
17
  if (eventId && eventId in dic) {
18
18
  if (error) {
19
19
  dic[eventId].reject(error);
@@ -21,11 +21,11 @@ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
21
21
  dic[eventId].resolve(results);
22
22
  }
23
23
  } else {
24
- if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
24
+ if (methodName && !(methodName in cbks)) {
25
25
  console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
26
26
  }
27
27
  }
28
- methodName && cbks[methodName]?.(arg);
28
+ methodName && cbks[methodName]?.({ source, ...arg });
29
29
  };
30
30
  const makeFn = (methodName) => {
31
31
  return ({ ports, ...arg } = {}) => {
@@ -33,7 +33,7 @@ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
33
33
  const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
34
34
  let resolved = false;
35
35
  const timeout = setTimeout(() => {
36
- console.warn("slow request for", methodName, arg);
36
+ console.warn("slow request for", methodName, arg, eventId);
37
37
  }, 3e3);
38
38
  const doResolve = (arg2) => {
39
39
  if (!resolved) {
@@ -43,11 +43,16 @@ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
43
43
  }
44
44
  };
45
45
  dic[eventId] = { resolve: doResolve, reject };
46
- offlineWorker.postMessage({ eventId, methodName, arg }, ports);
46
+ if (ports) {
47
+ offlineWorker.postMessage({ eventId, methodName, arg }, ports);
48
+ } else {
49
+ offlineWorker.postMessage({ eventId, methodName, arg });
50
+ }
47
51
  });
48
52
  };
49
53
  };
50
- const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
54
+ const fns = Object.fromEntries(methodNames.map((name) => [name, makeFn(name)]));
55
+ const obj = Object.assign(fns, {
51
56
  on: (key2, fn2) => {
52
57
  cbks[key2] = fn2;
53
58
  }
@@ -64,31 +69,33 @@ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
64
69
  };
65
70
  var a = (a2) => a2;
66
71
  var makeSyncHandler = a(
67
- (sender2, services) => ({
72
+ (services) => ({
68
73
  canHandle: (methodName) => {
69
74
  return methodName in services;
70
75
  },
71
- handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
76
+ handle: async ({ data: { eventId, methodName, arg } = {}, source, ports, target }) => {
77
+ const emitter = source?.postMessage && source || target?.postMessage && target || self;
72
78
  try {
73
79
  if (typeof methodName === "string" && methodName in services && services[methodName]) {
74
80
  const results = await services[methodName](arg, { ports });
75
- sender2.postMessage({ eventId, methodName, results });
81
+ emitter.postMessage({ eventId, methodName, results });
76
82
  return;
77
83
  }
78
84
  throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
79
85
  } catch (e) {
80
86
  console.debug("failed", e);
81
- sender2.postMessage({ eventId, methodName, error: e });
87
+ emitter.postMessage({ eventId, methodName, error: e });
82
88
  }
83
89
  }
84
90
  })
85
91
  );
86
92
  var bufToJson = (buf) => JSON.parse(new TextDecoder("UTF-8").decode(new Uint8Array(buf)));
87
93
  var combineHandlers = (...handlers) => {
88
- return ({ data: { methodName, ...data } = {}, ports }) => {
94
+ return (message) => {
95
+ const methodName = String("methodName" in message.data ? message.data.methodName : "");
89
96
  for (const h of handlers) {
90
- if (h.canHandle(String(methodName))) {
91
- return h.handle({ data: { methodName, ...data }, ports });
97
+ if (h.canHandle(methodName)) {
98
+ return h.handle(message);
92
99
  }
93
100
  }
94
101
  console.error("no handlers matched", methodName);
@@ -401,6 +408,7 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
401
408
  await accessHandle.close();
402
409
  } else {
403
410
  const cache = await openCache(CONF_ENV);
411
+ console.time("writing cache");
404
412
  const response2 = new Response(arrayBuffer, {
405
413
  headers: {
406
414
  "Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
@@ -413,9 +421,12 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
413
421
  type: "cors",
414
422
  url: zipUrl
415
423
  });
416
- await cache.put(zipUrl, response2);
424
+ console.debug("writing to cache asynchronously, because takes 4s");
425
+ cache.put(zipUrl, response2).then(() => {
426
+ console.timeEnd("writing cache");
427
+ });
417
428
  }
418
- return await readArrayBuffer();
429
+ return arrayBuffer;
419
430
  }
420
431
  };
421
432
  var slugToFileMeta2 = async ({ slug }) => {
@@ -437,12 +448,7 @@ var loadBook2 = async ({ id: bookId, forceRefresh }) => {
437
448
  console.time("loadzip");
438
449
  const entryNames = await loadZip(new Uint8Array(buf), bookId);
439
450
  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;
451
+ await fetchWorkerSync.setKnownLinks({ entryNames, bookId });
446
452
  if (isOldZip(bookId)) {
447
453
  const json = await readZipEntry({
448
454
  entryName: `preload/${bookId}.json`,
@@ -485,43 +491,30 @@ var loadPageContent2 = async ({
485
491
  });
486
492
  return await loadPageContent({ chapterId, json });
487
493
  };
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
- }
494
+ var ensureAppReady = () => {
496
495
  };
496
+ var fetchWorker;
497
+ var fetchWorkerSync;
497
498
  var initChannel = (__, { ports }) => {
498
499
  fetchWorker = ports[0];
499
- const { canHandle, handle } = makeSyncHandler(fetchWorker, {
500
+ const onHandler = makeSyncHandler({
500
501
  readZipEntry,
501
502
  loadBook: loadBook2,
502
503
  slugToFileMeta: slugToFileMeta2,
503
504
  loadPageContent: loadPageContent2,
504
- searchBooks
505
+ searchBooks,
506
+ ensureAppReady
505
507
  });
506
- const fn = (dataObj) => {
507
- if (dataObj.data?.methodName === "sendAppReady") {
508
- self.postMessage({ methodName: "ready" });
509
- return;
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
- }
519
- if (canHandle(dataObj.data?.methodName || "")) {
520
- return handle(dataObj);
521
- }
522
- console.error("unknown methodName", dataObj.data?.methodName);
508
+ const { handler: syncHandler, ...sender2 } = makeSyncWorker(
509
+ fetchWorker,
510
+ ["setKnownLinks"],
511
+ "offline-worker"
512
+ );
513
+ fetchWorkerSync = sender2;
514
+ fetchWorker.onmessage = combineHandlers(syncHandler, onHandler);
515
+ fetchWorker.onclose = () => {
516
+ console.error("socket to sw has been closed!");
523
517
  };
524
- fetchWorker.onmessage = fn;
525
518
  };
526
519
  var loadBooks = async ({ primaryOnly = false } = {}) => {
527
520
  let entries = [];
@@ -593,7 +586,7 @@ var reset = async () => {
593
586
  await (await navigator.storage.getDirectory()).remove({ recursive: true });
594
587
  console.info("opfs cleared");
595
588
  };
596
- var mainHandle = makeSyncHandler(self, {
589
+ var mainHandle = makeSyncHandler({
597
590
  initChannel,
598
591
  loadPreload: loadPreload2,
599
592
  readZipEntry,
@@ -0,0 +1,33 @@
1
+ type UseOfflineProps = {
2
+ skipLoadBooks?: boolean;
3
+ primaryOnly?: boolean;
4
+ nativeReadFile?: (arg: {
5
+ fname: string;
6
+ }) => Promise<ArrayBuffer>;
7
+ nativeWriteFile?: (arg: {
8
+ fname: string;
9
+ buffer: ArrayBuffer;
10
+ }) => Promise<void>;
11
+ nativeListZips?: () => Promise<Array<{
12
+ name: string;
13
+ }>>;
14
+ checkUpdates?: boolean;
15
+ storage?: 'opfs' | 'native' | 'cache';
16
+ zipBaseUrl?: string;
17
+ confEnv?: 'development' | 'production';
18
+ };
19
+ declare const _default: ({ skipLoadBooks, primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, checkUpdates, storage, zipBaseUrl, confEnv }?: UseOfflineProps) => {
20
+ ready: boolean;
21
+ loadBook: ({ forceRefresh, ...idOrUri }: {
22
+ forceRefresh?: boolean;
23
+ } & ({
24
+ id: number;
25
+ } | {
26
+ uri: string;
27
+ }), cbk?: (arg: {
28
+ bytes: number;
29
+ total: number;
30
+ }) => void) => Promise<void>;
31
+ reset: () => Promise<void>;
32
+ };
33
+ export default _default;
@@ -0,0 +1,3 @@
1
+ export { default as useOfflineWorker } from '#offline/hooks/useOfflineWorker';
2
+ export * from '#offline/store/index';
3
+ export * from '#offline/store/offlinePersist';
@@ -0,0 +1,3 @@
1
+ import type { AnyFunction } from '@lls/typings/utils';
2
+ import type { Reducer } from 'redux';
3
+ export declare const persistEntitiesReducer: (entitiesReducer: AnyFunction, CONF_ENV: string) => Reducer;
@@ -0,0 +1,8 @@
1
+ import { persistStore } from 'redux-persist';
2
+ type Store = Parameters<typeof persistStore>[0];
3
+ import { type ReactNode } from 'react';
4
+ export declare const OfflinePersist: ({ store, children }: {
5
+ store: Store;
6
+ children: ReactNode;
7
+ }) => import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { AnyFunction } from '@lls/typings/utils';
2
+ export declare const tryCatch: (fn: (...args: any[]) => Promise<unknown>) => (resolve: AnyFunction, reject: AnyFunction) => void | Promise<void>;
@@ -0,0 +1,5 @@
1
+ export declare const parseRange: (range: string, size: number) => {
2
+ start: number;
3
+ end: number;
4
+ chunkSize: number;
5
+ };
@@ -0,0 +1,29 @@
1
+ import type { AnyFunction } from '@lls/typings/utils';
2
+ type OnMessageEvent = (m: MessageEvent) => void;
3
+ type Handler = {
4
+ canHandle: (methodName: string) => boolean;
5
+ handle: OnMessageEvent;
6
+ };
7
+ export declare const makeSyncWorker: <WorkerApi extends {
8
+ [k: string]: AnyFunction;
9
+ }>(offlineWorker: {
10
+ postMessage: Worker["postMessage"];
11
+ }, methodNames: string[], dbg?: string) => WorkerApi & {
12
+ on: (key: string, fn: AnyFunction) => void;
13
+ } & {
14
+ handler: Handler;
15
+ };
16
+ export declare const makeSyncHandler: (services: {
17
+ [k: string]: (...args: any[]) => any;
18
+ }) => {
19
+ canHandle: (methodName: string) => boolean;
20
+ handle: OnMessageEvent;
21
+ };
22
+ export declare const bufToJson: (buf: ArrayBuffer) => any;
23
+ export declare const jsonToBuf: (json: {}) => Uint8Array<ArrayBuffer>;
24
+ export declare const combineHandlers: (...handlers: Handler[]) => (message: MessageEvent) => void;
25
+ export declare const openCache: (mode: string) => Promise<Cache>;
26
+ export declare const clearCache: (mode: string) => Promise<boolean>;
27
+ export declare const isValidZipName: (name: string) => boolean;
28
+ export declare const isValidS3Zip: (response: Response) => boolean;
29
+ export {};
@@ -0,0 +1,7 @@
1
+ type Record = {
2
+ sofh: number;
3
+ name: Uint8Array;
4
+ };
5
+ export declare const readRecordData: (buf: Uint8Array, { sofh }: Record) => Uint8Array<ArrayBuffer>;
6
+ export declare const getHeaders: (buf: Uint8Array) => Record[];
7
+ export {};
@@ -0,0 +1,16 @@
1
+ import { setKnownLinks as handleSetKnownLinks } from '#offline/worker/swFetchScreenCall.ts';
2
+ declare const setKnownLinks: (arg: Parameters<typeof handleSetKnownLinks>[1]) => void;
3
+ export type FetchWorkerSync = {
4
+ setKnownLinks: typeof setKnownLinks;
5
+ };
6
+ declare const configure: ({ storage, confEnv }?: {
7
+ storage?: "native" | "opfs" | "cache";
8
+ confEnv?: "development" | "production";
9
+ }) => void;
10
+ export type FetchWorkerMain = {
11
+ initChannel: (d: {
12
+ ports: [MessagePort];
13
+ }) => void;
14
+ configure: typeof configure;
15
+ };
16
+ export {};
@@ -0,0 +1,67 @@
1
+ import type { EntryId } from './types.js';
2
+ import { searchBooks } from './wwApi.js';
3
+ import { readZipEntry } from './wwZip.js';
4
+ declare const slugToFileMeta: ({ slug }: {
5
+ slug: string;
6
+ }) => Promise<{
7
+ pageId: number | undefined;
8
+ chapterId: number | undefined;
9
+ }>;
10
+ type BookId = number | string;
11
+ declare const loadBook: ({ id: bookId, forceRefresh }: {
12
+ id: BookId;
13
+ forceRefresh?: boolean;
14
+ }) => Promise<void>;
15
+ declare const loadPageContent: ({ bookId: iBookId, chapterId, pageId, entryId }: {
16
+ bookId?: number;
17
+ chapterId?: number;
18
+ pageId?: number;
19
+ entryId?: EntryId;
20
+ }) => Promise<{
21
+ viewer: {
22
+ pages: {
23
+ hits: [{
24
+ id: number;
25
+ book: {
26
+ slug: string;
27
+ urlLite: string;
28
+ };
29
+ chapter: {
30
+ id: number;
31
+ };
32
+ }];
33
+ };
34
+ };
35
+ }>;
36
+ declare const ensureAppReady: () => void;
37
+ export type OfflineWorkerSync = {
38
+ readZipEntry: typeof readZipEntry;
39
+ loadBook: typeof loadBook;
40
+ slugToFileMeta: typeof slugToFileMeta;
41
+ loadPageContent: typeof loadPageContent;
42
+ searchBooks: typeof searchBooks;
43
+ ensureAppReady: typeof ensureAppReady;
44
+ };
45
+ declare const loadBooks: ({ primaryOnly }?: {
46
+ primaryOnly?: boolean | undefined;
47
+ }) => Promise<void>;
48
+ declare const configure: ({ storage, checkUpdates, zipBaseUrl, confEnv }: {
49
+ storage?: "native" | "opfs" | "cache";
50
+ checkUpdates?: boolean;
51
+ zipBaseUrl?: string;
52
+ confEnv?: "development" | "production";
53
+ }) => void;
54
+ declare const loadPreload: () => Promise<void>;
55
+ declare const reset: () => Promise<void>;
56
+ export type OfflineWorkerSyncMain = {
57
+ initChannel: (d: {
58
+ ports: [MessagePort];
59
+ }) => void;
60
+ loadPreload: typeof loadPreload;
61
+ readZipEntry: typeof readZipEntry;
62
+ loadBooks: typeof loadBooks;
63
+ loadBook: typeof loadBook;
64
+ configure: typeof configure;
65
+ reset: typeof reset;
66
+ };
67
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { OfflineWorkerSync } from './offline-worker';
2
+ import type { EntryId, SwEvent } from './types';
3
+ export declare const canHandleScreenCall: (url: string) => boolean;
4
+ export declare const handleScreenCall: (ev: SwEvent, { offlineWorkerSync, knownLinks }: {
5
+ offlineWorkerSync: OfflineWorkerSync;
6
+ knownLinks: Map<string, EntryId>;
7
+ }) => Promise<{
8
+ status: number;
9
+ json: any;
10
+ }>;
11
+ export declare const setKnownLinks: (knownLinks: Map<string, EntryId>, { entryNames, bookId }: {
12
+ entryNames: string[];
13
+ bookId: number | string;
14
+ }) => void;
@@ -0,0 +1,58 @@
1
+ type Book = {
2
+ id: number;
3
+ chapters: {
4
+ id: number;
5
+ }[];
6
+ uri: string;
7
+ slug: string;
8
+ urlLite: string;
9
+ };
10
+ type Page = {
11
+ id: number;
12
+ book: {
13
+ slug: string;
14
+ urlLite: string;
15
+ };
16
+ chapter: {
17
+ id: number;
18
+ };
19
+ };
20
+ type GqlBook = {
21
+ viewer: {
22
+ books: {
23
+ hits: [Book];
24
+ };
25
+ };
26
+ };
27
+ type GqlPage = {
28
+ viewer: {
29
+ pages: {
30
+ hits: [Page];
31
+ };
32
+ };
33
+ };
34
+ export declare const loadBook: (json: GqlBook) => void;
35
+ type SlugFile = {
36
+ [pageId: string]: `${number};${number}`;
37
+ };
38
+ export declare const slugToFileMeta: ({ slug, json }: {
39
+ slug: string;
40
+ json: SlugFile;
41
+ }) => Promise<{
42
+ pageId: number | undefined;
43
+ chapterId: number | undefined;
44
+ }>;
45
+ export declare const loadPageContent: ({ chapterId, json }: {
46
+ chapterId?: number;
47
+ json: GqlPage;
48
+ }) => Promise<GqlPage>;
49
+ export declare const searchBooks: ({ uris, slugs, ids, chapterIds }: {
50
+ uris?: string[];
51
+ slugs?: string[];
52
+ ids?: number[];
53
+ chapterIds?: (number | undefined)[];
54
+ }) => Promise<(Omit<Book, "chapters"> & {
55
+ chapters: number[];
56
+ })[]>;
57
+ export declare const loadPreload: (json: GqlBook) => void;
58
+ export {};
@@ -0,0 +1,9 @@
1
+ type BookId = number | string;
2
+ export declare const readZipEntry: ({ bookId, entryName, asJson }: {
3
+ bookId: number | string;
4
+ entryName: string;
5
+ asJson?: boolean;
6
+ }) => Promise<any>;
7
+ export declare const hasZip: (bookId: BookId | string) => boolean;
8
+ export declare const loadZip: (buf: Uint8Array, bookId: BookId) => Promise<string[]>;
9
+ export {};
package/lib/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // src/hooks/useOfflineWorker.ts
2
+ import { useCallback, useEffect, useState } from "react";
3
+
1
4
  // src/utils/error.ts
2
5
  var tryCatch = (fn) => {
3
6
  return (resolve, reject) => {
@@ -13,7 +16,7 @@ var tryCatch = (fn) => {
13
16
  var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
14
17
  const dic = {};
15
18
  const cbks = {};
16
- const fn = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
19
+ const fn = ({ source, data: { eventId, methodName, results, error, arg } = {} }) => {
17
20
  if (eventId && eventId in dic) {
18
21
  if (error) {
19
22
  dic[eventId].reject(error);
@@ -21,11 +24,11 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
21
24
  dic[eventId].resolve(results);
22
25
  }
23
26
  } else {
24
- if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
27
+ if (methodName && !(methodName in cbks)) {
25
28
  console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
26
29
  }
27
30
  }
28
- methodName && cbks[methodName]?.(arg);
31
+ methodName && cbks[methodName]?.({ source, ...arg });
29
32
  };
30
33
  const makeFn = (methodName) => {
31
34
  return ({ ports, ...arg } = {}) => {
@@ -33,7 +36,7 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
33
36
  const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
34
37
  let resolved = false;
35
38
  const timeout = setTimeout(() => {
36
- console.warn("slow request for", methodName, arg);
39
+ console.warn("slow request for", methodName, arg, eventId);
37
40
  }, 3e3);
38
41
  const doResolve = (arg2) => {
39
42
  if (!resolved) {
@@ -43,11 +46,16 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
43
46
  }
44
47
  };
45
48
  dic[eventId] = { resolve: doResolve, reject };
46
- offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
49
+ if (ports) {
50
+ offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
51
+ } else {
52
+ offlineWorker2.postMessage({ eventId, methodName, arg });
53
+ }
47
54
  });
48
55
  };
49
56
  };
50
- const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
57
+ const fns = Object.fromEntries(methodNames.map((name) => [name, makeFn(name)]));
58
+ const obj = Object.assign(fns, {
51
59
  on: (key, fn2) => {
52
60
  cbks[key] = fn2;
53
61
  }
@@ -64,30 +72,32 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
64
72
  };
65
73
  var a = (a2) => a2;
66
74
  var makeSyncHandler = a(
67
- (sender, services) => ({
75
+ (services) => ({
68
76
  canHandle: (methodName) => {
69
77
  return methodName in services;
70
78
  },
71
- handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
79
+ handle: async ({ data: { eventId, methodName, arg } = {}, source, ports, target }) => {
80
+ const emitter = source?.postMessage && source || target?.postMessage && target || self;
72
81
  try {
73
82
  if (typeof methodName === "string" && methodName in services && services[methodName]) {
74
83
  const results = await services[methodName](arg, { ports });
75
- sender.postMessage({ eventId, methodName, results });
84
+ emitter.postMessage({ eventId, methodName, results });
76
85
  return;
77
86
  }
78
87
  throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
79
88
  } catch (e) {
80
89
  console.debug("failed", e);
81
- sender.postMessage({ eventId, methodName, error: e });
90
+ emitter.postMessage({ eventId, methodName, error: e });
82
91
  }
83
92
  }
84
93
  })
85
94
  );
86
95
  var combineHandlers = (...handlers) => {
87
- return ({ data: { methodName, ...data } = {}, ports }) => {
96
+ return (message) => {
97
+ const methodName = String("methodName" in message.data ? message.data.methodName : "");
88
98
  for (const h of handlers) {
89
- if (h.canHandle(String(methodName))) {
90
- return h.handle({ data: { methodName, ...data }, ports });
99
+ if (h.canHandle(methodName)) {
100
+ return h.handle(message);
91
101
  }
92
102
  }
93
103
  console.error("no handlers matched", methodName);
@@ -96,7 +106,6 @@ var combineHandlers = (...handlers) => {
96
106
  var clearCache = async (mode) => caches.delete(`LLS_OFFLINE_${mode}`);
97
107
 
98
108
  // src/hooks/useOfflineWorker.ts
99
- import { useCallback, useEffect, useState } from "react";
100
109
  var offlineWorker = new Worker("/src/worker/offline-worker.js", { type: "module" });
101
110
  var { handler, ...offlineWorkerSync } = makeSyncWorker(
102
111
  offlineWorker,
@@ -124,14 +133,6 @@ var fetchWorkerPromise = new Promise(
124
133
  return fetchWorker;
125
134
  })
126
135
  );
127
- var setWorkersReady;
128
- var workersReadyPromise = new Promise((resolve) => {
129
- setWorkersReady = resolve;
130
- });
131
- var setBooksLoaded;
132
- var setBooksLoadedPromise = new Promise((resolve) => {
133
- setBooksLoaded = resolve;
134
- });
135
136
  var installWorker = async ({
136
137
  skipLoadBooks = false,
137
138
  primaryOnly = false,
@@ -146,7 +147,7 @@ var installWorker = async ({
146
147
  const fetchWorker = await fetchWorkerPromise;
147
148
  const configureOptions = { storage, zipBaseUrl, checkUpdates, confEnv };
148
149
  if (nativeReadFile) {
149
- const wwHandler = makeSyncHandler(offlineWorker, { nativeReadFile, nativeWriteFile, nativeListZips });
150
+ const wwHandler = makeSyncHandler({ nativeReadFile, nativeWriteFile, nativeListZips });
150
151
  offlineWorker.onmessage = combineHandlers(handler, wwHandler);
151
152
  if (storage !== "native") {
152
153
  console.warn("nativeReadFile given, ignoring storage", storage);
@@ -154,13 +155,17 @@ var installWorker = async ({
154
155
  offlineWorkerSync.configure({ ...configureOptions, storage: "native" });
155
156
  } else {
156
157
  offlineWorker.onmessage = handler.handle;
157
- offlineWorkerSync.configure(configureOptions);
158
+ await offlineWorkerSync.configure(configureOptions);
158
159
  }
159
- offlineWorkerSync.on("ready", setWorkersReady);
160
160
  const channel = new MessageChannel();
161
- offlineWorkerSync.initChannel({ ports: [channel.port1] });
162
- fetchWorker.postMessage({ methodName: "initChannel" }, [channel.port2]);
163
- fetchWorker.postMessage({ methodName: "configure", arg: { storage, confEnv } });
161
+ await offlineWorkerSync.initChannel({ ports: [channel.port1] });
162
+ const { handler: fetchWorkerHandler, ...fetchWorkerSync } = makeSyncWorker(fetchWorker, [
163
+ "initChannel",
164
+ "configure"
165
+ ]);
166
+ navigator.serviceWorker.onmessage = combineHandlers(fetchWorkerHandler);
167
+ await fetchWorkerSync.initChannel({ ports: [channel.port2] });
168
+ await fetchWorkerSync.configure({ storage, confEnv });
164
169
  offlineWorkerSync.on("opfsProgress", () => {
165
170
  });
166
171
  if (!primaryOnly) {
@@ -169,7 +174,6 @@ var installWorker = async ({
169
174
  if (!skipLoadBooks) {
170
175
  await offlineWorkerSync.loadBooks({ primaryOnly });
171
176
  }
172
- setBooksLoaded();
173
177
  };
174
178
  var useOfflineWorker_default = ({
175
179
  skipLoadBooks,
@@ -196,8 +200,9 @@ var useOfflineWorker_default = ({
196
200
  checkUpdates,
197
201
  confEnv
198
202
  });
199
- await Promise.all([workersReadyPromise, setBooksLoadedPromise]);
200
- fetch(window.location.href.replace(window.location.search, ""));
203
+ if (process.env.DESKTOP_FLOWER_OFFLINE !== "1") {
204
+ fetch(window.location.href.replace(window.location.search, ""));
205
+ }
201
206
  setReady(true);
202
207
  };
203
208
  fn();
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@lls/offline",
3
- "version": "24.28.0-fd6a3fbf",
3
+ "version": "24.29.0-038ff270",
4
4
  "description": "offline",
5
5
  "main": "./lib/index.js",
6
6
  "module": "./lib/index.js",
7
7
  "types": "./lib/builttypes/offline/src/index.d.ts",
8
8
  "exports": {
9
- ".": "./lib/index.js"
9
+ "import": "./lib/index.js",
10
+ "types": "./lib/builttypes/offline/src/index.d.ts"
10
11
  },
11
12
  "files": [
12
13
  "lib/",
@@ -53,7 +54,7 @@
53
54
  "vite": "7.0.7",
54
55
  "vite-tsconfig-paths": "5.1.4",
55
56
  "zod": "3.25.50",
56
- "@lls/vitescripts": "24.28.0-fd6a3fbf"
57
+ "@lls/vitescripts": "24.29.0-038ff270"
57
58
  },
58
59
  "dependencies": {
59
60
  "redux-persist": "6.0.0"
@@ -67,5 +68,8 @@
67
68
  "test": "bun test --bail __tests__/unit __tests__/e2e",
68
69
  "lint": "pnpm biome check --apply src __tests__ stories",
69
70
  "precommit": "pnpm lint"
71
+ },
72
+ "imports": {
73
+ "#offline/*": "./lib/builttypes/offline/src/*.d.ts"
70
74
  }
71
75
  }
package/lib/index.d.ts DELETED
@@ -1 +0,0 @@
1
- declare module "@lls/offline"