@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 ADDED
@@ -0,0 +1,5 @@
1
+ # offline
2
+
3
+ handles offline as webworker for android/ios and desktop
4
+
5
+ Likely used by core, cocore
@@ -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
+ };