@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/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 && !methodName.startsWith("async") && !(methodName in cbks)) {
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
- dic[eventId] = { resolve, reject };
35
- offlineWorker2.postMessage({ eventId, methodName, arg }, ports);
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 obj = Object.assign(Object.fromEntries(methodNames.map((name) => [name, makeFn(name)])), {
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
- (sender, services) => ({
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
- sender.postMessage({ eventId, methodName, results });
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
- sender.postMessage({ eventId, methodName, error: e });
87
+ emitter.postMessage({ eventId, methodName, error: e });
71
88
  }
72
89
  }
73
90
  })
74
91
  );
75
92
  var combineHandlers = (...handlers) => {
76
- return ({ data: { methodName, ...data } = {}, ports }) => {
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(String(methodName))) {
79
- return h.handle({ data: { methodName, ...data }, ports });
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(offlineWorker, { nativeReadFile, nativeWriteFile, nativeListZips });
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
- fetchWorker.postMessage({ methodName: "initChannel" }, [channel.port2]);
151
- fetchWorker.postMessage({ methodName: "configure", arg: { storage, confEnv } });
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
- }, [primaryOnly, nativeReadFile, nativeWriteFile, nativeListZips, storage, zipBaseUrl, checkUpdates, confEnv]);
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 ({ id, forceRefresh }, cbk) => {
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.27.0",
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.27.0"
56
+ "@lls/vitescripts": "24.28.0-0854ed80"
56
57
  },
57
58
  "dependencies": {
58
- "fflate": "0.8.2"
59
+ "redux-persist": "6.0.0"
59
60
  },
60
61
  "scripts": {
61
62
  "start": "pnpm ladle serve",