@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.
- package/lib/assets/fetch-worker.js +55 -9
- package/lib/assets/offline-worker.js +41 -5
- package/lib/index.js +16 -4
- package/package.json +2 -2
|
@@ -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 (
|
|
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?|
|
|
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",
|
|
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
|
-
|
|
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, "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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 ({
|
|
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-
|
|
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-
|
|
55
|
+
"@lls/vitescripts": "24.27.0-ade60496"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"fflate": "0.8.2"
|