@stamprally/react 0.1.0 → 0.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 +103 -0
- package/dist/index.cjs +126 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -4
- package/dist/index.d.ts +30 -4
- package/dist/index.js +127 -21
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @stamprally/react
|
|
2
|
+
|
|
3
|
+
React 19 integration for [`@stamprally/core`](https://www.npmjs.com/package/@stamprally/core).
|
|
4
|
+
The package provides the `useStampRally` hook with external-store updates,
|
|
5
|
+
optimistic stamp acquisition, persistence, reward redemption, and recovery-code
|
|
6
|
+
support.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @stamprally/core @stamprally/react react
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Or with pnpm:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
pnpm add @stamprally/core @stamprally/react react
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This package supports React `>=19.0.0 <20.0.0`.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { useMemo } from "react";
|
|
26
|
+
import {
|
|
27
|
+
LocalStorageAdapter,
|
|
28
|
+
StampRallyClient,
|
|
29
|
+
type RallyConfig,
|
|
30
|
+
} from "@stamprally/core";
|
|
31
|
+
import { useStampRally } from "@stamprally/react";
|
|
32
|
+
|
|
33
|
+
const config: RallyConfig = {
|
|
34
|
+
id: "city-tour",
|
|
35
|
+
stamps: [
|
|
36
|
+
{
|
|
37
|
+
id: "station",
|
|
38
|
+
name: "Central Station",
|
|
39
|
+
condition: { type: "instant" },
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function Rally() {
|
|
45
|
+
const client = useMemo(
|
|
46
|
+
() => new StampRallyClient(config, new LocalStorageAdapter()),
|
|
47
|
+
[],
|
|
48
|
+
);
|
|
49
|
+
const { state, isLoading, isPending, error, acquire } = useStampRally(client);
|
|
50
|
+
|
|
51
|
+
if (isLoading) return <p>Loading…</p>;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<section>
|
|
55
|
+
<p>
|
|
56
|
+
{state?.records.length ?? 0} stamp(s) collected
|
|
57
|
+
</p>
|
|
58
|
+
<button
|
|
59
|
+
disabled={isPending}
|
|
60
|
+
onClick={() => void acquire("station", { type: "instant" })}
|
|
61
|
+
>
|
|
62
|
+
Collect stamp
|
|
63
|
+
</button>
|
|
64
|
+
{error !== null && <p role="alert">Could not collect the stamp.</p>}
|
|
65
|
+
</section>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The hook initializes the client when needed, subscribes to state changes, and
|
|
71
|
+
keeps the UI synchronized with persisted state. Keep the `StampRallyClient`
|
|
72
|
+
instance stable, for example with `useMemo`, so that the hook does not switch
|
|
73
|
+
clients on every render.
|
|
74
|
+
|
|
75
|
+
## Hook return value
|
|
76
|
+
|
|
77
|
+
`useStampRally(client)` returns:
|
|
78
|
+
|
|
79
|
+
- `state`: the current `StampRallyState`, or `null` while it is not initialized.
|
|
80
|
+
- `isLoading`: whether the client is initializing or changing clients.
|
|
81
|
+
- `isPending`: whether an acquisition, reset, redemption, or import is pending.
|
|
82
|
+
- `error`: the latest typed engine/reward error or storage error.
|
|
83
|
+
- `rewardsState`: the current reward states, or an empty array when rewards are not configured.
|
|
84
|
+
- `acquire(stampId, context, now?)`: validates and persists a stamp acquisition.
|
|
85
|
+
- `reset(now?)`: clears the persisted rally state.
|
|
86
|
+
- `redeem(rewardId, options?)`: redeems an available reward, optionally with a staff passcode and ID.
|
|
87
|
+
- `exportRecoveryCode()`: exports confirmed stamp and reward progress.
|
|
88
|
+
- `importRecoveryCode(token)`: restores a rally-scoped recovery code and returns whether it was valid.
|
|
89
|
+
|
|
90
|
+
Acquisitions are shown optimistically while persistence is pending. Engine
|
|
91
|
+
validation and storage failures roll the optimistic state back and are exposed
|
|
92
|
+
through the returned `error` value and the rejected promise where applicable.
|
|
93
|
+
|
|
94
|
+
## Browser and server rendering
|
|
95
|
+
|
|
96
|
+
The hook uses React's external-store API and provides a `null` server snapshot.
|
|
97
|
+
The core package's browser adapters and detectors access browser globals only
|
|
98
|
+
when called, so applications can choose a different `StampStorage` for server
|
|
99
|
+
rendering, tests, or non-browser environments.
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,26 @@ function applyOptimisticAcquire(currentState, action) {
|
|
|
20
20
|
updatedAt: action.acquiredAt
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
function
|
|
23
|
+
function createIdempotencyKey() {
|
|
24
|
+
const cryptoApi = globalThis.crypto;
|
|
25
|
+
if (cryptoApi !== void 0 && typeof cryptoApi.randomUUID === "function") {
|
|
26
|
+
return cryptoApi.randomUUID();
|
|
27
|
+
}
|
|
28
|
+
return `stamp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
29
|
+
}
|
|
30
|
+
function isOnline(adapter) {
|
|
31
|
+
if (adapter?.isOnline === void 0) {
|
|
32
|
+
return typeof navigator === "undefined" || navigator.onLine !== false;
|
|
33
|
+
}
|
|
34
|
+
return typeof adapter.isOnline === "function" ? adapter.isOnline() : adapter.isOnline;
|
|
35
|
+
}
|
|
36
|
+
function isRejectedVerification(value) {
|
|
37
|
+
return value === false || typeof value === "object" && value !== null && "ok" in value && value.ok === false;
|
|
38
|
+
}
|
|
39
|
+
function useStampRally(client, options = {}) {
|
|
40
|
+
const syncAdapter = options.syncAdapter;
|
|
41
|
+
const events = options.events;
|
|
42
|
+
const queuedRequests = react.useRef([]);
|
|
24
43
|
const subscribe = react.useCallback(
|
|
25
44
|
(onStoreChange) => client.subscribe(() => onStoreChange()),
|
|
26
45
|
[client]
|
|
@@ -32,8 +51,16 @@ function useStampRally(client) {
|
|
|
32
51
|
isInitializing: client.getState() === null
|
|
33
52
|
}));
|
|
34
53
|
const [clientError, setClientError] = react.useState(null);
|
|
35
|
-
const [isPending
|
|
36
|
-
const [
|
|
54
|
+
const [isPending] = react.useTransition();
|
|
55
|
+
const [isOperationPending, setIsOperationPending] = react.useState(false);
|
|
56
|
+
const [optimisticState, setOptimisticState] = react.useState(rawState);
|
|
57
|
+
react.useEffect(() => {
|
|
58
|
+
setOptimisticState(rawState);
|
|
59
|
+
}, [rawState]);
|
|
60
|
+
react.useEffect(() => {
|
|
61
|
+
if (syncAdapter?.onStateChange === void 0) return;
|
|
62
|
+
return client.subscribe(syncAdapter.onStateChange);
|
|
63
|
+
}, [client, syncAdapter]);
|
|
37
64
|
react.useEffect(() => {
|
|
38
65
|
let active = true;
|
|
39
66
|
setClientError(null);
|
|
@@ -58,51 +85,109 @@ function useStampRally(client) {
|
|
|
58
85
|
};
|
|
59
86
|
}, [client, rawState]);
|
|
60
87
|
const acquire = react.useCallback(
|
|
61
|
-
(stampId, context, now) => {
|
|
88
|
+
(stampId, context, now, idempotencyKey) => {
|
|
62
89
|
const acquiredAt = now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
90
|
+
const request = {
|
|
91
|
+
stampId,
|
|
92
|
+
context,
|
|
93
|
+
now: acquiredAt,
|
|
94
|
+
idempotencyKey: idempotencyKey ?? createIdempotencyKey()
|
|
95
|
+
};
|
|
63
96
|
setClientError(null);
|
|
97
|
+
if (!isOnline(syncAdapter)) {
|
|
98
|
+
queuedRequests.current = [...queuedRequests.current, request];
|
|
99
|
+
const queued = {
|
|
100
|
+
ok: false,
|
|
101
|
+
error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
|
|
102
|
+
};
|
|
103
|
+
setClientError({ client, value: queued.error });
|
|
104
|
+
return Promise.resolve(queued);
|
|
105
|
+
}
|
|
106
|
+
setIsOperationPending(true);
|
|
107
|
+
setOptimisticState((current) => applyOptimisticAcquire(current, { stampId, acquiredAt }));
|
|
64
108
|
return new Promise((resolve, reject) => {
|
|
65
|
-
|
|
66
|
-
addOptimisticAcquire({ stampId, acquiredAt });
|
|
109
|
+
void (async () => {
|
|
67
110
|
try {
|
|
111
|
+
const before = await syncAdapter?.onBeforeCheckIn?.(request);
|
|
112
|
+
if (before === false) {
|
|
113
|
+
const rejected = {
|
|
114
|
+
ok: false,
|
|
115
|
+
error: { code: "INVALID_PROOF", stampId }
|
|
116
|
+
};
|
|
117
|
+
setClientError({ client, value: rejected.error });
|
|
118
|
+
setIsOperationPending(false);
|
|
119
|
+
resolve(rejected);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
68
122
|
const result = await client.acquire(stampId, context, acquiredAt);
|
|
123
|
+
if (result.ok) {
|
|
124
|
+
const verified = await syncAdapter?.onServerVerify?.(request);
|
|
125
|
+
if (isRejectedVerification(verified)) {
|
|
126
|
+
await client.restore(rawState ?? client.getState() ?? result.value.nextState);
|
|
127
|
+
const rejected = {
|
|
128
|
+
ok: false,
|
|
129
|
+
error: { code: "INVALID_PROOF", stampId }
|
|
130
|
+
};
|
|
131
|
+
setClientError({ client, value: rejected.error });
|
|
132
|
+
setOptimisticState(client.getState());
|
|
133
|
+
setIsOperationPending(false);
|
|
134
|
+
resolve(rejected);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
for (const event of result.value.events) {
|
|
138
|
+
if (event.type === "stampAcquired") {
|
|
139
|
+
(events?.onStampClaimed ?? options.onStampClaimed)?.(event.record);
|
|
140
|
+
}
|
|
141
|
+
if (event.type === "rewardUnlocked") {
|
|
142
|
+
(events?.onRewardUnlocked ?? options.onRewardUnlocked)?.(event.rewardId);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
69
146
|
if (!result.ok) {
|
|
70
147
|
setClientError({ client, value: result.error });
|
|
148
|
+
setOptimisticState(client.getState());
|
|
71
149
|
}
|
|
150
|
+
setIsOperationPending(false);
|
|
72
151
|
resolve(result);
|
|
73
152
|
} catch (acquireError) {
|
|
74
153
|
const normalizedError = toError(acquireError);
|
|
75
154
|
setClientError({ client, value: normalizedError });
|
|
155
|
+
setOptimisticState(client.getState());
|
|
156
|
+
setIsOperationPending(false);
|
|
76
157
|
reject(normalizedError);
|
|
77
158
|
}
|
|
78
|
-
});
|
|
159
|
+
})();
|
|
79
160
|
});
|
|
80
161
|
},
|
|
81
|
-
[
|
|
162
|
+
[client, events, options.onRewardUnlocked, options.onStampClaimed, rawState, syncAdapter]
|
|
82
163
|
);
|
|
83
164
|
const reset = react.useCallback(
|
|
84
165
|
(now) => {
|
|
85
166
|
setClientError(null);
|
|
167
|
+
setIsOperationPending(true);
|
|
86
168
|
return new Promise((resolve, reject) => {
|
|
87
|
-
|
|
169
|
+
void (async () => {
|
|
88
170
|
try {
|
|
89
171
|
const nextState = now === void 0 ? await client.reset() : await client.reset(now);
|
|
172
|
+
setIsOperationPending(false);
|
|
90
173
|
resolve(nextState);
|
|
91
174
|
} catch (resetError) {
|
|
92
175
|
const normalizedError = toError(resetError);
|
|
93
176
|
setClientError({ client, value: normalizedError });
|
|
177
|
+
setIsOperationPending(false);
|
|
94
178
|
reject(normalizedError);
|
|
95
179
|
}
|
|
96
|
-
});
|
|
180
|
+
})();
|
|
97
181
|
});
|
|
98
182
|
},
|
|
99
183
|
[client]
|
|
100
184
|
);
|
|
101
185
|
const redeem = react.useCallback(
|
|
102
|
-
(rewardId,
|
|
186
|
+
(rewardId, redeemOptions = {}) => {
|
|
103
187
|
setClientError(null);
|
|
188
|
+
setIsOperationPending(true);
|
|
104
189
|
return new Promise((resolve, reject) => {
|
|
105
|
-
|
|
190
|
+
void (async () => {
|
|
106
191
|
const reward = client.getConfig().rewards?.find((item) => item.id === rewardId);
|
|
107
192
|
if (reward === void 0) {
|
|
108
193
|
const result2 = {
|
|
@@ -110,6 +195,7 @@ function useStampRally(client) {
|
|
|
110
195
|
error: { code: "REWARD_NOT_FOUND", rewardId }
|
|
111
196
|
};
|
|
112
197
|
setClientError({ client, value: result2.error });
|
|
198
|
+
setIsOperationPending(false);
|
|
113
199
|
resolve(result2);
|
|
114
200
|
return;
|
|
115
201
|
}
|
|
@@ -123,6 +209,7 @@ function useStampRally(client) {
|
|
|
123
209
|
error: { code: "NOT_AVAILABLE", rewardId }
|
|
124
210
|
};
|
|
125
211
|
setClientError({ client, value: result2.error });
|
|
212
|
+
setIsOperationPending(false);
|
|
126
213
|
resolve(result2);
|
|
127
214
|
return;
|
|
128
215
|
}
|
|
@@ -130,15 +217,17 @@ function useStampRally(client) {
|
|
|
130
217
|
reward,
|
|
131
218
|
currentState: currentRewardState,
|
|
132
219
|
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
133
|
-
...
|
|
134
|
-
...
|
|
220
|
+
...redeemOptions.passcode === void 0 ? {} : { inputPasscode: redeemOptions.passcode },
|
|
221
|
+
...redeemOptions.staffId === void 0 ? {} : { staffId: redeemOptions.staffId }
|
|
135
222
|
});
|
|
136
223
|
if (!result.ok) {
|
|
137
224
|
setClientError({ client, value: result.error });
|
|
225
|
+
setIsOperationPending(false);
|
|
138
226
|
resolve(result);
|
|
139
227
|
return;
|
|
140
228
|
}
|
|
141
229
|
if (result.value === currentRewardState) {
|
|
230
|
+
setIsOperationPending(false);
|
|
142
231
|
resolve(result);
|
|
143
232
|
return;
|
|
144
233
|
}
|
|
@@ -151,16 +240,19 @@ function useStampRally(client) {
|
|
|
151
240
|
};
|
|
152
241
|
try {
|
|
153
242
|
await client.restore(nextState);
|
|
243
|
+
(events?.onRewardConsumed ?? options.onRewardConsumed)?.(rewardId);
|
|
244
|
+
setIsOperationPending(false);
|
|
154
245
|
resolve(result);
|
|
155
246
|
} catch (redeemError) {
|
|
156
247
|
const normalizedError = toError(redeemError);
|
|
157
248
|
setClientError({ client, value: normalizedError });
|
|
249
|
+
setIsOperationPending(false);
|
|
158
250
|
reject(normalizedError);
|
|
159
251
|
}
|
|
160
|
-
});
|
|
252
|
+
})();
|
|
161
253
|
});
|
|
162
254
|
},
|
|
163
|
-
[client]
|
|
255
|
+
[client, events, options.onRewardConsumed]
|
|
164
256
|
);
|
|
165
257
|
const exportRecoveryCode = react.useCallback(() => {
|
|
166
258
|
const state = client.getState();
|
|
@@ -178,11 +270,13 @@ function useStampRally(client) {
|
|
|
178
270
|
const importRecoveryCode = react.useCallback(
|
|
179
271
|
(token) => {
|
|
180
272
|
setClientError(null);
|
|
273
|
+
setIsOperationPending(true);
|
|
181
274
|
return new Promise((resolve, reject) => {
|
|
182
|
-
|
|
275
|
+
void (async () => {
|
|
183
276
|
const config = client.getConfig();
|
|
184
277
|
const snapshot = core.importProgressToken(token, config.id);
|
|
185
278
|
if (snapshot === null) {
|
|
279
|
+
setIsOperationPending(false);
|
|
186
280
|
resolve(false);
|
|
187
281
|
return;
|
|
188
282
|
}
|
|
@@ -208,30 +302,42 @@ function useStampRally(client) {
|
|
|
208
302
|
...config.rewards === void 0 && rewards.length === 0 ? {} : { rewards },
|
|
209
303
|
updatedAt: snapshot.exportedAt
|
|
210
304
|
});
|
|
305
|
+
setIsOperationPending(false);
|
|
211
306
|
resolve(true);
|
|
212
307
|
} catch (importError) {
|
|
213
308
|
const normalizedError = toError(importError);
|
|
214
309
|
setClientError({ client, value: normalizedError });
|
|
310
|
+
setIsOperationPending(false);
|
|
215
311
|
reject(normalizedError);
|
|
216
312
|
}
|
|
217
|
-
});
|
|
313
|
+
})();
|
|
218
314
|
});
|
|
219
315
|
},
|
|
220
316
|
[client]
|
|
221
317
|
);
|
|
222
318
|
const isLoading = rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);
|
|
223
319
|
const error = clientError?.client === client ? clientError.value : null;
|
|
320
|
+
const flushQueue = react.useCallback(async () => {
|
|
321
|
+
if (!isOnline(syncAdapter)) return;
|
|
322
|
+
const pending = queuedRequests.current;
|
|
323
|
+
queuedRequests.current = [];
|
|
324
|
+
for (const request of pending) {
|
|
325
|
+
await acquire(request.stampId, request.context, request.now, request.idempotencyKey);
|
|
326
|
+
}
|
|
327
|
+
}, [acquire, syncAdapter]);
|
|
224
328
|
return {
|
|
225
329
|
state: optimisticState,
|
|
226
330
|
isLoading,
|
|
227
|
-
isPending,
|
|
331
|
+
isPending: isPending || isOperationPending,
|
|
228
332
|
error,
|
|
229
333
|
rewardsState: optimisticState?.rewards ?? [],
|
|
230
334
|
acquire,
|
|
231
335
|
reset,
|
|
232
336
|
redeem,
|
|
233
337
|
exportRecoveryCode,
|
|
234
|
-
importRecoveryCode
|
|
338
|
+
importRecoveryCode,
|
|
339
|
+
queuedCount: queuedRequests.current.length,
|
|
340
|
+
flushQueue
|
|
235
341
|
};
|
|
236
342
|
}
|
|
237
343
|
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useStampRally.ts"],"names":["useCallback","useSyncExternalStore","useState","useTransition","useOptimistic","useEffect","result","consumeReward","exportProgressToken","importProgressToken"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n ConsumeResult,\n ProcessStampValue,\n Result,\n RewardConsumeError,\n RewardState,\n StampError,\n StampRallyClient,\n StampRallyState,\n VerificationContext,\n} from \"@stamprally/core\";\nimport { consumeReward, exportProgressToken, importProgressToken } from \"@stamprally/core\";\nimport {\n useCallback,\n useEffect,\n useOptimistic,\n useState,\n useSyncExternalStore,\n useTransition,\n} from \"react\";\n\ninterface OptimisticAcquire {\n readonly stampId: string;\n readonly acquiredAt: string;\n}\n\ninterface ClientStatus {\n readonly client: StampRallyClient;\n readonly isInitializing: boolean;\n}\n\ninterface ClientError {\n readonly client: StampRallyClient;\n readonly value: StampError | RewardConsumeError | Error;\n}\n\nexport interface RedeemOptions {\n readonly passcode?: string;\n readonly staffId?: string;\n}\n\nexport interface UseStampRallyReturn {\n readonly state: StampRallyState | null;\n readonly isLoading: boolean;\n readonly isPending: boolean;\n readonly error: StampError | RewardConsumeError | Error | null;\n readonly rewardsState: ReadonlyArray<RewardState>;\n readonly acquire: (\n stampId: string,\n context: VerificationContext,\n now?: string,\n ) => Promise<Result<ProcessStampValue, StampError>>;\n readonly reset: (now?: string) => Promise<StampRallyState>;\n readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;\n readonly exportRecoveryCode: () => string;\n readonly importRecoveryCode: (token: string) => Promise<boolean>;\n}\n\n/** @deprecated Use UseStampRallyReturn instead. */\nexport type UseStampRallyValue = UseStampRallyReturn;\n\nfunction getServerSnapshot(): null {\n return null;\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction applyOptimisticAcquire(\n currentState: StampRallyState | null,\n action: OptimisticAcquire,\n): StampRallyState | null {\n if (\n currentState === null ||\n currentState.records.some((record) => record.stampId === action.stampId)\n ) {\n return currentState;\n }\n\n return {\n ...currentState,\n records: [...currentState.records, { stampId: action.stampId, acquiredAt: action.acquiredAt }],\n updatedAt: action.acquiredAt,\n };\n}\n\nexport function useStampRally(client: StampRallyClient): UseStampRallyReturn {\n const subscribe = useCallback(\n (onStoreChange: () => void) => client.subscribe(() => onStoreChange()),\n [client],\n );\n const getSnapshot = useCallback(() => client.getState(), [client]);\n const rawState = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n const [clientStatus, setClientStatus] = useState<ClientStatus>(() => ({\n client,\n isInitializing: client.getState() === null,\n }));\n const [clientError, setClientError] = useState<ClientError | null>(null);\n const [isPending, startTransition] = useTransition();\n const [optimisticState, addOptimisticAcquire] = useOptimistic(rawState, applyOptimisticAcquire);\n\n useEffect(() => {\n let active = true;\n setClientError(null);\n if (rawState !== null) {\n setClientStatus({ client, isInitializing: false });\n return () => {\n active = false;\n };\n }\n\n setClientStatus({ client, isInitializing: true });\n\n void client\n .init()\n .catch((initializationError: unknown) => {\n if (active) {\n setClientError({ client, value: toError(initializationError) });\n }\n })\n .finally(() => {\n if (active) {\n setClientStatus({ client, isInitializing: false });\n }\n });\n\n return () => {\n active = false;\n };\n }, [client, rawState]);\n\n const acquire = useCallback(\n (\n stampId: string,\n context: VerificationContext,\n now?: string,\n ): Promise<Result<ProcessStampValue, StampError>> => {\n const acquiredAt = now ?? new Date().toISOString();\n setClientError(null);\n\n return new Promise((resolve, reject) => {\n startTransition(async () => {\n addOptimisticAcquire({ stampId, acquiredAt });\n try {\n const result = await client.acquire(stampId, context, acquiredAt);\n if (!result.ok) {\n setClientError({ client, value: result.error });\n }\n resolve(result);\n } catch (acquireError) {\n const normalizedError = toError(acquireError);\n setClientError({ client, value: normalizedError });\n reject(normalizedError);\n }\n });\n });\n },\n [addOptimisticAcquire, client],\n );\n\n const reset = useCallback(\n (now?: string): Promise<StampRallyState> => {\n setClientError(null);\n\n return new Promise((resolve, reject) => {\n startTransition(async () => {\n try {\n const nextState = now === undefined ? await client.reset() : await client.reset(now);\n resolve(nextState);\n } catch (resetError) {\n const normalizedError = toError(resetError);\n setClientError({ client, value: normalizedError });\n reject(normalizedError);\n }\n });\n });\n },\n [client],\n );\n\n const redeem = useCallback(\n (rewardId: string, options: RedeemOptions = {}): Promise<ConsumeResult> => {\n setClientError(null);\n\n return new Promise((resolve, reject) => {\n startTransition(async () => {\n const reward = client.getConfig().rewards?.find((item) => item.id === rewardId);\n if (reward === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"REWARD_NOT_FOUND\", rewardId },\n };\n setClientError({ client, value: result.error });\n resolve(result);\n return;\n }\n\n const currentState = client.getState();\n const currentRewardState = currentState?.rewards?.find(\n (state) => state.rewardId === rewardId,\n );\n if (currentState === null || currentRewardState === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"NOT_AVAILABLE\", rewardId },\n };\n setClientError({ client, value: result.error });\n resolve(result);\n return;\n }\n\n const result = consumeReward({\n reward,\n currentState: currentRewardState,\n now: new Date().toISOString(),\n ...(options.passcode === undefined ? {} : { inputPasscode: options.passcode }),\n ...(options.staffId === undefined ? {} : { staffId: options.staffId }),\n });\n if (!result.ok) {\n setClientError({ client, value: result.error });\n resolve(result);\n return;\n }\n\n if (result.value === currentRewardState) {\n resolve(result);\n return;\n }\n\n const nextState: StampRallyState = {\n ...currentState,\n rewards: (currentState.rewards ?? []).map((state) =>\n state.rewardId === rewardId ? result.value : state,\n ),\n updatedAt: result.value.consumedAt ?? currentState.updatedAt,\n };\n\n try {\n await client.restore(nextState);\n resolve(result);\n } catch (redeemError) {\n const normalizedError = toError(redeemError);\n setClientError({ client, value: normalizedError });\n reject(normalizedError);\n }\n });\n });\n },\n [client],\n );\n\n const exportRecoveryCode = useCallback((): string => {\n const state = client.getState();\n if (state === null) {\n throw new Error(\"Cannot export recovery code before the rally is initialized.\");\n }\n\n return exportProgressToken({\n version: 1,\n rallyId: state.rallyId,\n stamps: state.records,\n rewards: state.rewards ?? [],\n exportedAt: new Date().toISOString(),\n });\n }, [client]);\n\n const importRecoveryCode = useCallback(\n (token: string): Promise<boolean> => {\n setClientError(null);\n\n return new Promise((resolve, reject) => {\n startTransition(async () => {\n const config = client.getConfig();\n const snapshot = importProgressToken(token, config.id);\n if (snapshot === null) {\n resolve(false);\n return;\n }\n\n const stampIds = new Set(config.stamps.map((stamp) => stamp.id));\n const rewardIds = new Set((config.rewards ?? []).map((reward) => reward.id));\n const importedStampIds = new Set<string>();\n const importedRewardIds = new Set<string>();\n const stamps = snapshot.stamps.filter((record) => {\n if (!stampIds.has(record.stampId) || importedStampIds.has(record.stampId)) return false;\n importedStampIds.add(record.stampId);\n return true;\n });\n const rewards = snapshot.rewards.filter((state) => {\n if (!rewardIds.has(state.rewardId) || importedRewardIds.has(state.rewardId))\n return false;\n importedRewardIds.add(state.rewardId);\n return true;\n });\n\n try {\n await client.restore({\n rallyId: config.id,\n records: stamps,\n ...(config.rewards === undefined && rewards.length === 0 ? {} : { rewards }),\n updatedAt: snapshot.exportedAt,\n });\n resolve(true);\n } catch (importError) {\n const normalizedError = toError(importError);\n setClientError({ client, value: normalizedError });\n reject(normalizedError);\n }\n });\n });\n },\n [client],\n );\n\n const isLoading =\n rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);\n const error = clientError?.client === client ? clientError.value : null;\n\n return {\n state: optimisticState,\n isLoading,\n isPending,\n error,\n rewardsState: optimisticState?.rewards ?? [],\n acquire,\n reset,\n redeem,\n exportRecoveryCode,\n importRecoveryCode,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/useStampRally.ts"],"names":["useRef","useCallback","useSyncExternalStore","useState","useTransition","useEffect","result","consumeReward","exportProgressToken","importProgressToken"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n ConsumeResult,\n ProcessStampValue,\n Result,\n RewardConsumeError,\n RewardState,\n StampError,\n StampRallyClient,\n StampRallyState,\n VerificationContext,\n} from \"@stamprally/core\";\nimport { consumeReward, exportProgressToken, importProgressToken } from \"@stamprally/core\";\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n useTransition,\n} from \"react\";\n\ninterface OptimisticAcquire {\n readonly stampId: string;\n readonly acquiredAt: string;\n}\n\ninterface ClientStatus {\n readonly client: StampRallyClient;\n readonly isInitializing: boolean;\n}\n\ninterface ClientError {\n readonly client: StampRallyClient;\n readonly value: StampError | RewardConsumeError | Error;\n}\n\nexport interface CheckInRequest {\n readonly stampId: string;\n readonly context: VerificationContext;\n readonly now: string;\n readonly idempotencyKey: string;\n}\n\nexport interface SyncAdapter {\n readonly isOnline?: boolean | (() => boolean);\n readonly onBeforeCheckIn?: (request: CheckInRequest) => unknown;\n readonly onServerVerify?: (request: CheckInRequest) => unknown;\n readonly onStateChange?: (state: StampRallyState) => void;\n}\n\nexport interface StampRallyEventHandlers {\n readonly onStampClaimed?: (record: StampRallyState[\"records\"][number]) => void;\n readonly onRewardUnlocked?: (rewardId: string) => void;\n readonly onRewardConsumed?: (rewardId: string) => void;\n}\n\nexport interface UseStampRallyOptions {\n readonly syncAdapter?: SyncAdapter;\n readonly events?: StampRallyEventHandlers;\n readonly onStampClaimed?: StampRallyEventHandlers[\"onStampClaimed\"];\n readonly onRewardUnlocked?: StampRallyEventHandlers[\"onRewardUnlocked\"];\n readonly onRewardConsumed?: StampRallyEventHandlers[\"onRewardConsumed\"];\n}\n\nexport interface RedeemOptions {\n readonly passcode?: string;\n readonly staffId?: string;\n}\n\nexport interface UseStampRallyReturn {\n readonly state: StampRallyState | null;\n readonly isLoading: boolean;\n readonly isPending: boolean;\n readonly error: StampError | RewardConsumeError | Error | null;\n readonly rewardsState: ReadonlyArray<RewardState>;\n readonly acquire: (\n stampId: string,\n context: VerificationContext,\n now?: string,\n idempotencyKey?: string,\n ) => Promise<Result<ProcessStampValue, StampError>>;\n readonly reset: (now?: string) => Promise<StampRallyState>;\n readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;\n readonly exportRecoveryCode: () => string;\n readonly importRecoveryCode: (token: string) => Promise<boolean>;\n readonly queuedCount: number;\n readonly flushQueue: () => Promise<void>;\n}\n\n/** @deprecated Use UseStampRallyReturn instead. */\nexport type UseStampRallyValue = UseStampRallyReturn;\n\nfunction getServerSnapshot(): null {\n return null;\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction applyOptimisticAcquire(\n currentState: StampRallyState | null,\n action: OptimisticAcquire,\n): StampRallyState | null {\n if (\n currentState === null ||\n currentState.records.some((record) => record.stampId === action.stampId)\n ) {\n return currentState;\n }\n\n return {\n ...currentState,\n records: [...currentState.records, { stampId: action.stampId, acquiredAt: action.acquiredAt }],\n updatedAt: action.acquiredAt,\n };\n}\n\nfunction createIdempotencyKey(): string {\n const cryptoApi = globalThis.crypto;\n if (cryptoApi !== undefined && typeof cryptoApi.randomUUID === \"function\") {\n return cryptoApi.randomUUID();\n }\n return `stamp-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction isOnline(adapter: SyncAdapter | undefined): boolean {\n if (adapter?.isOnline === undefined) {\n return typeof navigator === \"undefined\" || navigator.onLine !== false;\n }\n return typeof adapter.isOnline === \"function\" ? adapter.isOnline() : adapter.isOnline;\n}\n\nfunction isRejectedVerification(value: unknown): boolean {\n return (\n value === false ||\n (typeof value === \"object\" && value !== null && \"ok\" in value && value.ok === false)\n );\n}\n\nexport function useStampRally(\n client: StampRallyClient,\n options: UseStampRallyOptions = {},\n): UseStampRallyReturn {\n const syncAdapter = options.syncAdapter;\n const events = options.events;\n const queuedRequests = useRef<ReadonlyArray<CheckInRequest>>([]);\n const subscribe = useCallback(\n (onStoreChange: () => void) => client.subscribe(() => onStoreChange()),\n [client],\n );\n const getSnapshot = useCallback(() => client.getState(), [client]);\n const rawState = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n const [clientStatus, setClientStatus] = useState<ClientStatus>(() => ({\n client,\n isInitializing: client.getState() === null,\n }));\n const [clientError, setClientError] = useState<ClientError | null>(null);\n const [isPending] = useTransition();\n const [isOperationPending, setIsOperationPending] = useState(false);\n const [optimisticState, setOptimisticState] = useState<StampRallyState | null>(rawState);\n\n useEffect(() => {\n setOptimisticState(rawState);\n }, [rawState]);\n\n useEffect(() => {\n if (syncAdapter?.onStateChange === undefined) return;\n return client.subscribe(syncAdapter.onStateChange);\n }, [client, syncAdapter]);\n\n useEffect(() => {\n let active = true;\n setClientError(null);\n if (rawState !== null) {\n setClientStatus({ client, isInitializing: false });\n return () => {\n active = false;\n };\n }\n\n setClientStatus({ client, isInitializing: true });\n\n void client\n .init()\n .catch((initializationError: unknown) => {\n if (active) {\n setClientError({ client, value: toError(initializationError) });\n }\n })\n .finally(() => {\n if (active) {\n setClientStatus({ client, isInitializing: false });\n }\n });\n\n return () => {\n active = false;\n };\n }, [client, rawState]);\n\n const acquire = useCallback(\n (\n stampId: string,\n context: VerificationContext,\n now?: string,\n idempotencyKey?: string,\n ): Promise<Result<ProcessStampValue, StampError>> => {\n const acquiredAt = now ?? new Date().toISOString();\n const request: CheckInRequest = {\n stampId,\n context,\n now: acquiredAt,\n idempotencyKey: idempotencyKey ?? createIdempotencyKey(),\n };\n setClientError(null);\n\n if (!isOnline(syncAdapter)) {\n queuedRequests.current = [...queuedRequests.current, request];\n const queued: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"OFFLINE_QUEUED\", stampId, idempotencyKey: request.idempotencyKey },\n };\n setClientError({ client, value: queued.error });\n return Promise.resolve(queued);\n }\n\n setIsOperationPending(true);\n setOptimisticState((current) => applyOptimisticAcquire(current, { stampId, acquiredAt }));\n return new Promise((resolve, reject) => {\n void (async () => {\n try {\n const before = await syncAdapter?.onBeforeCheckIn?.(request);\n if (before === false) {\n const rejected: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"INVALID_PROOF\", stampId },\n };\n setClientError({ client, value: rejected.error });\n setIsOperationPending(false);\n resolve(rejected);\n return;\n }\n const result = await client.acquire(stampId, context, acquiredAt);\n if (result.ok) {\n const verified = await syncAdapter?.onServerVerify?.(request);\n if (isRejectedVerification(verified)) {\n await client.restore(rawState ?? client.getState() ?? result.value.nextState);\n const rejected: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"INVALID_PROOF\", stampId },\n };\n setClientError({ client, value: rejected.error });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(rejected);\n return;\n }\n for (const event of result.value.events) {\n if (event.type === \"stampAcquired\") {\n (events?.onStampClaimed ?? options.onStampClaimed)?.(event.record);\n }\n if (event.type === \"rewardUnlocked\") {\n (events?.onRewardUnlocked ?? options.onRewardUnlocked)?.(event.rewardId);\n }\n }\n }\n if (!result.ok) {\n setClientError({ client, value: result.error });\n setOptimisticState(client.getState());\n }\n setIsOperationPending(false);\n resolve(result);\n } catch (acquireError) {\n const normalizedError = toError(acquireError);\n setClientError({ client, value: normalizedError });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client, events, options.onRewardUnlocked, options.onStampClaimed, rawState, syncAdapter],\n );\n\n const reset = useCallback(\n (now?: string): Promise<StampRallyState> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n try {\n const nextState = now === undefined ? await client.reset() : await client.reset(now);\n setIsOperationPending(false);\n resolve(nextState);\n } catch (resetError) {\n const normalizedError = toError(resetError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client],\n );\n\n const redeem = useCallback(\n (rewardId: string, redeemOptions: RedeemOptions = {}): Promise<ConsumeResult> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n const reward = client.getConfig().rewards?.find((item) => item.id === rewardId);\n if (reward === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"REWARD_NOT_FOUND\", rewardId },\n };\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const currentState = client.getState();\n const currentRewardState = currentState?.rewards?.find(\n (state) => state.rewardId === rewardId,\n );\n if (currentState === null || currentRewardState === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"NOT_AVAILABLE\", rewardId },\n };\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const result = consumeReward({\n reward,\n currentState: currentRewardState,\n now: new Date().toISOString(),\n ...(redeemOptions.passcode === undefined\n ? {}\n : { inputPasscode: redeemOptions.passcode }),\n ...(redeemOptions.staffId === undefined ? {} : { staffId: redeemOptions.staffId }),\n });\n if (!result.ok) {\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n if (result.value === currentRewardState) {\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const nextState: StampRallyState = {\n ...currentState,\n rewards: (currentState.rewards ?? []).map((state) =>\n state.rewardId === rewardId ? result.value : state,\n ),\n updatedAt: result.value.consumedAt ?? currentState.updatedAt,\n };\n\n try {\n await client.restore(nextState);\n (events?.onRewardConsumed ?? options.onRewardConsumed)?.(rewardId);\n setIsOperationPending(false);\n resolve(result);\n } catch (redeemError) {\n const normalizedError = toError(redeemError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client, events, options.onRewardConsumed],\n );\n\n const exportRecoveryCode = useCallback((): string => {\n const state = client.getState();\n if (state === null) {\n throw new Error(\"Cannot export recovery code before the rally is initialized.\");\n }\n\n return exportProgressToken({\n version: 1,\n rallyId: state.rallyId,\n stamps: state.records,\n rewards: state.rewards ?? [],\n exportedAt: new Date().toISOString(),\n });\n }, [client]);\n\n const importRecoveryCode = useCallback(\n (token: string): Promise<boolean> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n const config = client.getConfig();\n const snapshot = importProgressToken(token, config.id);\n if (snapshot === null) {\n setIsOperationPending(false);\n resolve(false);\n return;\n }\n\n const stampIds = new Set(config.stamps.map((stamp) => stamp.id));\n const rewardIds = new Set((config.rewards ?? []).map((reward) => reward.id));\n const importedStampIds = new Set<string>();\n const importedRewardIds = new Set<string>();\n const stamps = snapshot.stamps.filter((record) => {\n if (!stampIds.has(record.stampId) || importedStampIds.has(record.stampId)) return false;\n importedStampIds.add(record.stampId);\n return true;\n });\n const rewards = snapshot.rewards.filter((state) => {\n if (!rewardIds.has(state.rewardId) || importedRewardIds.has(state.rewardId))\n return false;\n importedRewardIds.add(state.rewardId);\n return true;\n });\n\n try {\n await client.restore({\n rallyId: config.id,\n records: stamps,\n ...(config.rewards === undefined && rewards.length === 0 ? {} : { rewards }),\n updatedAt: snapshot.exportedAt,\n });\n setIsOperationPending(false);\n resolve(true);\n } catch (importError) {\n const normalizedError = toError(importError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client],\n );\n\n const isLoading =\n rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);\n const error = clientError?.client === client ? clientError.value : null;\n\n const flushQueue = useCallback(async (): Promise<void> => {\n if (!isOnline(syncAdapter)) return;\n const pending = queuedRequests.current;\n queuedRequests.current = [];\n for (const request of pending) {\n await acquire(request.stampId, request.context, request.now, request.idempotencyKey);\n }\n }, [acquire, syncAdapter]);\n\n return {\n state: optimisticState,\n isLoading,\n isPending: isPending || isOperationPending,\n error,\n rewardsState: optimisticState?.rewards ?? [],\n acquire,\n reset,\n redeem,\n exportRecoveryCode,\n importRecoveryCode,\n queuedCount: queuedRequests.current.length,\n flushQueue,\n };\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
import { StampRallyState, StampError, RewardConsumeError, RewardState,
|
|
1
|
+
import { VerificationContext, StampRallyState, StampError, RewardConsumeError, RewardState, Result, ProcessStampValue, ConsumeResult, StampRallyClient } from '@stamprally/core';
|
|
2
2
|
|
|
3
|
+
interface CheckInRequest {
|
|
4
|
+
readonly stampId: string;
|
|
5
|
+
readonly context: VerificationContext;
|
|
6
|
+
readonly now: string;
|
|
7
|
+
readonly idempotencyKey: string;
|
|
8
|
+
}
|
|
9
|
+
interface SyncAdapter {
|
|
10
|
+
readonly isOnline?: boolean | (() => boolean);
|
|
11
|
+
readonly onBeforeCheckIn?: (request: CheckInRequest) => unknown;
|
|
12
|
+
readonly onServerVerify?: (request: CheckInRequest) => unknown;
|
|
13
|
+
readonly onStateChange?: (state: StampRallyState) => void;
|
|
14
|
+
}
|
|
15
|
+
interface StampRallyEventHandlers {
|
|
16
|
+
readonly onStampClaimed?: (record: StampRallyState["records"][number]) => void;
|
|
17
|
+
readonly onRewardUnlocked?: (rewardId: string) => void;
|
|
18
|
+
readonly onRewardConsumed?: (rewardId: string) => void;
|
|
19
|
+
}
|
|
20
|
+
interface UseStampRallyOptions {
|
|
21
|
+
readonly syncAdapter?: SyncAdapter;
|
|
22
|
+
readonly events?: StampRallyEventHandlers;
|
|
23
|
+
readonly onStampClaimed?: StampRallyEventHandlers["onStampClaimed"];
|
|
24
|
+
readonly onRewardUnlocked?: StampRallyEventHandlers["onRewardUnlocked"];
|
|
25
|
+
readonly onRewardConsumed?: StampRallyEventHandlers["onRewardConsumed"];
|
|
26
|
+
}
|
|
3
27
|
interface RedeemOptions {
|
|
4
28
|
readonly passcode?: string;
|
|
5
29
|
readonly staffId?: string;
|
|
@@ -10,14 +34,16 @@ interface UseStampRallyReturn {
|
|
|
10
34
|
readonly isPending: boolean;
|
|
11
35
|
readonly error: StampError | RewardConsumeError | Error | null;
|
|
12
36
|
readonly rewardsState: ReadonlyArray<RewardState>;
|
|
13
|
-
readonly acquire: (stampId: string, context: VerificationContext, now?: string) => Promise<Result<ProcessStampValue, StampError>>;
|
|
37
|
+
readonly acquire: (stampId: string, context: VerificationContext, now?: string, idempotencyKey?: string) => Promise<Result<ProcessStampValue, StampError>>;
|
|
14
38
|
readonly reset: (now?: string) => Promise<StampRallyState>;
|
|
15
39
|
readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;
|
|
16
40
|
readonly exportRecoveryCode: () => string;
|
|
17
41
|
readonly importRecoveryCode: (token: string) => Promise<boolean>;
|
|
42
|
+
readonly queuedCount: number;
|
|
43
|
+
readonly flushQueue: () => Promise<void>;
|
|
18
44
|
}
|
|
19
45
|
/** @deprecated Use UseStampRallyReturn instead. */
|
|
20
46
|
type UseStampRallyValue = UseStampRallyReturn;
|
|
21
|
-
declare function useStampRally(client: StampRallyClient): UseStampRallyReturn;
|
|
47
|
+
declare function useStampRally(client: StampRallyClient, options?: UseStampRallyOptions): UseStampRallyReturn;
|
|
22
48
|
|
|
23
|
-
export { type RedeemOptions, type UseStampRallyReturn, type UseStampRallyValue, useStampRally };
|
|
49
|
+
export { type CheckInRequest, type RedeemOptions, type StampRallyEventHandlers, type SyncAdapter, type UseStampRallyOptions, type UseStampRallyReturn, type UseStampRallyValue, useStampRally };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
import { StampRallyState, StampError, RewardConsumeError, RewardState,
|
|
1
|
+
import { VerificationContext, StampRallyState, StampError, RewardConsumeError, RewardState, Result, ProcessStampValue, ConsumeResult, StampRallyClient } from '@stamprally/core';
|
|
2
2
|
|
|
3
|
+
interface CheckInRequest {
|
|
4
|
+
readonly stampId: string;
|
|
5
|
+
readonly context: VerificationContext;
|
|
6
|
+
readonly now: string;
|
|
7
|
+
readonly idempotencyKey: string;
|
|
8
|
+
}
|
|
9
|
+
interface SyncAdapter {
|
|
10
|
+
readonly isOnline?: boolean | (() => boolean);
|
|
11
|
+
readonly onBeforeCheckIn?: (request: CheckInRequest) => unknown;
|
|
12
|
+
readonly onServerVerify?: (request: CheckInRequest) => unknown;
|
|
13
|
+
readonly onStateChange?: (state: StampRallyState) => void;
|
|
14
|
+
}
|
|
15
|
+
interface StampRallyEventHandlers {
|
|
16
|
+
readonly onStampClaimed?: (record: StampRallyState["records"][number]) => void;
|
|
17
|
+
readonly onRewardUnlocked?: (rewardId: string) => void;
|
|
18
|
+
readonly onRewardConsumed?: (rewardId: string) => void;
|
|
19
|
+
}
|
|
20
|
+
interface UseStampRallyOptions {
|
|
21
|
+
readonly syncAdapter?: SyncAdapter;
|
|
22
|
+
readonly events?: StampRallyEventHandlers;
|
|
23
|
+
readonly onStampClaimed?: StampRallyEventHandlers["onStampClaimed"];
|
|
24
|
+
readonly onRewardUnlocked?: StampRallyEventHandlers["onRewardUnlocked"];
|
|
25
|
+
readonly onRewardConsumed?: StampRallyEventHandlers["onRewardConsumed"];
|
|
26
|
+
}
|
|
3
27
|
interface RedeemOptions {
|
|
4
28
|
readonly passcode?: string;
|
|
5
29
|
readonly staffId?: string;
|
|
@@ -10,14 +34,16 @@ interface UseStampRallyReturn {
|
|
|
10
34
|
readonly isPending: boolean;
|
|
11
35
|
readonly error: StampError | RewardConsumeError | Error | null;
|
|
12
36
|
readonly rewardsState: ReadonlyArray<RewardState>;
|
|
13
|
-
readonly acquire: (stampId: string, context: VerificationContext, now?: string) => Promise<Result<ProcessStampValue, StampError>>;
|
|
37
|
+
readonly acquire: (stampId: string, context: VerificationContext, now?: string, idempotencyKey?: string) => Promise<Result<ProcessStampValue, StampError>>;
|
|
14
38
|
readonly reset: (now?: string) => Promise<StampRallyState>;
|
|
15
39
|
readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;
|
|
16
40
|
readonly exportRecoveryCode: () => string;
|
|
17
41
|
readonly importRecoveryCode: (token: string) => Promise<boolean>;
|
|
42
|
+
readonly queuedCount: number;
|
|
43
|
+
readonly flushQueue: () => Promise<void>;
|
|
18
44
|
}
|
|
19
45
|
/** @deprecated Use UseStampRallyReturn instead. */
|
|
20
46
|
type UseStampRallyValue = UseStampRallyReturn;
|
|
21
|
-
declare function useStampRally(client: StampRallyClient): UseStampRallyReturn;
|
|
47
|
+
declare function useStampRally(client: StampRallyClient, options?: UseStampRallyOptions): UseStampRallyReturn;
|
|
22
48
|
|
|
23
|
-
export { type RedeemOptions, type UseStampRallyReturn, type UseStampRallyValue, useStampRally };
|
|
49
|
+
export { type CheckInRequest, type RedeemOptions, type StampRallyEventHandlers, type SyncAdapter, type UseStampRallyOptions, type UseStampRallyReturn, type UseStampRallyValue, useStampRally };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { consumeReward, exportProgressToken, importProgressToken } from '@stamprally/core';
|
|
2
|
-
import { useCallback, useSyncExternalStore, useState, useTransition,
|
|
2
|
+
import { useRef, useCallback, useSyncExternalStore, useState, useTransition, useEffect } from 'react';
|
|
3
3
|
|
|
4
4
|
// src/useStampRally.ts
|
|
5
5
|
function getServerSnapshot() {
|
|
@@ -18,7 +18,26 @@ function applyOptimisticAcquire(currentState, action) {
|
|
|
18
18
|
updatedAt: action.acquiredAt
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
function
|
|
21
|
+
function createIdempotencyKey() {
|
|
22
|
+
const cryptoApi = globalThis.crypto;
|
|
23
|
+
if (cryptoApi !== void 0 && typeof cryptoApi.randomUUID === "function") {
|
|
24
|
+
return cryptoApi.randomUUID();
|
|
25
|
+
}
|
|
26
|
+
return `stamp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
27
|
+
}
|
|
28
|
+
function isOnline(adapter) {
|
|
29
|
+
if (adapter?.isOnline === void 0) {
|
|
30
|
+
return typeof navigator === "undefined" || navigator.onLine !== false;
|
|
31
|
+
}
|
|
32
|
+
return typeof adapter.isOnline === "function" ? adapter.isOnline() : adapter.isOnline;
|
|
33
|
+
}
|
|
34
|
+
function isRejectedVerification(value) {
|
|
35
|
+
return value === false || typeof value === "object" && value !== null && "ok" in value && value.ok === false;
|
|
36
|
+
}
|
|
37
|
+
function useStampRally(client, options = {}) {
|
|
38
|
+
const syncAdapter = options.syncAdapter;
|
|
39
|
+
const events = options.events;
|
|
40
|
+
const queuedRequests = useRef([]);
|
|
22
41
|
const subscribe = useCallback(
|
|
23
42
|
(onStoreChange) => client.subscribe(() => onStoreChange()),
|
|
24
43
|
[client]
|
|
@@ -30,8 +49,16 @@ function useStampRally(client) {
|
|
|
30
49
|
isInitializing: client.getState() === null
|
|
31
50
|
}));
|
|
32
51
|
const [clientError, setClientError] = useState(null);
|
|
33
|
-
const [isPending
|
|
34
|
-
const [
|
|
52
|
+
const [isPending] = useTransition();
|
|
53
|
+
const [isOperationPending, setIsOperationPending] = useState(false);
|
|
54
|
+
const [optimisticState, setOptimisticState] = useState(rawState);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
setOptimisticState(rawState);
|
|
57
|
+
}, [rawState]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (syncAdapter?.onStateChange === void 0) return;
|
|
60
|
+
return client.subscribe(syncAdapter.onStateChange);
|
|
61
|
+
}, [client, syncAdapter]);
|
|
35
62
|
useEffect(() => {
|
|
36
63
|
let active = true;
|
|
37
64
|
setClientError(null);
|
|
@@ -56,51 +83,109 @@ function useStampRally(client) {
|
|
|
56
83
|
};
|
|
57
84
|
}, [client, rawState]);
|
|
58
85
|
const acquire = useCallback(
|
|
59
|
-
(stampId, context, now) => {
|
|
86
|
+
(stampId, context, now, idempotencyKey) => {
|
|
60
87
|
const acquiredAt = now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
88
|
+
const request = {
|
|
89
|
+
stampId,
|
|
90
|
+
context,
|
|
91
|
+
now: acquiredAt,
|
|
92
|
+
idempotencyKey: idempotencyKey ?? createIdempotencyKey()
|
|
93
|
+
};
|
|
61
94
|
setClientError(null);
|
|
95
|
+
if (!isOnline(syncAdapter)) {
|
|
96
|
+
queuedRequests.current = [...queuedRequests.current, request];
|
|
97
|
+
const queued = {
|
|
98
|
+
ok: false,
|
|
99
|
+
error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
|
|
100
|
+
};
|
|
101
|
+
setClientError({ client, value: queued.error });
|
|
102
|
+
return Promise.resolve(queued);
|
|
103
|
+
}
|
|
104
|
+
setIsOperationPending(true);
|
|
105
|
+
setOptimisticState((current) => applyOptimisticAcquire(current, { stampId, acquiredAt }));
|
|
62
106
|
return new Promise((resolve, reject) => {
|
|
63
|
-
|
|
64
|
-
addOptimisticAcquire({ stampId, acquiredAt });
|
|
107
|
+
void (async () => {
|
|
65
108
|
try {
|
|
109
|
+
const before = await syncAdapter?.onBeforeCheckIn?.(request);
|
|
110
|
+
if (before === false) {
|
|
111
|
+
const rejected = {
|
|
112
|
+
ok: false,
|
|
113
|
+
error: { code: "INVALID_PROOF", stampId }
|
|
114
|
+
};
|
|
115
|
+
setClientError({ client, value: rejected.error });
|
|
116
|
+
setIsOperationPending(false);
|
|
117
|
+
resolve(rejected);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
66
120
|
const result = await client.acquire(stampId, context, acquiredAt);
|
|
121
|
+
if (result.ok) {
|
|
122
|
+
const verified = await syncAdapter?.onServerVerify?.(request);
|
|
123
|
+
if (isRejectedVerification(verified)) {
|
|
124
|
+
await client.restore(rawState ?? client.getState() ?? result.value.nextState);
|
|
125
|
+
const rejected = {
|
|
126
|
+
ok: false,
|
|
127
|
+
error: { code: "INVALID_PROOF", stampId }
|
|
128
|
+
};
|
|
129
|
+
setClientError({ client, value: rejected.error });
|
|
130
|
+
setOptimisticState(client.getState());
|
|
131
|
+
setIsOperationPending(false);
|
|
132
|
+
resolve(rejected);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
for (const event of result.value.events) {
|
|
136
|
+
if (event.type === "stampAcquired") {
|
|
137
|
+
(events?.onStampClaimed ?? options.onStampClaimed)?.(event.record);
|
|
138
|
+
}
|
|
139
|
+
if (event.type === "rewardUnlocked") {
|
|
140
|
+
(events?.onRewardUnlocked ?? options.onRewardUnlocked)?.(event.rewardId);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
67
144
|
if (!result.ok) {
|
|
68
145
|
setClientError({ client, value: result.error });
|
|
146
|
+
setOptimisticState(client.getState());
|
|
69
147
|
}
|
|
148
|
+
setIsOperationPending(false);
|
|
70
149
|
resolve(result);
|
|
71
150
|
} catch (acquireError) {
|
|
72
151
|
const normalizedError = toError(acquireError);
|
|
73
152
|
setClientError({ client, value: normalizedError });
|
|
153
|
+
setOptimisticState(client.getState());
|
|
154
|
+
setIsOperationPending(false);
|
|
74
155
|
reject(normalizedError);
|
|
75
156
|
}
|
|
76
|
-
});
|
|
157
|
+
})();
|
|
77
158
|
});
|
|
78
159
|
},
|
|
79
|
-
[
|
|
160
|
+
[client, events, options.onRewardUnlocked, options.onStampClaimed, rawState, syncAdapter]
|
|
80
161
|
);
|
|
81
162
|
const reset = useCallback(
|
|
82
163
|
(now) => {
|
|
83
164
|
setClientError(null);
|
|
165
|
+
setIsOperationPending(true);
|
|
84
166
|
return new Promise((resolve, reject) => {
|
|
85
|
-
|
|
167
|
+
void (async () => {
|
|
86
168
|
try {
|
|
87
169
|
const nextState = now === void 0 ? await client.reset() : await client.reset(now);
|
|
170
|
+
setIsOperationPending(false);
|
|
88
171
|
resolve(nextState);
|
|
89
172
|
} catch (resetError) {
|
|
90
173
|
const normalizedError = toError(resetError);
|
|
91
174
|
setClientError({ client, value: normalizedError });
|
|
175
|
+
setIsOperationPending(false);
|
|
92
176
|
reject(normalizedError);
|
|
93
177
|
}
|
|
94
|
-
});
|
|
178
|
+
})();
|
|
95
179
|
});
|
|
96
180
|
},
|
|
97
181
|
[client]
|
|
98
182
|
);
|
|
99
183
|
const redeem = useCallback(
|
|
100
|
-
(rewardId,
|
|
184
|
+
(rewardId, redeemOptions = {}) => {
|
|
101
185
|
setClientError(null);
|
|
186
|
+
setIsOperationPending(true);
|
|
102
187
|
return new Promise((resolve, reject) => {
|
|
103
|
-
|
|
188
|
+
void (async () => {
|
|
104
189
|
const reward = client.getConfig().rewards?.find((item) => item.id === rewardId);
|
|
105
190
|
if (reward === void 0) {
|
|
106
191
|
const result2 = {
|
|
@@ -108,6 +193,7 @@ function useStampRally(client) {
|
|
|
108
193
|
error: { code: "REWARD_NOT_FOUND", rewardId }
|
|
109
194
|
};
|
|
110
195
|
setClientError({ client, value: result2.error });
|
|
196
|
+
setIsOperationPending(false);
|
|
111
197
|
resolve(result2);
|
|
112
198
|
return;
|
|
113
199
|
}
|
|
@@ -121,6 +207,7 @@ function useStampRally(client) {
|
|
|
121
207
|
error: { code: "NOT_AVAILABLE", rewardId }
|
|
122
208
|
};
|
|
123
209
|
setClientError({ client, value: result2.error });
|
|
210
|
+
setIsOperationPending(false);
|
|
124
211
|
resolve(result2);
|
|
125
212
|
return;
|
|
126
213
|
}
|
|
@@ -128,15 +215,17 @@ function useStampRally(client) {
|
|
|
128
215
|
reward,
|
|
129
216
|
currentState: currentRewardState,
|
|
130
217
|
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
131
|
-
...
|
|
132
|
-
...
|
|
218
|
+
...redeemOptions.passcode === void 0 ? {} : { inputPasscode: redeemOptions.passcode },
|
|
219
|
+
...redeemOptions.staffId === void 0 ? {} : { staffId: redeemOptions.staffId }
|
|
133
220
|
});
|
|
134
221
|
if (!result.ok) {
|
|
135
222
|
setClientError({ client, value: result.error });
|
|
223
|
+
setIsOperationPending(false);
|
|
136
224
|
resolve(result);
|
|
137
225
|
return;
|
|
138
226
|
}
|
|
139
227
|
if (result.value === currentRewardState) {
|
|
228
|
+
setIsOperationPending(false);
|
|
140
229
|
resolve(result);
|
|
141
230
|
return;
|
|
142
231
|
}
|
|
@@ -149,16 +238,19 @@ function useStampRally(client) {
|
|
|
149
238
|
};
|
|
150
239
|
try {
|
|
151
240
|
await client.restore(nextState);
|
|
241
|
+
(events?.onRewardConsumed ?? options.onRewardConsumed)?.(rewardId);
|
|
242
|
+
setIsOperationPending(false);
|
|
152
243
|
resolve(result);
|
|
153
244
|
} catch (redeemError) {
|
|
154
245
|
const normalizedError = toError(redeemError);
|
|
155
246
|
setClientError({ client, value: normalizedError });
|
|
247
|
+
setIsOperationPending(false);
|
|
156
248
|
reject(normalizedError);
|
|
157
249
|
}
|
|
158
|
-
});
|
|
250
|
+
})();
|
|
159
251
|
});
|
|
160
252
|
},
|
|
161
|
-
[client]
|
|
253
|
+
[client, events, options.onRewardConsumed]
|
|
162
254
|
);
|
|
163
255
|
const exportRecoveryCode = useCallback(() => {
|
|
164
256
|
const state = client.getState();
|
|
@@ -176,11 +268,13 @@ function useStampRally(client) {
|
|
|
176
268
|
const importRecoveryCode = useCallback(
|
|
177
269
|
(token) => {
|
|
178
270
|
setClientError(null);
|
|
271
|
+
setIsOperationPending(true);
|
|
179
272
|
return new Promise((resolve, reject) => {
|
|
180
|
-
|
|
273
|
+
void (async () => {
|
|
181
274
|
const config = client.getConfig();
|
|
182
275
|
const snapshot = importProgressToken(token, config.id);
|
|
183
276
|
if (snapshot === null) {
|
|
277
|
+
setIsOperationPending(false);
|
|
184
278
|
resolve(false);
|
|
185
279
|
return;
|
|
186
280
|
}
|
|
@@ -206,30 +300,42 @@ function useStampRally(client) {
|
|
|
206
300
|
...config.rewards === void 0 && rewards.length === 0 ? {} : { rewards },
|
|
207
301
|
updatedAt: snapshot.exportedAt
|
|
208
302
|
});
|
|
303
|
+
setIsOperationPending(false);
|
|
209
304
|
resolve(true);
|
|
210
305
|
} catch (importError) {
|
|
211
306
|
const normalizedError = toError(importError);
|
|
212
307
|
setClientError({ client, value: normalizedError });
|
|
308
|
+
setIsOperationPending(false);
|
|
213
309
|
reject(normalizedError);
|
|
214
310
|
}
|
|
215
|
-
});
|
|
311
|
+
})();
|
|
216
312
|
});
|
|
217
313
|
},
|
|
218
314
|
[client]
|
|
219
315
|
);
|
|
220
316
|
const isLoading = rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);
|
|
221
317
|
const error = clientError?.client === client ? clientError.value : null;
|
|
318
|
+
const flushQueue = useCallback(async () => {
|
|
319
|
+
if (!isOnline(syncAdapter)) return;
|
|
320
|
+
const pending = queuedRequests.current;
|
|
321
|
+
queuedRequests.current = [];
|
|
322
|
+
for (const request of pending) {
|
|
323
|
+
await acquire(request.stampId, request.context, request.now, request.idempotencyKey);
|
|
324
|
+
}
|
|
325
|
+
}, [acquire, syncAdapter]);
|
|
222
326
|
return {
|
|
223
327
|
state: optimisticState,
|
|
224
328
|
isLoading,
|
|
225
|
-
isPending,
|
|
329
|
+
isPending: isPending || isOperationPending,
|
|
226
330
|
error,
|
|
227
331
|
rewardsState: optimisticState?.rewards ?? [],
|
|
228
332
|
acquire,
|
|
229
333
|
reset,
|
|
230
334
|
redeem,
|
|
231
335
|
exportRecoveryCode,
|
|
232
|
-
importRecoveryCode
|
|
336
|
+
importRecoveryCode,
|
|
337
|
+
queuedCount: queuedRequests.current.length,
|
|
338
|
+
flushQueue
|
|
233
339
|
};
|
|
234
340
|
}
|
|
235
341
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useStampRally.ts"],"names":["result"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n ConsumeResult,\n ProcessStampValue,\n Result,\n RewardConsumeError,\n RewardState,\n StampError,\n StampRallyClient,\n StampRallyState,\n VerificationContext,\n} from \"@stamprally/core\";\nimport { consumeReward, exportProgressToken, importProgressToken } from \"@stamprally/core\";\nimport {\n useCallback,\n useEffect,\n useOptimistic,\n useState,\n useSyncExternalStore,\n useTransition,\n} from \"react\";\n\ninterface OptimisticAcquire {\n readonly stampId: string;\n readonly acquiredAt: string;\n}\n\ninterface ClientStatus {\n readonly client: StampRallyClient;\n readonly isInitializing: boolean;\n}\n\ninterface ClientError {\n readonly client: StampRallyClient;\n readonly value: StampError | RewardConsumeError | Error;\n}\n\nexport interface RedeemOptions {\n readonly passcode?: string;\n readonly staffId?: string;\n}\n\nexport interface UseStampRallyReturn {\n readonly state: StampRallyState | null;\n readonly isLoading: boolean;\n readonly isPending: boolean;\n readonly error: StampError | RewardConsumeError | Error | null;\n readonly rewardsState: ReadonlyArray<RewardState>;\n readonly acquire: (\n stampId: string,\n context: VerificationContext,\n now?: string,\n ) => Promise<Result<ProcessStampValue, StampError>>;\n readonly reset: (now?: string) => Promise<StampRallyState>;\n readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;\n readonly exportRecoveryCode: () => string;\n readonly importRecoveryCode: (token: string) => Promise<boolean>;\n}\n\n/** @deprecated Use UseStampRallyReturn instead. */\nexport type UseStampRallyValue = UseStampRallyReturn;\n\nfunction getServerSnapshot(): null {\n return null;\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction applyOptimisticAcquire(\n currentState: StampRallyState | null,\n action: OptimisticAcquire,\n): StampRallyState | null {\n if (\n currentState === null ||\n currentState.records.some((record) => record.stampId === action.stampId)\n ) {\n return currentState;\n }\n\n return {\n ...currentState,\n records: [...currentState.records, { stampId: action.stampId, acquiredAt: action.acquiredAt }],\n updatedAt: action.acquiredAt,\n };\n}\n\nexport function useStampRally(client: StampRallyClient): UseStampRallyReturn {\n const subscribe = useCallback(\n (onStoreChange: () => void) => client.subscribe(() => onStoreChange()),\n [client],\n );\n const getSnapshot = useCallback(() => client.getState(), [client]);\n const rawState = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n const [clientStatus, setClientStatus] = useState<ClientStatus>(() => ({\n client,\n isInitializing: client.getState() === null,\n }));\n const [clientError, setClientError] = useState<ClientError | null>(null);\n const [isPending, startTransition] = useTransition();\n const [optimisticState, addOptimisticAcquire] = useOptimistic(rawState, applyOptimisticAcquire);\n\n useEffect(() => {\n let active = true;\n setClientError(null);\n if (rawState !== null) {\n setClientStatus({ client, isInitializing: false });\n return () => {\n active = false;\n };\n }\n\n setClientStatus({ client, isInitializing: true });\n\n void client\n .init()\n .catch((initializationError: unknown) => {\n if (active) {\n setClientError({ client, value: toError(initializationError) });\n }\n })\n .finally(() => {\n if (active) {\n setClientStatus({ client, isInitializing: false });\n }\n });\n\n return () => {\n active = false;\n };\n }, [client, rawState]);\n\n const acquire = useCallback(\n (\n stampId: string,\n context: VerificationContext,\n now?: string,\n ): Promise<Result<ProcessStampValue, StampError>> => {\n const acquiredAt = now ?? new Date().toISOString();\n setClientError(null);\n\n return new Promise((resolve, reject) => {\n startTransition(async () => {\n addOptimisticAcquire({ stampId, acquiredAt });\n try {\n const result = await client.acquire(stampId, context, acquiredAt);\n if (!result.ok) {\n setClientError({ client, value: result.error });\n }\n resolve(result);\n } catch (acquireError) {\n const normalizedError = toError(acquireError);\n setClientError({ client, value: normalizedError });\n reject(normalizedError);\n }\n });\n });\n },\n [addOptimisticAcquire, client],\n );\n\n const reset = useCallback(\n (now?: string): Promise<StampRallyState> => {\n setClientError(null);\n\n return new Promise((resolve, reject) => {\n startTransition(async () => {\n try {\n const nextState = now === undefined ? await client.reset() : await client.reset(now);\n resolve(nextState);\n } catch (resetError) {\n const normalizedError = toError(resetError);\n setClientError({ client, value: normalizedError });\n reject(normalizedError);\n }\n });\n });\n },\n [client],\n );\n\n const redeem = useCallback(\n (rewardId: string, options: RedeemOptions = {}): Promise<ConsumeResult> => {\n setClientError(null);\n\n return new Promise((resolve, reject) => {\n startTransition(async () => {\n const reward = client.getConfig().rewards?.find((item) => item.id === rewardId);\n if (reward === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"REWARD_NOT_FOUND\", rewardId },\n };\n setClientError({ client, value: result.error });\n resolve(result);\n return;\n }\n\n const currentState = client.getState();\n const currentRewardState = currentState?.rewards?.find(\n (state) => state.rewardId === rewardId,\n );\n if (currentState === null || currentRewardState === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"NOT_AVAILABLE\", rewardId },\n };\n setClientError({ client, value: result.error });\n resolve(result);\n return;\n }\n\n const result = consumeReward({\n reward,\n currentState: currentRewardState,\n now: new Date().toISOString(),\n ...(options.passcode === undefined ? {} : { inputPasscode: options.passcode }),\n ...(options.staffId === undefined ? {} : { staffId: options.staffId }),\n });\n if (!result.ok) {\n setClientError({ client, value: result.error });\n resolve(result);\n return;\n }\n\n if (result.value === currentRewardState) {\n resolve(result);\n return;\n }\n\n const nextState: StampRallyState = {\n ...currentState,\n rewards: (currentState.rewards ?? []).map((state) =>\n state.rewardId === rewardId ? result.value : state,\n ),\n updatedAt: result.value.consumedAt ?? currentState.updatedAt,\n };\n\n try {\n await client.restore(nextState);\n resolve(result);\n } catch (redeemError) {\n const normalizedError = toError(redeemError);\n setClientError({ client, value: normalizedError });\n reject(normalizedError);\n }\n });\n });\n },\n [client],\n );\n\n const exportRecoveryCode = useCallback((): string => {\n const state = client.getState();\n if (state === null) {\n throw new Error(\"Cannot export recovery code before the rally is initialized.\");\n }\n\n return exportProgressToken({\n version: 1,\n rallyId: state.rallyId,\n stamps: state.records,\n rewards: state.rewards ?? [],\n exportedAt: new Date().toISOString(),\n });\n }, [client]);\n\n const importRecoveryCode = useCallback(\n (token: string): Promise<boolean> => {\n setClientError(null);\n\n return new Promise((resolve, reject) => {\n startTransition(async () => {\n const config = client.getConfig();\n const snapshot = importProgressToken(token, config.id);\n if (snapshot === null) {\n resolve(false);\n return;\n }\n\n const stampIds = new Set(config.stamps.map((stamp) => stamp.id));\n const rewardIds = new Set((config.rewards ?? []).map((reward) => reward.id));\n const importedStampIds = new Set<string>();\n const importedRewardIds = new Set<string>();\n const stamps = snapshot.stamps.filter((record) => {\n if (!stampIds.has(record.stampId) || importedStampIds.has(record.stampId)) return false;\n importedStampIds.add(record.stampId);\n return true;\n });\n const rewards = snapshot.rewards.filter((state) => {\n if (!rewardIds.has(state.rewardId) || importedRewardIds.has(state.rewardId))\n return false;\n importedRewardIds.add(state.rewardId);\n return true;\n });\n\n try {\n await client.restore({\n rallyId: config.id,\n records: stamps,\n ...(config.rewards === undefined && rewards.length === 0 ? {} : { rewards }),\n updatedAt: snapshot.exportedAt,\n });\n resolve(true);\n } catch (importError) {\n const normalizedError = toError(importError);\n setClientError({ client, value: normalizedError });\n reject(normalizedError);\n }\n });\n });\n },\n [client],\n );\n\n const isLoading =\n rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);\n const error = clientError?.client === client ? clientError.value : null;\n\n return {\n state: optimisticState,\n isLoading,\n isPending,\n error,\n rewardsState: optimisticState?.rewards ?? [],\n acquire,\n reset,\n redeem,\n exportRecoveryCode,\n importRecoveryCode,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/useStampRally.ts"],"names":["result"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n ConsumeResult,\n ProcessStampValue,\n Result,\n RewardConsumeError,\n RewardState,\n StampError,\n StampRallyClient,\n StampRallyState,\n VerificationContext,\n} from \"@stamprally/core\";\nimport { consumeReward, exportProgressToken, importProgressToken } from \"@stamprally/core\";\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n useTransition,\n} from \"react\";\n\ninterface OptimisticAcquire {\n readonly stampId: string;\n readonly acquiredAt: string;\n}\n\ninterface ClientStatus {\n readonly client: StampRallyClient;\n readonly isInitializing: boolean;\n}\n\ninterface ClientError {\n readonly client: StampRallyClient;\n readonly value: StampError | RewardConsumeError | Error;\n}\n\nexport interface CheckInRequest {\n readonly stampId: string;\n readonly context: VerificationContext;\n readonly now: string;\n readonly idempotencyKey: string;\n}\n\nexport interface SyncAdapter {\n readonly isOnline?: boolean | (() => boolean);\n readonly onBeforeCheckIn?: (request: CheckInRequest) => unknown;\n readonly onServerVerify?: (request: CheckInRequest) => unknown;\n readonly onStateChange?: (state: StampRallyState) => void;\n}\n\nexport interface StampRallyEventHandlers {\n readonly onStampClaimed?: (record: StampRallyState[\"records\"][number]) => void;\n readonly onRewardUnlocked?: (rewardId: string) => void;\n readonly onRewardConsumed?: (rewardId: string) => void;\n}\n\nexport interface UseStampRallyOptions {\n readonly syncAdapter?: SyncAdapter;\n readonly events?: StampRallyEventHandlers;\n readonly onStampClaimed?: StampRallyEventHandlers[\"onStampClaimed\"];\n readonly onRewardUnlocked?: StampRallyEventHandlers[\"onRewardUnlocked\"];\n readonly onRewardConsumed?: StampRallyEventHandlers[\"onRewardConsumed\"];\n}\n\nexport interface RedeemOptions {\n readonly passcode?: string;\n readonly staffId?: string;\n}\n\nexport interface UseStampRallyReturn {\n readonly state: StampRallyState | null;\n readonly isLoading: boolean;\n readonly isPending: boolean;\n readonly error: StampError | RewardConsumeError | Error | null;\n readonly rewardsState: ReadonlyArray<RewardState>;\n readonly acquire: (\n stampId: string,\n context: VerificationContext,\n now?: string,\n idempotencyKey?: string,\n ) => Promise<Result<ProcessStampValue, StampError>>;\n readonly reset: (now?: string) => Promise<StampRallyState>;\n readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;\n readonly exportRecoveryCode: () => string;\n readonly importRecoveryCode: (token: string) => Promise<boolean>;\n readonly queuedCount: number;\n readonly flushQueue: () => Promise<void>;\n}\n\n/** @deprecated Use UseStampRallyReturn instead. */\nexport type UseStampRallyValue = UseStampRallyReturn;\n\nfunction getServerSnapshot(): null {\n return null;\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction applyOptimisticAcquire(\n currentState: StampRallyState | null,\n action: OptimisticAcquire,\n): StampRallyState | null {\n if (\n currentState === null ||\n currentState.records.some((record) => record.stampId === action.stampId)\n ) {\n return currentState;\n }\n\n return {\n ...currentState,\n records: [...currentState.records, { stampId: action.stampId, acquiredAt: action.acquiredAt }],\n updatedAt: action.acquiredAt,\n };\n}\n\nfunction createIdempotencyKey(): string {\n const cryptoApi = globalThis.crypto;\n if (cryptoApi !== undefined && typeof cryptoApi.randomUUID === \"function\") {\n return cryptoApi.randomUUID();\n }\n return `stamp-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction isOnline(adapter: SyncAdapter | undefined): boolean {\n if (adapter?.isOnline === undefined) {\n return typeof navigator === \"undefined\" || navigator.onLine !== false;\n }\n return typeof adapter.isOnline === \"function\" ? adapter.isOnline() : adapter.isOnline;\n}\n\nfunction isRejectedVerification(value: unknown): boolean {\n return (\n value === false ||\n (typeof value === \"object\" && value !== null && \"ok\" in value && value.ok === false)\n );\n}\n\nexport function useStampRally(\n client: StampRallyClient,\n options: UseStampRallyOptions = {},\n): UseStampRallyReturn {\n const syncAdapter = options.syncAdapter;\n const events = options.events;\n const queuedRequests = useRef<ReadonlyArray<CheckInRequest>>([]);\n const subscribe = useCallback(\n (onStoreChange: () => void) => client.subscribe(() => onStoreChange()),\n [client],\n );\n const getSnapshot = useCallback(() => client.getState(), [client]);\n const rawState = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n const [clientStatus, setClientStatus] = useState<ClientStatus>(() => ({\n client,\n isInitializing: client.getState() === null,\n }));\n const [clientError, setClientError] = useState<ClientError | null>(null);\n const [isPending] = useTransition();\n const [isOperationPending, setIsOperationPending] = useState(false);\n const [optimisticState, setOptimisticState] = useState<StampRallyState | null>(rawState);\n\n useEffect(() => {\n setOptimisticState(rawState);\n }, [rawState]);\n\n useEffect(() => {\n if (syncAdapter?.onStateChange === undefined) return;\n return client.subscribe(syncAdapter.onStateChange);\n }, [client, syncAdapter]);\n\n useEffect(() => {\n let active = true;\n setClientError(null);\n if (rawState !== null) {\n setClientStatus({ client, isInitializing: false });\n return () => {\n active = false;\n };\n }\n\n setClientStatus({ client, isInitializing: true });\n\n void client\n .init()\n .catch((initializationError: unknown) => {\n if (active) {\n setClientError({ client, value: toError(initializationError) });\n }\n })\n .finally(() => {\n if (active) {\n setClientStatus({ client, isInitializing: false });\n }\n });\n\n return () => {\n active = false;\n };\n }, [client, rawState]);\n\n const acquire = useCallback(\n (\n stampId: string,\n context: VerificationContext,\n now?: string,\n idempotencyKey?: string,\n ): Promise<Result<ProcessStampValue, StampError>> => {\n const acquiredAt = now ?? new Date().toISOString();\n const request: CheckInRequest = {\n stampId,\n context,\n now: acquiredAt,\n idempotencyKey: idempotencyKey ?? createIdempotencyKey(),\n };\n setClientError(null);\n\n if (!isOnline(syncAdapter)) {\n queuedRequests.current = [...queuedRequests.current, request];\n const queued: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"OFFLINE_QUEUED\", stampId, idempotencyKey: request.idempotencyKey },\n };\n setClientError({ client, value: queued.error });\n return Promise.resolve(queued);\n }\n\n setIsOperationPending(true);\n setOptimisticState((current) => applyOptimisticAcquire(current, { stampId, acquiredAt }));\n return new Promise((resolve, reject) => {\n void (async () => {\n try {\n const before = await syncAdapter?.onBeforeCheckIn?.(request);\n if (before === false) {\n const rejected: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"INVALID_PROOF\", stampId },\n };\n setClientError({ client, value: rejected.error });\n setIsOperationPending(false);\n resolve(rejected);\n return;\n }\n const result = await client.acquire(stampId, context, acquiredAt);\n if (result.ok) {\n const verified = await syncAdapter?.onServerVerify?.(request);\n if (isRejectedVerification(verified)) {\n await client.restore(rawState ?? client.getState() ?? result.value.nextState);\n const rejected: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"INVALID_PROOF\", stampId },\n };\n setClientError({ client, value: rejected.error });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(rejected);\n return;\n }\n for (const event of result.value.events) {\n if (event.type === \"stampAcquired\") {\n (events?.onStampClaimed ?? options.onStampClaimed)?.(event.record);\n }\n if (event.type === \"rewardUnlocked\") {\n (events?.onRewardUnlocked ?? options.onRewardUnlocked)?.(event.rewardId);\n }\n }\n }\n if (!result.ok) {\n setClientError({ client, value: result.error });\n setOptimisticState(client.getState());\n }\n setIsOperationPending(false);\n resolve(result);\n } catch (acquireError) {\n const normalizedError = toError(acquireError);\n setClientError({ client, value: normalizedError });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client, events, options.onRewardUnlocked, options.onStampClaimed, rawState, syncAdapter],\n );\n\n const reset = useCallback(\n (now?: string): Promise<StampRallyState> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n try {\n const nextState = now === undefined ? await client.reset() : await client.reset(now);\n setIsOperationPending(false);\n resolve(nextState);\n } catch (resetError) {\n const normalizedError = toError(resetError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client],\n );\n\n const redeem = useCallback(\n (rewardId: string, redeemOptions: RedeemOptions = {}): Promise<ConsumeResult> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n const reward = client.getConfig().rewards?.find((item) => item.id === rewardId);\n if (reward === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"REWARD_NOT_FOUND\", rewardId },\n };\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const currentState = client.getState();\n const currentRewardState = currentState?.rewards?.find(\n (state) => state.rewardId === rewardId,\n );\n if (currentState === null || currentRewardState === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"NOT_AVAILABLE\", rewardId },\n };\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const result = consumeReward({\n reward,\n currentState: currentRewardState,\n now: new Date().toISOString(),\n ...(redeemOptions.passcode === undefined\n ? {}\n : { inputPasscode: redeemOptions.passcode }),\n ...(redeemOptions.staffId === undefined ? {} : { staffId: redeemOptions.staffId }),\n });\n if (!result.ok) {\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n if (result.value === currentRewardState) {\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const nextState: StampRallyState = {\n ...currentState,\n rewards: (currentState.rewards ?? []).map((state) =>\n state.rewardId === rewardId ? result.value : state,\n ),\n updatedAt: result.value.consumedAt ?? currentState.updatedAt,\n };\n\n try {\n await client.restore(nextState);\n (events?.onRewardConsumed ?? options.onRewardConsumed)?.(rewardId);\n setIsOperationPending(false);\n resolve(result);\n } catch (redeemError) {\n const normalizedError = toError(redeemError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client, events, options.onRewardConsumed],\n );\n\n const exportRecoveryCode = useCallback((): string => {\n const state = client.getState();\n if (state === null) {\n throw new Error(\"Cannot export recovery code before the rally is initialized.\");\n }\n\n return exportProgressToken({\n version: 1,\n rallyId: state.rallyId,\n stamps: state.records,\n rewards: state.rewards ?? [],\n exportedAt: new Date().toISOString(),\n });\n }, [client]);\n\n const importRecoveryCode = useCallback(\n (token: string): Promise<boolean> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n const config = client.getConfig();\n const snapshot = importProgressToken(token, config.id);\n if (snapshot === null) {\n setIsOperationPending(false);\n resolve(false);\n return;\n }\n\n const stampIds = new Set(config.stamps.map((stamp) => stamp.id));\n const rewardIds = new Set((config.rewards ?? []).map((reward) => reward.id));\n const importedStampIds = new Set<string>();\n const importedRewardIds = new Set<string>();\n const stamps = snapshot.stamps.filter((record) => {\n if (!stampIds.has(record.stampId) || importedStampIds.has(record.stampId)) return false;\n importedStampIds.add(record.stampId);\n return true;\n });\n const rewards = snapshot.rewards.filter((state) => {\n if (!rewardIds.has(state.rewardId) || importedRewardIds.has(state.rewardId))\n return false;\n importedRewardIds.add(state.rewardId);\n return true;\n });\n\n try {\n await client.restore({\n rallyId: config.id,\n records: stamps,\n ...(config.rewards === undefined && rewards.length === 0 ? {} : { rewards }),\n updatedAt: snapshot.exportedAt,\n });\n setIsOperationPending(false);\n resolve(true);\n } catch (importError) {\n const normalizedError = toError(importError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client],\n );\n\n const isLoading =\n rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);\n const error = clientError?.client === client ? clientError.value : null;\n\n const flushQueue = useCallback(async (): Promise<void> => {\n if (!isOnline(syncAdapter)) return;\n const pending = queuedRequests.current;\n queuedRequests.current = [];\n for (const request of pending) {\n await acquire(request.stampId, request.context, request.now, request.idempotencyKey);\n }\n }, [acquire, syncAdapter]);\n\n return {\n state: optimisticState,\n isLoading,\n isPending: isPending || isOperationPending,\n error,\n rewardsState: optimisticState?.rewards ?? [],\n acquire,\n reset,\n redeem,\n exportRecoveryCode,\n importRecoveryCode,\n queuedCount: queuedRequests.current.length,\n flushQueue,\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamprally/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React hooks for @stamprally/core.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -31,10 +31,11 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@stamprally/core": "0.
|
|
34
|
+
"@stamprally/core": "0.2.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"react": "
|
|
37
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
38
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@testing-library/dom": "^10.4.1",
|