@lls/offline 24.27.0-8446b483 → 24.27.0-9f297e77

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 };
@@ -202,12 +220,23 @@ var coreFetchPagesApi = async (ev, { offlineWorkerSync: offlineWorkerSync2, vari
202
220
  }
203
221
  }
204
222
  };
223
+ var cocoreFetchPrimaryMethod = async (_ev, { offlineWorkerSync: offlineWorkerSync2, variables }) => {
224
+ const uri = variables.uris?.[0];
225
+ if (!uri) {
226
+ throw new Error("invalid uri");
227
+ }
228
+ const method = await offlineWorkerSync2.readZipEntry({ bookId: uri, entryName: "gqls/method.json", asJson: true });
229
+ return {
230
+ data: method
231
+ };
232
+ };
205
233
  var handles = {
206
234
  coreFetchBookDrawerScreen,
207
235
  cocoreFetchBookScreen,
208
236
  coreFetchPagesApi,
209
237
  cocoreFetchPartialPageScreen,
210
- cocoreFetchPartialPageSiblingsScreen
238
+ cocoreFetchPartialPageSiblingsScreen,
239
+ cocoreFetchPrimaryMethod
211
240
  };
212
241
  var canHandleScreenCall = (url) => new RegExp(Object.keys(handles).join("|")).test(url);
213
242
  var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, knownLinks: knownLinks2 }) => {
@@ -216,8 +245,8 @@ var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, known
216
245
  // @ts-expect-error TODO: handle variables from body(stream) properly!!
217
246
  ev.request.body?.variables || JSON.parse(new URLSearchParams(new URL(ev.request.url).search).get("variables") || "{}")
218
247
  );
219
- console.debug("variables are", variables, "vs", ev.request.body);
220
248
  const key = ev.request.url.match(Object.keys(handles).join("|"))?.[0];
249
+ console.debug("screencall", key, "qs", variables, "vs body", ev.request.body);
221
250
  if (!key) {
222
251
  throw new Error("unknown ev");
223
252
  }
@@ -238,17 +267,39 @@ var setKnownLinks = (knownLinks2, { entryNames, bookId }) => {
238
267
  // src/worker/fetch-worker.ts
239
268
  var STORAGE = "cache";
240
269
  var CONF_ENV = "development";
241
- var fetchAsset = async (_ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
270
+ var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
242
271
  try {
243
- const buf = await offlineWorkerSync2.readZipEntry(entryId);
272
+ const [buf, maybeRangeHeaders] = await offlineWorkerSync2.readZipEntry(entryId).then((buf2) => {
273
+ const range = ev.request.headers.get("Range");
274
+ if (!ev.request.url.endsWith(".mp4") && !ev.request.url.endsWith(".webm")) {
275
+ return [buf2, {}];
276
+ }
277
+ if (!range) {
278
+ return [
279
+ buf2,
280
+ {
281
+ "Accept-Ranges": "bytes",
282
+ "Content-Range": `bytes ${0}-${buf2.byteLength}/${buf2.byteLength}`
283
+ }
284
+ ];
285
+ }
286
+ const { start, end, chunkSize } = parseRange(range, buf2.byteLength);
287
+ const rangeHeaders = {
288
+ "Content-Length": chunkSize,
289
+ "Accept-Ranges": "bytes",
290
+ "Content-Range": `bytes ${start}-${end}/${buf2.byteLength}`
291
+ };
292
+ return [buf2.slice(start, end + 1), rangeHeaders];
293
+ });
244
294
  return new Response(buf, {
245
295
  headers: {
246
296
  "Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
247
297
  "Content-Length": buf.byteLength,
248
- "Content-Type": mimeType
298
+ "Content-Type": mimeType,
299
+ ...maybeRangeHeaders
249
300
  },
250
301
  // @ts-ignore: poor ts typing
251
- status: 200,
302
+ status: maybeRangeHeaders["Content-Range"] ? 206 : 200,
252
303
  statusText: "OK"
253
304
  });
254
305
  } catch (e) {
@@ -347,14 +398,16 @@ swSelf.addEventListener("fetch", (ev) => {
347
398
  return;
348
399
  }
349
400
  if (!mimeType && !isValidScreenCall) {
350
- if (!/\.(css|tsx?|jsx?|woff2|fr|manifest)(\??|$)|@(vite|react|id)/.test(url)) {
401
+ if (!/\.(css|tsx?|[mj]sx?|woff2|fr|(web)?manifest)(\??|$)|@(vite|react|id)|(:6019\/?$)/.test(url)) {
351
402
  console.debug("no ext", extension || url);
352
403
  }
353
404
  return ev.respondWith(onlineFirst(ev.request, ev));
354
405
  }
406
+ if (/http:\/\/localhost:\d+\/(favicon\.svg|assets\/)/.test(url)) {
407
+ return;
408
+ }
355
409
  const entryId = knownLinks.get(url.replace(/https:\/\/.*?\//, "")) || knownLinks.get(`medias/${url.replace(/https:\/\/.*?\//, "")}`);
356
410
  if (isValidScreenCall) {
357
- console.debug("screencall!");
358
411
  return ev.respondWith(fetchScreenCall(ev, { offlineWorkerSync, knownLinks }));
359
412
  }
360
413
  if (entryId) {
@@ -363,6 +416,7 @@ swSelf.addEventListener("fetch", (ev) => {
363
416
  if (/lls|lelivrescolaire/.test(url)) {
364
417
  console.info("no link for", url.replace(/https:\/\/.*?\//, ""), `medias/${url.replace(/https:\/\/.*?\//, "")}`);
365
418
  }
419
+ console.debug("unintercepted url", url);
366
420
  return;
367
421
  });
368
422
  swSelf.addEventListener("install", (_event) => {
@@ -383,7 +437,10 @@ var initChannel = async (__, { ports }) => {
383
437
  );
384
438
  offlineWorkerSync = sender;
385
439
  offlineWorker.onmessage = handler.handle;
386
- offlineWorkerSync.on("setKnownLinks", setKnownLinks.bind(null, knownLinks));
440
+ offlineWorkerSync.on("setKnownLinks", ({ entryNames, bookId, eventId }) => {
441
+ setKnownLinks(knownLinks, { entryNames, bookId });
442
+ offlineWorker.postMessage({ methodName: "setKnownLinks:back", eventId });
443
+ });
387
444
  offlineWorker.postMessage({ methodName: "sendAppReady" });
388
445
  };
389
446
  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")) {
@@ -947,6 +955,7 @@ var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
947
955
  self.postMessage({ methodName: "opfsProgress", arg: { total: contentLength, bytes: position } });
948
956
  }
949
957
  }
958
+ console.debug("zip loaded:", arrayBuffer.byteLength);
950
959
  if (STORAGE === "native") {
951
960
  await sender.nativeWriteFile({ fname: fileName, buffer: arrayBuffer });
952
961
  } else if (STORAGE === "opfs") {
@@ -992,7 +1001,12 @@ var loadBook2 = async ({ id: bookId, forceRefresh }) => {
992
1001
  const buf = await fetchZipFile(`${ZIP_BASE_URL}/${bookId}.zip`, { forceRefresh });
993
1002
  try {
994
1003
  const entryNames = await loadZip(new Uint8Array(buf), bookId);
995
- fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId } });
1004
+ const eventId = `setKnownLinks:${Date.now()}-${Math.random().toFixed(5)}`;
1005
+ const responseForEventId = new Promise((resolve, reject) => {
1006
+ dicFetchWorkerCallback[eventId] = { resolve, reject };
1007
+ });
1008
+ fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId, eventId } });
1009
+ await responseForEventId;
996
1010
  if (isOldZip(bookId)) {
997
1011
  const json = await readZipEntry({
998
1012
  entryName: `preload/${bookId}.json`,
@@ -1000,6 +1014,13 @@ var loadBook2 = async ({ id: bookId, forceRefresh }) => {
1000
1014
  asJson: true
1001
1015
  });
1002
1016
  loadBook(json);
1017
+ } else {
1018
+ try {
1019
+ const { createdAt } = await readZipEntry({ bookId, entryName: "manifest.json", asJson: true });
1020
+ console.debug("zipversion", createdAt);
1021
+ } catch (e) {
1022
+ console.debug("failed to read manifest.json", e);
1023
+ }
1003
1024
  }
1004
1025
  return true;
1005
1026
  } catch (e) {
@@ -1029,6 +1050,14 @@ var loadPageContent2 = async ({
1029
1050
  return await loadPageContent({ chapterId, json });
1030
1051
  };
1031
1052
  var fetchWorker;
1053
+ var dicFetchWorkerCallback = {};
1054
+ var handleFetchWorkerCallback = ({ data: { eventId } }) => {
1055
+ if (dicFetchWorkerCallback[eventId]) {
1056
+ dicFetchWorkerCallback[eventId].resolve();
1057
+ } else {
1058
+ console.debug("unknown eventId", eventId);
1059
+ }
1060
+ };
1032
1061
  var initChannel = (__, { ports }) => {
1033
1062
  fetchWorker = ports[0];
1034
1063
  const { canHandle, handle } = makeSyncHandler(fetchWorker, {
@@ -1043,6 +1072,14 @@ var initChannel = (__, { ports }) => {
1043
1072
  self.postMessage({ methodName: "ready" });
1044
1073
  return;
1045
1074
  }
1075
+ if (dataObj.data?.methodName === "setKnownLinks:back") {
1076
+ if (typeof dataObj?.data?.eventId !== "string") {
1077
+ console.debug("missing eventId to get response back for setKnownLinks:back");
1078
+ return;
1079
+ }
1080
+ handleFetchWorkerCallback(dataObj);
1081
+ return;
1082
+ }
1046
1083
  if (canHandle(dataObj.data?.methodName || "")) {
1047
1084
  return handle(dataObj);
1048
1085
  }
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-8446b483",
3
+ "version": "24.27.0-9f297e77",
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-8446b483"
55
+ "@lls/vitescripts": "24.27.0-9f297e77"
56
56
  },
57
57
  "dependencies": {
58
58
  "fflate": "0.8.2"