@stamprally/react 0.2.1 → 0.4.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/dist/index.cjs +165 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +166 -20
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -36,10 +36,28 @@ function isOnline(adapter) {
|
|
|
36
36
|
function isRejectedVerification(value) {
|
|
37
37
|
return value === false || typeof value === "object" && value !== null && "ok" in value && value.ok === false;
|
|
38
38
|
}
|
|
39
|
+
function isNetworkFailure(error) {
|
|
40
|
+
if (typeof navigator !== "undefined" && navigator.onLine === false) return true;
|
|
41
|
+
if (!(error instanceof Error)) return true;
|
|
42
|
+
return /aborted|connection|fetch|network|offline|timeout/i.test(`${error.name} ${error.message}`);
|
|
43
|
+
}
|
|
44
|
+
function notifyAcquisitionEvents(result, events, onStampClaimed, onRewardUnlocked) {
|
|
45
|
+
for (const event of result.events) {
|
|
46
|
+
if (event.type === "stampAcquired") {
|
|
47
|
+
(events?.onStampClaimed ?? onStampClaimed)?.(event.record);
|
|
48
|
+
}
|
|
49
|
+
if (event.type === "rewardUnlocked") {
|
|
50
|
+
(events?.onRewardUnlocked ?? onRewardUnlocked)?.(event.rewardId);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
39
54
|
function useStampRally(client, options = {}) {
|
|
40
55
|
const syncAdapter = options.syncAdapter;
|
|
41
56
|
const events = options.events;
|
|
42
|
-
const
|
|
57
|
+
const [offlineQueue, setOfflineQueue] = react.useState([]);
|
|
58
|
+
const queuedMetadata = react.useRef(/* @__PURE__ */ new Map());
|
|
59
|
+
const isFlushingQueue = react.useRef(false);
|
|
60
|
+
const activeClient = react.useRef(client);
|
|
43
61
|
const subscribe = react.useCallback(
|
|
44
62
|
(onStoreChange) => client.subscribe(() => onStoreChange()),
|
|
45
63
|
[client]
|
|
@@ -57,6 +75,12 @@ function useStampRally(client, options = {}) {
|
|
|
57
75
|
react.useEffect(() => {
|
|
58
76
|
setOptimisticState(rawState);
|
|
59
77
|
}, [rawState]);
|
|
78
|
+
react.useEffect(() => {
|
|
79
|
+
if (activeClient.current === client) return;
|
|
80
|
+
activeClient.current = client;
|
|
81
|
+
setOfflineQueue([]);
|
|
82
|
+
queuedMetadata.current.clear();
|
|
83
|
+
}, [client]);
|
|
60
84
|
react.useEffect(() => {
|
|
61
85
|
if (syncAdapter?.onStateChange === void 0) return;
|
|
62
86
|
return client.subscribe(syncAdapter.onStateChange);
|
|
@@ -84,6 +108,15 @@ function useStampRally(client, options = {}) {
|
|
|
84
108
|
active = false;
|
|
85
109
|
};
|
|
86
110
|
}, [client, rawState]);
|
|
111
|
+
const queueRequest = react.useCallback(
|
|
112
|
+
(request, metadata) => {
|
|
113
|
+
if (metadata !== void 0) queuedMetadata.current.set(request.idempotencyKey, metadata);
|
|
114
|
+
setOfflineQueue(
|
|
115
|
+
(current) => current.some((item) => item.idempotencyKey === request.idempotencyKey) ? current : [...current, request]
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
[]
|
|
119
|
+
);
|
|
87
120
|
const acquire = react.useCallback(
|
|
88
121
|
(stampId, context, now, idempotencyKey) => {
|
|
89
122
|
const acquiredAt = now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -95,7 +128,7 @@ function useStampRally(client, options = {}) {
|
|
|
95
128
|
};
|
|
96
129
|
setClientError(null);
|
|
97
130
|
if (!isOnline(syncAdapter)) {
|
|
98
|
-
|
|
131
|
+
queueRequest(request);
|
|
99
132
|
const queued = {
|
|
100
133
|
ok: false,
|
|
101
134
|
error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
|
|
@@ -119,9 +152,29 @@ function useStampRally(client, options = {}) {
|
|
|
119
152
|
resolve(rejected);
|
|
120
153
|
return;
|
|
121
154
|
}
|
|
155
|
+
const previousState = client.getState();
|
|
122
156
|
const result = await client.acquire(stampId, context, acquiredAt);
|
|
123
157
|
if (result.ok) {
|
|
124
|
-
|
|
158
|
+
let verified;
|
|
159
|
+
try {
|
|
160
|
+
verified = await syncAdapter?.onServerVerify?.(request);
|
|
161
|
+
} catch (verificationError) {
|
|
162
|
+
if (!isNetworkFailure(verificationError)) throw verificationError;
|
|
163
|
+
queueRequest(request, { previousState, result: result.value });
|
|
164
|
+
const queued = {
|
|
165
|
+
ok: false,
|
|
166
|
+
error: {
|
|
167
|
+
code: "OFFLINE_QUEUED",
|
|
168
|
+
stampId,
|
|
169
|
+
idempotencyKey: request.idempotencyKey
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
setClientError({ client, value: toError(verificationError) });
|
|
173
|
+
setOptimisticState(client.getState());
|
|
174
|
+
setIsOperationPending(false);
|
|
175
|
+
resolve(queued);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
125
178
|
if (isRejectedVerification(verified)) {
|
|
126
179
|
await client.restore(rawState ?? client.getState() ?? result.value.nextState);
|
|
127
180
|
const rejected = {
|
|
@@ -134,14 +187,12 @@ function useStampRally(client, options = {}) {
|
|
|
134
187
|
resolve(rejected);
|
|
135
188
|
return;
|
|
136
189
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
}
|
|
190
|
+
notifyAcquisitionEvents(
|
|
191
|
+
result.value,
|
|
192
|
+
events,
|
|
193
|
+
options.onStampClaimed,
|
|
194
|
+
options.onRewardUnlocked
|
|
195
|
+
);
|
|
145
196
|
}
|
|
146
197
|
if (!result.ok) {
|
|
147
198
|
setClientError({ client, value: result.error });
|
|
@@ -150,6 +201,18 @@ function useStampRally(client, options = {}) {
|
|
|
150
201
|
setIsOperationPending(false);
|
|
151
202
|
resolve(result);
|
|
152
203
|
} catch (acquireError) {
|
|
204
|
+
if (isNetworkFailure(acquireError)) {
|
|
205
|
+
queueRequest(request);
|
|
206
|
+
const queued = {
|
|
207
|
+
ok: false,
|
|
208
|
+
error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
|
|
209
|
+
};
|
|
210
|
+
setClientError({ client, value: queued.error });
|
|
211
|
+
setOptimisticState(client.getState());
|
|
212
|
+
setIsOperationPending(false);
|
|
213
|
+
resolve(queued);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
153
216
|
const normalizedError = toError(acquireError);
|
|
154
217
|
setClientError({ client, value: normalizedError });
|
|
155
218
|
setOptimisticState(client.getState());
|
|
@@ -159,7 +222,15 @@ function useStampRally(client, options = {}) {
|
|
|
159
222
|
})();
|
|
160
223
|
});
|
|
161
224
|
},
|
|
162
|
-
[
|
|
225
|
+
[
|
|
226
|
+
client,
|
|
227
|
+
events,
|
|
228
|
+
options.onRewardUnlocked,
|
|
229
|
+
options.onStampClaimed,
|
|
230
|
+
queueRequest,
|
|
231
|
+
rawState,
|
|
232
|
+
syncAdapter
|
|
233
|
+
]
|
|
163
234
|
);
|
|
164
235
|
const reset = react.useCallback(
|
|
165
236
|
(now) => {
|
|
@@ -318,13 +389,87 @@ function useStampRally(client, options = {}) {
|
|
|
318
389
|
const isLoading = rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);
|
|
319
390
|
const error = clientError?.client === client ? clientError.value : null;
|
|
320
391
|
const flushQueue = react.useCallback(async () => {
|
|
321
|
-
if (!isOnline(syncAdapter)) return;
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
392
|
+
if (!isOnline(syncAdapter) || isFlushingQueue.current) return;
|
|
393
|
+
isFlushingQueue.current = true;
|
|
394
|
+
setIsOperationPending(true);
|
|
395
|
+
try {
|
|
396
|
+
for (const request of offlineQueue) {
|
|
397
|
+
if (!isOnline(syncAdapter)) break;
|
|
398
|
+
const metadata = queuedMetadata.current.get(request.idempotencyKey);
|
|
399
|
+
const previousState = metadata?.previousState ?? client.getState();
|
|
400
|
+
try {
|
|
401
|
+
const before = await syncAdapter?.onBeforeCheckIn?.(request);
|
|
402
|
+
if (before === false) {
|
|
403
|
+
if (metadata !== void 0 && previousState !== null)
|
|
404
|
+
await client.restore(previousState);
|
|
405
|
+
setClientError({ client, value: { code: "INVALID_PROOF", stampId: request.stampId } });
|
|
406
|
+
queuedMetadata.current.delete(request.idempotencyKey);
|
|
407
|
+
setOfflineQueue(
|
|
408
|
+
(current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
|
|
409
|
+
);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
let result = metadata?.result;
|
|
413
|
+
if (result === void 0) {
|
|
414
|
+
const localResult = await client.acquire(request.stampId, request.context, request.now);
|
|
415
|
+
if (!localResult.ok) {
|
|
416
|
+
setClientError({ client, value: localResult.error });
|
|
417
|
+
queuedMetadata.current.delete(request.idempotencyKey);
|
|
418
|
+
setOfflineQueue(
|
|
419
|
+
(current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
|
|
420
|
+
);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
result = localResult.value;
|
|
424
|
+
queuedMetadata.current.set(request.idempotencyKey, {
|
|
425
|
+
previousState,
|
|
426
|
+
result
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
const verified = await syncAdapter?.onServerVerify?.(request);
|
|
430
|
+
if (isRejectedVerification(verified)) {
|
|
431
|
+
if (previousState !== null) await client.restore(previousState);
|
|
432
|
+
setClientError({
|
|
433
|
+
client,
|
|
434
|
+
value: { code: "INVALID_PROOF", stampId: request.stampId }
|
|
435
|
+
});
|
|
436
|
+
} else {
|
|
437
|
+
notifyAcquisitionEvents(
|
|
438
|
+
result,
|
|
439
|
+
events,
|
|
440
|
+
options.onStampClaimed,
|
|
441
|
+
options.onRewardUnlocked
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
queuedMetadata.current.delete(request.idempotencyKey);
|
|
445
|
+
setOfflineQueue(
|
|
446
|
+
(current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
|
|
447
|
+
);
|
|
448
|
+
} catch (flushError) {
|
|
449
|
+
if (isNetworkFailure(flushError)) {
|
|
450
|
+
setClientError({ client, value: toError(flushError) });
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
setClientError({ client, value: toError(flushError) });
|
|
454
|
+
queuedMetadata.current.delete(request.idempotencyKey);
|
|
455
|
+
setOfflineQueue(
|
|
456
|
+
(current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} finally {
|
|
461
|
+
isFlushingQueue.current = false;
|
|
462
|
+
setIsOperationPending(false);
|
|
326
463
|
}
|
|
327
|
-
}, [
|
|
464
|
+
}, [client, events, offlineQueue, options.onRewardUnlocked, options.onStampClaimed, syncAdapter]);
|
|
465
|
+
react.useEffect(() => {
|
|
466
|
+
if (typeof window === "undefined") return;
|
|
467
|
+
const handleOnline = () => {
|
|
468
|
+
void flushQueue();
|
|
469
|
+
};
|
|
470
|
+
window.addEventListener("online", handleOnline);
|
|
471
|
+
return () => window.removeEventListener("online", handleOnline);
|
|
472
|
+
}, [flushQueue]);
|
|
328
473
|
return {
|
|
329
474
|
state: optimisticState,
|
|
330
475
|
isLoading,
|
|
@@ -336,7 +481,8 @@ function useStampRally(client, options = {}) {
|
|
|
336
481
|
redeem,
|
|
337
482
|
exportRecoveryCode,
|
|
338
483
|
importRecoveryCode,
|
|
339
|
-
|
|
484
|
+
offlineQueue,
|
|
485
|
+
queuedCount: offlineQueue.length,
|
|
340
486
|
flushQueue
|
|
341
487
|
};
|
|
342
488
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
1
|
+
{"version":3,"sources":["../src/useStampRally.ts"],"names":["useState","useRef","useCallback","useSyncExternalStore","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\ninterface QueuedRequestMetadata {\n readonly previousState: StampRallyState | null;\n readonly result: ProcessStampValue;\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 offlineQueue: ReadonlyArray<CheckInRequest>;\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\nfunction isNetworkFailure(error: unknown): boolean {\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) return true;\n if (!(error instanceof Error)) return true;\n return /aborted|connection|fetch|network|offline|timeout/i.test(`${error.name} ${error.message}`);\n}\n\nfunction notifyAcquisitionEvents(\n result: ProcessStampValue,\n events: StampRallyEventHandlers | undefined,\n onStampClaimed: StampRallyEventHandlers[\"onStampClaimed\"] | undefined,\n onRewardUnlocked: StampRallyEventHandlers[\"onRewardUnlocked\"] | undefined,\n): void {\n for (const event of result.events) {\n if (event.type === \"stampAcquired\") {\n (events?.onStampClaimed ?? onStampClaimed)?.(event.record);\n }\n if (event.type === \"rewardUnlocked\") {\n (events?.onRewardUnlocked ?? onRewardUnlocked)?.(event.rewardId);\n }\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 [offlineQueue, setOfflineQueue] = useState<ReadonlyArray<CheckInRequest>>([]);\n const queuedMetadata = useRef(new Map<string, QueuedRequestMetadata>());\n const isFlushingQueue = useRef(false);\n const activeClient = useRef(client);\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 (activeClient.current === client) return;\n activeClient.current = client;\n setOfflineQueue([]);\n queuedMetadata.current.clear();\n }, [client]);\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 queueRequest = useCallback(\n (request: CheckInRequest, metadata?: QueuedRequestMetadata): void => {\n if (metadata !== undefined) queuedMetadata.current.set(request.idempotencyKey, metadata);\n setOfflineQueue((current) =>\n current.some((item) => item.idempotencyKey === request.idempotencyKey)\n ? current\n : [...current, request],\n );\n },\n [],\n );\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 queueRequest(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 previousState = client.getState();\n const result = await client.acquire(stampId, context, acquiredAt);\n if (result.ok) {\n let verified: unknown;\n try {\n verified = await syncAdapter?.onServerVerify?.(request);\n } catch (verificationError) {\n if (!isNetworkFailure(verificationError)) throw verificationError;\n queueRequest(request, { previousState, result: result.value });\n const queued: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: {\n code: \"OFFLINE_QUEUED\",\n stampId,\n idempotencyKey: request.idempotencyKey,\n },\n };\n setClientError({ client, value: toError(verificationError) });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(queued);\n return;\n }\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 notifyAcquisitionEvents(\n result.value,\n events,\n options.onStampClaimed,\n options.onRewardUnlocked,\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 if (isNetworkFailure(acquireError)) {\n queueRequest(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 setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(queued);\n return;\n }\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 [\n client,\n events,\n options.onRewardUnlocked,\n options.onStampClaimed,\n queueRequest,\n rawState,\n syncAdapter,\n ],\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) || isFlushingQueue.current) return;\n isFlushingQueue.current = true;\n setIsOperationPending(true);\n try {\n for (const request of offlineQueue) {\n if (!isOnline(syncAdapter)) break;\n const metadata = queuedMetadata.current.get(request.idempotencyKey);\n const previousState = metadata?.previousState ?? client.getState();\n try {\n const before = await syncAdapter?.onBeforeCheckIn?.(request);\n if (before === false) {\n if (metadata !== undefined && previousState !== null)\n await client.restore(previousState);\n setClientError({ client, value: { code: \"INVALID_PROOF\", stampId: request.stampId } });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n continue;\n }\n\n let result = metadata?.result;\n if (result === undefined) {\n const localResult = await client.acquire(request.stampId, request.context, request.now);\n if (!localResult.ok) {\n setClientError({ client, value: localResult.error });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n continue;\n }\n result = localResult.value;\n queuedMetadata.current.set(request.idempotencyKey, {\n previousState,\n result,\n });\n }\n\n const verified = await syncAdapter?.onServerVerify?.(request);\n if (isRejectedVerification(verified)) {\n if (previousState !== null) await client.restore(previousState);\n setClientError({\n client,\n value: { code: \"INVALID_PROOF\", stampId: request.stampId },\n });\n } else {\n notifyAcquisitionEvents(\n result,\n events,\n options.onStampClaimed,\n options.onRewardUnlocked,\n );\n }\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n } catch (flushError) {\n if (isNetworkFailure(flushError)) {\n setClientError({ client, value: toError(flushError) });\n break;\n }\n setClientError({ client, value: toError(flushError) });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n }\n }\n } finally {\n isFlushingQueue.current = false;\n setIsOperationPending(false);\n }\n }, [client, events, offlineQueue, options.onRewardUnlocked, options.onStampClaimed, syncAdapter]);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const handleOnline = (): void => {\n void flushQueue();\n };\n window.addEventListener(\"online\", handleOnline);\n return () => window.removeEventListener(\"online\", handleOnline);\n }, [flushQueue]);\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 offlineQueue,\n queuedCount: offlineQueue.length,\n flushQueue,\n };\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -39,6 +39,7 @@ interface UseStampRallyReturn {
|
|
|
39
39
|
readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;
|
|
40
40
|
readonly exportRecoveryCode: () => string;
|
|
41
41
|
readonly importRecoveryCode: (token: string) => Promise<boolean>;
|
|
42
|
+
readonly offlineQueue: ReadonlyArray<CheckInRequest>;
|
|
42
43
|
readonly queuedCount: number;
|
|
43
44
|
readonly flushQueue: () => Promise<void>;
|
|
44
45
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ interface UseStampRallyReturn {
|
|
|
39
39
|
readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;
|
|
40
40
|
readonly exportRecoveryCode: () => string;
|
|
41
41
|
readonly importRecoveryCode: (token: string) => Promise<boolean>;
|
|
42
|
+
readonly offlineQueue: ReadonlyArray<CheckInRequest>;
|
|
42
43
|
readonly queuedCount: number;
|
|
43
44
|
readonly flushQueue: () => Promise<void>;
|
|
44
45
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { consumeReward, exportProgressToken, importProgressToken } from '@stamprally/core';
|
|
2
|
-
import { useRef, useCallback, useSyncExternalStore,
|
|
2
|
+
import { useState, useRef, useCallback, useSyncExternalStore, useTransition, useEffect } from 'react';
|
|
3
3
|
|
|
4
4
|
// src/useStampRally.ts
|
|
5
5
|
function getServerSnapshot() {
|
|
@@ -34,10 +34,28 @@ function isOnline(adapter) {
|
|
|
34
34
|
function isRejectedVerification(value) {
|
|
35
35
|
return value === false || typeof value === "object" && value !== null && "ok" in value && value.ok === false;
|
|
36
36
|
}
|
|
37
|
+
function isNetworkFailure(error) {
|
|
38
|
+
if (typeof navigator !== "undefined" && navigator.onLine === false) return true;
|
|
39
|
+
if (!(error instanceof Error)) return true;
|
|
40
|
+
return /aborted|connection|fetch|network|offline|timeout/i.test(`${error.name} ${error.message}`);
|
|
41
|
+
}
|
|
42
|
+
function notifyAcquisitionEvents(result, events, onStampClaimed, onRewardUnlocked) {
|
|
43
|
+
for (const event of result.events) {
|
|
44
|
+
if (event.type === "stampAcquired") {
|
|
45
|
+
(events?.onStampClaimed ?? onStampClaimed)?.(event.record);
|
|
46
|
+
}
|
|
47
|
+
if (event.type === "rewardUnlocked") {
|
|
48
|
+
(events?.onRewardUnlocked ?? onRewardUnlocked)?.(event.rewardId);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
37
52
|
function useStampRally(client, options = {}) {
|
|
38
53
|
const syncAdapter = options.syncAdapter;
|
|
39
54
|
const events = options.events;
|
|
40
|
-
const
|
|
55
|
+
const [offlineQueue, setOfflineQueue] = useState([]);
|
|
56
|
+
const queuedMetadata = useRef(/* @__PURE__ */ new Map());
|
|
57
|
+
const isFlushingQueue = useRef(false);
|
|
58
|
+
const activeClient = useRef(client);
|
|
41
59
|
const subscribe = useCallback(
|
|
42
60
|
(onStoreChange) => client.subscribe(() => onStoreChange()),
|
|
43
61
|
[client]
|
|
@@ -55,6 +73,12 @@ function useStampRally(client, options = {}) {
|
|
|
55
73
|
useEffect(() => {
|
|
56
74
|
setOptimisticState(rawState);
|
|
57
75
|
}, [rawState]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (activeClient.current === client) return;
|
|
78
|
+
activeClient.current = client;
|
|
79
|
+
setOfflineQueue([]);
|
|
80
|
+
queuedMetadata.current.clear();
|
|
81
|
+
}, [client]);
|
|
58
82
|
useEffect(() => {
|
|
59
83
|
if (syncAdapter?.onStateChange === void 0) return;
|
|
60
84
|
return client.subscribe(syncAdapter.onStateChange);
|
|
@@ -82,6 +106,15 @@ function useStampRally(client, options = {}) {
|
|
|
82
106
|
active = false;
|
|
83
107
|
};
|
|
84
108
|
}, [client, rawState]);
|
|
109
|
+
const queueRequest = useCallback(
|
|
110
|
+
(request, metadata) => {
|
|
111
|
+
if (metadata !== void 0) queuedMetadata.current.set(request.idempotencyKey, metadata);
|
|
112
|
+
setOfflineQueue(
|
|
113
|
+
(current) => current.some((item) => item.idempotencyKey === request.idempotencyKey) ? current : [...current, request]
|
|
114
|
+
);
|
|
115
|
+
},
|
|
116
|
+
[]
|
|
117
|
+
);
|
|
85
118
|
const acquire = useCallback(
|
|
86
119
|
(stampId, context, now, idempotencyKey) => {
|
|
87
120
|
const acquiredAt = now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -93,7 +126,7 @@ function useStampRally(client, options = {}) {
|
|
|
93
126
|
};
|
|
94
127
|
setClientError(null);
|
|
95
128
|
if (!isOnline(syncAdapter)) {
|
|
96
|
-
|
|
129
|
+
queueRequest(request);
|
|
97
130
|
const queued = {
|
|
98
131
|
ok: false,
|
|
99
132
|
error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
|
|
@@ -117,9 +150,29 @@ function useStampRally(client, options = {}) {
|
|
|
117
150
|
resolve(rejected);
|
|
118
151
|
return;
|
|
119
152
|
}
|
|
153
|
+
const previousState = client.getState();
|
|
120
154
|
const result = await client.acquire(stampId, context, acquiredAt);
|
|
121
155
|
if (result.ok) {
|
|
122
|
-
|
|
156
|
+
let verified;
|
|
157
|
+
try {
|
|
158
|
+
verified = await syncAdapter?.onServerVerify?.(request);
|
|
159
|
+
} catch (verificationError) {
|
|
160
|
+
if (!isNetworkFailure(verificationError)) throw verificationError;
|
|
161
|
+
queueRequest(request, { previousState, result: result.value });
|
|
162
|
+
const queued = {
|
|
163
|
+
ok: false,
|
|
164
|
+
error: {
|
|
165
|
+
code: "OFFLINE_QUEUED",
|
|
166
|
+
stampId,
|
|
167
|
+
idempotencyKey: request.idempotencyKey
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
setClientError({ client, value: toError(verificationError) });
|
|
171
|
+
setOptimisticState(client.getState());
|
|
172
|
+
setIsOperationPending(false);
|
|
173
|
+
resolve(queued);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
123
176
|
if (isRejectedVerification(verified)) {
|
|
124
177
|
await client.restore(rawState ?? client.getState() ?? result.value.nextState);
|
|
125
178
|
const rejected = {
|
|
@@ -132,14 +185,12 @@ function useStampRally(client, options = {}) {
|
|
|
132
185
|
resolve(rejected);
|
|
133
186
|
return;
|
|
134
187
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
}
|
|
188
|
+
notifyAcquisitionEvents(
|
|
189
|
+
result.value,
|
|
190
|
+
events,
|
|
191
|
+
options.onStampClaimed,
|
|
192
|
+
options.onRewardUnlocked
|
|
193
|
+
);
|
|
143
194
|
}
|
|
144
195
|
if (!result.ok) {
|
|
145
196
|
setClientError({ client, value: result.error });
|
|
@@ -148,6 +199,18 @@ function useStampRally(client, options = {}) {
|
|
|
148
199
|
setIsOperationPending(false);
|
|
149
200
|
resolve(result);
|
|
150
201
|
} catch (acquireError) {
|
|
202
|
+
if (isNetworkFailure(acquireError)) {
|
|
203
|
+
queueRequest(request);
|
|
204
|
+
const queued = {
|
|
205
|
+
ok: false,
|
|
206
|
+
error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
|
|
207
|
+
};
|
|
208
|
+
setClientError({ client, value: queued.error });
|
|
209
|
+
setOptimisticState(client.getState());
|
|
210
|
+
setIsOperationPending(false);
|
|
211
|
+
resolve(queued);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
151
214
|
const normalizedError = toError(acquireError);
|
|
152
215
|
setClientError({ client, value: normalizedError });
|
|
153
216
|
setOptimisticState(client.getState());
|
|
@@ -157,7 +220,15 @@ function useStampRally(client, options = {}) {
|
|
|
157
220
|
})();
|
|
158
221
|
});
|
|
159
222
|
},
|
|
160
|
-
[
|
|
223
|
+
[
|
|
224
|
+
client,
|
|
225
|
+
events,
|
|
226
|
+
options.onRewardUnlocked,
|
|
227
|
+
options.onStampClaimed,
|
|
228
|
+
queueRequest,
|
|
229
|
+
rawState,
|
|
230
|
+
syncAdapter
|
|
231
|
+
]
|
|
161
232
|
);
|
|
162
233
|
const reset = useCallback(
|
|
163
234
|
(now) => {
|
|
@@ -316,13 +387,87 @@ function useStampRally(client, options = {}) {
|
|
|
316
387
|
const isLoading = rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);
|
|
317
388
|
const error = clientError?.client === client ? clientError.value : null;
|
|
318
389
|
const flushQueue = useCallback(async () => {
|
|
319
|
-
if (!isOnline(syncAdapter)) return;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
390
|
+
if (!isOnline(syncAdapter) || isFlushingQueue.current) return;
|
|
391
|
+
isFlushingQueue.current = true;
|
|
392
|
+
setIsOperationPending(true);
|
|
393
|
+
try {
|
|
394
|
+
for (const request of offlineQueue) {
|
|
395
|
+
if (!isOnline(syncAdapter)) break;
|
|
396
|
+
const metadata = queuedMetadata.current.get(request.idempotencyKey);
|
|
397
|
+
const previousState = metadata?.previousState ?? client.getState();
|
|
398
|
+
try {
|
|
399
|
+
const before = await syncAdapter?.onBeforeCheckIn?.(request);
|
|
400
|
+
if (before === false) {
|
|
401
|
+
if (metadata !== void 0 && previousState !== null)
|
|
402
|
+
await client.restore(previousState);
|
|
403
|
+
setClientError({ client, value: { code: "INVALID_PROOF", stampId: request.stampId } });
|
|
404
|
+
queuedMetadata.current.delete(request.idempotencyKey);
|
|
405
|
+
setOfflineQueue(
|
|
406
|
+
(current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
|
|
407
|
+
);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
let result = metadata?.result;
|
|
411
|
+
if (result === void 0) {
|
|
412
|
+
const localResult = await client.acquire(request.stampId, request.context, request.now);
|
|
413
|
+
if (!localResult.ok) {
|
|
414
|
+
setClientError({ client, value: localResult.error });
|
|
415
|
+
queuedMetadata.current.delete(request.idempotencyKey);
|
|
416
|
+
setOfflineQueue(
|
|
417
|
+
(current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
|
|
418
|
+
);
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
result = localResult.value;
|
|
422
|
+
queuedMetadata.current.set(request.idempotencyKey, {
|
|
423
|
+
previousState,
|
|
424
|
+
result
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
const verified = await syncAdapter?.onServerVerify?.(request);
|
|
428
|
+
if (isRejectedVerification(verified)) {
|
|
429
|
+
if (previousState !== null) await client.restore(previousState);
|
|
430
|
+
setClientError({
|
|
431
|
+
client,
|
|
432
|
+
value: { code: "INVALID_PROOF", stampId: request.stampId }
|
|
433
|
+
});
|
|
434
|
+
} else {
|
|
435
|
+
notifyAcquisitionEvents(
|
|
436
|
+
result,
|
|
437
|
+
events,
|
|
438
|
+
options.onStampClaimed,
|
|
439
|
+
options.onRewardUnlocked
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
queuedMetadata.current.delete(request.idempotencyKey);
|
|
443
|
+
setOfflineQueue(
|
|
444
|
+
(current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
|
|
445
|
+
);
|
|
446
|
+
} catch (flushError) {
|
|
447
|
+
if (isNetworkFailure(flushError)) {
|
|
448
|
+
setClientError({ client, value: toError(flushError) });
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
setClientError({ client, value: toError(flushError) });
|
|
452
|
+
queuedMetadata.current.delete(request.idempotencyKey);
|
|
453
|
+
setOfflineQueue(
|
|
454
|
+
(current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
} finally {
|
|
459
|
+
isFlushingQueue.current = false;
|
|
460
|
+
setIsOperationPending(false);
|
|
324
461
|
}
|
|
325
|
-
}, [
|
|
462
|
+
}, [client, events, offlineQueue, options.onRewardUnlocked, options.onStampClaimed, syncAdapter]);
|
|
463
|
+
useEffect(() => {
|
|
464
|
+
if (typeof window === "undefined") return;
|
|
465
|
+
const handleOnline = () => {
|
|
466
|
+
void flushQueue();
|
|
467
|
+
};
|
|
468
|
+
window.addEventListener("online", handleOnline);
|
|
469
|
+
return () => window.removeEventListener("online", handleOnline);
|
|
470
|
+
}, [flushQueue]);
|
|
326
471
|
return {
|
|
327
472
|
state: optimisticState,
|
|
328
473
|
isLoading,
|
|
@@ -334,7 +479,8 @@ function useStampRally(client, options = {}) {
|
|
|
334
479
|
redeem,
|
|
335
480
|
exportRecoveryCode,
|
|
336
481
|
importRecoveryCode,
|
|
337
|
-
|
|
482
|
+
offlineQueue,
|
|
483
|
+
queuedCount: offlineQueue.length,
|
|
338
484
|
flushQueue
|
|
339
485
|
};
|
|
340
486
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useStampRally.ts"],"names":["result"],"mappings":";;;;AA4FA,SAAS,iBAAA,GAA0B;AACjC,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,QAAQ,KAAA,EAAuB;AACtC,EAAA,OAAO,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AACjE;AAEA,SAAS,sBAAA,CACP,cACA,MAAA,EACwB;AACxB,EAAA,IACE,YAAA,KAAiB,IAAA,IACjB,YAAA,CAAa,OAAA,CAAQ,IAAA,CAAK,CAAC,MAAA,KAAW,MAAA,CAAO,OAAA,KAAY,MAAA,CAAO,OAAO,CAAA,EACvE;AACA,IAAA,OAAO,YAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,GAAG,YAAA;AAAA,IACH,OAAA,EAAS,CAAC,GAAG,YAAA,CAAa,OAAA,EAAS,EAAE,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,UAAA,EAAY,MAAA,CAAO,UAAA,EAAY,CAAA;AAAA,IAC7F,WAAW,MAAA,CAAO;AAAA,GACpB;AACF;AAEA,SAAS,oBAAA,GAA+B;AACtC,EAAA,MAAM,YAAY,UAAA,CAAW,MAAA;AAC7B,EAAA,IAAI,SAAA,KAAc,MAAA,IAAa,OAAO,SAAA,CAAU,eAAe,UAAA,EAAY;AACzE,IAAA,OAAO,UAAU,UAAA,EAAW;AAAA,EAC9B;AACA,EAAA,OAAO,CAAA,MAAA,EAAS,IAAA,CAAK,GAAA,EAAK,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,EAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AACnE;AAEA,SAAS,SAAS,OAAA,EAA2C;AAC3D,EAAA,IAAI,OAAA,EAAS,aAAa,MAAA,EAAW;AACnC,IAAA,OAAO,OAAO,SAAA,KAAc,WAAA,IAAe,SAAA,CAAU,MAAA,KAAW,KAAA;AAAA,EAClE;AACA,EAAA,OAAO,OAAO,OAAA,CAAQ,QAAA,KAAa,aAAa,OAAA,CAAQ,QAAA,KAAa,OAAA,CAAQ,QAAA;AAC/E;AAEA,SAAS,uBAAuB,KAAA,EAAyB;AACvD,EAAA,OACE,KAAA,KAAU,KAAA,IACT,OAAO,KAAA,KAAU,QAAA,IAAY,UAAU,IAAA,IAAQ,IAAA,IAAQ,KAAA,IAAS,KAAA,CAAM,EAAA,KAAO,KAAA;AAElF;AAEO,SAAS,aAAA,CACd,MAAA,EACA,OAAA,GAAgC,EAAC,EACZ;AACrB,EAAA,MAAM,cAAc,OAAA,CAAQ,WAAA;AAC5B,EAAA,MAAM,SAAS,OAAA,CAAQ,MAAA;AACvB,EAAA,MAAM,cAAA,GAAiB,MAAA,CAAsC,EAAE,CAAA;AAC/D,EAAA,MAAM,SAAA,GAAY,WAAA;AAAA,IAChB,CAAC,aAAA,KAA8B,MAAA,CAAO,SAAA,CAAU,MAAM,eAAe,CAAA;AAAA,IACrE,CAAC,MAAM;AAAA,GACT;AACA,EAAA,MAAM,WAAA,GAAc,YAAY,MAAM,MAAA,CAAO,UAAS,EAAG,CAAC,MAAM,CAAC,CAAA;AACjE,EAAA,MAAM,QAAA,GAAW,oBAAA,CAAqB,SAAA,EAAW,WAAA,EAAa,iBAAiB,CAAA;AAC/E,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAI,SAAuB,OAAO;AAAA,IACpE,MAAA;AAAA,IACA,cAAA,EAAgB,MAAA,CAAO,QAAA,EAAS,KAAM;AAAA,GACxC,CAAE,CAAA;AACF,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAA6B,IAAI,CAAA;AACvE,EAAA,MAAM,CAAC,SAAS,CAAA,GAAI,aAAA,EAAc;AAClC,EAAA,MAAM,CAAC,kBAAA,EAAoB,qBAAqB,CAAA,GAAI,SAAS,KAAK,CAAA;AAClE,EAAA,MAAM,CAAC,eAAA,EAAiB,kBAAkB,CAAA,GAAI,SAAiC,QAAQ,CAAA;AAEvF,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,kBAAA,CAAmB,QAAQ,CAAA;AAAA,EAC7B,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,WAAA,EAAa,kBAAkB,MAAA,EAAW;AAC9C,IAAA,OAAO,MAAA,CAAO,SAAA,CAAU,WAAA,CAAY,aAAa,CAAA;AAAA,EACnD,CAAA,EAAG,CAAC,MAAA,EAAQ,WAAW,CAAC,CAAA;AAExB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,MAAA,GAAS,IAAA;AACb,IAAA,cAAA,CAAe,IAAI,CAAA;AACnB,IAAA,IAAI,aAAa,IAAA,EAAM;AACrB,MAAA,eAAA,CAAgB,EAAE,MAAA,EAAQ,cAAA,EAAgB,KAAA,EAAO,CAAA;AACjD,MAAA,OAAO,MAAM;AACX,QAAA,MAAA,GAAS,KAAA;AAAA,MACX,CAAA;AAAA,IACF;AAEA,IAAA,eAAA,CAAgB,EAAE,MAAA,EAAQ,cAAA,EAAgB,IAAA,EAAM,CAAA;AAEhD,IAAA,KAAK,MAAA,CACF,IAAA,EAAK,CACL,KAAA,CAAM,CAAC,mBAAA,KAAiC;AACvC,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAO,OAAA,CAAQ,mBAAmB,GAAG,CAAA;AAAA,MAChE;AAAA,IACF,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,eAAA,CAAgB,EAAE,MAAA,EAAQ,cAAA,EAAgB,KAAA,EAAO,CAAA;AAAA,MACnD;AAAA,IACF,CAAC,CAAA;AAEH,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,GAAS,KAAA;AAAA,IACX,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,QAAQ,CAAC,CAAA;AAErB,EAAA,MAAM,OAAA,GAAU,WAAA;AAAA,IACd,CACE,OAAA,EACA,OAAA,EACA,GAAA,EACA,cAAA,KACmD;AACnD,MAAA,MAAM,UAAA,GAAa,GAAA,IAAA,iBAAO,IAAI,IAAA,IAAO,WAAA,EAAY;AACjD,MAAA,MAAM,OAAA,GAA0B;AAAA,QAC9B,OAAA;AAAA,QACA,OAAA;AAAA,QACA,GAAA,EAAK,UAAA;AAAA,QACL,cAAA,EAAgB,kBAAkB,oBAAA;AAAqB,OACzD;AACA,MAAA,cAAA,CAAe,IAAI,CAAA;AAEnB,MAAA,IAAI,CAAC,QAAA,CAAS,WAAW,CAAA,EAAG;AAC1B,QAAA,cAAA,CAAe,OAAA,GAAU,CAAC,GAAG,cAAA,CAAe,SAAS,OAAO,CAAA;AAC5D,QAAA,MAAM,MAAA,GAAgD;AAAA,UACpD,EAAA,EAAI,KAAA;AAAA,UACJ,OAAO,EAAE,IAAA,EAAM,kBAAkB,OAAA,EAAS,cAAA,EAAgB,QAAQ,cAAA;AAAe,SACnF;AACA,QAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAO,MAAA,CAAO,OAAO,CAAA;AAC9C,QAAA,OAAO,OAAA,CAAQ,QAAQ,MAAM,CAAA;AAAA,MAC/B;AAEA,MAAA,qBAAA,CAAsB,IAAI,CAAA;AAC1B,MAAA,kBAAA,CAAmB,CAAC,YAAY,sBAAA,CAAuB,OAAA,EAAS,EAAE,OAAA,EAAS,UAAA,EAAY,CAAC,CAAA;AACxF,MAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,QAAA,KAAA,CAAM,YAAY;AAChB,UAAA,IAAI;AACF,YAAA,MAAM,MAAA,GAAS,MAAM,WAAA,EAAa,eAAA,GAAkB,OAAO,CAAA;AAC3D,YAAA,IAAI,WAAW,KAAA,EAAO;AACpB,cAAA,MAAM,QAAA,GAAkD;AAAA,gBACtD,EAAA,EAAI,KAAA;AAAA,gBACJ,KAAA,EAAO,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAA;AAAQ,eAC1C;AACA,cAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAO,QAAA,CAAS,OAAO,CAAA;AAChD,cAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,cAAA,OAAA,CAAQ,QAAQ,CAAA;AAChB,cAAA;AAAA,YACF;AACA,YAAA,MAAM,SAAS,MAAM,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS,SAAS,UAAU,CAAA;AAChE,YAAA,IAAI,OAAO,EAAA,EAAI;AACb,cAAA,MAAM,QAAA,GAAW,MAAM,WAAA,EAAa,cAAA,GAAiB,OAAO,CAAA;AAC5D,cAAA,IAAI,sBAAA,CAAuB,QAAQ,CAAA,EAAG;AACpC,gBAAA,MAAM,MAAA,CAAO,QAAQ,QAAA,IAAY,MAAA,CAAO,UAAS,IAAK,MAAA,CAAO,MAAM,SAAS,CAAA;AAC5E,gBAAA,MAAM,QAAA,GAAkD;AAAA,kBACtD,EAAA,EAAI,KAAA;AAAA,kBACJ,KAAA,EAAO,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAA;AAAQ,iBAC1C;AACA,gBAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAO,QAAA,CAAS,OAAO,CAAA;AAChD,gBAAA,kBAAA,CAAmB,MAAA,CAAO,UAAU,CAAA;AACpC,gBAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,gBAAA,OAAA,CAAQ,QAAQ,CAAA;AAChB,gBAAA;AAAA,cACF;AACA,cAAA,KAAA,MAAW,KAAA,IAAS,MAAA,CAAO,KAAA,CAAM,MAAA,EAAQ;AACvC,gBAAA,IAAI,KAAA,CAAM,SAAS,eAAA,EAAiB;AAClC,kBAAA,CAAC,MAAA,EAAQ,cAAA,IAAkB,OAAA,CAAQ,cAAA,IAAkB,MAAM,MAAM,CAAA;AAAA,gBACnE;AACA,gBAAA,IAAI,KAAA,CAAM,SAAS,gBAAA,EAAkB;AACnC,kBAAA,CAAC,MAAA,EAAQ,gBAAA,IAAoB,OAAA,CAAQ,gBAAA,IAAoB,MAAM,QAAQ,CAAA;AAAA,gBACzE;AAAA,cACF;AAAA,YACF;AACA,YAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,cAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAO,MAAA,CAAO,OAAO,CAAA;AAC9C,cAAA,kBAAA,CAAmB,MAAA,CAAO,UAAU,CAAA;AAAA,YACtC;AACA,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,OAAA,CAAQ,MAAM,CAAA;AAAA,UAChB,SAAS,YAAA,EAAc;AACrB,YAAA,MAAM,eAAA,GAAkB,QAAQ,YAAY,CAAA;AAC5C,YAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAO,eAAA,EAAiB,CAAA;AACjD,YAAA,kBAAA,CAAmB,MAAA,CAAO,UAAU,CAAA;AACpC,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,MAAA,CAAO,eAAe,CAAA;AAAA,UACxB;AAAA,QACF,CAAA,GAAG;AAAA,MACL,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,QAAQ,MAAA,EAAQ,OAAA,CAAQ,kBAAkB,OAAA,CAAQ,cAAA,EAAgB,UAAU,WAAW;AAAA,GAC1F;AAEA,EAAA,MAAM,KAAA,GAAQ,WAAA;AAAA,IACZ,CAAC,GAAA,KAA2C;AAC1C,MAAA,cAAA,CAAe,IAAI,CAAA;AACnB,MAAA,qBAAA,CAAsB,IAAI,CAAA;AAE1B,MAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,QAAA,KAAA,CAAM,YAAY;AAChB,UAAA,IAAI;AACF,YAAA,MAAM,SAAA,GAAY,GAAA,KAAQ,KAAA,CAAA,GAAY,MAAM,MAAA,CAAO,OAAM,GAAI,MAAM,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA;AACnF,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,OAAA,CAAQ,SAAS,CAAA;AAAA,UACnB,SAAS,UAAA,EAAY;AACnB,YAAA,MAAM,eAAA,GAAkB,QAAQ,UAAU,CAAA;AAC1C,YAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAO,eAAA,EAAiB,CAAA;AACjD,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,MAAA,CAAO,eAAe,CAAA;AAAA,UACxB;AAAA,QACF,CAAA,GAAG;AAAA,MACL,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,CAAC,QAAA,EAAkB,aAAA,GAA+B,EAAC,KAA8B;AAC/E,MAAA,cAAA,CAAe,IAAI,CAAA;AACnB,MAAA,qBAAA,CAAsB,IAAI,CAAA;AAE1B,MAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,QAAA,KAAA,CAAM,YAAY;AAChB,UAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,EAAU,CAAE,OAAA,EAAS,KAAK,CAAC,IAAA,KAAS,IAAA,CAAK,EAAA,KAAO,QAAQ,CAAA;AAC9E,UAAA,IAAI,WAAW,MAAA,EAAW;AACxB,YAAA,MAAMA,OAAAA,GAAwB;AAAA,cAC5B,EAAA,EAAI,KAAA;AAAA,cACJ,KAAA,EAAO,EAAE,IAAA,EAAM,kBAAA,EAAoB,QAAA;AAAS,aAC9C;AACA,YAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAOA,OAAAA,CAAO,OAAO,CAAA;AAC9C,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,OAAA,CAAQA,OAAM,CAAA;AACd,YAAA;AAAA,UACF;AAEA,UAAA,MAAM,YAAA,GAAe,OAAO,QAAA,EAAS;AACrC,UAAA,MAAM,kBAAA,GAAqB,cAAc,OAAA,EAAS,IAAA;AAAA,YAChD,CAAC,KAAA,KAAU,KAAA,CAAM,QAAA,KAAa;AAAA,WAChC;AACA,UAAA,IAAI,YAAA,KAAiB,IAAA,IAAQ,kBAAA,KAAuB,MAAA,EAAW;AAC7D,YAAA,MAAMA,OAAAA,GAAwB;AAAA,cAC5B,EAAA,EAAI,KAAA;AAAA,cACJ,KAAA,EAAO,EAAE,IAAA,EAAM,eAAA,EAAiB,QAAA;AAAS,aAC3C;AACA,YAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAOA,OAAAA,CAAO,OAAO,CAAA;AAC9C,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,OAAA,CAAQA,OAAM,CAAA;AACd,YAAA;AAAA,UACF;AAEA,UAAA,MAAM,SAAS,aAAA,CAAc;AAAA,YAC3B,MAAA;AAAA,YACA,YAAA,EAAc,kBAAA;AAAA,YACd,GAAA,EAAA,iBAAK,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,YAC5B,GAAI,cAAc,QAAA,KAAa,MAAA,GAC3B,EAAC,GACD,EAAE,aAAA,EAAe,aAAA,CAAc,QAAA,EAAS;AAAA,YAC5C,GAAI,cAAc,OAAA,KAAY,MAAA,GAAY,EAAC,GAAI,EAAE,OAAA,EAAS,aAAA,CAAc,OAAA;AAAQ,WACjF,CAAA;AACD,UAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,YAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAO,MAAA,CAAO,OAAO,CAAA;AAC9C,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,OAAA,CAAQ,MAAM,CAAA;AACd,YAAA;AAAA,UACF;AAEA,UAAA,IAAI,MAAA,CAAO,UAAU,kBAAA,EAAoB;AACvC,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,OAAA,CAAQ,MAAM,CAAA;AACd,YAAA;AAAA,UACF;AAEA,UAAA,MAAM,SAAA,GAA6B;AAAA,YACjC,GAAG,YAAA;AAAA,YACH,OAAA,EAAA,CAAU,YAAA,CAAa,OAAA,IAAW,EAAC,EAAG,GAAA;AAAA,cAAI,CAAC,KAAA,KACzC,KAAA,CAAM,QAAA,KAAa,QAAA,GAAW,OAAO,KAAA,GAAQ;AAAA,aAC/C;AAAA,YACA,SAAA,EAAW,MAAA,CAAO,KAAA,CAAM,UAAA,IAAc,YAAA,CAAa;AAAA,WACrD;AAEA,UAAA,IAAI;AACF,YAAA,MAAM,MAAA,CAAO,QAAQ,SAAS,CAAA;AAC9B,YAAA,CAAC,MAAA,EAAQ,gBAAA,IAAoB,OAAA,CAAQ,gBAAA,IAAoB,QAAQ,CAAA;AACjE,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,OAAA,CAAQ,MAAM,CAAA;AAAA,UAChB,SAAS,WAAA,EAAa;AACpB,YAAA,MAAM,eAAA,GAAkB,QAAQ,WAAW,CAAA;AAC3C,YAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAO,eAAA,EAAiB,CAAA;AACjD,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,MAAA,CAAO,eAAe,CAAA;AAAA,UACxB;AAAA,QACF,CAAA,GAAG;AAAA,MACL,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,MAAA,EAAQ,MAAA,EAAQ,OAAA,CAAQ,gBAAgB;AAAA,GAC3C;AAEA,EAAA,MAAM,kBAAA,GAAqB,YAAY,MAAc;AACnD,IAAA,MAAM,KAAA,GAAQ,OAAO,QAAA,EAAS;AAC9B,IAAA,IAAI,UAAU,IAAA,EAAM;AAClB,MAAA,MAAM,IAAI,MAAM,8DAA8D,CAAA;AAAA,IAChF;AAEA,IAAA,OAAO,mBAAA,CAAoB;AAAA,MACzB,OAAA,EAAS,CAAA;AAAA,MACT,SAAS,KAAA,CAAM,OAAA;AAAA,MACf,QAAQ,KAAA,CAAM,OAAA;AAAA,MACd,OAAA,EAAS,KAAA,CAAM,OAAA,IAAW,EAAC;AAAA,MAC3B,UAAA,EAAA,iBAAY,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACpC,CAAA;AAAA,EACH,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,MAAM,kBAAA,GAAqB,WAAA;AAAA,IACzB,CAAC,KAAA,KAAoC;AACnC,MAAA,cAAA,CAAe,IAAI,CAAA;AACnB,MAAA,qBAAA,CAAsB,IAAI,CAAA;AAE1B,MAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,QAAA,KAAA,CAAM,YAAY;AAChB,UAAA,MAAM,MAAA,GAAS,OAAO,SAAA,EAAU;AAChC,UAAA,MAAM,QAAA,GAAW,mBAAA,CAAoB,KAAA,EAAO,MAAA,CAAO,EAAE,CAAA;AACrD,UAAA,IAAI,aAAa,IAAA,EAAM;AACrB,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,OAAA,CAAQ,KAAK,CAAA;AACb,YAAA;AAAA,UACF;AAEA,UAAA,MAAM,QAAA,GAAW,IAAI,GAAA,CAAI,MAAA,CAAO,MAAA,CAAO,IAAI,CAAC,KAAA,KAAU,KAAA,CAAM,EAAE,CAAC,CAAA;AAC/D,UAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAA,CAAK,MAAA,CAAO,OAAA,IAAW,EAAC,EAAG,GAAA,CAAI,CAAC,MAAA,KAAW,MAAA,CAAO,EAAE,CAAC,CAAA;AAC3E,UAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAY;AACzC,UAAA,MAAM,iBAAA,uBAAwB,GAAA,EAAY;AAC1C,UAAA,MAAM,MAAA,GAAS,QAAA,CAAS,MAAA,CAAO,MAAA,CAAO,CAAC,MAAA,KAAW;AAChD,YAAA,IAAI,CAAC,QAAA,CAAS,GAAA,CAAI,MAAA,CAAO,OAAO,CAAA,IAAK,gBAAA,CAAiB,GAAA,CAAI,MAAA,CAAO,OAAO,CAAA,EAAG,OAAO,KAAA;AAClF,YAAA,gBAAA,CAAiB,GAAA,CAAI,OAAO,OAAO,CAAA;AACnC,YAAA,OAAO,IAAA;AAAA,UACT,CAAC,CAAA;AACD,UAAA,MAAM,OAAA,GAAU,QAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,CAAC,KAAA,KAAU;AACjD,YAAA,IAAI,CAAC,UAAU,GAAA,CAAI,KAAA,CAAM,QAAQ,CAAA,IAAK,iBAAA,CAAkB,GAAA,CAAI,KAAA,CAAM,QAAQ,CAAA;AACxE,cAAA,OAAO,KAAA;AACT,YAAA,iBAAA,CAAkB,GAAA,CAAI,MAAM,QAAQ,CAAA;AACpC,YAAA,OAAO,IAAA;AAAA,UACT,CAAC,CAAA;AAED,UAAA,IAAI;AACF,YAAA,MAAM,OAAO,OAAA,CAAQ;AAAA,cACnB,SAAS,MAAA,CAAO,EAAA;AAAA,cAChB,OAAA,EAAS,MAAA;AAAA,cACT,GAAI,MAAA,CAAO,OAAA,KAAY,KAAA,CAAA,IAAa,OAAA,CAAQ,WAAW,CAAA,GAAI,EAAC,GAAI,EAAE,OAAA,EAAQ;AAAA,cAC1E,WAAW,QAAA,CAAS;AAAA,aACrB,CAAA;AACD,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,UACd,SAAS,WAAA,EAAa;AACpB,YAAA,MAAM,eAAA,GAAkB,QAAQ,WAAW,CAAA;AAC3C,YAAA,cAAA,CAAe,EAAE,MAAA,EAAQ,KAAA,EAAO,eAAA,EAAiB,CAAA;AACjD,YAAA,qBAAA,CAAsB,KAAK,CAAA;AAC3B,YAAA,MAAA,CAAO,eAAe,CAAA;AAAA,UACxB;AAAA,QACF,CAAA,GAAG;AAAA,MACL,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,MAAM,YACJ,QAAA,KAAa,IAAA,KAAS,YAAA,CAAa,MAAA,KAAW,UAAU,YAAA,CAAa,cAAA,CAAA;AACvE,EAAA,MAAM,KAAA,GAAQ,WAAA,EAAa,MAAA,KAAW,MAAA,GAAS,YAAY,KAAA,GAAQ,IAAA;AAEnE,EAAA,MAAM,UAAA,GAAa,YAAY,YAA2B;AACxD,IAAA,IAAI,CAAC,QAAA,CAAS,WAAW,CAAA,EAAG;AAC5B,IAAA,MAAM,UAAU,cAAA,CAAe,OAAA;AAC/B,IAAA,cAAA,CAAe,UAAU,EAAC;AAC1B,IAAA,KAAA,MAAW,WAAW,OAAA,EAAS;AAC7B,MAAA,MAAM,OAAA,CAAQ,QAAQ,OAAA,EAAS,OAAA,CAAQ,SAAS,OAAA,CAAQ,GAAA,EAAK,QAAQ,cAAc,CAAA;AAAA,IACrF;AAAA,EACF,CAAA,EAAG,CAAC,OAAA,EAAS,WAAW,CAAC,CAAA;AAEzB,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,eAAA;AAAA,IACP,SAAA;AAAA,IACA,WAAW,SAAA,IAAa,kBAAA;AAAA,IACxB,KAAA;AAAA,IACA,YAAA,EAAc,eAAA,EAAiB,OAAA,IAAW,EAAC;AAAA,IAC3C,OAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA;AAAA,IACA,kBAAA;AAAA,IACA,kBAAA;AAAA,IACA,WAAA,EAAa,eAAe,OAAA,CAAQ,MAAA;AAAA,IACpC;AAAA,GACF;AACF","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"]}
|
|
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\ninterface QueuedRequestMetadata {\n readonly previousState: StampRallyState | null;\n readonly result: ProcessStampValue;\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 offlineQueue: ReadonlyArray<CheckInRequest>;\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\nfunction isNetworkFailure(error: unknown): boolean {\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) return true;\n if (!(error instanceof Error)) return true;\n return /aborted|connection|fetch|network|offline|timeout/i.test(`${error.name} ${error.message}`);\n}\n\nfunction notifyAcquisitionEvents(\n result: ProcessStampValue,\n events: StampRallyEventHandlers | undefined,\n onStampClaimed: StampRallyEventHandlers[\"onStampClaimed\"] | undefined,\n onRewardUnlocked: StampRallyEventHandlers[\"onRewardUnlocked\"] | undefined,\n): void {\n for (const event of result.events) {\n if (event.type === \"stampAcquired\") {\n (events?.onStampClaimed ?? onStampClaimed)?.(event.record);\n }\n if (event.type === \"rewardUnlocked\") {\n (events?.onRewardUnlocked ?? onRewardUnlocked)?.(event.rewardId);\n }\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 [offlineQueue, setOfflineQueue] = useState<ReadonlyArray<CheckInRequest>>([]);\n const queuedMetadata = useRef(new Map<string, QueuedRequestMetadata>());\n const isFlushingQueue = useRef(false);\n const activeClient = useRef(client);\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 (activeClient.current === client) return;\n activeClient.current = client;\n setOfflineQueue([]);\n queuedMetadata.current.clear();\n }, [client]);\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 queueRequest = useCallback(\n (request: CheckInRequest, metadata?: QueuedRequestMetadata): void => {\n if (metadata !== undefined) queuedMetadata.current.set(request.idempotencyKey, metadata);\n setOfflineQueue((current) =>\n current.some((item) => item.idempotencyKey === request.idempotencyKey)\n ? current\n : [...current, request],\n );\n },\n [],\n );\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 queueRequest(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 previousState = client.getState();\n const result = await client.acquire(stampId, context, acquiredAt);\n if (result.ok) {\n let verified: unknown;\n try {\n verified = await syncAdapter?.onServerVerify?.(request);\n } catch (verificationError) {\n if (!isNetworkFailure(verificationError)) throw verificationError;\n queueRequest(request, { previousState, result: result.value });\n const queued: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: {\n code: \"OFFLINE_QUEUED\",\n stampId,\n idempotencyKey: request.idempotencyKey,\n },\n };\n setClientError({ client, value: toError(verificationError) });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(queued);\n return;\n }\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 notifyAcquisitionEvents(\n result.value,\n events,\n options.onStampClaimed,\n options.onRewardUnlocked,\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 if (isNetworkFailure(acquireError)) {\n queueRequest(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 setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(queued);\n return;\n }\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 [\n client,\n events,\n options.onRewardUnlocked,\n options.onStampClaimed,\n queueRequest,\n rawState,\n syncAdapter,\n ],\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) || isFlushingQueue.current) return;\n isFlushingQueue.current = true;\n setIsOperationPending(true);\n try {\n for (const request of offlineQueue) {\n if (!isOnline(syncAdapter)) break;\n const metadata = queuedMetadata.current.get(request.idempotencyKey);\n const previousState = metadata?.previousState ?? client.getState();\n try {\n const before = await syncAdapter?.onBeforeCheckIn?.(request);\n if (before === false) {\n if (metadata !== undefined && previousState !== null)\n await client.restore(previousState);\n setClientError({ client, value: { code: \"INVALID_PROOF\", stampId: request.stampId } });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n continue;\n }\n\n let result = metadata?.result;\n if (result === undefined) {\n const localResult = await client.acquire(request.stampId, request.context, request.now);\n if (!localResult.ok) {\n setClientError({ client, value: localResult.error });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n continue;\n }\n result = localResult.value;\n queuedMetadata.current.set(request.idempotencyKey, {\n previousState,\n result,\n });\n }\n\n const verified = await syncAdapter?.onServerVerify?.(request);\n if (isRejectedVerification(verified)) {\n if (previousState !== null) await client.restore(previousState);\n setClientError({\n client,\n value: { code: \"INVALID_PROOF\", stampId: request.stampId },\n });\n } else {\n notifyAcquisitionEvents(\n result,\n events,\n options.onStampClaimed,\n options.onRewardUnlocked,\n );\n }\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n } catch (flushError) {\n if (isNetworkFailure(flushError)) {\n setClientError({ client, value: toError(flushError) });\n break;\n }\n setClientError({ client, value: toError(flushError) });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n }\n }\n } finally {\n isFlushingQueue.current = false;\n setIsOperationPending(false);\n }\n }, [client, events, offlineQueue, options.onRewardUnlocked, options.onStampClaimed, syncAdapter]);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const handleOnline = (): void => {\n void flushQueue();\n };\n window.addEventListener(\"online\", handleOnline);\n return () => window.removeEventListener(\"online\", handleOnline);\n }, [flushQueue]);\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 offlineQueue,\n queuedCount: offlineQueue.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.4.0",
|
|
4
4
|
"description": "React hooks for @stamprally/core.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@stamprally/core": "0.
|
|
34
|
+
"@stamprally/core": "0.4.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": "^18.0.0 || ^19.0.0",
|