@stamprally/react 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -36,10 +36,28 @@ function isOnline(adapter) {
36
36
  function isRejectedVerification(value) {
37
37
  return value === false || typeof value === "object" && value !== null && "ok" in value && value.ok === false;
38
38
  }
39
+ function isNetworkFailure(error) {
40
+ if (typeof navigator !== "undefined" && navigator.onLine === false) return true;
41
+ if (!(error instanceof Error)) return true;
42
+ return /aborted|connection|fetch|network|offline|timeout/i.test(`${error.name} ${error.message}`);
43
+ }
44
+ function notifyAcquisitionEvents(result, events, onStampClaimed, onRewardUnlocked) {
45
+ for (const event of result.events) {
46
+ if (event.type === "stampAcquired") {
47
+ (events?.onStampClaimed ?? onStampClaimed)?.(event.record);
48
+ }
49
+ if (event.type === "rewardUnlocked") {
50
+ (events?.onRewardUnlocked ?? onRewardUnlocked)?.(event.rewardId);
51
+ }
52
+ }
53
+ }
39
54
  function useStampRally(client, options = {}) {
40
55
  const syncAdapter = options.syncAdapter;
41
56
  const events = options.events;
42
- const queuedRequests = react.useRef([]);
57
+ const [offlineQueue, setOfflineQueue] = react.useState([]);
58
+ const queuedMetadata = react.useRef(/* @__PURE__ */ new Map());
59
+ const isFlushingQueue = react.useRef(false);
60
+ const activeClient = react.useRef(client);
43
61
  const subscribe = react.useCallback(
44
62
  (onStoreChange) => client.subscribe(() => onStoreChange()),
45
63
  [client]
@@ -57,6 +75,12 @@ function useStampRally(client, options = {}) {
57
75
  react.useEffect(() => {
58
76
  setOptimisticState(rawState);
59
77
  }, [rawState]);
78
+ react.useEffect(() => {
79
+ if (activeClient.current === client) return;
80
+ activeClient.current = client;
81
+ setOfflineQueue([]);
82
+ queuedMetadata.current.clear();
83
+ }, [client]);
60
84
  react.useEffect(() => {
61
85
  if (syncAdapter?.onStateChange === void 0) return;
62
86
  return client.subscribe(syncAdapter.onStateChange);
@@ -84,6 +108,15 @@ function useStampRally(client, options = {}) {
84
108
  active = false;
85
109
  };
86
110
  }, [client, rawState]);
111
+ const queueRequest = react.useCallback(
112
+ (request, metadata) => {
113
+ if (metadata !== void 0) queuedMetadata.current.set(request.idempotencyKey, metadata);
114
+ setOfflineQueue(
115
+ (current) => current.some((item) => item.idempotencyKey === request.idempotencyKey) ? current : [...current, request]
116
+ );
117
+ },
118
+ []
119
+ );
87
120
  const acquire = react.useCallback(
88
121
  (stampId, context, now, idempotencyKey) => {
89
122
  const acquiredAt = now ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -95,7 +128,7 @@ function useStampRally(client, options = {}) {
95
128
  };
96
129
  setClientError(null);
97
130
  if (!isOnline(syncAdapter)) {
98
- queuedRequests.current = [...queuedRequests.current, request];
131
+ queueRequest(request);
99
132
  const queued = {
100
133
  ok: false,
101
134
  error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
@@ -119,9 +152,29 @@ function useStampRally(client, options = {}) {
119
152
  resolve(rejected);
120
153
  return;
121
154
  }
155
+ const previousState = client.getState();
122
156
  const result = await client.acquire(stampId, context, acquiredAt);
123
157
  if (result.ok) {
124
- const verified = await syncAdapter?.onServerVerify?.(request);
158
+ let verified;
159
+ try {
160
+ verified = await syncAdapter?.onServerVerify?.(request);
161
+ } catch (verificationError) {
162
+ if (!isNetworkFailure(verificationError)) throw verificationError;
163
+ queueRequest(request, { previousState, result: result.value });
164
+ const queued = {
165
+ ok: false,
166
+ error: {
167
+ code: "OFFLINE_QUEUED",
168
+ stampId,
169
+ idempotencyKey: request.idempotencyKey
170
+ }
171
+ };
172
+ setClientError({ client, value: toError(verificationError) });
173
+ setOptimisticState(client.getState());
174
+ setIsOperationPending(false);
175
+ resolve(queued);
176
+ return;
177
+ }
125
178
  if (isRejectedVerification(verified)) {
126
179
  await client.restore(rawState ?? client.getState() ?? result.value.nextState);
127
180
  const rejected = {
@@ -134,14 +187,12 @@ function useStampRally(client, options = {}) {
134
187
  resolve(rejected);
135
188
  return;
136
189
  }
137
- 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
- }
190
+ notifyAcquisitionEvents(
191
+ result.value,
192
+ events,
193
+ options.onStampClaimed,
194
+ options.onRewardUnlocked
195
+ );
145
196
  }
146
197
  if (!result.ok) {
147
198
  setClientError({ client, value: result.error });
@@ -150,6 +201,18 @@ function useStampRally(client, options = {}) {
150
201
  setIsOperationPending(false);
151
202
  resolve(result);
152
203
  } catch (acquireError) {
204
+ if (isNetworkFailure(acquireError)) {
205
+ queueRequest(request);
206
+ const queued = {
207
+ ok: false,
208
+ error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
209
+ };
210
+ setClientError({ client, value: queued.error });
211
+ setOptimisticState(client.getState());
212
+ setIsOperationPending(false);
213
+ resolve(queued);
214
+ return;
215
+ }
153
216
  const normalizedError = toError(acquireError);
154
217
  setClientError({ client, value: normalizedError });
155
218
  setOptimisticState(client.getState());
@@ -159,7 +222,15 @@ function useStampRally(client, options = {}) {
159
222
  })();
160
223
  });
161
224
  },
162
- [client, events, options.onRewardUnlocked, options.onStampClaimed, rawState, syncAdapter]
225
+ [
226
+ client,
227
+ events,
228
+ options.onRewardUnlocked,
229
+ options.onStampClaimed,
230
+ queueRequest,
231
+ rawState,
232
+ syncAdapter
233
+ ]
163
234
  );
164
235
  const reset = react.useCallback(
165
236
  (now) => {
@@ -318,13 +389,87 @@ function useStampRally(client, options = {}) {
318
389
  const isLoading = rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);
319
390
  const error = clientError?.client === client ? clientError.value : null;
320
391
  const flushQueue = react.useCallback(async () => {
321
- if (!isOnline(syncAdapter)) return;
322
- const pending = queuedRequests.current;
323
- queuedRequests.current = [];
324
- for (const request of pending) {
325
- await acquire(request.stampId, request.context, request.now, request.idempotencyKey);
392
+ if (!isOnline(syncAdapter) || isFlushingQueue.current) return;
393
+ isFlushingQueue.current = true;
394
+ setIsOperationPending(true);
395
+ try {
396
+ for (const request of offlineQueue) {
397
+ if (!isOnline(syncAdapter)) break;
398
+ const metadata = queuedMetadata.current.get(request.idempotencyKey);
399
+ const previousState = metadata?.previousState ?? client.getState();
400
+ try {
401
+ const before = await syncAdapter?.onBeforeCheckIn?.(request);
402
+ if (before === false) {
403
+ if (metadata !== void 0 && previousState !== null)
404
+ await client.restore(previousState);
405
+ setClientError({ client, value: { code: "INVALID_PROOF", stampId: request.stampId } });
406
+ queuedMetadata.current.delete(request.idempotencyKey);
407
+ setOfflineQueue(
408
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
409
+ );
410
+ continue;
411
+ }
412
+ let result = metadata?.result;
413
+ if (result === void 0) {
414
+ const localResult = await client.acquire(request.stampId, request.context, request.now);
415
+ if (!localResult.ok) {
416
+ setClientError({ client, value: localResult.error });
417
+ queuedMetadata.current.delete(request.idempotencyKey);
418
+ setOfflineQueue(
419
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
420
+ );
421
+ continue;
422
+ }
423
+ result = localResult.value;
424
+ queuedMetadata.current.set(request.idempotencyKey, {
425
+ previousState,
426
+ result
427
+ });
428
+ }
429
+ const verified = await syncAdapter?.onServerVerify?.(request);
430
+ if (isRejectedVerification(verified)) {
431
+ if (previousState !== null) await client.restore(previousState);
432
+ setClientError({
433
+ client,
434
+ value: { code: "INVALID_PROOF", stampId: request.stampId }
435
+ });
436
+ } else {
437
+ notifyAcquisitionEvents(
438
+ result,
439
+ events,
440
+ options.onStampClaimed,
441
+ options.onRewardUnlocked
442
+ );
443
+ }
444
+ queuedMetadata.current.delete(request.idempotencyKey);
445
+ setOfflineQueue(
446
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
447
+ );
448
+ } catch (flushError) {
449
+ if (isNetworkFailure(flushError)) {
450
+ setClientError({ client, value: toError(flushError) });
451
+ break;
452
+ }
453
+ setClientError({ client, value: toError(flushError) });
454
+ queuedMetadata.current.delete(request.idempotencyKey);
455
+ setOfflineQueue(
456
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
457
+ );
458
+ }
459
+ }
460
+ } finally {
461
+ isFlushingQueue.current = false;
462
+ setIsOperationPending(false);
326
463
  }
327
- }, [acquire, syncAdapter]);
464
+ }, [client, events, offlineQueue, options.onRewardUnlocked, options.onStampClaimed, syncAdapter]);
465
+ react.useEffect(() => {
466
+ if (typeof window === "undefined") return;
467
+ const handleOnline = () => {
468
+ void flushQueue();
469
+ };
470
+ window.addEventListener("online", handleOnline);
471
+ return () => window.removeEventListener("online", handleOnline);
472
+ }, [flushQueue]);
328
473
  return {
329
474
  state: optimisticState,
330
475
  isLoading,
@@ -336,7 +481,8 @@ function useStampRally(client, options = {}) {
336
481
  redeem,
337
482
  exportRecoveryCode,
338
483
  importRecoveryCode,
339
- queuedCount: queuedRequests.current.length,
484
+ offlineQueue,
485
+ queuedCount: offlineQueue.length,
340
486
  flushQueue
341
487
  };
342
488
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useStampRally.ts"],"names":["useRef","useCallback","useSyncExternalStore","useState","useTransition","useEffect","result","consumeReward","exportProgressToken","importProgressToken"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n ConsumeResult,\n ProcessStampValue,\n Result,\n RewardConsumeError,\n RewardState,\n StampError,\n StampRallyClient,\n StampRallyState,\n VerificationContext,\n} from \"@stamprally/core\";\nimport { consumeReward, exportProgressToken, importProgressToken } from \"@stamprally/core\";\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n useTransition,\n} from \"react\";\n\ninterface OptimisticAcquire {\n readonly stampId: string;\n readonly acquiredAt: string;\n}\n\ninterface ClientStatus {\n readonly client: StampRallyClient;\n readonly isInitializing: boolean;\n}\n\ninterface ClientError {\n readonly client: StampRallyClient;\n readonly value: StampError | RewardConsumeError | Error;\n}\n\nexport interface CheckInRequest {\n readonly stampId: string;\n readonly context: VerificationContext;\n readonly now: string;\n readonly idempotencyKey: string;\n}\n\nexport interface SyncAdapter {\n readonly isOnline?: boolean | (() => boolean);\n readonly onBeforeCheckIn?: (request: CheckInRequest) => unknown;\n readonly onServerVerify?: (request: CheckInRequest) => unknown;\n readonly onStateChange?: (state: StampRallyState) => void;\n}\n\nexport interface StampRallyEventHandlers {\n readonly onStampClaimed?: (record: StampRallyState[\"records\"][number]) => void;\n readonly onRewardUnlocked?: (rewardId: string) => void;\n readonly onRewardConsumed?: (rewardId: string) => void;\n}\n\nexport interface UseStampRallyOptions {\n readonly syncAdapter?: SyncAdapter;\n readonly events?: StampRallyEventHandlers;\n readonly onStampClaimed?: StampRallyEventHandlers[\"onStampClaimed\"];\n readonly onRewardUnlocked?: StampRallyEventHandlers[\"onRewardUnlocked\"];\n readonly onRewardConsumed?: StampRallyEventHandlers[\"onRewardConsumed\"];\n}\n\nexport interface RedeemOptions {\n readonly passcode?: string;\n readonly staffId?: string;\n}\n\nexport interface UseStampRallyReturn {\n readonly state: StampRallyState | null;\n readonly isLoading: boolean;\n readonly isPending: boolean;\n readonly error: StampError | RewardConsumeError | Error | null;\n readonly rewardsState: ReadonlyArray<RewardState>;\n readonly acquire: (\n stampId: string,\n context: VerificationContext,\n now?: string,\n idempotencyKey?: string,\n ) => Promise<Result<ProcessStampValue, StampError>>;\n readonly reset: (now?: string) => Promise<StampRallyState>;\n readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;\n readonly exportRecoveryCode: () => string;\n readonly importRecoveryCode: (token: string) => Promise<boolean>;\n readonly queuedCount: number;\n readonly flushQueue: () => Promise<void>;\n}\n\n/** @deprecated Use UseStampRallyReturn instead. */\nexport type UseStampRallyValue = UseStampRallyReturn;\n\nfunction getServerSnapshot(): null {\n return null;\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction applyOptimisticAcquire(\n currentState: StampRallyState | null,\n action: OptimisticAcquire,\n): StampRallyState | null {\n if (\n currentState === null ||\n currentState.records.some((record) => record.stampId === action.stampId)\n ) {\n return currentState;\n }\n\n return {\n ...currentState,\n records: [...currentState.records, { stampId: action.stampId, acquiredAt: action.acquiredAt }],\n updatedAt: action.acquiredAt,\n };\n}\n\nfunction createIdempotencyKey(): string {\n const cryptoApi = globalThis.crypto;\n if (cryptoApi !== undefined && typeof cryptoApi.randomUUID === \"function\") {\n return cryptoApi.randomUUID();\n }\n return `stamp-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction isOnline(adapter: SyncAdapter | undefined): boolean {\n if (adapter?.isOnline === undefined) {\n return typeof navigator === \"undefined\" || navigator.onLine !== false;\n }\n return typeof adapter.isOnline === \"function\" ? adapter.isOnline() : adapter.isOnline;\n}\n\nfunction isRejectedVerification(value: unknown): boolean {\n return (\n value === false ||\n (typeof value === \"object\" && value !== null && \"ok\" in value && value.ok === false)\n );\n}\n\nexport function useStampRally(\n client: StampRallyClient,\n options: UseStampRallyOptions = {},\n): UseStampRallyReturn {\n const syncAdapter = options.syncAdapter;\n const events = options.events;\n const queuedRequests = useRef<ReadonlyArray<CheckInRequest>>([]);\n const subscribe = useCallback(\n (onStoreChange: () => void) => client.subscribe(() => onStoreChange()),\n [client],\n );\n const getSnapshot = useCallback(() => client.getState(), [client]);\n const rawState = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n const [clientStatus, setClientStatus] = useState<ClientStatus>(() => ({\n client,\n isInitializing: client.getState() === null,\n }));\n const [clientError, setClientError] = useState<ClientError | null>(null);\n const [isPending] = useTransition();\n const [isOperationPending, setIsOperationPending] = useState(false);\n const [optimisticState, setOptimisticState] = useState<StampRallyState | null>(rawState);\n\n useEffect(() => {\n setOptimisticState(rawState);\n }, [rawState]);\n\n useEffect(() => {\n if (syncAdapter?.onStateChange === undefined) return;\n return client.subscribe(syncAdapter.onStateChange);\n }, [client, syncAdapter]);\n\n useEffect(() => {\n let active = true;\n setClientError(null);\n if (rawState !== null) {\n setClientStatus({ client, isInitializing: false });\n return () => {\n active = false;\n };\n }\n\n setClientStatus({ client, isInitializing: true });\n\n void client\n .init()\n .catch((initializationError: unknown) => {\n if (active) {\n setClientError({ client, value: toError(initializationError) });\n }\n })\n .finally(() => {\n if (active) {\n setClientStatus({ client, isInitializing: false });\n }\n });\n\n return () => {\n active = false;\n };\n }, [client, rawState]);\n\n const acquire = useCallback(\n (\n stampId: string,\n context: VerificationContext,\n now?: string,\n idempotencyKey?: string,\n ): Promise<Result<ProcessStampValue, StampError>> => {\n const acquiredAt = now ?? new Date().toISOString();\n const request: CheckInRequest = {\n stampId,\n context,\n now: acquiredAt,\n idempotencyKey: idempotencyKey ?? createIdempotencyKey(),\n };\n setClientError(null);\n\n if (!isOnline(syncAdapter)) {\n queuedRequests.current = [...queuedRequests.current, request];\n const queued: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"OFFLINE_QUEUED\", stampId, idempotencyKey: request.idempotencyKey },\n };\n setClientError({ client, value: queued.error });\n return Promise.resolve(queued);\n }\n\n setIsOperationPending(true);\n setOptimisticState((current) => applyOptimisticAcquire(current, { stampId, acquiredAt }));\n return new Promise((resolve, reject) => {\n void (async () => {\n try {\n const before = await syncAdapter?.onBeforeCheckIn?.(request);\n if (before === false) {\n const rejected: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"INVALID_PROOF\", stampId },\n };\n setClientError({ client, value: rejected.error });\n setIsOperationPending(false);\n resolve(rejected);\n return;\n }\n const result = await client.acquire(stampId, context, acquiredAt);\n if (result.ok) {\n const verified = await syncAdapter?.onServerVerify?.(request);\n if (isRejectedVerification(verified)) {\n await client.restore(rawState ?? client.getState() ?? result.value.nextState);\n const rejected: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"INVALID_PROOF\", stampId },\n };\n setClientError({ client, value: rejected.error });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(rejected);\n return;\n }\n for (const event of result.value.events) {\n if (event.type === \"stampAcquired\") {\n (events?.onStampClaimed ?? options.onStampClaimed)?.(event.record);\n }\n if (event.type === \"rewardUnlocked\") {\n (events?.onRewardUnlocked ?? options.onRewardUnlocked)?.(event.rewardId);\n }\n }\n }\n if (!result.ok) {\n setClientError({ client, value: result.error });\n setOptimisticState(client.getState());\n }\n setIsOperationPending(false);\n resolve(result);\n } catch (acquireError) {\n const normalizedError = toError(acquireError);\n setClientError({ client, value: normalizedError });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client, events, options.onRewardUnlocked, options.onStampClaimed, rawState, syncAdapter],\n );\n\n const reset = useCallback(\n (now?: string): Promise<StampRallyState> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n try {\n const nextState = now === undefined ? await client.reset() : await client.reset(now);\n setIsOperationPending(false);\n resolve(nextState);\n } catch (resetError) {\n const normalizedError = toError(resetError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client],\n );\n\n const redeem = useCallback(\n (rewardId: string, redeemOptions: RedeemOptions = {}): Promise<ConsumeResult> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n const reward = client.getConfig().rewards?.find((item) => item.id === rewardId);\n if (reward === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"REWARD_NOT_FOUND\", rewardId },\n };\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const currentState = client.getState();\n const currentRewardState = currentState?.rewards?.find(\n (state) => state.rewardId === rewardId,\n );\n if (currentState === null || currentRewardState === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"NOT_AVAILABLE\", rewardId },\n };\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const result = consumeReward({\n reward,\n currentState: currentRewardState,\n now: new Date().toISOString(),\n ...(redeemOptions.passcode === undefined\n ? {}\n : { inputPasscode: redeemOptions.passcode }),\n ...(redeemOptions.staffId === undefined ? {} : { staffId: redeemOptions.staffId }),\n });\n if (!result.ok) {\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n if (result.value === currentRewardState) {\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const nextState: StampRallyState = {\n ...currentState,\n rewards: (currentState.rewards ?? []).map((state) =>\n state.rewardId === rewardId ? result.value : state,\n ),\n updatedAt: result.value.consumedAt ?? currentState.updatedAt,\n };\n\n try {\n await client.restore(nextState);\n (events?.onRewardConsumed ?? options.onRewardConsumed)?.(rewardId);\n setIsOperationPending(false);\n resolve(result);\n } catch (redeemError) {\n const normalizedError = toError(redeemError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client, events, options.onRewardConsumed],\n );\n\n const exportRecoveryCode = useCallback((): string => {\n const state = client.getState();\n if (state === null) {\n throw new Error(\"Cannot export recovery code before the rally is initialized.\");\n }\n\n return exportProgressToken({\n version: 1,\n rallyId: state.rallyId,\n stamps: state.records,\n rewards: state.rewards ?? [],\n exportedAt: new Date().toISOString(),\n });\n }, [client]);\n\n const importRecoveryCode = useCallback(\n (token: string): Promise<boolean> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n const config = client.getConfig();\n const snapshot = importProgressToken(token, config.id);\n if (snapshot === null) {\n setIsOperationPending(false);\n resolve(false);\n return;\n }\n\n const stampIds = new Set(config.stamps.map((stamp) => stamp.id));\n const rewardIds = new Set((config.rewards ?? []).map((reward) => reward.id));\n const importedStampIds = new Set<string>();\n const importedRewardIds = new Set<string>();\n const stamps = snapshot.stamps.filter((record) => {\n if (!stampIds.has(record.stampId) || importedStampIds.has(record.stampId)) return false;\n importedStampIds.add(record.stampId);\n return true;\n });\n const rewards = snapshot.rewards.filter((state) => {\n if (!rewardIds.has(state.rewardId) || importedRewardIds.has(state.rewardId))\n return false;\n importedRewardIds.add(state.rewardId);\n return true;\n });\n\n try {\n await client.restore({\n rallyId: config.id,\n records: stamps,\n ...(config.rewards === undefined && rewards.length === 0 ? {} : { rewards }),\n updatedAt: snapshot.exportedAt,\n });\n setIsOperationPending(false);\n resolve(true);\n } catch (importError) {\n const normalizedError = toError(importError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client],\n );\n\n const isLoading =\n rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);\n const error = clientError?.client === client ? clientError.value : null;\n\n const flushQueue = useCallback(async (): Promise<void> => {\n if (!isOnline(syncAdapter)) return;\n const pending = queuedRequests.current;\n queuedRequests.current = [];\n for (const request of pending) {\n await acquire(request.stampId, request.context, request.now, request.idempotencyKey);\n }\n }, [acquire, syncAdapter]);\n\n return {\n state: optimisticState,\n isLoading,\n isPending: isPending || isOperationPending,\n error,\n rewardsState: optimisticState?.rewards ?? [],\n acquire,\n reset,\n redeem,\n exportRecoveryCode,\n importRecoveryCode,\n queuedCount: queuedRequests.current.length,\n flushQueue,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/useStampRally.ts"],"names":["useState","useRef","useCallback","useSyncExternalStore","useTransition","useEffect","result","consumeReward","exportProgressToken","importProgressToken"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n ConsumeResult,\n ProcessStampValue,\n Result,\n RewardConsumeError,\n RewardState,\n StampError,\n StampRallyClient,\n StampRallyState,\n VerificationContext,\n} from \"@stamprally/core\";\nimport { consumeReward, exportProgressToken, importProgressToken } from \"@stamprally/core\";\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n useTransition,\n} from \"react\";\n\ninterface OptimisticAcquire {\n readonly stampId: string;\n readonly acquiredAt: string;\n}\n\ninterface ClientStatus {\n readonly client: StampRallyClient;\n readonly isInitializing: boolean;\n}\n\ninterface ClientError {\n readonly client: StampRallyClient;\n readonly value: StampError | RewardConsumeError | Error;\n}\n\ninterface QueuedRequestMetadata {\n readonly previousState: StampRallyState | null;\n readonly result: ProcessStampValue;\n}\n\nexport interface CheckInRequest {\n readonly stampId: string;\n readonly context: VerificationContext;\n readonly now: string;\n readonly idempotencyKey: string;\n}\n\nexport interface SyncAdapter {\n readonly isOnline?: boolean | (() => boolean);\n readonly onBeforeCheckIn?: (request: CheckInRequest) => unknown;\n readonly onServerVerify?: (request: CheckInRequest) => unknown;\n readonly onStateChange?: (state: StampRallyState) => void;\n}\n\nexport interface StampRallyEventHandlers {\n readonly onStampClaimed?: (record: StampRallyState[\"records\"][number]) => void;\n readonly onRewardUnlocked?: (rewardId: string) => void;\n readonly onRewardConsumed?: (rewardId: string) => void;\n}\n\nexport interface UseStampRallyOptions {\n readonly syncAdapter?: SyncAdapter;\n readonly events?: StampRallyEventHandlers;\n readonly onStampClaimed?: StampRallyEventHandlers[\"onStampClaimed\"];\n readonly onRewardUnlocked?: StampRallyEventHandlers[\"onRewardUnlocked\"];\n readonly onRewardConsumed?: StampRallyEventHandlers[\"onRewardConsumed\"];\n}\n\nexport interface RedeemOptions {\n readonly passcode?: string;\n readonly staffId?: string;\n}\n\nexport interface UseStampRallyReturn {\n readonly state: StampRallyState | null;\n readonly isLoading: boolean;\n readonly isPending: boolean;\n readonly error: StampError | RewardConsumeError | Error | null;\n readonly rewardsState: ReadonlyArray<RewardState>;\n readonly acquire: (\n stampId: string,\n context: VerificationContext,\n now?: string,\n idempotencyKey?: string,\n ) => Promise<Result<ProcessStampValue, StampError>>;\n readonly reset: (now?: string) => Promise<StampRallyState>;\n readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;\n readonly exportRecoveryCode: () => string;\n readonly importRecoveryCode: (token: string) => Promise<boolean>;\n readonly offlineQueue: ReadonlyArray<CheckInRequest>;\n readonly queuedCount: number;\n readonly flushQueue: () => Promise<void>;\n}\n\n/** @deprecated Use UseStampRallyReturn instead. */\nexport type UseStampRallyValue = UseStampRallyReturn;\n\nfunction getServerSnapshot(): null {\n return null;\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction applyOptimisticAcquire(\n currentState: StampRallyState | null,\n action: OptimisticAcquire,\n): StampRallyState | null {\n if (\n currentState === null ||\n currentState.records.some((record) => record.stampId === action.stampId)\n ) {\n return currentState;\n }\n\n return {\n ...currentState,\n records: [...currentState.records, { stampId: action.stampId, acquiredAt: action.acquiredAt }],\n updatedAt: action.acquiredAt,\n };\n}\n\nfunction createIdempotencyKey(): string {\n const cryptoApi = globalThis.crypto;\n if (cryptoApi !== undefined && typeof cryptoApi.randomUUID === \"function\") {\n return cryptoApi.randomUUID();\n }\n return `stamp-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction isOnline(adapter: SyncAdapter | undefined): boolean {\n if (adapter?.isOnline === undefined) {\n return typeof navigator === \"undefined\" || navigator.onLine !== false;\n }\n return typeof adapter.isOnline === \"function\" ? adapter.isOnline() : adapter.isOnline;\n}\n\nfunction isRejectedVerification(value: unknown): boolean {\n return (\n value === false ||\n (typeof value === \"object\" && value !== null && \"ok\" in value && value.ok === false)\n );\n}\n\nfunction isNetworkFailure(error: unknown): boolean {\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) return true;\n if (!(error instanceof Error)) return true;\n return /aborted|connection|fetch|network|offline|timeout/i.test(`${error.name} ${error.message}`);\n}\n\nfunction notifyAcquisitionEvents(\n result: ProcessStampValue,\n events: StampRallyEventHandlers | undefined,\n onStampClaimed: StampRallyEventHandlers[\"onStampClaimed\"] | undefined,\n onRewardUnlocked: StampRallyEventHandlers[\"onRewardUnlocked\"] | undefined,\n): void {\n for (const event of result.events) {\n if (event.type === \"stampAcquired\") {\n (events?.onStampClaimed ?? onStampClaimed)?.(event.record);\n }\n if (event.type === \"rewardUnlocked\") {\n (events?.onRewardUnlocked ?? onRewardUnlocked)?.(event.rewardId);\n }\n }\n}\n\nexport function useStampRally(\n client: StampRallyClient,\n options: UseStampRallyOptions = {},\n): UseStampRallyReturn {\n const syncAdapter = options.syncAdapter;\n const events = options.events;\n const [offlineQueue, setOfflineQueue] = useState<ReadonlyArray<CheckInRequest>>([]);\n const queuedMetadata = useRef(new Map<string, QueuedRequestMetadata>());\n const isFlushingQueue = useRef(false);\n const activeClient = useRef(client);\n const subscribe = useCallback(\n (onStoreChange: () => void) => client.subscribe(() => onStoreChange()),\n [client],\n );\n const getSnapshot = useCallback(() => client.getState(), [client]);\n const rawState = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n const [clientStatus, setClientStatus] = useState<ClientStatus>(() => ({\n client,\n isInitializing: client.getState() === null,\n }));\n const [clientError, setClientError] = useState<ClientError | null>(null);\n const [isPending] = useTransition();\n const [isOperationPending, setIsOperationPending] = useState(false);\n const [optimisticState, setOptimisticState] = useState<StampRallyState | null>(rawState);\n\n useEffect(() => {\n setOptimisticState(rawState);\n }, [rawState]);\n\n useEffect(() => {\n if (activeClient.current === client) return;\n activeClient.current = client;\n setOfflineQueue([]);\n queuedMetadata.current.clear();\n }, [client]);\n\n useEffect(() => {\n if (syncAdapter?.onStateChange === undefined) return;\n return client.subscribe(syncAdapter.onStateChange);\n }, [client, syncAdapter]);\n\n useEffect(() => {\n let active = true;\n setClientError(null);\n if (rawState !== null) {\n setClientStatus({ client, isInitializing: false });\n return () => {\n active = false;\n };\n }\n\n setClientStatus({ client, isInitializing: true });\n\n void client\n .init()\n .catch((initializationError: unknown) => {\n if (active) {\n setClientError({ client, value: toError(initializationError) });\n }\n })\n .finally(() => {\n if (active) {\n setClientStatus({ client, isInitializing: false });\n }\n });\n\n return () => {\n active = false;\n };\n }, [client, rawState]);\n\n const queueRequest = useCallback(\n (request: CheckInRequest, metadata?: QueuedRequestMetadata): void => {\n if (metadata !== undefined) queuedMetadata.current.set(request.idempotencyKey, metadata);\n setOfflineQueue((current) =>\n current.some((item) => item.idempotencyKey === request.idempotencyKey)\n ? current\n : [...current, request],\n );\n },\n [],\n );\n\n const acquire = useCallback(\n (\n stampId: string,\n context: VerificationContext,\n now?: string,\n idempotencyKey?: string,\n ): Promise<Result<ProcessStampValue, StampError>> => {\n const acquiredAt = now ?? new Date().toISOString();\n const request: CheckInRequest = {\n stampId,\n context,\n now: acquiredAt,\n idempotencyKey: idempotencyKey ?? createIdempotencyKey(),\n };\n setClientError(null);\n\n if (!isOnline(syncAdapter)) {\n queueRequest(request);\n const queued: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"OFFLINE_QUEUED\", stampId, idempotencyKey: request.idempotencyKey },\n };\n setClientError({ client, value: queued.error });\n return Promise.resolve(queued);\n }\n\n setIsOperationPending(true);\n setOptimisticState((current) => applyOptimisticAcquire(current, { stampId, acquiredAt }));\n return new Promise((resolve, reject) => {\n void (async () => {\n try {\n const before = await syncAdapter?.onBeforeCheckIn?.(request);\n if (before === false) {\n const rejected: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"INVALID_PROOF\", stampId },\n };\n setClientError({ client, value: rejected.error });\n setIsOperationPending(false);\n resolve(rejected);\n return;\n }\n const previousState = client.getState();\n const result = await client.acquire(stampId, context, acquiredAt);\n if (result.ok) {\n let verified: unknown;\n try {\n verified = await syncAdapter?.onServerVerify?.(request);\n } catch (verificationError) {\n if (!isNetworkFailure(verificationError)) throw verificationError;\n queueRequest(request, { previousState, result: result.value });\n const queued: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: {\n code: \"OFFLINE_QUEUED\",\n stampId,\n idempotencyKey: request.idempotencyKey,\n },\n };\n setClientError({ client, value: toError(verificationError) });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(queued);\n return;\n }\n if (isRejectedVerification(verified)) {\n await client.restore(rawState ?? client.getState() ?? result.value.nextState);\n const rejected: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"INVALID_PROOF\", stampId },\n };\n setClientError({ client, value: rejected.error });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(rejected);\n return;\n }\n notifyAcquisitionEvents(\n result.value,\n events,\n options.onStampClaimed,\n options.onRewardUnlocked,\n );\n }\n if (!result.ok) {\n setClientError({ client, value: result.error });\n setOptimisticState(client.getState());\n }\n setIsOperationPending(false);\n resolve(result);\n } catch (acquireError) {\n if (isNetworkFailure(acquireError)) {\n queueRequest(request);\n const queued: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"OFFLINE_QUEUED\", stampId, idempotencyKey: request.idempotencyKey },\n };\n setClientError({ client, value: queued.error });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(queued);\n return;\n }\n const normalizedError = toError(acquireError);\n setClientError({ client, value: normalizedError });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [\n client,\n events,\n options.onRewardUnlocked,\n options.onStampClaimed,\n queueRequest,\n rawState,\n syncAdapter,\n ],\n );\n\n const reset = useCallback(\n (now?: string): Promise<StampRallyState> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n try {\n const nextState = now === undefined ? await client.reset() : await client.reset(now);\n setIsOperationPending(false);\n resolve(nextState);\n } catch (resetError) {\n const normalizedError = toError(resetError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client],\n );\n\n const redeem = useCallback(\n (rewardId: string, redeemOptions: RedeemOptions = {}): Promise<ConsumeResult> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n const reward = client.getConfig().rewards?.find((item) => item.id === rewardId);\n if (reward === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"REWARD_NOT_FOUND\", rewardId },\n };\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const currentState = client.getState();\n const currentRewardState = currentState?.rewards?.find(\n (state) => state.rewardId === rewardId,\n );\n if (currentState === null || currentRewardState === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"NOT_AVAILABLE\", rewardId },\n };\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const result = consumeReward({\n reward,\n currentState: currentRewardState,\n now: new Date().toISOString(),\n ...(redeemOptions.passcode === undefined\n ? {}\n : { inputPasscode: redeemOptions.passcode }),\n ...(redeemOptions.staffId === undefined ? {} : { staffId: redeemOptions.staffId }),\n });\n if (!result.ok) {\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n if (result.value === currentRewardState) {\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const nextState: StampRallyState = {\n ...currentState,\n rewards: (currentState.rewards ?? []).map((state) =>\n state.rewardId === rewardId ? result.value : state,\n ),\n updatedAt: result.value.consumedAt ?? currentState.updatedAt,\n };\n\n try {\n await client.restore(nextState);\n (events?.onRewardConsumed ?? options.onRewardConsumed)?.(rewardId);\n setIsOperationPending(false);\n resolve(result);\n } catch (redeemError) {\n const normalizedError = toError(redeemError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client, events, options.onRewardConsumed],\n );\n\n const exportRecoveryCode = useCallback((): string => {\n const state = client.getState();\n if (state === null) {\n throw new Error(\"Cannot export recovery code before the rally is initialized.\");\n }\n\n return exportProgressToken({\n version: 1,\n rallyId: state.rallyId,\n stamps: state.records,\n rewards: state.rewards ?? [],\n exportedAt: new Date().toISOString(),\n });\n }, [client]);\n\n const importRecoveryCode = useCallback(\n (token: string): Promise<boolean> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n const config = client.getConfig();\n const snapshot = importProgressToken(token, config.id);\n if (snapshot === null) {\n setIsOperationPending(false);\n resolve(false);\n return;\n }\n\n const stampIds = new Set(config.stamps.map((stamp) => stamp.id));\n const rewardIds = new Set((config.rewards ?? []).map((reward) => reward.id));\n const importedStampIds = new Set<string>();\n const importedRewardIds = new Set<string>();\n const stamps = snapshot.stamps.filter((record) => {\n if (!stampIds.has(record.stampId) || importedStampIds.has(record.stampId)) return false;\n importedStampIds.add(record.stampId);\n return true;\n });\n const rewards = snapshot.rewards.filter((state) => {\n if (!rewardIds.has(state.rewardId) || importedRewardIds.has(state.rewardId))\n return false;\n importedRewardIds.add(state.rewardId);\n return true;\n });\n\n try {\n await client.restore({\n rallyId: config.id,\n records: stamps,\n ...(config.rewards === undefined && rewards.length === 0 ? {} : { rewards }),\n updatedAt: snapshot.exportedAt,\n });\n setIsOperationPending(false);\n resolve(true);\n } catch (importError) {\n const normalizedError = toError(importError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client],\n );\n\n const isLoading =\n rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);\n const error = clientError?.client === client ? clientError.value : null;\n\n const flushQueue = useCallback(async (): Promise<void> => {\n if (!isOnline(syncAdapter) || isFlushingQueue.current) return;\n isFlushingQueue.current = true;\n setIsOperationPending(true);\n try {\n for (const request of offlineQueue) {\n if (!isOnline(syncAdapter)) break;\n const metadata = queuedMetadata.current.get(request.idempotencyKey);\n const previousState = metadata?.previousState ?? client.getState();\n try {\n const before = await syncAdapter?.onBeforeCheckIn?.(request);\n if (before === false) {\n if (metadata !== undefined && previousState !== null)\n await client.restore(previousState);\n setClientError({ client, value: { code: \"INVALID_PROOF\", stampId: request.stampId } });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n continue;\n }\n\n let result = metadata?.result;\n if (result === undefined) {\n const localResult = await client.acquire(request.stampId, request.context, request.now);\n if (!localResult.ok) {\n setClientError({ client, value: localResult.error });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n continue;\n }\n result = localResult.value;\n queuedMetadata.current.set(request.idempotencyKey, {\n previousState,\n result,\n });\n }\n\n const verified = await syncAdapter?.onServerVerify?.(request);\n if (isRejectedVerification(verified)) {\n if (previousState !== null) await client.restore(previousState);\n setClientError({\n client,\n value: { code: \"INVALID_PROOF\", stampId: request.stampId },\n });\n } else {\n notifyAcquisitionEvents(\n result,\n events,\n options.onStampClaimed,\n options.onRewardUnlocked,\n );\n }\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n } catch (flushError) {\n if (isNetworkFailure(flushError)) {\n setClientError({ client, value: toError(flushError) });\n break;\n }\n setClientError({ client, value: toError(flushError) });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n }\n }\n } finally {\n isFlushingQueue.current = false;\n setIsOperationPending(false);\n }\n }, [client, events, offlineQueue, options.onRewardUnlocked, options.onStampClaimed, syncAdapter]);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const handleOnline = (): void => {\n void flushQueue();\n };\n window.addEventListener(\"online\", handleOnline);\n return () => window.removeEventListener(\"online\", handleOnline);\n }, [flushQueue]);\n\n return {\n state: optimisticState,\n isLoading,\n isPending: isPending || isOperationPending,\n error,\n rewardsState: optimisticState?.rewards ?? [],\n acquire,\n reset,\n redeem,\n exportRecoveryCode,\n importRecoveryCode,\n offlineQueue,\n queuedCount: offlineQueue.length,\n flushQueue,\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -39,6 +39,7 @@ interface UseStampRallyReturn {
39
39
  readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;
40
40
  readonly exportRecoveryCode: () => string;
41
41
  readonly importRecoveryCode: (token: string) => Promise<boolean>;
42
+ readonly offlineQueue: ReadonlyArray<CheckInRequest>;
42
43
  readonly queuedCount: number;
43
44
  readonly flushQueue: () => Promise<void>;
44
45
  }
package/dist/index.d.ts CHANGED
@@ -39,6 +39,7 @@ interface UseStampRallyReturn {
39
39
  readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;
40
40
  readonly exportRecoveryCode: () => string;
41
41
  readonly importRecoveryCode: (token: string) => Promise<boolean>;
42
+ readonly offlineQueue: ReadonlyArray<CheckInRequest>;
42
43
  readonly queuedCount: number;
43
44
  readonly flushQueue: () => Promise<void>;
44
45
  }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { consumeReward, exportProgressToken, importProgressToken } from '@stamprally/core';
2
- import { useRef, useCallback, useSyncExternalStore, useState, useTransition, useEffect } from 'react';
2
+ import { useState, useRef, useCallback, useSyncExternalStore, useTransition, useEffect } from 'react';
3
3
 
4
4
  // src/useStampRally.ts
5
5
  function getServerSnapshot() {
@@ -34,10 +34,28 @@ function isOnline(adapter) {
34
34
  function isRejectedVerification(value) {
35
35
  return value === false || typeof value === "object" && value !== null && "ok" in value && value.ok === false;
36
36
  }
37
+ function isNetworkFailure(error) {
38
+ if (typeof navigator !== "undefined" && navigator.onLine === false) return true;
39
+ if (!(error instanceof Error)) return true;
40
+ return /aborted|connection|fetch|network|offline|timeout/i.test(`${error.name} ${error.message}`);
41
+ }
42
+ function notifyAcquisitionEvents(result, events, onStampClaimed, onRewardUnlocked) {
43
+ for (const event of result.events) {
44
+ if (event.type === "stampAcquired") {
45
+ (events?.onStampClaimed ?? onStampClaimed)?.(event.record);
46
+ }
47
+ if (event.type === "rewardUnlocked") {
48
+ (events?.onRewardUnlocked ?? onRewardUnlocked)?.(event.rewardId);
49
+ }
50
+ }
51
+ }
37
52
  function useStampRally(client, options = {}) {
38
53
  const syncAdapter = options.syncAdapter;
39
54
  const events = options.events;
40
- const queuedRequests = useRef([]);
55
+ const [offlineQueue, setOfflineQueue] = useState([]);
56
+ const queuedMetadata = useRef(/* @__PURE__ */ new Map());
57
+ const isFlushingQueue = useRef(false);
58
+ const activeClient = useRef(client);
41
59
  const subscribe = useCallback(
42
60
  (onStoreChange) => client.subscribe(() => onStoreChange()),
43
61
  [client]
@@ -55,6 +73,12 @@ function useStampRally(client, options = {}) {
55
73
  useEffect(() => {
56
74
  setOptimisticState(rawState);
57
75
  }, [rawState]);
76
+ useEffect(() => {
77
+ if (activeClient.current === client) return;
78
+ activeClient.current = client;
79
+ setOfflineQueue([]);
80
+ queuedMetadata.current.clear();
81
+ }, [client]);
58
82
  useEffect(() => {
59
83
  if (syncAdapter?.onStateChange === void 0) return;
60
84
  return client.subscribe(syncAdapter.onStateChange);
@@ -82,6 +106,15 @@ function useStampRally(client, options = {}) {
82
106
  active = false;
83
107
  };
84
108
  }, [client, rawState]);
109
+ const queueRequest = useCallback(
110
+ (request, metadata) => {
111
+ if (metadata !== void 0) queuedMetadata.current.set(request.idempotencyKey, metadata);
112
+ setOfflineQueue(
113
+ (current) => current.some((item) => item.idempotencyKey === request.idempotencyKey) ? current : [...current, request]
114
+ );
115
+ },
116
+ []
117
+ );
85
118
  const acquire = useCallback(
86
119
  (stampId, context, now, idempotencyKey) => {
87
120
  const acquiredAt = now ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -93,7 +126,7 @@ function useStampRally(client, options = {}) {
93
126
  };
94
127
  setClientError(null);
95
128
  if (!isOnline(syncAdapter)) {
96
- queuedRequests.current = [...queuedRequests.current, request];
129
+ queueRequest(request);
97
130
  const queued = {
98
131
  ok: false,
99
132
  error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
@@ -117,9 +150,29 @@ function useStampRally(client, options = {}) {
117
150
  resolve(rejected);
118
151
  return;
119
152
  }
153
+ const previousState = client.getState();
120
154
  const result = await client.acquire(stampId, context, acquiredAt);
121
155
  if (result.ok) {
122
- const verified = await syncAdapter?.onServerVerify?.(request);
156
+ let verified;
157
+ try {
158
+ verified = await syncAdapter?.onServerVerify?.(request);
159
+ } catch (verificationError) {
160
+ if (!isNetworkFailure(verificationError)) throw verificationError;
161
+ queueRequest(request, { previousState, result: result.value });
162
+ const queued = {
163
+ ok: false,
164
+ error: {
165
+ code: "OFFLINE_QUEUED",
166
+ stampId,
167
+ idempotencyKey: request.idempotencyKey
168
+ }
169
+ };
170
+ setClientError({ client, value: toError(verificationError) });
171
+ setOptimisticState(client.getState());
172
+ setIsOperationPending(false);
173
+ resolve(queued);
174
+ return;
175
+ }
123
176
  if (isRejectedVerification(verified)) {
124
177
  await client.restore(rawState ?? client.getState() ?? result.value.nextState);
125
178
  const rejected = {
@@ -132,14 +185,12 @@ function useStampRally(client, options = {}) {
132
185
  resolve(rejected);
133
186
  return;
134
187
  }
135
- 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
- }
188
+ notifyAcquisitionEvents(
189
+ result.value,
190
+ events,
191
+ options.onStampClaimed,
192
+ options.onRewardUnlocked
193
+ );
143
194
  }
144
195
  if (!result.ok) {
145
196
  setClientError({ client, value: result.error });
@@ -148,6 +199,18 @@ function useStampRally(client, options = {}) {
148
199
  setIsOperationPending(false);
149
200
  resolve(result);
150
201
  } catch (acquireError) {
202
+ if (isNetworkFailure(acquireError)) {
203
+ queueRequest(request);
204
+ const queued = {
205
+ ok: false,
206
+ error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
207
+ };
208
+ setClientError({ client, value: queued.error });
209
+ setOptimisticState(client.getState());
210
+ setIsOperationPending(false);
211
+ resolve(queued);
212
+ return;
213
+ }
151
214
  const normalizedError = toError(acquireError);
152
215
  setClientError({ client, value: normalizedError });
153
216
  setOptimisticState(client.getState());
@@ -157,7 +220,15 @@ function useStampRally(client, options = {}) {
157
220
  })();
158
221
  });
159
222
  },
160
- [client, events, options.onRewardUnlocked, options.onStampClaimed, rawState, syncAdapter]
223
+ [
224
+ client,
225
+ events,
226
+ options.onRewardUnlocked,
227
+ options.onStampClaimed,
228
+ queueRequest,
229
+ rawState,
230
+ syncAdapter
231
+ ]
161
232
  );
162
233
  const reset = useCallback(
163
234
  (now) => {
@@ -316,13 +387,87 @@ function useStampRally(client, options = {}) {
316
387
  const isLoading = rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);
317
388
  const error = clientError?.client === client ? clientError.value : null;
318
389
  const flushQueue = useCallback(async () => {
319
- if (!isOnline(syncAdapter)) return;
320
- const pending = queuedRequests.current;
321
- queuedRequests.current = [];
322
- for (const request of pending) {
323
- await acquire(request.stampId, request.context, request.now, request.idempotencyKey);
390
+ if (!isOnline(syncAdapter) || isFlushingQueue.current) return;
391
+ isFlushingQueue.current = true;
392
+ setIsOperationPending(true);
393
+ try {
394
+ for (const request of offlineQueue) {
395
+ if (!isOnline(syncAdapter)) break;
396
+ const metadata = queuedMetadata.current.get(request.idempotencyKey);
397
+ const previousState = metadata?.previousState ?? client.getState();
398
+ try {
399
+ const before = await syncAdapter?.onBeforeCheckIn?.(request);
400
+ if (before === false) {
401
+ if (metadata !== void 0 && previousState !== null)
402
+ await client.restore(previousState);
403
+ setClientError({ client, value: { code: "INVALID_PROOF", stampId: request.stampId } });
404
+ queuedMetadata.current.delete(request.idempotencyKey);
405
+ setOfflineQueue(
406
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
407
+ );
408
+ continue;
409
+ }
410
+ let result = metadata?.result;
411
+ if (result === void 0) {
412
+ const localResult = await client.acquire(request.stampId, request.context, request.now);
413
+ if (!localResult.ok) {
414
+ setClientError({ client, value: localResult.error });
415
+ queuedMetadata.current.delete(request.idempotencyKey);
416
+ setOfflineQueue(
417
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
418
+ );
419
+ continue;
420
+ }
421
+ result = localResult.value;
422
+ queuedMetadata.current.set(request.idempotencyKey, {
423
+ previousState,
424
+ result
425
+ });
426
+ }
427
+ const verified = await syncAdapter?.onServerVerify?.(request);
428
+ if (isRejectedVerification(verified)) {
429
+ if (previousState !== null) await client.restore(previousState);
430
+ setClientError({
431
+ client,
432
+ value: { code: "INVALID_PROOF", stampId: request.stampId }
433
+ });
434
+ } else {
435
+ notifyAcquisitionEvents(
436
+ result,
437
+ events,
438
+ options.onStampClaimed,
439
+ options.onRewardUnlocked
440
+ );
441
+ }
442
+ queuedMetadata.current.delete(request.idempotencyKey);
443
+ setOfflineQueue(
444
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
445
+ );
446
+ } catch (flushError) {
447
+ if (isNetworkFailure(flushError)) {
448
+ setClientError({ client, value: toError(flushError) });
449
+ break;
450
+ }
451
+ setClientError({ client, value: toError(flushError) });
452
+ queuedMetadata.current.delete(request.idempotencyKey);
453
+ setOfflineQueue(
454
+ (current) => current.filter((item) => item.idempotencyKey !== request.idempotencyKey)
455
+ );
456
+ }
457
+ }
458
+ } finally {
459
+ isFlushingQueue.current = false;
460
+ setIsOperationPending(false);
324
461
  }
325
- }, [acquire, syncAdapter]);
462
+ }, [client, events, offlineQueue, options.onRewardUnlocked, options.onStampClaimed, syncAdapter]);
463
+ useEffect(() => {
464
+ if (typeof window === "undefined") return;
465
+ const handleOnline = () => {
466
+ void flushQueue();
467
+ };
468
+ window.addEventListener("online", handleOnline);
469
+ return () => window.removeEventListener("online", handleOnline);
470
+ }, [flushQueue]);
326
471
  return {
327
472
  state: optimisticState,
328
473
  isLoading,
@@ -334,7 +479,8 @@ function useStampRally(client, options = {}) {
334
479
  redeem,
335
480
  exportRecoveryCode,
336
481
  importRecoveryCode,
337
- queuedCount: queuedRequests.current.length,
482
+ offlineQueue,
483
+ queuedCount: offlineQueue.length,
338
484
  flushQueue
339
485
  };
340
486
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useStampRally.ts"],"names":["result"],"mappings":"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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"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n ConsumeResult,\n ProcessStampValue,\n Result,\n RewardConsumeError,\n RewardState,\n StampError,\n StampRallyClient,\n StampRallyState,\n VerificationContext,\n} from \"@stamprally/core\";\nimport { consumeReward, exportProgressToken, importProgressToken } from \"@stamprally/core\";\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n useTransition,\n} from \"react\";\n\ninterface OptimisticAcquire {\n readonly stampId: string;\n readonly acquiredAt: string;\n}\n\ninterface ClientStatus {\n readonly client: StampRallyClient;\n readonly isInitializing: boolean;\n}\n\ninterface ClientError {\n readonly client: StampRallyClient;\n readonly value: StampError | RewardConsumeError | Error;\n}\n\ninterface QueuedRequestMetadata {\n readonly previousState: StampRallyState | null;\n readonly result: ProcessStampValue;\n}\n\nexport interface CheckInRequest {\n readonly stampId: string;\n readonly context: VerificationContext;\n readonly now: string;\n readonly idempotencyKey: string;\n}\n\nexport interface SyncAdapter {\n readonly isOnline?: boolean | (() => boolean);\n readonly onBeforeCheckIn?: (request: CheckInRequest) => unknown;\n readonly onServerVerify?: (request: CheckInRequest) => unknown;\n readonly onStateChange?: (state: StampRallyState) => void;\n}\n\nexport interface StampRallyEventHandlers {\n readonly onStampClaimed?: (record: StampRallyState[\"records\"][number]) => void;\n readonly onRewardUnlocked?: (rewardId: string) => void;\n readonly onRewardConsumed?: (rewardId: string) => void;\n}\n\nexport interface UseStampRallyOptions {\n readonly syncAdapter?: SyncAdapter;\n readonly events?: StampRallyEventHandlers;\n readonly onStampClaimed?: StampRallyEventHandlers[\"onStampClaimed\"];\n readonly onRewardUnlocked?: StampRallyEventHandlers[\"onRewardUnlocked\"];\n readonly onRewardConsumed?: StampRallyEventHandlers[\"onRewardConsumed\"];\n}\n\nexport interface RedeemOptions {\n readonly passcode?: string;\n readonly staffId?: string;\n}\n\nexport interface UseStampRallyReturn {\n readonly state: StampRallyState | null;\n readonly isLoading: boolean;\n readonly isPending: boolean;\n readonly error: StampError | RewardConsumeError | Error | null;\n readonly rewardsState: ReadonlyArray<RewardState>;\n readonly acquire: (\n stampId: string,\n context: VerificationContext,\n now?: string,\n idempotencyKey?: string,\n ) => Promise<Result<ProcessStampValue, StampError>>;\n readonly reset: (now?: string) => Promise<StampRallyState>;\n readonly redeem: (rewardId: string, options?: RedeemOptions) => Promise<ConsumeResult>;\n readonly exportRecoveryCode: () => string;\n readonly importRecoveryCode: (token: string) => Promise<boolean>;\n readonly offlineQueue: ReadonlyArray<CheckInRequest>;\n readonly queuedCount: number;\n readonly flushQueue: () => Promise<void>;\n}\n\n/** @deprecated Use UseStampRallyReturn instead. */\nexport type UseStampRallyValue = UseStampRallyReturn;\n\nfunction getServerSnapshot(): null {\n return null;\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction applyOptimisticAcquire(\n currentState: StampRallyState | null,\n action: OptimisticAcquire,\n): StampRallyState | null {\n if (\n currentState === null ||\n currentState.records.some((record) => record.stampId === action.stampId)\n ) {\n return currentState;\n }\n\n return {\n ...currentState,\n records: [...currentState.records, { stampId: action.stampId, acquiredAt: action.acquiredAt }],\n updatedAt: action.acquiredAt,\n };\n}\n\nfunction createIdempotencyKey(): string {\n const cryptoApi = globalThis.crypto;\n if (cryptoApi !== undefined && typeof cryptoApi.randomUUID === \"function\") {\n return cryptoApi.randomUUID();\n }\n return `stamp-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction isOnline(adapter: SyncAdapter | undefined): boolean {\n if (adapter?.isOnline === undefined) {\n return typeof navigator === \"undefined\" || navigator.onLine !== false;\n }\n return typeof adapter.isOnline === \"function\" ? adapter.isOnline() : adapter.isOnline;\n}\n\nfunction isRejectedVerification(value: unknown): boolean {\n return (\n value === false ||\n (typeof value === \"object\" && value !== null && \"ok\" in value && value.ok === false)\n );\n}\n\nfunction isNetworkFailure(error: unknown): boolean {\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) return true;\n if (!(error instanceof Error)) return true;\n return /aborted|connection|fetch|network|offline|timeout/i.test(`${error.name} ${error.message}`);\n}\n\nfunction notifyAcquisitionEvents(\n result: ProcessStampValue,\n events: StampRallyEventHandlers | undefined,\n onStampClaimed: StampRallyEventHandlers[\"onStampClaimed\"] | undefined,\n onRewardUnlocked: StampRallyEventHandlers[\"onRewardUnlocked\"] | undefined,\n): void {\n for (const event of result.events) {\n if (event.type === \"stampAcquired\") {\n (events?.onStampClaimed ?? onStampClaimed)?.(event.record);\n }\n if (event.type === \"rewardUnlocked\") {\n (events?.onRewardUnlocked ?? onRewardUnlocked)?.(event.rewardId);\n }\n }\n}\n\nexport function useStampRally(\n client: StampRallyClient,\n options: UseStampRallyOptions = {},\n): UseStampRallyReturn {\n const syncAdapter = options.syncAdapter;\n const events = options.events;\n const [offlineQueue, setOfflineQueue] = useState<ReadonlyArray<CheckInRequest>>([]);\n const queuedMetadata = useRef(new Map<string, QueuedRequestMetadata>());\n const isFlushingQueue = useRef(false);\n const activeClient = useRef(client);\n const subscribe = useCallback(\n (onStoreChange: () => void) => client.subscribe(() => onStoreChange()),\n [client],\n );\n const getSnapshot = useCallback(() => client.getState(), [client]);\n const rawState = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n const [clientStatus, setClientStatus] = useState<ClientStatus>(() => ({\n client,\n isInitializing: client.getState() === null,\n }));\n const [clientError, setClientError] = useState<ClientError | null>(null);\n const [isPending] = useTransition();\n const [isOperationPending, setIsOperationPending] = useState(false);\n const [optimisticState, setOptimisticState] = useState<StampRallyState | null>(rawState);\n\n useEffect(() => {\n setOptimisticState(rawState);\n }, [rawState]);\n\n useEffect(() => {\n if (activeClient.current === client) return;\n activeClient.current = client;\n setOfflineQueue([]);\n queuedMetadata.current.clear();\n }, [client]);\n\n useEffect(() => {\n if (syncAdapter?.onStateChange === undefined) return;\n return client.subscribe(syncAdapter.onStateChange);\n }, [client, syncAdapter]);\n\n useEffect(() => {\n let active = true;\n setClientError(null);\n if (rawState !== null) {\n setClientStatus({ client, isInitializing: false });\n return () => {\n active = false;\n };\n }\n\n setClientStatus({ client, isInitializing: true });\n\n void client\n .init()\n .catch((initializationError: unknown) => {\n if (active) {\n setClientError({ client, value: toError(initializationError) });\n }\n })\n .finally(() => {\n if (active) {\n setClientStatus({ client, isInitializing: false });\n }\n });\n\n return () => {\n active = false;\n };\n }, [client, rawState]);\n\n const queueRequest = useCallback(\n (request: CheckInRequest, metadata?: QueuedRequestMetadata): void => {\n if (metadata !== undefined) queuedMetadata.current.set(request.idempotencyKey, metadata);\n setOfflineQueue((current) =>\n current.some((item) => item.idempotencyKey === request.idempotencyKey)\n ? current\n : [...current, request],\n );\n },\n [],\n );\n\n const acquire = useCallback(\n (\n stampId: string,\n context: VerificationContext,\n now?: string,\n idempotencyKey?: string,\n ): Promise<Result<ProcessStampValue, StampError>> => {\n const acquiredAt = now ?? new Date().toISOString();\n const request: CheckInRequest = {\n stampId,\n context,\n now: acquiredAt,\n idempotencyKey: idempotencyKey ?? createIdempotencyKey(),\n };\n setClientError(null);\n\n if (!isOnline(syncAdapter)) {\n queueRequest(request);\n const queued: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"OFFLINE_QUEUED\", stampId, idempotencyKey: request.idempotencyKey },\n };\n setClientError({ client, value: queued.error });\n return Promise.resolve(queued);\n }\n\n setIsOperationPending(true);\n setOptimisticState((current) => applyOptimisticAcquire(current, { stampId, acquiredAt }));\n return new Promise((resolve, reject) => {\n void (async () => {\n try {\n const before = await syncAdapter?.onBeforeCheckIn?.(request);\n if (before === false) {\n const rejected: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"INVALID_PROOF\", stampId },\n };\n setClientError({ client, value: rejected.error });\n setIsOperationPending(false);\n resolve(rejected);\n return;\n }\n const previousState = client.getState();\n const result = await client.acquire(stampId, context, acquiredAt);\n if (result.ok) {\n let verified: unknown;\n try {\n verified = await syncAdapter?.onServerVerify?.(request);\n } catch (verificationError) {\n if (!isNetworkFailure(verificationError)) throw verificationError;\n queueRequest(request, { previousState, result: result.value });\n const queued: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: {\n code: \"OFFLINE_QUEUED\",\n stampId,\n idempotencyKey: request.idempotencyKey,\n },\n };\n setClientError({ client, value: toError(verificationError) });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(queued);\n return;\n }\n if (isRejectedVerification(verified)) {\n await client.restore(rawState ?? client.getState() ?? result.value.nextState);\n const rejected: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"INVALID_PROOF\", stampId },\n };\n setClientError({ client, value: rejected.error });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(rejected);\n return;\n }\n notifyAcquisitionEvents(\n result.value,\n events,\n options.onStampClaimed,\n options.onRewardUnlocked,\n );\n }\n if (!result.ok) {\n setClientError({ client, value: result.error });\n setOptimisticState(client.getState());\n }\n setIsOperationPending(false);\n resolve(result);\n } catch (acquireError) {\n if (isNetworkFailure(acquireError)) {\n queueRequest(request);\n const queued: Result<ProcessStampValue, StampError> = {\n ok: false,\n error: { code: \"OFFLINE_QUEUED\", stampId, idempotencyKey: request.idempotencyKey },\n };\n setClientError({ client, value: queued.error });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n resolve(queued);\n return;\n }\n const normalizedError = toError(acquireError);\n setClientError({ client, value: normalizedError });\n setOptimisticState(client.getState());\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [\n client,\n events,\n options.onRewardUnlocked,\n options.onStampClaimed,\n queueRequest,\n rawState,\n syncAdapter,\n ],\n );\n\n const reset = useCallback(\n (now?: string): Promise<StampRallyState> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n try {\n const nextState = now === undefined ? await client.reset() : await client.reset(now);\n setIsOperationPending(false);\n resolve(nextState);\n } catch (resetError) {\n const normalizedError = toError(resetError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client],\n );\n\n const redeem = useCallback(\n (rewardId: string, redeemOptions: RedeemOptions = {}): Promise<ConsumeResult> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n const reward = client.getConfig().rewards?.find((item) => item.id === rewardId);\n if (reward === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"REWARD_NOT_FOUND\", rewardId },\n };\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const currentState = client.getState();\n const currentRewardState = currentState?.rewards?.find(\n (state) => state.rewardId === rewardId,\n );\n if (currentState === null || currentRewardState === undefined) {\n const result: ConsumeResult = {\n ok: false,\n error: { code: \"NOT_AVAILABLE\", rewardId },\n };\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const result = consumeReward({\n reward,\n currentState: currentRewardState,\n now: new Date().toISOString(),\n ...(redeemOptions.passcode === undefined\n ? {}\n : { inputPasscode: redeemOptions.passcode }),\n ...(redeemOptions.staffId === undefined ? {} : { staffId: redeemOptions.staffId }),\n });\n if (!result.ok) {\n setClientError({ client, value: result.error });\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n if (result.value === currentRewardState) {\n setIsOperationPending(false);\n resolve(result);\n return;\n }\n\n const nextState: StampRallyState = {\n ...currentState,\n rewards: (currentState.rewards ?? []).map((state) =>\n state.rewardId === rewardId ? result.value : state,\n ),\n updatedAt: result.value.consumedAt ?? currentState.updatedAt,\n };\n\n try {\n await client.restore(nextState);\n (events?.onRewardConsumed ?? options.onRewardConsumed)?.(rewardId);\n setIsOperationPending(false);\n resolve(result);\n } catch (redeemError) {\n const normalizedError = toError(redeemError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client, events, options.onRewardConsumed],\n );\n\n const exportRecoveryCode = useCallback((): string => {\n const state = client.getState();\n if (state === null) {\n throw new Error(\"Cannot export recovery code before the rally is initialized.\");\n }\n\n return exportProgressToken({\n version: 1,\n rallyId: state.rallyId,\n stamps: state.records,\n rewards: state.rewards ?? [],\n exportedAt: new Date().toISOString(),\n });\n }, [client]);\n\n const importRecoveryCode = useCallback(\n (token: string): Promise<boolean> => {\n setClientError(null);\n setIsOperationPending(true);\n\n return new Promise((resolve, reject) => {\n void (async () => {\n const config = client.getConfig();\n const snapshot = importProgressToken(token, config.id);\n if (snapshot === null) {\n setIsOperationPending(false);\n resolve(false);\n return;\n }\n\n const stampIds = new Set(config.stamps.map((stamp) => stamp.id));\n const rewardIds = new Set((config.rewards ?? []).map((reward) => reward.id));\n const importedStampIds = new Set<string>();\n const importedRewardIds = new Set<string>();\n const stamps = snapshot.stamps.filter((record) => {\n if (!stampIds.has(record.stampId) || importedStampIds.has(record.stampId)) return false;\n importedStampIds.add(record.stampId);\n return true;\n });\n const rewards = snapshot.rewards.filter((state) => {\n if (!rewardIds.has(state.rewardId) || importedRewardIds.has(state.rewardId))\n return false;\n importedRewardIds.add(state.rewardId);\n return true;\n });\n\n try {\n await client.restore({\n rallyId: config.id,\n records: stamps,\n ...(config.rewards === undefined && rewards.length === 0 ? {} : { rewards }),\n updatedAt: snapshot.exportedAt,\n });\n setIsOperationPending(false);\n resolve(true);\n } catch (importError) {\n const normalizedError = toError(importError);\n setClientError({ client, value: normalizedError });\n setIsOperationPending(false);\n reject(normalizedError);\n }\n })();\n });\n },\n [client],\n );\n\n const isLoading =\n rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);\n const error = clientError?.client === client ? clientError.value : null;\n\n const flushQueue = useCallback(async (): Promise<void> => {\n if (!isOnline(syncAdapter) || isFlushingQueue.current) return;\n isFlushingQueue.current = true;\n setIsOperationPending(true);\n try {\n for (const request of offlineQueue) {\n if (!isOnline(syncAdapter)) break;\n const metadata = queuedMetadata.current.get(request.idempotencyKey);\n const previousState = metadata?.previousState ?? client.getState();\n try {\n const before = await syncAdapter?.onBeforeCheckIn?.(request);\n if (before === false) {\n if (metadata !== undefined && previousState !== null)\n await client.restore(previousState);\n setClientError({ client, value: { code: \"INVALID_PROOF\", stampId: request.stampId } });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n continue;\n }\n\n let result = metadata?.result;\n if (result === undefined) {\n const localResult = await client.acquire(request.stampId, request.context, request.now);\n if (!localResult.ok) {\n setClientError({ client, value: localResult.error });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n continue;\n }\n result = localResult.value;\n queuedMetadata.current.set(request.idempotencyKey, {\n previousState,\n result,\n });\n }\n\n const verified = await syncAdapter?.onServerVerify?.(request);\n if (isRejectedVerification(verified)) {\n if (previousState !== null) await client.restore(previousState);\n setClientError({\n client,\n value: { code: \"INVALID_PROOF\", stampId: request.stampId },\n });\n } else {\n notifyAcquisitionEvents(\n result,\n events,\n options.onStampClaimed,\n options.onRewardUnlocked,\n );\n }\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n } catch (flushError) {\n if (isNetworkFailure(flushError)) {\n setClientError({ client, value: toError(flushError) });\n break;\n }\n setClientError({ client, value: toError(flushError) });\n queuedMetadata.current.delete(request.idempotencyKey);\n setOfflineQueue((current) =>\n current.filter((item) => item.idempotencyKey !== request.idempotencyKey),\n );\n }\n }\n } finally {\n isFlushingQueue.current = false;\n setIsOperationPending(false);\n }\n }, [client, events, offlineQueue, options.onRewardUnlocked, options.onStampClaimed, syncAdapter]);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const handleOnline = (): void => {\n void flushQueue();\n };\n window.addEventListener(\"online\", handleOnline);\n return () => window.removeEventListener(\"online\", handleOnline);\n }, [flushQueue]);\n\n return {\n state: optimisticState,\n isLoading,\n isPending: isPending || isOperationPending,\n error,\n rewardsState: optimisticState?.rewards ?? [],\n acquire,\n reset,\n redeem,\n exportRecoveryCode,\n importRecoveryCode,\n offlineQueue,\n queuedCount: offlineQueue.length,\n flushQueue,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamprally/react",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "React hooks for @stamprally/core.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -31,7 +31,7 @@
31
31
  }
32
32
  },
33
33
  "dependencies": {
34
- "@stamprally/core": "0.3.0"
34
+ "@stamprally/core": "0.4.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": "^18.0.0 || ^19.0.0",