@lls/offline 24.20.0-debug2 → 24.22.0-4a703cc1

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.
@@ -11,7 +11,7 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
11
11
  }
12
12
  } else {
13
13
  if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
14
- console.error(`syncWorker: ${dbg}`, eventId, methodName, error);
14
+ console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
15
15
  }
16
16
  }
17
17
  methodName && cbks[methodName]?.(arg);
@@ -19,7 +19,7 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
19
19
  const makeFn = (methodName) => {
20
20
  return ({ ports, ...arg } = {}) => {
21
21
  return new Promise((resolve, reject) => {
22
- const eventId = `${methodName}:${Date.now()}`;
22
+ const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
23
23
  dic[eventId] = { resolve, reject };
24
24
  offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
25
25
  });
@@ -62,6 +62,7 @@ var makeSyncHandler = a(
62
62
  })
63
63
  );
64
64
  var jsonToBuf = (json) => new TextEncoder().encode(JSON.stringify(json));
65
+ var openCache = async (mode) => caches.open(`LLS_OFFLINE_${mode}`);
65
66
 
66
67
  // src/worker/swFetchScreenCall.ts
67
68
  var zipSlugName = (str) => str.replace(/[^a-zA-Z0-9-.]/g, "");
@@ -74,6 +75,15 @@ var cocoreFetchPartialPageScreen = async (_ev, { offlineWorkerSync: offlineWorke
74
75
  const gqlPage = await offlineWorkerSync2.loadPageContent({ entryId });
75
76
  return { data: gqlPage };
76
77
  };
78
+ var cocoreFetchBookScreen = async (_ev, { offlineWorkerSync: offlineWorkerSync2, variables, knownLinks: knownLinks2 }) => {
79
+ const key = `gqls/${zipSlugName(variables.uris[0] || "")}.json`;
80
+ const entryId = knownLinks2.get(key);
81
+ if (!entryId) {
82
+ throw new Error(`uri not found ${key}`);
83
+ }
84
+ const gqlBook = await offlineWorkerSync2.loadPageContent({ entryId });
85
+ return { data: gqlBook };
86
+ };
77
87
  var cocoreFetchPartialPageSiblingsScreen = async (_ev) => {
78
88
  return { data: { viewer: {} } };
79
89
  };
@@ -188,6 +198,7 @@ var coreFetchPagesApi = async (ev, { offlineWorkerSync: offlineWorkerSync2, vari
188
198
  };
189
199
  var handles = {
190
200
  coreFetchBookDrawerScreen,
201
+ cocoreFetchBookScreen,
191
202
  coreFetchPagesApi,
192
203
  cocoreFetchPartialPageScreen,
193
204
  cocoreFetchPartialPageSiblingsScreen
@@ -196,7 +207,7 @@ var canHandleScreenCall = (url) => new RegExp(Object.keys(handles).join("|")).te
196
207
  var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, knownLinks: knownLinks2 }) => {
197
208
  try {
198
209
  const variables = (
199
- // @ts-ignore TODO: handle variables from body(stream) properly!!
210
+ // @ts-expect-error TODO: handle variables from body(stream) properly!!
200
211
  ev.request.body?.variables || JSON.parse(new URLSearchParams(new URL(ev.request.url).search).get("variables") || "{}")
201
212
  );
202
213
  console.debug("variables are", variables, "vs", ev.request.body);
@@ -211,23 +222,28 @@ var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, known
211
222
  return { status: 500, json: JSON.stringify(e) };
212
223
  }
213
224
  };
225
+ var setKnownLinks = (knownLinks2, { entryNames, bookId }) => {
226
+ entryNames.forEach((entryName) => {
227
+ const link = entryName.replace(new RegExp(`^\\d+/media/|^${bookId}/medias/`), "").replace(new RegExp(`^${bookId}/gqls/`), "gqls/");
228
+ knownLinks2.set(link, { entryName, bookId });
229
+ });
230
+ };
214
231
 
215
232
  // src/worker/fetch-worker.ts
216
- var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
233
+ var STORAGE = "cache";
234
+ var CONF_ENV = "development";
235
+ var fetchAsset = async (_ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
217
236
  try {
218
237
  const buf = await offlineWorkerSync2.readZipEntry(entryId);
219
- const url = ev.request.url;
220
238
  return new Response(buf, {
221
239
  headers: {
222
240
  "Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
241
+ "Content-Length": buf.byteLength,
223
242
  "Content-Type": mimeType
224
243
  },
225
244
  // @ts-ignore: poor ts typing
226
- ok: true,
227
- redirected: false,
228
245
  status: 200,
229
- type: "cors",
230
- url
246
+ statusText: "OK"
231
247
  });
232
248
  } catch (e) {
233
249
  console.error("FAILED fetchAsset", e);
@@ -235,8 +251,7 @@ var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorke
235
251
  // @ts-ignore: poor ts typing
236
252
  ok: false,
237
253
  status: 500,
238
- type: "cors",
239
- url: ev.request.url
254
+ statusText: e?.toString?.() || "fail fetch"
240
255
  });
241
256
  }
242
257
  };
@@ -278,36 +293,69 @@ var ACCEPTED_EXTENSIONS = {
278
293
  ".m4a": "audio/mp4",
279
294
  ".webp": "image/webp"
280
295
  };
296
+ var putInCache = async (request, response) => {
297
+ const cache = await openCache(CONF_ENV);
298
+ await cache.put(request, response);
299
+ };
300
+ var onlineFirst = async (request, event) => {
301
+ try {
302
+ const responseFromNetwork = await fetch(request);
303
+ const cached = putInCache(request, responseFromNetwork.clone());
304
+ if (request.url.endsWith(".zip")) {
305
+ await cached;
306
+ event.waitUntil(Promise.resolve());
307
+ } else {
308
+ event.waitUntil(cached);
309
+ }
310
+ return responseFromNetwork;
311
+ } catch (_e) {
312
+ const over = /\/\?story=basic/.test(request.url) ? request.url.replace(/\/\?story=.*$/, "") : request;
313
+ const responseFromCache = await caches.match(over);
314
+ if (!responseFromCache) {
315
+ return new Response("FAILED fetch", {
316
+ // @ts-ignore: poor ts typing
317
+ ok: false,
318
+ status: 500,
319
+ type: "cors",
320
+ url: event.request.url
321
+ });
322
+ }
323
+ return responseFromCache;
324
+ }
325
+ };
281
326
  var swSelf = self;
282
327
  swSelf.addEventListener("fetch", (ev) => {
283
328
  const url = ev.request.url;
284
329
  const extension = url.match(/\.[^.]+$/)?.[0];
285
330
  const mimeType = extension && extension in ACCEPTED_EXTENSIONS ? ACCEPTED_EXTENSIONS[extension] : "";
286
331
  const isValidScreenCall = canHandleScreenCall(url);
332
+ if (STORAGE !== "cache" && url.endsWith(".zip")) {
333
+ return;
334
+ }
287
335
  if (!mimeType && !isValidScreenCall) {
288
336
  if (!/\.(css|tsx?|jsx?|woff2|fr|manifest)(\??|$)|@(vite|react|id)/.test(url)) {
289
337
  console.debug("no ext", extension || url);
290
338
  }
291
- return;
339
+ return ev.respondWith(onlineFirst(ev.request, ev));
292
340
  }
293
341
  const entryId = knownLinks.get(url.replace(/https:\/\/.*?\//, "")) || knownLinks.get(`medias/${url.replace(/https:\/\/.*?\//, "")}`);
294
- if (!entryId && !isValidScreenCall) {
295
- if (/lls|lelivrescolaire/.test(url)) {
296
- console.info("no link for", url.replace(/https:\/\/.*?\//, ""));
297
- }
298
- return;
299
- }
300
342
  if (isValidScreenCall) {
301
343
  console.debug("screencall!");
302
344
  return ev.respondWith(fetchScreenCall(ev, { offlineWorkerSync, knownLinks }));
303
345
  }
304
- return ev.respondWith(fetchAsset(ev, { entryId, mimeType, offlineWorkerSync }));
346
+ if (entryId) {
347
+ return ev.respondWith(fetchAsset(ev, { entryId, mimeType, offlineWorkerSync }));
348
+ }
349
+ if (/lls|lelivrescolaire/.test(url)) {
350
+ console.info("no link for", url.replace(/https:\/\/.*?\//, ""), `medias/${url.replace(/https:\/\/.*?\//, "")}`);
351
+ }
352
+ return;
305
353
  });
306
354
  swSelf.addEventListener("install", (_event) => {
307
355
  swSelf.skipWaiting();
308
356
  });
309
- swSelf.addEventListener("activate", (_event) => {
310
- return swSelf.clients.claim();
357
+ swSelf.addEventListener("activate", (event) => {
358
+ event.waitUntil(swSelf.clients.claim());
311
359
  });
312
360
  var offlineWorker;
313
361
  var offlineWorkerSync;
@@ -321,24 +369,37 @@ var initChannel = async (__, { ports }) => {
321
369
  );
322
370
  offlineWorkerSync = sender;
323
371
  offlineWorker.onmessage = handler.handle;
324
- offlineWorkerSync.on("setKnownLinks", setKnownLinks);
372
+ offlineWorkerSync.on("setKnownLinks", setKnownLinks.bind(null, knownLinks));
325
373
  offlineWorker.postMessage({ methodName: "sendAppReady" });
326
374
  };
327
375
  var knownLinks = /* @__PURE__ */ new Map();
328
- var setKnownLinks = ({ entryNames, bookId }) => {
329
- entryNames.forEach((entryName) => {
330
- const link = entryName.replace(/^\d+\/media\//, "");
331
- knownLinks.set(link, { entryName, bookId });
332
- });
376
+ var configure = ({
377
+ storage,
378
+ confEnv
379
+ } = {}) => {
380
+ STORAGE = "cache";
381
+ CONF_ENV = "development";
382
+ if (typeof storage !== "undefined") {
383
+ STORAGE = storage;
384
+ }
385
+ if (typeof confEnv !== "undefined") {
386
+ CONF_ENV = confEnv;
387
+ }
388
+ console.info("sw::configure", { STORAGE, CONF_ENV });
333
389
  };
334
390
  swSelf.onmessage = async ({
335
391
  data: { methodName, arg } = {},
336
392
  ports
337
393
  }) => {
338
394
  const services = {
339
- initChannel
395
+ initChannel,
396
+ configure
340
397
  };
341
398
  try {
399
+ if (methodName === "configure") {
400
+ await services[methodName](arg);
401
+ return;
402
+ }
342
403
  if (methodName === "initChannel") {
343
404
  if (!ports.length) {
344
405
  throw new Error("fetchworker::missing port!");
@@ -1,3 +1,14 @@
1
+ // src/utils/error.ts
2
+ var tryCatch = (fn) => {
3
+ return (resolve, reject) => {
4
+ try {
5
+ return fn().then(resolve);
6
+ } catch (e) {
7
+ return reject(e);
8
+ }
9
+ };
10
+ };
11
+
1
12
  // src/utils/worker.ts
2
13
  var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
3
14
  const dic = {};
@@ -11,7 +22,7 @@ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
11
22
  }
12
23
  } else {
13
24
  if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
14
- console.error(`syncWorker: ${dbg}`, eventId, methodName, error);
25
+ console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
15
26
  }
16
27
  }
17
28
  methodName && cbks[methodName]?.(arg);
@@ -19,7 +30,7 @@ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
19
30
  const makeFn = (methodName) => {
20
31
  return ({ ports, ...arg } = {}) => {
21
32
  return new Promise((resolve, reject) => {
22
- const eventId = `${methodName}:${Date.now()}`;
33
+ const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
23
34
  dic[eventId] = { resolve, reject };
24
35
  offlineWorker.postMessage({ eventId, methodName, arg }, ports);
25
36
  });
@@ -63,15 +74,16 @@ var makeSyncHandler = a(
63
74
  );
64
75
  var bufToJson = (buf) => JSON.parse(new TextDecoder("UTF-8").decode(new Uint8Array(buf)));
65
76
  var combineHandlers = (...handlers) => {
66
- return ({ data: { methodName, ...data }, ports }) => {
77
+ return ({ data: { methodName, ...data } = {}, ports }) => {
67
78
  for (const h of handlers) {
68
- if (h.canHandle(methodName)) {
79
+ if (h.canHandle(String(methodName))) {
69
80
  return h.handle({ data: { methodName, ...data }, ports });
70
81
  }
71
82
  }
72
83
  console.error("no handlers matched", methodName);
73
84
  };
74
85
  };
86
+ var openCache = async (mode) => caches.open(`LLS_OFFLINE_${mode}`);
75
87
 
76
88
  // src/worker/wwApi.ts
77
89
  var STATE = {
@@ -830,17 +842,25 @@ var loadZip = async (buf, bookId) => {
830
842
 
831
843
  // src/worker/offline-worker.ts
832
844
  var CHECK_UPDATES = true;
833
- var NATIVE = false;
834
- var LOADED_BOOKS = /* @__PURE__ */ new Set();
845
+ var STORAGE = "cache";
846
+ var ZIP_BASE_URL = "https://ci.lls.fr/artefacts/content/development";
847
+ var CONF_ENV = "development";
848
+ var LOADED_BOOKS = /* @__PURE__ */ new Map();
835
849
  var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
836
850
  const fileName = zipUrl.split("/").slice(-1)[0];
837
851
  const readArrayBuffer = async () => {
838
- if (NATIVE) {
839
- console.debug("try reading native", fileName);
852
+ if (STORAGE === "native") {
840
853
  const back = await sender.nativeReadFile({ fname: fileName });
841
- console.debug("done readnig");
842
854
  return back;
843
855
  }
856
+ if (STORAGE === "cache") {
857
+ const response = await caches.match(zipUrl);
858
+ if (!response) {
859
+ throw new Error(`missing zip ${zipUrl}`);
860
+ }
861
+ const buf = await response.arrayBuffer();
862
+ return buf;
863
+ }
844
864
  const opfsRoot = await navigator.storage.getDirectory();
845
865
  const handle = await opfsRoot.getFileHandle(fileName);
846
866
  const accessHandle = await handle.createSyncAccessHandle();
@@ -867,8 +887,8 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
867
887
  }
868
888
  return buf;
869
889
  } catch (e) {
870
- if (e.message !== "forceRefresh" && !e.message.includes("refetching")) {
871
- console.debug("opfs db not found, creating it", e, zipUrl);
890
+ if (e instanceof Error && e.message !== "forceRefresh" && !e.message.includes("refetching")) {
891
+ console.debug(`${STORAGE} zip not found, creating it`, e, zipUrl);
872
892
  }
873
893
  const response = await fetch(zipUrl);
874
894
  const contentLength = +response.headers.get("Content-Length");
@@ -879,6 +899,7 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
879
899
  while (true) {
880
900
  const { done, value: chunk } = await reader.read();
881
901
  if (done) {
902
+ self.postMessage({ methodName: "opfsProgress", arg: { total: contentLength, bytes: contentLength } });
882
903
  break;
883
904
  }
884
905
  arrayBuffer.set(chunk, position);
@@ -893,44 +914,69 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
893
914
  console.debug(error);
894
915
  throw new Error(error);
895
916
  }
896
- if (NATIVE) {
917
+ if (STORAGE === "native") {
897
918
  await sender.nativeWriteFile({ fname: fileName, buffer: arrayBuffer });
898
- return await readArrayBuffer();
919
+ } else if (STORAGE === "opfs") {
920
+ const opfsRoot = await navigator.storage.getDirectory();
921
+ const handle = await opfsRoot.getFileHandle(fileName, { create: true });
922
+ const accessHandle = await handle.createSyncAccessHandle();
923
+ await accessHandle.truncate(0);
924
+ await accessHandle.write(arrayBuffer);
925
+ await accessHandle.close();
926
+ } else {
927
+ const cache = await openCache(CONF_ENV);
928
+ const response2 = new Response(arrayBuffer, {
929
+ headers: {
930
+ "Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
931
+ "Content-Type": "application/zip"
932
+ },
933
+ // @ts-ignore: poor ts typing
934
+ ok: true,
935
+ redirected: false,
936
+ status: 200,
937
+ type: "cors",
938
+ url: zipUrl
939
+ });
940
+ await cache.put(zipUrl, response2);
899
941
  }
900
- const opfsRoot = await navigator.storage.getDirectory();
901
- const handle = await opfsRoot.getFileHandle(fileName, { create: true });
902
- const accessHandle = await handle.createSyncAccessHandle();
903
- await accessHandle.truncate(0);
904
- await accessHandle.write(arrayBuffer);
905
- await accessHandle.close();
906
942
  return await readArrayBuffer();
907
943
  }
908
944
  };
909
945
  var slugToFileMeta2 = async ({ slug }) => {
910
- console.debug("hereshit");
911
946
  const json = await readZipEntry({ bookId: "preload", entryName: "preload/hashmap_slugToMetaPage.json", asJson: true });
912
- console.debug("hereshit2");
913
947
  return slugToFileMeta({ slug, json });
914
948
  };
915
949
  var PRELOAD_AS_BOOK_ID = "preload";
916
950
  var isOldZip = (bookId) => typeof bookId === "number";
917
951
  var loadBook2 = async ({ id: bookId, forceRefresh }) => {
918
952
  if (LOADED_BOOKS.has(bookId)) {
953
+ await LOADED_BOOKS.get(bookId);
919
954
  self.postMessage({ methodName: "opfsProgress", arg: { total: 1, bytes: 1 } });
920
955
  return;
921
956
  }
922
- LOADED_BOOKS.add(bookId);
923
- const buf = await fetchZipFile(`https://ci.lls.fr/artefacts/content/development/${bookId}.zip`, { forceRefresh });
924
- try {
925
- const entryNames = await loadZip(new Uint8Array(buf), bookId);
926
- fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId } });
927
- if (isOldZip(bookId)) {
928
- const json = await readZipEntry({ entryName: `preload/${bookId}.json`, bookId: PRELOAD_AS_BOOK_ID, asJson: true });
929
- loadBook(json);
930
- }
931
- } catch (e) {
932
- console.error("failed", e);
933
- }
957
+ const p = new Promise(
958
+ tryCatch(async () => {
959
+ const buf = await fetchZipFile(`${ZIP_BASE_URL}/${bookId}.zip`, { forceRefresh });
960
+ try {
961
+ const entryNames = await loadZip(new Uint8Array(buf), bookId);
962
+ fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId } });
963
+ if (isOldZip(bookId)) {
964
+ const json = await readZipEntry({
965
+ entryName: `preload/${bookId}.json`,
966
+ bookId: PRELOAD_AS_BOOK_ID,
967
+ asJson: true
968
+ });
969
+ loadBook(json);
970
+ }
971
+ return true;
972
+ } catch (e) {
973
+ console.error("failed", e);
974
+ return false;
975
+ }
976
+ })
977
+ );
978
+ LOADED_BOOKS.set(bookId, p);
979
+ await p;
934
980
  };
935
981
  var loadPageContent2 = async ({
936
982
  bookId: iBookId,
@@ -964,28 +1010,37 @@ var initChannel = (__, { ports }) => {
964
1010
  self.postMessage({ methodName: "ready" });
965
1011
  return;
966
1012
  }
967
- if (canHandle(dataObj.data?.methodName)) {
1013
+ if (canHandle(dataObj.data?.methodName || "")) {
968
1014
  return handle(dataObj);
969
1015
  }
970
1016
  console.error("unknown methodName", dataObj.data?.methodName);
971
1017
  };
972
1018
  fetchWorker.onmessage = fn;
973
1019
  };
974
- var loadBooks = async () => {
1020
+ var loadBooks = async ({ primaryOnly = false } = {}) => {
975
1021
  let entries = [];
976
- if (NATIVE) {
1022
+ if (STORAGE === "native") {
977
1023
  entries = await sender.nativeListZips();
978
- } else {
1024
+ } else if (STORAGE === "opfs") {
979
1025
  const opfsRoot = await navigator.storage.getDirectory();
980
1026
  entries = await opfsRoot.values();
1027
+ } else {
1028
+ const cache = await openCache(CONF_ENV);
1029
+ const keys = await cache.keys();
1030
+ const zips = keys.filter((key2) => {
1031
+ return key2.url.match(/lecture-cp\.zip/);
1032
+ });
1033
+ entries = zips.map((zip) => ({ name: String(zip.url.split("/").pop()) }));
981
1034
  }
982
1035
  for await (const entry of entries) {
983
1036
  if (/preload/.test(entry.name)) {
984
1037
  continue;
985
1038
  }
986
1039
  if (/\d+\.zip$/.test(entry.name)) {
987
- const bookId = entry.name.match(/\d+/)[0];
988
- await loadBook2({ id: Number.parseInt(bookId, 10) });
1040
+ if (!primaryOnly) {
1041
+ const bookId = entry.name.match(/\d+/)[0];
1042
+ await loadBook2({ id: Number.parseInt(bookId, 10) });
1043
+ }
989
1044
  } else if (/zip$/.test(entry.name)) {
990
1045
  const methodUri = entry.name.replace(".zip", "");
991
1046
  await loadBook2({ id: methodUri });
@@ -994,14 +1049,29 @@ var loadBooks = async () => {
994
1049
  }
995
1050
  }
996
1051
  };
997
- var configure = ({ native, checkUpdates }) => {
998
- console.info("configuring!!", native, checkUpdates);
999
- if (typeof native !== "undefined") {
1000
- NATIVE = !!native;
1052
+ var configure = ({
1053
+ storage,
1054
+ checkUpdates,
1055
+ zipBaseUrl,
1056
+ confEnv
1057
+ }) => {
1058
+ CHECK_UPDATES = true;
1059
+ STORAGE = "cache";
1060
+ ZIP_BASE_URL = "https://ci.lls.fr/artefacts/content/development";
1061
+ CONF_ENV = "development";
1062
+ if (typeof storage !== "undefined") {
1063
+ STORAGE = storage;
1001
1064
  }
1002
- if (typeof native !== "undefined") {
1065
+ if (typeof checkUpdates !== "undefined") {
1003
1066
  CHECK_UPDATES = !!checkUpdates;
1004
1067
  }
1068
+ if (typeof zipBaseUrl !== "undefined") {
1069
+ ZIP_BASE_URL = zipBaseUrl;
1070
+ }
1071
+ if (typeof confEnv !== "undefined") {
1072
+ CONF_ENV = confEnv;
1073
+ }
1074
+ console.info("ow::configure", { STORAGE, CHECK_UPDATES, ZIP_BASE_URL, CONF_ENV });
1005
1075
  };
1006
1076
  var loadPreload2 = async () => {
1007
1077
  const zipUrl = "https://lls-ci-fr.s3.eu-west-3.amazonaws.com/artefacts/content/development/preload.zip";
package/lib/index.js CHANGED
@@ -1,3 +1,14 @@
1
+ // src/utils/error.ts
2
+ var tryCatch = (fn) => {
3
+ return (resolve, reject) => {
4
+ try {
5
+ return fn().then(resolve);
6
+ } catch (e) {
7
+ return reject(e);
8
+ }
9
+ };
10
+ };
11
+
1
12
  // src/utils/worker.ts
2
13
  var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
3
14
  const dic = {};
@@ -11,7 +22,7 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
11
22
  }
12
23
  } else {
13
24
  if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
14
- console.error(`syncWorker: ${dbg}`, eventId, methodName, error);
25
+ console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
15
26
  }
16
27
  }
17
28
  methodName && cbks[methodName]?.(arg);
@@ -19,7 +30,7 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
19
30
  const makeFn = (methodName) => {
20
31
  return ({ ports, ...arg } = {}) => {
21
32
  return new Promise((resolve, reject) => {
22
- const eventId = `${methodName}:${Date.now()}`;
33
+ const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
23
34
  dic[eventId] = { resolve, reject };
24
35
  offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
25
36
  });
@@ -62,9 +73,9 @@ var makeSyncHandler = a(
62
73
  })
63
74
  );
64
75
  var combineHandlers = (...handlers) => {
65
- return ({ data: { methodName, ...data }, ports }) => {
76
+ return ({ data: { methodName, ...data } = {}, ports }) => {
66
77
  for (const h of handlers) {
67
- if (h.canHandle(methodName)) {
78
+ if (h.canHandle(String(methodName))) {
68
79
  return h.handle({ data: { methodName, ...data }, ports });
69
80
  }
70
81
  }
@@ -76,11 +87,28 @@ var combineHandlers = (...handlers) => {
76
87
  import { useCallback, useEffect, useState } from "react";
77
88
  var offlineWorker = new Worker("/src/worker/offline-worker.js", { type: "module" });
78
89
  var { handler, ...offlineWorkerSync } = makeSyncWorker(
79
- // @ts-ignore: TODO
80
90
  offlineWorker,
81
91
  ["initChannel", "readZipEntry", "configure", "loadPreload", "loadBooks", "loadBook"],
82
92
  "app:offlineWorker"
83
93
  );
94
+ var fetchWorkerPromise = new Promise(
95
+ tryCatch(async () => {
96
+ let fetchWorker = null;
97
+ await navigator.serviceWorker.register("/fetch-worker.js", { type: "module" }).then((reg) => {
98
+ return reg.update().catch((e) => {
99
+ console.debug(e, "likely we are offline");
100
+ });
101
+ });
102
+ await navigator.serviceWorker.ready.then(async (reg) => {
103
+ console.debug("Service Worker registered:", reg.scope);
104
+ fetchWorker = reg.active;
105
+ }).catch((err) => console.error("Service Worker registration failed:", err));
106
+ if (!fetchWorker) {
107
+ console.debug("failed to get fetch worker");
108
+ }
109
+ return fetchWorker;
110
+ })
111
+ );
84
112
  var setWorkersReady;
85
113
  var workersReadyPromise = new Promise((resolve) => {
86
114
  setWorkersReady = resolve;
@@ -90,61 +118,80 @@ var setBooksLoadedPromise = new Promise((resolve) => {
90
118
  setBooksLoaded = resolve;
91
119
  });
92
120
  var installWorker = async ({
93
- skipPreload = false,
121
+ skipLoadBooks = false,
122
+ primaryOnly = false,
94
123
  nativeReadFile,
95
124
  nativeWriteFile,
96
125
  nativeListZips,
97
- checkUpdates
126
+ storage,
127
+ zipBaseUrl,
128
+ checkUpdates,
129
+ confEnv
98
130
  } = {}) => {
99
- let fetchWorker = null;
100
- await navigator.serviceWorker.getRegistrations().then(async (registrations) => {
101
- for (const registration of registrations) {
102
- await registration.unregister();
103
- }
104
- });
105
- await navigator.serviceWorker.register("/fetch-worker.js", { type: "module" }).then((reg) => reg.update());
106
- await navigator.serviceWorker.ready.then(async (reg) => {
107
- console.debug("Service Worker registered:", reg.scope);
108
- fetchWorker = reg.active;
109
- }).catch((err) => console.error("Service Worker registration failed:", err));
110
- if (!fetchWorker) {
111
- console.debug("failed to get fetch worker");
112
- return;
113
- }
131
+ const fetchWorker = await fetchWorkerPromise;
132
+ const configureOptions = { storage, zipBaseUrl, checkUpdates, confEnv };
114
133
  if (nativeReadFile) {
115
134
  const wwHandler = makeSyncHandler(offlineWorker, { nativeReadFile, nativeWriteFile, nativeListZips });
116
135
  offlineWorker.onmessage = combineHandlers(handler, wwHandler);
117
- offlineWorkerSync.configure({ native: true, checkUpdates });
136
+ if (storage !== "native") {
137
+ console.warn("nativeReadFile given, ignoring storage", storage);
138
+ }
139
+ offlineWorkerSync.configure({ ...configureOptions, storage: "native" });
118
140
  } else {
119
- offlineWorkerSync.configure({ checkUpdates });
120
141
  offlineWorker.onmessage = handler.handle;
142
+ offlineWorkerSync.configure(configureOptions);
121
143
  }
122
144
  offlineWorkerSync.on("ready", setWorkersReady);
123
145
  const channel = new MessageChannel();
124
146
  offlineWorkerSync.initChannel({ ports: [channel.port1] });
125
147
  fetchWorker.postMessage({ methodName: "initChannel" }, [channel.port2]);
148
+ fetchWorker.postMessage({ methodName: "configure", arg: { storage } });
126
149
  offlineWorkerSync.on("opfsProgress", () => {
127
150
  });
128
- if (!skipPreload) {
151
+ if (!primaryOnly) {
129
152
  await offlineWorkerSync.loadPreload();
130
153
  }
131
- await offlineWorkerSync.loadBooks();
154
+ if (!skipLoadBooks) {
155
+ await offlineWorkerSync.loadBooks({ primaryOnly });
156
+ }
132
157
  setBooksLoaded();
133
158
  };
134
- var useOfflineWorker_default = ({ skipPreload = false, nativeReadFile, nativeWriteFile, nativeListZips, checkUpdates } = {}) => {
159
+ var useOfflineWorker_default = ({
160
+ primaryOnly = false,
161
+ nativeReadFile,
162
+ nativeWriteFile,
163
+ nativeListZips,
164
+ checkUpdates,
165
+ storage,
166
+ zipBaseUrl,
167
+ confEnv
168
+ } = {}) => {
135
169
  const [ready, setReady] = useState(false);
136
170
  useEffect(() => {
137
171
  const fn = async () => {
138
- await installWorker({ skipPreload, nativeReadFile, nativeWriteFile, nativeListZips, checkUpdates });
172
+ await installWorker({
173
+ primaryOnly,
174
+ nativeReadFile,
175
+ nativeWriteFile,
176
+ nativeListZips,
177
+ storage,
178
+ zipBaseUrl,
179
+ checkUpdates,
180
+ confEnv
181
+ });
139
182
  await Promise.all([workersReadyPromise, setBooksLoadedPromise]);
183
+ fetch(window.location.href.replace(window.location.search, ""));
140
184
  setReady(true);
141
185
  };
142
186
  fn();
143
- }, [skipPreload, nativeReadFile, nativeWriteFile, nativeListZips, checkUpdates]);
144
- const loadBook = useCallback(async ({ id, forceRefresh }, cbk) => {
145
- offlineWorkerSync.on("opfsProgress", cbk);
146
- return await offlineWorkerSync.loadBook({ id, forceRefresh });
147
- }, []);
187
+ }, [primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, storage, zipBaseUrl, checkUpdates, confEnv]);
188
+ const loadBook = useCallback(
189
+ async ({ id, forceRefresh }, cbk) => {
190
+ offlineWorkerSync.on("opfsProgress", cbk);
191
+ return await offlineWorkerSync.loadBook({ id, forceRefresh });
192
+ },
193
+ []
194
+ );
148
195
  return {
149
196
  ready,
150
197
  loadBook
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lls/offline",
3
- "version": "24.20.0-debug2",
3
+ "version": "24.22.0-4a703cc1",
4
4
  "description": "offline",
5
5
  "main": "./lib/index.js",
6
6
  "module": "./lib/index.js",
@@ -52,7 +52,7 @@
52
52
  "vite": "7.0.0",
53
53
  "vite-tsconfig-paths": "5.1.4",
54
54
  "zod": "3.25.50",
55
- "@lls/vitescripts": "24.22.0"
55
+ "@lls/vitescripts": "24.22.0-4a703cc1"
56
56
  },
57
57
  "dependencies": {
58
58
  "fflate": "0.8.2"