@stamprally/core 0.4.0 → 0.5.1

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
@@ -223,7 +223,7 @@ function evaluateCheckIn(spot, input) {
223
223
  // src/engine/order.ts
224
224
  function getOrderedStamps(config) {
225
225
  return config.stamps.map((stamp, index) => ({ stamp, index })).sort((left, right) => {
226
- const orderDifference = (left.stamp.order ?? Number.POSITIVE_INFINITY) - (right.stamp.order ?? Number.POSITIVE_INFINITY);
226
+ const orderDifference = (left.stamp.orderIndex ?? left.stamp.order ?? Number.POSITIVE_INFINITY) - (right.stamp.orderIndex ?? right.stamp.order ?? Number.POSITIVE_INFINITY);
227
227
  return orderDifference === 0 ? left.index - right.index : orderDifference;
228
228
  }).map(({ stamp }) => stamp);
229
229
  }
@@ -259,10 +259,10 @@ function hash(value) {
259
259
  return result.toString(36).toUpperCase().padStart(7, "0");
260
260
  }
261
261
  function createRandomHash() {
262
- const cryptoApi2 = globalThis.crypto;
263
- if (cryptoApi2 !== void 0 && typeof cryptoApi2.getRandomValues === "function") {
262
+ const cryptoApi3 = globalThis.crypto;
263
+ if (cryptoApi3 !== void 0 && typeof cryptoApi3.getRandomValues === "function") {
264
264
  const values = new Uint32Array(2);
265
- cryptoApi2.getRandomValues(values);
265
+ cryptoApi3.getRandomValues(values);
266
266
  return Array.from(values, (value) => value.toString(36).toUpperCase().padStart(7, "0")).join(
267
267
  ""
268
268
  );
@@ -631,7 +631,8 @@ function reconcileRewardStates(rewards, currentStates, acquiredStampCount, now)
631
631
  if (reward.validUntil !== void 0 && Date.parse(reward.validUntil) <= Date.parse(now)) {
632
632
  return { rewardId: reward.id, status: "EXPIRED" };
633
633
  }
634
- if (reward.maxStock !== void 0 && (current?.redeemedCount ?? 0) >= reward.maxStock) {
634
+ const stockLimit = reward.stockLimit ?? reward.maxStock;
635
+ if (stockLimit !== void 0 && (current?.redeemedCount ?? 0) >= stockLimit) {
635
636
  return {
636
637
  rewardId: reward.id,
637
638
  status: "EXPIRED",
@@ -668,10 +669,12 @@ function consumeReward(params) {
668
669
  if (reward.validUntil !== void 0 && Date.parse(reward.validUntil) <= Date.parse(params.now)) {
669
670
  return { ok: false, error: { code: "EXPIRED", reason: "EXPIRED", rewardId: reward.id } };
670
671
  }
671
- if (reward.maxStock !== void 0 && (currentState.redeemedCount ?? 0) >= reward.maxStock) {
672
+ const stockLimit = reward.stockLimit ?? reward.maxStock;
673
+ const userClaimLimit = reward.userClaimLimit ?? reward.limitPerUser;
674
+ if (stockLimit !== void 0 && (currentState.redeemedCount ?? 0) >= stockLimit) {
672
675
  return { ok: false, error: { code: "OUT_OF_STOCK", rewardId: reward.id } };
673
676
  }
674
- if (reward.limitPerUser !== void 0 && (params.userRedemptionCount ?? currentState.userRedemptionCount ?? 0) >= reward.limitPerUser) {
677
+ if (userClaimLimit !== void 0 && (params.userRedemptionCount ?? currentState.userRedemptionCount ?? 0) >= userClaimLimit) {
675
678
  return {
676
679
  ok: false,
677
680
  error: { code: "USER_LIMIT_REACHED", reason: "LIMIT_EXCEEDED", rewardId: reward.id }
@@ -700,7 +703,7 @@ function consumeReward(params) {
700
703
  status: "CONSUMED",
701
704
  consumedAt: params.now,
702
705
  claimTicketNumber: createUniqueClaimTicketNumber(reward.id, params.now),
703
- ...reward.maxStock !== void 0 || params.userId !== void 0 || params.userRedemptionCount !== void 0 ? { redeemedCount: (currentState.redeemedCount ?? 0) + 1 } : {},
706
+ ...stockLimit !== void 0 || params.userId !== void 0 || params.userRedemptionCount !== void 0 ? { redeemedCount: (currentState.redeemedCount ?? 0) + 1 } : {},
704
707
  ...params.userId === void 0 ? {} : { userRedemptionCount: (currentState.userRedemptionCount ?? 0) + 1 },
705
708
  ...params.staffId === void 0 ? {} : { consumedByStaffId: params.staffId }
706
709
  }
@@ -1197,6 +1200,7 @@ var IndexedDBAdapter = class {
1197
1200
  var systemClock = () => (/* @__PURE__ */ new Date()).toISOString();
1198
1201
  var StampRallyClient = class {
1199
1202
  #listeners = /* @__PURE__ */ new Set();
1203
+ #eventListeners = /* @__PURE__ */ new Set();
1200
1204
  #config;
1201
1205
  #storage;
1202
1206
  #clock;
@@ -1214,12 +1218,38 @@ var StampRallyClient = class {
1214
1218
  getConfig() {
1215
1219
  return this.#config;
1216
1220
  }
1217
- subscribe(listener) {
1221
+ subscribe(listener, options = {}) {
1222
+ if (options.events === true) {
1223
+ this.#eventListeners.add(listener);
1224
+ return () => this.#eventListeners.delete(listener);
1225
+ }
1218
1226
  this.#listeners.add(listener);
1219
1227
  return () => {
1220
1228
  this.#listeners.delete(listener);
1221
1229
  };
1222
1230
  }
1231
+ subscribeEvents(listener) {
1232
+ this.#eventListeners.add(listener);
1233
+ return () => this.#eventListeners.delete(listener);
1234
+ }
1235
+ async updateConfig(newConfig) {
1236
+ return this.#enqueue(async () => {
1237
+ const current = await this.initialize();
1238
+ this.#config = newConfig;
1239
+ const next = this.#reconcileState(cloneState(current), this.#clock());
1240
+ await this.#storage.save(next);
1241
+ this.#currentState = next;
1242
+ this.#initialization = Promise.resolve(next);
1243
+ this.#emit(next);
1244
+ return next;
1245
+ });
1246
+ }
1247
+ notifyRewardClaimed(rewardId, state = this.#currentState) {
1248
+ if (state !== null) this.#emitEvent({ type: "rewardClaimed", rewardId, state });
1249
+ }
1250
+ notifySyncCompleted(state = this.#currentState) {
1251
+ if (state !== null) this.#emitEvent({ type: "syncCompleted", state });
1252
+ }
1223
1253
  init() {
1224
1254
  return this.initialize();
1225
1255
  }
@@ -1245,11 +1275,13 @@ var StampRallyClient = class {
1245
1275
  const currentState = await this.initialize();
1246
1276
  const result = processStamp(currentState, this.#config, stampId, context, now);
1247
1277
  if (!result.ok) {
1278
+ this.#emitEvent({ type: "error", error: result.error });
1248
1279
  return result;
1249
1280
  }
1250
1281
  await this.#storage.save(result.value.nextState);
1251
1282
  this.#currentState = result.value.nextState;
1252
1283
  this.#emit(result.value.nextState);
1284
+ this.#emitEvent({ type: "checkIn", stampId, state: result.value.nextState });
1253
1285
  return result;
1254
1286
  });
1255
1287
  }
@@ -1299,6 +1331,9 @@ var StampRallyClient = class {
1299
1331
  listener(state);
1300
1332
  }
1301
1333
  }
1334
+ #emitEvent(event) {
1335
+ for (const listener of this.#eventListeners) listener(event);
1336
+ }
1302
1337
  #createEmptyState(now) {
1303
1338
  const state = {
1304
1339
  rallyId: this.#config.id,
@@ -1334,12 +1369,144 @@ var StampRallyClient = class {
1334
1369
  }
1335
1370
  };
1336
1371
 
1372
+ // src/crypto/token.ts
1373
+ var encoder = new TextEncoder();
1374
+ var decoder = new TextDecoder();
1375
+ function cryptoApi() {
1376
+ if (globalThis.crypto?.subtle === void 0) {
1377
+ throw new Error("Web Crypto API is unavailable in this environment.");
1378
+ }
1379
+ return globalThis.crypto;
1380
+ }
1381
+ function bytes(value) {
1382
+ return typeof value === "string" ? encoder.encode(value) : new Uint8Array(value);
1383
+ }
1384
+ function source(value) {
1385
+ return value.buffer;
1386
+ }
1387
+ function encode(value) {
1388
+ let binary = "";
1389
+ for (const byte of value) binary += String.fromCharCode(byte);
1390
+ return globalThis.btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
1391
+ }
1392
+ function decode(value) {
1393
+ const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
1394
+ const padded = `${base64}${"=".repeat((4 - base64.length % 4) % 4)}`;
1395
+ return Uint8Array.from(globalThis.atob(padded), (character) => character.charCodeAt(0));
1396
+ }
1397
+ async function hmacKey(secret) {
1398
+ return cryptoApi().subtle.importKey(
1399
+ "raw",
1400
+ source(secret),
1401
+ { name: "HMAC", hash: "SHA-256" },
1402
+ false,
1403
+ ["sign", "verify"]
1404
+ );
1405
+ }
1406
+ async function aesKey(secret) {
1407
+ const digest = await cryptoApi().subtle.digest("SHA-256", source(secret));
1408
+ return cryptoApi().subtle.importKey("raw", digest, "AES-GCM", false, ["encrypt", "decrypt"]);
1409
+ }
1410
+ async function createSecureToken(payload, secretKey, options = {}) {
1411
+ const api = cryptoApi();
1412
+ const expiresInSeconds = options.expiresInSeconds;
1413
+ const effectivePayload = expiresInSeconds === void 0 || payload.exp !== void 0 ? payload : { ...payload, exp: Math.floor(Date.now() / 1e3) + expiresInSeconds };
1414
+ const secret = bytes(secretKey);
1415
+ const plaintext = encoder.encode(JSON.stringify(effectivePayload));
1416
+ const actualBody = options.encrypt === true ? await encryptPayload(plaintext, secret, api) : encode(plaintext);
1417
+ const signature = new Uint8Array(
1418
+ await api.subtle.sign("HMAC", await hmacKey(secret), encoder.encode(actualBody))
1419
+ );
1420
+ return `sr3.${options.encrypt === true ? "e" : "p"}.${actualBody}.${encode(signature)}`;
1421
+ }
1422
+ async function encryptPayload(plaintext, secret, api) {
1423
+ const iv = api.getRandomValues(new Uint8Array(12));
1424
+ const encrypted = new Uint8Array(
1425
+ await api.subtle.encrypt(
1426
+ { name: "AES-GCM", iv: source(iv) },
1427
+ await aesKey(secret),
1428
+ source(plaintext)
1429
+ )
1430
+ );
1431
+ return encode(new Uint8Array([...iv, ...encrypted]));
1432
+ }
1433
+ async function verifySecureToken(token, secretKey, now = Date.now()) {
1434
+ try {
1435
+ const parts = token.split(".");
1436
+ if (parts.length !== 4 || parts[0] !== "sr3" || parts[1] !== "e" && parts[1] !== "p") {
1437
+ return {
1438
+ ok: false,
1439
+ valid: false,
1440
+ error: { code: "MALFORMED", message: "Secure token is malformed." }
1441
+ };
1442
+ }
1443
+ const mode = parts[1];
1444
+ const body = parts[2];
1445
+ const encodedSignature = parts[3];
1446
+ if (body === void 0 || encodedSignature === void 0 || mode === void 0) {
1447
+ return {
1448
+ ok: false,
1449
+ valid: false,
1450
+ error: { code: "MALFORMED", message: "Secure token is malformed." }
1451
+ };
1452
+ }
1453
+ const secret = bytes(secretKey);
1454
+ const valid = await cryptoApi().subtle.verify(
1455
+ "HMAC",
1456
+ await hmacKey(secret),
1457
+ source(decode(encodedSignature)),
1458
+ source(encoder.encode(body))
1459
+ );
1460
+ if (!valid)
1461
+ return {
1462
+ ok: false,
1463
+ valid: false,
1464
+ error: { code: "INVALID_SIGNATURE", message: "Secure token signature is invalid." }
1465
+ };
1466
+ const plaintext = mode === "e" ? await decryptPayload(body, secret) : decode(body);
1467
+ const parsed = JSON.parse(decoder.decode(plaintext));
1468
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1469
+ return {
1470
+ ok: false,
1471
+ valid: false,
1472
+ error: { code: "INVALID_PAYLOAD", message: "Secure token payload is invalid." }
1473
+ };
1474
+ }
1475
+ const payload = parsed;
1476
+ if (typeof payload.exp === "number" && now >= payload.exp * 1e3) {
1477
+ return {
1478
+ ok: false,
1479
+ valid: false,
1480
+ error: { code: "EXPIRED", message: "Secure token has expired." }
1481
+ };
1482
+ }
1483
+ return { ok: true, valid: true, payload };
1484
+ } catch {
1485
+ return {
1486
+ ok: false,
1487
+ valid: false,
1488
+ error: { code: "DECRYPTION_FAILED", message: "Secure token could not be verified." }
1489
+ };
1490
+ }
1491
+ }
1492
+ async function decryptPayload(body, secret) {
1493
+ const encrypted = decode(body);
1494
+ if (encrypted.length <= 12) throw new Error("Invalid encrypted payload.");
1495
+ return new Uint8Array(
1496
+ await cryptoApi().subtle.decrypt(
1497
+ { name: "AES-GCM", iv: source(encrypted.slice(0, 12)) },
1498
+ await aesKey(secret),
1499
+ source(encrypted.slice(12))
1500
+ )
1501
+ );
1502
+ }
1503
+
1337
1504
  // src/domain/i18n.ts
1338
1505
  function resolveLocalizedText(text2, locale, fallbackLocale) {
1339
1506
  if (text2 === void 0 || text2 === "") return "";
1340
1507
  if (typeof text2 === "string") return text2;
1341
- const fallback = fallbackLocale ?? "ja";
1342
- return text2[locale] || text2[fallback] || "";
1508
+ const fallback = fallbackLocale === void 0 ? Object.values(text2).find((value) => typeof value === "string") : text2[fallbackLocale];
1509
+ return text2[locale] || fallback || "";
1343
1510
  }
1344
1511
  function toLocalizedString(text2) {
1345
1512
  if (text2 === void 0) return { ja: "", en: "" };
@@ -1441,6 +1608,11 @@ function validateSpot(value, index, errors) {
1441
1608
  add(errors, `${path}.${field}`, "INVALID_TYPE", `${field} must be an array of IDs.`);
1442
1609
  }
1443
1610
  }
1611
+ for (const field of ["order", "orderIndex"]) {
1612
+ if (value[field] !== void 0 && (typeof value[field] !== "number" || !Number.isFinite(value[field]))) {
1613
+ add(errors, `${path}.${field}`, "INVALID_TYPE", `${field} must be a finite number.`);
1614
+ }
1615
+ }
1444
1616
  }
1445
1617
  function validateReward(value, index, stampCount, errors) {
1446
1618
  const path = `rewards[${index}]`;
@@ -1469,7 +1641,7 @@ function validateReward(value, index, stampCount, errors) {
1469
1641
  if (value.validUntil !== void 0 && (typeof value.validUntil !== "string" || Number.isNaN(Date.parse(value.validUntil)))) {
1470
1642
  add(errors, `${path}.validUntil`, "INVALID_DATE", "Reward expiry must be a valid ISO date.");
1471
1643
  }
1472
- for (const field of ["maxStock", "limitPerUser"]) {
1644
+ for (const field of ["maxStock", "limitPerUser", "stockLimit", "userClaimLimit"]) {
1473
1645
  const count = value[field];
1474
1646
  if (count !== void 0 && (typeof count !== "number" || !Number.isInteger(count) || count <= 0)) {
1475
1647
  add(errors, `${path}.${field}`, "INVALID_REWARD", `${field} must be a positive integer.`);
@@ -1629,66 +1801,68 @@ function condition(value) {
1629
1801
  }
1630
1802
  }
1631
1803
  function migrateSpot(value, index) {
1632
- const source = isObject2(value) ? value : {};
1633
- const id = typeof source.id === "string" && source.id.trim() !== "" ? source.id.trim() : `spot-${index + 1}`;
1634
- const description = text(source.description);
1635
- const hint = text(source.hint);
1804
+ const source2 = isObject2(value) ? value : {};
1805
+ const id = typeof source2.id === "string" && source2.id.trim() !== "" ? source2.id.trim() : `spot-${index + 1}`;
1806
+ const description = text(source2.description);
1807
+ const hint = text(source2.hint);
1636
1808
  return {
1637
1809
  id,
1638
- name: text(source.name) ?? `Spot ${index + 1}`,
1810
+ name: text(source2.name) ?? `Spot ${index + 1}`,
1639
1811
  ...description === void 0 ? {} : { description },
1640
1812
  ...hint === void 0 ? {} : { hint },
1641
1813
  condition: condition(
1642
- source.condition ?? (typeof source.token === "string" ? { type: "token", token: source.token } : void 0)
1814
+ source2.condition ?? (typeof source2.token === "string" ? { type: "token", token: source2.token } : void 0)
1643
1815
  ),
1644
- ...typeof source.order === "number" ? { order: source.order } : {},
1645
- ...typeof source.deckId === "string" ? { deckId: source.deckId } : {},
1646
- ...typeof source.groupId === "string" ? { groupId: source.groupId } : {},
1647
- ...typeof source.guideId === "string" ? { guideId: source.guideId } : {},
1648
- ...typeof source.iconUrl === "string" ? { iconUrl: source.iconUrl } : {},
1649
- ...typeof source.imageUrl === "string" ? { imageUrl: source.imageUrl } : {},
1650
- ...typeof source.externalUrl === "string" ? { externalUrl: source.externalUrl } : {},
1651
- ...typeof source.redirectUrlAfterClaim === "string" ? { redirectUrlAfterClaim: source.redirectUrlAfterClaim } : {},
1652
- ...isObject2(source.metadata) ? { metadata: source.metadata } : {},
1653
- ...Array.isArray(source.dependsOn) ? { dependsOn: source.dependsOn.filter((item) => typeof item === "string") } : {}
1816
+ ...typeof source2.orderIndex === "number" ? { orderIndex: source2.orderIndex } : typeof source2.order === "number" ? { order: source2.order } : {},
1817
+ ...typeof source2.deckId === "string" ? { deckId: source2.deckId } : {},
1818
+ ...typeof source2.groupId === "string" ? { groupId: source2.groupId } : {},
1819
+ ...typeof source2.guideId === "string" ? { guideId: source2.guideId } : {},
1820
+ ...typeof source2.iconUrl === "string" ? { iconUrl: source2.iconUrl } : {},
1821
+ ...typeof source2.imageUrl === "string" ? { imageUrl: source2.imageUrl } : {},
1822
+ ...typeof source2.externalUrl === "string" ? { externalUrl: source2.externalUrl } : {},
1823
+ ...typeof source2.redirectUrlAfterClaim === "string" ? { redirectUrlAfterClaim: source2.redirectUrlAfterClaim } : {},
1824
+ ...isObject2(source2.metadata) ? { metadata: source2.metadata } : {},
1825
+ ...Array.isArray(source2.dependsOn) ? { dependsOn: source2.dependsOn.filter((item) => typeof item === "string") } : {}
1654
1826
  };
1655
1827
  }
1656
1828
  function migrateReward(value, index) {
1657
- const source = isObject2(value) ? value : {};
1829
+ const source2 = isObject2(value) ? value : {};
1658
1830
  return {
1659
- id: typeof source.id === "string" && source.id.trim() !== "" ? source.id.trim() : `reward-${index + 1}`,
1660
- title: text(source.title) ?? `Reward ${index + 1}`,
1661
- description: text(source.description) ?? "",
1662
- type: source.type === "digital" ? "digital" : "in_person",
1663
- redemptionMethod: source.redemptionMethod === "staff_passcode" || source.redemptionMethod === "view_only" ? source.redemptionMethod : "manual_slide",
1664
- requiredStampCount: typeof source.requiredStampCount === "number" && Number.isFinite(source.requiredStampCount) ? Math.max(0, Math.trunc(source.requiredStampCount)) : 0,
1665
- ...typeof source.digitalContentUrl === "string" ? { digitalContentUrl: source.digitalContentUrl } : {},
1666
- ...typeof source.staffPasscode === "string" ? { staffPasscode: source.staffPasscode } : {},
1667
- ...typeof source.validUntil === "string" ? { validUntil: source.validUntil } : {},
1668
- ...typeof source.maxStock === "number" ? { maxStock: source.maxStock } : {},
1669
- ...typeof source.limitPerUser === "number" ? { limitPerUser: source.limitPerUser } : {},
1670
- ...typeof source.claimTicketNumber === "string" ? { claimTicketNumber: source.claimTicketNumber } : {}
1831
+ id: typeof source2.id === "string" && source2.id.trim() !== "" ? source2.id.trim() : `reward-${index + 1}`,
1832
+ title: text(source2.title) ?? `Reward ${index + 1}`,
1833
+ description: text(source2.description) ?? "",
1834
+ type: source2.type === "digital" ? "digital" : "in_person",
1835
+ redemptionMethod: source2.redemptionMethod === "staff_passcode" || source2.redemptionMethod === "view_only" || source2.redemptionMethod === "server_claim" ? source2.redemptionMethod : "manual_slide",
1836
+ requiredStampCount: typeof source2.requiredStampCount === "number" && Number.isFinite(source2.requiredStampCount) ? Math.max(0, Math.trunc(source2.requiredStampCount)) : 0,
1837
+ ...typeof source2.digitalContentUrl === "string" ? { digitalContentUrl: source2.digitalContentUrl } : {},
1838
+ ...typeof source2.staffPasscode === "string" ? { staffPasscode: source2.staffPasscode } : {},
1839
+ ...typeof source2.validUntil === "string" ? { validUntil: source2.validUntil } : {},
1840
+ ...typeof source2.maxStock === "number" ? { maxStock: source2.maxStock } : {},
1841
+ ...typeof source2.limitPerUser === "number" ? { limitPerUser: source2.limitPerUser } : {},
1842
+ ...typeof source2.stockLimit === "number" ? { stockLimit: source2.stockLimit } : {},
1843
+ ...typeof source2.userClaimLimit === "number" ? { userClaimLimit: source2.userClaimLimit } : {},
1844
+ ...typeof source2.claimTicketNumber === "string" ? { claimTicketNumber: source2.claimTicketNumber } : {}
1671
1845
  };
1672
1846
  }
1673
1847
  function migrateRallyConfig(raw) {
1674
- const source = isObject2(raw) ? raw : {};
1675
- const rawStamps = Array.isArray(source.stamps) ? source.stamps : Array.isArray(source.spots) ? source.spots : [];
1676
- const rawRewards = Array.isArray(source.rewards) ? source.rewards : [];
1677
- const id = typeof source.id === "string" && source.id.trim() !== "" ? source.id.trim() : "migrated-rally";
1678
- const title = text(source.title);
1679
- const description = text(source.description);
1680
- const theme = isObject2(source.theme) ? source.theme : void 0;
1848
+ const source2 = isObject2(raw) ? raw : {};
1849
+ const rawStamps = Array.isArray(source2.stamps) ? source2.stamps : Array.isArray(source2.spots) ? source2.spots : [];
1850
+ const rawRewards = Array.isArray(source2.rewards) ? source2.rewards : [];
1851
+ const id = typeof source2.id === "string" && source2.id.trim() !== "" ? source2.id.trim() : "migrated-rally";
1852
+ const title = text(source2.title);
1853
+ const description = text(source2.description);
1854
+ const theme = isObject2(source2.theme) ? source2.theme : void 0;
1681
1855
  return {
1682
1856
  id,
1683
1857
  ...title === void 0 ? {} : { title },
1684
1858
  ...description === void 0 ? {} : { description },
1685
1859
  stamps: rawStamps.map(migrateSpot),
1686
1860
  ...rawRewards.length === 0 ? {} : { rewards: rawRewards.map(migrateReward) },
1687
- ...typeof source.isSequential === "boolean" ? { isSequential: source.isSequential } : {},
1861
+ ...typeof source2.isSequential === "boolean" ? { isSequential: source2.isSequential } : {},
1688
1862
  ...theme === void 0 ? {} : { theme },
1689
1863
  version: CURRENT_RALLY_CONFIG_VERSION,
1690
- ...typeof source.startDate === "string" ? { startDate: source.startDate } : typeof source.startsAt === "string" ? { startDate: source.startsAt } : {},
1691
- ...typeof source.endDate === "string" ? { endDate: source.endDate } : typeof source.endsAt === "string" ? { endDate: source.endsAt } : {}
1864
+ ...typeof source2.startDate === "string" ? { startDate: source2.startDate } : typeof source2.startsAt === "string" ? { startDate: source2.startsAt } : {},
1865
+ ...typeof source2.endDate === "string" ? { endDate: source2.endDate } : typeof source2.endsAt === "string" ? { endDate: source2.endsAt } : {}
1692
1866
  };
1693
1867
  }
1694
1868
 
@@ -1813,22 +1987,22 @@ var THEME_PRESETS = [
1813
1987
  ];
1814
1988
 
1815
1989
  // src/security/snapshotToken.ts
1816
- var encoder = new TextEncoder();
1817
- function cryptoApi() {
1990
+ var encoder2 = new TextEncoder();
1991
+ function cryptoApi2() {
1818
1992
  if (globalThis.crypto === void 0 || globalThis.crypto.subtle === void 0) {
1819
1993
  throw new Error("Web Crypto API is unavailable in this environment.");
1820
1994
  }
1821
1995
  return globalThis.crypto;
1822
1996
  }
1823
1997
  function secretBytes(secretKey) {
1824
- return typeof secretKey === "string" ? encoder.encode(secretKey) : new Uint8Array(secretKey);
1998
+ return typeof secretKey === "string" ? encoder2.encode(secretKey) : new Uint8Array(secretKey);
1825
1999
  }
1826
- function webCryptoBytes(bytes) {
1827
- return bytes.buffer;
2000
+ function webCryptoBytes(bytes2) {
2001
+ return bytes2.buffer;
1828
2002
  }
1829
- function base64Url(bytes) {
2003
+ function base64Url(bytes2) {
1830
2004
  let binary = "";
1831
- for (const byte of bytes) binary += String.fromCharCode(byte);
2005
+ for (const byte of bytes2) binary += String.fromCharCode(byte);
1832
2006
  return globalThis.btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
1833
2007
  }
1834
2008
  function fromBase64Url(value) {
@@ -1838,7 +2012,7 @@ function fromBase64Url(value) {
1838
2012
  return Uint8Array.from(binary, (character) => character.charCodeAt(0));
1839
2013
  }
1840
2014
  async function importHmacKey(secret) {
1841
- return cryptoApi().subtle.importKey(
2015
+ return cryptoApi2().subtle.importKey(
1842
2016
  "raw",
1843
2017
  webCryptoBytes(secret),
1844
2018
  { name: "HMAC", hash: "SHA-256" },
@@ -1847,18 +2021,18 @@ async function importHmacKey(secret) {
1847
2021
  );
1848
2022
  }
1849
2023
  async function importAesKey(secret) {
1850
- const digest = await cryptoApi().subtle.digest("SHA-256", webCryptoBytes(secret));
1851
- return cryptoApi().subtle.importKey("raw", digest, "AES-GCM", false, ["encrypt", "decrypt"]);
2024
+ const digest = await cryptoApi2().subtle.digest("SHA-256", webCryptoBytes(secret));
2025
+ return cryptoApi2().subtle.importKey("raw", digest, "AES-GCM", false, ["encrypt", "decrypt"]);
1852
2026
  }
1853
2027
  function isExpired(payload, now) {
1854
2028
  if (typeof payload.exp === "number" && now >= payload.exp * 1e3) return true;
1855
2029
  return payload.expiresAt !== void 0 && Date.parse(payload.expiresAt) <= now;
1856
2030
  }
1857
2031
  async function createSignedSnapshotToken(payload, secretKey) {
1858
- const api = cryptoApi();
2032
+ const api = cryptoApi2();
1859
2033
  const secret = secretBytes(secretKey);
1860
2034
  const iv = api.getRandomValues(new Uint8Array(12));
1861
- const plaintext = encoder.encode(JSON.stringify(payload));
2035
+ const plaintext = encoder2.encode(JSON.stringify(payload));
1862
2036
  const encrypted = new Uint8Array(
1863
2037
  await api.subtle.encrypt(
1864
2038
  { name: "AES-GCM", iv: webCryptoBytes(iv) },
@@ -1868,7 +2042,7 @@ async function createSignedSnapshotToken(payload, secretKey) {
1868
2042
  );
1869
2043
  const body = base64Url(new Uint8Array([...iv, ...encrypted]));
1870
2044
  const signature = new Uint8Array(
1871
- await api.subtle.sign("HMAC", await importHmacKey(secret), encoder.encode(body))
2045
+ await api.subtle.sign("HMAC", await importHmacKey(secret), encoder2.encode(body))
1872
2046
  );
1873
2047
  return `sr2.${body}.${base64Url(signature)}`;
1874
2048
  }
@@ -1891,11 +2065,11 @@ async function verifySnapshotToken(token, secretKey, now = Date.now()) {
1891
2065
  };
1892
2066
  }
1893
2067
  const secret = secretBytes(secretKey);
1894
- const validSignature = await cryptoApi().subtle.verify(
2068
+ const validSignature = await cryptoApi2().subtle.verify(
1895
2069
  "HMAC",
1896
2070
  await importHmacKey(secret),
1897
2071
  webCryptoBytes(fromBase64Url(encodedSignature)),
1898
- webCryptoBytes(encoder.encode(body))
2072
+ webCryptoBytes(encoder2.encode(body))
1899
2073
  );
1900
2074
  if (!validSignature) {
1901
2075
  return {
@@ -1906,7 +2080,7 @@ async function verifySnapshotToken(token, secretKey, now = Date.now()) {
1906
2080
  }
1907
2081
  const encrypted = fromBase64Url(body);
1908
2082
  if (encrypted.length <= 12) throw new Error("Invalid encrypted payload.");
1909
- const payloadText = await cryptoApi().subtle.decrypt(
2083
+ const payloadText = await cryptoApi2().subtle.decrypt(
1910
2084
  { name: "AES-GCM", iv: webCryptoBytes(encrypted.slice(0, 12)) },
1911
2085
  await importAesKey(secret),
1912
2086
  webCryptoBytes(encrypted.slice(12))
@@ -1931,6 +2105,6 @@ async function verifySnapshotToken(token, secretKey, now = Date.now()) {
1931
2105
  }
1932
2106
  }
1933
2107
 
1934
- export { CURRENT_RALLY_CONFIG_VERSION, DEFAULT_SHEET_THEME, InMemoryStorage, IndexedDBAdapter, LocalStorageAdapter, StampRallyClient, StorageAdapterError, THEME_PRESETS, calculateDistanceMeters, calculateProgress, consumeReward, createClaimTicketNumber, createSignedSnapshotToken, createUniqueClaimTicketNumber, evaluateCheckIn, evaluateCondition, evaluateConditionDetailed, exportProgressToken, getCurrentGeoContext, importProgressToken, isGeolocationSupported, isNfcSupported, isQrSupported, isRewardState, isStampRallyState, issueClaimTicketNumber, migrateRallyConfig, normalizePasscode, processStamp, readNfcContext, readQrContext, reconcileRewardStates, resolveLocalizedText, stripSensitiveConfig, toLocalizedString, validateRallyConfig, verifyPasscode, verifySnapshotToken };
2108
+ export { CURRENT_RALLY_CONFIG_VERSION, DEFAULT_SHEET_THEME, InMemoryStorage, IndexedDBAdapter, LocalStorageAdapter, StampRallyClient, StorageAdapterError, THEME_PRESETS, calculateDistanceMeters, calculateProgress, consumeReward, createClaimTicketNumber, createSecureToken, createSignedSnapshotToken, createUniqueClaimTicketNumber, evaluateCheckIn, evaluateCondition, evaluateConditionDetailed, exportProgressToken, getCurrentGeoContext, importProgressToken, isGeolocationSupported, isNfcSupported, isQrSupported, isRewardState, isStampRallyState, issueClaimTicketNumber, migrateRallyConfig, normalizePasscode, processStamp, readNfcContext, readQrContext, reconcileRewardStates, resolveLocalizedText, stripSensitiveConfig, toLocalizedString, validateRallyConfig, verifyPasscode, verifySecureToken, verifySnapshotToken };
1935
2109
  //# sourceMappingURL=index.js.map
1936
2110
  //# sourceMappingURL=index.js.map