@lls/offline 24.20.0-debug
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 +5 -0
- package/lib/assets/fetch.worker.js +323 -0
- package/lib/assets/offlineWorker.js +885 -0
- package/lib/index.js +430 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
// src/utils/worker.ts
|
|
2
|
+
var makeSyncWorker = (offlineWorker2, 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
|
+
offlineWorker2.onmessage = fn;
|
|
20
|
+
const makeFn = (methodName) => {
|
|
21
|
+
return ({ ports, ...arg } = {}) => {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const eventId = `${methodName}:${Date.now()}`;
|
|
24
|
+
dic[eventId] = { resolve, reject };
|
|
25
|
+
offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
|
|
30
|
+
on: (key, fn2) => {
|
|
31
|
+
cbks[key] = fn2;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return obj;
|
|
35
|
+
};
|
|
36
|
+
var a = (a2) => a2;
|
|
37
|
+
var makeSyncHandler = a(
|
|
38
|
+
(sender, services) => async ({ data: { eventId, methodName, arg } = {}, ports }) => {
|
|
39
|
+
try {
|
|
40
|
+
if (typeof methodName === "string" && methodName in services && services[methodName]) {
|
|
41
|
+
const results = await services[methodName](arg, { ports });
|
|
42
|
+
sender.postMessage({ eventId, methodName, results });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
throw new Error(`invalid methodName ${String(methodName)}`);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.debug("failed", e);
|
|
48
|
+
sender.postMessage({ eventId, methodName, error: e });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
var jsonToBuf = (json) => new TextEncoder().encode(JSON.stringify(json));
|
|
53
|
+
|
|
54
|
+
// src/worker/swFetchScreenCall.ts
|
|
55
|
+
var coreFetchBookDrawerScreen = async (_ev, { offlineWorkerSync: offlineWorkerSync2, variables }) => {
|
|
56
|
+
const slug = variables.slugs?.[0];
|
|
57
|
+
const uris = variables.uris;
|
|
58
|
+
const bookIds = variables.bookIds;
|
|
59
|
+
console.debug("beforestuff", slug, uris, bookIds);
|
|
60
|
+
if (!slug && !uris?.length && !bookIds?.length) {
|
|
61
|
+
console.debug("empty payload", slug, uris?.length, bookIds?.length);
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
const preloadBook = await offlineWorkerSync2.searchBooks({
|
|
65
|
+
slugs: slug ? [slug] : void 0,
|
|
66
|
+
uris,
|
|
67
|
+
ids: bookIds
|
|
68
|
+
}).then((books) => books?.[0]);
|
|
69
|
+
if (!preloadBook) {
|
|
70
|
+
console.info("fetchBookEffect::no book found", { slug, uris, bookIds });
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
console.debug("backbook???", preloadBook);
|
|
74
|
+
const back = {
|
|
75
|
+
data: {
|
|
76
|
+
viewer: {
|
|
77
|
+
books: {
|
|
78
|
+
hits: [preloadBook]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
console.debug("MAXIME/////", back);
|
|
84
|
+
return back;
|
|
85
|
+
};
|
|
86
|
+
var coreFetchPagesApi = async (ev, { offlineWorkerSync: offlineWorkerSync2, variables }) => {
|
|
87
|
+
const slug = variables.slugs?.[0];
|
|
88
|
+
let pageId = -1;
|
|
89
|
+
let chapterId = -1;
|
|
90
|
+
let bookId;
|
|
91
|
+
let bookSlug;
|
|
92
|
+
try {
|
|
93
|
+
if (!slug) {
|
|
94
|
+
throw new Error("invalid slug");
|
|
95
|
+
}
|
|
96
|
+
console.debug("slug");
|
|
97
|
+
const { pageId: aPageId, chapterId: aChapterId } = await offlineWorkerSync2.slugToFileMeta({ slug });
|
|
98
|
+
console.debug("searchBooks");
|
|
99
|
+
const book = await offlineWorkerSync2.searchBooks({ chapterIds: [aChapterId] }).then((books) => books?.[0]);
|
|
100
|
+
pageId = aPageId;
|
|
101
|
+
chapterId = aChapterId;
|
|
102
|
+
bookId = book?.id;
|
|
103
|
+
bookSlug = book?.slug;
|
|
104
|
+
if (!bookId) {
|
|
105
|
+
throw new Error(`book not found for slug:${slug}`);
|
|
106
|
+
}
|
|
107
|
+
console.debug("loadPageContent");
|
|
108
|
+
const pageResponse = await offlineWorkerSync2.loadPageContent({ bookId, chapterId, pageId });
|
|
109
|
+
if (Object.keys(pageResponse).length === 0) {
|
|
110
|
+
throw new Error("no offline page");
|
|
111
|
+
}
|
|
112
|
+
const rawPage = pageResponse?.viewer?.pages?.hits?.[0];
|
|
113
|
+
console.debug("before???", rawPage);
|
|
114
|
+
const page = {
|
|
115
|
+
...rawPage,
|
|
116
|
+
book: {
|
|
117
|
+
...rawPage.book,
|
|
118
|
+
slug: book?.slug,
|
|
119
|
+
urlLite: book?.urlLite
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const backPage = page;
|
|
123
|
+
const backData = {
|
|
124
|
+
data: {
|
|
125
|
+
viewer: {
|
|
126
|
+
pages: {
|
|
127
|
+
hits: [backPage]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
console.debug("back", backData);
|
|
133
|
+
return backData;
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.info("no offline page", e, JSON.stringify({ bookId, chapterId, pageId, e }).substring(0, 150));
|
|
136
|
+
try {
|
|
137
|
+
if (!slug) {
|
|
138
|
+
throw new Error("invalid slug");
|
|
139
|
+
}
|
|
140
|
+
return ev;
|
|
141
|
+
} catch (e2) {
|
|
142
|
+
console.info("failed offline and online", e2);
|
|
143
|
+
const backPage = {
|
|
144
|
+
book: {
|
|
145
|
+
id: bookId,
|
|
146
|
+
slug: bookSlug
|
|
147
|
+
},
|
|
148
|
+
slug,
|
|
149
|
+
id: pageId
|
|
150
|
+
};
|
|
151
|
+
const backData = {
|
|
152
|
+
data: {
|
|
153
|
+
viewer: {
|
|
154
|
+
pages: {
|
|
155
|
+
hits: [backPage]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
return backData;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var handleScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2 }) => {
|
|
165
|
+
try {
|
|
166
|
+
const variables = (
|
|
167
|
+
// @ts-ignore TODO: handle variables from body(stream) properly!!
|
|
168
|
+
ev.request.body?.variables || JSON.parse(new URLSearchParams(new URL(ev.request.url).search).get("variables") || "{}")
|
|
169
|
+
);
|
|
170
|
+
console.debug("variables are", variables, "vs", ev.request.body);
|
|
171
|
+
const handles = {
|
|
172
|
+
coreFetchBookDrawerScreen,
|
|
173
|
+
coreFetchPagesApi
|
|
174
|
+
};
|
|
175
|
+
const key = ev.request.url.match(Object.keys(handles).join("|"))?.[0];
|
|
176
|
+
if (!key) {
|
|
177
|
+
throw new Error("unknown ev");
|
|
178
|
+
}
|
|
179
|
+
const json = await handles[key](ev, { offlineWorkerSync: offlineWorkerSync2, variables });
|
|
180
|
+
return { status: 200, json };
|
|
181
|
+
} catch (e) {
|
|
182
|
+
console.error(e);
|
|
183
|
+
return { status: 500, json: JSON.stringify(e) };
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// src/worker/fetch.worker.ts
|
|
188
|
+
var fetchAsset = async (ev, { entryId, mimeType, offlineWorkerSync: offlineWorkerSync2 }) => {
|
|
189
|
+
try {
|
|
190
|
+
const buf = await offlineWorkerSync2.readZipEntry(entryId);
|
|
191
|
+
const url = ev.request.url;
|
|
192
|
+
return new Response(buf, {
|
|
193
|
+
headers: {
|
|
194
|
+
"Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
|
|
195
|
+
"Content-Type": mimeType
|
|
196
|
+
},
|
|
197
|
+
// @ts-ignore: poor ts typing
|
|
198
|
+
ok: true,
|
|
199
|
+
redirected: false,
|
|
200
|
+
status: 200,
|
|
201
|
+
type: "cors",
|
|
202
|
+
url
|
|
203
|
+
});
|
|
204
|
+
} catch (e) {
|
|
205
|
+
console.error("FAILED fetchAsset", e);
|
|
206
|
+
return new Response("FAILED fetchAsset", {
|
|
207
|
+
// @ts-ignore: poor ts typing
|
|
208
|
+
ok: false,
|
|
209
|
+
status: 500,
|
|
210
|
+
type: "cors",
|
|
211
|
+
url: ev.request.url
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var fetchScreenCall = async (ev, { offlineWorkerSync: offlineWorkerSync2 }) => {
|
|
216
|
+
try {
|
|
217
|
+
const { status, json } = await handleScreenCall(ev, { offlineWorkerSync: offlineWorkerSync2 });
|
|
218
|
+
return new Response(jsonToBuf(json), {
|
|
219
|
+
headers: {
|
|
220
|
+
"Cache-Control": "max-age=600, s-maxage=600, public, proxy-revalidate",
|
|
221
|
+
"Content-Type": "application/json"
|
|
222
|
+
},
|
|
223
|
+
// @ts-ignore: poor ts typing
|
|
224
|
+
ok: true,
|
|
225
|
+
redirected: false,
|
|
226
|
+
status,
|
|
227
|
+
type: "cors",
|
|
228
|
+
url: ev.request.url
|
|
229
|
+
});
|
|
230
|
+
} catch (e) {
|
|
231
|
+
console.error("FAILED screenCall", e);
|
|
232
|
+
return new Response("FAILED screenCall", {
|
|
233
|
+
// @ts-ignore: poor ts typing
|
|
234
|
+
ok: false,
|
|
235
|
+
status: 500,
|
|
236
|
+
type: "cors",
|
|
237
|
+
url: ev.request.url
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
var ACCEPTED_EXTENSIONS = {
|
|
242
|
+
".svg": "image/svg+xml",
|
|
243
|
+
".png": "image/png",
|
|
244
|
+
".jpg": "image/jpeg",
|
|
245
|
+
".mp4": "video/mpeg",
|
|
246
|
+
".mp3": "audio/mpeg",
|
|
247
|
+
".jpeg": "image/jpeg",
|
|
248
|
+
".gif": "image/gif",
|
|
249
|
+
".webm": "video/webm",
|
|
250
|
+
".m4a": "audio/mp4",
|
|
251
|
+
".webp": "image/webp"
|
|
252
|
+
};
|
|
253
|
+
var swSelf = self;
|
|
254
|
+
swSelf.addEventListener("fetch", (ev) => {
|
|
255
|
+
const url = ev.request.url;
|
|
256
|
+
const extension = url.match(/\.[^.]+$/)?.[0];
|
|
257
|
+
const mimeType = extension && extension in ACCEPTED_EXTENSIONS ? ACCEPTED_EXTENSIONS[extension] : "";
|
|
258
|
+
const screenCall = /coreFetchPagesApi|coreFetchBookDrawerScreen/.test(url);
|
|
259
|
+
if (!mimeType && !screenCall) {
|
|
260
|
+
if (!/\.(css|tsx?|jsx?|woff2|fr)/.test(url)) {
|
|
261
|
+
console.debug("no ext", url.match(/\.[^.]+$/)?.[0]);
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const entryId = knownLinks.get(url.replace(/https:\/\/.*?\//, ""));
|
|
266
|
+
if (!entryId && !screenCall) {
|
|
267
|
+
if (/lls|lelivrescolaire/.test(url)) {
|
|
268
|
+
console.info("no link for", url.replace(/https:\/\/.*?\//, ""));
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (screenCall) {
|
|
273
|
+
console.debug("screencall!");
|
|
274
|
+
return ev.respondWith(fetchScreenCall(ev, { offlineWorkerSync }));
|
|
275
|
+
}
|
|
276
|
+
return ev.respondWith(fetchAsset(ev, { entryId, mimeType, offlineWorkerSync }));
|
|
277
|
+
});
|
|
278
|
+
swSelf.addEventListener("install", (_event) => {
|
|
279
|
+
swSelf.skipWaiting();
|
|
280
|
+
});
|
|
281
|
+
swSelf.addEventListener("activate", (_event) => {
|
|
282
|
+
return swSelf.clients.claim();
|
|
283
|
+
});
|
|
284
|
+
var offlineWorker;
|
|
285
|
+
var offlineWorkerSync;
|
|
286
|
+
var initChannel = async (__, { ports }) => {
|
|
287
|
+
console.info(`FETCH WORKER innitChannel v${13}`);
|
|
288
|
+
offlineWorker = ports[0];
|
|
289
|
+
offlineWorkerSync = makeSyncWorker(
|
|
290
|
+
offlineWorker,
|
|
291
|
+
["readZipEntry", "loadBook", "slugToFileMeta", "loadPageContent", "searchBooks"],
|
|
292
|
+
"fetchworker"
|
|
293
|
+
);
|
|
294
|
+
offlineWorkerSync.on("setKnownLinks", setKnownLinks);
|
|
295
|
+
offlineWorker.postMessage({ methodName: "sendAppReady" });
|
|
296
|
+
};
|
|
297
|
+
var knownLinks = /* @__PURE__ */ new Map();
|
|
298
|
+
var setKnownLinks = ({ entryNames, bookId }) => {
|
|
299
|
+
entryNames.forEach((entryName) => {
|
|
300
|
+
const link = entryName.replace(/^\d+\/media\//, "");
|
|
301
|
+
knownLinks.set(link, { entryName, bookId });
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
swSelf.onmessage = async ({
|
|
305
|
+
data: { methodName, arg } = {},
|
|
306
|
+
ports
|
|
307
|
+
}) => {
|
|
308
|
+
const services = {
|
|
309
|
+
initChannel
|
|
310
|
+
};
|
|
311
|
+
try {
|
|
312
|
+
if (methodName === "initChannel") {
|
|
313
|
+
if (!ports.length) {
|
|
314
|
+
throw new Error("fetchworker::missing port!");
|
|
315
|
+
}
|
|
316
|
+
await services[methodName](arg, { ports });
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
throw new Error(`fetchworker:invalid methodName ${methodName}`);
|
|
320
|
+
} catch (e) {
|
|
321
|
+
console.error("failed", e);
|
|
322
|
+
}
|
|
323
|
+
};
|