@lls/offline 24.20.0-debug → 24.22.0-34f32711
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} +126 -49
- package/lib/assets/{offlineWorker.js → offline-worker.js} +260 -56
- package/lib/index.d.ts +1 -0
- package/lib/index.js +139 -370
- 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
|
+
|
|
@@ -11,12 +11,11 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
|
|
|
11
11
|
}
|
|
12
12
|
} else {
|
|
13
13
|
if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
|
|
14
|
-
console.error(`syncWorker: ${dbg}`, eventId, methodName, error);
|
|
14
|
+
console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
|
|
15
15
|
}
|
|
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,54 @@ 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));
|
|
65
|
+
var openCache = async () => caches.open(`LLS_OFFLINE_${process.env.CONF_ENV}`);
|
|
53
66
|
|
|
54
67
|
// src/worker/swFetchScreenCall.ts
|
|
68
|
+
var zipSlugName = (str) => str.replace(/[^a-zA-Z0-9-.]/g, "");
|
|
69
|
+
var cocoreFetchPartialPageScreen = async (_ev, { offlineWorkerSync: offlineWorkerSync2, variables, knownLinks: knownLinks2 }) => {
|
|
70
|
+
const key = `gqls/${zipSlugName(variables.slugs[0] || "")}.json`;
|
|
71
|
+
const entryId = knownLinks2.get(key);
|
|
72
|
+
if (!entryId) {
|
|
73
|
+
throw new Error(`slug not found ${key}`);
|
|
74
|
+
}
|
|
75
|
+
const gqlPage = await offlineWorkerSync2.loadPageContent({ entryId });
|
|
76
|
+
return { data: gqlPage };
|
|
77
|
+
};
|
|
78
|
+
var cocoreFetchPartialPageSiblingsScreen = async (_ev) => {
|
|
79
|
+
return { data: { viewer: {} } };
|
|
80
|
+
};
|
|
55
81
|
var coreFetchBookDrawerScreen = async (_ev, { offlineWorkerSync: offlineWorkerSync2, variables }) => {
|
|
56
82
|
const slug = variables.slugs?.[0];
|
|
57
83
|
const uris = variables.uris;
|
|
@@ -161,30 +187,39 @@ var coreFetchPagesApi = async (ev, { offlineWorkerSync: offlineWorkerSync2, vari
|
|
|
161
187
|
}
|
|
162
188
|
}
|
|
163
189
|
};
|
|
164
|
-
var
|
|
190
|
+
var handles = {
|
|
191
|
+
coreFetchBookDrawerScreen,
|
|
192
|
+
coreFetchPagesApi,
|
|
193
|
+
cocoreFetchPartialPageScreen,
|
|
194
|
+
cocoreFetchPartialPageSiblingsScreen
|
|
195
|
+
};
|
|
196
|
+
var canHandleScreenCall = (url) => new RegExp(Object.keys(handles).join("|")).test(url);
|
|
197
|
+
var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, knownLinks: knownLinks2 }) => {
|
|
165
198
|
try {
|
|
166
199
|
const variables = (
|
|
167
|
-
// @ts-
|
|
200
|
+
// @ts-expect-error TODO: handle variables from body(stream) properly!!
|
|
168
201
|
ev.request.body?.variables || JSON.parse(new URLSearchParams(new URL(ev.request.url).search).get("variables") || "{}")
|
|
169
202
|
);
|
|
170
203
|
console.debug("variables are", variables, "vs", ev.request.body);
|
|
171
|
-
const handles = {
|
|
172
|
-
coreFetchBookDrawerScreen,
|
|
173
|
-
coreFetchPagesApi
|
|
174
|
-
};
|
|
175
204
|
const key = ev.request.url.match(Object.keys(handles).join("|"))?.[0];
|
|
176
205
|
if (!key) {
|
|
177
206
|
throw new Error("unknown ev");
|
|
178
207
|
}
|
|
179
|
-
const json = await handles[key](ev, { offlineWorkerSync: offlineWorkerSync2, variables });
|
|
208
|
+
const json = await handles[key](ev, { offlineWorkerSync: offlineWorkerSync2, variables, knownLinks: knownLinks2 });
|
|
180
209
|
return { status: 200, json };
|
|
181
210
|
} catch (e) {
|
|
182
211
|
console.error(e);
|
|
183
212
|
return { status: 500, json: JSON.stringify(e) };
|
|
184
213
|
}
|
|
185
214
|
};
|
|
215
|
+
var setKnownLinks = (knownLinks2, { entryNames, bookId }) => {
|
|
216
|
+
entryNames.forEach((entryName) => {
|
|
217
|
+
const link = entryName.replace(new RegExp(`^\\d+/media/|^${bookId}/medias/`), "").replace(new RegExp(`^${bookId}/gqls/`), "gqls/");
|
|
218
|
+
knownLinks2.set(link, { entryName, bookId });
|
|
219
|
+
});
|
|
220
|
+
};
|
|
186
221
|
|
|
187
|
-
// src/worker/fetch
|
|
222
|
+
// src/worker/fetch-worker.ts
|
|
188
223
|
var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
|
|
189
224
|
try {
|
|
190
225
|
const buf = await offlineWorkerSync2.readZipEntry(entryId);
|
|
@@ -212,9 +247,9 @@ var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorke
|
|
|
212
247
|
});
|
|
213
248
|
}
|
|
214
249
|
};
|
|
215
|
-
var fetchScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2 }) => {
|
|
250
|
+
var fetchScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2, knownLinks: knownLinks2 }) => {
|
|
216
251
|
try {
|
|
217
|
-
const { status, json } = await handleScreenCall(ev, { offlineWorkerSync: offlineWorkerSync2 });
|
|
252
|
+
const { status, json } = await handleScreenCall(ev, { offlineWorkerSync: offlineWorkerSync2, knownLinks: knownLinks2 });
|
|
218
253
|
return new Response(jsonToBuf(json), {
|
|
219
254
|
headers: {
|
|
220
255
|
"Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
|
|
@@ -250,65 +285,107 @@ var ACCEPTED_EXTENSIONS = {
|
|
|
250
285
|
".m4a": "audio/mp4",
|
|
251
286
|
".webp": "image/webp"
|
|
252
287
|
};
|
|
288
|
+
var putInCache = async (request, response) => {
|
|
289
|
+
const cache = await openCache();
|
|
290
|
+
await cache.put(request, response);
|
|
291
|
+
};
|
|
292
|
+
var onlineFirst = async (request, event) => {
|
|
293
|
+
try {
|
|
294
|
+
const responseFromNetwork = await fetch(request);
|
|
295
|
+
const cached = putInCache(request, responseFromNetwork.clone());
|
|
296
|
+
if (request.url.endsWith(".zip")) {
|
|
297
|
+
await cached;
|
|
298
|
+
event.waitUntil(Promise.resolve());
|
|
299
|
+
} else {
|
|
300
|
+
event.waitUntil(cached);
|
|
301
|
+
}
|
|
302
|
+
return responseFromNetwork;
|
|
303
|
+
} catch (_e) {
|
|
304
|
+
const over = /\/\?story=basic/.test(request.url) ? request.url.replace(/\/\?story=.*$/, "") : request;
|
|
305
|
+
const responseFromCache = await caches.match(over);
|
|
306
|
+
if (!responseFromCache) {
|
|
307
|
+
return new Response("FAILED fetch", {
|
|
308
|
+
// @ts-ignore: poor ts typing
|
|
309
|
+
ok: false,
|
|
310
|
+
status: 500,
|
|
311
|
+
type: "cors",
|
|
312
|
+
url: event.request.url
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
return responseFromCache;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
var STORAGE = "cache";
|
|
253
319
|
var swSelf = self;
|
|
254
320
|
swSelf.addEventListener("fetch", (ev) => {
|
|
255
321
|
const url = ev.request.url;
|
|
256
322
|
const extension = url.match(/\.[^.]+$/)?.[0];
|
|
257
323
|
const mimeType = extension && extension in ACCEPTED_EXTENSIONS ? ACCEPTED_EXTENSIONS[extension] : "";
|
|
258
|
-
const
|
|
259
|
-
if (
|
|
260
|
-
if (!/\.(css|tsx?|jsx?|woff2|fr)/.test(url)) {
|
|
261
|
-
console.debug("no ext", url.match(/\.[^.]+$/)?.[0]);
|
|
262
|
-
}
|
|
324
|
+
const isValidScreenCall = canHandleScreenCall(url);
|
|
325
|
+
if (STORAGE !== "cache" && url.endsWith(".zip")) {
|
|
263
326
|
return;
|
|
264
327
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
console.info("no link for", url.replace(/https:\/\/.*?\//, ""));
|
|
328
|
+
if (!mimeType && !isValidScreenCall) {
|
|
329
|
+
if (!/\.(css|tsx?|jsx?|woff2|fr|manifest)(\??|$)|@(vite|react|id)/.test(url)) {
|
|
330
|
+
console.debug("no ext", extension || url);
|
|
269
331
|
}
|
|
270
|
-
return;
|
|
332
|
+
return ev.respondWith(onlineFirst(ev.request, ev));
|
|
271
333
|
}
|
|
272
|
-
|
|
334
|
+
const entryId = knownLinks.get(url.replace(/https:\/\/.*?\//, "")) || knownLinks.get(`medias/${url.replace(/https:\/\/.*?\//, "")}`);
|
|
335
|
+
if (isValidScreenCall) {
|
|
273
336
|
console.debug("screencall!");
|
|
274
|
-
return ev.respondWith(fetchScreenCall(ev, { offlineWorkerSync }));
|
|
337
|
+
return ev.respondWith(fetchScreenCall(ev, { offlineWorkerSync, knownLinks }));
|
|
275
338
|
}
|
|
276
|
-
|
|
339
|
+
if (entryId) {
|
|
340
|
+
return ev.respondWith(fetchAsset(ev, { entryId, mimeType, offlineWorkerSync }));
|
|
341
|
+
}
|
|
342
|
+
if (/lls|lelivrescolaire/.test(url)) {
|
|
343
|
+
console.info("no link for", url.replace(/https:\/\/.*?\//, ""), `medias/${url.replace(/https:\/\/.*?\//, "")}`);
|
|
344
|
+
}
|
|
345
|
+
return;
|
|
277
346
|
});
|
|
278
347
|
swSelf.addEventListener("install", (_event) => {
|
|
279
348
|
swSelf.skipWaiting();
|
|
280
349
|
});
|
|
281
|
-
swSelf.addEventListener("activate", (
|
|
282
|
-
|
|
350
|
+
swSelf.addEventListener("activate", (event) => {
|
|
351
|
+
event.waitUntil(swSelf.clients.claim());
|
|
283
352
|
});
|
|
284
353
|
var offlineWorker;
|
|
285
354
|
var offlineWorkerSync;
|
|
286
355
|
var initChannel = async (__, { ports }) => {
|
|
287
|
-
console.info(`FETCH WORKER innitChannel v${
|
|
356
|
+
console.info(`FETCH WORKER innitChannel v${/* @__PURE__ */ new Date()}`);
|
|
288
357
|
offlineWorker = ports[0];
|
|
289
|
-
|
|
358
|
+
const { handler, ...sender } = makeSyncWorker(
|
|
290
359
|
offlineWorker,
|
|
291
360
|
["readZipEntry", "loadBook", "slugToFileMeta", "loadPageContent", "searchBooks"],
|
|
292
361
|
"fetchworker"
|
|
293
362
|
);
|
|
294
|
-
offlineWorkerSync
|
|
363
|
+
offlineWorkerSync = sender;
|
|
364
|
+
offlineWorker.onmessage = handler.handle;
|
|
365
|
+
offlineWorkerSync.on("setKnownLinks", setKnownLinks.bind(null, setKnownLinks));
|
|
295
366
|
offlineWorker.postMessage({ methodName: "sendAppReady" });
|
|
296
367
|
};
|
|
297
368
|
var knownLinks = /* @__PURE__ */ new Map();
|
|
298
|
-
var
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
369
|
+
var configure = ({ storage } = {}) => {
|
|
370
|
+
STORAGE = "cache";
|
|
371
|
+
if (typeof storage !== "undefined") {
|
|
372
|
+
STORAGE = storage;
|
|
373
|
+
}
|
|
374
|
+
console.info("sw::configure", { STORAGE });
|
|
303
375
|
};
|
|
304
376
|
swSelf.onmessage = async ({
|
|
305
377
|
data: { methodName, arg } = {},
|
|
306
378
|
ports
|
|
307
379
|
}) => {
|
|
308
380
|
const services = {
|
|
309
|
-
initChannel
|
|
381
|
+
initChannel,
|
|
382
|
+
configure
|
|
310
383
|
};
|
|
311
384
|
try {
|
|
385
|
+
if (methodName === "configure") {
|
|
386
|
+
await services[methodName](arg);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
312
389
|
if (methodName === "initChannel") {
|
|
313
390
|
if (!ports.length) {
|
|
314
391
|
throw new Error("fetchworker::missing port!");
|
|
@@ -316,7 +393,7 @@ swSelf.onmessage = async ({
|
|
|
316
393
|
await services[methodName](arg, { ports });
|
|
317
394
|
return;
|
|
318
395
|
}
|
|
319
|
-
throw new Error(`fetchworker:invalid methodName ${methodName}`);
|
|
396
|
+
throw new Error(`fetchworker:invalid methodName ${Object.keys(services)} ${methodName}`);
|
|
320
397
|
} catch (e) {
|
|
321
398
|
console.error("failed", e);
|
|
322
399
|
}
|
|
@@ -1,21 +1,89 @@
|
|
|
1
|
+
// src/utils/error.ts
|
|
2
|
+
var tryCatch = (fn) => {
|
|
3
|
+
return (resolve, reject) => {
|
|
4
|
+
try {
|
|
5
|
+
return fn().then(resolve);
|
|
6
|
+
} catch (e) {
|
|
7
|
+
return reject(e);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
1
12
|
// src/utils/worker.ts
|
|
13
|
+
var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
|
|
14
|
+
const dic = {};
|
|
15
|
+
const cbks = {};
|
|
16
|
+
const fn = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
|
|
17
|
+
if (eventId && eventId in dic) {
|
|
18
|
+
if (error) {
|
|
19
|
+
dic[eventId].reject(error);
|
|
20
|
+
} else {
|
|
21
|
+
dic[eventId].resolve(results);
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
|
|
25
|
+
console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
methodName && cbks[methodName]?.(arg);
|
|
29
|
+
};
|
|
30
|
+
const makeFn = (methodName) => {
|
|
31
|
+
return ({ ports, ...arg } = {}) => {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const eventId = `${methodName}:${Date.now()}`;
|
|
34
|
+
dic[eventId] = { resolve, reject };
|
|
35
|
+
offlineWorker.postMessage({ eventId, methodName, arg }, ports);
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
|
|
40
|
+
on: (key2, fn2) => {
|
|
41
|
+
cbks[key2] = fn2;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
...obj,
|
|
46
|
+
handler: {
|
|
47
|
+
canHandle: (methodName) => {
|
|
48
|
+
return methodName in cbks || methodNames.includes(methodName);
|
|
49
|
+
},
|
|
50
|
+
handle: fn
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
};
|
|
2
54
|
var a = (a2) => a2;
|
|
3
55
|
var makeSyncHandler = a(
|
|
4
|
-
(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
56
|
+
(sender2, services) => ({
|
|
57
|
+
canHandle: (methodName) => {
|
|
58
|
+
return methodName in services;
|
|
59
|
+
},
|
|
60
|
+
handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
|
|
61
|
+
try {
|
|
62
|
+
if (typeof methodName === "string" && methodName in services && services[methodName]) {
|
|
63
|
+
const results = await services[methodName](arg, { ports });
|
|
64
|
+
sender2.postMessage({ eventId, methodName, results });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.debug("failed", e);
|
|
70
|
+
sender2.postMessage({ eventId, methodName, error: e });
|
|
10
71
|
}
|
|
11
|
-
throw new Error(`invalid methodName ${String(methodName)}`);
|
|
12
|
-
} catch (e) {
|
|
13
|
-
console.debug("failed", e);
|
|
14
|
-
sender.postMessage({ eventId, methodName, error: e });
|
|
15
72
|
}
|
|
16
|
-
}
|
|
73
|
+
})
|
|
17
74
|
);
|
|
18
75
|
var bufToJson = (buf) => JSON.parse(new TextDecoder("UTF-8").decode(new Uint8Array(buf)));
|
|
76
|
+
var combineHandlers = (...handlers) => {
|
|
77
|
+
return ({ data: { methodName, ...data } = {}, ports }) => {
|
|
78
|
+
for (const h of handlers) {
|
|
79
|
+
if (h.canHandle(String(methodName))) {
|
|
80
|
+
return h.handle({ data: { methodName, ...data }, ports });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
console.error("no handlers matched", methodName);
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
var openCache = async () => caches.open(`LLS_OFFLINE_${process.env.CONF_ENV}`);
|
|
19
87
|
|
|
20
88
|
// src/worker/wwApi.ts
|
|
21
89
|
var STATE = {
|
|
@@ -40,7 +108,9 @@ var slugToFileMeta = async ({ slug, json }) => {
|
|
|
40
108
|
return { pageId, chapterId };
|
|
41
109
|
};
|
|
42
110
|
var loadPageContent = async ({ chapterId, json }) => {
|
|
43
|
-
|
|
111
|
+
if (chapterId) {
|
|
112
|
+
json.viewer.pages.hits[0].chapter = STATE.chapters[chapterId] || json.viewer.pages.hits[0].chapter;
|
|
113
|
+
}
|
|
44
114
|
return json;
|
|
45
115
|
};
|
|
46
116
|
var searchBooks = async ({
|
|
@@ -83,14 +153,7 @@ var loadPreload = (json) => {
|
|
|
83
153
|
});
|
|
84
154
|
};
|
|
85
155
|
|
|
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
|
-
}
|
|
156
|
+
// ../../node_modules/.pnpm/fflate@0.8.2/node_modules/fflate/esm/browser.js
|
|
94
157
|
var u8 = Uint8Array;
|
|
95
158
|
var u16 = Uint16Array;
|
|
96
159
|
var i32 = Int32Array;
|
|
@@ -739,6 +802,9 @@ var loadZip = async (buf, bookId) => {
|
|
|
739
802
|
const unzipper = new Unzip();
|
|
740
803
|
unzipper.register(UnzipInflate);
|
|
741
804
|
unzipper.onfile = (file) => {
|
|
805
|
+
if (!file.name.includes(".")) {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
742
808
|
m.set(file.name, {
|
|
743
809
|
decoded: null,
|
|
744
810
|
zipEntry: {
|
|
@@ -762,17 +828,39 @@ var loadZip = async (buf, bookId) => {
|
|
|
762
828
|
}
|
|
763
829
|
});
|
|
764
830
|
};
|
|
765
|
-
|
|
831
|
+
const chunkSize = 32 * 1024 * 1024;
|
|
832
|
+
const nChunks = Math.max(1, Math.floor(buf.byteLength / chunkSize));
|
|
833
|
+
const sleep = new Promise((resolve) => setTimeout(resolve, 0));
|
|
834
|
+
for (let i = 0; i < nChunks - 1; ++i) {
|
|
835
|
+
unzipper.push(buf.slice(i * chunkSize, (i + 1) * chunkSize));
|
|
836
|
+
await sleep;
|
|
837
|
+
}
|
|
838
|
+
unzipper.push(buf.slice((nChunks - 1) * chunkSize, buf.byteLength), true);
|
|
766
839
|
BOOKS[bookId] = m;
|
|
767
840
|
return [...m.keys()];
|
|
768
841
|
};
|
|
769
842
|
|
|
770
|
-
// src/worker/
|
|
771
|
-
var CHECK_UPDATES =
|
|
772
|
-
var
|
|
773
|
-
|
|
843
|
+
// src/worker/offline-worker.ts
|
|
844
|
+
var CHECK_UPDATES = true;
|
|
845
|
+
var STORAGE = "cache";
|
|
846
|
+
var ZIP_BASE_URL = "https://ci.lls.fr/artefacts/content/development";
|
|
847
|
+
var LOADED_BOOKS = /* @__PURE__ */ new Map();
|
|
848
|
+
var fetchZipFile = async (zipUrl, { forceRefresh } = {}) => {
|
|
774
849
|
const fileName = zipUrl.split("/").slice(-1)[0];
|
|
775
850
|
const readArrayBuffer = async () => {
|
|
851
|
+
if (STORAGE === "native") {
|
|
852
|
+
const back = await sender.nativeReadFile({ fname: fileName });
|
|
853
|
+
return back;
|
|
854
|
+
}
|
|
855
|
+
if (STORAGE === "cache") {
|
|
856
|
+
const response = await caches.match(zipUrl);
|
|
857
|
+
if (!response) {
|
|
858
|
+
throw new Error(`missing zip ${zipUrl}`);
|
|
859
|
+
}
|
|
860
|
+
const buf = await response.arrayBuffer();
|
|
861
|
+
return buf;
|
|
862
|
+
}
|
|
863
|
+
const opfsRoot = await navigator.storage.getDirectory();
|
|
776
864
|
const handle = await opfsRoot.getFileHandle(fileName);
|
|
777
865
|
const accessHandle = await handle.createSyncAccessHandle();
|
|
778
866
|
const size = accessHandle.getSize();
|
|
@@ -782,6 +870,9 @@ var fetchZipFile = async (zipUrl) => {
|
|
|
782
870
|
return arrayBuffer;
|
|
783
871
|
};
|
|
784
872
|
try {
|
|
873
|
+
if (forceRefresh) {
|
|
874
|
+
throw new Error("forceRefresh");
|
|
875
|
+
}
|
|
785
876
|
const buf = await readArrayBuffer();
|
|
786
877
|
if (CHECK_UPDATES) {
|
|
787
878
|
const response = await fetch(zipUrl, { method: "HEAD" });
|
|
@@ -795,51 +886,118 @@ var fetchZipFile = async (zipUrl) => {
|
|
|
795
886
|
}
|
|
796
887
|
return buf;
|
|
797
888
|
} catch (e) {
|
|
798
|
-
|
|
799
|
-
|
|
889
|
+
if (e instanceof Error && e.message !== "forceRefresh" && !e.message.includes("refetching")) {
|
|
890
|
+
console.debug(`${STORAGE} zip not found, creating it`, e, zipUrl);
|
|
891
|
+
}
|
|
892
|
+
const response = await fetch(zipUrl);
|
|
893
|
+
const contentLength = +response.headers.get("Content-Length");
|
|
894
|
+
const reader = response.body.getReader();
|
|
895
|
+
let position = 0;
|
|
896
|
+
const arrayBuffer = new Uint8Array(contentLength);
|
|
897
|
+
let prevPosition = 0;
|
|
898
|
+
while (true) {
|
|
899
|
+
const { done, value: chunk } = await reader.read();
|
|
900
|
+
if (done) {
|
|
901
|
+
self.postMessage({ methodName: "opfsProgress", arg: { total: contentLength, bytes: contentLength } });
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
arrayBuffer.set(chunk, position);
|
|
905
|
+
position += chunk.length;
|
|
906
|
+
if (position - prevPosition > 1e6) {
|
|
907
|
+
prevPosition = position;
|
|
908
|
+
self.postMessage({ methodName: "opfsProgress", arg: { total: contentLength, bytes: position } });
|
|
909
|
+
}
|
|
910
|
+
}
|
|
800
911
|
if (arrayBuffer.byteLength < 1e4) {
|
|
801
|
-
const error = `likely ${zipUrl} was not fetched`;
|
|
912
|
+
const error = `likely ${zipUrl} was not fetched ${arrayBuffer.byteLength}`;
|
|
802
913
|
console.debug(error);
|
|
803
914
|
throw new Error(error);
|
|
804
915
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
916
|
+
if (STORAGE === "native") {
|
|
917
|
+
await sender.nativeWriteFile({ fname: fileName, buffer: arrayBuffer });
|
|
918
|
+
} else if (STORAGE === "opfs") {
|
|
919
|
+
const opfsRoot = await navigator.storage.getDirectory();
|
|
920
|
+
const handle = await opfsRoot.getFileHandle(fileName, { create: true });
|
|
921
|
+
const accessHandle = await handle.createSyncAccessHandle();
|
|
922
|
+
await accessHandle.truncate(0);
|
|
923
|
+
await accessHandle.write(arrayBuffer);
|
|
924
|
+
await accessHandle.close();
|
|
925
|
+
} else {
|
|
926
|
+
const cache = await openCache();
|
|
927
|
+
const response2 = new Response(arrayBuffer, {
|
|
928
|
+
headers: {
|
|
929
|
+
"Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
|
|
930
|
+
"Content-Type": "application/zip"
|
|
931
|
+
},
|
|
932
|
+
// @ts-ignore: poor ts typing
|
|
933
|
+
ok: true,
|
|
934
|
+
redirected: false,
|
|
935
|
+
status: 200,
|
|
936
|
+
type: "cors",
|
|
937
|
+
url: zipUrl
|
|
938
|
+
});
|
|
939
|
+
await cache.put(zipUrl, response2);
|
|
940
|
+
}
|
|
810
941
|
return await readArrayBuffer();
|
|
811
942
|
}
|
|
812
943
|
};
|
|
813
944
|
var slugToFileMeta2 = async ({ slug }) => {
|
|
814
|
-
console.debug("hereshit");
|
|
815
945
|
const json = await readZipEntry({ bookId: "preload", entryName: "preload/hashmap_slugToMetaPage.json", asJson: true });
|
|
816
|
-
console.debug("hereshit2");
|
|
817
946
|
return slugToFileMeta({ slug, json });
|
|
818
947
|
};
|
|
819
948
|
var PRELOAD_AS_BOOK_ID = "preload";
|
|
820
|
-
var
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
loadBook(json);
|
|
827
|
-
} catch (e) {
|
|
828
|
-
console.error("failed", e);
|
|
949
|
+
var isOldZip = (bookId) => typeof bookId === "number";
|
|
950
|
+
var loadBook2 = async ({ id: bookId, forceRefresh }) => {
|
|
951
|
+
if (LOADED_BOOKS.has(bookId)) {
|
|
952
|
+
await LOADED_BOOKS.get(bookId);
|
|
953
|
+
self.postMessage({ methodName: "opfsProgress", arg: { total: 1, bytes: 1 } });
|
|
954
|
+
return;
|
|
829
955
|
}
|
|
956
|
+
const p = new Promise(
|
|
957
|
+
tryCatch(async () => {
|
|
958
|
+
const buf = await fetchZipFile(`${ZIP_BASE_URL}/${bookId}.zip`, { forceRefresh });
|
|
959
|
+
try {
|
|
960
|
+
const entryNames = await loadZip(new Uint8Array(buf), bookId);
|
|
961
|
+
fetchWorker.postMessage({ methodName: "setKnownLinks", arg: { entryNames, bookId } });
|
|
962
|
+
if (isOldZip(bookId)) {
|
|
963
|
+
const json = await readZipEntry({
|
|
964
|
+
entryName: `preload/${bookId}.json`,
|
|
965
|
+
bookId: PRELOAD_AS_BOOK_ID,
|
|
966
|
+
asJson: true
|
|
967
|
+
});
|
|
968
|
+
loadBook(json);
|
|
969
|
+
}
|
|
970
|
+
return true;
|
|
971
|
+
} catch (e) {
|
|
972
|
+
console.error("failed", e);
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
})
|
|
976
|
+
);
|
|
977
|
+
LOADED_BOOKS.set(bookId, p);
|
|
978
|
+
await p;
|
|
830
979
|
};
|
|
831
980
|
var loadPageContent2 = async ({
|
|
832
|
-
bookId,
|
|
981
|
+
bookId: iBookId,
|
|
833
982
|
chapterId,
|
|
834
|
-
pageId
|
|
983
|
+
pageId,
|
|
984
|
+
entryId
|
|
835
985
|
}) => {
|
|
836
|
-
const
|
|
986
|
+
const bookId = entryId ? entryId.bookId : iBookId;
|
|
987
|
+
if (!bookId) {
|
|
988
|
+
throw new Error("missing bookId: expect entryId or bookId");
|
|
989
|
+
}
|
|
990
|
+
const json = await readZipEntry({
|
|
991
|
+
entryName: entryId ? entryId.entryName : `${chapterId}/${pageId}.json`,
|
|
992
|
+
bookId,
|
|
993
|
+
asJson: true
|
|
994
|
+
});
|
|
837
995
|
return await loadPageContent({ chapterId, json });
|
|
838
996
|
};
|
|
839
997
|
var fetchWorker;
|
|
840
998
|
var initChannel = (__, { ports }) => {
|
|
841
999
|
fetchWorker = ports[0];
|
|
842
|
-
const
|
|
1000
|
+
const { canHandle, handle } = makeSyncHandler(fetchWorker, {
|
|
843
1001
|
readZipEntry,
|
|
844
1002
|
loadBook: loadBook2,
|
|
845
1003
|
slugToFileMeta: slugToFileMeta2,
|
|
@@ -851,20 +1009,62 @@ var initChannel = (__, { ports }) => {
|
|
|
851
1009
|
self.postMessage({ methodName: "ready" });
|
|
852
1010
|
return;
|
|
853
1011
|
}
|
|
854
|
-
|
|
1012
|
+
if (canHandle(dataObj.data?.methodName || "")) {
|
|
1013
|
+
return handle(dataObj);
|
|
1014
|
+
}
|
|
1015
|
+
console.error("unknown methodName", dataObj.data?.methodName);
|
|
855
1016
|
};
|
|
856
1017
|
fetchWorker.onmessage = fn;
|
|
857
1018
|
};
|
|
858
|
-
var loadBooks = async () => {
|
|
859
|
-
|
|
860
|
-
|
|
1019
|
+
var loadBooks = async ({ primaryOnly = false } = {}) => {
|
|
1020
|
+
let entries = [];
|
|
1021
|
+
if (STORAGE === "native") {
|
|
1022
|
+
entries = await sender.nativeListZips();
|
|
1023
|
+
} else if (STORAGE === "opfs") {
|
|
1024
|
+
const opfsRoot = await navigator.storage.getDirectory();
|
|
1025
|
+
entries = await opfsRoot.values();
|
|
1026
|
+
} else {
|
|
1027
|
+
const cache = await openCache();
|
|
1028
|
+
const keys = await cache.keys();
|
|
1029
|
+
const zips = keys.filter((key2) => key2.url.endsWith(".zip"));
|
|
1030
|
+
entries = zips.map((zip) => ({ name: String(zip.url.split("/").pop()) }));
|
|
1031
|
+
}
|
|
861
1032
|
for await (const entry of entries) {
|
|
1033
|
+
if (/preload/.test(entry.name)) {
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
862
1036
|
if (/\d+\.zip$/.test(entry.name)) {
|
|
863
|
-
|
|
864
|
-
|
|
1037
|
+
if (!primaryOnly) {
|
|
1038
|
+
const bookId = entry.name.match(/\d+/)[0];
|
|
1039
|
+
await loadBook2({ id: Number.parseInt(bookId, 10) });
|
|
1040
|
+
}
|
|
1041
|
+
} else if (/zip$/.test(entry.name)) {
|
|
1042
|
+
const methodUri = entry.name.replace(".zip", "");
|
|
1043
|
+
await loadBook2({ id: methodUri });
|
|
1044
|
+
} else {
|
|
1045
|
+
console.info(`invalid book entry ${entry.name}`);
|
|
865
1046
|
}
|
|
866
1047
|
}
|
|
867
1048
|
};
|
|
1049
|
+
var configure = ({
|
|
1050
|
+
storage,
|
|
1051
|
+
checkUpdates,
|
|
1052
|
+
zipBaseUrl
|
|
1053
|
+
}) => {
|
|
1054
|
+
CHECK_UPDATES = true;
|
|
1055
|
+
STORAGE = "cache";
|
|
1056
|
+
ZIP_BASE_URL = "https://ci.lls.fr/artefacts/content/development";
|
|
1057
|
+
if (typeof storage !== "undefined") {
|
|
1058
|
+
STORAGE = storage;
|
|
1059
|
+
}
|
|
1060
|
+
if (typeof checkUpdates !== "undefined") {
|
|
1061
|
+
CHECK_UPDATES = !!checkUpdates;
|
|
1062
|
+
}
|
|
1063
|
+
if (typeof zipBaseUrl !== "undefined") {
|
|
1064
|
+
ZIP_BASE_URL = zipBaseUrl;
|
|
1065
|
+
}
|
|
1066
|
+
console.info("ow::configure", { STORAGE, CHECK_UPDATES, ZIP_BASE_URL });
|
|
1067
|
+
};
|
|
868
1068
|
var loadPreload2 = async () => {
|
|
869
1069
|
const zipUrl = "https://lls-ci-fr.s3.eu-west-3.amazonaws.com/artefacts/content/development/preload.zip";
|
|
870
1070
|
const buf = await fetchZipFile(zipUrl);
|
|
@@ -877,9 +1077,13 @@ var loadPreload2 = async () => {
|
|
|
877
1077
|
console.error("failed", e);
|
|
878
1078
|
}
|
|
879
1079
|
};
|
|
880
|
-
|
|
1080
|
+
var mainHandle = makeSyncHandler(self, {
|
|
881
1081
|
initChannel,
|
|
882
1082
|
loadPreload: loadPreload2,
|
|
883
1083
|
readZipEntry,
|
|
884
|
-
loadBooks
|
|
1084
|
+
loadBooks,
|
|
1085
|
+
loadBook: loadBook2,
|
|
1086
|
+
configure
|
|
885
1087
|
});
|
|
1088
|
+
var { handler, ...sender } = makeSyncWorker(self, ["nativeWriteFile", "nativeReadFile", "nativeListZips"]);
|
|
1089
|
+
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,19 @@
|
|
|
1
|
-
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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;
|
|
1
|
+
// src/utils/error.ts
|
|
2
|
+
var tryCatch = (fn) => {
|
|
3
|
+
return (resolve, reject) => {
|
|
4
|
+
try {
|
|
5
|
+
return fn().then(resolve);
|
|
6
|
+
} catch (e) {
|
|
7
|
+
return reject(e);
|
|
308
8
|
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
9
|
+
};
|
|
10
|
+
};
|
|
311
11
|
|
|
312
12
|
// src/utils/worker.ts
|
|
313
|
-
var makeSyncWorker = (
|
|
13
|
+
var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
|
|
314
14
|
const dic = {};
|
|
315
15
|
const cbks = {};
|
|
316
|
-
const
|
|
16
|
+
const fn = ({ data: { eventId, methodName, results, error, arg } = {} } = {}) => {
|
|
317
17
|
if (eventId && eventId in dic) {
|
|
318
18
|
if (error) {
|
|
319
19
|
dic[eventId].reject(error);
|
|
@@ -322,53 +22,93 @@ var makeSyncWorker = (offlineWorker, methodNames, dbg) => {
|
|
|
322
22
|
}
|
|
323
23
|
} else {
|
|
324
24
|
if (methodName && !methodName.startsWith("async") && !(methodName in cbks)) {
|
|
325
|
-
console.error(`syncWorker: ${dbg}`, eventId, methodName, error);
|
|
25
|
+
console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
|
|
326
26
|
}
|
|
327
27
|
}
|
|
328
28
|
methodName && cbks[methodName]?.(arg);
|
|
329
29
|
};
|
|
330
|
-
offlineWorker.onmessage = fn2;
|
|
331
30
|
const makeFn = (methodName) => {
|
|
332
31
|
return ({ ports, ...arg } = {}) => {
|
|
333
32
|
return new Promise((resolve, reject) => {
|
|
334
33
|
const eventId = `${methodName}:${Date.now()}`;
|
|
335
34
|
dic[eventId] = { resolve, reject };
|
|
336
|
-
|
|
35
|
+
offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
|
|
337
36
|
});
|
|
338
37
|
};
|
|
339
38
|
};
|
|
340
39
|
const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
|
|
341
|
-
on: (key,
|
|
342
|
-
cbks[key] =
|
|
40
|
+
on: (key, fn2) => {
|
|
41
|
+
cbks[key] = fn2;
|
|
343
42
|
}
|
|
344
43
|
});
|
|
345
|
-
return
|
|
44
|
+
return {
|
|
45
|
+
...obj,
|
|
46
|
+
handler: {
|
|
47
|
+
canHandle: (methodName) => {
|
|
48
|
+
return methodName in cbks || methodNames.includes(methodName);
|
|
49
|
+
},
|
|
50
|
+
handle: fn
|
|
51
|
+
}
|
|
52
|
+
};
|
|
346
53
|
};
|
|
347
54
|
var a = (a2) => a2;
|
|
348
55
|
var makeSyncHandler = a(
|
|
349
|
-
(sender, services) =>
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
56
|
+
(sender, services) => ({
|
|
57
|
+
canHandle: (methodName) => {
|
|
58
|
+
return methodName in services;
|
|
59
|
+
},
|
|
60
|
+
handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
|
|
61
|
+
try {
|
|
62
|
+
if (typeof methodName === "string" && methodName in services && services[methodName]) {
|
|
63
|
+
const results = await services[methodName](arg, { ports });
|
|
64
|
+
sender.postMessage({ eventId, methodName, results });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.debug("failed", e);
|
|
70
|
+
sender.postMessage({ eventId, methodName, error: e });
|
|
355
71
|
}
|
|
356
|
-
throw new Error(`invalid methodName ${String(methodName)}`);
|
|
357
|
-
} catch (e) {
|
|
358
|
-
console.debug("failed", e);
|
|
359
|
-
sender.postMessage({ eventId, methodName, error: e });
|
|
360
72
|
}
|
|
361
|
-
}
|
|
73
|
+
})
|
|
362
74
|
);
|
|
75
|
+
var combineHandlers = (...handlers) => {
|
|
76
|
+
return ({ data: { methodName, ...data } = {}, ports }) => {
|
|
77
|
+
for (const h of handlers) {
|
|
78
|
+
if (h.canHandle(String(methodName))) {
|
|
79
|
+
return h.handle({ data: { methodName, ...data }, ports });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
console.error("no handlers matched", methodName);
|
|
83
|
+
};
|
|
84
|
+
};
|
|
363
85
|
|
|
364
86
|
// src/hooks/useOfflineWorker.ts
|
|
365
|
-
|
|
366
|
-
var
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
["initChannel", "readZipEntry", "loadPreload", "loadBooks"],
|
|
87
|
+
import { useCallback, useEffect, useState } from "react";
|
|
88
|
+
var offlineWorker = new Worker("/src/worker/offline-worker.js", { type: "module" });
|
|
89
|
+
var { handler, ...offlineWorkerSync } = makeSyncWorker(
|
|
90
|
+
offlineWorker,
|
|
91
|
+
["initChannel", "readZipEntry", "configure", "loadPreload", "loadBooks", "loadBook"],
|
|
370
92
|
"app:offlineWorker"
|
|
371
93
|
);
|
|
94
|
+
var fetchWorkerPromise = new Promise(
|
|
95
|
+
tryCatch(async () => {
|
|
96
|
+
let fetchWorker = null;
|
|
97
|
+
await navigator.serviceWorker.register("/fetch-worker.js", { type: "module" }).then((reg) => {
|
|
98
|
+
return reg.update().catch((e) => {
|
|
99
|
+
console.debug(e, "likely we are offline");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
await navigator.serviceWorker.ready.then(async (reg) => {
|
|
103
|
+
console.debug("Service Worker registered:", reg.scope);
|
|
104
|
+
fetchWorker = reg.active;
|
|
105
|
+
}).catch((err) => console.error("Service Worker registration failed:", err));
|
|
106
|
+
if (!fetchWorker) {
|
|
107
|
+
console.debug("failed to get fetch worker");
|
|
108
|
+
}
|
|
109
|
+
return fetchWorker;
|
|
110
|
+
})
|
|
111
|
+
);
|
|
372
112
|
var setWorkersReady;
|
|
373
113
|
var workersReadyPromise = new Promise((resolve) => {
|
|
374
114
|
setWorkersReady = resolve;
|
|
@@ -377,54 +117,83 @@ var setBooksLoaded;
|
|
|
377
117
|
var setBooksLoadedPromise = new Promise((resolve) => {
|
|
378
118
|
setBooksLoaded = resolve;
|
|
379
119
|
});
|
|
380
|
-
var
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (
|
|
393
|
-
|
|
120
|
+
var installWorker = async ({
|
|
121
|
+
skipLoadBooks = false,
|
|
122
|
+
primaryOnly = false,
|
|
123
|
+
nativeReadFile,
|
|
124
|
+
nativeWriteFile,
|
|
125
|
+
nativeListZips,
|
|
126
|
+
storage,
|
|
127
|
+
zipBaseUrl,
|
|
128
|
+
checkUpdates
|
|
129
|
+
} = {}) => {
|
|
130
|
+
const fetchWorker = await fetchWorkerPromise;
|
|
131
|
+
const configureOptions = { storage, zipBaseUrl, checkUpdates };
|
|
132
|
+
if (nativeReadFile) {
|
|
133
|
+
const wwHandler = makeSyncHandler(offlineWorker, { nativeReadFile, nativeWriteFile, nativeListZips });
|
|
134
|
+
offlineWorker.onmessage = combineHandlers(handler, wwHandler);
|
|
135
|
+
if (storage !== "native") {
|
|
136
|
+
console.warn("nativeReadFile given, ignoring storage", storage);
|
|
137
|
+
}
|
|
138
|
+
offlineWorkerSync.configure({ ...configureOptions, storage: "native" });
|
|
139
|
+
} else {
|
|
140
|
+
offlineWorker.onmessage = handler.handle;
|
|
141
|
+
offlineWorkerSync.configure(configureOptions);
|
|
394
142
|
}
|
|
395
143
|
offlineWorkerSync.on("ready", setWorkersReady);
|
|
396
144
|
const channel = new MessageChannel();
|
|
397
145
|
offlineWorkerSync.initChannel({ ports: [channel.port1] });
|
|
398
146
|
fetchWorker.postMessage({ methodName: "initChannel" }, [channel.port2]);
|
|
399
|
-
|
|
400
|
-
|
|
147
|
+
fetchWorker.postMessage({ methodName: "configure", arg: { storage } });
|
|
148
|
+
offlineWorkerSync.on("opfsProgress", () => {
|
|
149
|
+
});
|
|
150
|
+
if (!primaryOnly) {
|
|
151
|
+
await offlineWorkerSync.loadPreload();
|
|
152
|
+
}
|
|
153
|
+
if (!skipLoadBooks) {
|
|
154
|
+
await offlineWorkerSync.loadBooks({ primaryOnly });
|
|
155
|
+
}
|
|
401
156
|
setBooksLoaded();
|
|
402
157
|
};
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
158
|
+
var useOfflineWorker_default = ({
|
|
159
|
+
primaryOnly = false,
|
|
160
|
+
nativeReadFile,
|
|
161
|
+
nativeWriteFile,
|
|
162
|
+
nativeListZips,
|
|
163
|
+
checkUpdates,
|
|
164
|
+
storage,
|
|
165
|
+
zipBaseUrl
|
|
166
|
+
} = {}) => {
|
|
167
|
+
const [ready, setReady] = useState(false);
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
const fn = async () => {
|
|
170
|
+
await installWorker({
|
|
171
|
+
primaryOnly,
|
|
172
|
+
nativeReadFile,
|
|
173
|
+
nativeWriteFile,
|
|
174
|
+
nativeListZips,
|
|
175
|
+
storage,
|
|
176
|
+
zipBaseUrl,
|
|
177
|
+
checkUpdates
|
|
178
|
+
});
|
|
408
179
|
await Promise.all([workersReadyPromise, setBooksLoadedPromise]);
|
|
180
|
+
fetch(window.location.href.replace(window.location.search, ""));
|
|
409
181
|
setReady(true);
|
|
410
182
|
};
|
|
411
|
-
|
|
412
|
-
}, []);
|
|
413
|
-
|
|
183
|
+
fn();
|
|
184
|
+
}, [primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, storage, zipBaseUrl, checkUpdates]);
|
|
185
|
+
const loadBook = useCallback(
|
|
186
|
+
async ({ id, forceRefresh }, cbk) => {
|
|
187
|
+
offlineWorkerSync.on("opfsProgress", cbk);
|
|
188
|
+
return await offlineWorkerSync.loadBook({ id, forceRefresh });
|
|
189
|
+
},
|
|
190
|
+
[]
|
|
191
|
+
);
|
|
192
|
+
return {
|
|
193
|
+
ready,
|
|
194
|
+
loadBook
|
|
195
|
+
};
|
|
414
196
|
};
|
|
415
197
|
export {
|
|
416
198
|
useOfflineWorker_default as useOfflineWorker
|
|
417
199
|
};
|
|
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.
|
|
3
|
+
"version": "24.22.0-34f32711",
|
|
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-34f32711"
|
|
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
|
}
|