@lls/offline 24.27.0 → 24.28.0-0854ed80
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 +200 -71
- package/lib/assets/offline-worker.js +141 -670
- package/lib/index.js +100 -35
- package/package.json +4 -3
package/lib/index.js
CHANGED
|
@@ -13,7 +13,7 @@ var tryCatch = (fn) => {
|
|
|
13
13
|
var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
|
|
14
14
|
const dic = {};
|
|
15
15
|
const cbks = {};
|
|
16
|
-
const fn = ({ data: { eventId, methodName, results, error, arg } = {} }
|
|
16
|
+
const fn = ({ source, data: { eventId, methodName, results, error, arg } = {} }) => {
|
|
17
17
|
if (eventId && eventId in dic) {
|
|
18
18
|
if (error) {
|
|
19
19
|
dic[eventId].reject(error);
|
|
@@ -21,22 +21,38 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
|
|
|
21
21
|
dic[eventId].resolve(results);
|
|
22
22
|
}
|
|
23
23
|
} else {
|
|
24
|
-
if (methodName && !
|
|
24
|
+
if (methodName && !(methodName in cbks)) {
|
|
25
25
|
console.error(`syncWorker: unknown methodName cbk ${dbg}`, eventId, methodName, error);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
methodName && cbks[methodName]?.(arg);
|
|
28
|
+
methodName && cbks[methodName]?.({ source, ...arg });
|
|
29
29
|
};
|
|
30
30
|
const makeFn = (methodName) => {
|
|
31
31
|
return ({ ports, ...arg } = {}) => {
|
|
32
32
|
return new Promise((resolve, reject) => {
|
|
33
33
|
const eventId = `${methodName}:${Date.now()}-${Math.random().toFixed(5)}`;
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
let resolved = false;
|
|
35
|
+
const timeout = setTimeout(() => {
|
|
36
|
+
console.warn("slow request for", methodName, arg, eventId);
|
|
37
|
+
}, 3e3);
|
|
38
|
+
const doResolve = (arg2) => {
|
|
39
|
+
if (!resolved) {
|
|
40
|
+
resolved = true;
|
|
41
|
+
clearTimeout(timeout);
|
|
42
|
+
resolve(arg2);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
dic[eventId] = { resolve: doResolve, reject };
|
|
46
|
+
if (ports) {
|
|
47
|
+
offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
|
|
48
|
+
} else {
|
|
49
|
+
offlineWorker2.postMessage({ eventId, methodName, arg });
|
|
50
|
+
}
|
|
36
51
|
});
|
|
37
52
|
};
|
|
38
53
|
};
|
|
39
|
-
const
|
|
54
|
+
const fns = Object.fromEntries(methodNames.map((name) => [name, makeFn(name)]));
|
|
55
|
+
const obj = Object.assign(fns, {
|
|
40
56
|
on: (key, fn2) => {
|
|
41
57
|
cbks[key] = fn2;
|
|
42
58
|
}
|
|
@@ -53,42 +69,45 @@ var makeSyncWorker = (offlineWorker2, methodNames, dbg) => {
|
|
|
53
69
|
};
|
|
54
70
|
var a = (a2) => a2;
|
|
55
71
|
var makeSyncHandler = a(
|
|
56
|
-
(
|
|
72
|
+
(services) => ({
|
|
57
73
|
canHandle: (methodName) => {
|
|
58
74
|
return methodName in services;
|
|
59
75
|
},
|
|
60
|
-
handle: async ({ data: { eventId, methodName, arg } = {}, ports }) => {
|
|
76
|
+
handle: async ({ data: { eventId, methodName, arg } = {}, source, ports, target }) => {
|
|
77
|
+
const emitter = source?.postMessage && source || target?.postMessage && target || self;
|
|
61
78
|
try {
|
|
62
79
|
if (typeof methodName === "string" && methodName in services && services[methodName]) {
|
|
63
80
|
const results = await services[methodName](arg, { ports });
|
|
64
|
-
|
|
81
|
+
emitter.postMessage({ eventId, methodName, results });
|
|
65
82
|
return;
|
|
66
83
|
}
|
|
67
84
|
throw new Error(`invalid methodName ${Object.keys(services)} |${String(methodName)}`);
|
|
68
85
|
} catch (e) {
|
|
69
86
|
console.debug("failed", e);
|
|
70
|
-
|
|
87
|
+
emitter.postMessage({ eventId, methodName, error: e });
|
|
71
88
|
}
|
|
72
89
|
}
|
|
73
90
|
})
|
|
74
91
|
);
|
|
75
92
|
var combineHandlers = (...handlers) => {
|
|
76
|
-
return (
|
|
93
|
+
return (message) => {
|
|
94
|
+
const methodName = String("methodName" in message.data ? message.data.methodName : "");
|
|
77
95
|
for (const h of handlers) {
|
|
78
|
-
if (h.canHandle(
|
|
79
|
-
return h.handle(
|
|
96
|
+
if (h.canHandle(methodName)) {
|
|
97
|
+
return h.handle(message);
|
|
80
98
|
}
|
|
81
99
|
}
|
|
82
100
|
console.error("no handlers matched", methodName);
|
|
83
101
|
};
|
|
84
102
|
};
|
|
103
|
+
var clearCache = async (mode) => caches.delete(`LLS_OFFLINE_${mode}`);
|
|
85
104
|
|
|
86
105
|
// src/hooks/useOfflineWorker.ts
|
|
87
106
|
import { useCallback, useEffect, useState } from "react";
|
|
88
107
|
var offlineWorker = new Worker("/src/worker/offline-worker.js", { type: "module" });
|
|
89
108
|
var { handler, ...offlineWorkerSync } = makeSyncWorker(
|
|
90
109
|
offlineWorker,
|
|
91
|
-
["initChannel", "readZipEntry", "configure", "loadPreload", "loadBooks", "loadBook"],
|
|
110
|
+
["initChannel", "readZipEntry", "configure", "loadPreload", "loadBooks", "loadBook", "reset"],
|
|
92
111
|
"app:offlineWorker"
|
|
93
112
|
);
|
|
94
113
|
var fetchWorkerPromise = new Promise(
|
|
@@ -112,14 +131,6 @@ var fetchWorkerPromise = new Promise(
|
|
|
112
131
|
return fetchWorker;
|
|
113
132
|
})
|
|
114
133
|
);
|
|
115
|
-
var setWorkersReady;
|
|
116
|
-
var workersReadyPromise = new Promise((resolve) => {
|
|
117
|
-
setWorkersReady = resolve;
|
|
118
|
-
});
|
|
119
|
-
var setBooksLoaded;
|
|
120
|
-
var setBooksLoadedPromise = new Promise((resolve) => {
|
|
121
|
-
setBooksLoaded = resolve;
|
|
122
|
-
});
|
|
123
134
|
var installWorker = async ({
|
|
124
135
|
skipLoadBooks = false,
|
|
125
136
|
primaryOnly = false,
|
|
@@ -134,7 +145,7 @@ var installWorker = async ({
|
|
|
134
145
|
const fetchWorker = await fetchWorkerPromise;
|
|
135
146
|
const configureOptions = { storage, zipBaseUrl, checkUpdates, confEnv };
|
|
136
147
|
if (nativeReadFile) {
|
|
137
|
-
const wwHandler = makeSyncHandler(
|
|
148
|
+
const wwHandler = makeSyncHandler({ nativeReadFile, nativeWriteFile, nativeListZips });
|
|
138
149
|
offlineWorker.onmessage = combineHandlers(handler, wwHandler);
|
|
139
150
|
if (storage !== "native") {
|
|
140
151
|
console.warn("nativeReadFile given, ignoring storage", storage);
|
|
@@ -142,13 +153,17 @@ var installWorker = async ({
|
|
|
142
153
|
offlineWorkerSync.configure({ ...configureOptions, storage: "native" });
|
|
143
154
|
} else {
|
|
144
155
|
offlineWorker.onmessage = handler.handle;
|
|
145
|
-
offlineWorkerSync.configure(configureOptions);
|
|
156
|
+
await offlineWorkerSync.configure(configureOptions);
|
|
146
157
|
}
|
|
147
|
-
offlineWorkerSync.on("ready", setWorkersReady);
|
|
148
158
|
const channel = new MessageChannel();
|
|
149
|
-
offlineWorkerSync.initChannel({ ports: [channel.port1] });
|
|
150
|
-
|
|
151
|
-
|
|
159
|
+
await offlineWorkerSync.initChannel({ ports: [channel.port1] });
|
|
160
|
+
const { handler: fetchWorkerHandler, ...fetchWorkerSync } = makeSyncWorker(fetchWorker, [
|
|
161
|
+
"initChannel",
|
|
162
|
+
"configure"
|
|
163
|
+
]);
|
|
164
|
+
navigator.serviceWorker.onmessage = combineHandlers(fetchWorkerHandler);
|
|
165
|
+
await fetchWorkerSync.initChannel({ ports: [channel.port2] });
|
|
166
|
+
await fetchWorkerSync.configure({ storage, confEnv });
|
|
152
167
|
offlineWorkerSync.on("opfsProgress", () => {
|
|
153
168
|
});
|
|
154
169
|
if (!primaryOnly) {
|
|
@@ -157,9 +172,9 @@ var installWorker = async ({
|
|
|
157
172
|
if (!skipLoadBooks) {
|
|
158
173
|
await offlineWorkerSync.loadBooks({ primaryOnly });
|
|
159
174
|
}
|
|
160
|
-
setBooksLoaded();
|
|
161
175
|
};
|
|
162
176
|
var useOfflineWorker_default = ({
|
|
177
|
+
skipLoadBooks,
|
|
163
178
|
primaryOnly = false,
|
|
164
179
|
nativeReadFile,
|
|
165
180
|
nativeWriteFile,
|
|
@@ -173,6 +188,7 @@ var useOfflineWorker_default = ({
|
|
|
173
188
|
useEffect(() => {
|
|
174
189
|
const fn = async () => {
|
|
175
190
|
await installWorker({
|
|
191
|
+
skipLoadBooks,
|
|
176
192
|
primaryOnly,
|
|
177
193
|
nativeReadFile,
|
|
178
194
|
nativeWriteFile,
|
|
@@ -182,24 +198,73 @@ var useOfflineWorker_default = ({
|
|
|
182
198
|
checkUpdates,
|
|
183
199
|
confEnv
|
|
184
200
|
});
|
|
185
|
-
await Promise.all([workersReadyPromise, setBooksLoadedPromise]);
|
|
186
201
|
fetch(window.location.href.replace(window.location.search, ""));
|
|
187
202
|
setReady(true);
|
|
188
203
|
};
|
|
189
204
|
fn();
|
|
190
|
-
}, [
|
|
205
|
+
}, [
|
|
206
|
+
skipLoadBooks,
|
|
207
|
+
primaryOnly,
|
|
208
|
+
nativeReadFile,
|
|
209
|
+
nativeWriteFile,
|
|
210
|
+
nativeListZips,
|
|
211
|
+
storage,
|
|
212
|
+
zipBaseUrl,
|
|
213
|
+
checkUpdates,
|
|
214
|
+
confEnv
|
|
215
|
+
]);
|
|
191
216
|
const loadBook = useCallback(
|
|
192
|
-
async ({
|
|
193
|
-
offlineWorkerSync.on("opfsProgress", cbk);
|
|
194
|
-
return await offlineWorkerSync.loadBook({ id, forceRefresh });
|
|
217
|
+
async ({ forceRefresh, ...idOrUri }, cbk) => {
|
|
218
|
+
cbk && offlineWorkerSync.on("opfsProgress", cbk);
|
|
219
|
+
return await offlineWorkerSync.loadBook({ id: "id" in idOrUri ? idOrUri.id : idOrUri.uri, forceRefresh });
|
|
195
220
|
},
|
|
196
221
|
[]
|
|
197
222
|
);
|
|
223
|
+
const reset = useCallback(async () => {
|
|
224
|
+
await offlineWorkerSync.reset();
|
|
225
|
+
await clearCache(confEnv || "development");
|
|
226
|
+
console.debug("reset: keys:", await caches.keys());
|
|
227
|
+
}, [confEnv]);
|
|
198
228
|
return {
|
|
199
229
|
ready,
|
|
200
|
-
loadBook
|
|
230
|
+
loadBook,
|
|
231
|
+
reset
|
|
201
232
|
};
|
|
202
233
|
};
|
|
234
|
+
|
|
235
|
+
// src/store/index.ts
|
|
236
|
+
import { persistReducer } from "redux-persist";
|
|
237
|
+
var persistEntitiesReducer = (entitiesReducer, CONF_ENV) => {
|
|
238
|
+
const getKey = (key) => `store:${CONF_ENV}:${key}`;
|
|
239
|
+
return persistReducer(
|
|
240
|
+
{
|
|
241
|
+
key: "entities",
|
|
242
|
+
storage: {
|
|
243
|
+
getItem: async (key) => localStorage.getItem(getKey(key)) || "{}",
|
|
244
|
+
setItem: async (key, item) => localStorage.setItem(getKey(key), item),
|
|
245
|
+
removeItem: async (key) => localStorage.removeItem(getKey(key))
|
|
246
|
+
},
|
|
247
|
+
whitelist: ["lexicalFlowers"],
|
|
248
|
+
version: 1,
|
|
249
|
+
debug: true,
|
|
250
|
+
throttle: 1e3
|
|
251
|
+
// lexicalFlowers is small, we may persist it fast
|
|
252
|
+
},
|
|
253
|
+
entitiesReducer
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// src/store/offlinePersist.tsx
|
|
258
|
+
import { persistStore } from "redux-persist";
|
|
259
|
+
import { PersistGate } from "redux-persist/integration/react";
|
|
260
|
+
import { useMemo } from "react";
|
|
261
|
+
import { jsx } from "react/jsx-runtime";
|
|
262
|
+
var OfflinePersist = ({ store, children }) => {
|
|
263
|
+
const persistor = useMemo(() => persistStore(store), [store]);
|
|
264
|
+
return /* @__PURE__ */ jsx(PersistGate, { loading: null, persistor, children });
|
|
265
|
+
};
|
|
203
266
|
export {
|
|
267
|
+
OfflinePersist,
|
|
268
|
+
persistEntitiesReducer,
|
|
204
269
|
useOfflineWorker_default as useOfflineWorker
|
|
205
270
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lls/offline",
|
|
3
|
-
"version": "24.
|
|
3
|
+
"version": "24.28.0-0854ed80",
|
|
4
4
|
"description": "offline",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"module": "./lib/index.js",
|
|
@@ -48,14 +48,15 @@
|
|
|
48
48
|
"nock": "14.0.5",
|
|
49
49
|
"react": "18.3.1",
|
|
50
50
|
"react-dom": "18.3.1",
|
|
51
|
+
"redux": "5.0.1",
|
|
51
52
|
"typescript": "5.8.3",
|
|
52
53
|
"vite": "7.0.7",
|
|
53
54
|
"vite-tsconfig-paths": "5.1.4",
|
|
54
55
|
"zod": "3.25.50",
|
|
55
|
-
"@lls/vitescripts": "24.
|
|
56
|
+
"@lls/vitescripts": "24.28.0-0854ed80"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
|
-
"
|
|
59
|
+
"redux-persist": "6.0.0"
|
|
59
60
|
},
|
|
60
61
|
"scripts": {
|
|
61
62
|
"start": "pnpm ladle serve",
|