@stamprally/react 0.8.0 → 0.9.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.js CHANGED
@@ -1,627 +1,32 @@
1
- import { consumeReward, exportProgressToken, importProgressToken, createSecureToken, verifySecureToken, isStampRallyState } from '@stamprally/core';
2
- import { useState, useRef, useCallback, useSyncExternalStore, useTransition, useEffect } from 'react';
1
+ import { useCallback, useSyncExternalStore, useState, useEffect } from 'react';
3
2
 
4
3
  // src/useStampRally.ts
5
- function getServerSnapshot() {
6
- return null;
7
- }
8
- function toError(error) {
9
- return error instanceof Error ? error : new Error(String(error));
10
- }
11
- function isObject(value) {
12
- return typeof value === "object" && value !== null && !Array.isArray(value);
13
- }
14
- function applyOptimisticAcquire(currentState, action) {
15
- if (currentState === null || currentState.records.some((record) => record.stampId === action.stampId)) {
16
- return currentState;
17
- }
18
- return {
19
- ...currentState,
20
- records: [...currentState.records, { stampId: action.stampId, acquiredAt: action.acquiredAt }],
21
- updatedAt: action.acquiredAt
22
- };
23
- }
24
- function createIdempotencyKey() {
25
- const cryptoApi = globalThis.crypto;
26
- if (cryptoApi !== void 0 && typeof cryptoApi.randomUUID === "function") {
27
- return cryptoApi.randomUUID();
28
- }
29
- return `stamp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
30
- }
31
- function isOnline(adapter) {
32
- if (adapter?.isOnline === void 0) {
33
- return typeof navigator === "undefined" || navigator.onLine !== false;
34
- }
35
- return typeof adapter.isOnline === "function" ? adapter.isOnline() : adapter.isOnline;
36
- }
37
- function isRejectedVerification(value) {
38
- return value === false || typeof value === "object" && value !== null && "ok" in value && value.ok === false;
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
- }
55
- function useStampRally(client, options = {}) {
56
- const syncAdapter = options.syncAdapter;
57
- const events = options.events;
58
- const [offlineQueue, setOfflineQueue] = useState([]);
59
- const queuedMetadata = useRef(/* @__PURE__ */ new Map());
60
- const isFlushingQueue = useRef(false);
61
- const activeClient = useRef(client);
62
- const subscribe = useCallback(
63
- (onStoreChange) => client.subscribe(() => onStoreChange()),
64
- [client]
65
- );
66
- const getSnapshot = useCallback(() => client.getState(), [client]);
67
- const rawState = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
68
- const [clientStatus, setClientStatus] = useState(() => ({
69
- client,
70
- isInitializing: client.getState() === null
71
- }));
72
- const [clientError, setClientError] = useState(null);
73
- const [isPending] = useTransition();
74
- const [isOperationPending, setIsOperationPending] = useState(false);
75
- const [optimisticState, setOptimisticState] = useState(rawState);
76
- useEffect(() => {
77
- setOptimisticState(rawState);
78
- }, [rawState]);
79
- useEffect(() => {
80
- if (activeClient.current === client) return;
81
- activeClient.current = client;
82
- setOfflineQueue([]);
83
- queuedMetadata.current.clear();
84
- }, [client]);
85
- useEffect(() => {
86
- if (syncAdapter?.onStateChange === void 0) return;
87
- return client.subscribe(syncAdapter.onStateChange);
88
- }, [client, syncAdapter]);
89
- useEffect(() => {
90
- let active = true;
91
- setClientError(null);
92
- if (rawState !== null) {
93
- setClientStatus({ client, isInitializing: false });
94
- return () => {
95
- active = false;
96
- };
97
- }
98
- setClientStatus({ client, isInitializing: true });
99
- void client.init().catch((initializationError) => {
100
- if (active) {
101
- setClientError({ client, value: toError(initializationError) });
102
- }
103
- }).finally(() => {
104
- if (active) {
105
- setClientStatus({ client, isInitializing: false });
106
- }
107
- });
108
- return () => {
109
- active = false;
110
- };
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
- );
121
- const acquire = useCallback(
122
- (stampId, context, now, idempotencyKey) => {
123
- const acquiredAt = now ?? (/* @__PURE__ */ new Date()).toISOString();
124
- const request = {
125
- stampId,
126
- context,
127
- now: acquiredAt,
128
- idempotencyKey: idempotencyKey ?? createIdempotencyKey()
129
- };
130
- setClientError(null);
131
- if (!isOnline(syncAdapter)) {
132
- queueRequest(request);
133
- const queued = {
134
- ok: false,
135
- error: { code: "OFFLINE_QUEUED", stampId, idempotencyKey: request.idempotencyKey }
136
- };
137
- setClientError({ client, value: queued.error });
138
- return Promise.resolve(queued);
139
- }
140
- setIsOperationPending(true);
141
- setOptimisticState((current) => applyOptimisticAcquire(current, { stampId, acquiredAt }));
142
- return new Promise((resolve, reject) => {
143
- void (async () => {
144
- try {
145
- const before = await syncAdapter?.onBeforeCheckIn?.(request);
146
- if (before === false) {
147
- const rejected = {
148
- ok: false,
149
- error: { code: "INVALID_PROOF", stampId }
150
- };
151
- setClientError({ client, value: rejected.error });
152
- setIsOperationPending(false);
153
- resolve(rejected);
154
- return;
155
- }
156
- const previousState = client.getState();
157
- const result = await client.acquire(stampId, context, acquiredAt);
158
- if (result.ok) {
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
- }
179
- if (isRejectedVerification(verified)) {
180
- await client.restore(rawState ?? client.getState() ?? result.value.nextState);
181
- const rejected = {
182
- ok: false,
183
- error: { code: "INVALID_PROOF", stampId }
184
- };
185
- setClientError({ client, value: rejected.error });
186
- setOptimisticState(client.getState());
187
- setIsOperationPending(false);
188
- resolve(rejected);
189
- return;
190
- }
191
- notifyAcquisitionEvents(
192
- result.value,
193
- events,
194
- options.onStampClaimed,
195
- options.onRewardUnlocked
196
- );
197
- }
198
- if (!result.ok) {
199
- setClientError({ client, value: result.error });
200
- setOptimisticState(client.getState());
201
- }
202
- setIsOperationPending(false);
203
- resolve(result);
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
- }
217
- const normalizedError = toError(acquireError);
218
- setClientError({ client, value: normalizedError });
219
- setOptimisticState(client.getState());
220
- setIsOperationPending(false);
221
- reject(normalizedError);
222
- }
223
- })();
224
- });
225
- },
226
- [
227
- client,
228
- events,
229
- options.onRewardUnlocked,
230
- options.onStampClaimed,
231
- queueRequest,
232
- rawState,
233
- syncAdapter
234
- ]
235
- );
236
- const reset = useCallback(
237
- (now) => {
238
- setClientError(null);
239
- setIsOperationPending(true);
240
- return new Promise((resolve, reject) => {
241
- void (async () => {
242
- try {
243
- const nextState = now === void 0 ? await client.reset() : await client.reset(now);
244
- setIsOperationPending(false);
245
- resolve(nextState);
246
- } catch (resetError) {
247
- const normalizedError = toError(resetError);
248
- setClientError({ client, value: normalizedError });
249
- setIsOperationPending(false);
250
- reject(normalizedError);
251
- }
252
- })();
253
- });
254
- },
255
- [client]
256
- );
257
- const redeem = useCallback(
258
- (rewardId, redeemOptions = {}) => {
259
- setClientError(null);
260
- setIsOperationPending(true);
261
- return new Promise((resolve, reject) => {
262
- void (async () => {
263
- const reward = client.getConfig().rewards?.find((item) => item.id === rewardId);
264
- if (reward === void 0) {
265
- const result2 = {
266
- ok: false,
267
- error: { code: "REWARD_NOT_FOUND", rewardId }
268
- };
269
- setClientError({ client, value: result2.error });
270
- setIsOperationPending(false);
271
- resolve(result2);
272
- return;
273
- }
274
- const currentState = client.getState();
275
- const currentRewardState = currentState?.rewards?.find(
276
- (state) => state.rewardId === rewardId
277
- );
278
- if (currentState === null || currentRewardState === void 0) {
279
- const result2 = {
280
- ok: false,
281
- error: { code: "NOT_AVAILABLE", rewardId }
282
- };
283
- setClientError({ client, value: result2.error });
284
- setIsOperationPending(false);
285
- resolve(result2);
286
- return;
287
- }
288
- const result = consumeReward({
289
- reward,
290
- currentState: currentRewardState,
291
- now: (/* @__PURE__ */ new Date()).toISOString(),
292
- ...redeemOptions.passcode === void 0 ? {} : { inputPasscode: redeemOptions.passcode },
293
- ...redeemOptions.staffId === void 0 ? {} : { staffId: redeemOptions.staffId }
294
- });
295
- if (!result.ok) {
296
- setClientError({ client, value: result.error });
297
- setIsOperationPending(false);
298
- resolve(result);
299
- return;
300
- }
301
- if (result.value === currentRewardState) {
302
- setIsOperationPending(false);
303
- resolve(result);
304
- return;
305
- }
306
- const nextState = {
307
- ...currentState,
308
- rewards: (currentState.rewards ?? []).map(
309
- (state) => state.rewardId === rewardId ? result.value : state
310
- ),
311
- updatedAt: result.value.consumedAt ?? currentState.updatedAt
312
- };
313
- try {
314
- await client.restore(nextState);
315
- (events?.onRewardConsumed ?? options.onRewardConsumed)?.(rewardId);
316
- setIsOperationPending(false);
317
- resolve(result);
318
- } catch (redeemError) {
319
- const normalizedError = toError(redeemError);
320
- setClientError({ client, value: normalizedError });
321
- setIsOperationPending(false);
322
- reject(normalizedError);
323
- }
324
- })();
325
- });
326
- },
327
- [client, events, options.onRewardConsumed]
328
- );
329
- const exportRecoveryCode = useCallback(() => {
330
- const state = client.getState();
331
- if (state === null) {
332
- throw new Error("Cannot export recovery code before the rally is initialized.");
333
- }
334
- return exportProgressToken({
335
- version: 1,
336
- rallyId: state.rallyId,
337
- stamps: state.records,
338
- rewards: state.rewards ?? [],
339
- exportedAt: (/* @__PURE__ */ new Date()).toISOString()
340
- });
341
- }, [client]);
342
- const importRecoveryCode = useCallback(
343
- (token) => {
344
- setClientError(null);
345
- setIsOperationPending(true);
346
- return new Promise((resolve, reject) => {
347
- void (async () => {
348
- const config = client.getConfig();
349
- const snapshot = importProgressToken(token, config.id);
350
- if (snapshot === null) {
351
- setIsOperationPending(false);
352
- resolve(false);
353
- return;
354
- }
355
- const stampIds = new Set(config.stamps.map((stamp) => stamp.id));
356
- const rewardIds = new Set((config.rewards ?? []).map((reward) => reward.id));
357
- const importedStampIds = /* @__PURE__ */ new Set();
358
- const importedRewardIds = /* @__PURE__ */ new Set();
359
- const stamps = snapshot.stamps.filter((record) => {
360
- if (!stampIds.has(record.stampId) || importedStampIds.has(record.stampId)) return false;
361
- importedStampIds.add(record.stampId);
362
- return true;
363
- });
364
- const rewards = snapshot.rewards.filter((state) => {
365
- if (!rewardIds.has(state.rewardId) || importedRewardIds.has(state.rewardId))
366
- return false;
367
- importedRewardIds.add(state.rewardId);
368
- return true;
369
- });
370
- try {
371
- await client.restore({
372
- rallyId: config.id,
373
- records: stamps,
374
- ...config.rewards === void 0 && rewards.length === 0 ? {} : { rewards },
375
- updatedAt: snapshot.exportedAt
376
- });
377
- setIsOperationPending(false);
378
- resolve(true);
379
- } catch (importError) {
380
- const normalizedError = toError(importError);
381
- setClientError({ client, value: normalizedError });
382
- setIsOperationPending(false);
383
- reject(normalizedError);
384
- }
385
- })();
386
- });
387
- },
388
- [client]
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
- );
479
- const isLoading = rawState === null && (clientStatus.client !== client || clientStatus.isInitializing);
480
- const error = clientError?.client === client ? clientError.value : null;
481
- const flushQueue = useCallback(async () => {
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);
553
- }
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]);
563
- return {
564
- state: optimisticState,
565
- isLoading,
566
- isPending: isPending || isOperationPending,
567
- error,
568
- rewardsState: optimisticState?.rewards ?? [],
569
- acquire,
570
- reset,
571
- redeem,
572
- exportRecoveryCode,
573
- importRecoveryCode,
574
- exportRecoveryToken,
575
- importRecoveryToken,
576
- syncWithServer,
577
- offlineQueue,
578
- queuedCount: offlineQueue.length,
579
- flushQueue
580
- };
581
- }
582
- function usePublicStampRally(config, options) {
583
- return { ...useStampRally(options.client, options), config };
584
- }
585
4
  function serverSnapshot() {
586
5
  return null;
587
6
  }
588
- function errorMessage(error) {
589
- return error.message ?? error.code;
7
+ function errorFrom(value) {
8
+ return value instanceof Error ? value : new Error(String(value));
590
9
  }
591
- function useUniversalStampRally(client, options = {}) {
592
- const subscribe = useCallback(
593
- (listener) => client.subscribe(() => listener()),
594
- [client]
595
- );
10
+ function useStampRally(client, options = {}) {
11
+ const subscribe = useCallback((listener) => client.subscribe(listener), [client]);
596
12
  const getSnapshot = useCallback(() => client.getState(), [client]);
597
- const rawState = useSyncExternalStore(subscribe, getSnapshot, serverSnapshot);
598
- const state = rawState ?? {
599
- rallyId: client.getConfig().id,
600
- records: [],
601
- updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
602
- };
13
+ const state = useSyncExternalStore(subscribe, getSnapshot, serverSnapshot);
603
14
  const [error, setError] = useState(null);
604
15
  useEffect(() => {
605
16
  if (options.initialize === false || client.getState() !== null) return;
606
17
  let active = true;
607
18
  void client.init().catch((reason) => {
608
- if (active) setError(reason instanceof Error ? reason : new Error(String(reason)));
19
+ if (active) setError(errorFrom(reason));
609
20
  });
610
21
  return () => {
611
22
  active = false;
612
23
  };
613
24
  }, [client, options.initialize]);
614
- useEffect(
615
- () => client.subscribeEvents((event) => {
616
- if (event.type === "error") setError(new Error(errorMessage(event.error)));
617
- }),
618
- [client]
619
- );
620
25
  const onCheckIn = useCallback(
621
26
  (spotId, proof, checkInOptions = {}) => {
622
27
  setError(null);
623
28
  return client.checkIn(spotId, proof, checkInOptions).then((result) => {
624
- if (!result.ok) setError(new Error(errorMessage(result.error)));
29
+ if (!result.ok) setError(errorFrom(result.error));
625
30
  return result;
626
31
  });
627
32
  },
@@ -631,7 +36,7 @@ function useUniversalStampRally(client, options = {}) {
631
36
  (rewardId, claimOptions = {}) => {
632
37
  setError(null);
633
38
  return client.claimReward(rewardId, claimOptions).then((result) => {
634
- if (!result.ok) setError(new Error(errorMessage(result.error)));
39
+ if (!result.ok) setError(errorFrom(result.error));
635
40
  return result;
636
41
  });
637
42
  },
@@ -640,7 +45,7 @@ function useUniversalStampRally(client, options = {}) {
640
45
  const onSync = useCallback(() => {
641
46
  setError(null);
642
47
  return client.sync().catch((reason) => {
643
- const next = reason instanceof Error ? reason : new Error(String(reason));
48
+ const next = errorFrom(reason);
644
49
  setError(next);
645
50
  throw next;
646
51
  });
@@ -648,14 +53,16 @@ function useUniversalStampRally(client, options = {}) {
648
53
  return {
649
54
  state,
650
55
  config: client.getConfig(),
651
- isLoading: rawState === null,
56
+ isLoading: state === null,
652
57
  error,
653
58
  onCheckIn,
654
59
  onClaimReward,
655
- onSync
60
+ onSync,
61
+ switchUser: client.switchUser.bind(client),
62
+ clearUserState: client.clearUserState.bind(client)
656
63
  };
657
64
  }
658
65
 
659
- export { usePublicStampRally, useStampRally, useUniversalStampRally };
66
+ export { useStampRally };
660
67
  //# sourceMappingURL=index.js.map
661
68
  //# sourceMappingURL=index.js.map