@stamprally/core 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +385 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -1
- package/dist/index.d.ts +91 -1
- package/dist/index.js +381 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -987,6 +987,7 @@ var StampRallyClient = class {
|
|
|
987
987
|
#storage;
|
|
988
988
|
#options;
|
|
989
989
|
#config;
|
|
990
|
+
#offlineQueue;
|
|
990
991
|
#userId;
|
|
991
992
|
#state = null;
|
|
992
993
|
#initialization = null;
|
|
@@ -996,6 +997,7 @@ var StampRallyClient = class {
|
|
|
996
997
|
this.#options = isStorage(storageOrOptions) ? { storage: storageOrOptions } : storageOrOptions;
|
|
997
998
|
this.#storage = this.#options.storage ?? new InMemoryStorage();
|
|
998
999
|
this.#userId = this.#options.userId ?? null;
|
|
1000
|
+
this.#offlineQueue = this.#options.offlineQueue;
|
|
999
1001
|
}
|
|
1000
1002
|
getConfig() {
|
|
1001
1003
|
return this.#config;
|
|
@@ -1006,6 +1008,12 @@ var StampRallyClient = class {
|
|
|
1006
1008
|
getUserId() {
|
|
1007
1009
|
return this.#userId;
|
|
1008
1010
|
}
|
|
1011
|
+
get syncState() {
|
|
1012
|
+
return this.#offlineQueue?.syncState ?? "idle";
|
|
1013
|
+
}
|
|
1014
|
+
get pendingCount() {
|
|
1015
|
+
return this.#offlineQueue?.pendingCount ?? 0;
|
|
1016
|
+
}
|
|
1009
1017
|
subscribe(listener) {
|
|
1010
1018
|
this.#listeners.add(listener);
|
|
1011
1019
|
return () => this.#listeners.delete(listener);
|
|
@@ -1109,8 +1117,21 @@ var StampRallyClient = class {
|
|
|
1109
1117
|
};
|
|
1110
1118
|
const remote = this.#options.syncAdapter?.checkIn;
|
|
1111
1119
|
if (options.sync !== false && remote !== void 0) {
|
|
1112
|
-
|
|
1113
|
-
|
|
1120
|
+
try {
|
|
1121
|
+
const result = await remote(request);
|
|
1122
|
+
return result.ok ? this.#commitCheckIn(result) : this.#fail(result.error);
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
if (this.#offlineQueue === void 0) throw error;
|
|
1125
|
+
await this.#offlineQueue.enqueueCheckIn(request);
|
|
1126
|
+
const record2 = { stampId: spotId, acquiredAt: now };
|
|
1127
|
+
const next2 = this.#reconcile({
|
|
1128
|
+
...current,
|
|
1129
|
+
records: [...current.records, record2],
|
|
1130
|
+
updatedAt: now
|
|
1131
|
+
});
|
|
1132
|
+
await this.#storage.save(next2);
|
|
1133
|
+
return this.#commitCheckIn({ ok: true, value: { state: next2, record: record2 } });
|
|
1134
|
+
}
|
|
1114
1135
|
}
|
|
1115
1136
|
const record = { stampId: spotId, acquiredAt: now };
|
|
1116
1137
|
const next = this.#reconcile({
|
|
@@ -1152,8 +1173,22 @@ var StampRallyClient = class {
|
|
|
1152
1173
|
};
|
|
1153
1174
|
const remote = this.#options.syncAdapter?.claimReward;
|
|
1154
1175
|
if (options.sync !== false && remote !== void 0) {
|
|
1155
|
-
|
|
1156
|
-
|
|
1176
|
+
try {
|
|
1177
|
+
const result = await remote(request);
|
|
1178
|
+
return result.ok ? this.#commitClaim(result) : this.#fail(result.error);
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
if (this.#offlineQueue === void 0) throw error;
|
|
1181
|
+
await this.#offlineQueue.enqueueClaimReward(request);
|
|
1182
|
+
const next2 = {
|
|
1183
|
+
...current,
|
|
1184
|
+
rewards: current.rewards.map(
|
|
1185
|
+
(item) => item.rewardId === rewardId ? local.value : item
|
|
1186
|
+
),
|
|
1187
|
+
updatedAt: now
|
|
1188
|
+
};
|
|
1189
|
+
await this.#storage.save(next2);
|
|
1190
|
+
return this.#commitClaim({ ok: true, value: { state: next2, reward: local.value } });
|
|
1191
|
+
}
|
|
1157
1192
|
}
|
|
1158
1193
|
const next = {
|
|
1159
1194
|
...current,
|
|
@@ -1167,6 +1202,18 @@ var StampRallyClient = class {
|
|
|
1167
1202
|
sync(adapter = this.#options.syncAdapter) {
|
|
1168
1203
|
return this.#enqueue(async () => {
|
|
1169
1204
|
const current = await this.initialize();
|
|
1205
|
+
if (this.#offlineQueue !== void 0 && adapter !== void 0) {
|
|
1206
|
+
await this.#offlineQueue.sync(async (operation) => {
|
|
1207
|
+
if (operation.kind === "checkIn") {
|
|
1208
|
+
if (adapter.checkIn === void 0)
|
|
1209
|
+
throw new Error("No check-in sync adapter is configured.");
|
|
1210
|
+
return adapter.checkIn(operation.request);
|
|
1211
|
+
}
|
|
1212
|
+
if (adapter.claimReward === void 0)
|
|
1213
|
+
throw new Error("No reward sync adapter is configured.");
|
|
1214
|
+
return adapter.claimReward(operation.request);
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1170
1217
|
if (adapter?.sync === void 0) {
|
|
1171
1218
|
this.#emitEvent({ type: "sync", state: current });
|
|
1172
1219
|
return;
|
|
@@ -1180,6 +1227,9 @@ var StampRallyClient = class {
|
|
|
1180
1227
|
this.#emitEvent({ type: "sync", state: next });
|
|
1181
1228
|
});
|
|
1182
1229
|
}
|
|
1230
|
+
retrySync() {
|
|
1231
|
+
return this.sync();
|
|
1232
|
+
}
|
|
1183
1233
|
reset() {
|
|
1184
1234
|
return this.#enqueue(async () => {
|
|
1185
1235
|
await this.#storage.remove(this.#config.id, this.#userId);
|
|
@@ -1261,6 +1311,205 @@ var StampRallyClient = class {
|
|
|
1261
1311
|
}
|
|
1262
1312
|
};
|
|
1263
1313
|
|
|
1314
|
+
// src/client/offlineQueue.ts
|
|
1315
|
+
var MemoryQueueStorage = class {
|
|
1316
|
+
#values = /* @__PURE__ */ new Map();
|
|
1317
|
+
async load(key) {
|
|
1318
|
+
return this.#values.get(key) ?? [];
|
|
1319
|
+
}
|
|
1320
|
+
async save(key, operations) {
|
|
1321
|
+
this.#values.set(key, structuredClone(operations));
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
var LocalStorageQueueStorage = class {
|
|
1325
|
+
constructor(storage) {
|
|
1326
|
+
this.storage = storage;
|
|
1327
|
+
}
|
|
1328
|
+
storage;
|
|
1329
|
+
async load(key) {
|
|
1330
|
+
const value = this.storage.getItem(key);
|
|
1331
|
+
if (value === null) return [];
|
|
1332
|
+
try {
|
|
1333
|
+
const parsed = JSON.parse(value);
|
|
1334
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1335
|
+
} catch {
|
|
1336
|
+
return [];
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
async save(key, operations) {
|
|
1340
|
+
this.storage.setItem(key, JSON.stringify(operations));
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
var IndexedDBOfflineQueueStorage = class {
|
|
1344
|
+
#providedFactory;
|
|
1345
|
+
#databaseName;
|
|
1346
|
+
#databasePromise = null;
|
|
1347
|
+
constructor(options = {}) {
|
|
1348
|
+
this.#providedFactory = options.indexedDB;
|
|
1349
|
+
this.#databaseName = options.databaseName ?? "stamprally-offline-queue";
|
|
1350
|
+
}
|
|
1351
|
+
async load(key) {
|
|
1352
|
+
const database = await this.#open();
|
|
1353
|
+
return new Promise((resolve, reject) => {
|
|
1354
|
+
const request = database.transaction("operations", "readonly").objectStore("operations").get(key);
|
|
1355
|
+
request.onsuccess = () => resolve(Array.isArray(request.result) ? request.result : []);
|
|
1356
|
+
request.onerror = () => reject(request.error ?? new Error("Failed to read offline queue."));
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
async save(key, operations) {
|
|
1360
|
+
const database = await this.#open();
|
|
1361
|
+
return new Promise((resolve, reject) => {
|
|
1362
|
+
const transaction = database.transaction("operations", "readwrite");
|
|
1363
|
+
transaction.objectStore("operations").put(structuredClone(operations), key);
|
|
1364
|
+
transaction.oncomplete = () => resolve();
|
|
1365
|
+
transaction.onerror = () => reject(transaction.error ?? new Error("Failed to save offline queue."));
|
|
1366
|
+
transaction.onabort = () => reject(transaction.error ?? new Error("Offline queue write aborted."));
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
#open() {
|
|
1370
|
+
if (this.#databasePromise !== null) return this.#databasePromise;
|
|
1371
|
+
let factory = this.#providedFactory;
|
|
1372
|
+
if (factory === void 0)
|
|
1373
|
+
factory = globalThis.indexedDB;
|
|
1374
|
+
if (factory === void 0 || factory === null)
|
|
1375
|
+
return Promise.reject(new Error("IndexedDB is unavailable in this environment."));
|
|
1376
|
+
this.#databasePromise = new Promise((resolve, reject) => {
|
|
1377
|
+
const request = factory.open(this.#databaseName, 1);
|
|
1378
|
+
request.onupgradeneeded = () => {
|
|
1379
|
+
if (!request.result.objectStoreNames.contains("operations"))
|
|
1380
|
+
request.result.createObjectStore("operations");
|
|
1381
|
+
};
|
|
1382
|
+
request.onsuccess = () => resolve(request.result);
|
|
1383
|
+
request.onerror = () => reject(request.error ?? new Error("Failed to open offline queue."));
|
|
1384
|
+
}).catch((error) => {
|
|
1385
|
+
this.#databasePromise = null;
|
|
1386
|
+
throw error;
|
|
1387
|
+
});
|
|
1388
|
+
return this.#databasePromise;
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
function defaultStorage(databaseName) {
|
|
1392
|
+
try {
|
|
1393
|
+
const indexedDB = globalThis.indexedDB;
|
|
1394
|
+
if (indexedDB !== void 0)
|
|
1395
|
+
return new IndexedDBOfflineQueueStorage({
|
|
1396
|
+
indexedDB,
|
|
1397
|
+
...databaseName === void 0 ? {} : { databaseName }
|
|
1398
|
+
});
|
|
1399
|
+
const storage = globalThis.localStorage;
|
|
1400
|
+
if (storage !== void 0 && storage !== null) return new LocalStorageQueueStorage(storage);
|
|
1401
|
+
} catch {
|
|
1402
|
+
}
|
|
1403
|
+
return new MemoryQueueStorage();
|
|
1404
|
+
}
|
|
1405
|
+
function operationId(operation) {
|
|
1406
|
+
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
|
+
}
|
|
1408
|
+
var OfflineQueue = class {
|
|
1409
|
+
#storage;
|
|
1410
|
+
#key;
|
|
1411
|
+
#conflictPolicy;
|
|
1412
|
+
#onSyncConflict;
|
|
1413
|
+
#operations = [];
|
|
1414
|
+
#loaded = false;
|
|
1415
|
+
#state = "idle";
|
|
1416
|
+
#error = null;
|
|
1417
|
+
#sender;
|
|
1418
|
+
#syncPromise = null;
|
|
1419
|
+
constructor(options = {}) {
|
|
1420
|
+
if (options.storage !== void 0) this.#storage = options.storage;
|
|
1421
|
+
else if (options.storageLike !== void 0 && options.storageLike !== null)
|
|
1422
|
+
this.#storage = new LocalStorageQueueStorage(options.storageLike);
|
|
1423
|
+
else this.#storage = defaultStorage(options.databaseName);
|
|
1424
|
+
this.#key = options.key ?? "stamprally:offline-queue";
|
|
1425
|
+
this.#conflictPolicy = options.conflictPolicy ?? "server_wins";
|
|
1426
|
+
this.#onSyncConflict = options.onSyncConflict;
|
|
1427
|
+
}
|
|
1428
|
+
get syncState() {
|
|
1429
|
+
return this.#state;
|
|
1430
|
+
}
|
|
1431
|
+
get pendingCount() {
|
|
1432
|
+
return this.#operations.length;
|
|
1433
|
+
}
|
|
1434
|
+
get error() {
|
|
1435
|
+
return this.#error;
|
|
1436
|
+
}
|
|
1437
|
+
get operations() {
|
|
1438
|
+
return this.#operations;
|
|
1439
|
+
}
|
|
1440
|
+
async initialize() {
|
|
1441
|
+
if (this.#loaded) return;
|
|
1442
|
+
this.#operations = [...await this.#storage.load(this.#key)];
|
|
1443
|
+
this.#loaded = true;
|
|
1444
|
+
}
|
|
1445
|
+
setSender(sender) {
|
|
1446
|
+
this.#sender = sender;
|
|
1447
|
+
}
|
|
1448
|
+
async enqueue(operation) {
|
|
1449
|
+
await this.initialize();
|
|
1450
|
+
const id2 = operationId(operation);
|
|
1451
|
+
if (this.#operations.some((item) => operationId(item) === id2)) return;
|
|
1452
|
+
this.#operations = [...this.#operations, operation];
|
|
1453
|
+
await this.#storage.save(this.#key, this.#operations);
|
|
1454
|
+
}
|
|
1455
|
+
async enqueueCheckIn(request) {
|
|
1456
|
+
return this.enqueue({ kind: "checkIn", request });
|
|
1457
|
+
}
|
|
1458
|
+
async enqueueClaimReward(request) {
|
|
1459
|
+
return this.enqueue({ kind: "claimReward", request });
|
|
1460
|
+
}
|
|
1461
|
+
async clear() {
|
|
1462
|
+
await this.initialize();
|
|
1463
|
+
this.#operations = [];
|
|
1464
|
+
await this.#storage.save(this.#key, this.#operations);
|
|
1465
|
+
}
|
|
1466
|
+
async sync(sender = this.#sender) {
|
|
1467
|
+
await this.initialize();
|
|
1468
|
+
if (sender === void 0) throw new Error("OfflineQueue.sync requires a sender.");
|
|
1469
|
+
if (this.#syncPromise !== null) return this.#syncPromise;
|
|
1470
|
+
this.#sender = sender;
|
|
1471
|
+
this.#syncPromise = this.#run(sender).finally(() => {
|
|
1472
|
+
this.#syncPromise = null;
|
|
1473
|
+
});
|
|
1474
|
+
return this.#syncPromise;
|
|
1475
|
+
}
|
|
1476
|
+
async retrySync(sender = this.#sender) {
|
|
1477
|
+
return this.sync(sender);
|
|
1478
|
+
}
|
|
1479
|
+
async #run(sender) {
|
|
1480
|
+
this.#state = "syncing";
|
|
1481
|
+
this.#error = null;
|
|
1482
|
+
try {
|
|
1483
|
+
while (this.#operations.length > 0) {
|
|
1484
|
+
const operation = this.#operations[0];
|
|
1485
|
+
if (operation === void 0) break;
|
|
1486
|
+
let result;
|
|
1487
|
+
try {
|
|
1488
|
+
result = await sender(operation);
|
|
1489
|
+
} catch (cause) {
|
|
1490
|
+
throw cause instanceof Error ? cause : new Error(String(cause));
|
|
1491
|
+
}
|
|
1492
|
+
if ("conflict" in result && result.conflict === true)
|
|
1493
|
+
await this.resolveConflict(operation, result.localState, result.serverState);
|
|
1494
|
+
this.#operations = this.#operations.slice(1);
|
|
1495
|
+
await this.#storage.save(this.#key, this.#operations);
|
|
1496
|
+
}
|
|
1497
|
+
this.#state = "idle";
|
|
1498
|
+
} catch (cause) {
|
|
1499
|
+
this.#state = "error";
|
|
1500
|
+
this.#error = cause instanceof Error ? cause : new Error(String(cause));
|
|
1501
|
+
throw this.#error;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
async resolveConflict(operation, localState, serverState) {
|
|
1505
|
+
const configured = this.#onSyncConflict;
|
|
1506
|
+
const policy = (typeof configured === "function" ? await configured({ operation, localState, serverState }) : configured) ?? this.#conflictPolicy;
|
|
1507
|
+
if (policy === "merge") {
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
|
|
1264
1513
|
// src/crypto/token.ts
|
|
1265
1514
|
var encoder = new TextEncoder();
|
|
1266
1515
|
var decoder = new TextDecoder();
|
|
@@ -1393,27 +1642,6 @@ async function decryptPayload(body, secret) {
|
|
|
1393
1642
|
);
|
|
1394
1643
|
}
|
|
1395
1644
|
|
|
1396
|
-
// src/domain/i18n.ts
|
|
1397
|
-
function updateLocalizedField(current, locale, newValue) {
|
|
1398
|
-
if (typeof current === "string" || current === void 0)
|
|
1399
|
-
return { [locale]: newValue };
|
|
1400
|
-
return { ...current, [locale]: newValue };
|
|
1401
|
-
}
|
|
1402
|
-
function resolveLocalizedText(text, locale, fallbackLocale) {
|
|
1403
|
-
if (text === void 0 || text === "") return "";
|
|
1404
|
-
if (typeof text === "string") return text;
|
|
1405
|
-
const fallback = fallbackLocale === void 0 ? Object.values(text).find((value) => typeof value === "string") : text[fallbackLocale];
|
|
1406
|
-
return text[locale] || fallback || "";
|
|
1407
|
-
}
|
|
1408
|
-
function toLocalizedString(text) {
|
|
1409
|
-
if (text === void 0) return { ja: "", en: "" };
|
|
1410
|
-
return typeof text === "string" ? { ja: text, en: "" } : {
|
|
1411
|
-
ja: text["ja"] ?? "",
|
|
1412
|
-
en: text["en"] ?? "",
|
|
1413
|
-
...text
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
1645
|
// src/domain/models.ts
|
|
1418
1646
|
var DEFAULT_SHEET_THEME = {
|
|
1419
1647
|
primaryColor: "#9e551e",
|
|
@@ -1458,7 +1686,7 @@ function toPublicConfig(config) {
|
|
|
1458
1686
|
rewards: config.rewards.map(
|
|
1459
1687
|
({ digitalContentUrl: _content, staffPasscode: _passcode, ...reward2 }) => reward2
|
|
1460
1688
|
),
|
|
1461
|
-
...config.metadata === void 0 ? {} : { metadata: config.metadata },
|
|
1689
|
+
...config.publicMetadata !== void 0 ? { metadata: config.publicMetadata } : config.metadata === void 0 ? {} : { metadata: config.metadata },
|
|
1462
1690
|
...config.serverEndpoint === void 0 ? {} : { serverEndpoint: config.serverEndpoint }
|
|
1463
1691
|
};
|
|
1464
1692
|
}
|
|
@@ -1517,6 +1745,118 @@ function isPublicConfig(value) {
|
|
|
1517
1745
|
});
|
|
1518
1746
|
}
|
|
1519
1747
|
|
|
1748
|
+
// src/domain/configTransform.ts
|
|
1749
|
+
var PRIVATE_KEYS = /* @__PURE__ */ new Set([
|
|
1750
|
+
"staffPasscode",
|
|
1751
|
+
"serverMetadata",
|
|
1752
|
+
"inventory",
|
|
1753
|
+
"secretToken",
|
|
1754
|
+
"secretParams",
|
|
1755
|
+
"digitalContentUrl",
|
|
1756
|
+
"code",
|
|
1757
|
+
"tagId"
|
|
1758
|
+
]);
|
|
1759
|
+
var SENSITIVE_KEY_PATTERN = /^(?:api[_-]?key|access[_-]?token|auth[_-]?token|password|private[_-]?key|secret)$/i;
|
|
1760
|
+
function isRecord2(value) {
|
|
1761
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1762
|
+
}
|
|
1763
|
+
function isPrivateKey(key) {
|
|
1764
|
+
return PRIVATE_KEYS.has(key) || SENSITIVE_KEY_PATTERN.test(key);
|
|
1765
|
+
}
|
|
1766
|
+
function sanitizeValue(value, customFilter, seen) {
|
|
1767
|
+
if (Array.isArray(value)) {
|
|
1768
|
+
if (seen.has(value)) return void 0;
|
|
1769
|
+
seen.add(value);
|
|
1770
|
+
const items = value.map((item) => sanitizeValue(item, customFilter, seen)).filter((item) => item !== void 0);
|
|
1771
|
+
return items;
|
|
1772
|
+
}
|
|
1773
|
+
if (!isRecord2(value)) return value;
|
|
1774
|
+
if (seen.has(value)) return void 0;
|
|
1775
|
+
seen.add(value);
|
|
1776
|
+
const result = {};
|
|
1777
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1778
|
+
if (isPrivateKey(key) || customFilter?.(key, item) === false) continue;
|
|
1779
|
+
const sanitized = sanitizeValue(item, customFilter, seen);
|
|
1780
|
+
if (sanitized !== void 0) result[key] = sanitized;
|
|
1781
|
+
}
|
|
1782
|
+
return result;
|
|
1783
|
+
}
|
|
1784
|
+
function sanitizeSpot(spot2, customFilter) {
|
|
1785
|
+
return sanitizeValue(
|
|
1786
|
+
toPublicConfig({
|
|
1787
|
+
id: "__spot__",
|
|
1788
|
+
version: "1",
|
|
1789
|
+
title: "__spot__",
|
|
1790
|
+
spots: [spot2],
|
|
1791
|
+
rewards: []
|
|
1792
|
+
}).spots[0],
|
|
1793
|
+
customFilter,
|
|
1794
|
+
/* @__PURE__ */ new WeakSet()
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
function sanitizeReward(reward2, customFilter) {
|
|
1798
|
+
return sanitizeValue(
|
|
1799
|
+
toPublicConfig({
|
|
1800
|
+
id: "__reward__",
|
|
1801
|
+
version: "1",
|
|
1802
|
+
title: "__reward__",
|
|
1803
|
+
spots: [],
|
|
1804
|
+
rewards: [reward2]
|
|
1805
|
+
}).rewards[0],
|
|
1806
|
+
customFilter,
|
|
1807
|
+
/* @__PURE__ */ new WeakSet()
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
function sanitizeAdminConfig(admin, customFilter) {
|
|
1811
|
+
const publicConfig = toPublicConfig(admin);
|
|
1812
|
+
const sanitized = sanitizeValue(publicConfig, customFilter, /* @__PURE__ */ new WeakSet());
|
|
1813
|
+
sanitized.spots = admin.spots.map((spot2) => sanitizeSpot(spot2, customFilter));
|
|
1814
|
+
sanitized.rewards = admin.rewards.map((reward2) => sanitizeReward(reward2, customFilter));
|
|
1815
|
+
return sanitized;
|
|
1816
|
+
}
|
|
1817
|
+
function validatePublicConfigSafety(publicConfig) {
|
|
1818
|
+
const leakedKeys = [];
|
|
1819
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1820
|
+
const visit = (value, path) => {
|
|
1821
|
+
if (Array.isArray(value)) {
|
|
1822
|
+
value.forEach((item, index) => {
|
|
1823
|
+
visit(item, `${path}[${index}]`);
|
|
1824
|
+
});
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
if (!isRecord2(value) || seen.has(value)) return;
|
|
1828
|
+
seen.add(value);
|
|
1829
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1830
|
+
const nextPath = path === "$" ? key : `${path}.${key}`;
|
|
1831
|
+
if (isPrivateKey(key)) leakedKeys.push(nextPath);
|
|
1832
|
+
visit(item, nextPath);
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1835
|
+
visit(publicConfig, "$");
|
|
1836
|
+
return { safe: leakedKeys.length === 0, leakedKeys };
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// src/domain/i18n.ts
|
|
1840
|
+
function updateLocalizedField(current, locale, newValue) {
|
|
1841
|
+
if (typeof current === "string" || current === void 0)
|
|
1842
|
+
return { [locale]: newValue };
|
|
1843
|
+
return { ...current, [locale]: newValue };
|
|
1844
|
+
}
|
|
1845
|
+
function resolveLocalizedText(text, locale, fallbackLocale) {
|
|
1846
|
+
if (text === void 0 || text === "") return "";
|
|
1847
|
+
if (typeof text === "string") return text;
|
|
1848
|
+
const fallback = fallbackLocale === void 0 ? Object.values(text).find((value) => typeof value === "string") : text[fallbackLocale];
|
|
1849
|
+
return text[locale] || fallback || "";
|
|
1850
|
+
}
|
|
1851
|
+
function toLocalizedString(text) {
|
|
1852
|
+
if (text === void 0) return { ja: "", en: "" };
|
|
1853
|
+
return typeof text === "string" ? { ja: text, en: "" } : {
|
|
1854
|
+
ja: text["ja"] ?? "",
|
|
1855
|
+
en: text["en"] ?? "",
|
|
1856
|
+
...text
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1520
1860
|
// src/domain/themePresets.ts
|
|
1521
1861
|
var THEME_PRESETS = [
|
|
1522
1862
|
{
|
|
@@ -1625,7 +1965,7 @@ var ConfigValidationError = class extends Error {
|
|
|
1625
1965
|
errors;
|
|
1626
1966
|
name = "ConfigValidationError";
|
|
1627
1967
|
};
|
|
1628
|
-
function
|
|
1968
|
+
function isRecord3(value) {
|
|
1629
1969
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1630
1970
|
}
|
|
1631
1971
|
function hasOwn(value, key) {
|
|
@@ -1664,7 +2004,7 @@ function finiteNumber(value, key, path, errors, minimum) {
|
|
|
1664
2004
|
}
|
|
1665
2005
|
function localizedText(value, path, errors) {
|
|
1666
2006
|
if (typeof value === "string") return;
|
|
1667
|
-
if (!
|
|
2007
|
+
if (!isRecord3(value)) {
|
|
1668
2008
|
add(errors, path, "Expected a string or a locale map.", "invalid_localized_text");
|
|
1669
2009
|
return;
|
|
1670
2010
|
}
|
|
@@ -1674,7 +2014,7 @@ function localizedText(value, path, errors) {
|
|
|
1674
2014
|
}
|
|
1675
2015
|
}
|
|
1676
2016
|
function theme(value, path, errors) {
|
|
1677
|
-
if (!
|
|
2017
|
+
if (!isRecord3(value)) {
|
|
1678
2018
|
add(errors, path, "Expected a theme object.", "invalid_type");
|
|
1679
2019
|
return;
|
|
1680
2020
|
}
|
|
@@ -1699,7 +2039,7 @@ function externalReferences(value, path, errors) {
|
|
|
1699
2039
|
}
|
|
1700
2040
|
value.forEach((item, index) => {
|
|
1701
2041
|
const itemPath = `${path}[${index}]`;
|
|
1702
|
-
if (!
|
|
2042
|
+
if (!isRecord3(item)) {
|
|
1703
2043
|
add(errors, itemPath, "Expected an object.", "invalid_type");
|
|
1704
2044
|
return;
|
|
1705
2045
|
}
|
|
@@ -1709,7 +2049,7 @@ function externalReferences(value, path, errors) {
|
|
|
1709
2049
|
});
|
|
1710
2050
|
}
|
|
1711
2051
|
function condition(value, path, errors, isPublic) {
|
|
1712
|
-
if (!
|
|
2052
|
+
if (!isRecord3(value)) {
|
|
1713
2053
|
add(errors, path, "Expected a condition object.", "invalid_type");
|
|
1714
2054
|
return;
|
|
1715
2055
|
}
|
|
@@ -1759,14 +2099,14 @@ function condition(value, path, errors, isPublic) {
|
|
|
1759
2099
|
requiredString(value, "validatorName", path, errors);
|
|
1760
2100
|
if (isPublic && hasOwn(value, "secretParams"))
|
|
1761
2101
|
add(errors, `${path}.secretParams`, "Private field is not allowed.", "private_field");
|
|
1762
|
-
if (!isPublic && hasOwn(value, "secretParams") && !
|
|
2102
|
+
if (!isPublic && hasOwn(value, "secretParams") && !isRecord3(value.secretParams))
|
|
1763
2103
|
add(errors, `${path}.secretParams`, "Expected an object.", "invalid_type");
|
|
1764
2104
|
return;
|
|
1765
2105
|
}
|
|
1766
2106
|
add(errors, `${path}.type`, "Unknown condition type.", "invalid_enum");
|
|
1767
2107
|
}
|
|
1768
2108
|
function unlockCondition(value, path, errors) {
|
|
1769
|
-
if (!
|
|
2109
|
+
if (!isRecord3(value)) {
|
|
1770
2110
|
add(errors, path, "Expected an unlock condition object.", "invalid_type");
|
|
1771
2111
|
return;
|
|
1772
2112
|
}
|
|
@@ -1801,7 +2141,7 @@ function unlockCondition(value, path, errors) {
|
|
|
1801
2141
|
add(errors, `${path}.type`, "Unknown unlock condition type.", "invalid_enum");
|
|
1802
2142
|
}
|
|
1803
2143
|
function spot(value, path, errors, isPublic) {
|
|
1804
|
-
if (!
|
|
2144
|
+
if (!isRecord3(value)) {
|
|
1805
2145
|
add(errors, path, "Expected a spot object.", "invalid_type");
|
|
1806
2146
|
return;
|
|
1807
2147
|
}
|
|
@@ -1838,7 +2178,7 @@ function spot(value, path, errors, isPublic) {
|
|
|
1838
2178
|
});
|
|
1839
2179
|
}
|
|
1840
2180
|
function reward(value, path, errors, isPublic) {
|
|
1841
|
-
if (!
|
|
2181
|
+
if (!isRecord3(value)) {
|
|
1842
2182
|
add(errors, path, "Expected a reward object.", "invalid_type");
|
|
1843
2183
|
return;
|
|
1844
2184
|
}
|
|
@@ -1881,7 +2221,7 @@ function reward(value, path, errors, isPublic) {
|
|
|
1881
2221
|
}
|
|
1882
2222
|
function validate(value, isPublic) {
|
|
1883
2223
|
const errors = [];
|
|
1884
|
-
if (!
|
|
2224
|
+
if (!isRecord3(value)) {
|
|
1885
2225
|
add(errors, "$", "Expected a configuration object.", "invalid_type");
|
|
1886
2226
|
return errors;
|
|
1887
2227
|
}
|
|
@@ -1902,10 +2242,12 @@ function validate(value, isPublic) {
|
|
|
1902
2242
|
});
|
|
1903
2243
|
if (!isPublic) {
|
|
1904
2244
|
optionalString(value, "staffPasscode", "$", errors);
|
|
1905
|
-
if (hasOwn(value, "inventory") && value.inventory !== void 0 && !
|
|
2245
|
+
if (hasOwn(value, "inventory") && value.inventory !== void 0 && !isRecord3(value.inventory))
|
|
1906
2246
|
add(errors, "$.inventory", "Expected an object.", "invalid_type");
|
|
1907
|
-
if (hasOwn(value, "serverMetadata") && value.serverMetadata !== void 0 && !
|
|
2247
|
+
if (hasOwn(value, "serverMetadata") && value.serverMetadata !== void 0 && !isRecord3(value.serverMetadata))
|
|
1908
2248
|
add(errors, "$.serverMetadata", "Expected an object.", "invalid_type");
|
|
2249
|
+
if (hasOwn(value, "publicMetadata") && value.publicMetadata !== void 0 && !isRecord3(value.publicMetadata))
|
|
2250
|
+
add(errors, "$.publicMetadata", "Expected an object.", "invalid_type");
|
|
1909
2251
|
optionalString(value, "serverEndpoint", "$", errors);
|
|
1910
2252
|
} else {
|
|
1911
2253
|
for (const key of ["staffPasscode", "serverMetadata", "inventory"])
|
|
@@ -2053,6 +2395,6 @@ async function verifySnapshotToken(token, secretKey, now = Date.now()) {
|
|
|
2053
2395
|
}
|
|
2054
2396
|
}
|
|
2055
2397
|
|
|
2056
|
-
export { ConfigValidationError, DEFAULT_SHEET_THEME, InMemoryStorage, IndexedDBAdapter, LocalStorageAdapter, 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, storageKey, toLocalizedString, toPublicConfig, updateLocalizedField, verifyPasscode, verifySecureToken, verifySnapshotToken };
|
|
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 };
|
|
2057
2399
|
//# sourceMappingURL=index.js.map
|
|
2058
2400
|
//# sourceMappingURL=index.js.map
|