@stamprally/core 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/index.cjs +215 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -4
- package/dist/index.d.ts +56 -4
- package/dist/index.js +213 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -147,6 +147,7 @@ interface StampRecord {
|
|
|
147
147
|
readonly acquiredAt: string;
|
|
148
148
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
149
149
|
}
|
|
150
|
+
type SpotStatus = "UNCLAIMED" | "CLAIMED" | "LOCKED" | "VERIFYING";
|
|
150
151
|
type RewardStatus = "LOCKED" | "AVAILABLE" | "CONSUMED" | "EXPIRED";
|
|
151
152
|
interface RewardState {
|
|
152
153
|
readonly rewardId: string;
|
|
@@ -292,6 +293,11 @@ declare function parsePublicConfig(input: unknown): PublicRallyConfig;
|
|
|
292
293
|
declare function calculateDistanceMeters(aLat: number, aLon: number, bLat: number, bLon: number): number;
|
|
293
294
|
declare function evaluateConditionDetailed(condition: CheckInCondition, context: VerificationContext): Result<ConditionMatch, ConditionMismatch>;
|
|
294
295
|
declare function evaluateCondition(condition: CheckInCondition, context: VerificationContext): boolean;
|
|
296
|
+
/** Derives a viewer-safe spot status without changing the supplied state. */
|
|
297
|
+
declare function evaluateSpotStatus(spot: Pick<SpotItem, "id" | "prerequisites">, state: Pick<UserRallyState, "records">, options?: {
|
|
298
|
+
readonly verifying?: boolean;
|
|
299
|
+
}): SpotStatus;
|
|
300
|
+
declare const getSpotStatus: typeof evaluateSpotStatus;
|
|
295
301
|
|
|
296
302
|
declare function getOrderedSpots<TLocale extends string, TMeta extends Record<string, unknown>>(spots: ReadonlyArray<SpotItem<TLocale, TMeta>>): ReadonlyArray<SpotItem<TLocale, TMeta>>;
|
|
297
303
|
|
|
@@ -313,6 +319,12 @@ declare function createClaimTicketNumber(rewardId: string, options?: ClaimTicket
|
|
|
313
319
|
declare function createUniqueClaimTicketNumber(rewardId: string, issuedAt: string): string;
|
|
314
320
|
declare function issueClaimTicketNumber(reward: Reward, currentState: RewardState, options?: ClaimTicketOptions): RewardState;
|
|
315
321
|
|
|
322
|
+
interface MergeConflictOptions {
|
|
323
|
+
readonly policy: "server_wins" | "merge";
|
|
324
|
+
}
|
|
325
|
+
/** Resolves a server/local state conflict without mutating either input state. */
|
|
326
|
+
declare function resolveRallyStateConflict(serverState: UserRallyState, localState: UserRallyState, options?: MergeConflictOptions): UserRallyState;
|
|
327
|
+
|
|
316
328
|
type RewardConsumeError = {
|
|
317
329
|
readonly code: "NOT_AVAILABLE" | "ALREADY_CONSUMED" | "OUT_OF_STOCK" | "REWARD_NOT_FOUND";
|
|
318
330
|
readonly rewardId: string;
|
|
@@ -374,6 +386,10 @@ interface OfflineQueueOptions {
|
|
|
374
386
|
removeItem?(key: string): void;
|
|
375
387
|
} | null;
|
|
376
388
|
readonly key?: string;
|
|
389
|
+
/** Rally scope used by the default durable key. */
|
|
390
|
+
readonly rallyId?: string;
|
|
391
|
+
/** User scope used by the default durable key. */
|
|
392
|
+
readonly userId?: string | null;
|
|
377
393
|
readonly databaseName?: string;
|
|
378
394
|
readonly conflictPolicy?: SyncConflictPolicy;
|
|
379
395
|
readonly onSyncConflict?: SyncConflictPolicy | ((context: OfflineConflict) => SyncConflictPolicy | Promise<SyncConflictPolicy>);
|
|
@@ -383,7 +399,35 @@ interface OfflineConflict {
|
|
|
383
399
|
readonly localState: UserRallyState;
|
|
384
400
|
readonly serverState: UserRallyState;
|
|
385
401
|
}
|
|
386
|
-
type OfflineSender = (operation: OfflineOperation) => Promise<OfflineResult | OfflineConflictResult>;
|
|
402
|
+
type OfflineSender = (operation: OfflineOperation) => Promise<OfflineResult | OfflineConflictResult | OfflineOperationResponse>;
|
|
403
|
+
type OfflineOperationStatus = "ACCEPTED" | "REJECTED_PERMANENT" | "RETRYABLE_ERROR";
|
|
404
|
+
interface OfflineOperationError {
|
|
405
|
+
readonly code: string;
|
|
406
|
+
readonly message: string;
|
|
407
|
+
readonly [key: string]: unknown;
|
|
408
|
+
}
|
|
409
|
+
type OfflineOperationResponse = {
|
|
410
|
+
readonly status: "ACCEPTED";
|
|
411
|
+
readonly result?: OfflineResult | OfflineConflictResult;
|
|
412
|
+
readonly state?: UserRallyState;
|
|
413
|
+
} | {
|
|
414
|
+
readonly status: "REJECTED_PERMANENT";
|
|
415
|
+
readonly error?: ClientError | OfflineOperationError;
|
|
416
|
+
readonly reason?: ClientError | OfflineOperationError;
|
|
417
|
+
readonly state?: UserRallyState;
|
|
418
|
+
} | {
|
|
419
|
+
readonly status: "RETRYABLE_ERROR";
|
|
420
|
+
readonly error?: ClientError | OfflineOperationError;
|
|
421
|
+
readonly reason?: ClientError | OfflineOperationError;
|
|
422
|
+
};
|
|
423
|
+
interface OfflineSyncResultEvent {
|
|
424
|
+
readonly operation: OfflineOperation;
|
|
425
|
+
readonly result?: OfflineResult | OfflineConflictResult;
|
|
426
|
+
readonly status?: OfflineOperationStatus;
|
|
427
|
+
readonly error?: ClientError | OfflineOperationError;
|
|
428
|
+
readonly state?: UserRallyState;
|
|
429
|
+
}
|
|
430
|
+
type OfflineSyncResultListener = (event: OfflineSyncResultEvent) => void | Promise<void>;
|
|
387
431
|
declare class MemoryQueueStorage implements OfflineQueueStorage {
|
|
388
432
|
#private;
|
|
389
433
|
load(key: string): Promise<ReadonlyArray<OfflineOperation>>;
|
|
@@ -407,7 +451,15 @@ declare class OfflineQueue {
|
|
|
407
451
|
get pendingCount(): number;
|
|
408
452
|
get error(): Error | null;
|
|
409
453
|
get operations(): ReadonlyArray<OfflineOperation>;
|
|
454
|
+
get conflictPolicy(): SyncConflictPolicy;
|
|
455
|
+
get storageKey(): string;
|
|
456
|
+
get rallyId(): string | undefined;
|
|
457
|
+
get userId(): string | null;
|
|
458
|
+
setSyncResultListener(listener: OfflineSyncResultListener | undefined): void;
|
|
410
459
|
initialize(): Promise<void>;
|
|
460
|
+
/** Selects a rally/user queue scope and loads its pending operations. */
|
|
461
|
+
setScope(rallyId: string, userId: string | null): Promise<void>;
|
|
462
|
+
switchUser(newUserId: string | null): Promise<void>;
|
|
411
463
|
setSender(sender: OfflineSender): void;
|
|
412
464
|
enqueue(operation: OfflineOperation): Promise<void>;
|
|
413
465
|
enqueueCheckIn(request: CheckInRequest): Promise<void>;
|
|
@@ -415,7 +467,7 @@ declare class OfflineQueue {
|
|
|
415
467
|
clear(): Promise<void>;
|
|
416
468
|
sync(sender?: OfflineSender | undefined): Promise<void>;
|
|
417
469
|
retrySync(sender?: OfflineSender | undefined): Promise<void>;
|
|
418
|
-
resolveConflict(operation: OfflineOperation, localState: UserRallyState, serverState: UserRallyState): Promise<
|
|
470
|
+
resolveConflict(operation: OfflineOperation, localState: UserRallyState, serverState: UserRallyState): Promise<UserRallyState>;
|
|
419
471
|
}
|
|
420
472
|
|
|
421
473
|
interface StampStorage {
|
|
@@ -534,7 +586,7 @@ type ClientEvent = {
|
|
|
534
586
|
readonly state: UserRallyState;
|
|
535
587
|
} | {
|
|
536
588
|
readonly type: "error";
|
|
537
|
-
readonly error: ClientError;
|
|
589
|
+
readonly error: ClientError | OfflineOperationError;
|
|
538
590
|
};
|
|
539
591
|
type ClientListener = (state: UserRallyState) => void;
|
|
540
592
|
type ClientEventListener = (event: ClientEvent) => void;
|
|
@@ -703,4 +755,4 @@ type SnapshotTokenVerification<T extends SnapshotTokenPayload = SnapshotTokenPay
|
|
|
703
755
|
declare function createSignedSnapshotToken<T extends SnapshotTokenPayload>(payload: T, secretKey: SnapshotSecretKey): Promise<string>;
|
|
704
756
|
declare function verifySnapshotToken<T extends SnapshotTokenPayload = SnapshotTokenPayload>(token: string, secretKey: SnapshotSecretKey, now?: number): Promise<SnapshotTokenVerification<T>>;
|
|
705
757
|
|
|
706
|
-
export { type AdminRallyConfig, type CheckInCondition, type CheckInOptions, type CheckInRequest, type CheckInResult, type CheckInSuccess, type ClaimOptions, type ClaimRequest, type ClaimResult, type ClaimSuccess, type ClaimTicketOptions, type ClientError, type ClientEvent, type ClientEventListener, type ClientListener, type ClientOptions, type CompositeConditionFailure, type ConditionMatch, type ConditionMismatch, ConfigValidationError, type ConsumeResult, type ConsumeRewardParams, type CustomValidationContext, type CustomValidator, DEFAULT_SHEET_THEME, type DetectorError, type DetectorErrorCode, type DetectorKind, type DetectorResult, type ExternalReference, type FontFamily, type GeoDetectorOptions, type GeoVerificationContext, MemoryQueueStorage as InMemoryOfflineQueueStorage, InMemoryStorage, IndexedDBAdapter, type IndexedDBAdapterOptions, type IndexedDBOfflineQueueOptions, IndexedDBOfflineQueueStorage, LocalStorageAdapter, type LocalStorageAdapterOptions, type LocalStorageFailureMode, type LocaleDictionary, type LocalizedString, type LocalizedText, type NfcDetectorOptions, type NfcVerificationContext, type OfflineConflict, type OfflineConflictResult, type OfflineOperation, OfflineQueue, type OfflineQueueOptions, type OfflineQueueStorage, type OfflineResult, type OfflineSender, type ParseResult, type PasscodeCondition$1 as PasscodeCondition, type ProcessStampValue, type PublicCheckInCondition, type PublicConfigSafety, type PublicRallyConfig, type PublicReward, type PublicSpotItem, type QrDetectorOptions, type QrVerificationContext, type RallyConfig, type RallySnapshot, type RedemptionMethod, type Result, type Reward, type RewardConsumeError, type RewardState, type RewardStatus, type RewardType, type RewardUnlockCondition, type SecureTokenError, type SecureTokenErrorCode, type SecureTokenOptions, type SecureTokenPayload, type SecureTokenSecretKey, type SecureTokenVerification, type SheetTheme, type SlotShape, type SnapshotSecretKey, type SnapshotTokenError, type SnapshotTokenErrorCode, type SnapshotTokenPayload, type SnapshotTokenVerification, type SpotItem, type StampError, StampRallyClient, type StampRallyProgress, type StampRallyState, type StampRecord, type StampStorage, StorageAdapterError, type StorageAdapterErrorCode, type StorageLike, type StorageOperation, type StorageWarningHandler, type SupportedLocale, type SyncAdapter, type SyncConflictPolicy, type SyncState, THEME_PRESETS, type ThemePreset, type ThemePresetId, type UserRallyState, type ValidationError, type Validator, type VerificationContext, assertPublicConfig, calculateDistanceMeters, calculateProgress, consumeReward, createClaimTicketNumber, createSecureToken, createSignedSnapshotToken, createUniqueClaimTicketNumber, evaluateCondition, evaluateConditionDetailed, exportProgressToken, getCurrentGeoContext, getOrderedSpots, importProgressToken, isGeolocationSupported, isNfcSupported, isPublicConfig, isQrSupported, isRewardState, isStampRallyState, issueClaimTicketNumber, normalizePasscode, parseAdminConfig, parsePublicConfig, processStamp, readNfcContext, readQrContext, reconcileRewardStates, resolveLocalizedText, safeParseAdminConfig, safeParsePublicConfig, sanitizeAdminConfig, storageKey, toLocalizedString, toPublicConfig, updateLocalizedField, validatePublicConfigSafety, verifyPasscode, verifySecureToken, verifySnapshotToken };
|
|
758
|
+
export { type AdminRallyConfig, type CheckInCondition, type CheckInOptions, type CheckInRequest, type CheckInResult, type CheckInSuccess, type ClaimOptions, type ClaimRequest, type ClaimResult, type ClaimSuccess, type ClaimTicketOptions, type ClientError, type ClientEvent, type ClientEventListener, type ClientListener, type ClientOptions, type CompositeConditionFailure, type ConditionMatch, type ConditionMismatch, ConfigValidationError, type ConsumeResult, type ConsumeRewardParams, type CustomValidationContext, type CustomValidator, DEFAULT_SHEET_THEME, type DetectorError, type DetectorErrorCode, type DetectorKind, type DetectorResult, type ExternalReference, type FontFamily, type GeoDetectorOptions, type GeoVerificationContext, MemoryQueueStorage as InMemoryOfflineQueueStorage, InMemoryStorage, IndexedDBAdapter, type IndexedDBAdapterOptions, type IndexedDBOfflineQueueOptions, IndexedDBOfflineQueueStorage, LocalStorageAdapter, type LocalStorageAdapterOptions, type LocalStorageFailureMode, type LocaleDictionary, type LocalizedString, type LocalizedText, type MergeConflictOptions, type NfcDetectorOptions, type NfcVerificationContext, type OfflineConflict, type OfflineConflictResult, type OfflineOperation, type OfflineOperationError, type OfflineOperationResponse, type OfflineOperationStatus, OfflineQueue, type OfflineQueueOptions, type OfflineQueueStorage, type OfflineResult, type OfflineSender, type OfflineSyncResultEvent, type OfflineSyncResultListener, type ParseResult, type PasscodeCondition$1 as PasscodeCondition, type ProcessStampValue, type PublicCheckInCondition, type PublicConfigSafety, type PublicRallyConfig, type PublicReward, type PublicSpotItem, type QrDetectorOptions, type QrVerificationContext, type RallyConfig, type RallySnapshot, type RedemptionMethod, type Result, type Reward, type RewardConsumeError, type RewardState, type RewardStatus, type RewardType, type RewardUnlockCondition, type SecureTokenError, type SecureTokenErrorCode, type SecureTokenOptions, type SecureTokenPayload, type SecureTokenSecretKey, type SecureTokenVerification, type SheetTheme, type SlotShape, type SnapshotSecretKey, type SnapshotTokenError, type SnapshotTokenErrorCode, type SnapshotTokenPayload, type SnapshotTokenVerification, type SpotItem, type SpotStatus, type StampError, StampRallyClient, type StampRallyProgress, type StampRallyState, type StampRecord, type StampStorage, StorageAdapterError, type StorageAdapterErrorCode, type StorageLike, type StorageOperation, type StorageWarningHandler, type SupportedLocale, type SyncAdapter, type SyncConflictPolicy, type SyncState, THEME_PRESETS, type ThemePreset, type ThemePresetId, type UserRallyState, type ValidationError, type Validator, type VerificationContext, assertPublicConfig, calculateDistanceMeters, calculateProgress, consumeReward, createClaimTicketNumber, createSecureToken, createSignedSnapshotToken, createUniqueClaimTicketNumber, evaluateCondition, evaluateConditionDetailed, evaluateSpotStatus, exportProgressToken, getCurrentGeoContext, getOrderedSpots, getSpotStatus, importProgressToken, isGeolocationSupported, isNfcSupported, isPublicConfig, isQrSupported, isRewardState, isStampRallyState, issueClaimTicketNumber, normalizePasscode, parseAdminConfig, parsePublicConfig, processStamp, readNfcContext, readQrContext, reconcileRewardStates, resolveLocalizedText, resolveRallyStateConflict, safeParseAdminConfig, safeParsePublicConfig, sanitizeAdminConfig, storageKey, toLocalizedString, toPublicConfig, updateLocalizedField, validatePublicConfigSafety, verifyPasscode, verifySecureToken, verifySnapshotToken };
|
package/dist/index.d.ts
CHANGED
|
@@ -147,6 +147,7 @@ interface StampRecord {
|
|
|
147
147
|
readonly acquiredAt: string;
|
|
148
148
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
149
149
|
}
|
|
150
|
+
type SpotStatus = "UNCLAIMED" | "CLAIMED" | "LOCKED" | "VERIFYING";
|
|
150
151
|
type RewardStatus = "LOCKED" | "AVAILABLE" | "CONSUMED" | "EXPIRED";
|
|
151
152
|
interface RewardState {
|
|
152
153
|
readonly rewardId: string;
|
|
@@ -292,6 +293,11 @@ declare function parsePublicConfig(input: unknown): PublicRallyConfig;
|
|
|
292
293
|
declare function calculateDistanceMeters(aLat: number, aLon: number, bLat: number, bLon: number): number;
|
|
293
294
|
declare function evaluateConditionDetailed(condition: CheckInCondition, context: VerificationContext): Result<ConditionMatch, ConditionMismatch>;
|
|
294
295
|
declare function evaluateCondition(condition: CheckInCondition, context: VerificationContext): boolean;
|
|
296
|
+
/** Derives a viewer-safe spot status without changing the supplied state. */
|
|
297
|
+
declare function evaluateSpotStatus(spot: Pick<SpotItem, "id" | "prerequisites">, state: Pick<UserRallyState, "records">, options?: {
|
|
298
|
+
readonly verifying?: boolean;
|
|
299
|
+
}): SpotStatus;
|
|
300
|
+
declare const getSpotStatus: typeof evaluateSpotStatus;
|
|
295
301
|
|
|
296
302
|
declare function getOrderedSpots<TLocale extends string, TMeta extends Record<string, unknown>>(spots: ReadonlyArray<SpotItem<TLocale, TMeta>>): ReadonlyArray<SpotItem<TLocale, TMeta>>;
|
|
297
303
|
|
|
@@ -313,6 +319,12 @@ declare function createClaimTicketNumber(rewardId: string, options?: ClaimTicket
|
|
|
313
319
|
declare function createUniqueClaimTicketNumber(rewardId: string, issuedAt: string): string;
|
|
314
320
|
declare function issueClaimTicketNumber(reward: Reward, currentState: RewardState, options?: ClaimTicketOptions): RewardState;
|
|
315
321
|
|
|
322
|
+
interface MergeConflictOptions {
|
|
323
|
+
readonly policy: "server_wins" | "merge";
|
|
324
|
+
}
|
|
325
|
+
/** Resolves a server/local state conflict without mutating either input state. */
|
|
326
|
+
declare function resolveRallyStateConflict(serverState: UserRallyState, localState: UserRallyState, options?: MergeConflictOptions): UserRallyState;
|
|
327
|
+
|
|
316
328
|
type RewardConsumeError = {
|
|
317
329
|
readonly code: "NOT_AVAILABLE" | "ALREADY_CONSUMED" | "OUT_OF_STOCK" | "REWARD_NOT_FOUND";
|
|
318
330
|
readonly rewardId: string;
|
|
@@ -374,6 +386,10 @@ interface OfflineQueueOptions {
|
|
|
374
386
|
removeItem?(key: string): void;
|
|
375
387
|
} | null;
|
|
376
388
|
readonly key?: string;
|
|
389
|
+
/** Rally scope used by the default durable key. */
|
|
390
|
+
readonly rallyId?: string;
|
|
391
|
+
/** User scope used by the default durable key. */
|
|
392
|
+
readonly userId?: string | null;
|
|
377
393
|
readonly databaseName?: string;
|
|
378
394
|
readonly conflictPolicy?: SyncConflictPolicy;
|
|
379
395
|
readonly onSyncConflict?: SyncConflictPolicy | ((context: OfflineConflict) => SyncConflictPolicy | Promise<SyncConflictPolicy>);
|
|
@@ -383,7 +399,35 @@ interface OfflineConflict {
|
|
|
383
399
|
readonly localState: UserRallyState;
|
|
384
400
|
readonly serverState: UserRallyState;
|
|
385
401
|
}
|
|
386
|
-
type OfflineSender = (operation: OfflineOperation) => Promise<OfflineResult | OfflineConflictResult>;
|
|
402
|
+
type OfflineSender = (operation: OfflineOperation) => Promise<OfflineResult | OfflineConflictResult | OfflineOperationResponse>;
|
|
403
|
+
type OfflineOperationStatus = "ACCEPTED" | "REJECTED_PERMANENT" | "RETRYABLE_ERROR";
|
|
404
|
+
interface OfflineOperationError {
|
|
405
|
+
readonly code: string;
|
|
406
|
+
readonly message: string;
|
|
407
|
+
readonly [key: string]: unknown;
|
|
408
|
+
}
|
|
409
|
+
type OfflineOperationResponse = {
|
|
410
|
+
readonly status: "ACCEPTED";
|
|
411
|
+
readonly result?: OfflineResult | OfflineConflictResult;
|
|
412
|
+
readonly state?: UserRallyState;
|
|
413
|
+
} | {
|
|
414
|
+
readonly status: "REJECTED_PERMANENT";
|
|
415
|
+
readonly error?: ClientError | OfflineOperationError;
|
|
416
|
+
readonly reason?: ClientError | OfflineOperationError;
|
|
417
|
+
readonly state?: UserRallyState;
|
|
418
|
+
} | {
|
|
419
|
+
readonly status: "RETRYABLE_ERROR";
|
|
420
|
+
readonly error?: ClientError | OfflineOperationError;
|
|
421
|
+
readonly reason?: ClientError | OfflineOperationError;
|
|
422
|
+
};
|
|
423
|
+
interface OfflineSyncResultEvent {
|
|
424
|
+
readonly operation: OfflineOperation;
|
|
425
|
+
readonly result?: OfflineResult | OfflineConflictResult;
|
|
426
|
+
readonly status?: OfflineOperationStatus;
|
|
427
|
+
readonly error?: ClientError | OfflineOperationError;
|
|
428
|
+
readonly state?: UserRallyState;
|
|
429
|
+
}
|
|
430
|
+
type OfflineSyncResultListener = (event: OfflineSyncResultEvent) => void | Promise<void>;
|
|
387
431
|
declare class MemoryQueueStorage implements OfflineQueueStorage {
|
|
388
432
|
#private;
|
|
389
433
|
load(key: string): Promise<ReadonlyArray<OfflineOperation>>;
|
|
@@ -407,7 +451,15 @@ declare class OfflineQueue {
|
|
|
407
451
|
get pendingCount(): number;
|
|
408
452
|
get error(): Error | null;
|
|
409
453
|
get operations(): ReadonlyArray<OfflineOperation>;
|
|
454
|
+
get conflictPolicy(): SyncConflictPolicy;
|
|
455
|
+
get storageKey(): string;
|
|
456
|
+
get rallyId(): string | undefined;
|
|
457
|
+
get userId(): string | null;
|
|
458
|
+
setSyncResultListener(listener: OfflineSyncResultListener | undefined): void;
|
|
410
459
|
initialize(): Promise<void>;
|
|
460
|
+
/** Selects a rally/user queue scope and loads its pending operations. */
|
|
461
|
+
setScope(rallyId: string, userId: string | null): Promise<void>;
|
|
462
|
+
switchUser(newUserId: string | null): Promise<void>;
|
|
411
463
|
setSender(sender: OfflineSender): void;
|
|
412
464
|
enqueue(operation: OfflineOperation): Promise<void>;
|
|
413
465
|
enqueueCheckIn(request: CheckInRequest): Promise<void>;
|
|
@@ -415,7 +467,7 @@ declare class OfflineQueue {
|
|
|
415
467
|
clear(): Promise<void>;
|
|
416
468
|
sync(sender?: OfflineSender | undefined): Promise<void>;
|
|
417
469
|
retrySync(sender?: OfflineSender | undefined): Promise<void>;
|
|
418
|
-
resolveConflict(operation: OfflineOperation, localState: UserRallyState, serverState: UserRallyState): Promise<
|
|
470
|
+
resolveConflict(operation: OfflineOperation, localState: UserRallyState, serverState: UserRallyState): Promise<UserRallyState>;
|
|
419
471
|
}
|
|
420
472
|
|
|
421
473
|
interface StampStorage {
|
|
@@ -534,7 +586,7 @@ type ClientEvent = {
|
|
|
534
586
|
readonly state: UserRallyState;
|
|
535
587
|
} | {
|
|
536
588
|
readonly type: "error";
|
|
537
|
-
readonly error: ClientError;
|
|
589
|
+
readonly error: ClientError | OfflineOperationError;
|
|
538
590
|
};
|
|
539
591
|
type ClientListener = (state: UserRallyState) => void;
|
|
540
592
|
type ClientEventListener = (event: ClientEvent) => void;
|
|
@@ -703,4 +755,4 @@ type SnapshotTokenVerification<T extends SnapshotTokenPayload = SnapshotTokenPay
|
|
|
703
755
|
declare function createSignedSnapshotToken<T extends SnapshotTokenPayload>(payload: T, secretKey: SnapshotSecretKey): Promise<string>;
|
|
704
756
|
declare function verifySnapshotToken<T extends SnapshotTokenPayload = SnapshotTokenPayload>(token: string, secretKey: SnapshotSecretKey, now?: number): Promise<SnapshotTokenVerification<T>>;
|
|
705
757
|
|
|
706
|
-
export { type AdminRallyConfig, type CheckInCondition, type CheckInOptions, type CheckInRequest, type CheckInResult, type CheckInSuccess, type ClaimOptions, type ClaimRequest, type ClaimResult, type ClaimSuccess, type ClaimTicketOptions, type ClientError, type ClientEvent, type ClientEventListener, type ClientListener, type ClientOptions, type CompositeConditionFailure, type ConditionMatch, type ConditionMismatch, ConfigValidationError, type ConsumeResult, type ConsumeRewardParams, type CustomValidationContext, type CustomValidator, DEFAULT_SHEET_THEME, type DetectorError, type DetectorErrorCode, type DetectorKind, type DetectorResult, type ExternalReference, type FontFamily, type GeoDetectorOptions, type GeoVerificationContext, MemoryQueueStorage as InMemoryOfflineQueueStorage, InMemoryStorage, IndexedDBAdapter, type IndexedDBAdapterOptions, type IndexedDBOfflineQueueOptions, IndexedDBOfflineQueueStorage, LocalStorageAdapter, type LocalStorageAdapterOptions, type LocalStorageFailureMode, type LocaleDictionary, type LocalizedString, type LocalizedText, type NfcDetectorOptions, type NfcVerificationContext, type OfflineConflict, type OfflineConflictResult, type OfflineOperation, OfflineQueue, type OfflineQueueOptions, type OfflineQueueStorage, type OfflineResult, type OfflineSender, type ParseResult, type PasscodeCondition$1 as PasscodeCondition, type ProcessStampValue, type PublicCheckInCondition, type PublicConfigSafety, type PublicRallyConfig, type PublicReward, type PublicSpotItem, type QrDetectorOptions, type QrVerificationContext, type RallyConfig, type RallySnapshot, type RedemptionMethod, type Result, type Reward, type RewardConsumeError, type RewardState, type RewardStatus, type RewardType, type RewardUnlockCondition, type SecureTokenError, type SecureTokenErrorCode, type SecureTokenOptions, type SecureTokenPayload, type SecureTokenSecretKey, type SecureTokenVerification, type SheetTheme, type SlotShape, type SnapshotSecretKey, type SnapshotTokenError, type SnapshotTokenErrorCode, type SnapshotTokenPayload, type SnapshotTokenVerification, type SpotItem, type StampError, StampRallyClient, type StampRallyProgress, type StampRallyState, type StampRecord, type StampStorage, StorageAdapterError, type StorageAdapterErrorCode, type StorageLike, type StorageOperation, type StorageWarningHandler, type SupportedLocale, type SyncAdapter, type SyncConflictPolicy, type SyncState, THEME_PRESETS, type ThemePreset, type ThemePresetId, type UserRallyState, type ValidationError, type Validator, type VerificationContext, assertPublicConfig, calculateDistanceMeters, calculateProgress, consumeReward, createClaimTicketNumber, createSecureToken, createSignedSnapshotToken, createUniqueClaimTicketNumber, evaluateCondition, evaluateConditionDetailed, exportProgressToken, getCurrentGeoContext, getOrderedSpots, importProgressToken, isGeolocationSupported, isNfcSupported, isPublicConfig, isQrSupported, isRewardState, isStampRallyState, issueClaimTicketNumber, normalizePasscode, parseAdminConfig, parsePublicConfig, processStamp, readNfcContext, readQrContext, reconcileRewardStates, resolveLocalizedText, safeParseAdminConfig, safeParsePublicConfig, sanitizeAdminConfig, storageKey, toLocalizedString, toPublicConfig, updateLocalizedField, validatePublicConfigSafety, verifyPasscode, verifySecureToken, verifySnapshotToken };
|
|
758
|
+
export { type AdminRallyConfig, type CheckInCondition, type CheckInOptions, type CheckInRequest, type CheckInResult, type CheckInSuccess, type ClaimOptions, type ClaimRequest, type ClaimResult, type ClaimSuccess, type ClaimTicketOptions, type ClientError, type ClientEvent, type ClientEventListener, type ClientListener, type ClientOptions, type CompositeConditionFailure, type ConditionMatch, type ConditionMismatch, ConfigValidationError, type ConsumeResult, type ConsumeRewardParams, type CustomValidationContext, type CustomValidator, DEFAULT_SHEET_THEME, type DetectorError, type DetectorErrorCode, type DetectorKind, type DetectorResult, type ExternalReference, type FontFamily, type GeoDetectorOptions, type GeoVerificationContext, MemoryQueueStorage as InMemoryOfflineQueueStorage, InMemoryStorage, IndexedDBAdapter, type IndexedDBAdapterOptions, type IndexedDBOfflineQueueOptions, IndexedDBOfflineQueueStorage, LocalStorageAdapter, type LocalStorageAdapterOptions, type LocalStorageFailureMode, type LocaleDictionary, type LocalizedString, type LocalizedText, type MergeConflictOptions, type NfcDetectorOptions, type NfcVerificationContext, type OfflineConflict, type OfflineConflictResult, type OfflineOperation, type OfflineOperationError, type OfflineOperationResponse, type OfflineOperationStatus, OfflineQueue, type OfflineQueueOptions, type OfflineQueueStorage, type OfflineResult, type OfflineSender, type OfflineSyncResultEvent, type OfflineSyncResultListener, type ParseResult, type PasscodeCondition$1 as PasscodeCondition, type ProcessStampValue, type PublicCheckInCondition, type PublicConfigSafety, type PublicRallyConfig, type PublicReward, type PublicSpotItem, type QrDetectorOptions, type QrVerificationContext, type RallyConfig, type RallySnapshot, type RedemptionMethod, type Result, type Reward, type RewardConsumeError, type RewardState, type RewardStatus, type RewardType, type RewardUnlockCondition, type SecureTokenError, type SecureTokenErrorCode, type SecureTokenOptions, type SecureTokenPayload, type SecureTokenSecretKey, type SecureTokenVerification, type SheetTheme, type SlotShape, type SnapshotSecretKey, type SnapshotTokenError, type SnapshotTokenErrorCode, type SnapshotTokenPayload, type SnapshotTokenVerification, type SpotItem, type SpotStatus, type StampError, StampRallyClient, type StampRallyProgress, type StampRallyState, type StampRecord, type StampStorage, StorageAdapterError, type StorageAdapterErrorCode, type StorageLike, type StorageOperation, type StorageWarningHandler, type SupportedLocale, type SyncAdapter, type SyncConflictPolicy, type SyncState, THEME_PRESETS, type ThemePreset, type ThemePresetId, type UserRallyState, type ValidationError, type Validator, type VerificationContext, assertPublicConfig, calculateDistanceMeters, calculateProgress, consumeReward, createClaimTicketNumber, createSecureToken, createSignedSnapshotToken, createUniqueClaimTicketNumber, evaluateCondition, evaluateConditionDetailed, evaluateSpotStatus, exportProgressToken, getCurrentGeoContext, getOrderedSpots, getSpotStatus, importProgressToken, isGeolocationSupported, isNfcSupported, isPublicConfig, isQrSupported, isRewardState, isStampRallyState, issueClaimTicketNumber, normalizePasscode, parseAdminConfig, parsePublicConfig, processStamp, readNfcContext, readQrContext, reconcileRewardStates, resolveLocalizedText, resolveRallyStateConflict, safeParseAdminConfig, safeParsePublicConfig, sanitizeAdminConfig, storageKey, toLocalizedString, toPublicConfig, updateLocalizedField, validatePublicConfigSafety, verifyPasscode, verifySecureToken, verifySnapshotToken };
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,14 @@ function evaluateConditionDetailed(condition2, context) {
|
|
|
39
39
|
function evaluateCondition(condition2, context) {
|
|
40
40
|
return evaluateConditionDetailed(condition2, context).ok;
|
|
41
41
|
}
|
|
42
|
+
function evaluateSpotStatus(spot2, state, options = {}) {
|
|
43
|
+
if (options.verifying === true) return "VERIFYING";
|
|
44
|
+
if (state.records.some((record) => record.stampId === spot2.id)) return "CLAIMED";
|
|
45
|
+
const acquired = new Set(state.records.map((record) => record.stampId));
|
|
46
|
+
if (spot2.prerequisites?.some((prerequisite) => !acquired.has(prerequisite))) return "LOCKED";
|
|
47
|
+
return "UNCLAIMED";
|
|
48
|
+
}
|
|
49
|
+
var getSpotStatus = evaluateSpotStatus;
|
|
42
50
|
|
|
43
51
|
// src/engine/order.ts
|
|
44
52
|
function getOrderedSpots(spots) {
|
|
@@ -97,6 +105,76 @@ function issueClaimTicketNumber(reward2, currentState, options = {}) {
|
|
|
97
105
|
return { ...currentState, claimTicketNumber };
|
|
98
106
|
}
|
|
99
107
|
|
|
108
|
+
// src/engine/sync.ts
|
|
109
|
+
function latestTimestamp(serverTimestamp, localTimestamp) {
|
|
110
|
+
const serverTime = Date.parse(serverTimestamp);
|
|
111
|
+
const localTime = Date.parse(localTimestamp);
|
|
112
|
+
if (!Number.isNaN(serverTime) && !Number.isNaN(localTime))
|
|
113
|
+
return serverTime >= localTime ? serverTimestamp : localTimestamp;
|
|
114
|
+
if (!Number.isNaN(serverTime)) return serverTimestamp;
|
|
115
|
+
if (!Number.isNaN(localTime)) return localTimestamp;
|
|
116
|
+
return serverTimestamp >= localTimestamp ? serverTimestamp : localTimestamp;
|
|
117
|
+
}
|
|
118
|
+
function mergeRewardStates(serverRewards, localRewards) {
|
|
119
|
+
const merged = new Map(serverRewards.map((reward2) => [reward2.rewardId, reward2]));
|
|
120
|
+
for (const localReward of localRewards) {
|
|
121
|
+
const serverReward = merged.get(localReward.rewardId);
|
|
122
|
+
if (serverReward === void 0) {
|
|
123
|
+
merged.set(localReward.rewardId, localReward);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const winner = localReward.status === "CONSUMED" && serverReward.status !== "CONSUMED" ? localReward : serverReward;
|
|
127
|
+
merged.set(localReward.rewardId, {
|
|
128
|
+
...winner,
|
|
129
|
+
...serverReward.unlockedAt === void 0 && localReward.unlockedAt === void 0 ? {} : {
|
|
130
|
+
unlockedAt: serverReward.unlockedAt === void 0 ? localReward.unlockedAt : localReward.unlockedAt === void 0 ? serverReward.unlockedAt : latestTimestamp(serverReward.unlockedAt, localReward.unlockedAt)
|
|
131
|
+
},
|
|
132
|
+
...serverReward.consumedAt === void 0 && localReward.consumedAt === void 0 ? {} : {
|
|
133
|
+
consumedAt: serverReward.consumedAt === void 0 ? localReward.consumedAt : localReward.consumedAt === void 0 ? serverReward.consumedAt : latestTimestamp(serverReward.consumedAt, localReward.consumedAt)
|
|
134
|
+
},
|
|
135
|
+
...serverReward.redeemedCount === void 0 && localReward.redeemedCount === void 0 ? {} : {
|
|
136
|
+
redeemedCount: Math.max(
|
|
137
|
+
serverReward.redeemedCount ?? 0,
|
|
138
|
+
localReward.redeemedCount ?? 0
|
|
139
|
+
)
|
|
140
|
+
},
|
|
141
|
+
...serverReward.userRedemptionCount === void 0 && localReward.userRedemptionCount === void 0 ? {} : {
|
|
142
|
+
userRedemptionCount: Math.max(
|
|
143
|
+
serverReward.userRedemptionCount ?? 0,
|
|
144
|
+
localReward.userRedemptionCount ?? 0
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return [...merged.values()];
|
|
150
|
+
}
|
|
151
|
+
function mergeStampRecords(serverRecords, localRecords) {
|
|
152
|
+
const merged = /* @__PURE__ */ new Map();
|
|
153
|
+
for (const record of [...serverRecords, ...localRecords]) {
|
|
154
|
+
const current = merged.get(record.stampId);
|
|
155
|
+
if (current === void 0) {
|
|
156
|
+
merged.set(record.stampId, record);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const acquiredAt = latestTimestamp(current.acquiredAt, record.acquiredAt);
|
|
160
|
+
merged.set(record.stampId, {
|
|
161
|
+
...current,
|
|
162
|
+
...acquiredAt === current.acquiredAt ? {} : { acquiredAt },
|
|
163
|
+
...current.metadata === void 0 && record.metadata !== void 0 ? { metadata: record.metadata } : {}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return [...merged.values()];
|
|
167
|
+
}
|
|
168
|
+
function resolveRallyStateConflict(serverState, localState, options = { policy: "merge" }) {
|
|
169
|
+
if (options.policy === "server_wins") return serverState;
|
|
170
|
+
return {
|
|
171
|
+
...serverState,
|
|
172
|
+
records: mergeStampRecords(serverState.records, localState.records),
|
|
173
|
+
rewards: mergeRewardStates(serverState.rewards, localState.rewards),
|
|
174
|
+
updatedAt: latestTimestamp(serverState.updatedAt, localState.updatedAt)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
100
178
|
// src/detectors/types.ts
|
|
101
179
|
function createDetectorError(detector, code, message, cause) {
|
|
102
180
|
return cause === void 0 ? { detector, code, message } : { detector, code, message, cause };
|
|
@@ -998,6 +1076,7 @@ var StampRallyClient = class {
|
|
|
998
1076
|
this.#storage = this.#options.storage ?? new InMemoryStorage();
|
|
999
1077
|
this.#userId = this.#options.userId ?? null;
|
|
1000
1078
|
this.#offlineQueue = this.#options.offlineQueue;
|
|
1079
|
+
this.#offlineQueue?.setSyncResultListener((event) => this.#handleOfflineSyncResult(event));
|
|
1001
1080
|
}
|
|
1002
1081
|
getConfig() {
|
|
1003
1082
|
return this.#config;
|
|
@@ -1028,7 +1107,10 @@ var StampRallyClient = class {
|
|
|
1028
1107
|
initialize() {
|
|
1029
1108
|
if (this.#state !== null) return Promise.resolve(this.#state);
|
|
1030
1109
|
if (this.#initialization === null) {
|
|
1031
|
-
this.#initialization =
|
|
1110
|
+
this.#initialization = (async () => {
|
|
1111
|
+
await this.#offlineQueue?.setScope(this.#config.id, this.#userId);
|
|
1112
|
+
return this.#storage.load(this.#config.id, this.#userId);
|
|
1113
|
+
})().then((state) => {
|
|
1032
1114
|
const next = state === null ? emptyState(this.#config, this.#userId, this.#now()) : this.#reconcile(state);
|
|
1033
1115
|
this.#state = next;
|
|
1034
1116
|
this.#emit(next);
|
|
@@ -1046,6 +1128,7 @@ var StampRallyClient = class {
|
|
|
1046
1128
|
this.#userId = newUserId;
|
|
1047
1129
|
this.#state = null;
|
|
1048
1130
|
this.#initialization = null;
|
|
1131
|
+
await this.#offlineQueue?.switchUser(newUserId);
|
|
1049
1132
|
return this.initialize();
|
|
1050
1133
|
});
|
|
1051
1134
|
}
|
|
@@ -1215,12 +1298,19 @@ var StampRallyClient = class {
|
|
|
1215
1298
|
});
|
|
1216
1299
|
}
|
|
1217
1300
|
if (adapter?.sync === void 0) {
|
|
1218
|
-
this.#emitEvent({ type: "sync", state: current });
|
|
1301
|
+
this.#emitEvent({ type: "sync", state: this.#state ?? current });
|
|
1219
1302
|
return;
|
|
1220
1303
|
}
|
|
1221
|
-
const
|
|
1222
|
-
|
|
1223
|
-
|
|
1304
|
+
const serverState = await adapter.sync({
|
|
1305
|
+
rallyId: this.#config.id,
|
|
1306
|
+
userId: this.#userId,
|
|
1307
|
+
state: this.#state ?? current
|
|
1308
|
+
});
|
|
1309
|
+
const localState = this.#state ?? current;
|
|
1310
|
+
const merged = this.#offlineQueue === void 0 ? serverState : resolveRallyStateConflict(serverState, localState, {
|
|
1311
|
+
policy: this.#offlineQueue.conflictPolicy
|
|
1312
|
+
});
|
|
1313
|
+
const next = this.#reconcile(merged);
|
|
1224
1314
|
await this.#storage.save(next);
|
|
1225
1315
|
this.#state = next;
|
|
1226
1316
|
this.#emit(next);
|
|
@@ -1280,6 +1370,17 @@ var StampRallyClient = class {
|
|
|
1280
1370
|
)
|
|
1281
1371
|
};
|
|
1282
1372
|
}
|
|
1373
|
+
async #handleOfflineSyncResult(event) {
|
|
1374
|
+
if (event.state !== void 0) {
|
|
1375
|
+
const next = this.#reconcile(event.state);
|
|
1376
|
+
await this.#storage.save(next);
|
|
1377
|
+
this.#state = next;
|
|
1378
|
+
this.#emit(next);
|
|
1379
|
+
}
|
|
1380
|
+
if (event.error !== void 0) this.#emitEvent({ type: "error", error: event.error });
|
|
1381
|
+
else if (event.result !== void 0 && "ok" in event.result && !event.result.ok)
|
|
1382
|
+
this.#emitEvent({ type: "error", error: event.result.error });
|
|
1383
|
+
}
|
|
1283
1384
|
#now() {
|
|
1284
1385
|
return this.#options.clock?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1285
1386
|
}
|
|
@@ -1405,9 +1506,27 @@ function defaultStorage(databaseName) {
|
|
|
1405
1506
|
function operationId(operation) {
|
|
1406
1507
|
return operation.kind === "checkIn" ? `checkIn:${operation.request.rallyId}:${operation.request.userId ?? "anonymous"}:${operation.request.idempotencyKey}` : `claimReward:${operation.request.rallyId}:${operation.request.userId ?? "anonymous"}:${operation.request.idempotencyKey}`;
|
|
1407
1508
|
}
|
|
1509
|
+
function requestScope(operation) {
|
|
1510
|
+
return {
|
|
1511
|
+
rallyId: operation.request.rallyId,
|
|
1512
|
+
userId: operation.request.userId
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
function errorValue(value, fallbackCode) {
|
|
1516
|
+
if (typeof value === "object" && value !== null) {
|
|
1517
|
+
const candidate = value;
|
|
1518
|
+
if (typeof candidate.code === "string" && typeof candidate.message === "string")
|
|
1519
|
+
return { ...candidate, code: candidate.code, message: candidate.message };
|
|
1520
|
+
}
|
|
1521
|
+
if (value instanceof Error) return { code: fallbackCode, message: value.message };
|
|
1522
|
+
if (typeof value === "string") return { code: fallbackCode, message: value };
|
|
1523
|
+
return { code: fallbackCode, message: "Offline operation was rejected." };
|
|
1524
|
+
}
|
|
1408
1525
|
var OfflineQueue = class {
|
|
1409
1526
|
#storage;
|
|
1410
|
-
#
|
|
1527
|
+
#configuredKey;
|
|
1528
|
+
#rallyId;
|
|
1529
|
+
#userId;
|
|
1411
1530
|
#conflictPolicy;
|
|
1412
1531
|
#onSyncConflict;
|
|
1413
1532
|
#operations = [];
|
|
@@ -1416,12 +1535,15 @@ var OfflineQueue = class {
|
|
|
1416
1535
|
#error = null;
|
|
1417
1536
|
#sender;
|
|
1418
1537
|
#syncPromise = null;
|
|
1538
|
+
#syncResultListener;
|
|
1419
1539
|
constructor(options = {}) {
|
|
1420
1540
|
if (options.storage !== void 0) this.#storage = options.storage;
|
|
1421
1541
|
else if (options.storageLike !== void 0 && options.storageLike !== null)
|
|
1422
1542
|
this.#storage = new LocalStorageQueueStorage(options.storageLike);
|
|
1423
1543
|
else this.#storage = defaultStorage(options.databaseName);
|
|
1424
|
-
this.#
|
|
1544
|
+
this.#configuredKey = options.key;
|
|
1545
|
+
this.#rallyId = options.rallyId;
|
|
1546
|
+
this.#userId = options.userId ?? null;
|
|
1425
1547
|
this.#conflictPolicy = options.conflictPolicy ?? "server_wins";
|
|
1426
1548
|
this.#onSyncConflict = options.onSyncConflict;
|
|
1427
1549
|
}
|
|
@@ -1437,20 +1559,60 @@ var OfflineQueue = class {
|
|
|
1437
1559
|
get operations() {
|
|
1438
1560
|
return this.#operations;
|
|
1439
1561
|
}
|
|
1562
|
+
get conflictPolicy() {
|
|
1563
|
+
return this.#conflictPolicy;
|
|
1564
|
+
}
|
|
1565
|
+
get storageKey() {
|
|
1566
|
+
return this.#storageKey();
|
|
1567
|
+
}
|
|
1568
|
+
get rallyId() {
|
|
1569
|
+
return this.#rallyId;
|
|
1570
|
+
}
|
|
1571
|
+
get userId() {
|
|
1572
|
+
return this.#userId;
|
|
1573
|
+
}
|
|
1574
|
+
setSyncResultListener(listener) {
|
|
1575
|
+
this.#syncResultListener = listener;
|
|
1576
|
+
}
|
|
1440
1577
|
async initialize() {
|
|
1441
1578
|
if (this.#loaded) return;
|
|
1442
|
-
this.#operations = [...await this.#storage.load(this.#
|
|
1579
|
+
this.#operations = [...await this.#storage.load(this.#storageKey())];
|
|
1443
1580
|
this.#loaded = true;
|
|
1444
1581
|
}
|
|
1582
|
+
/** Selects a rally/user queue scope and loads its pending operations. */
|
|
1583
|
+
async setScope(rallyId, userId) {
|
|
1584
|
+
if (this.#configuredKey !== void 0) {
|
|
1585
|
+
this.#rallyId = rallyId;
|
|
1586
|
+
this.#userId = userId;
|
|
1587
|
+
return this.initialize();
|
|
1588
|
+
}
|
|
1589
|
+
if (this.#rallyId === rallyId && this.#userId === userId && this.#loaded) return;
|
|
1590
|
+
this.#rallyId = rallyId;
|
|
1591
|
+
this.#userId = userId;
|
|
1592
|
+
this.#operations = [];
|
|
1593
|
+
this.#loaded = false;
|
|
1594
|
+
await this.initialize();
|
|
1595
|
+
}
|
|
1596
|
+
async switchUser(newUserId) {
|
|
1597
|
+
if (this.#rallyId === void 0)
|
|
1598
|
+
throw new Error("OfflineQueue.switchUser requires a rally scope.");
|
|
1599
|
+
await this.setScope(this.#rallyId, newUserId);
|
|
1600
|
+
}
|
|
1445
1601
|
setSender(sender) {
|
|
1446
1602
|
this.#sender = sender;
|
|
1447
1603
|
}
|
|
1448
1604
|
async enqueue(operation) {
|
|
1605
|
+
if (this.#configuredKey === void 0) {
|
|
1606
|
+
const scope = requestScope(operation);
|
|
1607
|
+
if (this.#rallyId === void 0) await this.setScope(scope.rallyId, scope.userId);
|
|
1608
|
+
if (this.#rallyId !== scope.rallyId || this.#userId !== scope.userId)
|
|
1609
|
+
throw new Error("Offline operation belongs to another rally or user queue.");
|
|
1610
|
+
}
|
|
1449
1611
|
await this.initialize();
|
|
1450
1612
|
const id2 = operationId(operation);
|
|
1451
1613
|
if (this.#operations.some((item) => operationId(item) === id2)) return;
|
|
1452
1614
|
this.#operations = [...this.#operations, operation];
|
|
1453
|
-
await this.#storage.save(this.#
|
|
1615
|
+
await this.#storage.save(this.#storageKey(), this.#operations);
|
|
1454
1616
|
}
|
|
1455
1617
|
async enqueueCheckIn(request) {
|
|
1456
1618
|
return this.enqueue({ kind: "checkIn", request });
|
|
@@ -1461,7 +1623,7 @@ var OfflineQueue = class {
|
|
|
1461
1623
|
async clear() {
|
|
1462
1624
|
await this.initialize();
|
|
1463
1625
|
this.#operations = [];
|
|
1464
|
-
await this.#storage.save(this.#
|
|
1626
|
+
await this.#storage.save(this.#storageKey(), this.#operations);
|
|
1465
1627
|
}
|
|
1466
1628
|
async sync(sender = this.#sender) {
|
|
1467
1629
|
await this.initialize();
|
|
@@ -1483,16 +1645,30 @@ var OfflineQueue = class {
|
|
|
1483
1645
|
while (this.#operations.length > 0) {
|
|
1484
1646
|
const operation = this.#operations[0];
|
|
1485
1647
|
if (operation === void 0) break;
|
|
1486
|
-
let
|
|
1648
|
+
let rawResult;
|
|
1487
1649
|
try {
|
|
1488
|
-
|
|
1650
|
+
rawResult = await sender(operation);
|
|
1489
1651
|
} catch (cause) {
|
|
1490
|
-
throw
|
|
1652
|
+
throw new Error(errorValue(cause, "RETRYABLE_ERROR").message);
|
|
1491
1653
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1654
|
+
const response = this.#normalizeResponse(rawResult);
|
|
1655
|
+
if (response.status === "RETRYABLE_ERROR") {
|
|
1656
|
+
const error2 = errorValue(response.error ?? response.reason, "RETRYABLE_ERROR");
|
|
1657
|
+
await this.#syncResultListener?.({ operation, status: response.status, error: error2 });
|
|
1658
|
+
throw new Error(error2.message);
|
|
1659
|
+
}
|
|
1660
|
+
const result = response.result;
|
|
1661
|
+
const state = response.state ?? (result !== void 0 && "conflict" in result && result.conflict === true ? await this.resolveConflict(operation, result.localState, result.serverState) : result !== void 0 && "ok" in result && result.ok ? result.value.state : void 0);
|
|
1662
|
+
const error = response.status === "REJECTED_PERMANENT" ? errorValue(response.error ?? response.reason, "REJECTED_PERMANENT") : void 0;
|
|
1494
1663
|
this.#operations = this.#operations.slice(1);
|
|
1495
|
-
await this.#storage.save(this.#
|
|
1664
|
+
await this.#storage.save(this.#storageKey(), this.#operations);
|
|
1665
|
+
await this.#syncResultListener?.({
|
|
1666
|
+
operation,
|
|
1667
|
+
...result === void 0 ? {} : { result },
|
|
1668
|
+
status: response.status,
|
|
1669
|
+
...error === void 0 ? {} : { error },
|
|
1670
|
+
...state === void 0 ? {} : { state }
|
|
1671
|
+
});
|
|
1496
1672
|
}
|
|
1497
1673
|
this.#state = "idle";
|
|
1498
1674
|
} catch (cause) {
|
|
@@ -1501,12 +1677,29 @@ var OfflineQueue = class {
|
|
|
1501
1677
|
throw this.#error;
|
|
1502
1678
|
}
|
|
1503
1679
|
}
|
|
1680
|
+
#storageKey() {
|
|
1681
|
+
if (this.#configuredKey !== void 0) return this.#configuredKey;
|
|
1682
|
+
return `stamprally:queue:${this.#rallyId ?? "unscoped"}:${this.#userId ?? "anonymous"}`;
|
|
1683
|
+
}
|
|
1684
|
+
#normalizeResponse(value) {
|
|
1685
|
+
if ("ok" in value) {
|
|
1686
|
+
if (value.ok === false) {
|
|
1687
|
+
if ("status" in value && value.status === "RETRYABLE_ERROR")
|
|
1688
|
+
return { status: "RETRYABLE_ERROR", error: errorValue(value.error, "RETRYABLE_ERROR") };
|
|
1689
|
+
return { status: "REJECTED_PERMANENT", result: value };
|
|
1690
|
+
}
|
|
1691
|
+
return { status: "ACCEPTED", result: value };
|
|
1692
|
+
}
|
|
1693
|
+
if ("status" in value) {
|
|
1694
|
+
if (value.status === "ACCEPTED") return value;
|
|
1695
|
+
return value;
|
|
1696
|
+
}
|
|
1697
|
+
return { status: "ACCEPTED", result: value };
|
|
1698
|
+
}
|
|
1504
1699
|
async resolveConflict(operation, localState, serverState) {
|
|
1505
1700
|
const configured = this.#onSyncConflict;
|
|
1506
1701
|
const policy = (typeof configured === "function" ? await configured({ operation, localState, serverState }) : configured) ?? this.#conflictPolicy;
|
|
1507
|
-
|
|
1508
|
-
return;
|
|
1509
|
-
}
|
|
1702
|
+
return resolveRallyStateConflict(serverState, localState, { policy });
|
|
1510
1703
|
}
|
|
1511
1704
|
};
|
|
1512
1705
|
|
|
@@ -2395,6 +2588,6 @@ async function verifySnapshotToken(token, secretKey, now = Date.now()) {
|
|
|
2395
2588
|
}
|
|
2396
2589
|
}
|
|
2397
2590
|
|
|
2398
|
-
export { ConfigValidationError, DEFAULT_SHEET_THEME, MemoryQueueStorage as InMemoryOfflineQueueStorage, InMemoryStorage, IndexedDBAdapter, IndexedDBOfflineQueueStorage, LocalStorageAdapter, OfflineQueue, StampRallyClient, StorageAdapterError, THEME_PRESETS, assertPublicConfig, calculateDistanceMeters, calculateProgress, consumeReward, createClaimTicketNumber, createSecureToken, createSignedSnapshotToken, createUniqueClaimTicketNumber, evaluateCondition, evaluateConditionDetailed, exportProgressToken, getCurrentGeoContext, getOrderedSpots, importProgressToken, isGeolocationSupported, isNfcSupported, isPublicConfig, isQrSupported, isRewardState, isStampRallyState, issueClaimTicketNumber, normalizePasscode, parseAdminConfig, parsePublicConfig, processStamp, readNfcContext, readQrContext, reconcileRewardStates, resolveLocalizedText, safeParseAdminConfig, safeParsePublicConfig, sanitizeAdminConfig, storageKey, toLocalizedString, toPublicConfig, updateLocalizedField, validatePublicConfigSafety, verifyPasscode, verifySecureToken, verifySnapshotToken };
|
|
2591
|
+
export { ConfigValidationError, DEFAULT_SHEET_THEME, MemoryQueueStorage as InMemoryOfflineQueueStorage, InMemoryStorage, IndexedDBAdapter, IndexedDBOfflineQueueStorage, LocalStorageAdapter, OfflineQueue, StampRallyClient, StorageAdapterError, THEME_PRESETS, assertPublicConfig, calculateDistanceMeters, calculateProgress, consumeReward, createClaimTicketNumber, createSecureToken, createSignedSnapshotToken, createUniqueClaimTicketNumber, evaluateCondition, evaluateConditionDetailed, evaluateSpotStatus, exportProgressToken, getCurrentGeoContext, getOrderedSpots, getSpotStatus, importProgressToken, isGeolocationSupported, isNfcSupported, isPublicConfig, isQrSupported, isRewardState, isStampRallyState, issueClaimTicketNumber, normalizePasscode, parseAdminConfig, parsePublicConfig, processStamp, readNfcContext, readQrContext, reconcileRewardStates, resolveLocalizedText, resolveRallyStateConflict, safeParseAdminConfig, safeParsePublicConfig, sanitizeAdminConfig, storageKey, toLocalizedString, toPublicConfig, updateLocalizedField, validatePublicConfigSafety, verifyPasscode, verifySecureToken, verifySnapshotToken };
|
|
2399
2592
|
//# sourceMappingURL=index.js.map
|
|
2400
2593
|
//# sourceMappingURL=index.js.map
|