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