@lls/offline 24.27.0-a58e1855 → 24.27.0-ade60496

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,21 @@
1
+ // src/utils/stream.ts
2
+ var parseRange = (range, size) => {
3
+ const parts = range.replace("bytes=", "").split("-");
4
+ let start = Number.parseInt(parts[0], 10);
5
+ let end = Number.parseInt(parts[1], 10);
6
+ if (isNaN(start)) {
7
+ start = size - end;
8
+ end = size - 1;
9
+ } else if (isNaN(end)) {
10
+ end = size - 1;
11
+ }
12
+ if (end > size - 1) {
13
+ end = size - 1;
14
+ }
15
+ const chunkSize = end - start + 1;
16
+ return { start, end, chunkSize };
17
+ };
18
+
1
19
  // src/utils/worker.ts
2
20
  var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
3
21
  const dic = {};
@@ -77,7 +95,7 @@ var cocoreFetchPartialPageScreen = async (_ev, { offlineWorkerSync: offlineWorke
77
95
  const key = `gqls/${zipSlugName(variables.slugs[0] || "")}.json`;
78
96
  const entryId = knownLinks2.get(key);
79
97
  if (!entryId) {
80
- throw new Error(`slug not found ${key}`);
98
+ throw new Error(`slug not found ${key}, links size: ${knownLinks2.size}`);
81
99
  }
82
100
  const gqlPage = await offlineWorkerSync2.loadPageContent({ entryId });
83
101
  return { data: gqlPage };
@@ -216,8 +234,8 @@ var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, known
216
234
  // @ts-expect-error TODO: handle variables from body(stream) properly!!
217
235
  ev.request.body?.variables || JSON.parse(new URLSearchParams(new URL(ev.request.url).search).get("variables") || "{}")
218
236
  );
219
- console.debug("variables are", variables, "vs", ev.request.body);
220
237
  const key = ev.request.url.match(Object.keys(handles).join("|"))?.[0];
238
+ console.debug("screencall", key, "qs", variables, "vs body", ev.request.body);
221
239
  if (!key) {
222
240
  throw new Error("unknown ev");
223
241
  }
@@ -238,17 +256,39 @@ var setKnownLinks = (knownLinks2, { entryNames, bookId }) => {
238
256
  // src/worker/fetch-worker.ts
239
257
  var STORAGE = "cache";
240
258
  var CONF_ENV = "development";
241
- var fetchAsset = async (_ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
259
+ var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
242
260
  try {
243
- const buf = await offlineWorkerSync2.readZipEntry(entryId);
261
+ const [buf, maybeRangeHeaders] = await offlineWorkerSync2.readZipEntry(entryId).then((buf2) => {
262
+ const range = ev.request.headers.get("Range");
263
+ if (!ev.request.url.endsWith(".mp4") && !ev.request.url.endsWith(".webm")) {
264
+ return [buf2, {}];
265
+ }
266
+ if (!range) {
267
+ return [
268
+ buf2,
269
+ {
270
+ "Accept-Ranges": "bytes",
271
+ "Content-Range": `bytes ${0}-${buf2.byteLength}/${buf2.byteLength}`
272
+ }
273
+ ];
274
+ }
275
+ const { start, end, chunkSize } = parseRange(range, buf2.byteLength);
276
+ const rangeHeaders = {
277
+ "Content-Length": chunkSize,
278
+ "Accept-Ranges": "bytes",
279
+ "Content-Range": `bytes ${start}-${end}/${buf2.byteLength}`
280
+ };
281
+ return [buf2.slice(start, end + 1), rangeHeaders];
282
+ });
244
283
  return new Response(buf, {
245
284
  headers: {
246
285
  "Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
247
286
  "Content-Length": buf.byteLength,
248
- "Content-Type": mimeType
287
+ "Content-Type": mimeType,
288
+ ...maybeRangeHeaders
249
289
  },
250
290
  // @ts-ignore: poor ts typing
251
- status: 200,
291
+ status: maybeRangeHeaders["Content-Range"] ? 206 : 200,
252
292
  statusText: "OK"
253
293
  });
254
294
  } catch (e) {
@@ -347,14 +387,16 @@ swSelf.addEventListener("fetch", (ev) => {
347
387
  return;
348
388
  }
349
389
  if (!mimeType && !isValidScreenCall) {
350
- if (!/\.(css|tsx?|jsx?|woff2|fr|manifest)(\??|$)|@(vite|react|id)/.test(url)) {
390
+ if (!/\.(css|tsx?|[mj]sx?|woff2|fr|(web)?manifest)(\??|$)|@(vite|react|id)|(:6019\/?$)/.test(url)) {
351
391
  console.debug("no ext", extension || url);
352
392
  }
353
393
  return ev.respondWith(onlineFirst(ev.request, ev));
354
394
  }
395
+ if (/favicon\.svg$/.test(url)) {
396
+ return;
397
+ }
355
398
  const entryId = knownLinks.get(url.replace(/https:\/\/.*?\//, "")) || knownLinks.get(`medias/${url.replace(/https:\/\/.*?\//, "")}`);
356
399
  if (isValidScreenCall) {
357
- console.debug("screencall!");
358
400
  return ev.respondWith(fetchScreenCall(ev, { offlineWorkerSync, knownLinks }));
359
401
  }
360
402
  if (entryId) {
@@ -363,6 +405,7 @@ swSelf.addEventListener("fetch", (ev) => {
363
405
  if (/lls|lelivrescolaire/.test(url)) {
364
406
  console.info("no link for", url.replace(/https:\/\/.*?\//, ""), `medias/${url.replace(/https:\/\/.*?\//, "")}`);
365
407
  }
408
+ console.debug("unintercepted url", url);
366
409
  return;
367
410
  });
368
411
  swSelf.addEventListener("install", (_event) => {
@@ -383,7 +426,10 @@ var initChannel = async (__, { ports }) => {
383
426
  );
384
427
  offlineWorkerSync = sender;
385
428
  offlineWorker.onmessage = handler.handle;
386
- offlineWorkerSync.on("setKnownLinks", setKnownLinks.bind(null, knownLinks));
429
+ offlineWorkerSync.on("setKnownLinks", ({ entryNames, bookId, eventId }) => {
430
+ setKnownLinks(knownLinks, { entryNames, bookId });
431
+ offlineWorker.postMessage({ methodName: "setKnownLinks:back", eventId });
432
+ });
387
433
  offlineWorker.postMessage({ methodName: "sendAppReady" });
388
434
  };
389
435
  var knownLinks = /* @__PURE__ */ new Map();
@@ -802,12 +802,14 @@ var readZipEntry = async ({
802
802
  const originalName = manifest.medias[zipName];
803
803
  overridenName = `medias/${originalName}`;
804
804
  } else {
805
- console.error("any media should exist in manifest", zipName, entryName, bookId);
805
+ if (entryName !== "manifest.json") {
806
+ console.error("any media should exist in manifest", zipName, entryName, bookId);
807
+ }
806
808
  }
807
809
  }
808
810
  const bookEntry = BOOKS[bookId].map.get(overridenName);
809
811
  if (!bookEntry) {
810
- console.info("no bookEntry", entryName, "found", overridenName);
812
+ console.info("no bookEntry", entryName, "overridingName:", overridenName, "for book", bookId);
811
813
  return null;
812
814
  }
813
815
  if (!bookEntry.decoded) {
@@ -886,7 +888,8 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
886
888
  return back;
887
889
  }
888
890
  if (STORAGE === "cache") {
889
- const response = await caches.match(zipUrl);
891
+ const cache = await openCache(CONF_ENV);
892
+ const response = await cache.match(zipUrl);
890
893
  if (!response) {
891
894
  throw new Error(`missing zip ${zipUrl}`);
892
895
  }
@@ -910,7 +913,11 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
910
913
  const buf = await readArrayBuffer();
911
914
  if (CHECK_UPDATES) {
912
915
  const response = await fetch(zipUrl, { method: "HEAD" });
913
- const remoteSize = Number.parseInt(response.headers.get("content-length") || "0", 10);
916
+ let remoteSize = Number.parseInt(response.headers.get("content-length") ?? "");
917
+ if (!remoteSize) {
918
+ console.info("remoteSize not found. will skip update. Is the server up at", zipUrl);
919
+ remoteSize = buf.byteLength;
920
+ }
914
921
  if (remoteSize !== buf.byteLength) {
915
922
  const error = `new ${fileName} available, refetching (opfs:${buf.byteLength} vs remote:${remoteSize})`;
916
923
  console.debug(error);
@@ -918,6 +925,7 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
918
925
  throw new Error(`new ${fileName} available, refetching`);
919
926
  }
920
927
  }
928
+ self.postMessage({ methodName: "opfsProgress", arg: { total: buf.byteLength, bytes: buf.byteLength } });
921
929
  return buf;
922
930
  } catch (e) {
923
931
  if (e instanceof Error && e.message !== "forceRefresh" && !e.message.includes("refetching")) {
@@ -992,7 +1000,12 @@ var loadBook2 = async ({ id: bookId, forceRefresh }) => {
992
1000
  const buf = await fetchZipFile(`${ZIP_BASE_URL}/${bookId}.zip`, { forceRefresh });
993
1001
  try {
994
1002
  const entryNames = await loadZip(new Uint8Array(buf), bookId);
995
- fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId } });
1003
+ const eventId = `setKnownLinks:${Date.now()}-${Math.random().toFixed(5)}`;
1004
+ const responseForEventId = new Promise((resolve, reject) => {
1005
+ dicFetchWorkerCallback[eventId] = { resolve, reject };
1006
+ });
1007
+ fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId, eventId } });
1008
+ await responseForEventId;
996
1009
  if (isOldZip(bookId)) {
997
1010
  const json = await readZipEntry({
998
1011
  entryName: `preload/${bookId}.json`,
@@ -1000,6 +1013,13 @@ var loadBook2 = async ({ id: bookId, forceRefresh }) => {
1000
1013
  asJson: true
1001
1014
  });
1002
1015
  loadBook(json);
1016
+ } else {
1017
+ try {
1018
+ const { createdAt } = await readZipEntry({ bookId, entryName: "manifest.json", asJson: true });
1019
+ console.debug("zipversion", createdAt);
1020
+ } catch (e) {
1021
+ console.debug("failed to read manifest.json", e);
1022
+ }
1003
1023
  }
1004
1024
  return true;
1005
1025
  } catch (e) {
@@ -1029,6 +1049,14 @@ var loadPageContent2 = async ({
1029
1049
  return await loadPageContent({ chapterId, json });
1030
1050
  };
1031
1051
  var fetchWorker;
1052
+ var dicFetchWorkerCallback = {};
1053
+ var handleFetchWorkerCallback = ({ data: { eventId } }) => {
1054
+ if (dicFetchWorkerCallback[eventId]) {
1055
+ dicFetchWorkerCallback[eventId].resolve();
1056
+ } else {
1057
+ console.debug("unknown eventId", eventId);
1058
+ }
1059
+ };
1032
1060
  var initChannel = (__, { ports }) => {
1033
1061
  fetchWorker = ports[0];
1034
1062
  const { canHandle, handle } = makeSyncHandler(fetchWorker, {
@@ -1043,6 +1071,14 @@ var initChannel = (__, { ports }) => {
1043
1071
  self.postMessage({ methodName: "ready" });
1044
1072
  return;
1045
1073
  }
1074
+ if (dataObj.data?.methodName === "setKnownLinks:back") {
1075
+ if (typeof dataObj?.data?.eventId !== "string") {
1076
+ console.debug("missing eventId to get response back for setKnownLinks:back");
1077
+ return;
1078
+ }
1079
+ handleFetchWorkerCallback(dataObj);
1080
+ return;
1081
+ }
1046
1082
  if (canHandle(dataObj.data?.methodName || "")) {
1047
1083
  return handle(dataObj);
1048
1084
  }
package/lib/index.js CHANGED
@@ -161,6 +161,7 @@ var installWorker = async ({
161
161
  setBooksLoaded();
162
162
  };
163
163
  var useOfflineWorker_default = ({
164
+ skipLoadBooks,
164
165
  primaryOnly = false,
165
166
  nativeReadFile,
166
167
  nativeWriteFile,
@@ -174,6 +175,7 @@ var useOfflineWorker_default = ({
174
175
  useEffect(() => {
175
176
  const fn = async () => {
176
177
  await installWorker({
178
+ skipLoadBooks,
177
179
  primaryOnly,
178
180
  nativeReadFile,
179
181
  nativeWriteFile,
@@ -188,11 +190,21 @@ var useOfflineWorker_default = ({
188
190
  setReady(true);
189
191
  };
190
192
  fn();
191
- }, [primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, storage, zipBaseUrl, checkUpdates, confEnv]);
193
+ }, [
194
+ skipLoadBooks,
195
+ primaryOnly,
196
+ nativeReadFile,
197
+ nativeWriteFile,
198
+ nativeListZips,
199
+ storage,
200
+ zipBaseUrl,
201
+ checkUpdates,
202
+ confEnv
203
+ ]);
192
204
  const loadBook = useCallback(
193
- async ({ id, forceRefresh }, cbk) => {
194
- offlineWorkerSync.on("opfsProgress", cbk);
195
- return await offlineWorkerSync.loadBook({ id, forceRefresh });
205
+ async ({ forceRefresh, ...idOrUri }, cbk) => {
206
+ cbk && offlineWorkerSync.on("opfsProgress", cbk);
207
+ return await offlineWorkerSync.loadBook({ id: "id" in idOrUri ? idOrUri.id : idOrUri.uri, forceRefresh });
196
208
  },
197
209
  []
198
210
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lls/offline",
3
- "version": "24.27.0-a58e1855",
3
+ "version": "24.27.0-ade60496",
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.7",
53
53
  "vite-tsconfig-paths": "5.1.4",
54
54
  "zod": "3.25.50",
55
- "@lls/vitescripts": "24.27.0-a58e1855"
55
+ "@lls/vitescripts": "24.27.0-ade60496"
56
56
  },
57
57
  "dependencies": {
58
58
  "fflate": "0.8.2"