@domino-sdk/relay 0.12.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/build.d.mts +4 -1
- package/build.mjs +15 -8
- package/dist/authoring.d.ts +4 -3
- package/dist/authoring.js +22 -9
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +6 -5
- package/dist/{chunk-CE5XVIJQ.js → chunk-CJARNIVP.js} +265 -76
- package/dist/{chunk-3UOTI4IU.js → chunk-FUQYKTOJ.js} +30 -7
- package/dist/{chunk-VDWQL6HV.js → chunk-I2CCMGN6.js} +1 -1
- package/dist/{chunk-NLMEK77N.js → chunk-YZJOZL4V.js} +146 -60
- package/dist/index.d.ts +86 -2
- package/dist/index.js +46 -6
- package/dist/portal-proxy.js +26 -13
- package/dist/schema.d.ts +12 -12
- package/dist/schema.js +1 -1
- package/dist/{settings-B61jIDDA.d.ts → settings-CU2y-wUM.d.ts} +382 -1
- package/package.json +1 -1
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
var communityTimeZoneSchema = z.string().min(1).max(100).refine((zone) => {
|
|
4
4
|
try {
|
|
5
|
-
if (/^[+-]/.test(zone))
|
|
5
|
+
if (/^[+-]/.test(zone)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
6
8
|
new Intl.DateTimeFormat("en", { timeZone: zone });
|
|
7
9
|
return true;
|
|
8
10
|
} catch {
|
|
@@ -23,12 +25,30 @@ var streakProgressSchema = z.object({
|
|
|
23
25
|
todayQualified: z.boolean(),
|
|
24
26
|
timeZone: communityTimeZoneSchema,
|
|
25
27
|
nextBoundary: z.number().int(),
|
|
26
|
-
pendingTimeZone: z.object({
|
|
27
|
-
|
|
28
|
+
pendingTimeZone: z.object({
|
|
29
|
+
timeZone: communityTimeZoneSchema,
|
|
30
|
+
effectiveAt: z.number().int()
|
|
31
|
+
}).nullable(),
|
|
32
|
+
days: z.array(
|
|
33
|
+
z.object({
|
|
34
|
+
day: z.number().int(),
|
|
35
|
+
date: z.string(),
|
|
36
|
+
timeZone: communityTimeZoneSchema,
|
|
37
|
+
occurredAt: z.number().int()
|
|
38
|
+
})
|
|
39
|
+
),
|
|
28
40
|
milestone: z.discriminatedUnion("status", [
|
|
29
41
|
z.object({ status: z.literal("pending") }),
|
|
30
|
-
z.object({
|
|
31
|
-
|
|
42
|
+
z.object({
|
|
43
|
+
status: z.literal("awarded"),
|
|
44
|
+
completion: z.string(),
|
|
45
|
+
achievedAt: z.number().int()
|
|
46
|
+
}),
|
|
47
|
+
z.object({
|
|
48
|
+
status: z.literal("budget-exhausted"),
|
|
49
|
+
budget: z.string(),
|
|
50
|
+
achievedAt: z.number().int()
|
|
51
|
+
})
|
|
32
52
|
])
|
|
33
53
|
});
|
|
34
54
|
|
|
@@ -406,8 +426,9 @@ var rewardSchema = z9.union([
|
|
|
406
426
|
integrationRewardSchema
|
|
407
427
|
]);
|
|
408
428
|
function rewardKey(reward) {
|
|
409
|
-
if (reward.kind === "integration")
|
|
429
|
+
if (reward.kind === "integration") {
|
|
410
430
|
return `integration:${reward.provider}:${reward.action}:${reward.roleId}`;
|
|
431
|
+
}
|
|
411
432
|
return reward.kind === "points" ? "points:" + reward.balance : "tier:" + reward.reward.id;
|
|
412
433
|
}
|
|
413
434
|
var triggerSchema = z9.union([
|
|
@@ -453,7 +474,9 @@ var rewardBundleSchema = z9.array(rewardSchema).max(10).refine(
|
|
|
453
474
|
"Use one entry per balance or shared reward"
|
|
454
475
|
);
|
|
455
476
|
var mediaUrlSchema = z9.string().max(2e3).refine((value) => {
|
|
456
|
-
if (/^\/(?!\/)[^\\\s]*$/.test(value))
|
|
477
|
+
if (/^\/(?!\/)[^\\\s]*$/.test(value)) {
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
457
480
|
try {
|
|
458
481
|
return new URL(value).protocol === "https:";
|
|
459
482
|
} catch {
|
|
@@ -7,18 +7,20 @@ import {
|
|
|
7
7
|
leaderboardQuerySchema,
|
|
8
8
|
normalizeAuthoringIdentity,
|
|
9
9
|
publishedReferralSchema,
|
|
10
|
+
publishedTrackerSchema,
|
|
10
11
|
referralAcceptSchema,
|
|
11
12
|
referralArtifactSchema,
|
|
12
13
|
referralDefinitionSchema,
|
|
13
14
|
referralHistorySchema,
|
|
14
15
|
referralSummarySchema,
|
|
16
|
+
trackerArtifactSchema,
|
|
15
17
|
validateAuthoringIdentity
|
|
16
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-CJARNIVP.js";
|
|
17
19
|
import {
|
|
18
20
|
authScopeSchema,
|
|
19
21
|
exchangeResultSchema,
|
|
20
22
|
sessionSchema
|
|
21
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-I2CCMGN6.js";
|
|
22
24
|
import {
|
|
23
25
|
accountConnectionSchema,
|
|
24
26
|
attemptSchema,
|
|
@@ -43,7 +45,7 @@ import {
|
|
|
43
45
|
snapshotSchema,
|
|
44
46
|
storedReviewModeSchema,
|
|
45
47
|
templateSchema
|
|
46
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-FUQYKTOJ.js";
|
|
47
49
|
|
|
48
50
|
// src/point-ledgers.ts
|
|
49
51
|
import { z } from "zod";
|
|
@@ -265,15 +267,17 @@ function submissions(transport, configuredStore, namespace) {
|
|
|
265
267
|
async function open(quest, options = {
|
|
266
268
|
input: "photo"
|
|
267
269
|
}) {
|
|
268
|
-
if (!configuredStore)
|
|
270
|
+
if (!configuredStore) {
|
|
269
271
|
throw new Error(
|
|
270
272
|
"Submissions require durable storage. Use createBrowserRelayClient or provide a SubmissionStore."
|
|
271
273
|
);
|
|
274
|
+
}
|
|
272
275
|
const store = configuredStore;
|
|
273
276
|
identifier.parse(quest);
|
|
274
277
|
const session = await transport.session();
|
|
275
|
-
if (session?.principal.kind !== "member")
|
|
278
|
+
if (session?.principal.kind !== "member") {
|
|
276
279
|
throw new Error("Sign in to submit an activity");
|
|
280
|
+
}
|
|
277
281
|
const sessionId = session.id;
|
|
278
282
|
const key = JSON.stringify([
|
|
279
283
|
"relay-submission-v1",
|
|
@@ -286,25 +290,30 @@ function submissions(transport, configuredStore, namespace) {
|
|
|
286
290
|
]);
|
|
287
291
|
async function currentSession() {
|
|
288
292
|
const current = await transport.session();
|
|
289
|
-
if (current?.id !== sessionId)
|
|
293
|
+
if (current?.id !== sessionId) {
|
|
290
294
|
throw new Error(
|
|
291
295
|
"Your session changed. Reopen the submission before continuing."
|
|
292
296
|
);
|
|
297
|
+
}
|
|
293
298
|
}
|
|
294
299
|
async function read() {
|
|
295
300
|
const saved = await store.read(key);
|
|
296
|
-
if (saved === void 0 || saved === null)
|
|
301
|
+
if (saved === void 0 || saved === null) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
297
304
|
const parsed = recordSchema.safeParse(saved);
|
|
298
|
-
if (!parsed.success || parsed.data.quest !== quest)
|
|
305
|
+
if (!parsed.success || parsed.data.quest !== quest) {
|
|
299
306
|
throw new Error(
|
|
300
307
|
"Saved submission could not be read. Do not create another claim until it is recovered."
|
|
301
308
|
);
|
|
309
|
+
}
|
|
302
310
|
const record = parsed.data;
|
|
303
311
|
const input = record.stage === "review" ? record.draft?.kind : "input" in record ? record.input.kind : "photo";
|
|
304
|
-
if (!options.recoverPrevious && input !== options.input)
|
|
312
|
+
if (!options.recoverPrevious && input !== options.input) {
|
|
305
313
|
throw new Error(
|
|
306
314
|
"Saved submission uses a different input type. Reopen it with its original type to recover it."
|
|
307
315
|
);
|
|
316
|
+
}
|
|
308
317
|
return record;
|
|
309
318
|
}
|
|
310
319
|
function claim(record) {
|
|
@@ -319,12 +328,13 @@ function submissions(transport, configuredStore, namespace) {
|
|
|
319
328
|
);
|
|
320
329
|
}
|
|
321
330
|
async function resumeRecord(saved) {
|
|
322
|
-
if (saved.stage === "review")
|
|
331
|
+
if (saved.stage === "review") {
|
|
323
332
|
throw new QuestUpdatedError(
|
|
324
333
|
"This quest changed. Review it before submitting again.",
|
|
325
334
|
"quest-updated",
|
|
326
335
|
saved.draft
|
|
327
336
|
);
|
|
337
|
+
}
|
|
328
338
|
let record = saved;
|
|
329
339
|
if (record.stage === "upload") {
|
|
330
340
|
const evidence = await transport.upload(
|
|
@@ -367,30 +377,35 @@ function submissions(transport, configuredStore, namespace) {
|
|
|
367
377
|
stage: "submitted",
|
|
368
378
|
attempt: attempt.id
|
|
369
379
|
});
|
|
370
|
-
if (attempt.status === "failed")
|
|
380
|
+
if (attempt.status === "failed") {
|
|
371
381
|
attempt = await transport.retry(attempt.id, sessionId);
|
|
382
|
+
}
|
|
372
383
|
return attempt;
|
|
373
384
|
}
|
|
374
385
|
async function submit(record) {
|
|
375
386
|
return store.exclusive(key, async () => {
|
|
376
387
|
await currentSession();
|
|
377
388
|
const existing = await read();
|
|
378
|
-
if (existing?.stage === "review" && existing.release === record.release)
|
|
389
|
+
if (existing?.stage === "review" && existing.release === record.release) {
|
|
379
390
|
throw new QuestUpdatedError(
|
|
380
391
|
"Review the latest quest before submitting again.",
|
|
381
392
|
"quest-updated",
|
|
382
393
|
existing.draft
|
|
383
394
|
);
|
|
384
|
-
|
|
395
|
+
}
|
|
396
|
+
if (existing && existing.stage !== "submitted" && existing.stage !== "review") {
|
|
385
397
|
throw new Error(
|
|
386
398
|
"A submission is unfinished. Resume it before choosing new input."
|
|
387
399
|
);
|
|
400
|
+
}
|
|
388
401
|
if (existing?.stage === "submitted") {
|
|
389
402
|
const previous = await claim(existing);
|
|
390
|
-
if (previous.status === "failed")
|
|
403
|
+
if (previous.status === "failed") {
|
|
391
404
|
return transport.retry(previous.id, sessionId);
|
|
392
|
-
|
|
405
|
+
}
|
|
406
|
+
if (previous.status !== "rejected" && !(options.repeatable && previous.status === "accepted")) {
|
|
393
407
|
return previous;
|
|
408
|
+
}
|
|
394
409
|
}
|
|
395
410
|
await store.write(key, record);
|
|
396
411
|
return resumeRecord(record);
|
|
@@ -401,37 +416,46 @@ function submissions(transport, configuredStore, namespace) {
|
|
|
401
416
|
return store.exclusive(key, async () => {
|
|
402
417
|
await currentSession();
|
|
403
418
|
const record = await read();
|
|
404
|
-
if (!record)
|
|
405
|
-
|
|
419
|
+
if (!record) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (record.stage !== "submitted") {
|
|
406
423
|
throw new Error(
|
|
407
424
|
"Resume the unfinished submission before starting over."
|
|
408
425
|
);
|
|
426
|
+
}
|
|
409
427
|
const attempt = await claim(record);
|
|
410
|
-
if (attempt.status !== "failed" && attempt.status !== "rejected" && !(options.repeatable && attempt.status === "accepted"))
|
|
428
|
+
if (attempt.status !== "failed" && attempt.status !== "rejected" && !(options.repeatable && attempt.status === "accepted")) {
|
|
411
429
|
throw new Error(
|
|
412
430
|
"Only a failed or rejected submission can be discarded."
|
|
413
431
|
);
|
|
432
|
+
}
|
|
414
433
|
await store.write(key, null);
|
|
415
434
|
});
|
|
416
435
|
},
|
|
417
436
|
async status() {
|
|
418
437
|
await currentSession();
|
|
419
438
|
const record = await read();
|
|
420
|
-
if (!record)
|
|
421
|
-
|
|
439
|
+
if (!record) {
|
|
440
|
+
return { kind: "empty" };
|
|
441
|
+
}
|
|
442
|
+
if (record.stage === "review") {
|
|
443
|
+
return { kind: "needs-review" };
|
|
444
|
+
}
|
|
422
445
|
return record.stage === "submitted" ? { kind: "submitted", attempt: record.attempt } : { kind: "pending", stage: record.stage };
|
|
423
446
|
},
|
|
424
447
|
async resume() {
|
|
425
448
|
return store.exclusive(key, async () => {
|
|
426
449
|
await currentSession();
|
|
427
450
|
const record = await read();
|
|
428
|
-
if (!record)
|
|
451
|
+
if (!record) {
|
|
429
452
|
throw new Error("There is no saved submission to resume");
|
|
453
|
+
}
|
|
430
454
|
return resumeRecord(record);
|
|
431
455
|
});
|
|
432
456
|
}
|
|
433
457
|
};
|
|
434
|
-
if (options.input === "quiz")
|
|
458
|
+
if (options.input === "quiz") {
|
|
435
459
|
return {
|
|
436
460
|
...handle,
|
|
437
461
|
submit(answers, version) {
|
|
@@ -449,7 +473,8 @@ function submissions(transport, configuredStore, namespace) {
|
|
|
449
473
|
});
|
|
450
474
|
}
|
|
451
475
|
};
|
|
452
|
-
|
|
476
|
+
}
|
|
477
|
+
if (options.input === "none") {
|
|
453
478
|
return {
|
|
454
479
|
...handle,
|
|
455
480
|
submit() {
|
|
@@ -462,15 +487,18 @@ function submissions(transport, configuredStore, namespace) {
|
|
|
462
487
|
});
|
|
463
488
|
}
|
|
464
489
|
};
|
|
490
|
+
}
|
|
465
491
|
return {
|
|
466
492
|
...handle,
|
|
467
493
|
async submit(file) {
|
|
468
|
-
if (!["image/jpeg", "image/png"].includes(file.type) || file.size === 0 || file.size > 5 * 1024 * 1024)
|
|
494
|
+
if (!["image/jpeg", "image/png"].includes(file.type) || file.size === 0 || file.size > 5 * 1024 * 1024) {
|
|
469
495
|
throw new Error("Choose a JPG or PNG photo under 5 MB");
|
|
496
|
+
}
|
|
470
497
|
const bytes = new Uint8Array(await file.slice(0, 4).arrayBuffer());
|
|
471
498
|
const signature = file.type === "image/png" ? [137, 80, 78, 71] : [255, 216, 255];
|
|
472
|
-
if (!signature.every((byte, index) => bytes[index] === byte))
|
|
499
|
+
if (!signature.every((byte, index) => bytes[index] === byte)) {
|
|
473
500
|
throw new Error("Image content does not match its file type");
|
|
501
|
+
}
|
|
474
502
|
return submit({
|
|
475
503
|
id: crypto.randomUUID(),
|
|
476
504
|
...options.release ? { release: options.release } : {},
|
|
@@ -493,11 +521,18 @@ var QuestUpdatedError = class extends RelayError {
|
|
|
493
521
|
|
|
494
522
|
// src/quest-experience.ts
|
|
495
523
|
function reconcileQuestDraft(view, draft) {
|
|
496
|
-
if (!draft)
|
|
497
|
-
|
|
524
|
+
if (!draft) {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
if (draft.kind === "photo") {
|
|
498
528
|
return view.interaction === "photo" ? draft : null;
|
|
499
|
-
|
|
500
|
-
if (
|
|
529
|
+
}
|
|
530
|
+
if (draft.kind === "none") {
|
|
531
|
+
return view.interaction === "claim" ? draft : null;
|
|
532
|
+
}
|
|
533
|
+
if (view.interaction !== "quiz") {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
501
536
|
const answers = Object.fromEntries(
|
|
502
537
|
(view.presentation.quiz ?? []).flatMap((q) => {
|
|
503
538
|
const answer = draft.answers[q.id];
|
|
@@ -512,7 +547,9 @@ function questActions(submission, latest) {
|
|
|
512
547
|
try {
|
|
513
548
|
return { kind: "submitted", attempt: await task() };
|
|
514
549
|
} catch (error) {
|
|
515
|
-
if (!(error instanceof QuestUpdatedError))
|
|
550
|
+
if (!(error instanceof QuestUpdatedError)) {
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
516
553
|
const current = await latest(view.quest).catch(() => {
|
|
517
554
|
throw error;
|
|
518
555
|
});
|
|
@@ -597,10 +634,11 @@ function observeProgress(transport, listener, options = {}, onDispose = () => {
|
|
|
597
634
|
}) {
|
|
598
635
|
const interval = options.intervalMs ?? 1500;
|
|
599
636
|
const maximum = options.maxBackoffMs ?? 3e4;
|
|
600
|
-
if (!Number.isFinite(interval) || interval <= 0 || !Number.isFinite(maximum) || maximum < interval)
|
|
637
|
+
if (!Number.isFinite(interval) || interval <= 0 || !Number.isFinite(maximum) || maximum < interval) {
|
|
601
638
|
throw new Error(
|
|
602
639
|
"Progress polling requires a positive interval and a maximum at least as large"
|
|
603
640
|
);
|
|
641
|
+
}
|
|
604
642
|
let disposed = false;
|
|
605
643
|
let controller;
|
|
606
644
|
let timer;
|
|
@@ -608,29 +646,43 @@ function observeProgress(transport, listener, options = {}, onDispose = () => {
|
|
|
608
646
|
let failures = 0;
|
|
609
647
|
function clear() {
|
|
610
648
|
controller?.abort();
|
|
611
|
-
if (timer !== void 0)
|
|
649
|
+
if (timer !== void 0) {
|
|
650
|
+
clearTimeout(timer);
|
|
651
|
+
}
|
|
612
652
|
sessionId = void 0;
|
|
613
|
-
if (!disposed)
|
|
653
|
+
if (!disposed) {
|
|
654
|
+
listener({ kind: "loading" });
|
|
655
|
+
}
|
|
614
656
|
}
|
|
615
657
|
async function refresh() {
|
|
616
|
-
if (disposed)
|
|
658
|
+
if (disposed) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
617
661
|
controller?.abort();
|
|
618
|
-
if (timer !== void 0)
|
|
662
|
+
if (timer !== void 0) {
|
|
663
|
+
clearTimeout(timer);
|
|
664
|
+
}
|
|
619
665
|
const request = new AbortController();
|
|
620
666
|
controller = request;
|
|
621
667
|
const current = () => !disposed && !request.signal.aborted;
|
|
622
668
|
try {
|
|
623
669
|
const session = await transport.session(request.signal);
|
|
624
|
-
if (!current())
|
|
670
|
+
if (!current()) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
625
673
|
if (sessionId !== (session?.id ?? null)) {
|
|
626
674
|
sessionId = session?.id ?? null;
|
|
627
675
|
listener({ kind: "session", session });
|
|
628
676
|
}
|
|
629
677
|
if (session?.principal.kind === "member") {
|
|
630
678
|
const progress = await transport.progress(session.id, request.signal);
|
|
631
|
-
if (!current())
|
|
679
|
+
if (!current()) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
632
682
|
const confirmed = await transport.session(request.signal);
|
|
633
|
-
if (!current())
|
|
683
|
+
if (!current()) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
634
686
|
if (confirmed?.id !== session.id || progress.member !== session.principal.member) {
|
|
635
687
|
sessionId = void 0;
|
|
636
688
|
listener({ kind: "loading" });
|
|
@@ -640,7 +692,9 @@ function observeProgress(transport, listener, options = {}, onDispose = () => {
|
|
|
640
692
|
}
|
|
641
693
|
failures = 0;
|
|
642
694
|
} catch (error) {
|
|
643
|
-
if (!current())
|
|
695
|
+
if (!current()) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
644
698
|
failures = Math.min(failures + 1, 20);
|
|
645
699
|
sessionId = void 0;
|
|
646
700
|
listener({
|
|
@@ -648,21 +702,26 @@ function observeProgress(transport, listener, options = {}, onDispose = () => {
|
|
|
648
702
|
error: error instanceof Error ? error : new Error("Could not refresh progress")
|
|
649
703
|
});
|
|
650
704
|
} finally {
|
|
651
|
-
if (current())
|
|
705
|
+
if (current()) {
|
|
652
706
|
timer = setTimeout(
|
|
653
707
|
() => void refresh(),
|
|
654
708
|
Math.min(maximum, interval * 2 ** failures)
|
|
655
709
|
);
|
|
710
|
+
}
|
|
656
711
|
}
|
|
657
712
|
}
|
|
658
713
|
const observer = {
|
|
659
714
|
refresh,
|
|
660
715
|
clear,
|
|
661
716
|
dispose() {
|
|
662
|
-
if (disposed)
|
|
717
|
+
if (disposed) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
663
720
|
disposed = true;
|
|
664
721
|
controller?.abort();
|
|
665
|
-
if (timer !== void 0)
|
|
722
|
+
if (timer !== void 0) {
|
|
723
|
+
clearTimeout(timer);
|
|
724
|
+
}
|
|
666
725
|
onDispose();
|
|
667
726
|
}
|
|
668
727
|
};
|
|
@@ -739,16 +798,18 @@ function questConfiguration(release) {
|
|
|
739
798
|
// src/deployments.ts
|
|
740
799
|
import { z as z7 } from "zod";
|
|
741
800
|
var projectCatalogArtifactSchema = catalogDeploymentSchema.omit({ expectedRevision: true }).extend({
|
|
801
|
+
trackers: z7.array(trackerArtifactSchema).max(50).default([]),
|
|
742
802
|
pointLedgers: z7.array(pointLedgerDeclarationSchema).max(50).default([]),
|
|
743
803
|
referrals: z7.array(referralArtifactSchema).max(50).default([]),
|
|
744
804
|
leaderboards: z7.array(leaderboardArtifactSchema).max(50).default([])
|
|
745
805
|
});
|
|
746
806
|
var projectDeploymentSchema = projectCatalogArtifactSchema.extend({ releases: z7.array(releaseSchema).max(50).default([]) }).refine(
|
|
747
|
-
(value) => value.pointLedgers.length > 0 || value.releases.length > 0 || value.leaderboards.length > 0 || value.referrals.length > 0 || value.types.length > 0 || value.collections.length > 0 || value.supportedInteractions !== void 0,
|
|
748
|
-
"No resources configured. Add quests, questTypes, pointLedgers, leaderboards, referrals, collections, or supportedInteractions to relay.json."
|
|
807
|
+
(value) => value.trackers.length > 0 || value.pointLedgers.length > 0 || value.releases.length > 0 || value.leaderboards.length > 0 || value.referrals.length > 0 || value.types.length > 0 || value.collections.length > 0 || value.supportedInteractions !== void 0,
|
|
808
|
+
"No resources configured. Add trackers, quests, questTypes, pointLedgers, leaderboards, referrals, collections, or supportedInteractions to relay.json."
|
|
749
809
|
);
|
|
750
810
|
var deploymentPreviewSchema = z7.object({
|
|
751
811
|
id: identifier,
|
|
812
|
+
trackers: z7.array(publishedTrackerSchema).default([]),
|
|
752
813
|
baseRevision: z7.number().int().nonnegative(),
|
|
753
814
|
pointLedgers: z7.array(pointLedgerSchema).default([]),
|
|
754
815
|
releases: z7.array(publishedReleaseSchema),
|
|
@@ -792,13 +853,21 @@ var participantExchangeSchema = z8.object({
|
|
|
792
853
|
function createRelayClient(options) {
|
|
793
854
|
async function send(path, init = {}) {
|
|
794
855
|
const headers = new Headers(init.headers);
|
|
795
|
-
if (options.token)
|
|
796
|
-
|
|
856
|
+
if (options.token) {
|
|
857
|
+
headers.set("Authorization", `Bearer ${options.token}`);
|
|
858
|
+
}
|
|
859
|
+
if (options.organization) {
|
|
797
860
|
headers.set("X-Relay-Organization", options.organization);
|
|
798
|
-
|
|
799
|
-
if (options.
|
|
861
|
+
}
|
|
862
|
+
if (options.project) {
|
|
863
|
+
headers.set("X-Relay-Project", options.project);
|
|
864
|
+
}
|
|
865
|
+
if (options.environment) {
|
|
800
866
|
headers.set("X-Relay-Environment", options.environment);
|
|
801
|
-
|
|
867
|
+
}
|
|
868
|
+
if (init.method && init.method !== "GET") {
|
|
869
|
+
headers.set("X-Relay-CSRF", "1");
|
|
870
|
+
}
|
|
802
871
|
const response = await fetch(options.baseUrl + path, {
|
|
803
872
|
...init,
|
|
804
873
|
signal: init.signal ? AbortSignal.any([init.signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3),
|
|
@@ -828,7 +897,9 @@ function createRelayClient(options) {
|
|
|
828
897
|
response = await send(path, init);
|
|
829
898
|
} catch (error) {
|
|
830
899
|
const transient = error instanceof RelayError && [500, 502, 503, 504].includes(error.status) || error instanceof TypeError || error instanceof DOMException && error.name === "TimeoutError";
|
|
831
|
-
if (!transient || attempt === 2)
|
|
900
|
+
if (!transient || attempt === 2) {
|
|
901
|
+
throw error;
|
|
902
|
+
}
|
|
832
903
|
await new Promise(
|
|
833
904
|
(resolve) => setTimeout(resolve, attempt === 0 ? 250 : 750)
|
|
834
905
|
);
|
|
@@ -840,8 +911,11 @@ function createRelayClient(options) {
|
|
|
840
911
|
}
|
|
841
912
|
function query(path, values) {
|
|
842
913
|
const params = new URLSearchParams();
|
|
843
|
-
for (const [key, value] of Object.entries(values))
|
|
844
|
-
if (value !== void 0)
|
|
914
|
+
for (const [key, value] of Object.entries(values)) {
|
|
915
|
+
if (value !== void 0) {
|
|
916
|
+
params.set(key, String(value));
|
|
917
|
+
}
|
|
918
|
+
}
|
|
845
919
|
return path + "?" + params;
|
|
846
920
|
}
|
|
847
921
|
function json(body) {
|
|
@@ -883,11 +957,15 @@ function createRelayClient(options) {
|
|
|
883
957
|
);
|
|
884
958
|
const observers = /* @__PURE__ */ new Set();
|
|
885
959
|
async function changeSession(task) {
|
|
886
|
-
for (const observer of observers)
|
|
960
|
+
for (const observer of observers) {
|
|
961
|
+
observer.clear();
|
|
962
|
+
}
|
|
887
963
|
try {
|
|
888
964
|
return await task();
|
|
889
965
|
} finally {
|
|
890
|
-
for (const observer of observers)
|
|
966
|
+
for (const observer of observers) {
|
|
967
|
+
void observer.refresh();
|
|
968
|
+
}
|
|
891
969
|
}
|
|
892
970
|
}
|
|
893
971
|
const experience = () => request("/v1/me/experience", questExperienceSchema);
|
|
@@ -895,7 +973,9 @@ function createRelayClient(options) {
|
|
|
895
973
|
const quest = (await experience()).quests.find(
|
|
896
974
|
(q) => q.quest === id || q.key === id
|
|
897
975
|
);
|
|
898
|
-
if (!quest)
|
|
976
|
+
if (!quest) {
|
|
977
|
+
throw new RelayError("Quest not found", 404);
|
|
978
|
+
}
|
|
899
979
|
return quest;
|
|
900
980
|
}
|
|
901
981
|
return {
|
|
@@ -924,18 +1004,21 @@ function createRelayClient(options) {
|
|
|
924
1004
|
quests: {
|
|
925
1005
|
get: getQuest,
|
|
926
1006
|
retry: async (view) => {
|
|
927
|
-
if (view.trigger.kind === "automatic")
|
|
1007
|
+
if (view.trigger.kind === "automatic") {
|
|
928
1008
|
throw new Error(
|
|
929
1009
|
"Automatic quests recover without participant retries."
|
|
930
1010
|
);
|
|
931
|
-
|
|
1011
|
+
}
|
|
1012
|
+
if (!view.attempt || view.attempt.status !== "failed") {
|
|
932
1013
|
throw new Error("There is no failed attempt to retry.");
|
|
1014
|
+
}
|
|
933
1015
|
const session = await request(
|
|
934
1016
|
"/v1/auth/session",
|
|
935
1017
|
sessionSchema.nullable()
|
|
936
1018
|
);
|
|
937
|
-
if (session?.principal.kind !== "member")
|
|
1019
|
+
if (session?.principal.kind !== "member") {
|
|
938
1020
|
throw new Error("Sign in to retry verification.");
|
|
1021
|
+
}
|
|
939
1022
|
return request(`/v1/attempts/${view.attempt.id}/retry`, attemptSchema, {
|
|
940
1023
|
...json({}),
|
|
941
1024
|
headers: {
|
|
@@ -949,7 +1032,9 @@ function createRelayClient(options) {
|
|
|
949
1032
|
const collection = (await experience()).collections.find(
|
|
950
1033
|
(c) => c.id === id || c.key === id
|
|
951
1034
|
);
|
|
952
|
-
if (!collection)
|
|
1035
|
+
if (!collection) {
|
|
1036
|
+
throw new RelayError("Collection not found", 404);
|
|
1037
|
+
}
|
|
953
1038
|
return collection;
|
|
954
1039
|
},
|
|
955
1040
|
open: questActions(submission, getQuest),
|
|
@@ -1110,8 +1195,9 @@ function createRelayClient(options) {
|
|
|
1110
1195
|
requestOptions
|
|
1111
1196
|
);
|
|
1112
1197
|
const type = response.headers.get("Content-Type");
|
|
1113
|
-
if (type !== "image/jpeg" && type !== "image/png")
|
|
1198
|
+
if (type !== "image/jpeg" && type !== "image/png") {
|
|
1114
1199
|
throw new RelayError("Unexpected evidence content type", 502);
|
|
1200
|
+
}
|
|
1115
1201
|
return response.blob();
|
|
1116
1202
|
}
|
|
1117
1203
|
},
|