@stamprally/react 0.3.0 → 0.5.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 CHANGED
@@ -10,6 +10,9 @@ function getServerSnapshot() {
10
10
  function toError(error) {
11
11
  return error instanceof Error ? error : new Error(String(error));
12
12
  }
13
+ function isObject(value) {
14
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15
+ }
13
16
  function applyOptimisticAcquire(currentState, action) {
14
17
  if (currentState === null || currentState.records.some((record) => record.stampId === action.stampId)) {
15
18
  return currentState;
@@ -36,10 +39,28 @@ function isOnline(adapter) {
36
39
  function isRejectedVerification(value) {
37
40
  return value === false || typeof value === "object" && value !== null && "ok" in value && value.ok === false;
38
41
  }
42
+ function isNetworkFailure(error) {
43
+ if (typeof navigator !== "undefined" && navigator.onLine === false) return true;
44
+ if (!(error instanceof Error)) return true;
45
+ return /aborted|connection|fetch|network|offline|timeout/i.test(`${error.name} ${error.message}`);
46
+ }
47
+ function notifyAcquisitionEvents(result, events, onStampClaimed, onRewardUnlocked) {
48
+ for (const event of result.events) {
49
+ if (event.type === "stampAcquired") {
50
+ (events?.onStampClaimed ?? onStampClaimed)?.(event.record);
51
+ }
52
+ if (event.type === "rewardUnlocked") {
53
+ (events?.onRewardUnlocked ?? onRewardUnlocked)?.(event.rewardId);
54
+ }
55
+ }
56
+ }
39
57
  function useStampRally(client, options = {}) {
40
58
  const syncAdapter = options.syncAdapter;
41
59
  const events = options.events;
42
- const queuedRequests = react.useRef([]);
60
+ const [offlineQueue, setOfflineQueue] = react.useState([]);
61
+ const queuedMetadata = react.useRef(/* @__PURE__ */ new Map());
62
+ const isFlushingQueue = react.useRef(false);
63
+ const activeClient = react.useRef(client);
43
64
  const subscribe = react.useCallback(
44
65
  (onStoreChange) => client.subscribe(() => onStoreChange()),
45
66
  [client]
@@ -57,6 +78,12 @@ function useStampRally(client, options = {}) {
57
78
  react.useEffect(() => {
58
79
  setOptimisticState(rawState);
59
80
  }, [rawState]);
81
+ react.useEffect(() => {
82
+ if (activeClient.current === client) return;
83
+ activeClient.current = client;
84
+ setOfflineQueue([]);
85
+ queuedMetadata.current.clear();
86
+ }, [client]);
60
87
  react.useEffect(() => {
61
88
  if (syncAdapter?.onStateChange === void 0) return;
62
89
  return client.subscribe(syncAdapter.onStateChange);
@@ -84,6 +111,15 @@ function useStampRally(client, options = {}) {
84
111
  active = false;
85
112
  };
86
113
  }, [client, rawState]);
114
+ const queueRequest = react.useCallback(
115
+ (request, metadata) => {
116
+ if (metadata !== void 0) queuedMetadata.current.set(request.idempotencyKey, metadata);
117
+ setOfflineQueue(
118
+ (current) => current.some((item) => item.idempotencyKey === request.idempotencyKey) ? current : [...current, request]
119
+ );
120
+ },
121
+ []
122
+ );
87
123
  const acquire = react.useCallback(
88
124
  (stampId, context, now, idempotencyKey) => {
89
125
  const acquiredAt = now ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -95,7 +131,7 @@ function useStampRally(client, options = {}) {
95
131
  };
96
132
  setClientError(null);
97
133
  if (!isOnline(syncAdapter)) {
98
- queuedRequests.current = [...queuedRequests.current, request];
134
+ queueRequest(request);
99
135
  const queued = {
100
136
  ok: false,
101
137
  error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
@@ -119,9 +155,29 @@ function useStampRally(client, options = {}) {
119
155
  resolve(rejected);
120
156
  return;
121
157
  }
158
+ const previousState = client.getState();
122
159
  const result = await client.acquire(stampId, context, acquiredAt);
123
160
  if (result.ok) {
124
- const verified = await syncAdapter?.onServerVerify?.(request);
161
+ let verified;
162
+ try {
163
+ verified = await syncAdapter?.onServerVerify?.(request);
164
+ } catch (verificationError) {
165
+ if (!isNetworkFailure(verificationError)) throw verificationError;
166
+ queueRequest(request, { previousState, result: result.value });
167
+ const queued = {
168
+ ok: false,
169
+ error: {
170
+ code: "OFFLINE_QUEUED",
171
+ stampId,
172
+ idempotencyKey: request.idempotencyKey
173
+ }
174
+ };
175
+ setClientError({ client, value: toError(verificationError) });
176
+ setOptimisticState(client.getState());
177
+ setIsOperationPending(false);
178
+ resolve(queued);
179
+ return;
180
+ }
125
181
  if (isRejectedVerification(verified)) {
126
182
  await client.restore(rawState ?? client.getState() ?? result.value.nextState);
127
183
  const rejected = {
@@ -134,14 +190,12 @@ function useStampRally(client, options = {}) {
134
190
  resolve(rejected);
135
191
  return;
136
192
  }
137
- for (const event of result.value.events) {
138
- if (event.type === "stampAcquired") {
139
- (events?.onStampClaimed ?? options.onStampClaimed)?.(event.record);
140
- }
141
- if (event.type === "rewardUnlocked") {
142
- (events?.onRewardUnlocked ?? options.onRewardUnlocked)?.(event.rewardId);
143
- }
144
- }
193
+ notifyAcquisitionEvents(
194
+ result.value,
195
+ events,
196
+ options.onStampClaimed,
197
+ options.onRewardUnlocked
198
+ );
145
199
  }
146
200
  if (!result.ok) {
147
201
  setClientError({ client, value: result.error });
@@ -150,6 +204,18 @@ function useStampRally(client, options = {}) {
150
204
  setIsOperationPending(false);
151
205
  resolve(result);
152
206
  } catch (acquireError) {
207
+ if (isNetworkFailure(acquireError)) {
208
+ queueRequest(request);
209
+ const queued = {
210
+ ok: false,
211
+ error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
212
+ };
213
+ setClientError({ client, value: queued.error });
214
+ setOptimisticState(client.getState());
215
+ setIsOperationPending(false);
216
+ resolve(queued);
217
+ return;
218
+ }
153
219
  const normalizedError = toError(acquireError);
154
220
  setClientError({ client, value: normalizedError });
155
221
  setOptimisticState(client.getState());
@@ -159,7 +225,15 @@ function useStampRally(client, options = {}) {
159
225
  })();
160
226
  });
161
227
  },
162
- [client, events, options.onRewardUnlocked, options.onStampClaimed, rawState, syncAdapter]
228
+ [
229
+ client,
230
+ events,
231
+ options.onRewardUnlocked,
232
+ options.onStampClaimed,
233
+ queueRequest,
234
+ rawState,
235
+ syncAdapter
236
+ ]
163
237
  );
164
238
  const reset = react.useCallback(
165
239
  (now) => {
@@ -315,16 +389,179 @@ function useStampRally(client, options = {}) {
315
389
  },
316
390
  [client]
317
391
  );
392
+ const exportRecoveryToken = react.useCallback(
393
+ async (secretKey, tokenOptions = {}) => {
394
+ const state = client.getState();
395
+ if (state === null)
396
+ throw new Error("Cannot export recovery token before the rally is initialized.");
397
+ return core.createSecureToken(
398
+ {
399
+ type: "recovery",
400
+ rallyId: state.rallyId,
401
+ state,
402
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString()
403
+ },
404
+ secretKey,
405
+ { encrypt: tokenOptions.encrypt ?? true, ...tokenOptions }
406
+ );
407
+ },
408
+ [client]
409
+ );
410
+ const importRecoveryToken = react.useCallback(
411
+ async (token, secretKey) => {
412
+ setClientError(null);
413
+ setIsOperationPending(true);
414
+ try {
415
+ const verified = await core.verifySecureToken(token, secretKey);
416
+ if (!verified.ok || verified.payload.type !== "recovery" || verified.payload.rallyId !== client.getConfig().id) {
417
+ setIsOperationPending(false);
418
+ return false;
419
+ }
420
+ const candidate = verified.payload.state;
421
+ if (!core.isStampRallyState(candidate)) {
422
+ setIsOperationPending(false);
423
+ return false;
424
+ }
425
+ await client.restore(candidate);
426
+ setIsOperationPending(false);
427
+ return true;
428
+ } catch (error2) {
429
+ setClientError({ client, value: toError(error2) });
430
+ setIsOperationPending(false);
431
+ return false;
432
+ }
433
+ },
434
+ [client]
435
+ );
436
+ const syncWithServer = react.useCallback(
437
+ async (serverEndpoint, authHeader) => {
438
+ if (typeof fetch !== "function")
439
+ throw new Error("Fetch API is unavailable in this environment.");
440
+ setClientError(null);
441
+ setIsOperationPending(true);
442
+ try {
443
+ const endpoint = serverEndpoint.replace(/\/$/u, "").endsWith("/sync") ? serverEndpoint : `${serverEndpoint.replace(/\/$/u, "")}/api/sync`;
444
+ const response = await fetch(endpoint, {
445
+ method: "POST",
446
+ headers: {
447
+ "content-type": "application/json",
448
+ ...authHeader === void 0 ? {} : { authorization: authHeader }
449
+ },
450
+ body: JSON.stringify({
451
+ userId: options.serverUserId ?? "anonymous",
452
+ queue: offlineQueue.map((request) => ({
453
+ ...request,
454
+ userId: options.serverUserId ?? "anonymous",
455
+ spotId: request.stampId,
456
+ claimMethod: request.context.type,
457
+ proofData: request.context.type === "token" ? { token: request.context.token } : request.context.type === "geo" ? {
458
+ latitude: request.context.currentLatitude,
459
+ longitude: request.context.currentLongitude
460
+ } : void 0
461
+ }))
462
+ })
463
+ });
464
+ const payload = await response.json();
465
+ if (!response.ok || !isObject(payload) || payload.ok !== true || !core.isStampRallyState(payload.state)) {
466
+ throw new Error("Server synchronization was rejected.");
467
+ }
468
+ await client.restore(payload.state);
469
+ queuedMetadata.current.clear();
470
+ setOfflineQueue([]);
471
+ client.notifySyncCompleted(payload.state);
472
+ } catch (error2) {
473
+ setClientError({ client, value: toError(error2) });
474
+ throw error2;
475
+ } finally {
476
+ setIsOperationPending(false);
477
+ }
478
+ },
479
+ [client, offlineQueue, options.serverUserId]
480
+ );
318
481
  const isLoading = rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);
319
482
  const error = clientError?.client === client ? clientError.value : null;
320
483
  const flushQueue = react.useCallback(async () => {
321
- if (!isOnline(syncAdapter)) return;
322
- const pending = queuedRequests.current;
323
- queuedRequests.current = [];
324
- for (const request of pending) {
325
- await acquire(request.stampId, request.context, request.now, request.idempotencyKey);
484
+ if (!isOnline(syncAdapter) || isFlushingQueue.current) return;
485
+ isFlushingQueue.current = true;
486
+ setIsOperationPending(true);
487
+ try {
488
+ for (const request of offlineQueue) {
489
+ if (!isOnline(syncAdapter)) break;
490
+ const metadata = queuedMetadata.current.get(request.idempotencyKey);
491
+ const previousState = metadata?.previousState ?? client.getState();
492
+ try {
493
+ const before = await syncAdapter?.onBeforeCheckIn?.(request);
494
+ if (before === false) {
495
+ if (metadata !== void 0 && previousState !== null)
496
+ await client.restore(previousState);
497
+ setClientError({ client, value: { code: "INVALID_PROOF", stampId: request.stampId } });
498
+ queuedMetadata.current.delete(request.idempotencyKey);
499
+ setOfflineQueue(
500
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
501
+ );
502
+ continue;
503
+ }
504
+ let result = metadata?.result;
505
+ if (result === void 0) {
506
+ const localResult = await client.acquire(request.stampId, request.context, request.now);
507
+ if (!localResult.ok) {
508
+ setClientError({ client, value: localResult.error });
509
+ queuedMetadata.current.delete(request.idempotencyKey);
510
+ setOfflineQueue(
511
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
512
+ );
513
+ continue;
514
+ }
515
+ result = localResult.value;
516
+ queuedMetadata.current.set(request.idempotencyKey, {
517
+ previousState,
518
+ result
519
+ });
520
+ }
521
+ const verified = await syncAdapter?.onServerVerify?.(request);
522
+ if (isRejectedVerification(verified)) {
523
+ if (previousState !== null) await client.restore(previousState);
524
+ setClientError({
525
+ client,
526
+ value: { code: "INVALID_PROOF", stampId: request.stampId }
527
+ });
528
+ } else {
529
+ notifyAcquisitionEvents(
530
+ result,
531
+ events,
532
+ options.onStampClaimed,
533
+ options.onRewardUnlocked
534
+ );
535
+ }
536
+ queuedMetadata.current.delete(request.idempotencyKey);
537
+ setOfflineQueue(
538
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
539
+ );
540
+ } catch (flushError) {
541
+ if (isNetworkFailure(flushError)) {
542
+ setClientError({ client, value: toError(flushError) });
543
+ break;
544
+ }
545
+ setClientError({ client, value: toError(flushError) });
546
+ queuedMetadata.current.delete(request.idempotencyKey);
547
+ setOfflineQueue(
548
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
549
+ );
550
+ }
551
+ }
552
+ } finally {
553
+ isFlushingQueue.current = false;
554
+ setIsOperationPending(false);
326
555
  }
327
- }, [acquire, syncAdapter]);
556
+ }, [client, events, offlineQueue, options.onRewardUnlocked, options.onStampClaimed, syncAdapter]);
557
+ react.useEffect(() => {
558
+ if (typeof window === "undefined") return;
559
+ const handleOnline = () => {
560
+ void flushQueue();
561
+ };
562
+ window.addEventListener("online", handleOnline);
563
+ return () => window.removeEventListener("online", handleOnline);
564
+ }, [flushQueue]);
328
565
  return {
329
566
  state: optimisticState,
330
567
  isLoading,
@@ -336,7 +573,11 @@ function useStampRally(client, options = {}) {
336
573
  redeem,
337
574
  exportRecoveryCode,
338
575
  importRecoveryCode,
339
- queuedCount: queuedRequests.current.length,
576
+ exportRecoveryToken,
577
+ importRecoveryToken,
578
+ syncWithServer,
579
+ offlineQueue,
580
+ queuedCount: offlineQueue.length,
340
581
  flushQueue
341
582
  };
342
583
  }
@@ -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","createSecureToken","verifySecureToken","isStampRallyState","error"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n ConsumeResult,\n ProcessStampValue,\n Result,\n RewardConsumeError,\n RewardState,\n SecureTokenOptions,\n StampError,\n StampRallyClient,\n StampRallyState,\n VerificationContext,\n} from \"@stamprally/core\";\nimport {\n consumeReward,\n createSecureToken,\n exportProgressToken,\n importProgressToken,\n isStampRallyState,\n verifySecureToken,\n} 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 readonly serverUserId?: string;\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 exportRecoveryToken: (\n secretKey: string,\n options?: SecureTokenOptions,\n ) => Promise<string>;\n readonly importRecoveryToken: (token: string, secretKey: string) => Promise<boolean>;\n readonly syncWithServer: (serverEndpoint: string, authHeader?: string) => Promise<void>;\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 isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\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 exportRecoveryToken = useCallback(\n async (secretKey: string, tokenOptions: SecureTokenOptions = {}): Promise<string> => {\n const state = client.getState();\n if (state === null)\n throw new Error(\"Cannot export recovery token before the rally is initialized.\");\n return createSecureToken(\n {\n type: \"recovery\",\n rallyId: state.rallyId,\n state,\n exportedAt: new Date().toISOString(),\n },\n secretKey,\n { encrypt: tokenOptions.encrypt ?? true, ...tokenOptions },\n );\n },\n [client],\n );\n\n const importRecoveryToken = useCallback(\n async (token: string, secretKey: string): Promise<boolean> => {\n setClientError(null);\n setIsOperationPending(true);\n try {\n const verified = await verifySecureToken(token, secretKey);\n if (\n !verified.ok ||\n verified.payload.type !== \"recovery\" ||\n verified.payload.rallyId !== client.getConfig().id\n ) {\n setIsOperationPending(false);\n return false;\n }\n const candidate = verified.payload.state;\n if (!isStampRallyState(candidate)) {\n setIsOperationPending(false);\n return false;\n }\n await client.restore(candidate);\n setIsOperationPending(false);\n return true;\n } catch (error) {\n setClientError({ client, value: toError(error) });\n setIsOperationPending(false);\n return false;\n }\n },\n [client],\n );\n\n const syncWithServer = useCallback(\n async (serverEndpoint: string, authHeader?: string): Promise<void> => {\n if (typeof fetch !== \"function\")\n throw new Error(\"Fetch API is unavailable in this environment.\");\n setClientError(null);\n setIsOperationPending(true);\n try {\n const endpoint = serverEndpoint.replace(/\\/$/u, \"\").endsWith(\"/sync\")\n ? serverEndpoint\n : `${serverEndpoint.replace(/\\/$/u, \"\")}/api/sync`;\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(authHeader === undefined ? {} : { authorization: authHeader }),\n },\n body: JSON.stringify({\n userId: options.serverUserId ?? \"anonymous\",\n queue: offlineQueue.map((request) => ({\n ...request,\n userId: options.serverUserId ?? \"anonymous\",\n spotId: request.stampId,\n claimMethod: request.context.type,\n proofData:\n request.context.type === \"token\"\n ? { token: request.context.token }\n : request.context.type === \"geo\"\n ? {\n latitude: request.context.currentLatitude,\n longitude: request.context.currentLongitude,\n }\n : undefined,\n })),\n }),\n });\n const payload: unknown = await response.json();\n if (\n !response.ok ||\n !isObject(payload) ||\n payload.ok !== true ||\n !isStampRallyState(payload.state)\n ) {\n throw new Error(\"Server synchronization was rejected.\");\n }\n await client.restore(payload.state);\n queuedMetadata.current.clear();\n setOfflineQueue([]);\n client.notifySyncCompleted(payload.state);\n } catch (error) {\n setClientError({ client, value: toError(error) });\n throw error;\n } finally {\n setIsOperationPending(false);\n }\n },\n [client, offlineQueue, options.serverUserId],\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 exportRecoveryToken,\n importRecoveryToken,\n syncWithServer,\n offlineQueue,\n queuedCount: offlineQueue.length,\n flushQueue,\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { VerificationContext, StampRallyState, StampError, RewardConsumeError, RewardState, Result, ProcessStampValue, ConsumeResult, StampRallyClient } from '@stamprally/core';
1
+ import { VerificationContext, StampRallyState, StampError, RewardConsumeError, RewardState, Result, ProcessStampValue, ConsumeResult, SecureTokenOptions, StampRallyClient } from '@stamprally/core';
2
2
 
3
3
  interface CheckInRequest {
4
4
  readonly stampId: string;
@@ -23,6 +23,7 @@ interface UseStampRallyOptions {
23
23
  readonly onStampClaimed?: StampRallyEventHandlers["onStampClaimed"];
24
24
  readonly onRewardUnlocked?: StampRallyEventHandlers["onRewardUnlocked"];
25
25
  readonly onRewardConsumed?: StampRallyEventHandlers["onRewardConsumed"];
26
+ readonly serverUserId?: string;
26
27
  }
27
28
  interface RedeemOptions {
28
29
  readonly passcode?: string;
@@ -39,6 +40,10 @@ interface UseStampRallyReturn {
39
40
  readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;
40
41
  readonly exportRecoveryCode: () => string;
41
42
  readonly importRecoveryCode: (token: string) => Promise<boolean>;
43
+ readonly exportRecoveryToken: (secretKey: string, options?: SecureTokenOptions) => Promise<string>;
44
+ readonly importRecoveryToken: (token: string, secretKey: string) => Promise<boolean>;
45
+ readonly syncWithServer: (serverEndpoint: string, authHeader?: string) => Promise<void>;
46
+ readonly offlineQueue: ReadonlyArray<CheckInRequest>;
42
47
  readonly queuedCount: number;
43
48
  readonly flushQueue: () => Promise<void>;
44
49
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { VerificationContext, StampRallyState, StampError, RewardConsumeError, RewardState, Result, ProcessStampValue, ConsumeResult, StampRallyClient } from '@stamprally/core';
1
+ import { VerificationContext, StampRallyState, StampError, RewardConsumeError, RewardState, Result, ProcessStampValue, ConsumeResult, SecureTokenOptions, StampRallyClient } from '@stamprally/core';
2
2
 
3
3
  interface CheckInRequest {
4
4
  readonly stampId: string;
@@ -23,6 +23,7 @@ interface UseStampRallyOptions {
23
23
  readonly onStampClaimed?: StampRallyEventHandlers["onStampClaimed"];
24
24
  readonly onRewardUnlocked?: StampRallyEventHandlers["onRewardUnlocked"];
25
25
  readonly onRewardConsumed?: StampRallyEventHandlers["onRewardConsumed"];
26
+ readonly serverUserId?: string;
26
27
  }
27
28
  interface RedeemOptions {
28
29
  readonly passcode?: string;
@@ -39,6 +40,10 @@ interface UseStampRallyReturn {
39
40
  readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;
40
41
  readonly exportRecoveryCode: () => string;
41
42
  readonly importRecoveryCode: (token: string) => Promise<boolean>;
43
+ readonly exportRecoveryToken: (secretKey: string, options?: SecureTokenOptions) => Promise<string>;
44
+ readonly importRecoveryToken: (token: string, secretKey: string) => Promise<boolean>;
45
+ readonly syncWithServer: (serverEndpoint: string, authHeader?: string) => Promise<void>;
46
+ readonly offlineQueue: ReadonlyArray<CheckInRequest>;
42
47
  readonly queuedCount: number;
43
48
  readonly flushQueue: () => Promise<void>;
44
49
  }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { consumeReward, exportProgressToken, importProgressToken } from '@stamprally/core';
2
- import { useRef, useCallback, useSyncExternalStore, useState, useTransition, useEffect } from 'react';
1
+ import { consumeReward, exportProgressToken, importProgressToken, createSecureToken, verifySecureToken, isStampRallyState } from '@stamprally/core';
2
+ import { useState, useRef, useCallback, useSyncExternalStore, useTransition, useEffect } from 'react';
3
3
 
4
4
  // src/useStampRally.ts
5
5
  function getServerSnapshot() {
@@ -8,6 +8,9 @@ function getServerSnapshot() {
8
8
  function toError(error) {
9
9
  return error instanceof Error ? error : new Error(String(error));
10
10
  }
11
+ function isObject(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
11
14
  function applyOptimisticAcquire(currentState, action) {
12
15
  if (currentState === null || currentState.records.some((record) => record.stampId === action.stampId)) {
13
16
  return currentState;
@@ -34,10 +37,28 @@ function isOnline(adapter) {
34
37
  function isRejectedVerification(value) {
35
38
  return value === false || typeof value === "object" && value !== null && "ok" in value && value.ok === false;
36
39
  }
40
+ function isNetworkFailure(error) {
41
+ if (typeof navigator !== "undefined" && navigator.onLine === false) return true;
42
+ if (!(error instanceof Error)) return true;
43
+ return /aborted|connection|fetch|network|offline|timeout/i.test(`${error.name} ${error.message}`);
44
+ }
45
+ function notifyAcquisitionEvents(result, events, onStampClaimed, onRewardUnlocked) {
46
+ for (const event of result.events) {
47
+ if (event.type === "stampAcquired") {
48
+ (events?.onStampClaimed ?? onStampClaimed)?.(event.record);
49
+ }
50
+ if (event.type === "rewardUnlocked") {
51
+ (events?.onRewardUnlocked ?? onRewardUnlocked)?.(event.rewardId);
52
+ }
53
+ }
54
+ }
37
55
  function useStampRally(client, options = {}) {
38
56
  const syncAdapter = options.syncAdapter;
39
57
  const events = options.events;
40
- const queuedRequests = useRef([]);
58
+ const [offlineQueue, setOfflineQueue] = useState([]);
59
+ const queuedMetadata = useRef(/* @__PURE__ */ new Map());
60
+ const isFlushingQueue = useRef(false);
61
+ const activeClient = useRef(client);
41
62
  const subscribe = useCallback(
42
63
  (onStoreChange) => client.subscribe(() => onStoreChange()),
43
64
  [client]
@@ -55,6 +76,12 @@ function useStampRally(client, options = {}) {
55
76
  useEffect(() => {
56
77
  setOptimisticState(rawState);
57
78
  }, [rawState]);
79
+ useEffect(() => {
80
+ if (activeClient.current === client) return;
81
+ activeClient.current = client;
82
+ setOfflineQueue([]);
83
+ queuedMetadata.current.clear();
84
+ }, [client]);
58
85
  useEffect(() => {
59
86
  if (syncAdapter?.onStateChange === void 0) return;
60
87
  return client.subscribe(syncAdapter.onStateChange);
@@ -82,6 +109,15 @@ function useStampRally(client, options = {}) {
82
109
  active = false;
83
110
  };
84
111
  }, [client, rawState]);
112
+ const queueRequest = useCallback(
113
+ (request, metadata) => {
114
+ if (metadata !== void 0) queuedMetadata.current.set(request.idempotencyKey, metadata);
115
+ setOfflineQueue(
116
+ (current) => current.some((item) => item.idempotencyKey === request.idempotencyKey) ? current : [...current, request]
117
+ );
118
+ },
119
+ []
120
+ );
85
121
  const acquire = useCallback(
86
122
  (stampId, context, now, idempotencyKey) => {
87
123
  const acquiredAt = now ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -93,7 +129,7 @@ function useStampRally(client, options = {}) {
93
129
  };
94
130
  setClientError(null);
95
131
  if (!isOnline(syncAdapter)) {
96
- queuedRequests.current = [...queuedRequests.current, request];
132
+ queueRequest(request);
97
133
  const queued = {
98
134
  ok: false,
99
135
  error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
@@ -117,9 +153,29 @@ function useStampRally(client, options = {}) {
117
153
  resolve(rejected);
118
154
  return;
119
155
  }
156
+ const previousState = client.getState();
120
157
  const result = await client.acquire(stampId, context, acquiredAt);
121
158
  if (result.ok) {
122
- const verified = await syncAdapter?.onServerVerify?.(request);
159
+ let verified;
160
+ try {
161
+ verified = await syncAdapter?.onServerVerify?.(request);
162
+ } catch (verificationError) {
163
+ if (!isNetworkFailure(verificationError)) throw verificationError;
164
+ queueRequest(request, { previousState, result: result.value });
165
+ const queued = {
166
+ ok: false,
167
+ error: {
168
+ code: "OFFLINE_QUEUED",
169
+ stampId,
170
+ idempotencyKey: request.idempotencyKey
171
+ }
172
+ };
173
+ setClientError({ client, value: toError(verificationError) });
174
+ setOptimisticState(client.getState());
175
+ setIsOperationPending(false);
176
+ resolve(queued);
177
+ return;
178
+ }
123
179
  if (isRejectedVerification(verified)) {
124
180
  await client.restore(rawState ?? client.getState() ?? result.value.nextState);
125
181
  const rejected = {
@@ -132,14 +188,12 @@ function useStampRally(client, options = {}) {
132
188
  resolve(rejected);
133
189
  return;
134
190
  }
135
- for (const event of result.value.events) {
136
- if (event.type === "stampAcquired") {
137
- (events?.onStampClaimed ?? options.onStampClaimed)?.(event.record);
138
- }
139
- if (event.type === "rewardUnlocked") {
140
- (events?.onRewardUnlocked ?? options.onRewardUnlocked)?.(event.rewardId);
141
- }
142
- }
191
+ notifyAcquisitionEvents(
192
+ result.value,
193
+ events,
194
+ options.onStampClaimed,
195
+ options.onRewardUnlocked
196
+ );
143
197
  }
144
198
  if (!result.ok) {
145
199
  setClientError({ client, value: result.error });
@@ -148,6 +202,18 @@ function useStampRally(client, options = {}) {
148
202
  setIsOperationPending(false);
149
203
  resolve(result);
150
204
  } catch (acquireError) {
205
+ if (isNetworkFailure(acquireError)) {
206
+ queueRequest(request);
207
+ const queued = {
208
+ ok: false,
209
+ error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
210
+ };
211
+ setClientError({ client, value: queued.error });
212
+ setOptimisticState(client.getState());
213
+ setIsOperationPending(false);
214
+ resolve(queued);
215
+ return;
216
+ }
151
217
  const normalizedError = toError(acquireError);
152
218
  setClientError({ client, value: normalizedError });
153
219
  setOptimisticState(client.getState());
@@ -157,7 +223,15 @@ function useStampRally(client, options = {}) {
157
223
  })();
158
224
  });
159
225
  },
160
- [client, events, options.onRewardUnlocked, options.onStampClaimed, rawState, syncAdapter]
226
+ [
227
+ client,
228
+ events,
229
+ options.onRewardUnlocked,
230
+ options.onStampClaimed,
231
+ queueRequest,
232
+ rawState,
233
+ syncAdapter
234
+ ]
161
235
  );
162
236
  const reset = useCallback(
163
237
  (now) => {
@@ -313,16 +387,179 @@ function useStampRally(client, options = {}) {
313
387
  },
314
388
  [client]
315
389
  );
390
+ const exportRecoveryToken = useCallback(
391
+ async (secretKey, tokenOptions = {}) => {
392
+ const state = client.getState();
393
+ if (state === null)
394
+ throw new Error("Cannot export recovery token before the rally is initialized.");
395
+ return createSecureToken(
396
+ {
397
+ type: "recovery",
398
+ rallyId: state.rallyId,
399
+ state,
400
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString()
401
+ },
402
+ secretKey,
403
+ { encrypt: tokenOptions.encrypt ?? true, ...tokenOptions }
404
+ );
405
+ },
406
+ [client]
407
+ );
408
+ const importRecoveryToken = useCallback(
409
+ async (token, secretKey) => {
410
+ setClientError(null);
411
+ setIsOperationPending(true);
412
+ try {
413
+ const verified = await verifySecureToken(token, secretKey);
414
+ if (!verified.ok || verified.payload.type !== "recovery" || verified.payload.rallyId !== client.getConfig().id) {
415
+ setIsOperationPending(false);
416
+ return false;
417
+ }
418
+ const candidate = verified.payload.state;
419
+ if (!isStampRallyState(candidate)) {
420
+ setIsOperationPending(false);
421
+ return false;
422
+ }
423
+ await client.restore(candidate);
424
+ setIsOperationPending(false);
425
+ return true;
426
+ } catch (error2) {
427
+ setClientError({ client, value: toError(error2) });
428
+ setIsOperationPending(false);
429
+ return false;
430
+ }
431
+ },
432
+ [client]
433
+ );
434
+ const syncWithServer = useCallback(
435
+ async (serverEndpoint, authHeader) => {
436
+ if (typeof fetch !== "function")
437
+ throw new Error("Fetch API is unavailable in this environment.");
438
+ setClientError(null);
439
+ setIsOperationPending(true);
440
+ try {
441
+ const endpoint = serverEndpoint.replace(/\/$/u, "").endsWith("/sync") ? serverEndpoint : `${serverEndpoint.replace(/\/$/u, "")}/api/sync`;
442
+ const response = await fetch(endpoint, {
443
+ method: "POST",
444
+ headers: {
445
+ "content-type": "application/json",
446
+ ...authHeader === void 0 ? {} : { authorization: authHeader }
447
+ },
448
+ body: JSON.stringify({
449
+ userId: options.serverUserId ?? "anonymous",
450
+ queue: offlineQueue.map((request) => ({
451
+ ...request,
452
+ userId: options.serverUserId ?? "anonymous",
453
+ spotId: request.stampId,
454
+ claimMethod: request.context.type,
455
+ proofData: request.context.type === "token" ? { token: request.context.token } : request.context.type === "geo" ? {
456
+ latitude: request.context.currentLatitude,
457
+ longitude: request.context.currentLongitude
458
+ } : void 0
459
+ }))
460
+ })
461
+ });
462
+ const payload = await response.json();
463
+ if (!response.ok || !isObject(payload) || payload.ok !== true || !isStampRallyState(payload.state)) {
464
+ throw new Error("Server synchronization was rejected.");
465
+ }
466
+ await client.restore(payload.state);
467
+ queuedMetadata.current.clear();
468
+ setOfflineQueue([]);
469
+ client.notifySyncCompleted(payload.state);
470
+ } catch (error2) {
471
+ setClientError({ client, value: toError(error2) });
472
+ throw error2;
473
+ } finally {
474
+ setIsOperationPending(false);
475
+ }
476
+ },
477
+ [client, offlineQueue, options.serverUserId]
478
+ );
316
479
  const isLoading = rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);
317
480
  const error = clientError?.client === client ? clientError.value : null;
318
481
  const flushQueue = useCallback(async () => {
319
- if (!isOnline(syncAdapter)) return;
320
- const pending = queuedRequests.current;
321
- queuedRequests.current = [];
322
- for (const request of pending) {
323
- await acquire(request.stampId, request.context, request.now, request.idempotencyKey);
482
+ if (!isOnline(syncAdapter) || isFlushingQueue.current) return;
483
+ isFlushingQueue.current = true;
484
+ setIsOperationPending(true);
485
+ try {
486
+ for (const request of offlineQueue) {
487
+ if (!isOnline(syncAdapter)) break;
488
+ const metadata = queuedMetadata.current.get(request.idempotencyKey);
489
+ const previousState = metadata?.previousState ?? client.getState();
490
+ try {
491
+ const before = await syncAdapter?.onBeforeCheckIn?.(request);
492
+ if (before === false) {
493
+ if (metadata !== void 0 && previousState !== null)
494
+ await client.restore(previousState);
495
+ setClientError({ client, value: { code: "INVALID_PROOF", stampId: request.stampId } });
496
+ queuedMetadata.current.delete(request.idempotencyKey);
497
+ setOfflineQueue(
498
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
499
+ );
500
+ continue;
501
+ }
502
+ let result = metadata?.result;
503
+ if (result === void 0) {
504
+ const localResult = await client.acquire(request.stampId, request.context, request.now);
505
+ if (!localResult.ok) {
506
+ setClientError({ client, value: localResult.error });
507
+ queuedMetadata.current.delete(request.idempotencyKey);
508
+ setOfflineQueue(
509
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
510
+ );
511
+ continue;
512
+ }
513
+ result = localResult.value;
514
+ queuedMetadata.current.set(request.idempotencyKey, {
515
+ previousState,
516
+ result
517
+ });
518
+ }
519
+ const verified = await syncAdapter?.onServerVerify?.(request);
520
+ if (isRejectedVerification(verified)) {
521
+ if (previousState !== null) await client.restore(previousState);
522
+ setClientError({
523
+ client,
524
+ value: { code: "INVALID_PROOF", stampId: request.stampId }
525
+ });
526
+ } else {
527
+ notifyAcquisitionEvents(
528
+ result,
529
+ events,
530
+ options.onStampClaimed,
531
+ options.onRewardUnlocked
532
+ );
533
+ }
534
+ queuedMetadata.current.delete(request.idempotencyKey);
535
+ setOfflineQueue(
536
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
537
+ );
538
+ } catch (flushError) {
539
+ if (isNetworkFailure(flushError)) {
540
+ setClientError({ client, value: toError(flushError) });
541
+ break;
542
+ }
543
+ setClientError({ client, value: toError(flushError) });
544
+ queuedMetadata.current.delete(request.idempotencyKey);
545
+ setOfflineQueue(
546
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
547
+ );
548
+ }
549
+ }
550
+ } finally {
551
+ isFlushingQueue.current = false;
552
+ setIsOperationPending(false);
324
553
  }
325
- }, [acquire, syncAdapter]);
554
+ }, [client, events, offlineQueue, options.onRewardUnlocked, options.onStampClaimed, syncAdapter]);
555
+ useEffect(() => {
556
+ if (typeof window === "undefined") return;
557
+ const handleOnline = () => {
558
+ void flushQueue();
559
+ };
560
+ window.addEventListener("online", handleOnline);
561
+ return () => window.removeEventListener("online", handleOnline);
562
+ }, [flushQueue]);
326
563
  return {
327
564
  state: optimisticState,
328
565
  isLoading,
@@ -334,7 +571,11 @@ function useStampRally(client, options = {}) {
334
571
  redeem,
335
572
  exportRecoveryCode,
336
573
  importRecoveryCode,
337
- queuedCount: queuedRequests.current.length,
574
+ exportRecoveryToken,
575
+ importRecoveryToken,
576
+ syncWithServer,
577
+ offlineQueue,
578
+ queuedCount: offlineQueue.length,
338
579
  flushQueue
339
580
  };
340
581
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useStampRally.ts"],"names":["result"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n ConsumeResult,\n ProcessStampValue,\n Result,\n RewardConsumeError,\n RewardState,\n StampError,\n StampRallyClient,\n StampRallyState,\n VerificationContext,\n} from \"@stamprally/core\";\nimport { consumeReward, exportProgressToken, importProgressToken } from \"@stamprally/core\";\nimport {\n useCallback,\n useEffect,\n 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","error"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n ConsumeResult,\n ProcessStampValue,\n Result,\n RewardConsumeError,\n RewardState,\n SecureTokenOptions,\n StampError,\n StampRallyClient,\n StampRallyState,\n VerificationContext,\n} from \"@stamprally/core\";\nimport {\n consumeReward,\n createSecureToken,\n exportProgressToken,\n importProgressToken,\n isStampRallyState,\n verifySecureToken,\n} 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 readonly serverUserId?: string;\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 exportRecoveryToken: (\n secretKey: string,\n options?: SecureTokenOptions,\n ) => Promise<string>;\n readonly importRecoveryToken: (token: string, secretKey: string) => Promise<boolean>;\n readonly syncWithServer: (serverEndpoint: string, authHeader?: string) => Promise<void>;\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 isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\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 exportRecoveryToken = useCallback(\n async (secretKey: string, tokenOptions: SecureTokenOptions = {}): Promise<string> => {\n const state = client.getState();\n if (state === null)\n throw new Error(\"Cannot export recovery token before the rally is initialized.\");\n return createSecureToken(\n {\n type: \"recovery\",\n rallyId: state.rallyId,\n state,\n exportedAt: new Date().toISOString(),\n },\n secretKey,\n { encrypt: tokenOptions.encrypt ?? true, ...tokenOptions },\n );\n },\n [client],\n );\n\n const importRecoveryToken = useCallback(\n async (token: string, secretKey: string): Promise<boolean> => {\n setClientError(null);\n setIsOperationPending(true);\n try {\n const verified = await verifySecureToken(token, secretKey);\n if (\n !verified.ok ||\n verified.payload.type !== \"recovery\" ||\n verified.payload.rallyId !== client.getConfig().id\n ) {\n setIsOperationPending(false);\n return false;\n }\n const candidate = verified.payload.state;\n if (!isStampRallyState(candidate)) {\n setIsOperationPending(false);\n return false;\n }\n await client.restore(candidate);\n setIsOperationPending(false);\n return true;\n } catch (error) {\n setClientError({ client, value: toError(error) });\n setIsOperationPending(false);\n return false;\n }\n },\n [client],\n );\n\n const syncWithServer = useCallback(\n async (serverEndpoint: string, authHeader?: string): Promise<void> => {\n if (typeof fetch !== \"function\")\n throw new Error(\"Fetch API is unavailable in this environment.\");\n setClientError(null);\n setIsOperationPending(true);\n try {\n const endpoint = serverEndpoint.replace(/\\/$/u, \"\").endsWith(\"/sync\")\n ? serverEndpoint\n : `${serverEndpoint.replace(/\\/$/u, \"\")}/api/sync`;\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(authHeader === undefined ? {} : { authorization: authHeader }),\n },\n body: JSON.stringify({\n userId: options.serverUserId ?? \"anonymous\",\n queue: offlineQueue.map((request) => ({\n ...request,\n userId: options.serverUserId ?? \"anonymous\",\n spotId: request.stampId,\n claimMethod: request.context.type,\n proofData:\n request.context.type === \"token\"\n ? { token: request.context.token }\n : request.context.type === \"geo\"\n ? {\n latitude: request.context.currentLatitude,\n longitude: request.context.currentLongitude,\n }\n : undefined,\n })),\n }),\n });\n const payload: unknown = await response.json();\n if (\n !response.ok ||\n !isObject(payload) ||\n payload.ok !== true ||\n !isStampRallyState(payload.state)\n ) {\n throw new Error(\"Server synchronization was rejected.\");\n }\n await client.restore(payload.state);\n queuedMetadata.current.clear();\n setOfflineQueue([]);\n client.notifySyncCompleted(payload.state);\n } catch (error) {\n setClientError({ client, value: toError(error) });\n throw error;\n } finally {\n setIsOperationPending(false);\n }\n },\n [client, offlineQueue, options.serverUserId],\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 exportRecoveryToken,\n importRecoveryToken,\n syncWithServer,\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.0",
3
+ "version": "0.5.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.3.0"
34
+ "@stamprally/core": "0.5.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": "^18.0.0 || ^19.0.0",