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