@lls/offline 24.22.0-5bf1d034 → 24.22.0-658f61a6

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.
@@ -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
  };
@@ -276,7 +291,42 @@ var ACCEPTED_EXTENSIONS = {
276
291
  ".gif": "image/gif",
277
292
  ".webm": "video/webm",
278
293
  ".m4a": "audio/mp4",
279
- ".webp": "image/webp"
294
+ ".webp": "image/webp",
295
+ ".riv": "application/octet-stream",
296
+ ".pdf": "application/pdf"
297
+ };
298
+ var putInCache = async (request, response) => {
299
+ const cache = await openCache(CONF_ENV);
300
+ await cache.put(request, response);
301
+ };
302
+ var onlineFirst = async (request, event) => {
303
+ try {
304
+ const responseFromNetwork = await fetch(request);
305
+ const cached = putInCache(request, responseFromNetwork.clone());
306
+ if (request.url.endsWith(".zip")) {
307
+ await cached;
308
+ event.waitUntil(Promise.resolve());
309
+ } else {
310
+ event.waitUntil(cached);
311
+ }
312
+ return responseFromNetwork;
313
+ } catch (e) {
314
+ const over = /\/\?story=basic/.test(request.url) ? request.url.replace(/\/\?story=.*$/, "") : request;
315
+ const responseFromCache = await caches.match(over);
316
+ if (!responseFromCache) {
317
+ return new Response(
318
+ `FAILED fetch ${e && typeof e === "object" && "message" in e ? e.message : e?.toString?.()}`,
319
+ {
320
+ // @ts-ignore: poor ts typing
321
+ ok: false,
322
+ status: 500,
323
+ type: "cors",
324
+ url: event.request.url
325
+ }
326
+ );
327
+ }
328
+ return responseFromCache;
329
+ }
280
330
  };
281
331
  var swSelf = self;
282
332
  swSelf.addEventListener("fetch", (ev) => {
@@ -284,30 +334,33 @@ swSelf.addEventListener("fetch", (ev) => {
284
334
  const extension = url.match(/\.[^.]+$/)?.[0];
285
335
  const mimeType = extension && extension in ACCEPTED_EXTENSIONS ? ACCEPTED_EXTENSIONS[extension] : "";
286
336
  const isValidScreenCall = canHandleScreenCall(url);
337
+ if (STORAGE !== "cache" && url.endsWith(".zip")) {
338
+ return;
339
+ }
287
340
  if (!mimeType && !isValidScreenCall) {
288
341
  if (!/\.(css|tsx?|jsx?|woff2|fr|manifest)(\??|$)|@(vite|react|id)/.test(url)) {
289
342
  console.debug("no ext", extension || url);
290
343
  }
291
- return;
344
+ return ev.respondWith(onlineFirst(ev.request, ev));
292
345
  }
293
346
  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
347
  if (isValidScreenCall) {
301
348
  console.debug("screencall!");
302
349
  return ev.respondWith(fetchScreenCall(ev, { offlineWorkerSync, knownLinks }));
303
350
  }
304
- return ev.respondWith(fetchAsset(ev, { entryId, mimeType, offlineWorkerSync }));
351
+ if (entryId) {
352
+ return ev.respondWith(fetchAsset(ev, { entryId, mimeType, offlineWorkerSync }));
353
+ }
354
+ if (/lls|lelivrescolaire/.test(url)) {
355
+ console.info("no link for", url.replace(/https:\/\/.*?\//, ""), `medias/${url.replace(/https:\/\/.*?\//, "")}`);
356
+ }
357
+ return;
305
358
  });
306
359
  swSelf.addEventListener("install", (_event) => {
307
360
  swSelf.skipWaiting();
308
361
  });
309
- swSelf.addEventListener("activate", (_event) => {
310
- return swSelf.clients.claim();
362
+ swSelf.addEventListener("activate", (event) => {
363
+ event.waitUntil(swSelf.clients.claim());
311
364
  });
312
365
  var offlineWorker;
313
366
  var offlineWorkerSync;
@@ -321,24 +374,37 @@ var initChannel = async (__, { ports }) => {
321
374
  );
322
375
  offlineWorkerSync = sender;
323
376
  offlineWorker.onmessage = handler.handle;
324
- offlineWorkerSync.on("setKnownLinks", setKnownLinks);
377
+ offlineWorkerSync.on("setKnownLinks", setKnownLinks.bind(null, knownLinks));
325
378
  offlineWorker.postMessage({ methodName: "sendAppReady" });
326
379
  };
327
380
  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
- });
381
+ var configure = ({
382
+ storage,
383
+ confEnv
384
+ } = {}) => {
385
+ STORAGE = "cache";
386
+ CONF_ENV = "development";
387
+ if (typeof storage !== "undefined") {
388
+ STORAGE = storage;
389
+ }
390
+ if (typeof confEnv !== "undefined") {
391
+ CONF_ENV = confEnv;
392
+ }
393
+ console.info("sw::configure", { STORAGE, CONF_ENV });
333
394
  };
334
395
  swSelf.onmessage = async ({
335
396
  data: { methodName, arg } = {},
336
397
  ports
337
398
  }) => {
338
399
  const services = {
339
- initChannel
400
+ initChannel,
401
+ configure
340
402
  };
341
403
  try {
404
+ if (methodName === "configure") {
405
+ await services[methodName](arg);
406
+ return;
407
+ }
342
408
  if (methodName === "initChannel") {
343
409
  if (!ports.length) {
344
410
  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 = {};
@@ -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
  });
@@ -72,6 +83,17 @@ var combineHandlers = (...handlers) => {
72
83
  console.error("no handlers matched", methodName);
73
84
  };
74
85
  };
86
+ 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
+ };
75
97
 
76
98
  // src/worker/wwApi.ts
77
99
  var STATE = {
@@ -753,6 +775,9 @@ async function decryptData(encryptedData, key2, iv2) {
753
775
  return decryptedData;
754
776
  }
755
777
  var BOOKS = {};
778
+ var zipNameInMedias = (zipName) => {
779
+ return /^(pages|book)/.test(zipName);
780
+ };
756
781
  var readZipEntry = async ({
757
782
  bookId,
758
783
  entryName,
@@ -762,9 +787,20 @@ var readZipEntry = async ({
762
787
  console.trace("no bookId", bookId, "found");
763
788
  return null;
764
789
  }
765
- const bookEntry = BOOKS[bookId].get(entryName);
790
+ const manifest = BOOKS[bookId].manifest;
791
+ let overridenName = entryName;
792
+ if (manifest && !entryName.includes("gqls/")) {
793
+ const zipName = entryName.replace("medias/", "");
794
+ if (zipNameInMedias(zipName)) {
795
+ const originalName = manifest.medias[zipName];
796
+ overridenName = `medias/${originalName}`;
797
+ } else {
798
+ console.error("any media should exist in manifest", zipName, entryName, bookId);
799
+ }
800
+ }
801
+ const bookEntry = BOOKS[bookId].map.get(overridenName);
766
802
  if (!bookEntry) {
767
- console.info("no bookEntry", entryName, "found");
803
+ console.info("no bookEntry", entryName, "found", overridenName);
768
804
  return null;
769
805
  }
770
806
  if (!bookEntry.decoded) {
@@ -775,16 +811,6 @@ var readZipEntry = async ({
775
811
  var hasZip = (bookId) => {
776
812
  return !!BOOKS[bookId];
777
813
  };
778
- function concatArrayBuffers(buffers) {
779
- const totalLength = buffers.reduce((sum, buf) => sum + buf.byteLength, 0);
780
- const concatenated = new Uint8Array(totalLength);
781
- let offset = 0;
782
- for (const buffer of buffers) {
783
- concatenated.set(new Uint8Array(buffer), offset);
784
- offset += buffer.byteLength;
785
- }
786
- return concatenated.buffer;
787
- }
788
814
  var loadZip = async (buf, bookId) => {
789
815
  const m = /* @__PURE__ */ new Map();
790
816
  const unzipper = new Unzip();
@@ -824,23 +850,42 @@ var loadZip = async (buf, bookId) => {
824
850
  await sleep;
825
851
  }
826
852
  unzipper.push(buf.slice((nChunks - 1) * chunkSize, buf.byteLength), true);
827
- BOOKS[bookId] = m;
828
- return [...m.keys()];
853
+ BOOKS[bookId] = {
854
+ map: m,
855
+ manifest: null
856
+ };
857
+ const manifestEntry = m.get("manifest.json");
858
+ const keys = [...m.keys()];
859
+ if (manifestEntry) {
860
+ manifestEntry.decoded = await decryptData(await manifestEntry.zipEntry.getData(), key, iv);
861
+ BOOKS[bookId].manifest = bufToJson(manifestEntry.decoded);
862
+ const { medias } = BOOKS[bookId].manifest;
863
+ keys.push(...Object.keys(medias).map((entryName) => `medias/${entryName}`));
864
+ }
865
+ return keys;
829
866
  };
830
867
 
831
868
  // src/worker/offline-worker.ts
832
869
  var CHECK_UPDATES = true;
833
- var NATIVE = false;
834
- var LOADED_BOOKS = /* @__PURE__ */ new Set();
870
+ var STORAGE = "cache";
871
+ var ZIP_BASE_URL = "https://ci.lls.fr/artefacts/content/development";
872
+ var CONF_ENV = "development";
873
+ var LOADED_BOOKS = /* @__PURE__ */ new Map();
835
874
  var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
836
875
  const fileName = zipUrl.split("/").slice(-1)[0];
837
876
  const readArrayBuffer = async () => {
838
- if (NATIVE) {
839
- console.debug("try reading native", fileName);
877
+ if (STORAGE === "native") {
840
878
  const back = await sender.nativeReadFile({ fname: fileName });
841
- console.debug("done readnig");
842
879
  return back;
843
880
  }
881
+ if (STORAGE === "cache") {
882
+ const response = await caches.match(zipUrl);
883
+ if (!response) {
884
+ throw new Error(`missing zip ${zipUrl}`);
885
+ }
886
+ const buf = await response.arrayBuffer();
887
+ return buf;
888
+ }
844
889
  const opfsRoot = await navigator.storage.getDirectory();
845
890
  const handle = await opfsRoot.getFileHandle(fileName);
846
891
  const accessHandle = await handle.createSyncAccessHandle();
@@ -868,7 +913,7 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
868
913
  return buf;
869
914
  } catch (e) {
870
915
  if (e instanceof Error && e.message !== "forceRefresh" && !e.message.includes("refetching")) {
871
- console.debug("opfs db not found, creating it", e, zipUrl);
916
+ console.debug(`${STORAGE} zip not found, creating it`, e, zipUrl);
872
917
  }
873
918
  const response = await fetch(zipUrl);
874
919
  const contentLength = +response.headers.get("Content-Length");
@@ -879,6 +924,7 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
879
924
  while (true) {
880
925
  const { done, value: chunk } = await reader.read();
881
926
  if (done) {
927
+ self.postMessage({ methodName: "opfsProgress", arg: { total: contentLength, bytes: contentLength } });
882
928
  break;
883
929
  }
884
930
  arrayBuffer.set(chunk, position);
@@ -893,44 +939,69 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
893
939
  console.debug(error);
894
940
  throw new Error(error);
895
941
  }
896
- if (NATIVE) {
942
+ if (STORAGE === "native") {
897
943
  await sender.nativeWriteFile({ fname: fileName, buffer: arrayBuffer });
898
- return await readArrayBuffer();
944
+ } else if (STORAGE === "opfs") {
945
+ const opfsRoot = await navigator.storage.getDirectory();
946
+ const handle = await opfsRoot.getFileHandle(fileName, { create: true });
947
+ const accessHandle = await handle.createSyncAccessHandle();
948
+ await accessHandle.truncate(0);
949
+ await accessHandle.write(arrayBuffer);
950
+ await accessHandle.close();
951
+ } else {
952
+ const cache = await openCache(CONF_ENV);
953
+ const response2 = new Response(arrayBuffer, {
954
+ headers: {
955
+ "Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
956
+ "Content-Type": "application/zip"
957
+ },
958
+ // @ts-ignore: poor ts typing
959
+ ok: true,
960
+ redirected: false,
961
+ status: 200,
962
+ type: "cors",
963
+ url: zipUrl
964
+ });
965
+ await cache.put(zipUrl, response2);
899
966
  }
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
967
  return await readArrayBuffer();
907
968
  }
908
969
  };
909
970
  var slugToFileMeta2 = async ({ slug }) => {
910
- console.debug("hereshit");
911
971
  const json = await readZipEntry({ bookId: "preload", entryName: "preload/hashmap_slugToMetaPage.json", asJson: true });
912
- console.debug("hereshit2");
913
972
  return slugToFileMeta({ slug, json });
914
973
  };
915
974
  var PRELOAD_AS_BOOK_ID = "preload";
916
975
  var isOldZip = (bookId) => typeof bookId === "number";
917
976
  var loadBook2 = async ({ id: bookId, forceRefresh }) => {
918
977
  if (LOADED_BOOKS.has(bookId)) {
978
+ await LOADED_BOOKS.get(bookId);
919
979
  self.postMessage({ methodName: "opfsProgress", arg: { total: 1, bytes: 1 } });
920
980
  return;
921
981
  }
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
- }
982
+ const p = new Promise(
983
+ tryCatch(async () => {
984
+ const buf = await fetchZipFile(`${ZIP_BASE_URL}/${bookId}.zip`, { forceRefresh });
985
+ try {
986
+ const entryNames = await loadZip(new Uint8Array(buf), bookId);
987
+ fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId } });
988
+ if (isOldZip(bookId)) {
989
+ const json = await readZipEntry({
990
+ entryName: `preload/${bookId}.json`,
991
+ bookId: PRELOAD_AS_BOOK_ID,
992
+ asJson: true
993
+ });
994
+ loadBook(json);
995
+ }
996
+ return true;
997
+ } catch (e) {
998
+ console.error("failed", e);
999
+ return false;
1000
+ }
1001
+ })
1002
+ );
1003
+ LOADED_BOOKS.set(bookId, p);
1004
+ await p;
934
1005
  };
935
1006
  var loadPageContent2 = async ({
936
1007
  bookId: iBookId,
@@ -973,11 +1044,18 @@ var initChannel = (__, { ports }) => {
973
1044
  };
974
1045
  var loadBooks = async ({ primaryOnly = false } = {}) => {
975
1046
  let entries = [];
976
- if (NATIVE) {
1047
+ if (STORAGE === "native") {
977
1048
  entries = await sender.nativeListZips();
978
- } else {
1049
+ } else if (STORAGE === "opfs") {
979
1050
  const opfsRoot = await navigator.storage.getDirectory();
980
1051
  entries = await opfsRoot.values();
1052
+ } else {
1053
+ const cache = await openCache(CONF_ENV);
1054
+ const keys = await cache.keys();
1055
+ const zips = keys.filter((key2) => {
1056
+ return key2.url.match(/lecture-cp\.zip|story-.*?\.zip/);
1057
+ });
1058
+ entries = zips.map((zip) => ({ name: String(zip.url.split("/").pop()) }));
981
1059
  }
982
1060
  for await (const entry of entries) {
983
1061
  if (/preload/.test(entry.name)) {
@@ -996,14 +1074,29 @@ var loadBooks = async ({ primaryOnly = false } = {}) => {
996
1074
  }
997
1075
  }
998
1076
  };
999
- var configure = ({ native, checkUpdates }) => {
1000
- console.info("configuring!!", native, checkUpdates);
1001
- if (typeof native !== "undefined") {
1002
- NATIVE = !!native;
1077
+ var configure = ({
1078
+ storage,
1079
+ checkUpdates,
1080
+ zipBaseUrl,
1081
+ confEnv
1082
+ }) => {
1083
+ CHECK_UPDATES = true;
1084
+ STORAGE = "cache";
1085
+ ZIP_BASE_URL = "https://ci.lls.fr/artefacts/content/development";
1086
+ CONF_ENV = "development";
1087
+ if (typeof storage !== "undefined") {
1088
+ STORAGE = storage;
1003
1089
  }
1004
- if (typeof native !== "undefined") {
1090
+ if (typeof checkUpdates !== "undefined") {
1005
1091
  CHECK_UPDATES = !!checkUpdates;
1006
1092
  }
1093
+ if (typeof zipBaseUrl !== "undefined") {
1094
+ ZIP_BASE_URL = zipBaseUrl;
1095
+ }
1096
+ if (typeof confEnv !== "undefined") {
1097
+ CONF_ENV = confEnv;
1098
+ }
1099
+ console.info("ow::configure", { STORAGE, CHECK_UPDATES, ZIP_BASE_URL, CONF_ENV });
1007
1100
  };
1008
1101
  var loadPreload2 = async () => {
1009
1102
  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 = {};
@@ -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
  });
@@ -76,11 +87,31 @@ 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.getRegistrations().then((registrations) => {
98
+ return Promise.all(registrations.map((reg) => reg.unregister()));
99
+ });
100
+ await navigator.serviceWorker.register("/fetch-worker.js", { type: "module" }).then((reg) => {
101
+ return reg.update().catch((e) => {
102
+ console.debug(e, "likely we are offline");
103
+ });
104
+ });
105
+ await navigator.serviceWorker.ready.then(async (reg) => {
106
+ console.debug("Service Worker registered:", reg.scope);
107
+ fetchWorker = reg.active;
108
+ }).catch((err) => console.error("Service Worker registration failed:", err));
109
+ if (!fetchWorker) {
110
+ console.debug("failed to get fetch worker");
111
+ }
112
+ return fetchWorker;
113
+ })
114
+ );
84
115
  var setWorkersReady;
85
116
  var workersReadyPromise = new Promise((resolve) => {
86
117
  setWorkersReady = resolve;
@@ -90,45 +121,42 @@ var setBooksLoadedPromise = new Promise((resolve) => {
90
121
  setBooksLoaded = resolve;
91
122
  });
92
123
  var installWorker = async ({
124
+ skipLoadBooks = false,
93
125
  primaryOnly = false,
94
126
  nativeReadFile,
95
127
  nativeWriteFile,
96
128
  nativeListZips,
97
- checkUpdates
129
+ storage,
130
+ zipBaseUrl,
131
+ checkUpdates,
132
+ confEnv
98
133
  } = {}) => {
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
- }
134
+ const fetchWorker = await fetchWorkerPromise;
135
+ const configureOptions = { storage, zipBaseUrl, checkUpdates, confEnv };
114
136
  if (nativeReadFile) {
115
137
  const wwHandler = makeSyncHandler(offlineWorker, { nativeReadFile, nativeWriteFile, nativeListZips });
116
138
  offlineWorker.onmessage = combineHandlers(handler, wwHandler);
117
- offlineWorkerSync.configure({ native: true, checkUpdates });
139
+ if (storage !== "native") {
140
+ console.warn("nativeReadFile given, ignoring storage", storage);
141
+ }
142
+ offlineWorkerSync.configure({ ...configureOptions, storage: "native" });
118
143
  } else {
119
- offlineWorkerSync.configure({ native: false, checkUpdates });
120
144
  offlineWorker.onmessage = handler.handle;
145
+ offlineWorkerSync.configure(configureOptions);
121
146
  }
122
147
  offlineWorkerSync.on("ready", setWorkersReady);
123
148
  const channel = new MessageChannel();
124
149
  offlineWorkerSync.initChannel({ ports: [channel.port1] });
125
150
  fetchWorker.postMessage({ methodName: "initChannel" }, [channel.port2]);
151
+ fetchWorker.postMessage({ methodName: "configure", arg: { storage } });
126
152
  offlineWorkerSync.on("opfsProgress", () => {
127
153
  });
128
154
  if (!primaryOnly) {
129
155
  await offlineWorkerSync.loadPreload();
130
156
  }
131
- await offlineWorkerSync.loadBooks();
157
+ if (!skipLoadBooks) {
158
+ await offlineWorkerSync.loadBooks({ primaryOnly });
159
+ }
132
160
  setBooksLoaded();
133
161
  };
134
162
  var useOfflineWorker_default = ({
@@ -136,17 +164,30 @@ var useOfflineWorker_default = ({
136
164
  nativeReadFile,
137
165
  nativeWriteFile,
138
166
  nativeListZips,
139
- checkUpdates
167
+ checkUpdates,
168
+ storage,
169
+ zipBaseUrl,
170
+ confEnv
140
171
  } = {}) => {
141
172
  const [ready, setReady] = useState(false);
142
173
  useEffect(() => {
143
174
  const fn = async () => {
144
- await installWorker({ primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, checkUpdates });
175
+ await installWorker({
176
+ primaryOnly,
177
+ nativeReadFile,
178
+ nativeWriteFile,
179
+ nativeListZips,
180
+ storage,
181
+ zipBaseUrl,
182
+ checkUpdates,
183
+ confEnv
184
+ });
145
185
  await Promise.all([workersReadyPromise, setBooksLoadedPromise]);
186
+ fetch(window.location.href.replace(window.location.search, ""));
146
187
  setReady(true);
147
188
  };
148
189
  fn();
149
- }, [primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, checkUpdates]);
190
+ }, [primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, storage, zipBaseUrl, checkUpdates, confEnv]);
150
191
  const loadBook = useCallback(
151
192
  async ({ id, forceRefresh }, cbk) => {
152
193
  offlineWorkerSync.on("opfsProgress", cbk);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lls/offline",
3
- "version": "24.22.0-5bf1d034",
3
+ "version": "24.22.0-658f61a6",
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-5bf1d034"
55
+ "@lls/vitescripts": "24.22.0-658f61a6"
56
56
  },
57
57
  "dependencies": {
58
58
  "fflate": "0.8.2"