@domino-sdk/relay 1.0.0-next.8 → 1.0.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/README.md +1 -1
- package/dist/activity-DeKJsRry.d.ts +9112 -0
- package/dist/authoring.d.ts +1135 -285
- package/dist/authoring.js +545 -269
- package/dist/automatic-outcomes-B1xrETxm.d.ts +758 -0
- package/dist/avatar.d.ts +2 -2
- package/dist/browser-DFe7sWSN.d.ts +4154 -0
- package/dist/browser.d.ts +11 -2528
- package/dist/browser.js +8 -6
- package/dist/capability-error-DNMwKmlL.d.ts +458 -0
- package/dist/{chunk-V7EZUZ3Z.js → chunk-36HTX2BM.js} +389 -169
- package/dist/chunk-6QJ3LADK.js +112 -0
- package/dist/{chunk-77H7APVD.js → chunk-BGGKNIRP.js} +1 -1
- package/dist/chunk-EYH5RDIE.js +496 -0
- package/dist/chunk-GQKYAQM6.js +84 -0
- package/dist/chunk-GUP7YRR4.js +42 -0
- package/dist/{chunk-V5R2DMGY.js → chunk-GZ6FJAXN.js} +380 -128
- package/dist/chunk-JON5SESL.js +2986 -0
- package/dist/chunk-MC6V6VMG.js +49 -0
- package/dist/chunk-NPGENI7K.js +37 -0
- package/dist/chunk-VFHHLN6A.js +284 -0
- package/dist/chunk-WJSSJ7AF.js +151 -0
- package/dist/{chunk-QBOHLT7M.js → chunk-XFMSUPNS.js} +1 -1
- package/dist/{chunk-6A6K5RUD.js → chunk-ZVIRG24J.js} +145 -42
- package/dist/{client-CK4nwt7V.d.ts → client-XiuFnug_.d.ts} +2193 -562
- package/dist/{submission-input-C9qm9ixd.d.ts → groups-Ck-OxzjD.d.ts} +919 -1142
- package/dist/identity-integration-GxAHdKR2.d.ts +58 -0
- package/dist/{images--vZ9U0kr.d.ts → images-Dv-P5VBw.d.ts} +2 -2
- package/dist/index.d.ts +13 -9
- package/dist/index.js +17 -6
- package/dist/internal.d.ts +82 -43
- package/dist/internal.js +58 -90
- package/dist/offers-Cy-e4o5x.d.ts +431 -0
- package/dist/{pages-BzqJuI69.d.ts → outcomes-kVB-Juzs.d.ts} +2386 -1490
- package/dist/pages-BBYD21IE.d.ts +359 -0
- package/dist/participant-rewards-3duLb8YJ.d.ts +668 -0
- package/dist/portal-proxy.js +22 -4
- package/dist/{profiles-DDMFBRKe.d.ts → profiles-D2aFcOxu.d.ts} +15 -15
- package/dist/react/index.d.ts +3122 -1011
- package/dist/react/index.js +1627 -779
- package/dist/request-error-DWduJCGV.d.ts +62 -0
- package/dist/schema-DfJzH2ci.d.ts +5644 -0
- package/dist/schema.d.ts +15 -4216
- package/dist/schema.js +322 -81
- package/dist/server/index.d.ts +81498 -0
- package/dist/server/index.js +2520 -0
- package/dist/{streaks-CwGReWGt.d.ts → streaks-CWyq8Px5.d.ts} +2 -4
- package/dist/submission-input-BZkm6ohU.d.ts +290 -0
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +1 -0
- package/dist/{wallet-BGOfFkG9.d.ts → wallet-YssWxGg8.d.ts} +35 -38
- package/package.json +8 -2
- package/dist/chunk-MRIURJE3.js +0 -257
- package/dist/chunk-VARGF4JS.js +0 -1733
- package/dist/chunk-ZWY3R6B3.js +0 -26
- package/dist/collection-BGneJ6Bk.d.ts +0 -114
- package/dist/integrations-C3Sp558f.d.ts +0 -1297
- package/dist/point-ledgers-iS00Z_v0.d.ts +0 -37
- package/dist/schema-pnuWqvGn.d.ts +0 -3396
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
collectionPassSchema,
|
|
3
|
-
|
|
3
|
+
participantActivityQuerySchema,
|
|
4
|
+
participantActivitySchema,
|
|
5
|
+
participantDeliveriesSchema,
|
|
6
|
+
participantEntitlementsSchema,
|
|
7
|
+
questClaimInputSchema,
|
|
8
|
+
questEnrollmentInputSchema,
|
|
9
|
+
questEnrollmentSchema,
|
|
4
10
|
questExperienceSchema,
|
|
11
|
+
questOutcomePageSchema,
|
|
12
|
+
questOutcomeQuerySchema,
|
|
13
|
+
questOutcomeSchema,
|
|
5
14
|
rewardDeliveriesSchema
|
|
6
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-EYH5RDIE.js";
|
|
7
16
|
import {
|
|
8
17
|
AVATAR_UPLOAD_LIMIT,
|
|
9
18
|
RelayError,
|
|
@@ -18,13 +27,19 @@ import {
|
|
|
18
27
|
leaderboardPlacementSchema,
|
|
19
28
|
leaderboardQuerySchema,
|
|
20
29
|
linkedWalletSchema,
|
|
30
|
+
offerSchema,
|
|
21
31
|
ownProfileSchema,
|
|
22
32
|
parseInput,
|
|
33
|
+
pointBalancesSchema,
|
|
34
|
+
pointHistoryPageSchema,
|
|
35
|
+
pointHistoryQuerySchema,
|
|
36
|
+
pointLedgerReferenceSchema,
|
|
23
37
|
profileCompletionPageSchema,
|
|
24
38
|
profileCompletionQuerySchema,
|
|
25
39
|
profileReferralSchema,
|
|
26
40
|
profileSummarySchema,
|
|
27
41
|
profileUpdateSchema,
|
|
42
|
+
redemptionSchema,
|
|
28
43
|
referralAcceptSchema,
|
|
29
44
|
referralDefinitionSchema,
|
|
30
45
|
referralHistorySchema,
|
|
@@ -35,31 +50,36 @@ import {
|
|
|
35
50
|
walletSignInNetworksSchema,
|
|
36
51
|
walletSignatureSchema,
|
|
37
52
|
walletUnlinkInputSchema
|
|
38
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-36HTX2BM.js";
|
|
54
|
+
import {
|
|
55
|
+
PARTICIPANT_TIMEOUT,
|
|
56
|
+
lifetime,
|
|
57
|
+
retryAfter,
|
|
58
|
+
retrying,
|
|
59
|
+
validTimeout
|
|
60
|
+
} from "./chunk-6QJ3LADK.js";
|
|
39
61
|
import {
|
|
40
62
|
exchangeResultSchema,
|
|
41
63
|
exchangeSchema,
|
|
42
64
|
memberSummarySchema,
|
|
43
65
|
participantSessionSchema,
|
|
44
66
|
sessionMember
|
|
45
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-XFMSUPNS.js";
|
|
46
68
|
import {
|
|
47
69
|
accountConnectionSchema,
|
|
48
70
|
attemptSchema,
|
|
49
71
|
authProviderSchema,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
contentEnrollmentInputSchema,
|
|
54
|
-
contentEnrollmentSchema,
|
|
72
|
+
campaignEnrollmentInputSchema,
|
|
73
|
+
campaignEnrollmentSchema,
|
|
74
|
+
campaignWithdrawalInputSchema,
|
|
55
75
|
contentPerformancePageSchema,
|
|
56
76
|
contentPerformanceQuerySchema,
|
|
57
77
|
contentQualificationPageSchema,
|
|
58
78
|
contentQualificationQuerySchema,
|
|
59
|
-
contentReleaseInputSchema,
|
|
60
79
|
evidenceSchema,
|
|
61
80
|
identifier,
|
|
62
81
|
memberProgressSchema,
|
|
82
|
+
missingPrerequisiteSchema,
|
|
63
83
|
pageQuerySchema,
|
|
64
84
|
quizEvidenceSchema,
|
|
65
85
|
submissionEvidenceSchema,
|
|
@@ -67,7 +87,7 @@ import {
|
|
|
67
87
|
tieredRewardReferenceSchema,
|
|
68
88
|
validateSubmissionValue,
|
|
69
89
|
valueEvidenceSchema
|
|
70
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-JON5SESL.js";
|
|
71
91
|
|
|
72
92
|
// src/submissions.ts
|
|
73
93
|
import { z } from "zod";
|
|
@@ -434,6 +454,20 @@ function observeProgress(transport, listener, options = {}, onDispose = () => {
|
|
|
434
454
|
let timer;
|
|
435
455
|
let sessionId;
|
|
436
456
|
let failures = 0;
|
|
457
|
+
const visible = () => typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
458
|
+
const visibility = () => {
|
|
459
|
+
if (visible()) {
|
|
460
|
+
void refresh();
|
|
461
|
+
} else {
|
|
462
|
+
controller?.abort();
|
|
463
|
+
if (timer !== void 0) {
|
|
464
|
+
clearTimeout(timer);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
if (typeof document !== "undefined") {
|
|
469
|
+
document.addEventListener("visibilitychange", visibility);
|
|
470
|
+
}
|
|
437
471
|
function clear() {
|
|
438
472
|
controller?.abort();
|
|
439
473
|
if (timer !== void 0) {
|
|
@@ -445,7 +479,7 @@ function observeProgress(transport, listener, options = {}, onDispose = () => {
|
|
|
445
479
|
}
|
|
446
480
|
}
|
|
447
481
|
async function refresh() {
|
|
448
|
-
if (disposed) {
|
|
482
|
+
if (disposed || !visible()) {
|
|
449
483
|
return;
|
|
450
484
|
}
|
|
451
485
|
controller?.abort();
|
|
@@ -509,6 +543,9 @@ function observeProgress(transport, listener, options = {}, onDispose = () => {
|
|
|
509
543
|
return;
|
|
510
544
|
}
|
|
511
545
|
disposed = true;
|
|
546
|
+
if (typeof document !== "undefined") {
|
|
547
|
+
document.removeEventListener("visibilitychange", visibility);
|
|
548
|
+
}
|
|
512
549
|
controller?.abort();
|
|
513
550
|
if (timer !== void 0) {
|
|
514
551
|
clearTimeout(timer);
|
|
@@ -569,7 +606,7 @@ function questActions(submission, latest) {
|
|
|
569
606
|
input: "none",
|
|
570
607
|
release: view.release,
|
|
571
608
|
recoverPrevious: true,
|
|
572
|
-
repeatable: view.completion.kind === "keyed"
|
|
609
|
+
repeatable: view.completion.kind === "keyed" || view.completion.kind === "progress"
|
|
573
610
|
});
|
|
574
611
|
return {
|
|
575
612
|
kind: "status",
|
|
@@ -587,7 +624,7 @@ function questActions(submission, latest) {
|
|
|
587
624
|
input: kind,
|
|
588
625
|
release: view.release,
|
|
589
626
|
recoverPrevious: true,
|
|
590
|
-
repeatable: view.completion.kind === "keyed"
|
|
627
|
+
repeatable: view.completion.kind === "keyed" || view.completion.kind === "progress"
|
|
591
628
|
});
|
|
592
629
|
return {
|
|
593
630
|
kind,
|
|
@@ -618,7 +655,7 @@ function questActions(submission, latest) {
|
|
|
618
655
|
input: "photo",
|
|
619
656
|
release: view.release,
|
|
620
657
|
recoverPrevious: true,
|
|
621
|
-
repeatable: view.completion.kind === "keyed"
|
|
658
|
+
repeatable: view.completion.kind === "keyed" || view.completion.kind === "progress"
|
|
622
659
|
});
|
|
623
660
|
return {
|
|
624
661
|
kind: "photo",
|
|
@@ -634,7 +671,7 @@ function questActions(submission, latest) {
|
|
|
634
671
|
input: "quiz",
|
|
635
672
|
release: view.release,
|
|
636
673
|
recoverPrevious: true,
|
|
637
|
-
repeatable: view.completion.kind === "keyed"
|
|
674
|
+
repeatable: view.completion.kind === "keyed" || view.completion.kind === "progress"
|
|
638
675
|
});
|
|
639
676
|
return {
|
|
640
677
|
kind: "quiz",
|
|
@@ -650,7 +687,7 @@ function questActions(submission, latest) {
|
|
|
650
687
|
input: "none",
|
|
651
688
|
release: view.release,
|
|
652
689
|
recoverPrevious: true,
|
|
653
|
-
repeatable: view.completion.kind === "keyed"
|
|
690
|
+
repeatable: view.completion.kind === "keyed" || view.completion.kind === "progress"
|
|
654
691
|
});
|
|
655
692
|
return {
|
|
656
693
|
kind: "claim",
|
|
@@ -666,22 +703,35 @@ function questActions(submission, latest) {
|
|
|
666
703
|
}
|
|
667
704
|
|
|
668
705
|
// src/client.ts
|
|
669
|
-
var TIMEOUT_MS = 15e3;
|
|
670
|
-
var missingEntrySchema = z2.object({ quest: identifier, title: z2.string() });
|
|
671
706
|
function errorBody(value) {
|
|
672
707
|
const body = value !== null && typeof value === "object" ? value : {};
|
|
673
708
|
return {
|
|
674
709
|
message: typeof body.error === "string" && body.error.trim() ? body.error : void 0,
|
|
675
710
|
code: typeof body.code === "string" && body.code.trim() ? body.code : void 0,
|
|
676
711
|
missing: Array.isArray(body.missing) ? body.missing.flatMap((entry) => {
|
|
677
|
-
const parsed =
|
|
712
|
+
const parsed = missingPrerequisiteSchema.safeParse(entry);
|
|
678
713
|
return parsed.success ? [parsed.data] : [];
|
|
679
714
|
}) : []
|
|
680
715
|
};
|
|
681
716
|
}
|
|
682
|
-
async function fromSigner(step) {
|
|
717
|
+
async function fromSigner(step, signal) {
|
|
718
|
+
let stop;
|
|
683
719
|
try {
|
|
684
|
-
return await
|
|
720
|
+
return await new Promise((resolve, reject) => {
|
|
721
|
+
stop = () => reject(
|
|
722
|
+
new RelayError("The request was cancelled", {
|
|
723
|
+
kind: "aborted",
|
|
724
|
+
code: RelayErrorCode.Aborted,
|
|
725
|
+
cause: signal?.reason
|
|
726
|
+
})
|
|
727
|
+
);
|
|
728
|
+
if (signal?.aborted) {
|
|
729
|
+
stop();
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
signal?.addEventListener("abort", stop, { once: true });
|
|
733
|
+
step().then(resolve, reject);
|
|
734
|
+
});
|
|
685
735
|
} catch (error) {
|
|
686
736
|
if (error instanceof RelayError) {
|
|
687
737
|
throw error;
|
|
@@ -691,8 +741,16 @@ async function fromSigner(step) {
|
|
|
691
741
|
RelayErrorCode.WalletSigner,
|
|
692
742
|
{ cause: error }
|
|
693
743
|
);
|
|
744
|
+
} finally {
|
|
745
|
+
if (stop) {
|
|
746
|
+
signal?.removeEventListener("abort", stop);
|
|
747
|
+
}
|
|
694
748
|
}
|
|
695
749
|
}
|
|
750
|
+
var invalidTimeout = () => clientError(
|
|
751
|
+
"timeout must be a positive, finite number of milliseconds",
|
|
752
|
+
RelayErrorCode.InvalidInput
|
|
753
|
+
);
|
|
696
754
|
var uncertainStatuses = /* @__PURE__ */ new Set([500, 502, 504]);
|
|
697
755
|
var methodOf = (call) => call.method ?? (call.json === void 0 ? "GET" : "POST");
|
|
698
756
|
function withQuery(path, values) {
|
|
@@ -705,22 +763,27 @@ function withQuery(path, values) {
|
|
|
705
763
|
const search = params.toString();
|
|
706
764
|
return search ? path + "?" + search : path;
|
|
707
765
|
}
|
|
708
|
-
function interrupted(call, error, stage) {
|
|
766
|
+
function interrupted(call, life, error, stage) {
|
|
709
767
|
const details = {
|
|
710
768
|
status: stage.status,
|
|
769
|
+
retryAfter: stage.headers ? retryAfter(stage.headers) : void 0,
|
|
711
770
|
operationId: call.operationId,
|
|
712
|
-
|
|
771
|
+
replaySafe: call.replaySafe,
|
|
772
|
+
uncertain: stage.sent && methodOf(call) !== "GET" && // An error status whose body broke off still refused the write, unless the status itself
|
|
773
|
+
// does not prove that (500, 502, 504).
|
|
774
|
+
(!stage.refused || uncertainStatuses.has(stage.status ?? 0)),
|
|
713
775
|
cause: error
|
|
714
776
|
};
|
|
715
|
-
|
|
777
|
+
const ended = life.ended();
|
|
778
|
+
if (ended === "aborted") {
|
|
716
779
|
return new RelayError("The request was cancelled", {
|
|
717
780
|
...details,
|
|
718
781
|
kind: "aborted",
|
|
719
782
|
code: RelayErrorCode.Aborted
|
|
720
783
|
});
|
|
721
784
|
}
|
|
722
|
-
if (
|
|
723
|
-
return new RelayError(
|
|
785
|
+
if (ended === "timeout") {
|
|
786
|
+
return new RelayError(`Relay did not respond within ${life.timeout} ms`, {
|
|
724
787
|
...details,
|
|
725
788
|
kind: "timeout",
|
|
726
789
|
code: RelayErrorCode.Timeout
|
|
@@ -732,7 +795,11 @@ function interrupted(call, error, stage) {
|
|
|
732
795
|
);
|
|
733
796
|
}
|
|
734
797
|
function createRelayClient(options) {
|
|
735
|
-
|
|
798
|
+
if (options.timeout !== void 0 && !validTimeout(options.timeout)) {
|
|
799
|
+
throw invalidTimeout();
|
|
800
|
+
}
|
|
801
|
+
const clientTimeout = options.timeout ?? PARTICIPANT_TIMEOUT;
|
|
802
|
+
async function send(path, call, life) {
|
|
736
803
|
const method = methodOf(call);
|
|
737
804
|
const headers = new Headers(call.headers);
|
|
738
805
|
let body2 = call.body;
|
|
@@ -758,11 +825,8 @@ function createRelayClient(options) {
|
|
|
758
825
|
if (method !== "GET") {
|
|
759
826
|
headers.set("X-Relay-CSRF", "1");
|
|
760
827
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
call.deadline = deadline;
|
|
764
|
-
if (caller?.aborted) {
|
|
765
|
-
throw interrupted(call, caller.reason, { sent: false });
|
|
828
|
+
if (life.signal.aborted) {
|
|
829
|
+
throw interrupted(call, life, life.reason(), { sent: false });
|
|
766
830
|
}
|
|
767
831
|
let response;
|
|
768
832
|
try {
|
|
@@ -770,13 +834,23 @@ function createRelayClient(options) {
|
|
|
770
834
|
method,
|
|
771
835
|
headers,
|
|
772
836
|
body: body2,
|
|
773
|
-
signal:
|
|
837
|
+
signal: life.signal
|
|
774
838
|
});
|
|
775
839
|
} catch (error) {
|
|
776
|
-
throw interrupted(call, error, { sent: true });
|
|
840
|
+
throw interrupted(call, life, error, { sent: true });
|
|
777
841
|
}
|
|
778
842
|
if (!response.ok) {
|
|
779
|
-
|
|
843
|
+
let text;
|
|
844
|
+
try {
|
|
845
|
+
text = await response.text();
|
|
846
|
+
} catch (error) {
|
|
847
|
+
throw interrupted(call, life, error, {
|
|
848
|
+
sent: true,
|
|
849
|
+
status: response.status,
|
|
850
|
+
headers: response.headers,
|
|
851
|
+
refused: true
|
|
852
|
+
});
|
|
853
|
+
}
|
|
780
854
|
let value = null;
|
|
781
855
|
try {
|
|
782
856
|
value = JSON.parse(text);
|
|
@@ -789,16 +863,40 @@ function createRelayClient(options) {
|
|
|
789
863
|
code,
|
|
790
864
|
missing,
|
|
791
865
|
operationId: call.operationId,
|
|
792
|
-
|
|
866
|
+
replaySafe: call.replaySafe,
|
|
867
|
+
uncertain: methodOf(call) !== "GET" && uncertainStatuses.has(response.status),
|
|
868
|
+
retryAfter: retryAfter(response.headers)
|
|
793
869
|
});
|
|
794
870
|
}
|
|
795
871
|
return response;
|
|
796
872
|
}
|
|
797
|
-
async function
|
|
873
|
+
async function exchange(path, call, receive) {
|
|
874
|
+
const timeout = call.options?.timeout ?? clientTimeout;
|
|
875
|
+
if (!validTimeout(timeout)) {
|
|
876
|
+
throw invalidTimeout();
|
|
877
|
+
}
|
|
878
|
+
const life = lifetime(timeout, call.options?.signal);
|
|
879
|
+
try {
|
|
880
|
+
return await retrying(
|
|
881
|
+
life,
|
|
882
|
+
{
|
|
883
|
+
replaySafe: methodOf(call) === "GET" || (call.replaySafe ?? call.operationId !== void 0)
|
|
884
|
+
},
|
|
885
|
+
async () => receive(await send(path, call, life), life)
|
|
886
|
+
);
|
|
887
|
+
} finally {
|
|
888
|
+
life.end();
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
async function body(response, call, life, reader) {
|
|
798
892
|
try {
|
|
799
893
|
return await reader(response);
|
|
800
894
|
} catch (error) {
|
|
801
|
-
throw interrupted(call, error, {
|
|
895
|
+
throw interrupted(call, life, error, {
|
|
896
|
+
sent: true,
|
|
897
|
+
status: response.status,
|
|
898
|
+
headers: response.headers
|
|
899
|
+
});
|
|
802
900
|
}
|
|
803
901
|
}
|
|
804
902
|
function invalidResponse(status, call, cause) {
|
|
@@ -809,14 +907,15 @@ function createRelayClient(options) {
|
|
|
809
907
|
code: RelayErrorCode.InvalidResponse,
|
|
810
908
|
status,
|
|
811
909
|
operationId: call.operationId,
|
|
910
|
+
replaySafe: call.replaySafe,
|
|
812
911
|
// Relay accepted the write, but this client cannot tell what it did.
|
|
813
912
|
uncertain: methodOf(call) !== "GET",
|
|
814
913
|
cause
|
|
815
914
|
}
|
|
816
915
|
);
|
|
817
916
|
}
|
|
818
|
-
async function read(response, schema, call) {
|
|
819
|
-
const text = await body(response, call, (value2) => value2.text());
|
|
917
|
+
async function read(response, schema, call, life) {
|
|
918
|
+
const text = await body(response, call, life, (value2) => value2.text());
|
|
820
919
|
let value;
|
|
821
920
|
try {
|
|
822
921
|
value = JSON.parse(text);
|
|
@@ -830,38 +929,48 @@ function createRelayClient(options) {
|
|
|
830
929
|
return parsed.data;
|
|
831
930
|
}
|
|
832
931
|
async function request(path, schema, call = {}) {
|
|
833
|
-
return
|
|
932
|
+
return exchange(
|
|
933
|
+
path,
|
|
934
|
+
call,
|
|
935
|
+
(response, life) => read(response, schema, call, life)
|
|
936
|
+
);
|
|
834
937
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
throw error;
|
|
844
|
-
}
|
|
845
|
-
await new Promise(
|
|
846
|
-
(resolve) => setTimeout(resolve, attempt === 0 ? 250 : 750)
|
|
847
|
-
);
|
|
848
|
-
continue;
|
|
849
|
-
}
|
|
850
|
-
return read(response, schema, call);
|
|
851
|
-
}
|
|
938
|
+
const unusedChallengeExpires = true;
|
|
939
|
+
async function linkVerify(value, session) {
|
|
940
|
+
const proof = parseInput(walletSignatureSchema, value);
|
|
941
|
+
return request("/v1/me/wallets/verify", linkedWalletSchema, {
|
|
942
|
+
json: proof,
|
|
943
|
+
options: session,
|
|
944
|
+
operationId: proof.challenge
|
|
945
|
+
});
|
|
852
946
|
}
|
|
853
947
|
const readSession = (requestOptions) => request("/v1/auth/session", participantSessionSchema.nullable(), {
|
|
854
|
-
options: {
|
|
948
|
+
options: {
|
|
949
|
+
signal: requestOptions?.signal,
|
|
950
|
+
timeout: requestOptions?.timeout
|
|
951
|
+
}
|
|
855
952
|
});
|
|
856
953
|
async function bound(requestOptions = {}) {
|
|
857
954
|
if (requestOptions.session) {
|
|
858
955
|
return requestOptions;
|
|
859
956
|
}
|
|
860
|
-
const
|
|
957
|
+
const timeout = requestOptions.timeout ?? clientTimeout;
|
|
958
|
+
if (!validTimeout(timeout)) {
|
|
959
|
+
throw invalidTimeout();
|
|
960
|
+
}
|
|
961
|
+
const started = Date.now();
|
|
962
|
+
const session = await readSession({ ...requestOptions, timeout });
|
|
861
963
|
if (session?.principal.kind !== "member") {
|
|
862
964
|
throw clientError("Sign in to continue", RelayErrorCode.NotSignedIn);
|
|
863
965
|
}
|
|
864
|
-
|
|
966
|
+
const remaining = timeout - (Date.now() - started);
|
|
967
|
+
if (remaining <= 0) {
|
|
968
|
+
throw new RelayError(`Relay did not respond within ${timeout} ms`, {
|
|
969
|
+
kind: "timeout",
|
|
970
|
+
code: RelayErrorCode.Timeout
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
return { ...requestOptions, session: session.id, timeout: remaining };
|
|
865
974
|
}
|
|
866
975
|
const submission = submissions(
|
|
867
976
|
{
|
|
@@ -914,6 +1023,25 @@ function createRelayClient(options) {
|
|
|
914
1023
|
const ok = z2.object({ ok: z2.literal(true) });
|
|
915
1024
|
const acknowledged = z2.object({ ok: z2.boolean() });
|
|
916
1025
|
return {
|
|
1026
|
+
offers: {
|
|
1027
|
+
/** Offers available to this participant, including stock and per-member availability. */
|
|
1028
|
+
list: (requestOptions) => request("/v1/offers", z2.array(offerSchema), {
|
|
1029
|
+
options: requestOptions
|
|
1030
|
+
}),
|
|
1031
|
+
/** Redeems one offer. Persist `actionId` to retry after an uncertain result. */
|
|
1032
|
+
redeem: (offer, input = {}, requestOptions) => {
|
|
1033
|
+
const actionId = identifier.parse(input.actionId ?? crypto.randomUUID());
|
|
1034
|
+
return request(
|
|
1035
|
+
`/v1/offers/${identifier.parse(offer)}/redeem`,
|
|
1036
|
+
redemptionSchema,
|
|
1037
|
+
{
|
|
1038
|
+
json: { actionId },
|
|
1039
|
+
options: requestOptions,
|
|
1040
|
+
operationId: actionId
|
|
1041
|
+
}
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
},
|
|
917
1045
|
referrals: {
|
|
918
1046
|
list: (requestOptions) => request("/v1/referrals", z2.array(referralDefinitionSchema), {
|
|
919
1047
|
options: requestOptions
|
|
@@ -936,6 +1064,14 @@ function createRelayClient(options) {
|
|
|
936
1064
|
});
|
|
937
1065
|
}
|
|
938
1066
|
},
|
|
1067
|
+
rewards: {
|
|
1068
|
+
/** The signed-in member's earned rewards, including pickup allocation and collection state. */
|
|
1069
|
+
entitlements: (options2) => request("/v1/me/entitlements", participantEntitlementsSchema, {
|
|
1070
|
+
options: options2
|
|
1071
|
+
}),
|
|
1072
|
+
/** Point awards, current tier entitlements, and integration delivery states. */
|
|
1073
|
+
deliveries: (options2) => request("/v1/me/deliveries", participantDeliveriesSchema, { options: options2 })
|
|
1074
|
+
},
|
|
939
1075
|
tieredRewards: {
|
|
940
1076
|
/**
|
|
941
1077
|
* The project's tiered rewards with their keys. Entitlements and reward
|
|
@@ -997,7 +1133,8 @@ function createRelayClient(options) {
|
|
|
997
1133
|
}),
|
|
998
1134
|
/**
|
|
999
1135
|
* Uploads a JPEG, PNG or WebP of at most 5 MB as the member's avatar. The server crops it to
|
|
1000
|
-
* a 512×512 WebP without metadata.
|
|
1136
|
+
* a 512×512 WebP without metadata. The client never resends it: retrying with the same
|
|
1137
|
+
* `uploadId` and file stores no second picture, but only before another upload replaces it.
|
|
1001
1138
|
*/
|
|
1002
1139
|
uploadAvatar: async (file, requestOptions = {}) => {
|
|
1003
1140
|
if (!avatarUploadTypes.includes(file.type)) {
|
|
@@ -1022,7 +1159,10 @@ function createRelayClient(options) {
|
|
|
1022
1159
|
"X-Relay-Upload-ID": operationId
|
|
1023
1160
|
},
|
|
1024
1161
|
options: rest,
|
|
1025
|
-
operationId
|
|
1162
|
+
operationId,
|
|
1163
|
+
// Relay keeps no receipt for an upload ID: replaying an upload another one replaced
|
|
1164
|
+
// would make it current again. Retry only on purpose, with the same file.
|
|
1165
|
+
replaySafe: false
|
|
1026
1166
|
});
|
|
1027
1167
|
},
|
|
1028
1168
|
/**
|
|
@@ -1058,6 +1198,45 @@ function createRelayClient(options) {
|
|
|
1058
1198
|
},
|
|
1059
1199
|
quests: {
|
|
1060
1200
|
get: getQuest,
|
|
1201
|
+
/**
|
|
1202
|
+
* Enrolls the signed-in member in a quest whose tracker trigger requires enrollment. Activity
|
|
1203
|
+
* counts from then on; earlier activity is historical, or skipped with `before-enrollment`
|
|
1204
|
+
* on a quest that counts from enrollment. Enrolling again returns the first enrollment.
|
|
1205
|
+
*/
|
|
1206
|
+
enroll: async (value, requestOptions) => {
|
|
1207
|
+
const body2 = parseInput(questEnrollmentInputSchema, value);
|
|
1208
|
+
return request("/v1/quests/enrollments", questEnrollmentSchema, {
|
|
1209
|
+
json: body2,
|
|
1210
|
+
options: requestOptions,
|
|
1211
|
+
operationId: body2.actionId
|
|
1212
|
+
});
|
|
1213
|
+
},
|
|
1214
|
+
/**
|
|
1215
|
+
* Claims one of the signed-in member's outcomes skipped as `historical`, on a quest whose
|
|
1216
|
+
* historical policy is `"claim"` (`outcome.claimable`). Relay decides it again with
|
|
1217
|
+
* historical activity released and returns the outcome; a rule that still fails rejects
|
|
1218
|
+
* with a `RelayError` whose code is that rule's skip code.
|
|
1219
|
+
*/
|
|
1220
|
+
claim: async (value, requestOptions) => {
|
|
1221
|
+
const body2 = parseInput(questClaimInputSchema, value);
|
|
1222
|
+
return request("/v1/quests/claims", questOutcomeSchema, {
|
|
1223
|
+
json: body2,
|
|
1224
|
+
options: requestOptions,
|
|
1225
|
+
operationId: body2.actionId
|
|
1226
|
+
});
|
|
1227
|
+
},
|
|
1228
|
+
/**
|
|
1229
|
+
* Why automatic quests did or did not run for the signed-in member's activity, newest first:
|
|
1230
|
+
* each admission, and each skip with its stable code.
|
|
1231
|
+
*/
|
|
1232
|
+
outcomes: async (query = {}, requestOptions) => request(
|
|
1233
|
+
withQuery(
|
|
1234
|
+
"/v1/quests/outcomes",
|
|
1235
|
+
parseInput(questOutcomeQuerySchema, query)
|
|
1236
|
+
),
|
|
1237
|
+
questOutcomePageSchema,
|
|
1238
|
+
{ options: requestOptions }
|
|
1239
|
+
),
|
|
1061
1240
|
/** Retries verification of the quest's failed attempt. */
|
|
1062
1241
|
retry: async (view, requestOptions) => {
|
|
1063
1242
|
if (view.trigger.kind === "automatic") {
|
|
@@ -1106,25 +1285,43 @@ function createRelayClient(options) {
|
|
|
1106
1285
|
return { refresh: observer.refresh, dispose: observer.dispose };
|
|
1107
1286
|
}
|
|
1108
1287
|
},
|
|
1288
|
+
campaigns: {
|
|
1289
|
+
/** Enrolls the signed-in member once in every quest of a campaign that requires it. */
|
|
1290
|
+
enroll: async (value, requestOptions) => {
|
|
1291
|
+
const body2 = parseInput(campaignEnrollmentInputSchema, value);
|
|
1292
|
+
return request("/v1/campaigns/enrollments", campaignEnrollmentSchema, {
|
|
1293
|
+
json: body2,
|
|
1294
|
+
options: requestOptions,
|
|
1295
|
+
operationId: body2.actionId
|
|
1296
|
+
});
|
|
1297
|
+
},
|
|
1298
|
+
/** Pauses the signed-in member's campaign enrollment without erasing its history. */
|
|
1299
|
+
withdraw: async (value, requestOptions) => {
|
|
1300
|
+
const body2 = parseInput(campaignWithdrawalInputSchema, value);
|
|
1301
|
+
return request(
|
|
1302
|
+
"/v1/campaigns/enrollments/withdraw",
|
|
1303
|
+
campaignEnrollmentSchema,
|
|
1304
|
+
{
|
|
1305
|
+
json: body2,
|
|
1306
|
+
options: requestOptions,
|
|
1307
|
+
operationId: body2.actionId
|
|
1308
|
+
}
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
},
|
|
1109
1312
|
/** The signed-in member's linked Solana, EVM and Cosmos wallets for this project. */
|
|
1110
1313
|
wallets: {
|
|
1111
|
-
list: (requestOptions) =>
|
|
1314
|
+
list: (requestOptions) => request(
|
|
1112
1315
|
"/v1/me/wallets",
|
|
1113
1316
|
z2.object({ wallets: z2.array(linkedWalletSchema) }),
|
|
1114
1317
|
{ options: requestOptions }
|
|
1115
1318
|
).then((result) => result.wallets),
|
|
1116
|
-
challenge: async (value, requestOptions) =>
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
json: parseInput(walletAddressInputSchema, value),
|
|
1121
|
-
options: await bound(requestOptions)
|
|
1122
|
-
}
|
|
1123
|
-
),
|
|
1124
|
-
verify: async (value, requestOptions) => walletRequest("/v1/me/wallets/verify", linkedWalletSchema, {
|
|
1125
|
-
json: parseInput(walletSignatureSchema, value),
|
|
1126
|
-
options: await bound(requestOptions)
|
|
1319
|
+
challenge: async (value, requestOptions) => request("/v1/me/wallets/challenge", walletMessageChallengeSchema, {
|
|
1320
|
+
json: parseInput(walletAddressInputSchema, value),
|
|
1321
|
+
options: await bound(requestOptions),
|
|
1322
|
+
replaySafe: unusedChallengeExpires
|
|
1127
1323
|
}),
|
|
1324
|
+
verify: async (value, requestOptions) => linkVerify(value, await bound(requestOptions)),
|
|
1128
1325
|
/** The address stays bound to this member: another member still cannot link it. */
|
|
1129
1326
|
unlink: async (wallet, requestOptions) => request("/v1/me/wallets/unlink", ok, {
|
|
1130
1327
|
json: parseInput(walletUnlinkInputSchema, {
|
|
@@ -1133,30 +1330,36 @@ function createRelayClient(options) {
|
|
|
1133
1330
|
}),
|
|
1134
1331
|
options: requestOptions
|
|
1135
1332
|
}),
|
|
1136
|
-
/**
|
|
1333
|
+
/**
|
|
1334
|
+
* Asks the signer for its address, signs the returned challenge, and links the wallet. The
|
|
1335
|
+
* session read and the challenge share one deadline, and the verification gets its own:
|
|
1336
|
+
* the time the member takes in the wallet counts against neither.
|
|
1337
|
+
*/
|
|
1137
1338
|
link: async (signer, requestOptions) => {
|
|
1138
1339
|
const session = await bound(requestOptions);
|
|
1139
|
-
const challenge = await
|
|
1340
|
+
const challenge = await request(
|
|
1140
1341
|
"/v1/me/wallets/challenge",
|
|
1141
1342
|
walletMessageChallengeSchema,
|
|
1142
1343
|
{
|
|
1143
1344
|
json: parseInput(
|
|
1144
1345
|
walletAddressInputSchema,
|
|
1145
|
-
await fromSigner(
|
|
1346
|
+
await fromSigner(
|
|
1347
|
+
() => walletAddressInput(signer),
|
|
1348
|
+
requestOptions?.signal
|
|
1349
|
+
)
|
|
1146
1350
|
),
|
|
1147
|
-
options: session
|
|
1351
|
+
options: session,
|
|
1352
|
+
replaySafe: unusedChallengeExpires
|
|
1148
1353
|
}
|
|
1149
1354
|
);
|
|
1150
1355
|
const proof = await fromSigner(
|
|
1151
|
-
() => signer.sign(challenge.message, challenge.address)
|
|
1356
|
+
() => signer.sign(challenge.message, challenge.address),
|
|
1357
|
+
requestOptions?.signal
|
|
1358
|
+
);
|
|
1359
|
+
return linkVerify(
|
|
1360
|
+
{ challenge: challenge.id, ...proof },
|
|
1361
|
+
{ ...requestOptions, session: session.session }
|
|
1152
1362
|
);
|
|
1153
|
-
return walletRequest("/v1/me/wallets/verify", linkedWalletSchema, {
|
|
1154
|
-
json: parseInput(walletSignatureSchema, {
|
|
1155
|
-
challenge: challenge.id,
|
|
1156
|
-
...proof
|
|
1157
|
-
}),
|
|
1158
|
-
options: session
|
|
1159
|
-
});
|
|
1160
1363
|
}
|
|
1161
1364
|
},
|
|
1162
1365
|
auth: {
|
|
@@ -1170,8 +1373,8 @@ function createRelayClient(options) {
|
|
|
1170
1373
|
})
|
|
1171
1374
|
),
|
|
1172
1375
|
email: z2.boolean(),
|
|
1173
|
-
/** The wallet sign-in this project enabled
|
|
1174
|
-
wallets: walletSignInNetworksSchema
|
|
1376
|
+
/** The wallet sign-in this project enabled. */
|
|
1377
|
+
wallets: walletSignInNetworksSchema,
|
|
1175
1378
|
demo: z2.boolean()
|
|
1176
1379
|
}),
|
|
1177
1380
|
{ options: requestOptions }
|
|
@@ -1230,13 +1433,17 @@ function createRelayClient(options) {
|
|
|
1230
1433
|
{
|
|
1231
1434
|
json: parseInput(
|
|
1232
1435
|
walletAddressInputSchema,
|
|
1233
|
-
await fromSigner(
|
|
1436
|
+
await fromSigner(
|
|
1437
|
+
() => walletAddressInput(signer),
|
|
1438
|
+
requestOptions?.signal
|
|
1439
|
+
)
|
|
1234
1440
|
),
|
|
1235
1441
|
options: requestOptions
|
|
1236
1442
|
}
|
|
1237
1443
|
);
|
|
1238
1444
|
const proof = await fromSigner(
|
|
1239
|
-
() => signer.sign(challenge.message, challenge.address)
|
|
1445
|
+
() => signer.sign(challenge.message, challenge.address),
|
|
1446
|
+
requestOptions?.signal
|
|
1240
1447
|
);
|
|
1241
1448
|
return changeSession(
|
|
1242
1449
|
() => request("/v1/auth/wallet/verify", participantSessionSchema, {
|
|
@@ -1271,6 +1478,57 @@ function createRelayClient(options) {
|
|
|
1271
1478
|
})
|
|
1272
1479
|
},
|
|
1273
1480
|
me: {
|
|
1481
|
+
activity: {
|
|
1482
|
+
list: (query = {}, options2) => {
|
|
1483
|
+
const parsed = parseInput(participantActivityQuerySchema, query);
|
|
1484
|
+
return request(
|
|
1485
|
+
withQuery("/v1/me/activity", parsed),
|
|
1486
|
+
participantActivitySchema,
|
|
1487
|
+
{ options: options2 }
|
|
1488
|
+
);
|
|
1489
|
+
},
|
|
1490
|
+
observe: (listener, options2 = {}) => {
|
|
1491
|
+
let cursor = options2.cursor;
|
|
1492
|
+
let owner;
|
|
1493
|
+
const observer = observeProgress(
|
|
1494
|
+
{
|
|
1495
|
+
session: (signal) => readSession({ signal }),
|
|
1496
|
+
progress: (session, signal) => request(
|
|
1497
|
+
withQuery("/v1/me/activity", { cursor }),
|
|
1498
|
+
participantActivitySchema,
|
|
1499
|
+
{ options: { session, signal } }
|
|
1500
|
+
)
|
|
1501
|
+
},
|
|
1502
|
+
(state) => {
|
|
1503
|
+
if (state.kind === "session") {
|
|
1504
|
+
if (owner !== void 0 && owner !== (state.session?.id ?? null)) {
|
|
1505
|
+
cursor = void 0;
|
|
1506
|
+
}
|
|
1507
|
+
owner = state.session?.id ?? null;
|
|
1508
|
+
}
|
|
1509
|
+
if (state.kind === "ready") {
|
|
1510
|
+
for (const event of state.progress.items) {
|
|
1511
|
+
listener(event);
|
|
1512
|
+
}
|
|
1513
|
+
cursor = state.progress.nextCursor;
|
|
1514
|
+
if (state.progress.items.length === 50) {
|
|
1515
|
+
queueMicrotask(() => void observer.refresh());
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
options2.onState?.(state);
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
intervalMs: options2.intervalMs ?? 5e3,
|
|
1522
|
+
maxBackoffMs: options2.maxBackoffMs
|
|
1523
|
+
},
|
|
1524
|
+
() => observers.delete(observer)
|
|
1525
|
+
);
|
|
1526
|
+
observers.add(observer);
|
|
1527
|
+
observer.clear();
|
|
1528
|
+
void observer.refresh();
|
|
1529
|
+
return { refresh: observer.refresh, dispose: observer.dispose };
|
|
1530
|
+
}
|
|
1531
|
+
},
|
|
1274
1532
|
/** The signed-in member, and the seed of their generated avatar: `defaultAvatar(avatarSeed)`. */
|
|
1275
1533
|
get: async (requestOptions) => request("/v1/me", memberSummarySchema, { options: requestOptions }),
|
|
1276
1534
|
observeProgress: (listener, observeOptions) => {
|
|
@@ -1301,6 +1559,22 @@ function createRelayClient(options) {
|
|
|
1301
1559
|
rewardDeliveries: (requestOptions) => request("/v1/reward-deliveries", rewardDeliveriesSchema, {
|
|
1302
1560
|
options: requestOptions
|
|
1303
1561
|
}),
|
|
1562
|
+
/**
|
|
1563
|
+
* `{ items }`: the member's balance in each of the project's point ledgers, in the project's
|
|
1564
|
+
* ledger order, zero in a ledger they have no entries in.
|
|
1565
|
+
*/
|
|
1566
|
+
balances: (requestOptions) => request("/v1/me/points", pointBalancesSchema, {
|
|
1567
|
+
options: requestOptions
|
|
1568
|
+
}),
|
|
1569
|
+
/** The member's own entries in one ledger, by key or ID, most recently recorded first. */
|
|
1570
|
+
pointHistory: async (query, requestOptions) => {
|
|
1571
|
+
const { ledger, cursor } = parseInput(pointHistoryQuerySchema, query);
|
|
1572
|
+
return request(
|
|
1573
|
+
withQuery(`/v1/me/points/${ledger}/history`, { cursor }),
|
|
1574
|
+
pointHistoryPageSchema,
|
|
1575
|
+
{ options: requestOptions }
|
|
1576
|
+
);
|
|
1577
|
+
},
|
|
1304
1578
|
/** Claims a quest for the signed-in member. Quest controllers (`quests.open`) call this. */
|
|
1305
1579
|
claim: async (value, requestOptions) => {
|
|
1306
1580
|
const claim = parseInput(submitSchema.omit({ member: true }), value);
|
|
@@ -1321,19 +1595,21 @@ function createRelayClient(options) {
|
|
|
1321
1595
|
evidence: {
|
|
1322
1596
|
async download(id, requestOptions) {
|
|
1323
1597
|
const call = { options: requestOptions };
|
|
1324
|
-
|
|
1598
|
+
return exchange(
|
|
1325
1599
|
`/v1/evidence/${parseInput(identifier, id)}`,
|
|
1326
|
-
call
|
|
1600
|
+
call,
|
|
1601
|
+
(response, life) => {
|
|
1602
|
+
const type = response.headers.get("Content-Type");
|
|
1603
|
+
if (type !== "image/jpeg" && type !== "image/png") {
|
|
1604
|
+
throw invalidResponse(
|
|
1605
|
+
response.status,
|
|
1606
|
+
call,
|
|
1607
|
+
new Error("Unexpected evidence content type")
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
return body(response, call, life, (value) => value.blob());
|
|
1611
|
+
}
|
|
1327
1612
|
);
|
|
1328
|
-
const type = response.headers.get("Content-Type");
|
|
1329
|
-
if (type !== "image/jpeg" && type !== "image/png") {
|
|
1330
|
-
throw invalidResponse(
|
|
1331
|
-
response.status,
|
|
1332
|
-
call,
|
|
1333
|
-
new Error("Unexpected evidence content type")
|
|
1334
|
-
);
|
|
1335
|
-
}
|
|
1336
|
-
return body(response, call, (value) => value.blob());
|
|
1337
1613
|
}
|
|
1338
1614
|
},
|
|
1339
1615
|
engagement: {
|
|
@@ -1372,31 +1648,7 @@ function createRelayClient(options) {
|
|
|
1372
1648
|
),
|
|
1373
1649
|
contentQualificationPageSchema,
|
|
1374
1650
|
{ options: requestOptions }
|
|
1375
|
-
)
|
|
1376
|
-
admissions: async (query, requestOptions) => request(
|
|
1377
|
-
withQuery(
|
|
1378
|
-
"/v1/content/admissions",
|
|
1379
|
-
parseInput(contentAdmissionQuerySchema, query)
|
|
1380
|
-
),
|
|
1381
|
-
contentAdmissionPageSchema,
|
|
1382
|
-
{ options: requestOptions }
|
|
1383
|
-
),
|
|
1384
|
-
enroll: async (value, requestOptions) => {
|
|
1385
|
-
const body2 = parseInput(contentEnrollmentInputSchema, value);
|
|
1386
|
-
return request("/v1/content/enrollments", contentEnrollmentSchema, {
|
|
1387
|
-
json: body2,
|
|
1388
|
-
options: requestOptions,
|
|
1389
|
-
operationId: body2.actionId
|
|
1390
|
-
});
|
|
1391
|
-
},
|
|
1392
|
-
claim: async (value, requestOptions) => {
|
|
1393
|
-
const body2 = parseInput(contentReleaseInputSchema, value);
|
|
1394
|
-
return request("/v1/content/claims", contentAdmissionSchema, {
|
|
1395
|
-
json: body2,
|
|
1396
|
-
options: requestOptions,
|
|
1397
|
-
operationId: body2.actionId
|
|
1398
|
-
});
|
|
1399
|
-
}
|
|
1651
|
+
)
|
|
1400
1652
|
}
|
|
1401
1653
|
};
|
|
1402
1654
|
}
|