@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/README.md
CHANGED
|
@@ -23,6 +23,19 @@ The browser detectors `getCurrentGeoContext`, `readNfcContext`, and `readQrConte
|
|
|
23
23
|
|
|
24
24
|
`InMemoryStorage`, `LocalStorageAdapter`, and `IndexedDBAdapter` implement `StampStorage`. `updateLocalizedField` updates one locale without dropping existing translations.
|
|
25
25
|
|
|
26
|
+
## v0.13 offline synchronization
|
|
27
|
+
|
|
28
|
+
Configure `OfflineQueue` with `rallyId` and `userId` to persist pending work under
|
|
29
|
+
`stamprally:queue:<rallyId>:<userId-or-anonymous>`. `switchUser` loads the other
|
|
30
|
+
user's queue. Conflict policy `merge` unions stamp records, keeps the latest
|
|
31
|
+
timestamps, and gives `CONSUMED` reward states priority; `server_wins` uses the
|
|
32
|
+
server state unchanged. Sync adapters may return `ACCEPTED`, `REJECTED_PERMANENT`,
|
|
33
|
+
or `RETRYABLE_ERROR`; only the first two remove an operation.
|
|
34
|
+
|
|
35
|
+
`evaluateSpotStatus` derives `UNCLAIMED`, `CLAIMED`, `LOCKED`, or `VERIFYING`
|
|
36
|
+
without mutating state. A spot with incomplete `prerequisites` is `LOCKED` and
|
|
37
|
+
must not be verified by a client or viewer.
|
|
38
|
+
|
|
26
39
|
## License
|
|
27
40
|
|
|
28
41
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,14 @@ function evaluateConditionDetailed(condition2, context) {
|
|
|
41
41
|
function evaluateCondition(condition2, context) {
|
|
42
42
|
return evaluateConditionDetailed(condition2, context).ok;
|
|
43
43
|
}
|
|
44
|
+
function evaluateSpotStatus(spot2, state, options = {}) {
|
|
45
|
+
if (options.verifying === true) return "VERIFYING";
|
|
46
|
+
if (state.records.some((record) => record.stampId === spot2.id)) return "CLAIMED";
|
|
47
|
+
const acquired = new Set(state.records.map((record) => record.stampId));
|
|
48
|
+
if (spot2.prerequisites?.some((prerequisite) => !acquired.has(prerequisite))) return "LOCKED";
|
|
49
|
+
return "UNCLAIMED";
|
|
50
|
+
}
|
|
51
|
+
var getSpotStatus = evaluateSpotStatus;
|
|
44
52
|
|
|
45
53
|
// src/engine/order.ts
|
|
46
54
|
function getOrderedSpots(spots) {
|
|
@@ -99,6 +107,76 @@ function issueClaimTicketNumber(reward2, currentState, options = {}) {
|
|
|
99
107
|
return { ...currentState, claimTicketNumber };
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
// src/engine/sync.ts
|
|
111
|
+
function latestTimestamp(serverTimestamp, localTimestamp) {
|
|
112
|
+
const serverTime = Date.parse(serverTimestamp);
|
|
113
|
+
const localTime = Date.parse(localTimestamp);
|
|
114
|
+
if (!Number.isNaN(serverTime) && !Number.isNaN(localTime))
|
|
115
|
+
return serverTime >= localTime ? serverTimestamp : localTimestamp;
|
|
116
|
+
if (!Number.isNaN(serverTime)) return serverTimestamp;
|
|
117
|
+
if (!Number.isNaN(localTime)) return localTimestamp;
|
|
118
|
+
return serverTimestamp >= localTimestamp ? serverTimestamp : localTimestamp;
|
|
119
|
+
}
|
|
120
|
+
function mergeRewardStates(serverRewards, localRewards) {
|
|
121
|
+
const merged = new Map(serverRewards.map((reward2) => [reward2.rewardId, reward2]));
|
|
122
|
+
for (const localReward of localRewards) {
|
|
123
|
+
const serverReward = merged.get(localReward.rewardId);
|
|
124
|
+
if (serverReward === void 0) {
|
|
125
|
+
merged.set(localReward.rewardId, localReward);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const winner = localReward.status === "CONSUMED" && serverReward.status !== "CONSUMED" ? localReward : serverReward;
|
|
129
|
+
merged.set(localReward.rewardId, {
|
|
130
|
+
...winner,
|
|
131
|
+
...serverReward.unlockedAt === void 0 && localReward.unlockedAt === void 0 ? {} : {
|
|
132
|
+
unlockedAt: serverReward.unlockedAt === void 0 ? localReward.unlockedAt : localReward.unlockedAt === void 0 ? serverReward.unlockedAt : latestTimestamp(serverReward.unlockedAt, localReward.unlockedAt)
|
|
133
|
+
},
|
|
134
|
+
...serverReward.consumedAt === void 0 && localReward.consumedAt === void 0 ? {} : {
|
|
135
|
+
consumedAt: serverReward.consumedAt === void 0 ? localReward.consumedAt : localReward.consumedAt === void 0 ? serverReward.consumedAt : latestTimestamp(serverReward.consumedAt, localReward.consumedAt)
|
|
136
|
+
},
|
|
137
|
+
...serverReward.redeemedCount === void 0 && localReward.redeemedCount === void 0 ? {} : {
|
|
138
|
+
redeemedCount: Math.max(
|
|
139
|
+
serverReward.redeemedCount ?? 0,
|
|
140
|
+
localReward.redeemedCount ?? 0
|
|
141
|
+
)
|
|
142
|
+
},
|
|
143
|
+
...serverReward.userRedemptionCount === void 0 && localReward.userRedemptionCount === void 0 ? {} : {
|
|
144
|
+
userRedemptionCount: Math.max(
|
|
145
|
+
serverReward.userRedemptionCount ?? 0,
|
|
146
|
+
localReward.userRedemptionCount ?? 0
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return [...merged.values()];
|
|
152
|
+
}
|
|
153
|
+
function mergeStampRecords(serverRecords, localRecords) {
|
|
154
|
+
const merged = /* @__PURE__ */ new Map();
|
|
155
|
+
for (const record of [...serverRecords, ...localRecords]) {
|
|
156
|
+
const current = merged.get(record.stampId);
|
|
157
|
+
if (current === void 0) {
|
|
158
|
+
merged.set(record.stampId, record);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const acquiredAt = latestTimestamp(current.acquiredAt, record.acquiredAt);
|
|
162
|
+
merged.set(record.stampId, {
|
|
163
|
+
...current,
|
|
164
|
+
...acquiredAt === current.acquiredAt ? {} : { acquiredAt },
|
|
165
|
+
...current.metadata === void 0 && record.metadata !== void 0 ? { metadata: record.metadata } : {}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return [...merged.values()];
|
|
169
|
+
}
|
|
170
|
+
function resolveRallyStateConflict(serverState, localState, options = { policy: "merge" }) {
|
|
171
|
+
if (options.policy === "server_wins") return serverState;
|
|
172
|
+
return {
|
|
173
|
+
...serverState,
|
|
174
|
+
records: mergeStampRecords(serverState.records, localState.records),
|
|
175
|
+
rewards: mergeRewardStates(serverState.rewards, localState.rewards),
|
|
176
|
+
updatedAt: latestTimestamp(serverState.updatedAt, localState.updatedAt)
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
102
180
|
// src/detectors/types.ts
|
|
103
181
|
function createDetectorError(detector, code, message, cause) {
|
|
104
182
|
return cause === void 0 ? { detector, code, message } : { detector, code, message, cause };
|
|
@@ -1000,6 +1078,7 @@ var StampRallyClient = class {
|
|
|
1000
1078
|
this.#storage = this.#options.storage ?? new InMemoryStorage();
|
|
1001
1079
|
this.#userId = this.#options.userId ?? null;
|
|
1002
1080
|
this.#offlineQueue = this.#options.offlineQueue;
|
|
1081
|
+
this.#offlineQueue?.setSyncResultListener((event) => this.#handleOfflineSyncResult(event));
|
|
1003
1082
|
}
|
|
1004
1083
|
getConfig() {
|
|
1005
1084
|
return this.#config;
|
|
@@ -1030,7 +1109,10 @@ var StampRallyClient = class {
|
|
|
1030
1109
|
initialize() {
|
|
1031
1110
|
if (this.#state !== null) return Promise.resolve(this.#state);
|
|
1032
1111
|
if (this.#initialization === null) {
|
|
1033
|
-
this.#initialization =
|
|
1112
|
+
this.#initialization = (async () => {
|
|
1113
|
+
await this.#offlineQueue?.setScope(this.#config.id, this.#userId);
|
|
1114
|
+
return this.#storage.load(this.#config.id, this.#userId);
|
|
1115
|
+
})().then((state) => {
|
|
1034
1116
|
const next = state === null ? emptyState(this.#config, this.#userId, this.#now()) : this.#reconcile(state);
|
|
1035
1117
|
this.#state = next;
|
|
1036
1118
|
this.#emit(next);
|
|
@@ -1048,6 +1130,7 @@ var StampRallyClient = class {
|
|
|
1048
1130
|
this.#userId = newUserId;
|
|
1049
1131
|
this.#state = null;
|
|
1050
1132
|
this.#initialization = null;
|
|
1133
|
+
await this.#offlineQueue?.switchUser(newUserId);
|
|
1051
1134
|
return this.initialize();
|
|
1052
1135
|
});
|
|
1053
1136
|
}
|
|
@@ -1217,12 +1300,19 @@ var StampRallyClient = class {
|
|
|
1217
1300
|
});
|
|
1218
1301
|
}
|
|
1219
1302
|
if (adapter?.sync === void 0) {
|
|
1220
|
-
this.#emitEvent({ type: "sync", state: current });
|
|
1303
|
+
this.#emitEvent({ type: "sync", state: this.#state ?? current });
|
|
1221
1304
|
return;
|
|
1222
1305
|
}
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1306
|
+
const serverState = await adapter.sync({
|
|
1307
|
+
rallyId: this.#config.id,
|
|
1308
|
+
userId: this.#userId,
|
|
1309
|
+
state: this.#state ?? current
|
|
1310
|
+
});
|
|
1311
|
+
const localState = this.#state ?? current;
|
|
1312
|
+
const merged = this.#offlineQueue === void 0 ? serverState : resolveRallyStateConflict(serverState, localState, {
|
|
1313
|
+
policy: this.#offlineQueue.conflictPolicy
|
|
1314
|
+
});
|
|
1315
|
+
const next = this.#reconcile(merged);
|
|
1226
1316
|
await this.#storage.save(next);
|
|
1227
1317
|
this.#state = next;
|
|
1228
1318
|
this.#emit(next);
|
|
@@ -1282,6 +1372,17 @@ var StampRallyClient = class {
|
|
|
1282
1372
|
)
|
|
1283
1373
|
};
|
|
1284
1374
|
}
|
|
1375
|
+
async #handleOfflineSyncResult(event) {
|
|
1376
|
+
if (event.state !== void 0) {
|
|
1377
|
+
const next = this.#reconcile(event.state);
|
|
1378
|
+
await this.#storage.save(next);
|
|
1379
|
+
this.#state = next;
|
|
1380
|
+
this.#emit(next);
|
|
1381
|
+
}
|
|
1382
|
+
if (event.error !== void 0) this.#emitEvent({ type: "error", error: event.error });
|
|
1383
|
+
else if (event.result !== void 0 && "ok" in event.result && !event.result.ok)
|
|
1384
|
+
this.#emitEvent({ type: "error", error: event.result.error });
|
|
1385
|
+
}
|
|
1285
1386
|
#now() {
|
|
1286
1387
|
return this.#options.clock?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1287
1388
|
}
|
|
@@ -1407,9 +1508,27 @@ function defaultStorage(databaseName) {
|
|
|
1407
1508
|
function operationId(operation) {
|
|
1408
1509
|
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}`;
|
|
1409
1510
|
}
|
|
1511
|
+
function requestScope(operation) {
|
|
1512
|
+
return {
|
|
1513
|
+
rallyId: operation.request.rallyId,
|
|
1514
|
+
userId: operation.request.userId
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
function errorValue(value, fallbackCode) {
|
|
1518
|
+
if (typeof value === "object" && value !== null) {
|
|
1519
|
+
const candidate = value;
|
|
1520
|
+
if (typeof candidate.code === "string" && typeof candidate.message === "string")
|
|
1521
|
+
return { ...candidate, code: candidate.code, message: candidate.message };
|
|
1522
|
+
}
|
|
1523
|
+
if (value instanceof Error) return { code: fallbackCode, message: value.message };
|
|
1524
|
+
if (typeof value === "string") return { code: fallbackCode, message: value };
|
|
1525
|
+
return { code: fallbackCode, message: "Offline operation was rejected." };
|
|
1526
|
+
}
|
|
1410
1527
|
var OfflineQueue = class {
|
|
1411
1528
|
#storage;
|
|
1412
|
-
#
|
|
1529
|
+
#configuredKey;
|
|
1530
|
+
#rallyId;
|
|
1531
|
+
#userId;
|
|
1413
1532
|
#conflictPolicy;
|
|
1414
1533
|
#onSyncConflict;
|
|
1415
1534
|
#operations = [];
|
|
@@ -1418,12 +1537,15 @@ var OfflineQueue = class {
|
|
|
1418
1537
|
#error = null;
|
|
1419
1538
|
#sender;
|
|
1420
1539
|
#syncPromise = null;
|
|
1540
|
+
#syncResultListener;
|
|
1421
1541
|
constructor(options = {}) {
|
|
1422
1542
|
if (options.storage !== void 0) this.#storage = options.storage;
|
|
1423
1543
|
else if (options.storageLike !== void 0 && options.storageLike !== null)
|
|
1424
1544
|
this.#storage = new LocalStorageQueueStorage(options.storageLike);
|
|
1425
1545
|
else this.#storage = defaultStorage(options.databaseName);
|
|
1426
|
-
this.#
|
|
1546
|
+
this.#configuredKey = options.key;
|
|
1547
|
+
this.#rallyId = options.rallyId;
|
|
1548
|
+
this.#userId = options.userId ?? null;
|
|
1427
1549
|
this.#conflictPolicy = options.conflictPolicy ?? "server_wins";
|
|
1428
1550
|
this.#onSyncConflict = options.onSyncConflict;
|
|
1429
1551
|
}
|
|
@@ -1439,20 +1561,60 @@ var OfflineQueue = class {
|
|
|
1439
1561
|
get operations() {
|
|
1440
1562
|
return this.#operations;
|
|
1441
1563
|
}
|
|
1564
|
+
get conflictPolicy() {
|
|
1565
|
+
return this.#conflictPolicy;
|
|
1566
|
+
}
|
|
1567
|
+
get storageKey() {
|
|
1568
|
+
return this.#storageKey();
|
|
1569
|
+
}
|
|
1570
|
+
get rallyId() {
|
|
1571
|
+
return this.#rallyId;
|
|
1572
|
+
}
|
|
1573
|
+
get userId() {
|
|
1574
|
+
return this.#userId;
|
|
1575
|
+
}
|
|
1576
|
+
setSyncResultListener(listener) {
|
|
1577
|
+
this.#syncResultListener = listener;
|
|
1578
|
+
}
|
|
1442
1579
|
async initialize() {
|
|
1443
1580
|
if (this.#loaded) return;
|
|
1444
|
-
this.#operations = [...await this.#storage.load(this.#
|
|
1581
|
+
this.#operations = [...await this.#storage.load(this.#storageKey())];
|
|
1445
1582
|
this.#loaded = true;
|
|
1446
1583
|
}
|
|
1584
|
+
/** Selects a rally/user queue scope and loads its pending operations. */
|
|
1585
|
+
async setScope(rallyId, userId) {
|
|
1586
|
+
if (this.#configuredKey !== void 0) {
|
|
1587
|
+
this.#rallyId = rallyId;
|
|
1588
|
+
this.#userId = userId;
|
|
1589
|
+
return this.initialize();
|
|
1590
|
+
}
|
|
1591
|
+
if (this.#rallyId === rallyId && this.#userId === userId && this.#loaded) return;
|
|
1592
|
+
this.#rallyId = rallyId;
|
|
1593
|
+
this.#userId = userId;
|
|
1594
|
+
this.#operations = [];
|
|
1595
|
+
this.#loaded = false;
|
|
1596
|
+
await this.initialize();
|
|
1597
|
+
}
|
|
1598
|
+
async switchUser(newUserId) {
|
|
1599
|
+
if (this.#rallyId === void 0)
|
|
1600
|
+
throw new Error("OfflineQueue.switchUser requires a rally scope.");
|
|
1601
|
+
await this.setScope(this.#rallyId, newUserId);
|
|
1602
|
+
}
|
|
1447
1603
|
setSender(sender) {
|
|
1448
1604
|
this.#sender = sender;
|
|
1449
1605
|
}
|
|
1450
1606
|
async enqueue(operation) {
|
|
1607
|
+
if (this.#configuredKey === void 0) {
|
|
1608
|
+
const scope = requestScope(operation);
|
|
1609
|
+
if (this.#rallyId === void 0) await this.setScope(scope.rallyId, scope.userId);
|
|
1610
|
+
if (this.#rallyId !== scope.rallyId || this.#userId !== scope.userId)
|
|
1611
|
+
throw new Error("Offline operation belongs to another rally or user queue.");
|
|
1612
|
+
}
|
|
1451
1613
|
await this.initialize();
|
|
1452
1614
|
const id2 = operationId(operation);
|
|
1453
1615
|
if (this.#operations.some((item) => operationId(item) === id2)) return;
|
|
1454
1616
|
this.#operations = [...this.#operations, operation];
|
|
1455
|
-
await this.#storage.save(this.#
|
|
1617
|
+
await this.#storage.save(this.#storageKey(), this.#operations);
|
|
1456
1618
|
}
|
|
1457
1619
|
async enqueueCheckIn(request) {
|
|
1458
1620
|
return this.enqueue({ kind: "checkIn", request });
|
|
@@ -1463,7 +1625,7 @@ var OfflineQueue = class {
|
|
|
1463
1625
|
async clear() {
|
|
1464
1626
|
await this.initialize();
|
|
1465
1627
|
this.#operations = [];
|
|
1466
|
-
await this.#storage.save(this.#
|
|
1628
|
+
await this.#storage.save(this.#storageKey(), this.#operations);
|
|
1467
1629
|
}
|
|
1468
1630
|
async sync(sender = this.#sender) {
|
|
1469
1631
|
await this.initialize();
|
|
@@ -1485,16 +1647,30 @@ var OfflineQueue = class {
|
|
|
1485
1647
|
while (this.#operations.length > 0) {
|
|
1486
1648
|
const operation = this.#operations[0];
|
|
1487
1649
|
if (operation === void 0) break;
|
|
1488
|
-
let
|
|
1650
|
+
let rawResult;
|
|
1489
1651
|
try {
|
|
1490
|
-
|
|
1652
|
+
rawResult = await sender(operation);
|
|
1491
1653
|
} catch (cause) {
|
|
1492
|
-
throw
|
|
1654
|
+
throw new Error(errorValue(cause, "RETRYABLE_ERROR").message);
|
|
1493
1655
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1656
|
+
const response = this.#normalizeResponse(rawResult);
|
|
1657
|
+
if (response.status === "RETRYABLE_ERROR") {
|
|
1658
|
+
const error2 = errorValue(response.error ?? response.reason, "RETRYABLE_ERROR");
|
|
1659
|
+
await this.#syncResultListener?.({ operation, status: response.status, error: error2 });
|
|
1660
|
+
throw new Error(error2.message);
|
|
1661
|
+
}
|
|
1662
|
+
const result = response.result;
|
|
1663
|
+
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);
|
|
1664
|
+
const error = response.status === "REJECTED_PERMANENT" ? errorValue(response.error ?? response.reason, "REJECTED_PERMANENT") : void 0;
|
|
1496
1665
|
this.#operations = this.#operations.slice(1);
|
|
1497
|
-
await this.#storage.save(this.#
|
|
1666
|
+
await this.#storage.save(this.#storageKey(), this.#operations);
|
|
1667
|
+
await this.#syncResultListener?.({
|
|
1668
|
+
operation,
|
|
1669
|
+
...result === void 0 ? {} : { result },
|
|
1670
|
+
status: response.status,
|
|
1671
|
+
...error === void 0 ? {} : { error },
|
|
1672
|
+
...state === void 0 ? {} : { state }
|
|
1673
|
+
});
|
|
1498
1674
|
}
|
|
1499
1675
|
this.#state = "idle";
|
|
1500
1676
|
} catch (cause) {
|
|
@@ -1503,12 +1679,29 @@ var OfflineQueue = class {
|
|
|
1503
1679
|
throw this.#error;
|
|
1504
1680
|
}
|
|
1505
1681
|
}
|
|
1682
|
+
#storageKey() {
|
|
1683
|
+
if (this.#configuredKey !== void 0) return this.#configuredKey;
|
|
1684
|
+
return `stamprally:queue:${this.#rallyId ?? "unscoped"}:${this.#userId ?? "anonymous"}`;
|
|
1685
|
+
}
|
|
1686
|
+
#normalizeResponse(value) {
|
|
1687
|
+
if ("ok" in value) {
|
|
1688
|
+
if (value.ok === false) {
|
|
1689
|
+
if ("status" in value && value.status === "RETRYABLE_ERROR")
|
|
1690
|
+
return { status: "RETRYABLE_ERROR", error: errorValue(value.error, "RETRYABLE_ERROR") };
|
|
1691
|
+
return { status: "REJECTED_PERMANENT", result: value };
|
|
1692
|
+
}
|
|
1693
|
+
return { status: "ACCEPTED", result: value };
|
|
1694
|
+
}
|
|
1695
|
+
if ("status" in value) {
|
|
1696
|
+
if (value.status === "ACCEPTED") return value;
|
|
1697
|
+
return value;
|
|
1698
|
+
}
|
|
1699
|
+
return { status: "ACCEPTED", result: value };
|
|
1700
|
+
}
|
|
1506
1701
|
async resolveConflict(operation, localState, serverState) {
|
|
1507
1702
|
const configured = this.#onSyncConflict;
|
|
1508
1703
|
const policy = (typeof configured === "function" ? await configured({ operation, localState, serverState }) : configured) ?? this.#conflictPolicy;
|
|
1509
|
-
|
|
1510
|
-
return;
|
|
1511
|
-
}
|
|
1704
|
+
return resolveRallyStateConflict(serverState, localState, { policy });
|
|
1512
1705
|
}
|
|
1513
1706
|
};
|
|
1514
1707
|
|
|
@@ -2418,9 +2611,11 @@ exports.createSignedSnapshotToken = createSignedSnapshotToken;
|
|
|
2418
2611
|
exports.createUniqueClaimTicketNumber = createUniqueClaimTicketNumber;
|
|
2419
2612
|
exports.evaluateCondition = evaluateCondition;
|
|
2420
2613
|
exports.evaluateConditionDetailed = evaluateConditionDetailed;
|
|
2614
|
+
exports.evaluateSpotStatus = evaluateSpotStatus;
|
|
2421
2615
|
exports.exportProgressToken = exportProgressToken;
|
|
2422
2616
|
exports.getCurrentGeoContext = getCurrentGeoContext;
|
|
2423
2617
|
exports.getOrderedSpots = getOrderedSpots;
|
|
2618
|
+
exports.getSpotStatus = getSpotStatus;
|
|
2424
2619
|
exports.importProgressToken = importProgressToken;
|
|
2425
2620
|
exports.isGeolocationSupported = isGeolocationSupported;
|
|
2426
2621
|
exports.isNfcSupported = isNfcSupported;
|
|
@@ -2437,6 +2632,7 @@ exports.readNfcContext = readNfcContext;
|
|
|
2437
2632
|
exports.readQrContext = readQrContext;
|
|
2438
2633
|
exports.reconcileRewardStates = reconcileRewardStates;
|
|
2439
2634
|
exports.resolveLocalizedText = resolveLocalizedText;
|
|
2635
|
+
exports.resolveRallyStateConflict = resolveRallyStateConflict;
|
|
2440
2636
|
exports.safeParseAdminConfig = safeParseAdminConfig;
|
|
2441
2637
|
exports.safeParsePublicConfig = safeParsePublicConfig;
|
|
2442
2638
|
exports.sanitizeAdminConfig = sanitizeAdminConfig;
|