@lls/offline 24.22.0-bdccc0f6 → 24.22.0-debug

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,15 +11,16 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
11
11
  }
12
12
  } else {
13
13
  if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
14
- console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
14
+ console.error(`syncWorker: ${dbg}`, eventId, methodName, error);
15
15
  }
16
16
  }
17
17
  methodName && cbks[methodName]?.(arg);
18
18
  };
19
+ offlineWorker2.onmessage = fn;
19
20
  const makeFn = (methodName) => {
20
21
  return ({ ports, ...arg } = {}) => {
21
22
  return new Promise((resolve, reject) => {
22
- const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
23
+ const eventId = `${methodName}:${Date.now()}`;
23
24
  dic[eventId] = { resolve, reject };
24
25
  offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
25
26
  });
@@ -30,39 +31,25 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
30
31
  cbks[key] = fn2;
31
32
  }
32
33
  });
33
- return {
34
- ...obj,
35
- handler: {
36
- canHandle: (methodName) => {
37
- return methodName in cbks || methodNames.includes(methodName);
38
- },
39
- handle: fn
40
- }
41
- };
34
+ return obj;
42
35
  };
43
36
  var a = (a2) => a2;
44
37
  var makeSyncHandler = a(
45
- (sender, services) => ({
46
- canHandle: (methodName) => {
47
- return methodName in services;
48
- },
49
- handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
50
- try {
51
- if (typeof methodName === "string" && methodName in services && services[methodName]) {
52
- const results = await services[methodName](arg, { ports });
53
- sender.postMessage({ eventId, methodName, results });
54
- return;
55
- }
56
- throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
57
- } catch (e) {
58
- console.debug("failed", e);
59
- sender.postMessage({ eventId, methodName, error: e });
38
+ (sender, services) => async ({ data: { eventId, methodName, arg } = {}, ports }) => {
39
+ try {
40
+ if (typeof methodName === "string" && methodName in services && services[methodName]) {
41
+ const results = await services[methodName](arg, { ports });
42
+ sender.postMessage({ eventId, methodName, results });
43
+ return;
60
44
  }
45
+ throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
46
+ } catch (e) {
47
+ console.debug("failed", e);
48
+ sender.postMessage({ eventId, methodName, error: e });
61
49
  }
62
- })
50
+ }
63
51
  );
64
52
  var jsonToBuf = (json) => new TextEncoder().encode(JSON.stringify(json));
65
- var openCache = async (mode) => caches.open(`LLS_OFFLINE_${mode}`);
66
53
 
67
54
  // src/worker/swFetchScreenCall.ts
68
55
  var zipSlugName = (str) => str.replace(/[^a-zA-Z0-9-.]/g, "");
@@ -75,15 +62,6 @@ var cocoreFetchPartialPageScreen = async (_ev, { offlineWorkerSync: offlineWorke
75
62
  const gqlPage = await offlineWorkerSync2.loadPageContent({ entryId });
76
63
  return { data: gqlPage };
77
64
  };
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
- };
87
65
  var cocoreFetchPartialPageSiblingsScreen = async (_ev) => {
88
66
  return { data: { viewer: {} } };
89
67
  };
@@ -198,7 +176,6 @@ var coreFetchPagesApi = async (ev, { offlineWorkerSync: offlineWorkerSync2, vari
198
176
  };
199
177
  var handles = {
200
178
  coreFetchBookDrawerScreen,
201
- cocoreFetchBookScreen,
202
179
  coreFetchPagesApi,
203
180
  cocoreFetchPartialPageScreen,
204
181
  cocoreFetchPartialPageSiblingsScreen
@@ -207,7 +184,7 @@ var canHandleScreenCall = (url) => new RegExp(Object.keys(handles).join("|")).te
207
184
  var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, knownLinks: knownLinks2 }) => {
208
185
  try {
209
186
  const variables = (
210
- // @ts-expect-error TODO: handle variables from body(stream) properly!!
187
+ // @ts-ignore TODO: handle variables from body(stream) properly!!
211
188
  ev.request.body?.variables || JSON.parse(new URLSearchParams(new URL(ev.request.url).search).get("variables") || "{}")
212
189
  );
213
190
  console.debug("variables are", variables, "vs", ev.request.body);
@@ -222,28 +199,23 @@ var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, known
222
199
  return { status: 500, json: JSON.stringify(e) };
223
200
  }
224
201
  };
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
- };
231
202
 
232
203
  // src/worker/fetch-worker.ts
233
- var STORAGE = "cache";
234
- var CONF_ENV = "development";
235
- var fetchAsset = async (_ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
204
+ var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
236
205
  try {
237
206
  const buf = await offlineWorkerSync2.readZipEntry(entryId);
207
+ const url = ev.request.url;
238
208
  return new Response(buf, {
239
209
  headers: {
240
210
  "Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
241
- "Content-Length": buf.byteLength,
242
211
  "Content-Type": mimeType
243
212
  },
244
213
  // @ts-ignore: poor ts typing
214
+ ok: true,
215
+ redirected: false,
245
216
  status: 200,
246
- statusText: "OK"
217
+ type: "cors",
218
+ url
247
219
  });
248
220
  } catch (e) {
249
221
  console.error("FAILED fetchAsset", e);
@@ -251,7 +223,8 @@ var fetchAsset = async (_ev, { entryId, mimeType, offlineWorkerSync: offlineWork
251
223
  // @ts-ignore: poor ts typing
252
224
  ok: false,
253
225
  status: 500,
254
- statusText: e?.toString?.() || "fail fetch"
226
+ type: "cors",
227
+ url: ev.request.url
255
228
  });
256
229
  }
257
230
  };
@@ -291,42 +264,7 @@ var ACCEPTED_EXTENSIONS = {
291
264
  ".gif": "image/gif",
292
265
  ".webm": "video/webm",
293
266
  ".m4a": "audio/mp4",
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
- }
267
+ ".webp": "image/webp"
330
268
  };
331
269
  var swSelf = self;
332
270
  swSelf.addEventListener("fetch", (ev) => {
@@ -334,77 +272,59 @@ swSelf.addEventListener("fetch", (ev) => {
334
272
  const extension = url.match(/\.[^.]+$/)?.[0];
335
273
  const mimeType = extension && extension in ACCEPTED_EXTENSIONS ? ACCEPTED_EXTENSIONS[extension] : "";
336
274
  const isValidScreenCall = canHandleScreenCall(url);
337
- if (STORAGE !== "cache" && url.endsWith(".zip")) {
338
- return;
339
- }
340
275
  if (!mimeType && !isValidScreenCall) {
341
- if (!/\.(css|tsx?|jsx?|woff2|fr|manifest)(\??|$)|@(vite|react|id)/.test(url)) {
342
- console.debug("no ext", extension || url);
276
+ if (!/\.(css|tsx?|jsx?|woff2|fr)/.test(url)) {
277
+ console.debug("no ext", url.match(/\.[^.]+$/)?.[0]);
343
278
  }
344
- return ev.respondWith(onlineFirst(ev.request, ev));
279
+ return;
345
280
  }
346
281
  const entryId = knownLinks.get(url.replace(/https:\/\/.*?\//, "")) || knownLinks.get(`medias/${url.replace(/https:\/\/.*?\//, "")}`);
282
+ if (!entryId && !isValidScreenCall) {
283
+ if (/lls|lelivrescolaire/.test(url)) {
284
+ console.info("no link for", url.replace(/https:\/\/.*?\//, ""));
285
+ }
286
+ return;
287
+ }
347
288
  if (isValidScreenCall) {
348
289
  console.debug("screencall!");
349
290
  return ev.respondWith(fetchScreenCall(ev, { offlineWorkerSync, knownLinks }));
350
291
  }
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;
292
+ return ev.respondWith(fetchAsset(ev, { entryId, mimeType, offlineWorkerSync }));
358
293
  });
359
294
  swSelf.addEventListener("install", (_event) => {
360
295
  swSelf.skipWaiting();
361
296
  });
362
- swSelf.addEventListener("activate", (event) => {
363
- event.waitUntil(swSelf.clients.claim());
297
+ swSelf.addEventListener("activate", (_event) => {
298
+ return swSelf.clients.claim();
364
299
  });
365
300
  var offlineWorker;
366
301
  var offlineWorkerSync;
367
302
  var initChannel = async (__, { ports }) => {
368
- console.info(`FETCH WORKER innitChannel v${/* @__PURE__ */ new Date()}`);
303
+ console.info(`FETCH WORKER innitChannel v${13}`);
369
304
  offlineWorker = ports[0];
370
- const { handler, ...sender } = makeSyncWorker(
305
+ offlineWorkerSync = makeSyncWorker(
371
306
  offlineWorker,
372
307
  ["readZipEntry", "loadBook", "slugToFileMeta", "loadPageContent", "searchBooks"],
373
308
  "fetchworker"
374
309
  );
375
- offlineWorkerSync = sender;
376
- offlineWorker.onmessage = handler.handle;
377
- offlineWorkerSync.on("setKnownLinks", setKnownLinks.bind(null, knownLinks));
310
+ offlineWorkerSync.on("setKnownLinks", setKnownLinks);
378
311
  offlineWorker.postMessage({ methodName: "sendAppReady" });
379
312
  };
380
313
  var knownLinks = /* @__PURE__ */ new Map();
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 });
314
+ var setKnownLinks = ({ entryNames, bookId }) => {
315
+ entryNames.forEach((entryName) => {
316
+ const link = entryName.replace(/^\d+\/media\//, "");
317
+ knownLinks.set(link, { entryName, bookId });
318
+ });
394
319
  };
395
320
  swSelf.onmessage = async ({
396
321
  data: { methodName, arg } = {},
397
322
  ports
398
323
  }) => {
399
324
  const services = {
400
- initChannel,
401
- configure
325
+ initChannel
402
326
  };
403
327
  try {
404
- if (methodName === "configure") {
405
- await services[methodName](arg);
406
- return;
407
- }
408
328
  if (methodName === "initChannel") {
409
329
  if (!ports.length) {
410
330
  throw new Error("fetchworker::missing port!");
@@ -1,99 +1,21 @@
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
-
12
1
  // src/utils/worker.ts
13
- var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
14
- const dic = {};
15
- const cbks = {};
16
- const fn = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
17
- if (eventId && eventId in dic) {
18
- if (error) {
19
- dic[eventId].reject(error);
20
- } else {
21
- dic[eventId].resolve(results);
22
- }
23
- } else {
24
- if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
25
- console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
26
- }
27
- }
28
- methodName && cbks[methodName]?.(arg);
29
- };
30
- const makeFn = (methodName) => {
31
- return ({ ports, ...arg } = {}) => {
32
- return new Promise((resolve, reject) => {
33
- const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
34
- dic[eventId] = { resolve, reject };
35
- offlineWorker.postMessage({ eventId, methodName, arg }, ports);
36
- });
37
- };
38
- };
39
- const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
40
- on: (key2, fn2) => {
41
- cbks[key2] = fn2;
42
- }
43
- });
44
- return {
45
- ...obj,
46
- handler: {
47
- canHandle: (methodName) => {
48
- return methodName in cbks || methodNames.includes(methodName);
49
- },
50
- handle: fn
51
- }
52
- };
53
- };
54
2
  var a = (a2) => a2;
55
3
  var makeSyncHandler = a(
56
- (sender2, services) => ({
57
- canHandle: (methodName) => {
58
- return methodName in services;
59
- },
60
- handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
61
- try {
62
- if (typeof methodName === "string" && methodName in services && services[methodName]) {
63
- const results = await services[methodName](arg, { ports });
64
- sender2.postMessage({ eventId, methodName, results });
65
- return;
66
- }
67
- throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
68
- } catch (e) {
69
- console.debug("failed", e);
70
- sender2.postMessage({ eventId, methodName, error: e });
4
+ (sender, services) => async ({ data: { eventId, methodName, arg } = {}, ports }) => {
5
+ try {
6
+ if (typeof methodName === "string" && methodName in services && services[methodName]) {
7
+ const results = await services[methodName](arg, { ports });
8
+ sender.postMessage({ eventId, methodName, results });
9
+ return;
71
10
  }
11
+ throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
12
+ } catch (e) {
13
+ console.debug("failed", e);
14
+ sender.postMessage({ eventId, methodName, error: e });
72
15
  }
73
- })
16
+ }
74
17
  );
75
18
  var bufToJson = (buf) => JSON.parse(new TextDecoder("UTF-8").decode(new Uint8Array(buf)));
76
- var combineHandlers = (...handlers) => {
77
- return ({ data: { methodName, ...data } = {}, ports }) => {
78
- for (const h of handlers) {
79
- if (h.canHandle(String(methodName))) {
80
- return h.handle({ data: { methodName, ...data }, ports });
81
- }
82
- }
83
- console.error("no handlers matched", methodName);
84
- };
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
- };
97
19
 
98
20
  // src/worker/wwApi.ts
99
21
  var STATE = {
@@ -775,9 +697,6 @@ async function decryptData(encryptedData, key2, iv2) {
775
697
  return decryptedData;
776
698
  }
777
699
  var BOOKS = {};
778
- var zipNameInMedias = (zipName) => {
779
- return /^(pages|book)/.test(zipName);
780
- };
781
700
  var readZipEntry = async ({
782
701
  bookId,
783
702
  entryName,
@@ -787,20 +706,9 @@ var readZipEntry = async ({
787
706
  console.trace("no bookId", bookId, "found");
788
707
  return null;
789
708
  }
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);
709
+ const bookEntry = BOOKS[bookId].get(entryName);
802
710
  if (!bookEntry) {
803
- console.info("no bookEntry", entryName, "found", overridenName);
711
+ console.info("no bookEntry", entryName, "found");
804
712
  return null;
805
713
  }
806
714
  if (!bookEntry.decoded) {
@@ -811,14 +719,21 @@ var readZipEntry = async ({
811
719
  var hasZip = (bookId) => {
812
720
  return !!BOOKS[bookId];
813
721
  };
722
+ function concatArrayBuffers(buffers) {
723
+ const totalLength = buffers.reduce((sum, buf) => sum + buf.byteLength, 0);
724
+ const concatenated = new Uint8Array(totalLength);
725
+ let offset = 0;
726
+ for (const buffer of buffers) {
727
+ concatenated.set(new Uint8Array(buffer), offset);
728
+ offset += buffer.byteLength;
729
+ }
730
+ return concatenated.buffer;
731
+ }
814
732
  var loadZip = async (buf, bookId) => {
815
733
  const m = /* @__PURE__ */ new Map();
816
734
  const unzipper = new Unzip();
817
735
  unzipper.register(UnzipInflate);
818
736
  unzipper.onfile = (file) => {
819
- if (!file.name.includes(".")) {
820
- return;
821
- }
822
737
  m.set(file.name, {
823
738
  decoded: null,
824
739
  zipEntry: {
@@ -850,43 +765,16 @@ var loadZip = async (buf, bookId) => {
850
765
  await sleep;
851
766
  }
852
767
  unzipper.push(buf.slice((nChunks - 1) * chunkSize, buf.byteLength), true);
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;
768
+ BOOKS[bookId] = m;
769
+ return [...m.keys()];
866
770
  };
867
771
 
868
772
  // src/worker/offline-worker.ts
869
773
  var CHECK_UPDATES = true;
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();
874
- var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
774
+ var fetchZipFile = async (zipUrl, forceRefresh) => {
775
+ const opfsRoot = await navigator.storage.getDirectory();
875
776
  const fileName = zipUrl.split("/").slice(-1)[0];
876
777
  const readArrayBuffer = async () => {
877
- if (STORAGE === "native") {
878
- const back = await sender.nativeReadFile({ fname: fileName });
879
- return back;
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
- }
889
- const opfsRoot = await navigator.storage.getDirectory();
890
778
  const handle = await opfsRoot.getFileHandle(fileName);
891
779
  const accessHandle = await handle.createSyncAccessHandle();
892
780
  const size = accessHandle.getSize();
@@ -912,8 +800,8 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
912
800
  }
913
801
  return buf;
914
802
  } catch (e) {
915
- if (e instanceof Error && e.message !== "forceRefresh" && !e.message.includes("refetching")) {
916
- console.debug(`${STORAGE} zip not found, creating it`, e, zipUrl);
803
+ if (e.message !== "forceRefresh" && !e.message.includes("refetching")) {
804
+ console.debug("opfs db not found, creating it", e, zipUrl);
917
805
  }
918
806
  const response = await fetch(zipUrl);
919
807
  const contentLength = +response.headers.get("Content-Length");
@@ -924,7 +812,6 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
924
812
  while (true) {
925
813
  const { done, value: chunk } = await reader.read();
926
814
  if (done) {
927
- self.postMessage({ methodName: "opfsProgress", arg: { total: contentLength, bytes: contentLength } });
928
815
  break;
929
816
  }
930
817
  arrayBuffer.set(chunk, position);
@@ -939,69 +826,34 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
939
826
  console.debug(error);
940
827
  throw new Error(error);
941
828
  }
942
- if (STORAGE === "native") {
943
- await sender.nativeWriteFile({ fname: fileName, buffer: arrayBuffer });
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);
966
- }
829
+ const handle = await opfsRoot.getFileHandle(fileName, { create: true });
830
+ const accessHandle = await handle.createSyncAccessHandle();
831
+ await accessHandle.truncate(0);
832
+ await accessHandle.write(arrayBuffer);
833
+ await accessHandle.close();
967
834
  return await readArrayBuffer();
968
835
  }
969
836
  };
970
837
  var slugToFileMeta2 = async ({ slug }) => {
838
+ console.debug("hereshit");
971
839
  const json = await readZipEntry({ bookId: "preload", entryName: "preload/hashmap_slugToMetaPage.json", asJson: true });
840
+ console.debug("hereshit2");
972
841
  return slugToFileMeta({ slug, json });
973
842
  };
974
843
  var PRELOAD_AS_BOOK_ID = "preload";
975
844
  var isOldZip = (bookId) => typeof bookId === "number";
976
845
  var loadBook2 = async ({ id: bookId, forceRefresh }) => {
977
- if (LOADED_BOOKS.has(bookId)) {
978
- await LOADED_BOOKS.get(bookId);
979
- self.postMessage({ methodName: "opfsProgress", arg: { total: 1, bytes: 1 } });
980
- return;
846
+ const buf = await fetchZipFile(`https://ci.lls.fr/artefacts/content/development/${bookId}.zip`, forceRefresh);
847
+ try {
848
+ const entryNames = await loadZip(new Uint8Array(buf), bookId);
849
+ fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId } });
850
+ if (isOldZip(bookId)) {
851
+ const json = await readZipEntry({ entryName: `preload/${bookId}.json`, bookId: PRELOAD_AS_BOOK_ID, asJson: true });
852
+ loadBook(json);
853
+ }
854
+ } catch (e) {
855
+ console.error("failed", e);
981
856
  }
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;
1005
857
  };
1006
858
  var loadPageContent2 = async ({
1007
859
  bookId: iBookId,
@@ -1023,7 +875,7 @@ var loadPageContent2 = async ({
1023
875
  var fetchWorker;
1024
876
  var initChannel = (__, { ports }) => {
1025
877
  fetchWorker = ports[0];
1026
- const { canHandle, handle } = makeSyncHandler(fetchWorker, {
878
+ const defaultHandler = makeSyncHandler(fetchWorker, {
1027
879
  readZipEntry,
1028
880
  loadBook: loadBook2,
1029
881
  slugToFileMeta: slugToFileMeta2,
@@ -1035,69 +887,23 @@ var initChannel = (__, { ports }) => {
1035
887
  self.postMessage({ methodName: "ready" });
1036
888
  return;
1037
889
  }
1038
- if (canHandle(dataObj.data?.methodName || "")) {
1039
- return handle(dataObj);
1040
- }
1041
- console.error("unknown methodName", dataObj.data?.methodName);
890
+ defaultHandler(dataObj);
1042
891
  };
1043
892
  fetchWorker.onmessage = fn;
1044
893
  };
1045
- var loadBooks = async ({ primaryOnly = false } = {}) => {
1046
- let entries = [];
1047
- if (STORAGE === "native") {
1048
- entries = await sender.nativeListZips();
1049
- } else if (STORAGE === "opfs") {
1050
- const opfsRoot = await navigator.storage.getDirectory();
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()) }));
1059
- }
894
+ var loadBooks = async () => {
895
+ const opfsRoot = await navigator.storage.getDirectory();
896
+ const entries = await opfsRoot.values();
1060
897
  for await (const entry of entries) {
1061
- if (/preload/.test(entry.name)) {
1062
- continue;
1063
- }
1064
898
  if (/\d+\.zip$/.test(entry.name)) {
1065
- if (!primaryOnly) {
1066
- const bookId = entry.name.match(/\d+/)[0];
1067
- await loadBook2({ id: Number.parseInt(bookId, 10) });
1068
- }
899
+ const bookId = entry.name.match(/\d+/)[0];
900
+ await loadBook2({ id: Number.parseInt(bookId, 10) });
1069
901
  } else if (/zip$/.test(entry.name)) {
1070
902
  const methodUri = entry.name.replace(".zip", "");
1071
903
  await loadBook2({ id: methodUri });
1072
- } else {
1073
- console.info(`invalid book entry ${entry.name}`);
1074
904
  }
1075
905
  }
1076
906
  };
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;
1089
- }
1090
- if (typeof checkUpdates !== "undefined") {
1091
- CHECK_UPDATES = !!checkUpdates;
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 });
1100
- };
1101
907
  var loadPreload2 = async () => {
1102
908
  const zipUrl = "https://lls-ci-fr.s3.eu-west-3.amazonaws.com/artefacts/content/development/preload.zip";
1103
909
  const buf = await fetchZipFile(zipUrl);
@@ -1110,13 +916,10 @@ var loadPreload2 = async () => {
1110
916
  console.error("failed", e);
1111
917
  }
1112
918
  };
1113
- var mainHandle = makeSyncHandler(self, {
919
+ self.onmessage = makeSyncHandler(self, {
1114
920
  initChannel,
1115
921
  loadPreload: loadPreload2,
1116
922
  readZipEntry,
1117
923
  loadBooks,
1118
- loadBook: loadBook2,
1119
- configure
924
+ loadBook: loadBook2
1120
925
  });
1121
- var { handler, ...sender } = makeSyncWorker(self, ["nativeWriteFile", "nativeReadFile", "nativeListZips"]);
1122
- self.onmessage = combineHandlers(mainHandle, handler);
package/lib/index.js CHANGED
@@ -1,19 +1,8 @@
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
-
12
1
  // src/utils/worker.ts
13
- var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
2
+ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
14
3
  const dic = {};
15
4
  const cbks = {};
16
- const fn = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
5
+ const fn2 = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
17
6
  if (eventId && eventId in dic) {
18
7
  if (error) {
19
8
  dic[eventId].reject(error);
@@ -22,96 +11,53 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
22
11
  }
23
12
  } else {
24
13
  if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
25
- console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
14
+ console.error(`syncWorker: ${dbg}`, eventId, methodName, error);
26
15
  }
27
16
  }
28
17
  methodName && cbks[methodName]?.(arg);
29
18
  };
19
+ offlineWorker.onmessage = fn2;
30
20
  const makeFn = (methodName) => {
31
21
  return ({ ports, ...arg } = {}) => {
32
22
  return new Promise((resolve, reject) => {
33
- const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
23
+ const eventId = `${methodName}:${Date.now()}`;
34
24
  dic[eventId] = { resolve, reject };
35
- offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
25
+ offlineWorker.postMessage({ eventId, methodName, arg }, ports);
36
26
  });
37
27
  };
38
28
  };
39
29
  const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
40
- on: (key, fn2) => {
41
- cbks[key] = fn2;
30
+ on: (key, fn3) => {
31
+ cbks[key] = fn3;
42
32
  }
43
33
  });
44
- return {
45
- ...obj,
46
- handler: {
47
- canHandle: (methodName) => {
48
- return methodName in cbks || methodNames.includes(methodName);
49
- },
50
- handle: fn
51
- }
52
- };
34
+ return obj;
53
35
  };
54
36
  var a = (a2) => a2;
55
37
  var makeSyncHandler = a(
56
- (sender, services) => ({
57
- canHandle: (methodName) => {
58
- return methodName in services;
59
- },
60
- handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
61
- try {
62
- if (typeof methodName === "string" && methodName in services && services[methodName]) {
63
- const results = await services[methodName](arg, { ports });
64
- sender.postMessage({ eventId, methodName, results });
65
- return;
66
- }
67
- throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
68
- } catch (e) {
69
- console.debug("failed", e);
70
- sender.postMessage({ eventId, methodName, error: e });
38
+ (sender, services) => async ({ data: { eventId, methodName, arg } = {}, ports }) => {
39
+ try {
40
+ if (typeof methodName === "string" && methodName in services && services[methodName]) {
41
+ const results = await services[methodName](arg, { ports });
42
+ sender.postMessage({ eventId, methodName, results });
43
+ return;
71
44
  }
45
+ throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
46
+ } catch (e) {
47
+ console.debug("failed", e);
48
+ sender.postMessage({ eventId, methodName, error: e });
72
49
  }
73
- })
50
+ }
74
51
  );
75
- var combineHandlers = (...handlers) => {
76
- return ({ data: { methodName, ...data } = {}, ports }) => {
77
- for (const h of handlers) {
78
- if (h.canHandle(String(methodName))) {
79
- return h.handle({ data: { methodName, ...data }, ports });
80
- }
81
- }
82
- console.error("no handlers matched", methodName);
83
- };
84
- };
85
52
 
86
53
  // src/hooks/useOfflineWorker.ts
87
54
  import { useCallback, useEffect, useState } from "react";
88
- var offlineWorker = new Worker("/src/worker/offline-worker.js", { type: "module" });
89
- var { handler, ...offlineWorkerSync } = makeSyncWorker(
90
- offlineWorker,
91
- ["initChannel", "readZipEntry", "configure", "loadPreload", "loadBooks", "loadBook"],
55
+ var offlineWorkerSync = makeSyncWorker(
56
+ // @ts-ignore: TODO
57
+ new Worker("/src/worker/offline-worker.js", { type: "module" }),
58
+ ["initChannel", "readZipEntry", "loadPreload", "loadBooks", "loadBook"],
92
59
  "app:offlineWorker"
93
60
  );
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
- );
115
61
  var setWorkersReady;
116
62
  var workersReadyPromise = new Promise((resolve) => {
117
63
  setWorkersReady = resolve;
@@ -120,81 +66,46 @@ var setBooksLoaded;
120
66
  var setBooksLoadedPromise = new Promise((resolve) => {
121
67
  setBooksLoaded = resolve;
122
68
  });
123
- var installWorker = async ({
124
- skipLoadBooks = false,
125
- primaryOnly = false,
126
- nativeReadFile,
127
- nativeWriteFile,
128
- nativeListZips,
129
- storage,
130
- zipBaseUrl,
131
- checkUpdates,
132
- confEnv
133
- } = {}) => {
134
- const fetchWorker = await fetchWorkerPromise;
135
- const configureOptions = { storage, zipBaseUrl, checkUpdates, confEnv };
136
- if (nativeReadFile) {
137
- const wwHandler = makeSyncHandler(offlineWorker, { nativeReadFile, nativeWriteFile, nativeListZips });
138
- offlineWorker.onmessage = combineHandlers(handler, wwHandler);
139
- if (storage !== "native") {
140
- console.warn("nativeReadFile given, ignoring storage", storage);
69
+ var fn = async () => {
70
+ let fetchWorker = null;
71
+ await navigator.serviceWorker.getRegistrations().then(async (registrations) => {
72
+ for (const registration of registrations) {
73
+ await registration.unregister();
141
74
  }
142
- offlineWorkerSync.configure({ ...configureOptions, storage: "native" });
143
- } else {
144
- offlineWorker.onmessage = handler.handle;
145
- offlineWorkerSync.configure(configureOptions);
75
+ });
76
+ await navigator.serviceWorker.register("/fetch-worker.js", { type: "module" }).then((reg) => reg.update());
77
+ await navigator.serviceWorker.ready.then(async (reg) => {
78
+ console.debug("Service Worker registered:", reg.scope);
79
+ fetchWorker = reg.active;
80
+ }).catch((err) => console.error("Service Worker registration failed:", err));
81
+ if (!fetchWorker) {
82
+ console.debug("failed to get fetch worker");
83
+ return;
146
84
  }
147
85
  offlineWorkerSync.on("ready", setWorkersReady);
148
86
  const channel = new MessageChannel();
149
87
  offlineWorkerSync.initChannel({ ports: [channel.port1] });
150
88
  fetchWorker.postMessage({ methodName: "initChannel" }, [channel.port2]);
151
- fetchWorker.postMessage({ methodName: "configure", arg: { storage } });
152
89
  offlineWorkerSync.on("opfsProgress", () => {
153
90
  });
154
- if (!primaryOnly) {
155
- await offlineWorkerSync.loadPreload();
156
- }
157
- if (!skipLoadBooks) {
158
- await offlineWorkerSync.loadBooks({ primaryOnly });
159
- }
91
+ await offlineWorkerSync.loadPreload();
92
+ await offlineWorkerSync.loadBooks();
160
93
  setBooksLoaded();
161
94
  };
162
- var useOfflineWorker_default = ({
163
- primaryOnly = false,
164
- nativeReadFile,
165
- nativeWriteFile,
166
- nativeListZips,
167
- checkUpdates,
168
- storage,
169
- zipBaseUrl,
170
- confEnv
171
- } = {}) => {
95
+ fn();
96
+ var useOfflineWorker_default = () => {
172
97
  const [ready, setReady] = useState(false);
173
98
  useEffect(() => {
174
- const fn = async () => {
175
- await installWorker({
176
- primaryOnly,
177
- nativeReadFile,
178
- nativeWriteFile,
179
- nativeListZips,
180
- storage,
181
- zipBaseUrl,
182
- checkUpdates,
183
- confEnv
184
- });
99
+ const fn2 = async () => {
185
100
  await Promise.all([workersReadyPromise, setBooksLoadedPromise]);
186
- fetch(window.location.href.replace(window.location.search, ""));
187
101
  setReady(true);
188
102
  };
189
- fn();
190
- }, [primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, storage, zipBaseUrl, checkUpdates, confEnv]);
191
- const loadBook = useCallback(
192
- async ({ id, forceRefresh }, cbk) => {
193
- offlineWorkerSync.on("opfsProgress", cbk);
194
- return await offlineWorkerSync.loadBook({ id, forceRefresh });
195
- },
196
- []
197
- );
103
+ fn2();
104
+ }, []);
105
+ const loadBook = useCallback(async ({ id, forceRefresh }, cbk) => {
106
+ offlineWorkerSync.on("opfsProgress", cbk);
107
+ return await offlineWorkerSync.loadBook({ id, forceRefresh });
108
+ }, []);
198
109
  return {
199
110
  ready,
200
111
  loadBook
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lls/offline",
3
- "version": "24.22.0-bdccc0f6",
3
+ "version": "24.22.0-debug",
4
4
  "description": "offline",
5
5
  "main": "./lib/index.js",
6
6
  "module": "./lib/index.js",
@@ -36,12 +36,10 @@
36
36
  "devDependencies": {
37
37
  "@biomejs/biome": "1.9.4",
38
38
  "@happy-dom/global-registrator": "15.11.0",
39
- "@ladle/react": "5.0.3",
40
39
  "@testing-library/react": "16.3.0",
41
40
  "@types/react": "18.3.12",
42
41
  "@types/react-dom": "18.3.1",
43
42
  "@vitejs/plugin-react": "4.5.1",
44
- "@vitejs/plugin-react-swc": "3.10.1",
45
43
  "esbuild": "0.24.0",
46
44
  "happy-dom": "15.11.0",
47
45
  "lodash": "4.17.21",
@@ -50,21 +48,17 @@
50
48
  "react-dom": "18.3.1",
51
49
  "typescript": "5.8.3",
52
50
  "vite": "7.0.0",
53
- "vite-tsconfig-paths": "5.1.4",
54
51
  "zod": "3.25.50",
55
- "@lls/vitescripts": "24.22.0-bdccc0f6"
52
+ "@lls/vitescripts": "24.22.0"
56
53
  },
57
54
  "dependencies": {
58
55
  "fflate": "0.8.2"
59
56
  },
60
57
  "scripts": {
61
- "start": "pnpm ladle serve",
58
+ "start": "echo no devserver",
62
59
  "dts": "pnpm tsc --project tsconfig.production.json",
63
- "build": "NODE_ENV=production node build.js",
64
- "deploy-ladle": "PUBLISH=1 pnpm ladle build",
65
- "deploy-ladle-pr": "pnpm ladle build && s5cmd sync --delete --acl public-read --destination-region eu-west-1 'dist/*' s3://build.lelivrescolaire.fr/storybook/viewer-pr-$PR_NUMBER/",
66
- "test": "bun test --bail __tests__/unit __tests__/e2e",
67
- "lint": "pnpm biome check --apply src __tests__ stories",
68
- "precommit": "pnpm lint"
60
+ "build": "node build",
61
+ "test": "bun test --bail",
62
+ "lint": "pnpm biome check --apply src __tests__"
69
63
  }
70
64
  }