@stamprally/server 0.6.0 → 0.8.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 +393 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -2
- package/dist/index.d.ts +97 -2
- package/dist/index.js +393 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,397 @@
|
|
|
2
2
|
|
|
3
3
|
var core = require('@stamprally/core');
|
|
4
4
|
|
|
5
|
+
// src/index.ts
|
|
6
|
+
|
|
7
|
+
// src/persistence.ts
|
|
8
|
+
var InMemoryServerPersistenceAdapter = class {
|
|
9
|
+
#locks = /* @__PURE__ */ new Map();
|
|
10
|
+
#idempotent = /* @__PURE__ */ new Map();
|
|
11
|
+
#states = /* @__PURE__ */ new Map();
|
|
12
|
+
#stocks;
|
|
13
|
+
#auditLogs = [];
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.#stocks = new Map(Object.entries(options.stocks ?? {}));
|
|
16
|
+
}
|
|
17
|
+
async acquireLock(lockKey, ttlMs) {
|
|
18
|
+
const now2 = Date.now();
|
|
19
|
+
const expiresAt = this.#locks.get(lockKey);
|
|
20
|
+
if (expiresAt !== void 0 && expiresAt > now2) return false;
|
|
21
|
+
this.#locks.set(lockKey, now2 + Math.max(1, ttlMs));
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
async releaseLock(lockKey) {
|
|
25
|
+
this.#locks.delete(lockKey);
|
|
26
|
+
}
|
|
27
|
+
async decrementRewardStock(rewardId) {
|
|
28
|
+
const stock = this.#stocks.get(rewardId);
|
|
29
|
+
if (stock === void 0) return { success: true, remainingStock: Number.POSITIVE_INFINITY };
|
|
30
|
+
if (stock <= 0) return { success: false, remainingStock: 0 };
|
|
31
|
+
const remainingStock = stock - 1;
|
|
32
|
+
this.#stocks.set(rewardId, remainingStock);
|
|
33
|
+
return { success: true, remainingStock };
|
|
34
|
+
}
|
|
35
|
+
async getIdempotentResult(idempotencyKey) {
|
|
36
|
+
const entry = this.#idempotent.get(idempotencyKey);
|
|
37
|
+
if (entry === void 0) return null;
|
|
38
|
+
if (entry.expiresAt <= Date.now()) {
|
|
39
|
+
this.#idempotent.delete(idempotencyKey);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return structuredClone(entry.value);
|
|
43
|
+
}
|
|
44
|
+
async saveIdempotentResult(idempotencyKey, result, ttlMs) {
|
|
45
|
+
this.#idempotent.set(idempotencyKey, {
|
|
46
|
+
value: structuredClone(result),
|
|
47
|
+
expiresAt: Date.now() + Math.max(1, ttlMs)
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async getUserState(rallyId, userId) {
|
|
51
|
+
const state = this.#states.get(`${rallyId}:${userId}`);
|
|
52
|
+
return state === void 0 ? null : structuredClone(state);
|
|
53
|
+
}
|
|
54
|
+
async saveUserState(rallyId, userId, state) {
|
|
55
|
+
this.#states.set(`${rallyId}:${userId}`, structuredClone(state));
|
|
56
|
+
}
|
|
57
|
+
async recordAuditLog(log) {
|
|
58
|
+
this.#auditLogs.push(structuredClone(log));
|
|
59
|
+
}
|
|
60
|
+
getAuditLogs() {
|
|
61
|
+
return structuredClone(this.#auditLogs);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
function distanceMeters(aLat, aLon, bLat, bLon) {
|
|
65
|
+
const radians = (degrees) => degrees * Math.PI / 180;
|
|
66
|
+
const dLat = radians(bLat - aLat);
|
|
67
|
+
const dLon = radians(bLon - aLon);
|
|
68
|
+
const latA = radians(aLat);
|
|
69
|
+
const latB = radians(bLat);
|
|
70
|
+
const value = Math.sin(dLat / 2) ** 2 + Math.cos(latA) * Math.cos(latB) * Math.sin(dLon / 2) ** 2;
|
|
71
|
+
return 6371e3 * 2 * Math.asin(Math.sqrt(Math.min(1, value)));
|
|
72
|
+
}
|
|
73
|
+
function initialState(config, now2) {
|
|
74
|
+
return {
|
|
75
|
+
rallyId: config.id,
|
|
76
|
+
records: [],
|
|
77
|
+
rewards: core.reconcileRewardStates(
|
|
78
|
+
config.rewards.map((reward) => ({ ...reward, description: reward.description ?? "" })),
|
|
79
|
+
[],
|
|
80
|
+
0,
|
|
81
|
+
now2
|
|
82
|
+
),
|
|
83
|
+
updatedAt: now2
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function json(body, status = 200) {
|
|
87
|
+
return new Response(JSON.stringify(body), {
|
|
88
|
+
status,
|
|
89
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function requestBody(request) {
|
|
93
|
+
return request.json().then((value) => {
|
|
94
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
95
|
+
return value;
|
|
96
|
+
}).catch(() => null);
|
|
97
|
+
}
|
|
98
|
+
function adminReward(reward) {
|
|
99
|
+
return { ...reward, description: reward.description ?? "" };
|
|
100
|
+
}
|
|
101
|
+
function matchesCondition(condition, context, customValidators) {
|
|
102
|
+
switch (condition.type) {
|
|
103
|
+
case "qr":
|
|
104
|
+
return context.type === "qr" && context.token === condition.secretToken;
|
|
105
|
+
case "passcode":
|
|
106
|
+
return context.type === "passcode" && (condition.caseSensitive === false ? context.code.toLocaleLowerCase() === condition.code.toLocaleLowerCase() : context.code === condition.code);
|
|
107
|
+
case "gps":
|
|
108
|
+
return context.type === "gps" && distanceMeters(
|
|
109
|
+
condition.latitude,
|
|
110
|
+
condition.longitude,
|
|
111
|
+
context.latitude,
|
|
112
|
+
context.longitude
|
|
113
|
+
) <= condition.radiusMeters;
|
|
114
|
+
case "custom":
|
|
115
|
+
return customValidators?.[condition.validatorName]?.(
|
|
116
|
+
context.type === "custom" ? context.value : void 0,
|
|
117
|
+
condition
|
|
118
|
+
) ?? false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function audit(request, status, now2, errorCode) {
|
|
122
|
+
return {
|
|
123
|
+
id: `audit-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`,
|
|
124
|
+
timestamp: now2,
|
|
125
|
+
rallyId: request.rallyId,
|
|
126
|
+
userId: request.userId,
|
|
127
|
+
action: "CHECK_IN",
|
|
128
|
+
resourceId: request.spotId,
|
|
129
|
+
status,
|
|
130
|
+
idempotencyKey: request.idempotencyKey,
|
|
131
|
+
...errorCode === void 0 ? {} : { metadata: { errorCode } }
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
var UniversalRallyServer = class {
|
|
135
|
+
#config;
|
|
136
|
+
#persistence;
|
|
137
|
+
#options;
|
|
138
|
+
constructor(config, persistence, options = {}) {
|
|
139
|
+
this.#config = config;
|
|
140
|
+
this.#persistence = persistence;
|
|
141
|
+
this.#options = options;
|
|
142
|
+
}
|
|
143
|
+
/** Web Standard endpoint handler. Authentication, when configured, supplies the user identity. */
|
|
144
|
+
async handleCheckIn(request) {
|
|
145
|
+
const body = await requestBody(request);
|
|
146
|
+
const userId = await this.#authenticatedUser(request, body?.userId);
|
|
147
|
+
if (this.#options.authenticate !== void 0 && userId === null)
|
|
148
|
+
return json(
|
|
149
|
+
{ ok: false, error: { code: "UNAUTHORIZED", message: "Authentication is required." } },
|
|
150
|
+
401
|
|
151
|
+
);
|
|
152
|
+
if (body === null || userId === null || body.rallyId !== this.#config.id || body.spotId === "" || body.idempotencyKey === "")
|
|
153
|
+
return json(
|
|
154
|
+
{
|
|
155
|
+
ok: false,
|
|
156
|
+
error: {
|
|
157
|
+
code: "INVALID_REQUEST",
|
|
158
|
+
message: "rallyId, spotId, and idempotencyKey are required."
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
400
|
|
162
|
+
);
|
|
163
|
+
if (body.context === void 0)
|
|
164
|
+
return json(
|
|
165
|
+
{ ok: false, error: { code: "INVALID_REQUEST", message: "context is required." } },
|
|
166
|
+
400
|
|
167
|
+
);
|
|
168
|
+
const result = await this.checkIn({ ...body, userId });
|
|
169
|
+
return result.ok ? json(result) : json(result, result.code === "SPOT_NOT_FOUND" ? 404 : 422);
|
|
170
|
+
}
|
|
171
|
+
async handleClaimReward(request) {
|
|
172
|
+
const body = await requestBody(request);
|
|
173
|
+
const userId = await this.#authenticatedUser(request, body?.userId);
|
|
174
|
+
if (this.#options.authenticate !== void 0 && userId === null)
|
|
175
|
+
return json(
|
|
176
|
+
{ ok: false, error: { code: "UNAUTHORIZED", message: "Authentication is required." } },
|
|
177
|
+
401
|
|
178
|
+
);
|
|
179
|
+
if (body === null || userId === null || body.rallyId !== this.#config.id || body.rewardId === "" || body.idempotencyKey === "")
|
|
180
|
+
return json(
|
|
181
|
+
{
|
|
182
|
+
ok: false,
|
|
183
|
+
error: {
|
|
184
|
+
code: "INVALID_REQUEST",
|
|
185
|
+
message: "rallyId, rewardId, and idempotencyKey are required."
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
400
|
|
189
|
+
);
|
|
190
|
+
const key = `claim-reward:${body.rallyId}:${userId}:${body.rewardId}:${body.idempotencyKey}`;
|
|
191
|
+
const previous = await this.#persistence.getIdempotentResult(key);
|
|
192
|
+
if (previous !== null) return json(previous, previous.ok ? 200 : 422);
|
|
193
|
+
const reward = this.#config.rewards.find((item) => item.id === body.rewardId);
|
|
194
|
+
if (reward === void 0) {
|
|
195
|
+
const failure = {
|
|
196
|
+
ok: false,
|
|
197
|
+
code: "REWARD_NOT_FOUND",
|
|
198
|
+
message: "Reward was not found."
|
|
199
|
+
};
|
|
200
|
+
return json(await this.#rememberClaim(key, failure, body, userId), 404);
|
|
201
|
+
}
|
|
202
|
+
const timestamp = body.now ?? this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
203
|
+
const current = await this.sync(body.rallyId, userId);
|
|
204
|
+
const currentReward = current.rewards?.find((item) => item.rewardId === reward.id) ?? {
|
|
205
|
+
rewardId: reward.id,
|
|
206
|
+
status: "LOCKED"
|
|
207
|
+
};
|
|
208
|
+
const userClaims = await this.#getUserClaimCount(userId, reward.id);
|
|
209
|
+
const local = core.consumeReward({
|
|
210
|
+
reward: adminReward(reward),
|
|
211
|
+
currentState: currentReward,
|
|
212
|
+
now: timestamp,
|
|
213
|
+
userId,
|
|
214
|
+
userRedemptionCount: userClaims,
|
|
215
|
+
...body.staffPasscode === void 0 ? {} : { inputPasscode: body.staffPasscode },
|
|
216
|
+
...body.staffId === void 0 ? {} : { staffId: body.staffId }
|
|
217
|
+
});
|
|
218
|
+
if (!local.ok)
|
|
219
|
+
return json(
|
|
220
|
+
await this.#rememberClaim(
|
|
221
|
+
key,
|
|
222
|
+
{
|
|
223
|
+
ok: false,
|
|
224
|
+
code: local.error.code,
|
|
225
|
+
message: "Reward cannot be claimed.",
|
|
226
|
+
error: local.error
|
|
227
|
+
},
|
|
228
|
+
body,
|
|
229
|
+
userId
|
|
230
|
+
),
|
|
231
|
+
422
|
|
232
|
+
);
|
|
233
|
+
const stock = await this.#persistence.decrementRewardStock(reward.id);
|
|
234
|
+
if (!stock.success)
|
|
235
|
+
return json(
|
|
236
|
+
await this.#rememberClaim(
|
|
237
|
+
key,
|
|
238
|
+
{ ok: false, code: "OUT_OF_STOCK", message: "Reward is out of stock." },
|
|
239
|
+
body,
|
|
240
|
+
userId
|
|
241
|
+
),
|
|
242
|
+
422
|
|
243
|
+
);
|
|
244
|
+
const next = {
|
|
245
|
+
...current,
|
|
246
|
+
rewards: (current.rewards ?? []).map(
|
|
247
|
+
(item) => item.rewardId === reward.id ? local.value : item
|
|
248
|
+
),
|
|
249
|
+
updatedAt: timestamp
|
|
250
|
+
};
|
|
251
|
+
await this.#persistence.saveUserState(body.rallyId, userId, next);
|
|
252
|
+
const success = local.value.claimTicketNumber === void 0 ? { ok: true, state: next } : { ok: true, state: next, claimTicketNumber: local.value.claimTicketNumber };
|
|
253
|
+
await this.#persistence.saveIdempotentResult(
|
|
254
|
+
key,
|
|
255
|
+
success,
|
|
256
|
+
this.#options.idempotencyTtlMs ?? 864e5
|
|
257
|
+
);
|
|
258
|
+
await this.#persistence.recordAuditLog({
|
|
259
|
+
id: `audit-${globalThis.crypto?.randomUUID?.() ?? Date.now()}`,
|
|
260
|
+
timestamp,
|
|
261
|
+
rallyId: body.rallyId,
|
|
262
|
+
userId,
|
|
263
|
+
action: "CLAIM_REWARD",
|
|
264
|
+
resourceId: reward.id,
|
|
265
|
+
status: "SUCCESS",
|
|
266
|
+
idempotencyKey: body.idempotencyKey
|
|
267
|
+
});
|
|
268
|
+
return json(success);
|
|
269
|
+
}
|
|
270
|
+
async handleSync(request) {
|
|
271
|
+
const body = await requestBody(request);
|
|
272
|
+
const userId = await this.#authenticatedUser(request, body?.userId);
|
|
273
|
+
if (this.#options.authenticate !== void 0 && userId === null)
|
|
274
|
+
return json(
|
|
275
|
+
{ ok: false, error: { code: "UNAUTHORIZED", message: "Authentication is required." } },
|
|
276
|
+
401
|
|
277
|
+
);
|
|
278
|
+
if (body === null || userId === null || body.rallyId !== this.#config.id)
|
|
279
|
+
return json(
|
|
280
|
+
{ ok: false, error: { code: "INVALID_REQUEST", message: "rallyId is required." } },
|
|
281
|
+
400
|
|
282
|
+
);
|
|
283
|
+
return json({ ok: true, state: await this.sync(body.rallyId, userId) });
|
|
284
|
+
}
|
|
285
|
+
async handle(request) {
|
|
286
|
+
if (request.method !== "POST")
|
|
287
|
+
return json({ ok: false, error: { code: "METHOD_NOT_ALLOWED" } }, 405);
|
|
288
|
+
const path = new URL(request.url).pathname;
|
|
289
|
+
if (path.endsWith("/check-in")) return this.handleCheckIn(request);
|
|
290
|
+
if (path.endsWith("/claim-reward")) return this.handleClaimReward(request);
|
|
291
|
+
if (path.endsWith("/sync")) return this.handleSync(request);
|
|
292
|
+
return json({ ok: false, error: { code: "NOT_FOUND" } }, 404);
|
|
293
|
+
}
|
|
294
|
+
async checkIn(request) {
|
|
295
|
+
const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;
|
|
296
|
+
const previous = await this.#persistence.getIdempotentResult(key);
|
|
297
|
+
if (previous !== null) return previous;
|
|
298
|
+
const lockKey = `state:${request.rallyId}:${request.userId}`;
|
|
299
|
+
const locked = await this.#persistence.acquireLock(lockKey, this.#options.lockTtlMs ?? 5e3);
|
|
300
|
+
if (!locked)
|
|
301
|
+
return { ok: false, code: "CONFLICT", message: "The user state is being updated." };
|
|
302
|
+
const now2 = request.now ?? this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
303
|
+
try {
|
|
304
|
+
const spot = this.#config.spots.find((item) => item.id === request.spotId);
|
|
305
|
+
if (spot === void 0)
|
|
306
|
+
return this.#remember(
|
|
307
|
+
key,
|
|
308
|
+
{ ok: false, code: "SPOT_NOT_FOUND", message: "Spot was not found." },
|
|
309
|
+
request,
|
|
310
|
+
now2
|
|
311
|
+
);
|
|
312
|
+
const current = await this.#persistence.getUserState(request.rallyId, request.userId) ?? initialState(this.#config, now2);
|
|
313
|
+
if (current.records.some((record) => record.stampId === request.spotId))
|
|
314
|
+
return this.#remember(
|
|
315
|
+
key,
|
|
316
|
+
{ ok: false, code: "ALREADY_CLAIMED", message: "Spot was already claimed." },
|
|
317
|
+
request,
|
|
318
|
+
now2
|
|
319
|
+
);
|
|
320
|
+
const acquired = new Set(current.records.map((record) => record.stampId));
|
|
321
|
+
if (spot.prerequisites?.some((id2) => !acquired.has(id2)))
|
|
322
|
+
return this.#remember(
|
|
323
|
+
key,
|
|
324
|
+
{
|
|
325
|
+
ok: false,
|
|
326
|
+
code: "PREREQUISITES_NOT_MET",
|
|
327
|
+
message: "Prerequisite spots are not complete."
|
|
328
|
+
},
|
|
329
|
+
request,
|
|
330
|
+
now2
|
|
331
|
+
);
|
|
332
|
+
if (!spot.conditions.every(
|
|
333
|
+
(condition) => matchesCondition(condition, request.context, this.#options.customValidators)
|
|
334
|
+
))
|
|
335
|
+
return this.#remember(
|
|
336
|
+
key,
|
|
337
|
+
{ ok: false, code: "INVALID_PROOF", message: "Verification failed." },
|
|
338
|
+
request,
|
|
339
|
+
now2
|
|
340
|
+
);
|
|
341
|
+
const state = {
|
|
342
|
+
...current,
|
|
343
|
+
records: [...current.records, { stampId: request.spotId, acquiredAt: now2 }],
|
|
344
|
+
updatedAt: now2
|
|
345
|
+
};
|
|
346
|
+
await this.#persistence.saveUserState(request.rallyId, request.userId, state);
|
|
347
|
+
return this.#remember(key, { ok: true, state }, request, now2);
|
|
348
|
+
} finally {
|
|
349
|
+
await this.#persistence.releaseLock(lockKey);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async sync(rallyId, userId) {
|
|
353
|
+
const now2 = this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
354
|
+
return await this.#persistence.getUserState(rallyId, userId) ?? initialState(this.#config, now2);
|
|
355
|
+
}
|
|
356
|
+
async #authenticatedUser(request, requestedUserId) {
|
|
357
|
+
const authenticated = await this.#options.authenticate?.(request);
|
|
358
|
+
if (this.#options.authenticate !== void 0) return authenticated ?? null;
|
|
359
|
+
return requestedUserId ?? null;
|
|
360
|
+
}
|
|
361
|
+
async #getUserClaimCount(userId, rewardId) {
|
|
362
|
+
const state = await this.#persistence.getUserState(this.#config.id, userId);
|
|
363
|
+
return state?.rewards?.find((item) => item.rewardId === rewardId)?.userRedemptionCount ?? 0;
|
|
364
|
+
}
|
|
365
|
+
async #rememberClaim(key, result, request, userId) {
|
|
366
|
+
await this.#persistence.saveIdempotentResult(
|
|
367
|
+
key,
|
|
368
|
+
result,
|
|
369
|
+
this.#options.idempotencyTtlMs ?? 864e5
|
|
370
|
+
);
|
|
371
|
+
await this.#persistence.recordAuditLog({
|
|
372
|
+
id: `audit-${globalThis.crypto?.randomUUID?.() ?? Date.now()}`,
|
|
373
|
+
timestamp: request.now ?? this.#options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
374
|
+
rallyId: request.rallyId,
|
|
375
|
+
userId,
|
|
376
|
+
action: "CLAIM_REWARD",
|
|
377
|
+
resourceId: request.rewardId,
|
|
378
|
+
status: "REJECTED",
|
|
379
|
+
idempotencyKey: request.idempotencyKey
|
|
380
|
+
});
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
async #remember(key, result, request, now2) {
|
|
384
|
+
await this.#persistence.recordAuditLog(
|
|
385
|
+
audit(request, result.ok ? "SUCCESS" : "REJECTED", now2, result.ok ? void 0 : result.code)
|
|
386
|
+
);
|
|
387
|
+
await this.#persistence.saveIdempotentResult(
|
|
388
|
+
key,
|
|
389
|
+
result,
|
|
390
|
+
this.#options.idempotencyTtlMs ?? 864e5
|
|
391
|
+
);
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
5
396
|
// src/index.ts
|
|
6
397
|
var InMemoryServerStorage = class {
|
|
7
398
|
#states = /* @__PURE__ */ new Map();
|
|
@@ -369,7 +760,9 @@ function emptyState(config, timestamp) {
|
|
|
369
760
|
};
|
|
370
761
|
}
|
|
371
762
|
|
|
763
|
+
exports.InMemoryServerPersistenceAdapter = InMemoryServerPersistenceAdapter;
|
|
372
764
|
exports.InMemoryServerStorage = InMemoryServerStorage;
|
|
373
765
|
exports.StampRallyServer = StampRallyServer;
|
|
766
|
+
exports.UniversalRallyServer = UniversalRallyServer;
|
|
374
767
|
//# sourceMappingURL=index.cjs.map
|
|
375
768
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["processStamp","createSecureToken","consumeReward","reconcileRewardStates"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n consumeReward,\n createSecureToken,\n processStamp,\n type RallyConfig,\n type RewardConsumeError,\n reconcileRewardStates,\n type SecureTokenSecretKey,\n type StampError,\n type StampRallyState,\n type StampRecord,\n type VerificationContext,\n} from \"@stamprally/core\";\n\nexport interface UserRallyState extends StampRallyState {\n readonly userId?: string;\n}\n\nexport interface RallyAuditLog {\n readonly id: string;\n readonly timestamp: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly action: \"CHECK_IN\" | \"CLAIM_REWARD\";\n readonly resourceId: string;\n readonly status: \"SUCCESS\" | \"REJECTED\";\n readonly idempotencyKey: string;\n readonly proofData?: unknown;\n readonly metadata?: Readonly<Record<string, unknown>>;\n}\n\nexport interface ServerStorageAdapter {\n getRewardStock(rewardId: string): Promise<number | null>;\n decrementRewardStock(rewardId: string): Promise<boolean>;\n getUserClaims(userId: string, rewardId: string): Promise<number>;\n recordAuditLog(log: RallyAuditLog): Promise<void>;\n saveUserState(userId: string, state: UserRallyState): Promise<void>;\n getUserState(userId: string): Promise<UserRallyState | null>;\n}\n\nexport interface AdminRallyConfig extends RallyConfig {\n readonly secretKey: SecureTokenSecretKey;\n readonly proofTtlSeconds?: number;\n readonly authenticate?: (request: Request) => Promise<string | null> | string | null;\n}\n\nexport interface CheckInRequest {\n readonly userId: string;\n readonly spotId: string;\n readonly claimMethod: string;\n readonly proofData?: unknown;\n readonly idempotencyKey: string;\n}\n\nexport interface ClaimRewardRequest {\n readonly userId: string;\n readonly rewardId: string;\n readonly staffPasscode?: string;\n readonly idempotencyKey: string;\n readonly staffId?: string;\n}\n\nexport interface SyncRequest {\n readonly userId: string;\n readonly queue?: ReadonlyArray<SyncCheckInOperation>;\n readonly operations?: ReadonlyArray<SyncCheckInOperation>;\n}\n\nexport interface SyncCheckInOperation {\n readonly userId?: string;\n readonly spotId?: string;\n readonly stampId?: string;\n readonly claimMethod?: string;\n readonly proofData?: unknown;\n readonly context?: VerificationContext;\n readonly idempotencyKey: string;\n}\n\nexport interface StampClaimProof {\n readonly token: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly spotId: string;\n readonly acquiredAt: string;\n}\n\nexport interface InMemoryServerStorageOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\n\nexport class InMemoryServerStorage implements ServerStorageAdapter {\n readonly #states = new Map<string, UserRallyState>();\n readonly #stocks: Map<string, number>;\n readonly #claims = new Map<string, number>();\n readonly #auditLogs: RallyAuditLog[] = [];\n\n constructor(options: InMemoryServerStorageOptions = {}) {\n this.#stocks = new Map(Object.entries(options.stocks ?? {}));\n }\n\n async getRewardStock(rewardId: string): Promise<number | null> {\n return this.#stocks.get(rewardId) ?? null;\n }\n\n async decrementRewardStock(rewardId: string): Promise<boolean> {\n const stock = this.#stocks.get(rewardId);\n if (stock === undefined) return true;\n if (stock <= 0) return false;\n this.#stocks.set(rewardId, stock - 1);\n return true;\n }\n\n async getUserClaims(userId: string, rewardId: string): Promise<number> {\n return this.#claims.get(`${userId}:${rewardId}`) ?? 0;\n }\n\n async recordAuditLog(log: RallyAuditLog): Promise<void> {\n this.#auditLogs.push({ ...log });\n }\n\n async saveUserState(userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(userId, cloneUserState(state));\n }\n\n async getUserState(userId: string): Promise<UserRallyState | null> {\n const state = this.#states.get(userId);\n return state === undefined ? null : cloneUserState(state);\n }\n\n getAuditLogs(): ReadonlyArray<RallyAuditLog> {\n return this.#auditLogs.map((log) => ({ ...log }));\n }\n\n recordClaim(userId: string, rewardId: string): void {\n const key = `${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n }\n}\n\nfunction cloneUserState(state: UserRallyState): UserRallyState {\n return {\n ...state,\n records: state.records.map((record) => ({\n ...record,\n ...(record.metadata === undefined ? {} : { metadata: { ...record.metadata } }),\n })),\n ...(state.rewards === undefined\n ? {}\n : { rewards: state.rewards.map((reward) => ({ ...reward })) }),\n };\n}\n\nfunction jsonResponse(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"content-type\": \"application/json; charset=utf-8\" },\n });\n}\n\nfunction errorResponse(code: string, message: string, status: number): Response {\n return jsonResponse({ ok: false, error: { code, message } }, status);\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction contextForClaim(method: string, proofData: unknown): VerificationContext {\n if (method === \"token\" || method === \"qr\" || method === \"passcode\") {\n return {\n type: \"token\",\n token:\n isObject(proofData) && typeof proofData.token === \"string\"\n ? proofData.token\n : String(proofData ?? \"\"),\n };\n }\n if (method === \"geo\" || method === \"geolocation\") {\n const value = isObject(proofData) ? proofData : {};\n return {\n type: \"geo\",\n currentLatitude:\n typeof value.latitude === \"number\"\n ? value.latitude\n : typeof value.currentLatitude === \"number\"\n ? value.currentLatitude\n : Number.NaN,\n currentLongitude:\n typeof value.longitude === \"number\"\n ? value.longitude\n : typeof value.currentLongitude === \"number\"\n ? value.currentLongitude\n : Number.NaN,\n };\n }\n return { type: \"instant\" };\n}\n\nfunction now(): string {\n return new Date().toISOString();\n}\n\nfunction hasText(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\nfunction safeProofData(value: unknown): unknown {\n return isObject(value) && typeof value.token === \"string\" ? { type: \"token\" } : value;\n}\n\nfunction proofFromContext(context: VerificationContext | undefined): unknown {\n if (context?.type === \"token\") return { token: context.token };\n if (context?.type === \"geo\") {\n return { latitude: context.currentLatitude, longitude: context.currentLongitude };\n }\n return undefined;\n}\n\nfunction id(prefix: string): string {\n return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;\n}\n\nexport class StampRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #storage: ServerStorageAdapter;\n readonly #idempotent = new Map<string, Response>();\n readonly #claims = new Map<string, number>();\n #queue: Promise<unknown> = Promise.resolve();\n\n constructor(config: AdminRallyConfig, storage: ServerStorageAdapter) {\n this.#config = config;\n this.#storage = storage;\n }\n\n handle(request: Request): Promise<Response> {\n const path = new URL(request.url).pathname;\n if (request.method !== \"POST\")\n return Promise.resolve(errorResponse(\"METHOD_NOT_ALLOWED\", \"POST is required.\", 405));\n if (path.endsWith(\"/check-in\")) return this.verifyCheckIn(request);\n if (path.endsWith(\"/claim-reward\")) return this.claimReward(request);\n if (path.endsWith(\"/sync\")) return this.syncProgress(request);\n return Promise.resolve(errorResponse(\"NOT_FOUND\", \"Route not found.\", 404));\n }\n\n verifyCheckIn(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#verifyCheckIn(request));\n }\n\n claimReward(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#claimReward(request));\n }\n\n syncProgress(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#syncProgress(request));\n }\n\n #enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const next = this.#queue.then(operation, operation);\n this.#queue = next.then(\n () => undefined,\n () => undefined,\n );\n return next;\n }\n\n async #authenticate(request: Request): Promise<string | null> {\n return this.#config.authenticate === undefined ? null : this.#config.authenticate(request);\n }\n\n async #parse<T>(request: Request): Promise<T | null> {\n try {\n const value: unknown = await request.json();\n return isObject(value) ? (value as T) : null;\n } catch {\n return null;\n }\n }\n\n async #verifyCheckIn(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<CheckInRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.spotId) ||\n !hasText(body.claimMethod) ||\n !hasText(body.idempotencyKey)\n ) {\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, spotId, and idempotencyKey are required.\",\n 400,\n );\n }\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `check-in:${userId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const result = processStamp(\n current,\n this.#config,\n body.spotId,\n contextForClaim(body.claimMethod, body.proofData),\n timestamp,\n );\n if (!result.ok) {\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"REJECTED\",\n safeProofData(body.proofData),\n result.error,\n );\n return this.#remember(key, jsonResponse({ ok: false, error: result.error }, 422));\n }\n const ttl = this.#config.proofTtlSeconds ?? 3600;\n const token = await createSecureToken(\n {\n type: \"stamp_claim\",\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n exp: Math.floor(Date.now() / 1000) + ttl,\n },\n this.#config.secretKey,\n { encrypt: true },\n );\n await this.#storage.saveUserState(userId, result.value.nextState);\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"SUCCESS\",\n safeProofData(body.proofData),\n );\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: result.value.nextState,\n proof: {\n token,\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n },\n }),\n );\n }\n\n async #claimReward(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<ClaimRewardRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.rewardId) ||\n !hasText(body.idempotencyKey)\n )\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, rewardId, and idempotencyKey are required.\",\n 400,\n );\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `claim-reward:${userId}:${body.rewardId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const reward = this.#config.rewards?.find((item) => item.id === body.rewardId);\n if (reward === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", body.rewardId, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"REWARD_NOT_FOUND\", \"Reward was not found.\", 404));\n }\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const rewardState = current.rewards?.find((item) => item.rewardId === reward.id);\n if (rewardState === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"NOT_AVAILABLE\", \"Reward is not available.\", 422));\n }\n const userClaims = Math.max(\n await this.#storage.getUserClaims(userId, reward.id),\n this.#claims.get(`${userId}:${reward.id}`) ?? 0,\n );\n const stock = await this.#storage.getRewardStock(reward.id);\n const userLimit = reward.userClaimLimit ?? reward.limitPerUser;\n const canReclaimServerReward =\n reward.redemptionMethod === \"server_claim\" &&\n (userLimit === undefined || userClaims < userLimit) &&\n (stock === null || stock > 0);\n const claimableState =\n canReclaimServerReward && rewardState.status === \"CONSUMED\"\n ? { ...rewardState, status: \"AVAILABLE\" as const }\n : rewardState;\n const local = consumeReward({\n reward,\n currentState: claimableState,\n now: timestamp,\n ...(body.staffPasscode === undefined ? {} : { inputPasscode: body.staffPasscode }),\n ...(body.staffId === undefined ? {} : { staffId: body.staffId }),\n userId,\n userRedemptionCount: userClaims,\n });\n if (!local.ok)\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, local.error),\n );\n if (stock !== null && !(await this.#storage.decrementRewardStock(reward.id)))\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, {\n code: \"OUT_OF_STOCK\",\n rewardId: reward.id,\n }),\n );\n const nextState: StampRallyState = {\n ...current,\n rewards: (current.rewards ?? []).map((item) =>\n item.rewardId === reward.id ? local.value : item,\n ),\n updatedAt: timestamp,\n };\n await this.#storage.saveUserState(userId, nextState);\n const claimKey = `${userId}:${reward.id}`;\n this.#claims.set(claimKey, userClaims + 1);\n if (this.#storage instanceof InMemoryServerStorage)\n this.#storage.recordClaim(userId, reward.id);\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"SUCCESS\", {\n staffId: body.staffId,\n });\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: nextState,\n claimTicketNumber: local.value.claimTicketNumber,\n }),\n );\n }\n\n async #rewardError(\n userId: string,\n rewardId: string,\n key: string,\n error: RewardConsumeError,\n ): Promise<Response> {\n await this.#audit(userId, \"CLAIM_REWARD\", rewardId, key, \"REJECTED\", undefined, error);\n return jsonResponse({ ok: false, error }, 422);\n }\n\n async #syncProgress(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<SyncRequest>(request);\n if (body === null || body.userId === undefined)\n return errorResponse(\"INVALID_REQUEST\", \"userId is required.\", 400);\n if (authenticatedUser !== null && authenticatedUser !== body.userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const queue = body.queue ?? body.operations ?? [];\n for (const operation of queue) {\n const userId = operation.userId ?? body.userId;\n const spotId = operation.spotId ?? operation.stampId;\n const claimMethod = operation.claimMethod ?? operation.context?.type;\n if (!hasText(userId) || !hasText(spotId) || !hasText(claimMethod)) continue;\n const synthetic = new Request(new URL(\"/api/check-in\", request.url), {\n method: \"POST\",\n body: JSON.stringify({\n userId,\n spotId,\n claimMethod,\n proofData: operation.proofData ?? proofFromContext(operation.context),\n idempotencyKey: operation.idempotencyKey,\n }),\n headers: { \"content-type\": \"application/json\" },\n });\n await this.#verifyCheckIn(synthetic);\n }\n const timestamp = now();\n const state =\n (await this.#storage.getUserState(body.userId)) ?? emptyState(this.#config, timestamp);\n await this.#storage.saveUserState(body.userId, state);\n return jsonResponse({ ok: true, state, accepted: queue.length });\n }\n\n async #audit(\n userId: string,\n action: RallyAuditLog[\"action\"],\n resourceId: string,\n idempotencyKey: string,\n status: RallyAuditLog[\"status\"],\n proofData?: unknown,\n error?: unknown,\n ): Promise<void> {\n await this.#storage.recordAuditLog({\n id: id(\"audit\"),\n timestamp: now(),\n rallyId: this.#config.id,\n userId,\n action,\n resourceId,\n status,\n idempotencyKey,\n ...(proofData === undefined ? {} : { proofData }),\n ...(error === undefined\n ? {}\n : {\n metadata: {\n errorCode: isObject(error) && typeof error.code === \"string\" ? error.code : \"UNKNOWN\",\n },\n }),\n });\n }\n\n #remember(key: string, response: Response): Response {\n this.#idempotent.set(key, response.clone());\n return response;\n }\n}\n\nfunction emptyState(config: RallyConfig, timestamp: string): UserRallyState {\n return {\n rallyId: config.id,\n records: [],\n ...(config.rewards === undefined\n ? {}\n : { rewards: reconcileRewardStates(config.rewards, [], 0, timestamp) }),\n updatedAt: timestamp,\n };\n}\n\nexport type { StampError, StampRecord };\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/persistence.ts","../src/universalServer.ts","../src/index.ts"],"names":["now","reconcileRewardStates","consumeReward","id","processStamp","createSecureToken"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { RallyAuditLog, UserRallyState } from \"./index.js\";\n\n/** Persistence contract for implementations backed by Redis, SQL, or another RDB. */\nexport interface ServerPersistenceAdapter {\n acquireLock(lockKey: string, ttlMs: number): Promise<boolean>;\n releaseLock(lockKey: string): Promise<void>;\n decrementRewardStock(rewardId: string): Promise<{ success: boolean; remainingStock: number }>;\n getIdempotentResult<T>(idempotencyKey: string): Promise<T | null>;\n saveIdempotentResult<T>(idempotencyKey: string, result: T, ttlMs: number): Promise<void>;\n getUserState(rallyId: string, userId: string): Promise<UserRallyState | null>;\n saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void>;\n recordAuditLog(log: RallyAuditLog): Promise<void>;\n}\n\ninterface TimedValue<T> {\n readonly value: T;\n readonly expiresAt: number;\n}\n\nexport interface InMemoryServerPersistenceOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\n\n/** Deterministic adapter for tests and small single-process deployments. */\nexport class InMemoryServerPersistenceAdapter implements ServerPersistenceAdapter {\n readonly #locks = new Map<string, number>();\n readonly #idempotent = new Map<string, TimedValue<unknown>>();\n readonly #states = new Map<string, UserRallyState>();\n readonly #stocks: Map<string, number>;\n readonly #auditLogs: RallyAuditLog[] = [];\n\n constructor(options: InMemoryServerPersistenceOptions = {}) {\n this.#stocks = new Map(Object.entries(options.stocks ?? {}));\n }\n\n async acquireLock(lockKey: string, ttlMs: number): Promise<boolean> {\n const now = Date.now();\n const expiresAt = this.#locks.get(lockKey);\n if (expiresAt !== undefined && expiresAt > now) return false;\n this.#locks.set(lockKey, now + Math.max(1, ttlMs));\n return true;\n }\n\n async releaseLock(lockKey: string): Promise<void> {\n this.#locks.delete(lockKey);\n }\n\n async decrementRewardStock(\n rewardId: string,\n ): Promise<{ success: boolean; remainingStock: number }> {\n const stock = this.#stocks.get(rewardId);\n if (stock === undefined) return { success: true, remainingStock: Number.POSITIVE_INFINITY };\n if (stock <= 0) return { success: false, remainingStock: 0 };\n const remainingStock = stock - 1;\n this.#stocks.set(rewardId, remainingStock);\n return { success: true, remainingStock };\n }\n\n async getIdempotentResult<T>(idempotencyKey: string): Promise<T | null> {\n const entry = this.#idempotent.get(idempotencyKey);\n if (entry === undefined) return null;\n if (entry.expiresAt <= Date.now()) {\n this.#idempotent.delete(idempotencyKey);\n return null;\n }\n return structuredClone(entry.value) as T;\n }\n\n async saveIdempotentResult<T>(idempotencyKey: string, result: T, ttlMs: number): Promise<void> {\n this.#idempotent.set(idempotencyKey, {\n value: structuredClone(result),\n expiresAt: Date.now() + Math.max(1, ttlMs),\n });\n }\n\n async getUserState(rallyId: string, userId: string): Promise<UserRallyState | null> {\n const state = this.#states.get(`${rallyId}:${userId}`);\n return state === undefined ? null : structuredClone(state);\n }\n\n async saveUserState(rallyId: string, userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(`${rallyId}:${userId}`, structuredClone(state));\n }\n\n async recordAuditLog(log: RallyAuditLog): Promise<void> {\n this.#auditLogs.push(structuredClone(log));\n }\n\n getAuditLogs(): ReadonlyArray<RallyAuditLog> {\n return structuredClone(this.#auditLogs);\n }\n}\n","import {\n type AdminRallyConfig,\n type AdminReward,\n consumeReward,\n type RewardItem,\n reconcileRewardStates,\n type StampRallyState,\n type VerificationCondition,\n} from \"@stamprally/core\";\nimport type { RallyAuditLog, UserRallyState } from \"./index.js\";\nimport type { ServerPersistenceAdapter } from \"./persistence.js\";\n\nexport type UniversalVerificationContext =\n | { readonly type: \"qr\"; readonly token: string }\n | { readonly type: \"passcode\"; readonly code: string }\n | { readonly type: \"gps\"; readonly latitude: number; readonly longitude: number }\n | { readonly type: \"custom\"; readonly value: unknown };\n\nexport interface UniversalCheckInRequest {\n readonly rallyId: string;\n readonly userId: string;\n readonly spotId: string;\n readonly context: UniversalVerificationContext;\n readonly idempotencyKey: string;\n readonly now?: string;\n}\n\nexport interface UniversalClaimRewardRequest {\n readonly rallyId: string;\n readonly userId: string;\n readonly rewardId: string;\n readonly idempotencyKey: string;\n readonly staffPasscode?: string;\n readonly staffId?: string;\n readonly now?: string;\n}\n\nexport type UniversalCheckInResult =\n | { readonly ok: true; readonly state: UserRallyState }\n | { readonly ok: false; readonly code: string; readonly message: string };\n\nexport interface UniversalRallyServerOptions {\n readonly idempotencyTtlMs?: number;\n readonly lockTtlMs?: number;\n readonly customValidators?: Readonly<\n Record<string, (value: unknown, condition: VerificationCondition) => boolean>\n >;\n readonly now?: () => string;\n /** Extract the authenticated identity; request-body userId is never trusted when this is set. */\n readonly authenticate?: (request: Request) => Promise<string | null> | string | null;\n}\n\nfunction distanceMeters(aLat: number, aLon: number, bLat: number, bLon: number): number {\n const radians = (degrees: number): number => (degrees * Math.PI) / 180;\n const dLat = radians(bLat - aLat);\n const dLon = radians(bLon - aLon);\n const latA = radians(aLat);\n const latB = radians(bLat);\n const value = Math.sin(dLat / 2) ** 2 + Math.cos(latA) * Math.cos(latB) * Math.sin(dLon / 2) ** 2;\n return 6_371_000 * 2 * Math.asin(Math.sqrt(Math.min(1, value)));\n}\n\nfunction initialState(config: AdminRallyConfig, now: string): UserRallyState {\n return {\n rallyId: config.id,\n records: [],\n rewards: reconcileRewardStates(\n config.rewards.map((reward) => ({ ...reward, description: reward.description ?? \"\" })),\n [],\n 0,\n now,\n ),\n updatedAt: now,\n };\n}\n\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"content-type\": \"application/json; charset=utf-8\" },\n });\n}\n\nfunction requestBody<T>(request: Request): Promise<T | null> {\n return request\n .json()\n .then((value: unknown) => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return null;\n return value as T;\n })\n .catch(() => null);\n}\n\nfunction adminReward(reward: AdminReward): RewardItem {\n return { ...reward, description: reward.description ?? \"\" };\n}\n\nfunction matchesCondition(\n condition: VerificationCondition,\n context: UniversalVerificationContext,\n customValidators: UniversalRallyServerOptions[\"customValidators\"],\n): boolean {\n switch (condition.type) {\n case \"qr\":\n return context.type === \"qr\" && context.token === condition.secretToken;\n case \"passcode\":\n return (\n context.type === \"passcode\" &&\n (condition.caseSensitive === false\n ? context.code.toLocaleLowerCase() === condition.code.toLocaleLowerCase()\n : context.code === condition.code)\n );\n case \"gps\":\n return (\n context.type === \"gps\" &&\n distanceMeters(\n condition.latitude,\n condition.longitude,\n context.latitude,\n context.longitude,\n ) <= condition.radiusMeters\n );\n case \"custom\":\n return (\n customValidators?.[condition.validatorName]?.(\n context.type === \"custom\" ? context.value : undefined,\n condition,\n ) ?? false\n );\n }\n}\n\nfunction audit(\n request: UniversalCheckInRequest,\n status: RallyAuditLog[\"status\"],\n now: string,\n errorCode?: string,\n): RallyAuditLog {\n return {\n id: `audit-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`,\n timestamp: now,\n rallyId: request.rallyId,\n userId: request.userId,\n action: \"CHECK_IN\",\n resourceId: request.spotId,\n status,\n idempotencyKey: request.idempotencyKey,\n ...(errorCode === undefined ? {} : { metadata: { errorCode } }),\n };\n}\n\n/** Atomic, adapter-backed check-in service for the universal model. */\nexport class UniversalRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #persistence: ServerPersistenceAdapter;\n readonly #options: UniversalRallyServerOptions;\n\n constructor(\n config: AdminRallyConfig,\n persistence: ServerPersistenceAdapter,\n options: UniversalRallyServerOptions = {},\n ) {\n this.#config = config;\n this.#persistence = persistence;\n this.#options = options;\n }\n\n /** Web Standard endpoint handler. Authentication, when configured, supplies the user identity. */\n async handleCheckIn(request: Request): Promise<Response> {\n const body = await requestBody<UniversalCheckInRequest>(request);\n const userId = await this.#authenticatedUser(request, body?.userId);\n if (this.#options.authenticate !== undefined && userId === null)\n return json(\n { ok: false, error: { code: \"UNAUTHORIZED\", message: \"Authentication is required.\" } },\n 401,\n );\n if (\n body === null ||\n userId === null ||\n body.rallyId !== this.#config.id ||\n body.spotId === \"\" ||\n body.idempotencyKey === \"\"\n )\n return json(\n {\n ok: false,\n error: {\n code: \"INVALID_REQUEST\",\n message: \"rallyId, spotId, and idempotencyKey are required.\",\n },\n },\n 400,\n );\n if (body.context === undefined)\n return json(\n { ok: false, error: { code: \"INVALID_REQUEST\", message: \"context is required.\" } },\n 400,\n );\n const result = await this.checkIn({ ...body, userId });\n return result.ok ? json(result) : json(result, result.code === \"SPOT_NOT_FOUND\" ? 404 : 422);\n }\n\n async handleClaimReward(request: Request): Promise<Response> {\n const body = await requestBody<UniversalClaimRewardRequest>(request);\n const userId = await this.#authenticatedUser(request, body?.userId);\n if (this.#options.authenticate !== undefined && userId === null)\n return json(\n { ok: false, error: { code: \"UNAUTHORIZED\", message: \"Authentication is required.\" } },\n 401,\n );\n if (\n body === null ||\n userId === null ||\n body.rallyId !== this.#config.id ||\n body.rewardId === \"\" ||\n body.idempotencyKey === \"\"\n )\n return json(\n {\n ok: false,\n error: {\n code: \"INVALID_REQUEST\",\n message: \"rallyId, rewardId, and idempotencyKey are required.\",\n },\n },\n 400,\n );\n const key = `claim-reward:${body.rallyId}:${userId}:${body.rewardId}:${body.idempotencyKey}`;\n const previous = await this.#persistence.getIdempotentResult<UniversalClaimRewardResponse>(key);\n if (previous !== null) return json(previous, previous.ok ? 200 : 422);\n const reward = this.#config.rewards.find((item) => item.id === body.rewardId);\n if (reward === undefined) {\n const failure: UniversalClaimRewardResponse = {\n ok: false,\n code: \"REWARD_NOT_FOUND\",\n message: \"Reward was not found.\",\n };\n return json(await this.#rememberClaim(key, failure, body, userId), 404);\n }\n const timestamp = body.now ?? this.#options.now?.() ?? new Date().toISOString();\n const current = await this.sync(body.rallyId, userId);\n const currentReward = current.rewards?.find((item) => item.rewardId === reward.id) ?? {\n rewardId: reward.id,\n status: \"LOCKED\" as const,\n };\n const userClaims = await this.#getUserClaimCount(userId, reward.id);\n const local = consumeReward({\n reward: adminReward(reward),\n currentState: currentReward,\n now: timestamp,\n userId,\n userRedemptionCount: userClaims,\n ...(body.staffPasscode === undefined ? {} : { inputPasscode: body.staffPasscode }),\n ...(body.staffId === undefined ? {} : { staffId: body.staffId }),\n });\n if (!local.ok)\n return json(\n await this.#rememberClaim(\n key,\n {\n ok: false,\n code: local.error.code,\n message: \"Reward cannot be claimed.\",\n error: local.error,\n },\n body,\n userId,\n ),\n 422,\n );\n const stock = await this.#persistence.decrementRewardStock(reward.id);\n if (!stock.success)\n return json(\n await this.#rememberClaim(\n key,\n { ok: false, code: \"OUT_OF_STOCK\", message: \"Reward is out of stock.\" },\n body,\n userId,\n ),\n 422,\n );\n const next: UserRallyState = {\n ...current,\n rewards: (current.rewards ?? []).map((item) =>\n item.rewardId === reward.id ? local.value : item,\n ),\n updatedAt: timestamp,\n };\n await this.#persistence.saveUserState(body.rallyId, userId, next);\n const success: UniversalClaimRewardResponse =\n local.value.claimTicketNumber === undefined\n ? { ok: true, state: next }\n : { ok: true, state: next, claimTicketNumber: local.value.claimTicketNumber };\n await this.#persistence.saveIdempotentResult(\n key,\n success,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n await this.#persistence.recordAuditLog({\n id: `audit-${globalThis.crypto?.randomUUID?.() ?? Date.now()}`,\n timestamp,\n rallyId: body.rallyId,\n userId,\n action: \"CLAIM_REWARD\",\n resourceId: reward.id,\n status: \"SUCCESS\",\n idempotencyKey: body.idempotencyKey,\n });\n return json(success);\n }\n\n async handleSync(request: Request): Promise<Response> {\n const body = await requestBody<{ readonly rallyId: string; readonly userId?: string }>(request);\n const userId = await this.#authenticatedUser(request, body?.userId);\n if (this.#options.authenticate !== undefined && userId === null)\n return json(\n { ok: false, error: { code: \"UNAUTHORIZED\", message: \"Authentication is required.\" } },\n 401,\n );\n if (body === null || userId === null || body.rallyId !== this.#config.id)\n return json(\n { ok: false, error: { code: \"INVALID_REQUEST\", message: \"rallyId is required.\" } },\n 400,\n );\n return json({ ok: true, state: await this.sync(body.rallyId, userId) });\n }\n\n async handle(request: Request): Promise<Response> {\n if (request.method !== \"POST\")\n return json({ ok: false, error: { code: \"METHOD_NOT_ALLOWED\" } }, 405);\n const path = new URL(request.url).pathname;\n if (path.endsWith(\"/check-in\")) return this.handleCheckIn(request);\n if (path.endsWith(\"/claim-reward\")) return this.handleClaimReward(request);\n if (path.endsWith(\"/sync\")) return this.handleSync(request);\n return json({ ok: false, error: { code: \"NOT_FOUND\" } }, 404);\n }\n\n async checkIn(request: UniversalCheckInRequest): Promise<UniversalCheckInResult> {\n const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;\n const previous = await this.#persistence.getIdempotentResult<UniversalCheckInResult>(key);\n if (previous !== null) return previous;\n const lockKey = `state:${request.rallyId}:${request.userId}`;\n const locked = await this.#persistence.acquireLock(lockKey, this.#options.lockTtlMs ?? 5000);\n if (!locked)\n return { ok: false, code: \"CONFLICT\", message: \"The user state is being updated.\" };\n const now = request.now ?? this.#options.now?.() ?? new Date().toISOString();\n try {\n const spot = this.#config.spots.find((item) => item.id === request.spotId);\n if (spot === undefined)\n return this.#remember(\n key,\n { ok: false, code: \"SPOT_NOT_FOUND\", message: \"Spot was not found.\" },\n request,\n now,\n );\n const current =\n (await this.#persistence.getUserState(request.rallyId, request.userId)) ??\n initialState(this.#config, now);\n if (current.records.some((record) => record.stampId === request.spotId))\n return this.#remember(\n key,\n { ok: false, code: \"ALREADY_CLAIMED\", message: \"Spot was already claimed.\" },\n request,\n now,\n );\n const acquired = new Set(current.records.map((record) => record.stampId));\n if (spot.prerequisites?.some((id) => !acquired.has(id)))\n return this.#remember(\n key,\n {\n ok: false,\n code: \"PREREQUISITES_NOT_MET\",\n message: \"Prerequisite spots are not complete.\",\n },\n request,\n now,\n );\n if (\n !spot.conditions.every((condition) =>\n matchesCondition(condition, request.context, this.#options.customValidators),\n )\n )\n return this.#remember(\n key,\n { ok: false, code: \"INVALID_PROOF\", message: \"Verification failed.\" },\n request,\n now,\n );\n const state: UserRallyState = {\n ...current,\n records: [...current.records, { stampId: request.spotId, acquiredAt: now }],\n updatedAt: now,\n };\n await this.#persistence.saveUserState(request.rallyId, request.userId, state);\n return this.#remember(key, { ok: true, state }, request, now);\n } finally {\n await this.#persistence.releaseLock(lockKey);\n }\n }\n\n async sync(rallyId: string, userId: string): Promise<UserRallyState> {\n const now = this.#options.now?.() ?? new Date().toISOString();\n return (\n (await this.#persistence.getUserState(rallyId, userId)) ?? initialState(this.#config, now)\n );\n }\n\n async #authenticatedUser(\n request: Request,\n requestedUserId: string | undefined,\n ): Promise<string | null> {\n const authenticated = await this.#options.authenticate?.(request);\n if (this.#options.authenticate !== undefined) return authenticated ?? null;\n return requestedUserId ?? null;\n }\n\n async #getUserClaimCount(userId: string, rewardId: string): Promise<number> {\n const state = await this.#persistence.getUserState(this.#config.id, userId);\n return state?.rewards?.find((item) => item.rewardId === rewardId)?.userRedemptionCount ?? 0;\n }\n\n async #rememberClaim(\n key: string,\n result: UniversalClaimRewardResponse,\n request: UniversalClaimRewardRequest,\n userId: string,\n ): Promise<UniversalClaimRewardResponse> {\n await this.#persistence.saveIdempotentResult(\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n await this.#persistence.recordAuditLog({\n id: `audit-${globalThis.crypto?.randomUUID?.() ?? Date.now()}`,\n timestamp: request.now ?? this.#options.now?.() ?? new Date().toISOString(),\n rallyId: request.rallyId,\n userId,\n action: \"CLAIM_REWARD\",\n resourceId: request.rewardId,\n status: \"REJECTED\",\n idempotencyKey: request.idempotencyKey,\n });\n return result;\n }\n\n async #remember(\n key: string,\n result: UniversalCheckInResult,\n request: UniversalCheckInRequest,\n now: string,\n ): Promise<UniversalCheckInResult> {\n await this.#persistence.recordAuditLog(\n audit(request, result.ok ? \"SUCCESS\" : \"REJECTED\", now, result.ok ? undefined : result.code),\n );\n await this.#persistence.saveIdempotentResult(\n key,\n result,\n this.#options.idempotencyTtlMs ?? 86_400_000,\n );\n return result;\n }\n}\n\ntype UniversalClaimRewardResponse =\n | { readonly ok: true; readonly state: UserRallyState; readonly claimTicketNumber?: string }\n | {\n readonly ok: false;\n readonly code: string;\n readonly message: string;\n readonly error?: unknown;\n };\n\nexport type { StampRallyState };\n","import {\n consumeReward,\n createSecureToken,\n processStamp,\n type RallyConfig,\n type RewardConsumeError,\n reconcileRewardStates,\n type SecureTokenSecretKey,\n type StampError,\n type StampRallyState,\n type StampRecord,\n type VerificationContext,\n} from \"@stamprally/core\";\n\nexport interface UserRallyState extends StampRallyState {\n readonly userId?: string;\n}\n\nexport interface RallyAuditLog {\n readonly id: string;\n readonly timestamp: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly action: \"CHECK_IN\" | \"CLAIM_REWARD\";\n readonly resourceId: string;\n readonly status: \"SUCCESS\" | \"REJECTED\";\n readonly idempotencyKey: string;\n readonly proofData?: unknown;\n readonly metadata?: Readonly<Record<string, unknown>>;\n}\n\nexport interface ServerStorageAdapter {\n getRewardStock(rewardId: string): Promise<number | null>;\n decrementRewardStock(rewardId: string): Promise<boolean>;\n getUserClaims(userId: string, rewardId: string): Promise<number>;\n recordAuditLog(log: RallyAuditLog): Promise<void>;\n saveUserState(userId: string, state: UserRallyState): Promise<void>;\n getUserState(userId: string): Promise<UserRallyState | null>;\n}\n\nexport interface AdminRallyConfig extends RallyConfig {\n readonly secretKey: SecureTokenSecretKey;\n readonly proofTtlSeconds?: number;\n readonly authenticate?: (request: Request) => Promise<string | null> | string | null;\n}\n\nexport interface CheckInRequest {\n readonly userId: string;\n readonly spotId: string;\n readonly claimMethod: string;\n readonly proofData?: unknown;\n readonly idempotencyKey: string;\n}\n\nexport interface ClaimRewardRequest {\n readonly userId: string;\n readonly rewardId: string;\n readonly staffPasscode?: string;\n readonly idempotencyKey: string;\n readonly staffId?: string;\n}\n\nexport interface SyncRequest {\n readonly userId: string;\n readonly queue?: ReadonlyArray<SyncCheckInOperation>;\n readonly operations?: ReadonlyArray<SyncCheckInOperation>;\n}\n\nexport interface SyncCheckInOperation {\n readonly userId?: string;\n readonly spotId?: string;\n readonly stampId?: string;\n readonly claimMethod?: string;\n readonly proofData?: unknown;\n readonly context?: VerificationContext;\n readonly idempotencyKey: string;\n}\n\nexport interface StampClaimProof {\n readonly token: string;\n readonly rallyId: string;\n readonly userId: string;\n readonly spotId: string;\n readonly acquiredAt: string;\n}\n\nexport interface InMemoryServerStorageOptions {\n readonly stocks?: Readonly<Record<string, number>>;\n}\n\nexport class InMemoryServerStorage implements ServerStorageAdapter {\n readonly #states = new Map<string, UserRallyState>();\n readonly #stocks: Map<string, number>;\n readonly #claims = new Map<string, number>();\n readonly #auditLogs: RallyAuditLog[] = [];\n\n constructor(options: InMemoryServerStorageOptions = {}) {\n this.#stocks = new Map(Object.entries(options.stocks ?? {}));\n }\n\n async getRewardStock(rewardId: string): Promise<number | null> {\n return this.#stocks.get(rewardId) ?? null;\n }\n\n async decrementRewardStock(rewardId: string): Promise<boolean> {\n const stock = this.#stocks.get(rewardId);\n if (stock === undefined) return true;\n if (stock <= 0) return false;\n this.#stocks.set(rewardId, stock - 1);\n return true;\n }\n\n async getUserClaims(userId: string, rewardId: string): Promise<number> {\n return this.#claims.get(`${userId}:${rewardId}`) ?? 0;\n }\n\n async recordAuditLog(log: RallyAuditLog): Promise<void> {\n this.#auditLogs.push({ ...log });\n }\n\n async saveUserState(userId: string, state: UserRallyState): Promise<void> {\n this.#states.set(userId, cloneUserState(state));\n }\n\n async getUserState(userId: string): Promise<UserRallyState | null> {\n const state = this.#states.get(userId);\n return state === undefined ? null : cloneUserState(state);\n }\n\n getAuditLogs(): ReadonlyArray<RallyAuditLog> {\n return this.#auditLogs.map((log) => ({ ...log }));\n }\n\n recordClaim(userId: string, rewardId: string): void {\n const key = `${userId}:${rewardId}`;\n this.#claims.set(key, (this.#claims.get(key) ?? 0) + 1);\n }\n}\n\nfunction cloneUserState(state: UserRallyState): UserRallyState {\n return {\n ...state,\n records: state.records.map((record) => ({\n ...record,\n ...(record.metadata === undefined ? {} : { metadata: { ...record.metadata } }),\n })),\n ...(state.rewards === undefined\n ? {}\n : { rewards: state.rewards.map((reward) => ({ ...reward })) }),\n };\n}\n\nfunction jsonResponse(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"content-type\": \"application/json; charset=utf-8\" },\n });\n}\n\nfunction errorResponse(code: string, message: string, status: number): Response {\n return jsonResponse({ ok: false, error: { code, message } }, status);\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction contextForClaim(method: string, proofData: unknown): VerificationContext {\n if (method === \"token\" || method === \"qr\" || method === \"passcode\") {\n return {\n type: \"token\",\n token:\n isObject(proofData) && typeof proofData.token === \"string\"\n ? proofData.token\n : String(proofData ?? \"\"),\n };\n }\n if (method === \"geo\" || method === \"geolocation\") {\n const value = isObject(proofData) ? proofData : {};\n return {\n type: \"geo\",\n currentLatitude:\n typeof value.latitude === \"number\"\n ? value.latitude\n : typeof value.currentLatitude === \"number\"\n ? value.currentLatitude\n : Number.NaN,\n currentLongitude:\n typeof value.longitude === \"number\"\n ? value.longitude\n : typeof value.currentLongitude === \"number\"\n ? value.currentLongitude\n : Number.NaN,\n };\n }\n return { type: \"instant\" };\n}\n\nfunction now(): string {\n return new Date().toISOString();\n}\n\nfunction hasText(value: unknown): value is string {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\nfunction safeProofData(value: unknown): unknown {\n return isObject(value) && typeof value.token === \"string\" ? { type: \"token\" } : value;\n}\n\nfunction proofFromContext(context: VerificationContext | undefined): unknown {\n if (context?.type === \"token\") return { token: context.token };\n if (context?.type === \"geo\") {\n return { latitude: context.currentLatitude, longitude: context.currentLongitude };\n }\n return undefined;\n}\n\nfunction id(prefix: string): string {\n return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;\n}\n\nexport class StampRallyServer {\n readonly #config: AdminRallyConfig;\n readonly #storage: ServerStorageAdapter;\n readonly #idempotent = new Map<string, Response>();\n readonly #claims = new Map<string, number>();\n #queue: Promise<unknown> = Promise.resolve();\n\n constructor(config: AdminRallyConfig, storage: ServerStorageAdapter) {\n this.#config = config;\n this.#storage = storage;\n }\n\n handle(request: Request): Promise<Response> {\n const path = new URL(request.url).pathname;\n if (request.method !== \"POST\")\n return Promise.resolve(errorResponse(\"METHOD_NOT_ALLOWED\", \"POST is required.\", 405));\n if (path.endsWith(\"/check-in\")) return this.verifyCheckIn(request);\n if (path.endsWith(\"/claim-reward\")) return this.claimReward(request);\n if (path.endsWith(\"/sync\")) return this.syncProgress(request);\n return Promise.resolve(errorResponse(\"NOT_FOUND\", \"Route not found.\", 404));\n }\n\n verifyCheckIn(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#verifyCheckIn(request));\n }\n\n claimReward(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#claimReward(request));\n }\n\n syncProgress(request: Request): Promise<Response> {\n return this.#enqueue(() => this.#syncProgress(request));\n }\n\n #enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const next = this.#queue.then(operation, operation);\n this.#queue = next.then(\n () => undefined,\n () => undefined,\n );\n return next;\n }\n\n async #authenticate(request: Request): Promise<string | null> {\n return this.#config.authenticate === undefined ? null : this.#config.authenticate(request);\n }\n\n async #parse<T>(request: Request): Promise<T | null> {\n try {\n const value: unknown = await request.json();\n return isObject(value) ? (value as T) : null;\n } catch {\n return null;\n }\n }\n\n async #verifyCheckIn(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<CheckInRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.spotId) ||\n !hasText(body.claimMethod) ||\n !hasText(body.idempotencyKey)\n ) {\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, spotId, and idempotencyKey are required.\",\n 400,\n );\n }\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `check-in:${userId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const result = processStamp(\n current,\n this.#config,\n body.spotId,\n contextForClaim(body.claimMethod, body.proofData),\n timestamp,\n );\n if (!result.ok) {\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"REJECTED\",\n safeProofData(body.proofData),\n result.error,\n );\n return this.#remember(key, jsonResponse({ ok: false, error: result.error }, 422));\n }\n const ttl = this.#config.proofTtlSeconds ?? 3600;\n const token = await createSecureToken(\n {\n type: \"stamp_claim\",\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n exp: Math.floor(Date.now() / 1000) + ttl,\n },\n this.#config.secretKey,\n { encrypt: true },\n );\n await this.#storage.saveUserState(userId, result.value.nextState);\n await this.#audit(\n userId,\n \"CHECK_IN\",\n body.spotId,\n body.idempotencyKey,\n \"SUCCESS\",\n safeProofData(body.proofData),\n );\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: result.value.nextState,\n proof: {\n token,\n rallyId: this.#config.id,\n userId,\n spotId: body.spotId,\n acquiredAt: timestamp,\n },\n }),\n );\n }\n\n async #claimReward(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<ClaimRewardRequest>(request);\n const userId = body?.userId ?? authenticatedUser;\n if (\n userId === null ||\n userId === undefined ||\n body === null ||\n !hasText(userId) ||\n !hasText(body.rewardId) ||\n !hasText(body.idempotencyKey)\n )\n return errorResponse(\n \"INVALID_REQUEST\",\n \"userId, rewardId, and idempotencyKey are required.\",\n 400,\n );\n if (authenticatedUser !== null && authenticatedUser !== userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const key = `claim-reward:${userId}:${body.rewardId}:${body.idempotencyKey}`;\n const previous = this.#idempotent.get(key);\n if (previous !== undefined) return previous.clone();\n const reward = this.#config.rewards?.find((item) => item.id === body.rewardId);\n if (reward === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", body.rewardId, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"REWARD_NOT_FOUND\", \"Reward was not found.\", 404));\n }\n const timestamp = now();\n const current =\n (await this.#storage.getUserState(userId)) ?? emptyState(this.#config, timestamp);\n const rewardState = current.rewards?.find((item) => item.rewardId === reward.id);\n if (rewardState === undefined) {\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"REJECTED\");\n return this.#remember(key, errorResponse(\"NOT_AVAILABLE\", \"Reward is not available.\", 422));\n }\n const userClaims = Math.max(\n await this.#storage.getUserClaims(userId, reward.id),\n this.#claims.get(`${userId}:${reward.id}`) ?? 0,\n );\n const stock = await this.#storage.getRewardStock(reward.id);\n const userLimit = reward.userClaimLimit ?? reward.limitPerUser;\n const canReclaimServerReward =\n reward.redemptionMethod === \"server_claim\" &&\n (userLimit === undefined || userClaims < userLimit) &&\n (stock === null || stock > 0);\n const claimableState =\n canReclaimServerReward && rewardState.status === \"CONSUMED\"\n ? { ...rewardState, status: \"AVAILABLE\" as const }\n : rewardState;\n const local = consumeReward({\n reward,\n currentState: claimableState,\n now: timestamp,\n ...(body.staffPasscode === undefined ? {} : { inputPasscode: body.staffPasscode }),\n ...(body.staffId === undefined ? {} : { staffId: body.staffId }),\n userId,\n userRedemptionCount: userClaims,\n });\n if (!local.ok)\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, local.error),\n );\n if (stock !== null && !(await this.#storage.decrementRewardStock(reward.id)))\n return this.#remember(\n key,\n await this.#rewardError(userId, reward.id, body.idempotencyKey, {\n code: \"OUT_OF_STOCK\",\n rewardId: reward.id,\n }),\n );\n const nextState: StampRallyState = {\n ...current,\n rewards: (current.rewards ?? []).map((item) =>\n item.rewardId === reward.id ? local.value : item,\n ),\n updatedAt: timestamp,\n };\n await this.#storage.saveUserState(userId, nextState);\n const claimKey = `${userId}:${reward.id}`;\n this.#claims.set(claimKey, userClaims + 1);\n if (this.#storage instanceof InMemoryServerStorage)\n this.#storage.recordClaim(userId, reward.id);\n await this.#audit(userId, \"CLAIM_REWARD\", reward.id, body.idempotencyKey, \"SUCCESS\", {\n staffId: body.staffId,\n });\n return this.#remember(\n key,\n jsonResponse({\n ok: true,\n state: nextState,\n claimTicketNumber: local.value.claimTicketNumber,\n }),\n );\n }\n\n async #rewardError(\n userId: string,\n rewardId: string,\n key: string,\n error: RewardConsumeError,\n ): Promise<Response> {\n await this.#audit(userId, \"CLAIM_REWARD\", rewardId, key, \"REJECTED\", undefined, error);\n return jsonResponse({ ok: false, error }, 422);\n }\n\n async #syncProgress(request: Request): Promise<Response> {\n const authenticatedUser = await this.#authenticate(request);\n const body = await this.#parse<SyncRequest>(request);\n if (body === null || body.userId === undefined)\n return errorResponse(\"INVALID_REQUEST\", \"userId is required.\", 400);\n if (authenticatedUser !== null && authenticatedUser !== body.userId)\n return errorResponse(\"UNAUTHORIZED\", \"User identity does not match authentication.\", 401);\n const queue = body.queue ?? body.operations ?? [];\n for (const operation of queue) {\n const userId = operation.userId ?? body.userId;\n const spotId = operation.spotId ?? operation.stampId;\n const claimMethod = operation.claimMethod ?? operation.context?.type;\n if (!hasText(userId) || !hasText(spotId) || !hasText(claimMethod)) continue;\n const synthetic = new Request(new URL(\"/api/check-in\", request.url), {\n method: \"POST\",\n body: JSON.stringify({\n userId,\n spotId,\n claimMethod,\n proofData: operation.proofData ?? proofFromContext(operation.context),\n idempotencyKey: operation.idempotencyKey,\n }),\n headers: { \"content-type\": \"application/json\" },\n });\n await this.#verifyCheckIn(synthetic);\n }\n const timestamp = now();\n const state =\n (await this.#storage.getUserState(body.userId)) ?? emptyState(this.#config, timestamp);\n await this.#storage.saveUserState(body.userId, state);\n return jsonResponse({ ok: true, state, accepted: queue.length });\n }\n\n async #audit(\n userId: string,\n action: RallyAuditLog[\"action\"],\n resourceId: string,\n idempotencyKey: string,\n status: RallyAuditLog[\"status\"],\n proofData?: unknown,\n error?: unknown,\n ): Promise<void> {\n await this.#storage.recordAuditLog({\n id: id(\"audit\"),\n timestamp: now(),\n rallyId: this.#config.id,\n userId,\n action,\n resourceId,\n status,\n idempotencyKey,\n ...(proofData === undefined ? {} : { proofData }),\n ...(error === undefined\n ? {}\n : {\n metadata: {\n errorCode: isObject(error) && typeof error.code === \"string\" ? error.code : \"UNKNOWN\",\n },\n }),\n });\n }\n\n #remember(key: string, response: Response): Response {\n this.#idempotent.set(key, response.clone());\n return response;\n }\n}\n\nfunction emptyState(config: RallyConfig, timestamp: string): UserRallyState {\n return {\n rallyId: config.id,\n records: [],\n ...(config.rewards === undefined\n ? {}\n : { rewards: reconcileRewardStates(config.rewards, [], 0, timestamp) }),\n updatedAt: timestamp,\n };\n}\n\nexport type { InMemoryServerPersistenceOptions, ServerPersistenceAdapter } from \"./persistence.js\";\nexport { InMemoryServerPersistenceAdapter } from \"./persistence.js\";\nexport type {\n UniversalCheckInRequest,\n UniversalCheckInResult,\n UniversalClaimRewardRequest,\n UniversalRallyServerOptions,\n UniversalVerificationContext,\n} from \"./universalServer.js\";\nexport { UniversalRallyServer } from \"./universalServer.js\";\nexport type { StampError, StampRecord };\n"]}
|