@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.
- package/lib/assets/fetch-worker.js +67 -10
- package/lib/assets/offline-worker.js +42 -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 };
|
|
@@ -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 (
|
|
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?|
|
|
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",
|
|
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
|
-
|
|
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")) {
|
|
@@ -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
|
-
|
|
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
|
-
}, [
|
|
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-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-
|
|
55
|
+
"@lls/vitescripts": "24.27.0-9f297e77"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"fflate": "0.8.2"
|