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