@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.
Files changed (59) hide show
  1. package/README.md +1 -1
  2. package/dist/activity-DeKJsRry.d.ts +9112 -0
  3. package/dist/authoring.d.ts +1135 -285
  4. package/dist/authoring.js +545 -269
  5. package/dist/automatic-outcomes-B1xrETxm.d.ts +758 -0
  6. package/dist/avatar.d.ts +2 -2
  7. package/dist/browser-DFe7sWSN.d.ts +4154 -0
  8. package/dist/browser.d.ts +11 -2528
  9. package/dist/browser.js +8 -6
  10. package/dist/capability-error-DNMwKmlL.d.ts +458 -0
  11. package/dist/{chunk-V7EZUZ3Z.js → chunk-36HTX2BM.js} +389 -169
  12. package/dist/chunk-6QJ3LADK.js +112 -0
  13. package/dist/{chunk-77H7APVD.js → chunk-BGGKNIRP.js} +1 -1
  14. package/dist/chunk-EYH5RDIE.js +496 -0
  15. package/dist/chunk-GQKYAQM6.js +84 -0
  16. package/dist/chunk-GUP7YRR4.js +42 -0
  17. package/dist/{chunk-V5R2DMGY.js → chunk-GZ6FJAXN.js} +380 -128
  18. package/dist/chunk-JON5SESL.js +2986 -0
  19. package/dist/chunk-MC6V6VMG.js +49 -0
  20. package/dist/chunk-NPGENI7K.js +37 -0
  21. package/dist/chunk-VFHHLN6A.js +284 -0
  22. package/dist/chunk-WJSSJ7AF.js +151 -0
  23. package/dist/{chunk-QBOHLT7M.js → chunk-XFMSUPNS.js} +1 -1
  24. package/dist/{chunk-6A6K5RUD.js → chunk-ZVIRG24J.js} +145 -42
  25. package/dist/{client-CK4nwt7V.d.ts → client-XiuFnug_.d.ts} +2193 -562
  26. package/dist/{submission-input-C9qm9ixd.d.ts → groups-Ck-OxzjD.d.ts} +919 -1142
  27. package/dist/identity-integration-GxAHdKR2.d.ts +58 -0
  28. package/dist/{images--vZ9U0kr.d.ts → images-Dv-P5VBw.d.ts} +2 -2
  29. package/dist/index.d.ts +13 -9
  30. package/dist/index.js +17 -6
  31. package/dist/internal.d.ts +82 -43
  32. package/dist/internal.js +58 -90
  33. package/dist/offers-Cy-e4o5x.d.ts +431 -0
  34. package/dist/{pages-BzqJuI69.d.ts → outcomes-kVB-Juzs.d.ts} +2386 -1490
  35. package/dist/pages-BBYD21IE.d.ts +359 -0
  36. package/dist/participant-rewards-3duLb8YJ.d.ts +668 -0
  37. package/dist/portal-proxy.js +22 -4
  38. package/dist/{profiles-DDMFBRKe.d.ts → profiles-D2aFcOxu.d.ts} +15 -15
  39. package/dist/react/index.d.ts +3122 -1011
  40. package/dist/react/index.js +1627 -779
  41. package/dist/request-error-DWduJCGV.d.ts +62 -0
  42. package/dist/schema-DfJzH2ci.d.ts +5644 -0
  43. package/dist/schema.d.ts +15 -4216
  44. package/dist/schema.js +322 -81
  45. package/dist/server/index.d.ts +81498 -0
  46. package/dist/server/index.js +2520 -0
  47. package/dist/{streaks-CwGReWGt.d.ts → streaks-CWyq8Px5.d.ts} +2 -4
  48. package/dist/submission-input-BZkm6ohU.d.ts +290 -0
  49. package/dist/vite.d.ts +1 -1
  50. package/dist/vite.js +1 -0
  51. package/dist/{wallet-BGOfFkG9.d.ts → wallet-YssWxGg8.d.ts} +35 -38
  52. package/package.json +8 -2
  53. package/dist/chunk-MRIURJE3.js +0 -257
  54. package/dist/chunk-VARGF4JS.js +0 -1733
  55. package/dist/chunk-ZWY3R6B3.js +0 -26
  56. package/dist/collection-BGneJ6Bk.d.ts +0 -114
  57. package/dist/integrations-C3Sp558f.d.ts +0 -1297
  58. package/dist/point-ledgers-iS00Z_v0.d.ts +0 -37
  59. package/dist/schema-pnuWqvGn.d.ts +0 -3396
@@ -1,9 +1,18 @@
1
1
  import {
2
2
  collectionPassSchema,
3
- pointLedgerReferenceSchema,
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-MRIURJE3.js";
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-V7EZUZ3Z.js";
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-QBOHLT7M.js";
67
+ } from "./chunk-XFMSUPNS.js";
46
68
  import {
47
69
  accountConnectionSchema,
48
70
  attemptSchema,
49
71
  authProviderSchema,
50
- contentAdmissionPageSchema,
51
- contentAdmissionQuerySchema,
52
- contentAdmissionSchema,
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-VARGF4JS.js";
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 = missingEntrySchema.safeParse(entry);
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 step();
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
- uncertain: stage.sent && methodOf(call) !== "GET",
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
- if (call.options?.signal?.aborted) {
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 (call.deadline?.aborted) {
723
- return new RelayError("Relay did not respond in time", {
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
- async function send(path, call = {}) {
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
- const caller = call.options?.signal;
762
- const deadline = AbortSignal.timeout(TIMEOUT_MS);
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: caller ? AbortSignal.any([caller, deadline]) : deadline
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
- const text = await response.text().catch(() => "");
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
- uncertain: methodOf(call) !== "GET" && uncertainStatuses.has(response.status)
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 body(response, call, reader) {
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, { sent: true, status: response.status });
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 read(await send(path, call), schema, call);
932
+ return exchange(
933
+ path,
934
+ call,
935
+ (response, life) => read(response, schema, call, life)
936
+ );
834
937
  }
835
- async function walletRequest(path, schema, call = {}) {
836
- for (let attempt = 0; ; attempt++) {
837
- let response;
838
- try {
839
- response = await send(path, call);
840
- } catch (error) {
841
- const transient = error instanceof RelayError && (error.kind === "network" || error.kind === "timeout" || error.kind === "http" && [500, 502, 503, 504].includes(error.status ?? 0));
842
- if (!transient || attempt === 2) {
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: { signal: requestOptions?.signal }
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 session = await readSession(requestOptions);
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
- return { ...requestOptions, session: session.id };
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. Retrying with the same `uploadId` is idempotent.
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) => walletRequest(
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) => walletRequest(
1117
- "/v1/me/wallets/challenge",
1118
- walletMessageChallengeSchema,
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
- /** Asks the signer for its address, signs the returned challenge, and links the wallet. */
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 walletRequest(
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(() => walletAddressInput(signer))
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; older servers omit it. */
1174
- wallets: walletSignInNetworksSchema.default([]),
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(() => walletAddressInput(signer))
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
- const response = await send(
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
  }