@lls/offline 24.20.0-debug → 24.20.0-debug2
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/README.md +24 -0
- package/lib/assets/{fetch.worker.js → fetch-worker.js} +63 -33
- package/lib/assets/{offlineWorker.js → offline-worker.js} +184 -42
- package/lib/index.d.ts +1 -0
- package/lib/index.js +81 -356
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -3,3 +3,27 @@
|
|
|
3
3
|
handles offline as webworker for android/ios and desktop
|
|
4
4
|
|
|
5
5
|
Likely used by core, cocore
|
|
6
|
+
|
|
7
|
+
## Getting started
|
|
8
|
+
|
|
9
|
+
Deux types de workers pour gérer le offline:
|
|
10
|
+
|
|
11
|
+
- un web worker (WW): offlineWorker.ts
|
|
12
|
+
- un service worker (SW): fetch.worker.ts
|
|
13
|
+
|
|
14
|
+
Le main (useOfflineWorker) instancie les deux workers.
|
|
15
|
+
Dû à la nature du SW qui ne peut pas poster en retour au main, on crèe un lien entre WW et SW via MessageChannel afin qu'ils puissent communiquer entre eux.
|
|
16
|
+
|
|
17
|
+
SW a pour responsabilité d'intercepter les calls (GQL, assets)
|
|
18
|
+
Il demande le contenu à WW.
|
|
19
|
+
|
|
20
|
+
WW a pour responsabilité de
|
|
21
|
+
- fetcher le contenu offline (.zip sur s3)
|
|
22
|
+
- gérer l'opfs (stockage navigateur du contenu)
|
|
23
|
+
- lire dans les archives pour servir SW
|
|
24
|
+
|
|
25
|
+
## Testing
|
|
26
|
+
|
|
27
|
+
Les WW et SW ont pour interface (offlineWorker.ts, fetch.worker.ts) et délèguent les méthodes métiers aux autres fichiers worker agnostiques (donc testable)
|
|
28
|
+
|
|
29
|
+
|
|
@@ -16,7 +16,6 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
|
|
|
16
16
|
}
|
|
17
17
|
methodName && cbks[methodName]?.(arg);
|
|
18
18
|
};
|
|
19
|
-
offlineWorker2.onmessage = fn;
|
|
20
19
|
const makeFn = (methodName) => {
|
|
21
20
|
return ({ ports, ...arg } = {}) => {
|
|
22
21
|
return new Promise((resolve, reject) => {
|
|
@@ -31,27 +30,53 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
|
|
|
31
30
|
cbks[key] = fn2;
|
|
32
31
|
}
|
|
33
32
|
});
|
|
34
|
-
return
|
|
33
|
+
return {
|
|
34
|
+
...obj,
|
|
35
|
+
handler: {
|
|
36
|
+
canHandle: (methodName) => {
|
|
37
|
+
return methodName in cbks || methodNames.includes(methodName);
|
|
38
|
+
},
|
|
39
|
+
handle: fn
|
|
40
|
+
}
|
|
41
|
+
};
|
|
35
42
|
};
|
|
36
43
|
var a = (a2) => a2;
|
|
37
44
|
var makeSyncHandler = a(
|
|
38
|
-
(sender, services) =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 });
|
|
44
60
|
}
|
|
45
|
-
throw new Error(`invalid methodName ${String(methodName)}`);
|
|
46
|
-
} catch (e) {
|
|
47
|
-
console.debug("failed", e);
|
|
48
|
-
sender.postMessage({ eventId, methodName, error: e });
|
|
49
61
|
}
|
|
50
|
-
}
|
|
62
|
+
})
|
|
51
63
|
);
|
|
52
64
|
var jsonToBuf = (json) => new TextEncoder().encode(JSON.stringify(json));
|
|
53
65
|
|
|
54
66
|
// src/worker/swFetchScreenCall.ts
|
|
67
|
+
var zipSlugName = (str) => str.replace(/[^a-zA-Z0-9-.]/g, "");
|
|
68
|
+
var cocoreFetchPartialPageScreen = async (_ev, { offlineWorkerSync: offlineWorkerSync2, variables, knownLinks: knownLinks2 }) => {
|
|
69
|
+
const key = `gqls/${zipSlugName(variables.slugs[0] || "")}.json`;
|
|
70
|
+
const entryId = knownLinks2.get(key);
|
|
71
|
+
if (!entryId) {
|
|
72
|
+
throw new Error(`slug not found ${key}`);
|
|
73
|
+
}
|
|
74
|
+
const gqlPage = await offlineWorkerSync2.loadPageContent({ entryId });
|
|
75
|
+
return { data: gqlPage };
|
|
76
|
+
};
|
|
77
|
+
var cocoreFetchPartialPageSiblingsScreen = async (_ev) => {
|
|
78
|
+
return { data: { viewer: {} } };
|
|
79
|
+
};
|
|
55
80
|
var coreFetchBookDrawerScreen = async (_ev, { offlineWorkerSync: offlineWorkerSync2, variables }) => {
|
|
56
81
|
const slug = variables.slugs?.[0];
|
|
57
82
|
const uris = variables.uris;
|
|
@@ -161,22 +186,25 @@ var coreFetchPagesApi = async (ev, { offlineWorkerSync: offlineWorkerSync2, vari
|
|
|
161
186
|
}
|
|
162
187
|
}
|
|
163
188
|
};
|
|
164
|
-
var
|
|
189
|
+
var handles = {
|
|
190
|
+
coreFetchBookDrawerScreen,
|
|
191
|
+
coreFetchPagesApi,
|
|
192
|
+
cocoreFetchPartialPageScreen,
|
|
193
|
+
cocoreFetchPartialPageSiblingsScreen
|
|
194
|
+
};
|
|
195
|
+
var canHandleScreenCall = (url) => new RegExp(Object.keys(handles).join("|")).test(url);
|
|
196
|
+
var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, knownLinks: knownLinks2 }) => {
|
|
165
197
|
try {
|
|
166
198
|
const variables = (
|
|
167
199
|
// @ts-ignore TODO: handle variables from body(stream) properly!!
|
|
168
200
|
ev.request.body?.variables || JSON.parse(new URLSearchParams(new URL(ev.request.url).search).get("variables") || "{}")
|
|
169
201
|
);
|
|
170
202
|
console.debug("variables are", variables, "vs", ev.request.body);
|
|
171
|
-
const handles = {
|
|
172
|
-
coreFetchBookDrawerScreen,
|
|
173
|
-
coreFetchPagesApi
|
|
174
|
-
};
|
|
175
203
|
const key = ev.request.url.match(Object.keys(handles).join("|"))?.[0];
|
|
176
204
|
if (!key) {
|
|
177
205
|
throw new Error("unknown ev");
|
|
178
206
|
}
|
|
179
|
-
const json = await handles[key](ev, { offlineWorkerSync: offlineWorkerSync2, variables });
|
|
207
|
+
const json = await handles[key](ev, { offlineWorkerSync: offlineWorkerSync2, variables, knownLinks: knownLinks2 });
|
|
180
208
|
return { status: 200, json };
|
|
181
209
|
} catch (e) {
|
|
182
210
|
console.error(e);
|
|
@@ -184,7 +212,7 @@ var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2 }) =>
|
|
|
184
212
|
}
|
|
185
213
|
};
|
|
186
214
|
|
|
187
|
-
// src/worker/fetch
|
|
215
|
+
// src/worker/fetch-worker.ts
|
|
188
216
|
var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
|
|
189
217
|
try {
|
|
190
218
|
const buf = await offlineWorkerSync2.readZipEntry(entryId);
|
|
@@ -212,9 +240,9 @@ var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorke
|
|
|
212
240
|
});
|
|
213
241
|
}
|
|
214
242
|
};
|
|
215
|
-
var fetchScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2 }) => {
|
|
243
|
+
var fetchScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, knownLinks: knownLinks2 }) => {
|
|
216
244
|
try {
|
|
217
|
-
const { status, json } = await handleScreenCall(ev, { offlineWorkerSync: offlineWorkerSync2 });
|
|
245
|
+
const { status, json } = await handleScreenCall(ev, { offlineWorkerSync: offlineWorkerSync2, knownLinks: knownLinks2 });
|
|
218
246
|
return new Response(jsonToBuf(json), {
|
|
219
247
|
headers: {
|
|
220
248
|
"Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
|
|
@@ -255,23 +283,23 @@ swSelf.addEventListener("fetch", (ev) => {
|
|
|
255
283
|
const url = ev.request.url;
|
|
256
284
|
const extension = url.match(/\.[^.]+$/)?.[0];
|
|
257
285
|
const mimeType = extension && extension in ACCEPTED_EXTENSIONS ? ACCEPTED_EXTENSIONS[extension] : "";
|
|
258
|
-
const
|
|
259
|
-
if (!mimeType && !
|
|
260
|
-
if (!/\.(css|tsx?|jsx?|woff2|fr)/.test(url)) {
|
|
261
|
-
console.debug("no ext", url
|
|
286
|
+
const isValidScreenCall = canHandleScreenCall(url);
|
|
287
|
+
if (!mimeType && !isValidScreenCall) {
|
|
288
|
+
if (!/\.(css|tsx?|jsx?|woff2|fr|manifest)(\??|$)|@(vite|react|id)/.test(url)) {
|
|
289
|
+
console.debug("no ext", extension || url);
|
|
262
290
|
}
|
|
263
291
|
return;
|
|
264
292
|
}
|
|
265
|
-
const entryId = knownLinks.get(url.replace(/https:\/\/.*?\//, ""));
|
|
266
|
-
if (!entryId && !
|
|
293
|
+
const entryId = knownLinks.get(url.replace(/https:\/\/.*?\//, "")) || knownLinks.get(`medias/${url.replace(/https:\/\/.*?\//, "")}`);
|
|
294
|
+
if (!entryId && !isValidScreenCall) {
|
|
267
295
|
if (/lls|lelivrescolaire/.test(url)) {
|
|
268
296
|
console.info("no link for", url.replace(/https:\/\/.*?\//, ""));
|
|
269
297
|
}
|
|
270
298
|
return;
|
|
271
299
|
}
|
|
272
|
-
if (
|
|
300
|
+
if (isValidScreenCall) {
|
|
273
301
|
console.debug("screencall!");
|
|
274
|
-
return ev.respondWith(fetchScreenCall(ev, { offlineWorkerSync }));
|
|
302
|
+
return ev.respondWith(fetchScreenCall(ev, { offlineWorkerSync, knownLinks }));
|
|
275
303
|
}
|
|
276
304
|
return ev.respondWith(fetchAsset(ev, { entryId, mimeType, offlineWorkerSync }));
|
|
277
305
|
});
|
|
@@ -284,13 +312,15 @@ swSelf.addEventListener("activate", (_event) => {
|
|
|
284
312
|
var offlineWorker;
|
|
285
313
|
var offlineWorkerSync;
|
|
286
314
|
var initChannel = async (__, { ports }) => {
|
|
287
|
-
console.info(`FETCH WORKER innitChannel v${
|
|
315
|
+
console.info(`FETCH WORKER innitChannel v${/* @__PURE__ */ new Date()}`);
|
|
288
316
|
offlineWorker = ports[0];
|
|
289
|
-
|
|
317
|
+
const { handler, ...sender } = makeSyncWorker(
|
|
290
318
|
offlineWorker,
|
|
291
319
|
["readZipEntry", "loadBook", "slugToFileMeta", "loadPageContent", "searchBooks"],
|
|
292
320
|
"fetchworker"
|
|
293
321
|
);
|
|
322
|
+
offlineWorkerSync = sender;
|
|
323
|
+
offlineWorker.onmessage = handler.handle;
|
|
294
324
|
offlineWorkerSync.on("setKnownLinks", setKnownLinks);
|
|
295
325
|
offlineWorker.postMessage({ methodName: "sendAppReady" });
|
|
296
326
|
};
|
|
@@ -316,7 +346,7 @@ swSelf.onmessage = async ({
|
|
|
316
346
|
await services[methodName](arg, { ports });
|
|
317
347
|
return;
|
|
318
348
|
}
|
|
319
|
-
throw new Error(`fetchworker:invalid methodName ${methodName}`);
|
|
349
|
+
throw new Error(`fetchworker:invalid methodName ${Object.keys(services)} ${methodName}`);
|
|
320
350
|
} catch (e) {
|
|
321
351
|
console.error("failed", e);
|
|
322
352
|
}
|
|
@@ -1,21 +1,77 @@
|
|
|
1
1
|
// src/utils/worker.ts
|
|
2
|
+
var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
|
|
3
|
+
const dic = {};
|
|
4
|
+
const cbks = {};
|
|
5
|
+
const fn = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
|
|
6
|
+
if (eventId && eventId in dic) {
|
|
7
|
+
if (error) {
|
|
8
|
+
dic[eventId].reject(error);
|
|
9
|
+
} else {
|
|
10
|
+
dic[eventId].resolve(results);
|
|
11
|
+
}
|
|
12
|
+
} else {
|
|
13
|
+
if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
|
|
14
|
+
console.error(`syncWorker: ${dbg}`, eventId, methodName, error);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
methodName && cbks[methodName]?.(arg);
|
|
18
|
+
};
|
|
19
|
+
const makeFn = (methodName) => {
|
|
20
|
+
return ({ ports, ...arg } = {}) => {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const eventId = `${methodName}:${Date.now()}`;
|
|
23
|
+
dic[eventId] = { resolve, reject };
|
|
24
|
+
offlineWorker.postMessage({ eventId, methodName, arg }, ports);
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
|
|
29
|
+
on: (key2, fn2) => {
|
|
30
|
+
cbks[key2] = fn2;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
...obj,
|
|
35
|
+
handler: {
|
|
36
|
+
canHandle: (methodName) => {
|
|
37
|
+
return methodName in cbks || methodNames.includes(methodName);
|
|
38
|
+
},
|
|
39
|
+
handle: fn
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
};
|
|
2
43
|
var a = (a2) => a2;
|
|
3
44
|
var makeSyncHandler = a(
|
|
4
|
-
(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
45
|
+
(sender2, 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
|
+
sender2.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
|
+
sender2.postMessage({ eventId, methodName, error: e });
|
|
10
60
|
}
|
|
11
|
-
throw new Error(`invalid methodName ${String(methodName)}`);
|
|
12
|
-
} catch (e) {
|
|
13
|
-
console.debug("failed", e);
|
|
14
|
-
sender.postMessage({ eventId, methodName, error: e });
|
|
15
61
|
}
|
|
16
|
-
}
|
|
62
|
+
})
|
|
17
63
|
);
|
|
18
64
|
var bufToJson = (buf) => JSON.parse(new TextDecoder("UTF-8").decode(new Uint8Array(buf)));
|
|
65
|
+
var combineHandlers = (...handlers) => {
|
|
66
|
+
return ({ data: { methodName, ...data }, ports }) => {
|
|
67
|
+
for (const h of handlers) {
|
|
68
|
+
if (h.canHandle(methodName)) {
|
|
69
|
+
return h.handle({ data: { methodName, ...data }, ports });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
console.error("no handlers matched", methodName);
|
|
73
|
+
};
|
|
74
|
+
};
|
|
19
75
|
|
|
20
76
|
// src/worker/wwApi.ts
|
|
21
77
|
var STATE = {
|
|
@@ -40,7 +96,9 @@ var slugToFileMeta = async ({ slug, json }) => {
|
|
|
40
96
|
return { pageId, chapterId };
|
|
41
97
|
};
|
|
42
98
|
var loadPageContent = async ({ chapterId, json }) => {
|
|
43
|
-
|
|
99
|
+
if (chapterId) {
|
|
100
|
+
json.viewer.pages.hits[0].chapter = STATE.chapters[chapterId] || json.viewer.pages.hits[0].chapter;
|
|
101
|
+
}
|
|
44
102
|
return json;
|
|
45
103
|
};
|
|
46
104
|
var searchBooks = async ({
|
|
@@ -83,14 +141,7 @@ var loadPreload = (json) => {
|
|
|
83
141
|
});
|
|
84
142
|
};
|
|
85
143
|
|
|
86
|
-
// ../../node_modules/.pnpm/fflate@0.8.2/node_modules/fflate/esm/
|
|
87
|
-
import { createRequire } from "module";
|
|
88
|
-
var require2 = createRequire("/");
|
|
89
|
-
var Worker;
|
|
90
|
-
try {
|
|
91
|
-
Worker = require2("worker_threads").Worker;
|
|
92
|
-
} catch (e) {
|
|
93
|
-
}
|
|
144
|
+
// ../../node_modules/.pnpm/fflate@0.8.2/node_modules/fflate/esm/browser.js
|
|
94
145
|
var u8 = Uint8Array;
|
|
95
146
|
var u16 = Uint16Array;
|
|
96
147
|
var i32 = Int32Array;
|
|
@@ -739,6 +790,9 @@ var loadZip = async (buf, bookId) => {
|
|
|
739
790
|
const unzipper = new Unzip();
|
|
740
791
|
unzipper.register(UnzipInflate);
|
|
741
792
|
unzipper.onfile = (file) => {
|
|
793
|
+
if (!file.name.includes(".")) {
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
742
796
|
m.set(file.name, {
|
|
743
797
|
decoded: null,
|
|
744
798
|
zipEntry: {
|
|
@@ -762,17 +816,32 @@ var loadZip = async (buf, bookId) => {
|
|
|
762
816
|
}
|
|
763
817
|
});
|
|
764
818
|
};
|
|
765
|
-
|
|
819
|
+
const chunkSize = 32 * 1024 * 1024;
|
|
820
|
+
const nChunks = Math.max(1, Math.floor(buf.byteLength / chunkSize));
|
|
821
|
+
const sleep = new Promise((resolve) => setTimeout(resolve, 0));
|
|
822
|
+
for (let i = 0; i < nChunks - 1; ++i) {
|
|
823
|
+
unzipper.push(buf.slice(i * chunkSize, (i + 1) * chunkSize));
|
|
824
|
+
await sleep;
|
|
825
|
+
}
|
|
826
|
+
unzipper.push(buf.slice((nChunks - 1) * chunkSize, buf.byteLength), true);
|
|
766
827
|
BOOKS[bookId] = m;
|
|
767
828
|
return [...m.keys()];
|
|
768
829
|
};
|
|
769
830
|
|
|
770
|
-
// src/worker/
|
|
771
|
-
var CHECK_UPDATES =
|
|
772
|
-
var
|
|
773
|
-
|
|
831
|
+
// src/worker/offline-worker.ts
|
|
832
|
+
var CHECK_UPDATES = true;
|
|
833
|
+
var NATIVE = false;
|
|
834
|
+
var LOADED_BOOKS = /* @__PURE__ */ new Set();
|
|
835
|
+
var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
|
|
774
836
|
const fileName = zipUrl.split("/").slice(-1)[0];
|
|
775
837
|
const readArrayBuffer = async () => {
|
|
838
|
+
if (NATIVE) {
|
|
839
|
+
console.debug("try reading native", fileName);
|
|
840
|
+
const back = await sender.nativeReadFile({ fname: fileName });
|
|
841
|
+
console.debug("done readnig");
|
|
842
|
+
return back;
|
|
843
|
+
}
|
|
844
|
+
const opfsRoot = await navigator.storage.getDirectory();
|
|
776
845
|
const handle = await opfsRoot.getFileHandle(fileName);
|
|
777
846
|
const accessHandle = await handle.createSyncAccessHandle();
|
|
778
847
|
const size = accessHandle.getSize();
|
|
@@ -782,6 +851,9 @@ var fetchZipFile = async (zipUrl) => {
|
|
|
782
851
|
return arrayBuffer;
|
|
783
852
|
};
|
|
784
853
|
try {
|
|
854
|
+
if (forceRefresh) {
|
|
855
|
+
throw new Error("forceRefresh");
|
|
856
|
+
}
|
|
785
857
|
const buf = await readArrayBuffer();
|
|
786
858
|
if (CHECK_UPDATES) {
|
|
787
859
|
const response = await fetch(zipUrl, { method: "HEAD" });
|
|
@@ -795,13 +867,37 @@ var fetchZipFile = async (zipUrl) => {
|
|
|
795
867
|
}
|
|
796
868
|
return buf;
|
|
797
869
|
} catch (e) {
|
|
798
|
-
|
|
799
|
-
|
|
870
|
+
if (e.message !== "forceRefresh" && !e.message.includes("refetching")) {
|
|
871
|
+
console.debug("opfs db not found, creating it", e, zipUrl);
|
|
872
|
+
}
|
|
873
|
+
const response = await fetch(zipUrl);
|
|
874
|
+
const contentLength = +response.headers.get("Content-Length");
|
|
875
|
+
const reader = response.body.getReader();
|
|
876
|
+
let position = 0;
|
|
877
|
+
const arrayBuffer = new Uint8Array(contentLength);
|
|
878
|
+
let prevPosition = 0;
|
|
879
|
+
while (true) {
|
|
880
|
+
const { done, value: chunk } = await reader.read();
|
|
881
|
+
if (done) {
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
884
|
+
arrayBuffer.set(chunk, position);
|
|
885
|
+
position += chunk.length;
|
|
886
|
+
if (position - prevPosition > 1e6) {
|
|
887
|
+
prevPosition = position;
|
|
888
|
+
self.postMessage({ methodName: "opfsProgress", arg: { total: contentLength, bytes: position } });
|
|
889
|
+
}
|
|
890
|
+
}
|
|
800
891
|
if (arrayBuffer.byteLength < 1e4) {
|
|
801
|
-
const error = `likely ${zipUrl} was not fetched`;
|
|
892
|
+
const error = `likely ${zipUrl} was not fetched ${arrayBuffer.byteLength}`;
|
|
802
893
|
console.debug(error);
|
|
803
894
|
throw new Error(error);
|
|
804
895
|
}
|
|
896
|
+
if (NATIVE) {
|
|
897
|
+
await sender.nativeWriteFile({ fname: fileName, buffer: arrayBuffer });
|
|
898
|
+
return await readArrayBuffer();
|
|
899
|
+
}
|
|
900
|
+
const opfsRoot = await navigator.storage.getDirectory();
|
|
805
901
|
const handle = await opfsRoot.getFileHandle(fileName, { create: true });
|
|
806
902
|
const accessHandle = await handle.createSyncAccessHandle();
|
|
807
903
|
await accessHandle.truncate(0);
|
|
@@ -817,29 +913,46 @@ var slugToFileMeta2 = async ({ slug }) => {
|
|
|
817
913
|
return slugToFileMeta({ slug, json });
|
|
818
914
|
};
|
|
819
915
|
var PRELOAD_AS_BOOK_ID = "preload";
|
|
820
|
-
var
|
|
821
|
-
|
|
916
|
+
var isOldZip = (bookId) => typeof bookId === "number";
|
|
917
|
+
var loadBook2 = async ({ id: bookId, forceRefresh }) => {
|
|
918
|
+
if (LOADED_BOOKS.has(bookId)) {
|
|
919
|
+
self.postMessage({ methodName: "opfsProgress", arg: { total: 1, bytes: 1 } });
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
LOADED_BOOKS.add(bookId);
|
|
923
|
+
const buf = await fetchZipFile(`https://ci.lls.fr/artefacts/content/development/${bookId}.zip`, { forceRefresh });
|
|
822
924
|
try {
|
|
823
925
|
const entryNames = await loadZip(new Uint8Array(buf), bookId);
|
|
824
926
|
fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId } });
|
|
825
|
-
|
|
826
|
-
|
|
927
|
+
if (isOldZip(bookId)) {
|
|
928
|
+
const json = await readZipEntry({ entryName: `preload/${bookId}.json`, bookId: PRELOAD_AS_BOOK_ID, asJson: true });
|
|
929
|
+
loadBook(json);
|
|
930
|
+
}
|
|
827
931
|
} catch (e) {
|
|
828
932
|
console.error("failed", e);
|
|
829
933
|
}
|
|
830
934
|
};
|
|
831
935
|
var loadPageContent2 = async ({
|
|
832
|
-
bookId,
|
|
936
|
+
bookId: iBookId,
|
|
833
937
|
chapterId,
|
|
834
|
-
pageId
|
|
938
|
+
pageId,
|
|
939
|
+
entryId
|
|
835
940
|
}) => {
|
|
836
|
-
const
|
|
941
|
+
const bookId = entryId ? entryId.bookId : iBookId;
|
|
942
|
+
if (!bookId) {
|
|
943
|
+
throw new Error("missing bookId: expect entryId or bookId");
|
|
944
|
+
}
|
|
945
|
+
const json = await readZipEntry({
|
|
946
|
+
entryName: entryId ? entryId.entryName : `${chapterId}/${pageId}.json`,
|
|
947
|
+
bookId,
|
|
948
|
+
asJson: true
|
|
949
|
+
});
|
|
837
950
|
return await loadPageContent({ chapterId, json });
|
|
838
951
|
};
|
|
839
952
|
var fetchWorker;
|
|
840
953
|
var initChannel = (__, { ports }) => {
|
|
841
954
|
fetchWorker = ports[0];
|
|
842
|
-
const
|
|
955
|
+
const { canHandle, handle } = makeSyncHandler(fetchWorker, {
|
|
843
956
|
readZipEntry,
|
|
844
957
|
loadBook: loadBook2,
|
|
845
958
|
slugToFileMeta: slugToFileMeta2,
|
|
@@ -851,20 +964,45 @@ var initChannel = (__, { ports }) => {
|
|
|
851
964
|
self.postMessage({ methodName: "ready" });
|
|
852
965
|
return;
|
|
853
966
|
}
|
|
854
|
-
|
|
967
|
+
if (canHandle(dataObj.data?.methodName)) {
|
|
968
|
+
return handle(dataObj);
|
|
969
|
+
}
|
|
970
|
+
console.error("unknown methodName", dataObj.data?.methodName);
|
|
855
971
|
};
|
|
856
972
|
fetchWorker.onmessage = fn;
|
|
857
973
|
};
|
|
858
974
|
var loadBooks = async () => {
|
|
859
|
-
|
|
860
|
-
|
|
975
|
+
let entries = [];
|
|
976
|
+
if (NATIVE) {
|
|
977
|
+
entries = await sender.nativeListZips();
|
|
978
|
+
} else {
|
|
979
|
+
const opfsRoot = await navigator.storage.getDirectory();
|
|
980
|
+
entries = await opfsRoot.values();
|
|
981
|
+
}
|
|
861
982
|
for await (const entry of entries) {
|
|
983
|
+
if (/preload/.test(entry.name)) {
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
862
986
|
if (/\d+\.zip$/.test(entry.name)) {
|
|
863
987
|
const bookId = entry.name.match(/\d+/)[0];
|
|
864
|
-
await loadBook2(Number.parseInt(bookId, 10));
|
|
988
|
+
await loadBook2({ id: Number.parseInt(bookId, 10) });
|
|
989
|
+
} else if (/zip$/.test(entry.name)) {
|
|
990
|
+
const methodUri = entry.name.replace(".zip", "");
|
|
991
|
+
await loadBook2({ id: methodUri });
|
|
992
|
+
} else {
|
|
993
|
+
console.info(`invalid book entry ${entry.name}`);
|
|
865
994
|
}
|
|
866
995
|
}
|
|
867
996
|
};
|
|
997
|
+
var configure = ({ native, checkUpdates }) => {
|
|
998
|
+
console.info("configuring!!", native, checkUpdates);
|
|
999
|
+
if (typeof native !== "undefined") {
|
|
1000
|
+
NATIVE = !!native;
|
|
1001
|
+
}
|
|
1002
|
+
if (typeof native !== "undefined") {
|
|
1003
|
+
CHECK_UPDATES = !!checkUpdates;
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
868
1006
|
var loadPreload2 = async () => {
|
|
869
1007
|
const zipUrl = "https://lls-ci-fr.s3.eu-west-3.amazonaws.com/artefacts/content/development/preload.zip";
|
|
870
1008
|
const buf = await fetchZipFile(zipUrl);
|
|
@@ -877,9 +1015,13 @@ var loadPreload2 = async () => {
|
|
|
877
1015
|
console.error("failed", e);
|
|
878
1016
|
}
|
|
879
1017
|
};
|
|
880
|
-
|
|
1018
|
+
var mainHandle = makeSyncHandler(self, {
|
|
881
1019
|
initChannel,
|
|
882
1020
|
loadPreload: loadPreload2,
|
|
883
1021
|
readZipEntry,
|
|
884
|
-
loadBooks
|
|
1022
|
+
loadBooks,
|
|
1023
|
+
loadBook: loadBook2,
|
|
1024
|
+
configure
|
|
885
1025
|
});
|
|
1026
|
+
var { handler, ...sender } = makeSyncWorker(self, ["nativeWriteFile", "nativeReadFile", "nativeListZips"]);
|
|
1027
|
+
self.onmessage = combineHandlers(mainHandle, handler);
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "@lls/offline"
|
package/lib/index.js
CHANGED
|
@@ -1,319 +1,8 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
19
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
20
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
21
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
22
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
23
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
24
|
-
mod
|
|
25
|
-
));
|
|
26
|
-
|
|
27
|
-
// ../../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
|
|
28
|
-
var require_react_production_min = __commonJS({
|
|
29
|
-
"../../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js"(exports) {
|
|
30
|
-
"use strict";
|
|
31
|
-
var l = Symbol.for("react.element");
|
|
32
|
-
var n = Symbol.for("react.portal");
|
|
33
|
-
var p = Symbol.for("react.fragment");
|
|
34
|
-
var q = Symbol.for("react.strict_mode");
|
|
35
|
-
var r = Symbol.for("react.profiler");
|
|
36
|
-
var t = Symbol.for("react.provider");
|
|
37
|
-
var u = Symbol.for("react.context");
|
|
38
|
-
var v = Symbol.for("react.forward_ref");
|
|
39
|
-
var w = Symbol.for("react.suspense");
|
|
40
|
-
var x = Symbol.for("react.memo");
|
|
41
|
-
var y = Symbol.for("react.lazy");
|
|
42
|
-
var z = Symbol.iterator;
|
|
43
|
-
function A(a2) {
|
|
44
|
-
if (null === a2 || "object" !== typeof a2) return null;
|
|
45
|
-
a2 = z && a2[z] || a2["@@iterator"];
|
|
46
|
-
return "function" === typeof a2 ? a2 : null;
|
|
47
|
-
}
|
|
48
|
-
var B = { isMounted: function() {
|
|
49
|
-
return false;
|
|
50
|
-
}, enqueueForceUpdate: function() {
|
|
51
|
-
}, enqueueReplaceState: function() {
|
|
52
|
-
}, enqueueSetState: function() {
|
|
53
|
-
} };
|
|
54
|
-
var C = Object.assign;
|
|
55
|
-
var D = {};
|
|
56
|
-
function E(a2, b, e) {
|
|
57
|
-
this.props = a2;
|
|
58
|
-
this.context = b;
|
|
59
|
-
this.refs = D;
|
|
60
|
-
this.updater = e || B;
|
|
61
|
-
}
|
|
62
|
-
E.prototype.isReactComponent = {};
|
|
63
|
-
E.prototype.setState = function(a2, b) {
|
|
64
|
-
if ("object" !== typeof a2 && "function" !== typeof a2 && null != a2) throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");
|
|
65
|
-
this.updater.enqueueSetState(this, a2, b, "setState");
|
|
66
|
-
};
|
|
67
|
-
E.prototype.forceUpdate = function(a2) {
|
|
68
|
-
this.updater.enqueueForceUpdate(this, a2, "forceUpdate");
|
|
69
|
-
};
|
|
70
|
-
function F() {
|
|
71
|
-
}
|
|
72
|
-
F.prototype = E.prototype;
|
|
73
|
-
function G(a2, b, e) {
|
|
74
|
-
this.props = a2;
|
|
75
|
-
this.context = b;
|
|
76
|
-
this.refs = D;
|
|
77
|
-
this.updater = e || B;
|
|
78
|
-
}
|
|
79
|
-
var H = G.prototype = new F();
|
|
80
|
-
H.constructor = G;
|
|
81
|
-
C(H, E.prototype);
|
|
82
|
-
H.isPureReactComponent = true;
|
|
83
|
-
var I = Array.isArray;
|
|
84
|
-
var J = Object.prototype.hasOwnProperty;
|
|
85
|
-
var K = { current: null };
|
|
86
|
-
var L = { key: true, ref: true, __self: true, __source: true };
|
|
87
|
-
function M(a2, b, e) {
|
|
88
|
-
var d, c = {}, k = null, h = null;
|
|
89
|
-
if (null != b) for (d in void 0 !== b.ref && (h = b.ref), void 0 !== b.key && (k = "" + b.key), b) J.call(b, d) && !L.hasOwnProperty(d) && (c[d] = b[d]);
|
|
90
|
-
var g = arguments.length - 2;
|
|
91
|
-
if (1 === g) c.children = e;
|
|
92
|
-
else if (1 < g) {
|
|
93
|
-
for (var f = Array(g), m = 0; m < g; m++) f[m] = arguments[m + 2];
|
|
94
|
-
c.children = f;
|
|
95
|
-
}
|
|
96
|
-
if (a2 && a2.defaultProps) for (d in g = a2.defaultProps, g) void 0 === c[d] && (c[d] = g[d]);
|
|
97
|
-
return { $$typeof: l, type: a2, key: k, ref: h, props: c, _owner: K.current };
|
|
98
|
-
}
|
|
99
|
-
function N(a2, b) {
|
|
100
|
-
return { $$typeof: l, type: a2.type, key: b, ref: a2.ref, props: a2.props, _owner: a2._owner };
|
|
101
|
-
}
|
|
102
|
-
function O(a2) {
|
|
103
|
-
return "object" === typeof a2 && null !== a2 && a2.$$typeof === l;
|
|
104
|
-
}
|
|
105
|
-
function escape(a2) {
|
|
106
|
-
var b = { "=": "=0", ":": "=2" };
|
|
107
|
-
return "$" + a2.replace(/[=:]/g, function(a3) {
|
|
108
|
-
return b[a3];
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
var P = /\/+/g;
|
|
112
|
-
function Q(a2, b) {
|
|
113
|
-
return "object" === typeof a2 && null !== a2 && null != a2.key ? escape("" + a2.key) : b.toString(36);
|
|
114
|
-
}
|
|
115
|
-
function R(a2, b, e, d, c) {
|
|
116
|
-
var k = typeof a2;
|
|
117
|
-
if ("undefined" === k || "boolean" === k) a2 = null;
|
|
118
|
-
var h = false;
|
|
119
|
-
if (null === a2) h = true;
|
|
120
|
-
else switch (k) {
|
|
121
|
-
case "string":
|
|
122
|
-
case "number":
|
|
123
|
-
h = true;
|
|
124
|
-
break;
|
|
125
|
-
case "object":
|
|
126
|
-
switch (a2.$$typeof) {
|
|
127
|
-
case l:
|
|
128
|
-
case n:
|
|
129
|
-
h = true;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (h) return h = a2, c = c(h), a2 = "" === d ? "." + Q(h, 0) : d, I(c) ? (e = "", null != a2 && (e = a2.replace(P, "$&/") + "/"), R(c, b, e, "", function(a3) {
|
|
133
|
-
return a3;
|
|
134
|
-
})) : null != c && (O(c) && (c = N(c, e + (!c.key || h && h.key === c.key ? "" : ("" + c.key).replace(P, "$&/") + "/") + a2)), b.push(c)), 1;
|
|
135
|
-
h = 0;
|
|
136
|
-
d = "" === d ? "." : d + ":";
|
|
137
|
-
if (I(a2)) for (var g = 0; g < a2.length; g++) {
|
|
138
|
-
k = a2[g];
|
|
139
|
-
var f = d + Q(k, g);
|
|
140
|
-
h += R(k, b, e, f, c);
|
|
141
|
-
}
|
|
142
|
-
else if (f = A(a2), "function" === typeof f) for (a2 = f.call(a2), g = 0; !(k = a2.next()).done; ) k = k.value, f = d + Q(k, g++), h += R(k, b, e, f, c);
|
|
143
|
-
else if ("object" === k) throw b = String(a2), Error("Objects are not valid as a React child (found: " + ("[object Object]" === b ? "object with keys {" + Object.keys(a2).join(", ") + "}" : b) + "). If you meant to render a collection of children, use an array instead.");
|
|
144
|
-
return h;
|
|
145
|
-
}
|
|
146
|
-
function S(a2, b, e) {
|
|
147
|
-
if (null == a2) return a2;
|
|
148
|
-
var d = [], c = 0;
|
|
149
|
-
R(a2, d, "", "", function(a3) {
|
|
150
|
-
return b.call(e, a3, c++);
|
|
151
|
-
});
|
|
152
|
-
return d;
|
|
153
|
-
}
|
|
154
|
-
function T(a2) {
|
|
155
|
-
if (-1 === a2._status) {
|
|
156
|
-
var b = a2._result;
|
|
157
|
-
b = b();
|
|
158
|
-
b.then(function(b2) {
|
|
159
|
-
if (0 === a2._status || -1 === a2._status) a2._status = 1, a2._result = b2;
|
|
160
|
-
}, function(b2) {
|
|
161
|
-
if (0 === a2._status || -1 === a2._status) a2._status = 2, a2._result = b2;
|
|
162
|
-
});
|
|
163
|
-
-1 === a2._status && (a2._status = 0, a2._result = b);
|
|
164
|
-
}
|
|
165
|
-
if (1 === a2._status) return a2._result.default;
|
|
166
|
-
throw a2._result;
|
|
167
|
-
}
|
|
168
|
-
var U = { current: null };
|
|
169
|
-
var V = { transition: null };
|
|
170
|
-
var W = { ReactCurrentDispatcher: U, ReactCurrentBatchConfig: V, ReactCurrentOwner: K };
|
|
171
|
-
function X() {
|
|
172
|
-
throw Error("act(...) is not supported in production builds of React.");
|
|
173
|
-
}
|
|
174
|
-
exports.Children = { map: S, forEach: function(a2, b, e) {
|
|
175
|
-
S(a2, function() {
|
|
176
|
-
b.apply(this, arguments);
|
|
177
|
-
}, e);
|
|
178
|
-
}, count: function(a2) {
|
|
179
|
-
var b = 0;
|
|
180
|
-
S(a2, function() {
|
|
181
|
-
b++;
|
|
182
|
-
});
|
|
183
|
-
return b;
|
|
184
|
-
}, toArray: function(a2) {
|
|
185
|
-
return S(a2, function(a3) {
|
|
186
|
-
return a3;
|
|
187
|
-
}) || [];
|
|
188
|
-
}, only: function(a2) {
|
|
189
|
-
if (!O(a2)) throw Error("React.Children.only expected to receive a single React element child.");
|
|
190
|
-
return a2;
|
|
191
|
-
} };
|
|
192
|
-
exports.Component = E;
|
|
193
|
-
exports.Fragment = p;
|
|
194
|
-
exports.Profiler = r;
|
|
195
|
-
exports.PureComponent = G;
|
|
196
|
-
exports.StrictMode = q;
|
|
197
|
-
exports.Suspense = w;
|
|
198
|
-
exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = W;
|
|
199
|
-
exports.act = X;
|
|
200
|
-
exports.cloneElement = function(a2, b, e) {
|
|
201
|
-
if (null === a2 || void 0 === a2) throw Error("React.cloneElement(...): The argument must be a React element, but you passed " + a2 + ".");
|
|
202
|
-
var d = C({}, a2.props), c = a2.key, k = a2.ref, h = a2._owner;
|
|
203
|
-
if (null != b) {
|
|
204
|
-
void 0 !== b.ref && (k = b.ref, h = K.current);
|
|
205
|
-
void 0 !== b.key && (c = "" + b.key);
|
|
206
|
-
if (a2.type && a2.type.defaultProps) var g = a2.type.defaultProps;
|
|
207
|
-
for (f in b) J.call(b, f) && !L.hasOwnProperty(f) && (d[f] = void 0 === b[f] && void 0 !== g ? g[f] : b[f]);
|
|
208
|
-
}
|
|
209
|
-
var f = arguments.length - 2;
|
|
210
|
-
if (1 === f) d.children = e;
|
|
211
|
-
else if (1 < f) {
|
|
212
|
-
g = Array(f);
|
|
213
|
-
for (var m = 0; m < f; m++) g[m] = arguments[m + 2];
|
|
214
|
-
d.children = g;
|
|
215
|
-
}
|
|
216
|
-
return { $$typeof: l, type: a2.type, key: c, ref: k, props: d, _owner: h };
|
|
217
|
-
};
|
|
218
|
-
exports.createContext = function(a2) {
|
|
219
|
-
a2 = { $$typeof: u, _currentValue: a2, _currentValue2: a2, _threadCount: 0, Provider: null, Consumer: null, _defaultValue: null, _globalName: null };
|
|
220
|
-
a2.Provider = { $$typeof: t, _context: a2 };
|
|
221
|
-
return a2.Consumer = a2;
|
|
222
|
-
};
|
|
223
|
-
exports.createElement = M;
|
|
224
|
-
exports.createFactory = function(a2) {
|
|
225
|
-
var b = M.bind(null, a2);
|
|
226
|
-
b.type = a2;
|
|
227
|
-
return b;
|
|
228
|
-
};
|
|
229
|
-
exports.createRef = function() {
|
|
230
|
-
return { current: null };
|
|
231
|
-
};
|
|
232
|
-
exports.forwardRef = function(a2) {
|
|
233
|
-
return { $$typeof: v, render: a2 };
|
|
234
|
-
};
|
|
235
|
-
exports.isValidElement = O;
|
|
236
|
-
exports.lazy = function(a2) {
|
|
237
|
-
return { $$typeof: y, _payload: { _status: -1, _result: a2 }, _init: T };
|
|
238
|
-
};
|
|
239
|
-
exports.memo = function(a2, b) {
|
|
240
|
-
return { $$typeof: x, type: a2, compare: void 0 === b ? null : b };
|
|
241
|
-
};
|
|
242
|
-
exports.startTransition = function(a2) {
|
|
243
|
-
var b = V.transition;
|
|
244
|
-
V.transition = {};
|
|
245
|
-
try {
|
|
246
|
-
a2();
|
|
247
|
-
} finally {
|
|
248
|
-
V.transition = b;
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
exports.unstable_act = X;
|
|
252
|
-
exports.useCallback = function(a2, b) {
|
|
253
|
-
return U.current.useCallback(a2, b);
|
|
254
|
-
};
|
|
255
|
-
exports.useContext = function(a2) {
|
|
256
|
-
return U.current.useContext(a2);
|
|
257
|
-
};
|
|
258
|
-
exports.useDebugValue = function() {
|
|
259
|
-
};
|
|
260
|
-
exports.useDeferredValue = function(a2) {
|
|
261
|
-
return U.current.useDeferredValue(a2);
|
|
262
|
-
};
|
|
263
|
-
exports.useEffect = function(a2, b) {
|
|
264
|
-
return U.current.useEffect(a2, b);
|
|
265
|
-
};
|
|
266
|
-
exports.useId = function() {
|
|
267
|
-
return U.current.useId();
|
|
268
|
-
};
|
|
269
|
-
exports.useImperativeHandle = function(a2, b, e) {
|
|
270
|
-
return U.current.useImperativeHandle(a2, b, e);
|
|
271
|
-
};
|
|
272
|
-
exports.useInsertionEffect = function(a2, b) {
|
|
273
|
-
return U.current.useInsertionEffect(a2, b);
|
|
274
|
-
};
|
|
275
|
-
exports.useLayoutEffect = function(a2, b) {
|
|
276
|
-
return U.current.useLayoutEffect(a2, b);
|
|
277
|
-
};
|
|
278
|
-
exports.useMemo = function(a2, b) {
|
|
279
|
-
return U.current.useMemo(a2, b);
|
|
280
|
-
};
|
|
281
|
-
exports.useReducer = function(a2, b, e) {
|
|
282
|
-
return U.current.useReducer(a2, b, e);
|
|
283
|
-
};
|
|
284
|
-
exports.useRef = function(a2) {
|
|
285
|
-
return U.current.useRef(a2);
|
|
286
|
-
};
|
|
287
|
-
exports.useState = function(a2) {
|
|
288
|
-
return U.current.useState(a2);
|
|
289
|
-
};
|
|
290
|
-
exports.useSyncExternalStore = function(a2, b, e) {
|
|
291
|
-
return U.current.useSyncExternalStore(a2, b, e);
|
|
292
|
-
};
|
|
293
|
-
exports.useTransition = function() {
|
|
294
|
-
return U.current.useTransition();
|
|
295
|
-
};
|
|
296
|
-
exports.version = "18.3.1";
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
// ../../node_modules/.pnpm/react@18.3.1/node_modules/react/index.js
|
|
301
|
-
var require_react = __commonJS({
|
|
302
|
-
"../../node_modules/.pnpm/react@18.3.1/node_modules/react/index.js"(exports, module) {
|
|
303
|
-
"use strict";
|
|
304
|
-
if (true) {
|
|
305
|
-
module.exports = require_react_production_min();
|
|
306
|
-
} else {
|
|
307
|
-
module.exports = null;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
|
|
312
1
|
// src/utils/worker.ts
|
|
313
|
-
var makeSyncWorker = (
|
|
2
|
+
var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
|
|
314
3
|
const dic = {};
|
|
315
4
|
const cbks = {};
|
|
316
|
-
const
|
|
5
|
+
const fn = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
|
|
317
6
|
if (eventId && eventId in dic) {
|
|
318
7
|
if (error) {
|
|
319
8
|
dic[eventId].reject(error);
|
|
@@ -327,46 +16,69 @@ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
|
|
|
327
16
|
}
|
|
328
17
|
methodName && cbks[methodName]?.(arg);
|
|
329
18
|
};
|
|
330
|
-
offlineWorker.onmessage = fn2;
|
|
331
19
|
const makeFn = (methodName) => {
|
|
332
20
|
return ({ ports, ...arg } = {}) => {
|
|
333
21
|
return new Promise((resolve, reject) => {
|
|
334
22
|
const eventId = `${methodName}:${Date.now()}`;
|
|
335
23
|
dic[eventId] = { resolve, reject };
|
|
336
|
-
|
|
24
|
+
offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
|
|
337
25
|
});
|
|
338
26
|
};
|
|
339
27
|
};
|
|
340
28
|
const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
|
|
341
|
-
on: (key,
|
|
342
|
-
cbks[key] =
|
|
29
|
+
on: (key, fn2) => {
|
|
30
|
+
cbks[key] = fn2;
|
|
343
31
|
}
|
|
344
32
|
});
|
|
345
|
-
return
|
|
33
|
+
return {
|
|
34
|
+
...obj,
|
|
35
|
+
handler: {
|
|
36
|
+
canHandle: (methodName) => {
|
|
37
|
+
return methodName in cbks || methodNames.includes(methodName);
|
|
38
|
+
},
|
|
39
|
+
handle: fn
|
|
40
|
+
}
|
|
41
|
+
};
|
|
346
42
|
};
|
|
347
43
|
var a = (a2) => a2;
|
|
348
44
|
var makeSyncHandler = a(
|
|
349
|
-
(sender, services) =>
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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 });
|
|
355
60
|
}
|
|
356
|
-
throw new Error(`invalid methodName ${String(methodName)}`);
|
|
357
|
-
} catch (e) {
|
|
358
|
-
console.debug("failed", e);
|
|
359
|
-
sender.postMessage({ eventId, methodName, error: e });
|
|
360
61
|
}
|
|
361
|
-
}
|
|
62
|
+
})
|
|
362
63
|
);
|
|
64
|
+
var combineHandlers = (...handlers) => {
|
|
65
|
+
return ({ data: { methodName, ...data }, ports }) => {
|
|
66
|
+
for (const h of handlers) {
|
|
67
|
+
if (h.canHandle(methodName)) {
|
|
68
|
+
return h.handle({ data: { methodName, ...data }, ports });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
console.error("no handlers matched", methodName);
|
|
72
|
+
};
|
|
73
|
+
};
|
|
363
74
|
|
|
364
75
|
// src/hooks/useOfflineWorker.ts
|
|
365
|
-
|
|
366
|
-
var
|
|
76
|
+
import { useCallback, useEffect, useState } from "react";
|
|
77
|
+
var offlineWorker = new Worker("/src/worker/offline-worker.js", { type: "module" });
|
|
78
|
+
var { handler, ...offlineWorkerSync } = makeSyncWorker(
|
|
367
79
|
// @ts-ignore: TODO
|
|
368
|
-
|
|
369
|
-
["initChannel", "readZipEntry", "loadPreload", "loadBooks"],
|
|
80
|
+
offlineWorker,
|
|
81
|
+
["initChannel", "readZipEntry", "configure", "loadPreload", "loadBooks", "loadBook"],
|
|
370
82
|
"app:offlineWorker"
|
|
371
83
|
);
|
|
372
84
|
var setWorkersReady;
|
|
@@ -377,54 +89,67 @@ var setBooksLoaded;
|
|
|
377
89
|
var setBooksLoadedPromise = new Promise((resolve) => {
|
|
378
90
|
setBooksLoaded = resolve;
|
|
379
91
|
});
|
|
380
|
-
var
|
|
92
|
+
var installWorker = async ({
|
|
93
|
+
skipPreload = false,
|
|
94
|
+
nativeReadFile,
|
|
95
|
+
nativeWriteFile,
|
|
96
|
+
nativeListZips,
|
|
97
|
+
checkUpdates
|
|
98
|
+
} = {}) => {
|
|
381
99
|
let fetchWorker = null;
|
|
382
100
|
await navigator.serviceWorker.getRegistrations().then(async (registrations) => {
|
|
383
101
|
for (const registration of registrations) {
|
|
384
102
|
await registration.unregister();
|
|
385
103
|
}
|
|
386
104
|
});
|
|
387
|
-
await navigator.serviceWorker.register("/fetch
|
|
105
|
+
await navigator.serviceWorker.register("/fetch-worker.js", { type: "module" }).then((reg) => reg.update());
|
|
388
106
|
await navigator.serviceWorker.ready.then(async (reg) => {
|
|
389
107
|
console.debug("Service Worker registered:", reg.scope);
|
|
390
108
|
fetchWorker = reg.active;
|
|
391
109
|
}).catch((err) => console.error("Service Worker registration failed:", err));
|
|
392
110
|
if (!fetchWorker) {
|
|
111
|
+
console.debug("failed to get fetch worker");
|
|
393
112
|
return;
|
|
394
113
|
}
|
|
114
|
+
if (nativeReadFile) {
|
|
115
|
+
const wwHandler = makeSyncHandler(offlineWorker, { nativeReadFile, nativeWriteFile, nativeListZips });
|
|
116
|
+
offlineWorker.onmessage = combineHandlers(handler, wwHandler);
|
|
117
|
+
offlineWorkerSync.configure({ native: true, checkUpdates });
|
|
118
|
+
} else {
|
|
119
|
+
offlineWorkerSync.configure({ checkUpdates });
|
|
120
|
+
offlineWorker.onmessage = handler.handle;
|
|
121
|
+
}
|
|
395
122
|
offlineWorkerSync.on("ready", setWorkersReady);
|
|
396
123
|
const channel = new MessageChannel();
|
|
397
124
|
offlineWorkerSync.initChannel({ ports: [channel.port1] });
|
|
398
125
|
fetchWorker.postMessage({ methodName: "initChannel" }, [channel.port2]);
|
|
399
|
-
|
|
126
|
+
offlineWorkerSync.on("opfsProgress", () => {
|
|
127
|
+
});
|
|
128
|
+
if (!skipPreload) {
|
|
129
|
+
await offlineWorkerSync.loadPreload();
|
|
130
|
+
}
|
|
400
131
|
await offlineWorkerSync.loadBooks();
|
|
401
132
|
setBooksLoaded();
|
|
402
133
|
};
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
134
|
+
var useOfflineWorker_default = ({ skipPreload = false, nativeReadFile, nativeWriteFile, nativeListZips, checkUpdates } = {}) => {
|
|
135
|
+
const [ready, setReady] = useState(false);
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
const fn = async () => {
|
|
138
|
+
await installWorker({ skipPreload, nativeReadFile, nativeWriteFile, nativeListZips, checkUpdates });
|
|
408
139
|
await Promise.all([workersReadyPromise, setBooksLoadedPromise]);
|
|
409
140
|
setReady(true);
|
|
410
141
|
};
|
|
411
|
-
|
|
142
|
+
fn();
|
|
143
|
+
}, [skipPreload, nativeReadFile, nativeWriteFile, nativeListZips, checkUpdates]);
|
|
144
|
+
const loadBook = useCallback(async ({ id, forceRefresh }, cbk) => {
|
|
145
|
+
offlineWorkerSync.on("opfsProgress", cbk);
|
|
146
|
+
return await offlineWorkerSync.loadBook({ id, forceRefresh });
|
|
412
147
|
}, []);
|
|
413
|
-
return
|
|
148
|
+
return {
|
|
149
|
+
ready,
|
|
150
|
+
loadBook
|
|
151
|
+
};
|
|
414
152
|
};
|
|
415
153
|
export {
|
|
416
154
|
useOfflineWorker_default as useOfflineWorker
|
|
417
155
|
};
|
|
418
|
-
/*! Bundled license information:
|
|
419
|
-
|
|
420
|
-
react/cjs/react.production.min.js:
|
|
421
|
-
(**
|
|
422
|
-
* @license React
|
|
423
|
-
* react.production.min.js
|
|
424
|
-
*
|
|
425
|
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
426
|
-
*
|
|
427
|
-
* This source code is licensed under the MIT license found in the
|
|
428
|
-
* LICENSE file in the root directory of this source tree.
|
|
429
|
-
*)
|
|
430
|
-
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lls/offline",
|
|
3
|
-
"version": "24.20.0-
|
|
3
|
+
"version": "24.20.0-debug2",
|
|
4
4
|
"description": "offline",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"module": "./lib/index.js",
|
|
@@ -36,10 +36,12 @@
|
|
|
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",
|
|
39
40
|
"@testing-library/react": "16.3.0",
|
|
40
41
|
"@types/react": "18.3.12",
|
|
41
42
|
"@types/react-dom": "18.3.1",
|
|
42
43
|
"@vitejs/plugin-react": "4.5.1",
|
|
44
|
+
"@vitejs/plugin-react-swc": "3.10.1",
|
|
43
45
|
"esbuild": "0.24.0",
|
|
44
46
|
"happy-dom": "15.11.0",
|
|
45
47
|
"lodash": "4.17.21",
|
|
@@ -48,17 +50,21 @@
|
|
|
48
50
|
"react-dom": "18.3.1",
|
|
49
51
|
"typescript": "5.8.3",
|
|
50
52
|
"vite": "7.0.0",
|
|
53
|
+
"vite-tsconfig-paths": "5.1.4",
|
|
51
54
|
"zod": "3.25.50",
|
|
52
|
-
"@lls/vitescripts": "24.
|
|
55
|
+
"@lls/vitescripts": "24.22.0"
|
|
53
56
|
},
|
|
54
57
|
"dependencies": {
|
|
55
58
|
"fflate": "0.8.2"
|
|
56
59
|
},
|
|
57
60
|
"scripts": {
|
|
58
|
-
"start": "
|
|
61
|
+
"start": "pnpm ladle serve",
|
|
59
62
|
"dts": "pnpm tsc --project tsconfig.production.json",
|
|
60
|
-
"build": "node build",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
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"
|
|
63
69
|
}
|
|
64
70
|
}
|