@sweidos/eidos 1.0.34 → 1.2.0
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 +171 -89
- package/dist/action.js +197 -91
- package/dist/async-storage-adapter.js +15 -12
- package/dist/cli.js +102 -0
- package/dist/devtools.js +1009 -551
- package/dist/eidos-sw.js +280 -188
- package/dist/eidos.cjs +15 -0
- package/dist/idb.js +59 -56
- package/dist/index.d.ts +135 -18
- package/dist/index.js +46 -42
- package/dist/nextjs.js +1 -10
- package/dist/push.cjs +120 -0
- package/dist/push.d.ts +28 -0
- package/dist/push.js +113 -0
- package/dist/query.cjs +131 -0
- package/dist/query.js +121 -41
- package/dist/queue-storage.js +5 -4
- package/dist/react/Devtools.d.ts +1 -1
- package/dist/react/Provider.js +11 -7
- package/dist/react/hooks.js +48 -38
- package/dist/react-native.js +47 -53
- package/dist/replay.js +15 -0
- package/dist/resource.js +77 -79
- package/dist/runtime.js +39 -28
- package/dist/store-slices.js +43 -0
- package/dist/store.js +32 -49
- package/dist/stores.js +25 -22
- package/dist/sveltekit.js +22 -6
- package/dist/sw-bridge.js +64 -49
- package/dist/testing.cjs +165 -0
- package/dist/testing.js +140 -70
- package/dist/version.js +4 -3
- package/dist/vite.cjs +48 -0
- package/dist/vite.js +45 -29
- package/package.json +57 -28
- package/dist/action.js.map +0 -1
- package/dist/async-storage-adapter.js.map +0 -1
- package/dist/eidos.cjs.js +0 -14
- package/dist/eidos.cjs.js.map +0 -1
- package/dist/idb.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/query.cjs.js +0 -48
- package/dist/queue-storage.js.map +0 -1
- package/dist/react/Provider.js.map +0 -1
- package/dist/react/hooks.js.map +0 -1
- package/dist/resource.js.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/stores.js.map +0 -1
- package/dist/sw-bridge.js.map +0 -1
- package/dist/testing.cjs.js +0 -86
- package/dist/version.js.map +0 -1
- package/dist/vite.cjs.js +0 -31
package/dist/idb.js
CHANGED
|
@@ -1,80 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
let b = null;
|
|
4
|
-
function a() {
|
|
1
|
+
var f = "eidos", p = 1, o = "action-queue", b = null;
|
|
2
|
+
function c() {
|
|
5
3
|
return b ? Promise.resolve(b) : new Promise((s, n) => {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
if (!
|
|
10
|
-
const
|
|
11
|
-
|
|
4
|
+
const r = indexedDB.open(f, p);
|
|
5
|
+
r.onupgradeneeded = (e) => {
|
|
6
|
+
const t = e.target.result;
|
|
7
|
+
if (!t.objectStoreNames.contains(o)) {
|
|
8
|
+
const a = t.createObjectStore(o, { keyPath: "id" });
|
|
9
|
+
a.createIndex("status", "status", { unique: !1 }), a.createIndex("actionId", "actionId", { unique: !1 });
|
|
12
10
|
}
|
|
13
|
-
},
|
|
14
|
-
b =
|
|
15
|
-
},
|
|
11
|
+
}, r.onsuccess = () => {
|
|
12
|
+
b = r.result, s(r.result);
|
|
13
|
+
}, r.onerror = () => n(r.error);
|
|
16
14
|
});
|
|
17
15
|
}
|
|
18
|
-
async function
|
|
19
|
-
const n = await
|
|
20
|
-
return new Promise((
|
|
21
|
-
const
|
|
22
|
-
|
|
16
|
+
async function g(s) {
|
|
17
|
+
const n = await c();
|
|
18
|
+
return new Promise((r, e) => {
|
|
19
|
+
const t = n.transaction(o, "readwrite");
|
|
20
|
+
t.objectStore(o).add(s), t.oncomplete = () => r(), t.onerror = () => e(t.error);
|
|
23
21
|
});
|
|
24
22
|
}
|
|
25
23
|
async function y() {
|
|
26
|
-
const s = await
|
|
27
|
-
return new Promise((n,
|
|
24
|
+
const s = await c();
|
|
25
|
+
return new Promise((n, r) => {
|
|
28
26
|
const e = s.transaction(o, "readonly").objectStore(o).getAll();
|
|
29
|
-
e.onsuccess = () => n(e.result), e.onerror = () =>
|
|
27
|
+
e.onsuccess = () => n(e.result), e.onerror = () => r(e.error);
|
|
30
28
|
});
|
|
31
29
|
}
|
|
32
30
|
async function P(s, n) {
|
|
33
|
-
const
|
|
34
|
-
return new Promise((
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
const r = await c();
|
|
32
|
+
return new Promise((e, t) => {
|
|
33
|
+
const a = r.transaction(o, "readwrite"), u = a.objectStore(o), i = u.get(s);
|
|
34
|
+
i.onsuccess = () => {
|
|
35
|
+
i.result && u.put({
|
|
36
|
+
...i.result,
|
|
37
|
+
...n
|
|
38
|
+
});
|
|
39
|
+
}, a.oncomplete = () => e(), a.onerror = () => t(a.error);
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
42
|
async function S(s) {
|
|
42
|
-
const n = await
|
|
43
|
-
return new Promise((
|
|
44
|
-
const
|
|
45
|
-
|
|
43
|
+
const n = await c();
|
|
44
|
+
return new Promise((r, e) => {
|
|
45
|
+
const t = n.transaction(o, "readwrite");
|
|
46
|
+
t.objectStore(o).delete(s), t.oncomplete = () => r(), t.onerror = () => e(t.error);
|
|
46
47
|
});
|
|
47
48
|
}
|
|
48
|
-
async function
|
|
49
|
-
const s = await
|
|
50
|
-
function n(
|
|
51
|
-
return new Promise((
|
|
52
|
-
const
|
|
53
|
-
d.onsuccess = (
|
|
54
|
-
const l =
|
|
55
|
-
l ? (m.push(l.value), l.continue()) :
|
|
56
|
-
}, d.onerror = () =>
|
|
49
|
+
async function x() {
|
|
50
|
+
const s = await c();
|
|
51
|
+
function n(t) {
|
|
52
|
+
return new Promise((a, u) => {
|
|
53
|
+
const i = s.transaction(o, "readonly").objectStore(o).index("status"), m = [], d = i.openCursor(IDBKeyRange.only(t));
|
|
54
|
+
d.onsuccess = (w) => {
|
|
55
|
+
const l = w.target.result;
|
|
56
|
+
l ? (m.push(l.value), l.continue()) : a(m);
|
|
57
|
+
}, d.onerror = () => u(d.error);
|
|
57
58
|
});
|
|
58
59
|
}
|
|
59
|
-
const [
|
|
60
|
-
|
|
61
|
-
n("failed")
|
|
62
|
-
]);
|
|
63
|
-
return [...t, ...r];
|
|
60
|
+
const [r, e] = await Promise.all([n("pending"), n("failed")]);
|
|
61
|
+
return [...r, ...e];
|
|
64
62
|
}
|
|
65
|
-
async function
|
|
66
|
-
const s = await
|
|
67
|
-
return new Promise((n,
|
|
68
|
-
const
|
|
69
|
-
|
|
63
|
+
async function v() {
|
|
64
|
+
const s = await c();
|
|
65
|
+
return new Promise((n, r) => {
|
|
66
|
+
const e = s.transaction(o, "readwrite");
|
|
67
|
+
e.objectStore(o).clear(), e.oncomplete = () => n(), e.onerror = () => r(e.error);
|
|
70
68
|
});
|
|
71
69
|
}
|
|
70
|
+
var j = {
|
|
71
|
+
add: g,
|
|
72
|
+
getAll: y,
|
|
73
|
+
getPending: x,
|
|
74
|
+
update: P,
|
|
75
|
+
remove: S,
|
|
76
|
+
clear: v
|
|
77
|
+
};
|
|
72
78
|
export {
|
|
73
|
-
x as idbAddToQueue,
|
|
74
|
-
I as idbClearQueue,
|
|
75
|
-
g as idbGetPendingItems,
|
|
76
79
|
y as idbGetQueue,
|
|
77
|
-
|
|
78
|
-
P as idbUpdateQueueItem
|
|
80
|
+
j as idbQueueStorage
|
|
79
81
|
};
|
|
80
|
-
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=idb.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JSX
|
|
1
|
+
import { JSX } from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
export declare function action<TArgs extends any[], TReturn>(fn: ActionFn<TArgs, TReturn>, config: ActionConfig): ActionHandle<TArgs, TReturn>;
|
|
@@ -13,6 +13,13 @@ export declare interface ActionConfig {
|
|
|
13
13
|
maxRetries?: number;
|
|
14
14
|
/** Human-readable name for the action (used in devtools). */
|
|
15
15
|
name?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Prefixes the registered action id (`namespace::name`). Use to avoid
|
|
18
|
+
* collisions when two actions share a name (e.g. across micro-frontends,
|
|
19
|
+
* or two `createOrder` actions in different modules) — without a
|
|
20
|
+
* namespace, the second registration silently overwrites the first.
|
|
21
|
+
*/
|
|
22
|
+
namespace?: string;
|
|
16
23
|
/**
|
|
17
24
|
* Replay order when multiple queued actions are pending.
|
|
18
25
|
* `'high'` items replay before `'normal'`, which replay before `'low'`.
|
|
@@ -21,9 +28,11 @@ export declare interface ActionConfig {
|
|
|
21
28
|
*/
|
|
22
29
|
priority?: 'high' | 'normal' | 'low';
|
|
23
30
|
/**
|
|
24
|
-
* Called immediately before the async function executes, with the same args
|
|
25
|
-
* Use to apply an optimistic UI update (add
|
|
26
|
-
*
|
|
31
|
+
* Called immediately before the async function executes, with the same args
|
|
32
|
+
* plus a trailing `ActionContext`. Use to apply an optimistic UI update (add
|
|
33
|
+
* item, mark as pending, etc.) and to capture `idempotencyKey` for later
|
|
34
|
+
* `handle.cancel(idempotencyKey)` calls. Called on every invocation —
|
|
35
|
+
* online, offline, and during queue replay.
|
|
27
36
|
*/
|
|
28
37
|
onOptimistic?: (...args: any[]) => void;
|
|
29
38
|
/**
|
|
@@ -46,7 +55,25 @@ export declare interface ActionConfig {
|
|
|
46
55
|
* The `error` argument is whatever `fn` threw — typically a `Response` object
|
|
47
56
|
* or a custom error with a `.status` property.
|
|
48
57
|
*/
|
|
58
|
+
/**
|
|
59
|
+
* @deprecated Use `conflict` instead. If both are set, `conflict` wins.
|
|
60
|
+
* Return `'retry'` to keep the item in the queue and retry per normal
|
|
61
|
+
* backoff, or `'skip'` to silently remove the item.
|
|
62
|
+
*/
|
|
49
63
|
onConflict?: (error: unknown, args: any[]) => 'retry' | 'skip';
|
|
64
|
+
/**
|
|
65
|
+
* Declarative conflict-resolution strategy used during queue replay when
|
|
66
|
+
* the server responds with a 4xx status (conflict, gone, unprocessable,
|
|
67
|
+
* etc.). Replaces `onConflict` for new code — see `ConflictConfig`.
|
|
68
|
+
*/
|
|
69
|
+
conflict?: ConflictConfig;
|
|
70
|
+
/**
|
|
71
|
+
* When `true`, each invocation gets an `AbortController` whose `signal` is
|
|
72
|
+
* passed via `ActionContext.signal`. Forward it to `fetch`/etc. so
|
|
73
|
+
* `handle.cancel(idempotencyKey)` can abort an in-flight call, or remove a
|
|
74
|
+
* not-yet-replayed queued item.
|
|
75
|
+
*/
|
|
76
|
+
cancellable?: boolean;
|
|
50
77
|
}
|
|
51
78
|
|
|
52
79
|
declare type ActionFn<TArgs extends any[], TReturn> = (...args: TArgs) => Promise<TReturn>;
|
|
@@ -55,13 +82,28 @@ export declare interface ActionHandle<TArgs extends any[], TReturn> {
|
|
|
55
82
|
(...args: TArgs): Promise<TReturn | QueuedResult>;
|
|
56
83
|
readonly id: string;
|
|
57
84
|
readonly config: ActionConfig;
|
|
85
|
+
/**
|
|
86
|
+
* Cancel an invocation by its `idempotencyKey` (from `ActionContext` /
|
|
87
|
+
* `onOptimistic`). Aborts the in-flight call if `cancellable: true` and
|
|
88
|
+
* still running, otherwise removes a not-yet-replayed queued item.
|
|
89
|
+
* Returns `true` if something was cancelled/removed.
|
|
90
|
+
*/
|
|
91
|
+
cancel: (idempotencyKey: string) => Promise<boolean>;
|
|
58
92
|
}
|
|
59
93
|
|
|
60
94
|
export declare interface ActionQueueItem {
|
|
95
|
+
/** Shape version this item was persisted with. Items from before v2 are migrated on load. */
|
|
96
|
+
schemaVersion: number;
|
|
61
97
|
id: string;
|
|
62
98
|
/** ID of the registered action (maps to the function in the registry). */
|
|
63
99
|
actionId: string;
|
|
64
100
|
actionName: string;
|
|
101
|
+
/**
|
|
102
|
+
* Stable per-invocation key, generated once and reused across every retry/replay
|
|
103
|
+
* of this item. Pass to your server as an idempotency key so retries that reach
|
|
104
|
+
* the server after a dropped response don't double-execute.
|
|
105
|
+
*/
|
|
106
|
+
idempotencyKey: string;
|
|
65
107
|
args: unknown[];
|
|
66
108
|
queuedAt: number;
|
|
67
109
|
retryCount: number;
|
|
@@ -105,6 +147,46 @@ export declare type CacheStrategy = 'cache-first' | 'stale-while-revalidate' | '
|
|
|
105
147
|
/** Remove all items from the action queue (storage + in-memory store). */
|
|
106
148
|
export declare function clearQueue(): Promise<void>;
|
|
107
149
|
|
|
150
|
+
declare interface ConflictConfig {
|
|
151
|
+
/**
|
|
152
|
+
* - `'serverWins'`: drop the queued item, keeping the server's state.
|
|
153
|
+
* - `'clientWins'`: keep retrying — the client's write should eventually
|
|
154
|
+
* be accepted (e.g. once the server-side conflict is cleared).
|
|
155
|
+
* - `'lastWriteWins'`: same as `'clientWins'` for now — requires a
|
|
156
|
+
* server-supplied timestamp contract to compare against `queuedAt`
|
|
157
|
+
* (see Phase 3 of the roadmap). Treated as `'clientWins'` until then.
|
|
158
|
+
* - `'merge'` / `'custom'`: call `resolve` to decide.
|
|
159
|
+
*/
|
|
160
|
+
strategy: 'serverWins' | 'clientWins' | 'lastWriteWins' | 'merge' | 'custom';
|
|
161
|
+
/** Required for `'merge'` and `'custom'`. */
|
|
162
|
+
resolve?: (ctx: ConflictContext) => ConflictResolution;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Passed to `ConflictConfig.resolve` (for `'merge'`/`'custom'` strategies)
|
|
167
|
+
* when a queued action's replay receives a 4xx response.
|
|
168
|
+
*/
|
|
169
|
+
declare interface ConflictContext {
|
|
170
|
+
/** Whatever `fn` threw — typically a `Response` or an error with `.status`. */
|
|
171
|
+
error: unknown;
|
|
172
|
+
/** The original arguments the action was queued with. */
|
|
173
|
+
args: any[];
|
|
174
|
+
/** Number of replay attempts so far (0 on first replay). */
|
|
175
|
+
attempt: number;
|
|
176
|
+
idempotencyKey: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Outcome of `ConflictConfig.resolve`:
|
|
181
|
+
* - `'retry'`: keep the item queued, retry per normal backoff.
|
|
182
|
+
* - `'skip'`: silently remove the item (no `onRollback`).
|
|
183
|
+
* - `{ resolved: args }`: replace the queued args and retry immediately
|
|
184
|
+
* on the next replay pass.
|
|
185
|
+
*/
|
|
186
|
+
declare type ConflictResolution = 'retry' | 'skip' | {
|
|
187
|
+
resolved: any[];
|
|
188
|
+
};
|
|
189
|
+
|
|
108
190
|
/**
|
|
109
191
|
* Live state for a single queue item by ID. Returns `undefined` once the item
|
|
110
192
|
* is removed from the queue (after a successful replay or `clearQueue()`).
|
|
@@ -131,7 +213,7 @@ export declare interface EidosConfig {
|
|
|
131
213
|
* <App />
|
|
132
214
|
* </EidosProvider>
|
|
133
215
|
*/
|
|
134
|
-
export declare function EidosProvider({ children, swPath, autoReplay }: EidosProviderProps):
|
|
216
|
+
export declare function EidosProvider({ children, swPath, autoReplay }: EidosProviderProps): JSX.Element;
|
|
135
217
|
|
|
136
218
|
declare interface EidosProviderProps extends EidosConfig {
|
|
137
219
|
children: ReactNode;
|
|
@@ -185,20 +267,9 @@ export declare const eidosStatus: EidosReadable<{
|
|
|
185
267
|
swError: string | undefined;
|
|
186
268
|
}>;
|
|
187
269
|
|
|
188
|
-
export declare interface EidosStore extends EidosState {
|
|
270
|
+
export declare interface EidosStore extends EidosState, ResourceActions, QueueActions {
|
|
189
271
|
setOnline: (online: boolean) => void;
|
|
190
272
|
setSwStatus: (status: EidosState['swStatus'], error?: string) => void;
|
|
191
|
-
registerResource: (url: string, entry: ResourceEntry) => void;
|
|
192
|
-
updateResource: (url: string, update: Partial<ResourceEntry>) => void;
|
|
193
|
-
unregisterResource: (url: string) => void;
|
|
194
|
-
addQueueItem: (item: ActionQueueItem) => void;
|
|
195
|
-
updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void;
|
|
196
|
-
batchUpdateQueueItems: (updates: Array<{
|
|
197
|
-
id: string;
|
|
198
|
-
update: Partial<ActionQueueItem>;
|
|
199
|
-
}>) => void;
|
|
200
|
-
removeQueueItem: (id: string) => void;
|
|
201
|
-
hydrateQueue: (items: ActionQueueItem[]) => void;
|
|
202
273
|
}
|
|
203
274
|
|
|
204
275
|
/** Full Eidos state snapshot. Prefer the narrower stores below. */
|
|
@@ -220,14 +291,32 @@ export declare function _getQueueStorage(): QueueStorage | null;
|
|
|
220
291
|
|
|
221
292
|
declare function _getState(): EidosStore;
|
|
222
293
|
|
|
294
|
+
export declare function getSwRegistration(): ServiceWorkerRegistration | null;
|
|
295
|
+
|
|
223
296
|
export declare function initEidos(config?: EidosConfig): Promise<void>;
|
|
224
297
|
|
|
225
298
|
export declare function isBgSyncSupported(): boolean;
|
|
226
299
|
|
|
227
300
|
declare type Listener = () => void;
|
|
228
301
|
|
|
302
|
+
declare interface PushHandlers {
|
|
303
|
+
onNotificationClick?: (data: unknown) => void;
|
|
304
|
+
onSubscriptionExpired?: (sub: PushSubscriptionJSON) => void;
|
|
305
|
+
}
|
|
306
|
+
|
|
229
307
|
declare type QueryInvalidator = (queryKey: [string, string]) => void;
|
|
230
308
|
|
|
309
|
+
declare interface QueueActions {
|
|
310
|
+
addQueueItem: (item: ActionQueueItem) => void;
|
|
311
|
+
updateQueueItem: (id: string, update: Partial<ActionQueueItem>) => void;
|
|
312
|
+
batchUpdateQueueItems: (updates: Array<{
|
|
313
|
+
id: string;
|
|
314
|
+
update: Partial<ActionQueueItem>;
|
|
315
|
+
}>) => void;
|
|
316
|
+
removeQueueItem: (id: string) => void;
|
|
317
|
+
hydrateQueue: (items: ActionQueueItem[]) => void;
|
|
318
|
+
}
|
|
319
|
+
|
|
231
320
|
export declare interface QueuedResult {
|
|
232
321
|
readonly queued: true;
|
|
233
322
|
readonly id: string;
|
|
@@ -243,6 +332,8 @@ export declare interface QueueStorage {
|
|
|
243
332
|
clear(): Promise<void>;
|
|
244
333
|
}
|
|
245
334
|
|
|
335
|
+
export declare function registerPushCallbacks(handlers: PushHandlers): void;
|
|
336
|
+
|
|
246
337
|
export declare function replayQueue(): Promise<ReplayResult>;
|
|
247
338
|
|
|
248
339
|
/** Summary returned by replayQueue(). */
|
|
@@ -259,12 +350,20 @@ export declare interface ReplayResult {
|
|
|
259
350
|
skipped: number;
|
|
260
351
|
/** Items that received a 4xx response and were dropped via `onConflict: () => 'skip'`. */
|
|
261
352
|
conflicted: number;
|
|
353
|
+
/** Items removed via `handle.cancel(idempotencyKey)` before/during replay. */
|
|
354
|
+
cancelled: number;
|
|
262
355
|
}
|
|
263
356
|
|
|
264
357
|
export declare function _resetEidos(): void;
|
|
265
358
|
|
|
266
359
|
export declare function resource<T = unknown>(url: string, config: ResourceConfig): ResourceHandle<T>;
|
|
267
360
|
|
|
361
|
+
declare interface ResourceActions {
|
|
362
|
+
registerResource: (url: string, entry: ResourceEntry) => void;
|
|
363
|
+
updateResource: (url: string, update: Partial<ResourceEntry>) => void;
|
|
364
|
+
unregisterResource: (url: string) => void;
|
|
365
|
+
}
|
|
366
|
+
|
|
268
367
|
export declare interface ResourceConfig {
|
|
269
368
|
/** Make this resource available when the device is offline. */
|
|
270
369
|
offline: boolean;
|
|
@@ -305,6 +404,8 @@ export declare interface ResourceHandle<T = unknown> {
|
|
|
305
404
|
unregister: () => void;
|
|
306
405
|
}
|
|
307
406
|
|
|
407
|
+
export declare function sendToWorker(message: Record<string, unknown>): void;
|
|
408
|
+
|
|
308
409
|
export declare function setOfflineSimulation(enabled: boolean): void;
|
|
309
410
|
|
|
310
411
|
/* Excluded from this release type: setQueryInvalidator */
|
|
@@ -314,6 +415,22 @@ export declare function setQueueStorage(s: QueueStorage): void;
|
|
|
314
415
|
|
|
315
416
|
declare function _subscribe(listener: Listener): () => void;
|
|
316
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Subscribe to online/offline transitions and trigger replayQueue() on
|
|
420
|
+
* reconnect, plus replay any pending items left over from a previous session.
|
|
421
|
+
*
|
|
422
|
+
* Shared by the web (runtime.ts) and React Native (runtime-rn.ts) init paths.
|
|
423
|
+
*
|
|
424
|
+
* WHY subscribe to the store instead of window.addEventListener('online'):
|
|
425
|
+
* setOfflineSimulation() updates the store directly but never fires a real
|
|
426
|
+
* browser `online` event. Watching the store catches both:
|
|
427
|
+
* • Real network reconnects (sw-bridge updates store on window.online)
|
|
428
|
+
* • Simulation toggled off (setOfflineSimulation(false) → store.setOnline(true))
|
|
429
|
+
*
|
|
430
|
+
* Returns an unsubscribe function.
|
|
431
|
+
*/
|
|
432
|
+
export declare function subscribeReplayOnReconnect(): () => void;
|
|
433
|
+
|
|
317
434
|
/** Full Eidos store — prefer the narrower hooks below for performance. */
|
|
318
435
|
export declare function useEidos(): EidosStore;
|
|
319
436
|
|
|
@@ -372,7 +489,7 @@ export declare const useEidosStore: {
|
|
|
372
489
|
setState: (partial: Partial<EidosStore> | ((s: EidosStore) => Partial<EidosStore>)) => void;
|
|
373
490
|
};
|
|
374
491
|
|
|
375
|
-
export declare const VERSION = "1.0
|
|
492
|
+
export declare const VERSION = "1.2.0";
|
|
376
493
|
|
|
377
494
|
/**
|
|
378
495
|
* Bulk-prefetch an array of resource handles concurrently, warming the cache
|
package/dist/index.js
CHANGED
|
@@ -1,44 +1,48 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { _getQueueStorage as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
1
|
+
import { useEidosStore as o } from "./store.js";
|
|
2
|
+
import { getSwRegistration as s, isBgSyncSupported as i, registerPushCallbacks as t, sendToWorker as u, setOfflineSimulation as m } from "./sw-bridge.js";
|
|
3
|
+
import { resource as a, setQueryInvalidator as p, warmCache as n } from "./resource.js";
|
|
4
|
+
import { _getQueueStorage as S, setQueueStorage as f } from "./queue-storage.js";
|
|
5
|
+
import { action as Q, clearQueue as g, replayQueue as l } from "./action.js";
|
|
6
|
+
import { subscribeReplayOnReconnect as y } from "./replay.js";
|
|
7
|
+
import { _resetEidos as b, initEidos as A } from "./runtime.js";
|
|
8
|
+
import { AsyncStorageQueueStorage as k } from "./async-storage-adapter.js";
|
|
9
|
+
import { EidosProvider as w } from "./react/Provider.js";
|
|
10
|
+
import { useEidos as I, useEidosAction as P, useEidosOnDrain as _, useEidosQueue as x, useEidosQueueStats as B, useEidosResource as D, useEidosResources as N, useEidosStatus as T } from "./react/hooks.js";
|
|
11
|
+
import { VERSION as W } from "./version.js";
|
|
12
|
+
import { eidosAction as q, eidosQueue as z, eidosQueueStats as F, eidosResource as G, eidosStatus as H, eidosStore as J } from "./stores.js";
|
|
12
13
|
export {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
_ as
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
14
|
+
k as AsyncStorageQueueStorage,
|
|
15
|
+
w as EidosProvider,
|
|
16
|
+
W as VERSION,
|
|
17
|
+
S as _getQueueStorage,
|
|
18
|
+
b as _resetEidos,
|
|
19
|
+
Q as action,
|
|
20
|
+
g as clearQueue,
|
|
21
|
+
q as eidosAction,
|
|
22
|
+
z as eidosQueue,
|
|
23
|
+
F as eidosQueueStats,
|
|
24
|
+
G as eidosResource,
|
|
25
|
+
H as eidosStatus,
|
|
26
|
+
J as eidosStore,
|
|
27
|
+
s as getSwRegistration,
|
|
28
|
+
A as initEidos,
|
|
29
|
+
i as isBgSyncSupported,
|
|
30
|
+
t as registerPushCallbacks,
|
|
31
|
+
l as replayQueue,
|
|
32
|
+
a as resource,
|
|
33
|
+
u as sendToWorker,
|
|
34
|
+
m as setOfflineSimulation,
|
|
35
|
+
p as setQueryInvalidator,
|
|
36
|
+
f as setQueueStorage,
|
|
37
|
+
y as subscribeReplayOnReconnect,
|
|
38
|
+
I as useEidos,
|
|
39
|
+
P as useEidosAction,
|
|
40
|
+
_ as useEidosOnDrain,
|
|
41
|
+
x as useEidosQueue,
|
|
42
|
+
B as useEidosQueueStats,
|
|
43
|
+
D as useEidosResource,
|
|
44
|
+
N as useEidosResources,
|
|
45
|
+
T as useEidosStatus,
|
|
46
|
+
o as useEidosStore,
|
|
47
|
+
n as warmCache
|
|
43
48
|
};
|
|
44
|
-
//# sourceMappingURL=index.js.map
|
package/dist/nextjs.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { EidosProvider, useEidos, useEidosAction, useEidosOnDrain, useEidosQueue, useEidosQueueStats, useEidosResource, useEidosStatus } from "@sweidos/eidos";
|
|
3
|
-
export {
|
|
4
|
-
EidosProvider,
|
|
5
|
-
useEidos,
|
|
6
|
-
useEidosAction,
|
|
7
|
-
useEidosOnDrain,
|
|
8
|
-
useEidosQueue,
|
|
9
|
-
useEidosQueueStats,
|
|
10
|
-
useEidosResource,
|
|
11
|
-
useEidosStatus
|
|
12
|
-
};
|
|
3
|
+
export { EidosProvider, useEidos, useEidosAction, useEidosOnDrain, useEidosQueue, useEidosQueueStats, useEidosResource, useEidosStatus };
|
package/dist/push.cjs
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _sweidos_eidos = require("@sweidos/eidos");
|
|
3
|
+
//#region src/push.ts
|
|
4
|
+
/**
|
|
5
|
+
* @sweidos/eidos/push
|
|
6
|
+
*
|
|
7
|
+
* Web Push integration. Framework-agnostic: register click/expiry handlers
|
|
8
|
+
* once at app init (any tab), and trigger subscription from a user gesture.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { registerPushHandlers, subscribeToPush } from '@sweidos/eidos/push'
|
|
13
|
+
*
|
|
14
|
+
* // App init — every tab, no permission prompt
|
|
15
|
+
* registerPushHandlers({
|
|
16
|
+
* onNotificationClick: (data) => router.push(data.url),
|
|
17
|
+
* onSubscriptionExpired: (sub) => fetch('/api/push-subscribe', { method: 'POST', body: JSON.stringify(sub) }),
|
|
18
|
+
* })
|
|
19
|
+
*
|
|
20
|
+
* // User gesture (button click)
|
|
21
|
+
* const result = await subscribeToPush({
|
|
22
|
+
* vapidPublicKey: import.meta.env.VITE_EIDOS_VAPID_PUBLIC_KEY,
|
|
23
|
+
* onSubscribe: (sub) => fetch('/api/push-subscribe', { method: 'POST', body: JSON.stringify(sub) }),
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
/** Why push is unavailable on this device, or null if it's supported. */
|
|
28
|
+
function getPushUnsupportedReason() {
|
|
29
|
+
if (typeof window === "undefined") return "no-push-api";
|
|
30
|
+
if (/iPhone|iPad|iPod/.test(navigator.userAgent) && !navigator.standalone) return "ios-not-installed";
|
|
31
|
+
if (!("serviceWorker" in navigator) || !("PushManager" in window) || !("Notification" in window)) return "no-push-api";
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function isPushSupported() {
|
|
35
|
+
return getPushUnsupportedReason() === null;
|
|
36
|
+
}
|
|
37
|
+
function getPushPermissionState() {
|
|
38
|
+
if (!isPushSupported()) return "unsupported";
|
|
39
|
+
return Notification.permission;
|
|
40
|
+
}
|
|
41
|
+
function registerPushHandlers(handlers) {
|
|
42
|
+
(0, _sweidos_eidos.registerPushCallbacks)(handlers);
|
|
43
|
+
}
|
|
44
|
+
async function subscribeToPush(config) {
|
|
45
|
+
if (!isPushSupported()) return { status: "unsupported" };
|
|
46
|
+
let permission;
|
|
47
|
+
try {
|
|
48
|
+
permission = await Notification.requestPermission();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return {
|
|
51
|
+
status: "error",
|
|
52
|
+
error
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (permission !== "granted") return { status: "denied" };
|
|
56
|
+
const registration = (0, _sweidos_eidos.getSwRegistration)();
|
|
57
|
+
if (!registration) return { status: "sw-not-ready" };
|
|
58
|
+
try {
|
|
59
|
+
const applicationServerKey = urlBase64ToUint8Array(config.vapidPublicKey);
|
|
60
|
+
let subscription = await registration.pushManager.getSubscription();
|
|
61
|
+
if (subscription && !subscriptionKeyMatches(subscription, config.vapidPublicKey)) {
|
|
62
|
+
await subscription.unsubscribe();
|
|
63
|
+
subscription = null;
|
|
64
|
+
}
|
|
65
|
+
if (!subscription) subscription = await registration.pushManager.subscribe({
|
|
66
|
+
userVisibleOnly: true,
|
|
67
|
+
applicationServerKey
|
|
68
|
+
});
|
|
69
|
+
(0, _sweidos_eidos.sendToWorker)({
|
|
70
|
+
type: "EIDOS_CACHE_VAPID_KEY",
|
|
71
|
+
key: config.vapidPublicKey
|
|
72
|
+
});
|
|
73
|
+
const json = subscription.toJSON();
|
|
74
|
+
config.onSubscribe?.(json);
|
|
75
|
+
return {
|
|
76
|
+
status: "subscribed",
|
|
77
|
+
subscription: json
|
|
78
|
+
};
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return {
|
|
81
|
+
status: "error",
|
|
82
|
+
error
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function unsubscribeFromPush() {
|
|
87
|
+
const registration = (0, _sweidos_eidos.getSwRegistration)();
|
|
88
|
+
if (!registration) return;
|
|
89
|
+
await (await registration.pushManager.getSubscription())?.unsubscribe();
|
|
90
|
+
}
|
|
91
|
+
async function getCurrentPushSubscription() {
|
|
92
|
+
const registration = (0, _sweidos_eidos.getSwRegistration)();
|
|
93
|
+
if (!registration) return null;
|
|
94
|
+
const subscription = await registration.pushManager.getSubscription();
|
|
95
|
+
return subscription ? subscription.toJSON() : null;
|
|
96
|
+
}
|
|
97
|
+
function urlBase64ToUint8Array(base64Url) {
|
|
98
|
+
const base64 = (base64Url + "=".repeat((4 - base64Url.length % 4) % 4)).replace(/-/g, "+").replace(/_/g, "/");
|
|
99
|
+
const raw = atob(base64);
|
|
100
|
+
return Uint8Array.from(raw, (c) => c.charCodeAt(0));
|
|
101
|
+
}
|
|
102
|
+
function uint8ArrayToUrlBase64(bytes) {
|
|
103
|
+
let binary = "";
|
|
104
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
105
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
106
|
+
}
|
|
107
|
+
/** Compares an existing subscription's key against the configured VAPID public key. */
|
|
108
|
+
function subscriptionKeyMatches(subscription, vapidPublicKey) {
|
|
109
|
+
const key = subscription.options.applicationServerKey;
|
|
110
|
+
if (!key) return false;
|
|
111
|
+
return uint8ArrayToUrlBase64(new Uint8Array(key)) === uint8ArrayToUrlBase64(urlBase64ToUint8Array(vapidPublicKey));
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
exports.getCurrentPushSubscription = getCurrentPushSubscription;
|
|
115
|
+
exports.getPushPermissionState = getPushPermissionState;
|
|
116
|
+
exports.getPushUnsupportedReason = getPushUnsupportedReason;
|
|
117
|
+
exports.isPushSupported = isPushSupported;
|
|
118
|
+
exports.registerPushHandlers = registerPushHandlers;
|
|
119
|
+
exports.subscribeToPush = subscribeToPush;
|
|
120
|
+
exports.unsubscribeFromPush = unsubscribeFromPush;
|
package/dist/push.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface PushHandlers {
|
|
2
|
+
/** Fired when the user clicks a notification while a tab is open. */
|
|
3
|
+
onNotificationClick?: (data: unknown) => void;
|
|
4
|
+
/** Fired when the browser silently rotates the push subscription. Re-send to your backend. */
|
|
5
|
+
onSubscriptionExpired?: (sub: PushSubscriptionJSON) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface PushConfig {
|
|
8
|
+
/** Base64url-encoded VAPID public key. Generate with `npx @sweidos/eidos generate-vapid-keys`. */
|
|
9
|
+
vapidPublicKey: string;
|
|
10
|
+
/** Called with the new subscription right after a successful subscribe. Send this to your backend. */
|
|
11
|
+
onSubscribe?: (sub: PushSubscriptionJSON) => void;
|
|
12
|
+
}
|
|
13
|
+
export type PushResult = {
|
|
14
|
+
status: 'subscribed';
|
|
15
|
+
subscription: PushSubscriptionJSON;
|
|
16
|
+
} | {
|
|
17
|
+
status: 'denied' | 'unsupported' | 'sw-not-ready' | 'error';
|
|
18
|
+
error?: unknown;
|
|
19
|
+
};
|
|
20
|
+
export type PushUnsupportedReason = 'no-push-api' | 'ios-not-installed' | null;
|
|
21
|
+
/** Why push is unavailable on this device, or null if it's supported. */
|
|
22
|
+
export declare function getPushUnsupportedReason(): PushUnsupportedReason;
|
|
23
|
+
export declare function isPushSupported(): boolean;
|
|
24
|
+
export declare function getPushPermissionState(): NotificationPermission | 'unsupported';
|
|
25
|
+
export declare function registerPushHandlers(handlers: PushHandlers): void;
|
|
26
|
+
export declare function subscribeToPush(config: PushConfig): Promise<PushResult>;
|
|
27
|
+
export declare function unsubscribeFromPush(): Promise<void>;
|
|
28
|
+
export declare function getCurrentPushSubscription(): Promise<PushSubscriptionJSON | null>;
|