@stamprally/server 0.13.0 → 0.14.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 +253 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -2
- package/dist/index.d.ts +53 -2
- package/dist/index.js +251 -119
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -20,8 +20,8 @@ async function executeClaimRewardTransaction(database, store, params, mutation)
|
|
|
20
20
|
await store.writeIdempotency(transaction, params, next.result);
|
|
21
21
|
return { success: true };
|
|
22
22
|
});
|
|
23
|
-
} catch (
|
|
24
|
-
return { success: false, error:
|
|
23
|
+
} catch (error2) {
|
|
24
|
+
return { success: false, error: error2 instanceof Error ? error2.message : String(error2) };
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -138,8 +138,41 @@ var InMemoryServerPersistenceAdapter = class {
|
|
|
138
138
|
);
|
|
139
139
|
return { success: true };
|
|
140
140
|
});
|
|
141
|
-
} catch (
|
|
142
|
-
return { success: false, error:
|
|
141
|
+
} catch (error2) {
|
|
142
|
+
return { success: false, error: error2 instanceof Error ? error2.message : String(error2) };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async executeCheckInTransaction(params, mutation) {
|
|
146
|
+
try {
|
|
147
|
+
return await this.runTransaction(params.rallyId, async () => {
|
|
148
|
+
const userState = await this.getUserState(params.rallyId, params.userId) ?? params.initialUserState;
|
|
149
|
+
if (userState === void 0)
|
|
150
|
+
return { success: false, error: "A user state is required for this transaction." };
|
|
151
|
+
const mutationResult = mutation({ userState });
|
|
152
|
+
if (mutationResult.error !== void 0) {
|
|
153
|
+
await this.recordAuditLog(mutationResult.auditLog);
|
|
154
|
+
if (params.idempotencyKey !== void 0 && mutationResult.result !== void 0)
|
|
155
|
+
await this.saveIdempotentResult(
|
|
156
|
+
params.rallyId,
|
|
157
|
+
params.idempotencyKey,
|
|
158
|
+
mutationResult.result,
|
|
159
|
+
params.idempotencyTtlMs ?? 864e5
|
|
160
|
+
);
|
|
161
|
+
return { success: false, error: mutationResult.error };
|
|
162
|
+
}
|
|
163
|
+
await this.saveUserState(params.rallyId, params.userId, mutationResult.nextUserState);
|
|
164
|
+
await this.recordAuditLog(mutationResult.auditLog);
|
|
165
|
+
if (params.idempotencyKey !== void 0 && mutationResult.result !== void 0)
|
|
166
|
+
await this.saveIdempotentResult(
|
|
167
|
+
params.rallyId,
|
|
168
|
+
params.idempotencyKey,
|
|
169
|
+
mutationResult.result,
|
|
170
|
+
params.idempotencyTtlMs ?? 864e5
|
|
171
|
+
);
|
|
172
|
+
return { success: true };
|
|
173
|
+
});
|
|
174
|
+
} catch (error2) {
|
|
175
|
+
return { success: false, error: error2 instanceof Error ? error2.message : String(error2) };
|
|
143
176
|
}
|
|
144
177
|
}
|
|
145
178
|
async rollbackUserState(rallyId, userId, previousState) {
|
|
@@ -193,7 +226,7 @@ var InMemoryServerPersistenceAdapter = class {
|
|
|
193
226
|
if (count <= 1) this.#claims.delete(key);
|
|
194
227
|
else this.#claims.set(key, count - 1);
|
|
195
228
|
const index = this.#claimRecords.findIndex(
|
|
196
|
-
(
|
|
229
|
+
(record2) => record2.rallyId === rallyId && record2.userId === userId && record2.rewardId === rewardId && record2.ticketNumber === ticketNumber
|
|
197
230
|
);
|
|
198
231
|
if (index >= 0) this.#claimRecords.splice(index, 1);
|
|
199
232
|
}
|
|
@@ -225,7 +258,7 @@ var InMemoryServerPersistenceAdapter = class {
|
|
|
225
258
|
};
|
|
226
259
|
try {
|
|
227
260
|
return await operation(this);
|
|
228
|
-
} catch (
|
|
261
|
+
} catch (error2) {
|
|
229
262
|
this.#stocks.clear();
|
|
230
263
|
for (const [key, value] of snapshot.stocks) this.#stocks.set(key, value);
|
|
231
264
|
this.#idempotent.clear();
|
|
@@ -237,10 +270,63 @@ var InMemoryServerPersistenceAdapter = class {
|
|
|
237
270
|
for (const [key, value] of snapshot.claims) this.#claims.set(key, value);
|
|
238
271
|
this.#claimRecords.splice(0, this.#claimRecords.length, ...snapshot.claimRecords);
|
|
239
272
|
this.#auditLogs.splice(0, this.#auditLogs.length, ...snapshot.auditLogs);
|
|
240
|
-
throw
|
|
273
|
+
throw error2;
|
|
241
274
|
}
|
|
242
275
|
}
|
|
243
276
|
};
|
|
277
|
+
|
|
278
|
+
// src/security.ts
|
|
279
|
+
function error(path, message) {
|
|
280
|
+
return { success: false, errors: [{ path, message, code: "invalid_request" }] };
|
|
281
|
+
}
|
|
282
|
+
function record(value) {
|
|
283
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
284
|
+
}
|
|
285
|
+
function nonEmpty(value) {
|
|
286
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
287
|
+
}
|
|
288
|
+
function context(value) {
|
|
289
|
+
if (!record(value) || typeof value.type !== "string") return false;
|
|
290
|
+
if (value.type === "qr") return nonEmpty(value.token);
|
|
291
|
+
if (value.type === "passcode") return nonEmpty(value.code);
|
|
292
|
+
if (value.type === "nfc") return nonEmpty(value.tagId);
|
|
293
|
+
if (value.type === "custom") return "value" in value;
|
|
294
|
+
return value.type === "gps" && typeof value.latitude === "number" && Number.isFinite(value.latitude) && typeof value.longitude === "number" && Number.isFinite(value.longitude);
|
|
295
|
+
}
|
|
296
|
+
function common(value, fields) {
|
|
297
|
+
if (!record(value)) return false;
|
|
298
|
+
return fields.every((field) => nonEmpty(value[field]));
|
|
299
|
+
}
|
|
300
|
+
function validateCheckInRequest(value) {
|
|
301
|
+
if (!common(value, ["rallyId", "spotId", "idempotencyKey"]))
|
|
302
|
+
return error("$", "rallyId, spotId, and idempotencyKey must be non-empty strings.");
|
|
303
|
+
if (!context(value.context))
|
|
304
|
+
return error("context", "context is not a valid verification context.");
|
|
305
|
+
if (value.userId !== void 0 && !nonEmpty(value.userId))
|
|
306
|
+
return error("userId", "userId must be non-empty.");
|
|
307
|
+
if (value.now !== void 0 && !nonEmpty(value.now))
|
|
308
|
+
return error("now", "now must be non-empty when provided.");
|
|
309
|
+
return { success: true, data: value };
|
|
310
|
+
}
|
|
311
|
+
function validateClaimRewardRequest(value) {
|
|
312
|
+
if (!common(value, ["rallyId", "rewardId", "idempotencyKey"]))
|
|
313
|
+
return error("$", "rallyId, rewardId, and idempotencyKey must be non-empty strings.");
|
|
314
|
+
if (value.userId !== void 0 && !nonEmpty(value.userId))
|
|
315
|
+
return error("userId", "userId must be non-empty.");
|
|
316
|
+
if (value.staffPasscode !== void 0 && !nonEmpty(value.staffPasscode))
|
|
317
|
+
return error("staffPasscode", "staffPasscode must be non-empty.");
|
|
318
|
+
if (value.staffId !== void 0 && !nonEmpty(value.staffId))
|
|
319
|
+
return error("staffId", "staffId must be non-empty.");
|
|
320
|
+
if (value.now !== void 0 && !nonEmpty(value.now))
|
|
321
|
+
return error("now", "now must be non-empty when provided.");
|
|
322
|
+
return { success: true, data: value };
|
|
323
|
+
}
|
|
324
|
+
function validateSyncRequest(value) {
|
|
325
|
+
if (!common(value, ["rallyId"])) return error("rallyId", "rallyId must be a non-empty string.");
|
|
326
|
+
if (value.userId !== void 0 && !nonEmpty(value.userId))
|
|
327
|
+
return error("userId", "userId must be non-empty.");
|
|
328
|
+
return { success: true, data: value };
|
|
329
|
+
}
|
|
244
330
|
function json(body, status = 200) {
|
|
245
331
|
return new Response(JSON.stringify(body), {
|
|
246
332
|
status,
|
|
@@ -253,8 +339,12 @@ function isObject(value) {
|
|
|
253
339
|
function requestId(prefix) {
|
|
254
340
|
return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
|
|
255
341
|
}
|
|
256
|
-
function now(options
|
|
257
|
-
return
|
|
342
|
+
function now(options) {
|
|
343
|
+
return options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
344
|
+
}
|
|
345
|
+
function timestampMillis(timestamp) {
|
|
346
|
+
const value = Date.parse(timestamp);
|
|
347
|
+
return Number.isFinite(value) ? value : Date.now();
|
|
258
348
|
}
|
|
259
349
|
function initialState(config, userId, timestamp) {
|
|
260
350
|
return {
|
|
@@ -265,16 +355,16 @@ function initialState(config, userId, timestamp) {
|
|
|
265
355
|
updatedAt: timestamp
|
|
266
356
|
};
|
|
267
357
|
}
|
|
268
|
-
function getProof(
|
|
269
|
-
return
|
|
358
|
+
function getProof(context2) {
|
|
359
|
+
return context2.type === "qr" ? context2.token : context2.type === "passcode" ? context2.code : context2.type === "gps" ? { latitude: context2.latitude, longitude: context2.longitude } : context2.type === "nfc" ? context2.tagId : context2.value;
|
|
270
360
|
}
|
|
271
|
-
async function evaluate(condition,
|
|
272
|
-
if (condition.type !== "custom") return evaluateConditionDetailed(condition,
|
|
361
|
+
async function evaluate(condition, context2, validator, base) {
|
|
362
|
+
if (condition.type !== "custom") return evaluateConditionDetailed(condition, context2).ok;
|
|
273
363
|
if (validator === void 0) return false;
|
|
274
364
|
const validationContext = {
|
|
275
365
|
rallyId: base.rallyId,
|
|
276
366
|
spotId: base.spotId,
|
|
277
|
-
proofData: getProof(
|
|
367
|
+
proofData: getProof(context2),
|
|
278
368
|
condition,
|
|
279
369
|
userState: base.state
|
|
280
370
|
};
|
|
@@ -317,47 +407,51 @@ var StampRallyServer = class {
|
|
|
317
407
|
return json({ ok: false, code: "NOT_FOUND", message: "Route not found." }, 404);
|
|
318
408
|
}
|
|
319
409
|
async handleCheckIn(request) {
|
|
320
|
-
const body = await this.#body(request);
|
|
321
|
-
|
|
322
|
-
if (body === null || userId === null || body.rallyId !== this.#config.id || body.spotId === "" || body.idempotencyKey === "" || body.context === void 0)
|
|
410
|
+
const body = validateCheckInRequest(await this.#body(request));
|
|
411
|
+
if (!body.success || body.data.rallyId !== this.#config.id)
|
|
323
412
|
return json(
|
|
324
|
-
{
|
|
325
|
-
ok: false,
|
|
326
|
-
code: "INVALID_REQUEST",
|
|
327
|
-
message: "rallyId, spotId, context, and idempotencyKey are required."
|
|
328
|
-
},
|
|
413
|
+
{ ok: false, code: "INVALID_REQUEST", message: "Invalid check-in request." },
|
|
329
414
|
400
|
|
330
415
|
);
|
|
331
|
-
const
|
|
416
|
+
const userId = await this.#user(request);
|
|
417
|
+
if (userId === null)
|
|
418
|
+
return json(
|
|
419
|
+
{ ok: false, code: "UNAUTHENTICATED", message: "Authentication is required." },
|
|
420
|
+
401
|
|
421
|
+
);
|
|
422
|
+
const result = await this.checkIn({ ...body.data, userId });
|
|
332
423
|
return json(
|
|
333
424
|
{ ...result, status: operationStatus(result) },
|
|
334
425
|
result.ok ? 200 : result.code === "SPOT_NOT_FOUND" ? 404 : 422
|
|
335
426
|
);
|
|
336
427
|
}
|
|
337
428
|
async handleClaimReward(request) {
|
|
338
|
-
const body = await this.#body(request);
|
|
339
|
-
|
|
340
|
-
|
|
429
|
+
const body = validateClaimRewardRequest(await this.#body(request));
|
|
430
|
+
if (!body.success || body.data.rallyId !== this.#config.id)
|
|
431
|
+
return json({ ok: false, code: "INVALID_REQUEST", message: "Invalid reward request." }, 400);
|
|
432
|
+
const userId = await this.#user(request);
|
|
433
|
+
if (userId === null)
|
|
341
434
|
return json(
|
|
342
|
-
{
|
|
343
|
-
|
|
344
|
-
code: "INVALID_REQUEST",
|
|
345
|
-
message: "rallyId, rewardId, and idempotencyKey are required."
|
|
346
|
-
},
|
|
347
|
-
400
|
|
435
|
+
{ ok: false, code: "UNAUTHENTICATED", message: "Authentication is required." },
|
|
436
|
+
401
|
|
348
437
|
);
|
|
349
|
-
const result = await this.claimReward({ ...body, userId });
|
|
438
|
+
const result = await this.claimReward({ ...body.data, userId });
|
|
350
439
|
return json(
|
|
351
440
|
{ ...result, status: operationStatus(result) },
|
|
352
441
|
result.ok ? 200 : result.code === "REWARD_NOT_FOUND" ? 404 : 422
|
|
353
442
|
);
|
|
354
443
|
}
|
|
355
444
|
async handleSync(request) {
|
|
356
|
-
const body = await this.#body(request);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
445
|
+
const body = validateSyncRequest(await this.#body(request));
|
|
446
|
+
if (!body.success || body.data.rallyId !== this.#config.id)
|
|
447
|
+
return json({ ok: false, code: "INVALID_REQUEST", message: "Invalid sync request." }, 400);
|
|
448
|
+
const userId = await this.#user(request);
|
|
449
|
+
if (userId === null)
|
|
450
|
+
return json(
|
|
451
|
+
{ ok: false, code: "UNAUTHENTICATED", message: "Authentication is required." },
|
|
452
|
+
401
|
|
453
|
+
);
|
|
454
|
+
return json({ ok: true, state: await this.sync(body.data.rallyId, userId) });
|
|
361
455
|
}
|
|
362
456
|
async checkIn(request) {
|
|
363
457
|
const key = `check-in:${request.rallyId}:${request.userId}:${request.idempotencyKey}`;
|
|
@@ -373,47 +467,68 @@ var StampRallyServer = class {
|
|
|
373
467
|
this.#options.lockTtlMs ?? 5e3
|
|
374
468
|
))
|
|
375
469
|
return { ok: false, code: "CONFLICT", message: "The user state is being updated." };
|
|
376
|
-
const timestamp = now(this.#options
|
|
470
|
+
const timestamp = now(this.#options);
|
|
377
471
|
try {
|
|
472
|
+
const current = await this.#persistence.getUserState(request.rallyId, request.userId) ?? initialState(this.#config, request.userId, timestamp);
|
|
473
|
+
const responseHolder = { value: null };
|
|
474
|
+
const makeAudit = (status, code) => audit(
|
|
475
|
+
request.rallyId,
|
|
476
|
+
request.userId,
|
|
477
|
+
"CHECK_IN",
|
|
478
|
+
request.spotId,
|
|
479
|
+
request.idempotencyKey,
|
|
480
|
+
status,
|
|
481
|
+
timestamp,
|
|
482
|
+
code
|
|
483
|
+
);
|
|
378
484
|
const spot = this.#config.spots.find((item) => item.id === request.spotId);
|
|
379
|
-
if (spot === void 0)
|
|
380
|
-
|
|
485
|
+
if (spot === void 0) {
|
|
486
|
+
responseHolder.value = {
|
|
487
|
+
ok: false,
|
|
488
|
+
code: "SPOT_NOT_FOUND",
|
|
489
|
+
message: "Spot was not found."
|
|
490
|
+
};
|
|
491
|
+
return await this.#rememberCheckInTransaction(
|
|
492
|
+
request,
|
|
493
|
+
timestamp,
|
|
381
494
|
key,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
495
|
+
current,
|
|
496
|
+
{
|
|
497
|
+
nextUserState: current,
|
|
498
|
+
auditLog: makeAudit("REJECTED", "SPOT_NOT_FOUND"),
|
|
499
|
+
result: responseHolder.value,
|
|
500
|
+
error: "SPOT_NOT_FOUND"
|
|
501
|
+
},
|
|
502
|
+
responseHolder
|
|
389
503
|
);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
504
|
+
}
|
|
505
|
+
const rejected = (code, message) => {
|
|
506
|
+
responseHolder.value = { ok: false, code, message };
|
|
507
|
+
return {
|
|
508
|
+
nextUserState: current,
|
|
509
|
+
auditLog: makeAudit("REJECTED", code),
|
|
510
|
+
result: responseHolder.value,
|
|
511
|
+
error: code
|
|
512
|
+
};
|
|
513
|
+
};
|
|
514
|
+
if (current.records.some((record2) => record2.stampId === request.spotId))
|
|
515
|
+
return await this.#rememberCheckInTransaction(
|
|
516
|
+
request,
|
|
517
|
+
timestamp,
|
|
393
518
|
key,
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
"CHECK_IN",
|
|
398
|
-
request.spotId,
|
|
399
|
-
request.idempotencyKey,
|
|
400
|
-
timestamp
|
|
519
|
+
current,
|
|
520
|
+
rejected("STAMP_ALREADY_ACQUIRED", "Spot was already claimed."),
|
|
521
|
+
responseHolder
|
|
401
522
|
);
|
|
402
|
-
const acquired = new Set(current.records.map((
|
|
523
|
+
const acquired = new Set(current.records.map((record2) => record2.stampId));
|
|
403
524
|
if (spot.prerequisites?.some((id) => !acquired.has(id)))
|
|
404
|
-
return this.#
|
|
525
|
+
return await this.#rememberCheckInTransaction(
|
|
526
|
+
request,
|
|
527
|
+
timestamp,
|
|
405
528
|
key,
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
message: "Prerequisite spots are not complete."
|
|
410
|
-
},
|
|
411
|
-
request.rallyId,
|
|
412
|
-
request.userId,
|
|
413
|
-
"CHECK_IN",
|
|
414
|
-
request.spotId,
|
|
415
|
-
request.idempotencyKey,
|
|
416
|
-
timestamp
|
|
529
|
+
current,
|
|
530
|
+
rejected("PREREQUISITES_NOT_MET", "Prerequisite spots are not complete."),
|
|
531
|
+
responseHolder
|
|
417
532
|
);
|
|
418
533
|
for (const condition of spot.conditions)
|
|
419
534
|
if (!await evaluate(
|
|
@@ -422,15 +537,13 @@ var StampRallyServer = class {
|
|
|
422
537
|
condition.type === "custom" ? this.#options.customValidators?.[condition.validatorName] : void 0,
|
|
423
538
|
{ rallyId: request.rallyId, spotId: request.spotId, state: current }
|
|
424
539
|
))
|
|
425
|
-
return this.#
|
|
540
|
+
return await this.#rememberCheckInTransaction(
|
|
541
|
+
request,
|
|
542
|
+
timestamp,
|
|
426
543
|
key,
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
"CHECK_IN",
|
|
431
|
-
request.spotId,
|
|
432
|
-
request.idempotencyKey,
|
|
433
|
-
timestamp
|
|
544
|
+
current,
|
|
545
|
+
rejected("INVALID_PROOF", "Verification failed."),
|
|
546
|
+
responseHolder
|
|
434
547
|
);
|
|
435
548
|
const next = {
|
|
436
549
|
...current,
|
|
@@ -443,17 +556,30 @@ var StampRallyServer = class {
|
|
|
443
556
|
),
|
|
444
557
|
updatedAt: timestamp
|
|
445
558
|
};
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
559
|
+
responseHolder.value = { ok: true, state: next };
|
|
560
|
+
const transaction = await this.#persistence.executeCheckInTransaction(
|
|
561
|
+
{
|
|
562
|
+
rallyId: request.rallyId,
|
|
563
|
+
userId: request.userId,
|
|
564
|
+
spotId: request.spotId,
|
|
565
|
+
timestamp: timestampMillis(timestamp),
|
|
566
|
+
idempotencyKey: key,
|
|
567
|
+
proofData: request.context,
|
|
568
|
+
...this.#options.idempotencyTtlMs === void 0 ? {} : { idempotencyTtlMs: this.#options.idempotencyTtlMs },
|
|
569
|
+
initialUserState: current
|
|
570
|
+
},
|
|
571
|
+
() => ({
|
|
572
|
+
nextUserState: next,
|
|
573
|
+
auditLog: makeAudit("SUCCESS"),
|
|
574
|
+
result: responseHolder.value
|
|
575
|
+
})
|
|
456
576
|
);
|
|
577
|
+
if (transaction.success && responseHolder.value !== null) return responseHolder.value;
|
|
578
|
+
return {
|
|
579
|
+
ok: false,
|
|
580
|
+
code: "PERSISTENCE_FAILED",
|
|
581
|
+
message: transaction.error ?? "Check-in failed."
|
|
582
|
+
};
|
|
457
583
|
} finally {
|
|
458
584
|
await this.#persistence.releaseLock(request.rallyId, lockKey);
|
|
459
585
|
}
|
|
@@ -471,7 +597,7 @@ var StampRallyServer = class {
|
|
|
471
597
|
key,
|
|
472
598
|
{ ok: false, code: "REWARD_NOT_FOUND", message: "Reward was not found." },
|
|
473
599
|
request,
|
|
474
|
-
now(this.#options
|
|
600
|
+
now(this.#options)
|
|
475
601
|
);
|
|
476
602
|
const lockKey = `reward:${request.rallyId}:${reward.id}`;
|
|
477
603
|
if (!await this.#persistence.acquireLock(
|
|
@@ -480,7 +606,7 @@ var StampRallyServer = class {
|
|
|
480
606
|
this.#options.lockTtlMs ?? 5e3
|
|
481
607
|
))
|
|
482
608
|
return { ok: false, code: "CONFLICT", message: "The reward is being claimed." };
|
|
483
|
-
const timestamp = now(this.#options
|
|
609
|
+
const timestamp = now(this.#options);
|
|
484
610
|
try {
|
|
485
611
|
const checked = await this.#persistence.getIdempotentResult(
|
|
486
612
|
request.rallyId,
|
|
@@ -530,8 +656,9 @@ var StampRallyServer = class {
|
|
|
530
656
|
error: "OUT_OF_STOCK"
|
|
531
657
|
};
|
|
532
658
|
}
|
|
659
|
+
const effectiveReward = reward.staffPasscode === void 0 && this.#config.staffPasscode !== void 0 ? { ...reward, staffPasscode: this.#config.staffPasscode } : reward;
|
|
533
660
|
const local = consumeReward({
|
|
534
|
-
reward,
|
|
661
|
+
reward: effectiveReward,
|
|
535
662
|
currentState: currentReward,
|
|
536
663
|
now: timestamp,
|
|
537
664
|
userRedemptionCount: claimCount,
|
|
@@ -582,11 +709,11 @@ var StampRallyServer = class {
|
|
|
582
709
|
code: "PERSISTENCE_FAILED",
|
|
583
710
|
message: result.error ?? "Reward claim failed."
|
|
584
711
|
};
|
|
585
|
-
} catch (
|
|
712
|
+
} catch (error2) {
|
|
586
713
|
return {
|
|
587
714
|
ok: false,
|
|
588
715
|
code: "PERSISTENCE_FAILED",
|
|
589
|
-
message:
|
|
716
|
+
message: error2 instanceof Error ? error2.message : "Reward claim failed."
|
|
590
717
|
};
|
|
591
718
|
} finally {
|
|
592
719
|
await this.#persistence.releaseLock(request.rallyId, lockKey);
|
|
@@ -603,31 +730,36 @@ var StampRallyServer = class {
|
|
|
603
730
|
return null;
|
|
604
731
|
}
|
|
605
732
|
}
|
|
606
|
-
async #user(request
|
|
607
|
-
if (this.#options.authenticate !== void 0)
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
this.#options.idempotencyTtlMs ?? 864e5
|
|
733
|
+
async #user(request) {
|
|
734
|
+
if (this.#options.authenticate !== void 0) {
|
|
735
|
+
const identity = await this.#options.authenticate(request);
|
|
736
|
+
if (typeof identity === "string") return identity.length > 0 ? identity : null;
|
|
737
|
+
if (identity === null) return null;
|
|
738
|
+
const authenticatedUserId = identity.authenticatedUserId;
|
|
739
|
+
return authenticatedUserId.length > 0 ? authenticatedUserId : null;
|
|
740
|
+
}
|
|
741
|
+
return "anonymous";
|
|
742
|
+
}
|
|
743
|
+
async #rememberCheckInTransaction(request, timestamp, key, current, mutation, responseHolder) {
|
|
744
|
+
const transaction = await this.#persistence.executeCheckInTransaction(
|
|
745
|
+
{
|
|
746
|
+
rallyId: request.rallyId,
|
|
747
|
+
userId: request.userId,
|
|
748
|
+
spotId: request.spotId,
|
|
749
|
+
timestamp: timestampMillis(timestamp),
|
|
750
|
+
idempotencyKey: key,
|
|
751
|
+
...this.#options.idempotencyTtlMs === void 0 ? {} : { idempotencyTtlMs: this.#options.idempotencyTtlMs },
|
|
752
|
+
initialUserState: current
|
|
753
|
+
},
|
|
754
|
+
() => mutation
|
|
629
755
|
);
|
|
630
|
-
|
|
756
|
+
if (responseHolder.value !== null && (transaction.success || transaction.error === mutation.error))
|
|
757
|
+
return responseHolder.value;
|
|
758
|
+
return {
|
|
759
|
+
ok: false,
|
|
760
|
+
code: "PERSISTENCE_FAILED",
|
|
761
|
+
message: transaction.error ?? "Check-in failed."
|
|
762
|
+
};
|
|
631
763
|
}
|
|
632
764
|
async #rememberClaim(key, result, request, timestamp) {
|
|
633
765
|
await this.#persistence.recordAuditLog(
|
|
@@ -652,6 +784,6 @@ var StampRallyServer = class {
|
|
|
652
784
|
}
|
|
653
785
|
};
|
|
654
786
|
|
|
655
|
-
export { InMemoryServerPersistenceAdapter, StampRallyServer, executeClaimRewardTransaction };
|
|
787
|
+
export { InMemoryServerPersistenceAdapter, StampRallyServer, executeClaimRewardTransaction, validateCheckInRequest, validateClaimRewardRequest, validateSyncRequest };
|
|
656
788
|
//# sourceMappingURL=index.js.map
|
|
657
789
|
//# sourceMappingURL=index.js.map
|