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