@capxul/sdk 0.1.0-alpha.6 → 0.1.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -677,7 +677,12 @@ var Errors = {
677
677
  "Idempotency key was already used for a different request",
678
678
  { details }
679
679
  ),
680
- emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
680
+ emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
681
+ details
682
+ }),
683
+ rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
684
+ details: { ...details }
685
+ }),
681
686
  internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
682
687
  /**
683
688
  * Verification gate. Surfaced when a request hits a verification
@@ -750,13 +755,13 @@ function createLifecycle(initial) {
750
755
  }
751
756
  function makeBuildTimeUrlsTransport(config) {
752
757
  if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
753
- throw Errors.invalidInput(
758
+ throw invalidConfigError(
754
759
  "authBaseUrl",
755
760
  "build-time-urls transport requires a non-empty authBaseUrl."
756
761
  );
757
762
  }
758
763
  if (!config.convexUrl || config.convexUrl.trim().length === 0) {
759
- throw Errors.invalidInput(
764
+ throw invalidConfigError(
760
765
  "convexUrl",
761
766
  "build-time-urls transport requires a non-empty convexUrl."
762
767
  );
@@ -770,6 +775,7 @@ function makeBuildTimeUrlsTransport(config) {
770
775
  return {
771
776
  authBaseUrl,
772
777
  convexUrl,
778
+ ensureRuntime: async () => runtime,
773
779
  fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
774
780
  getState: lifecycle.getState,
775
781
  subscribe: lifecycle.subscribe,
@@ -785,14 +791,15 @@ function makeBuildTimeUrlsTransport(config) {
785
791
  };
786
792
  }
787
793
  function makePublishableKeyTransport(config) {
788
- if (!config.publishableKey || config.publishableKey.trim().length === 0) {
789
- throw Errors.invalidInput(
794
+ const publishableKey = config.publishableKey?.trim();
795
+ if (!publishableKey) {
796
+ throw invalidConfigError(
790
797
  "publishableKey",
791
798
  "publishable-key transport requires a non-empty publishableKey."
792
799
  );
793
800
  }
794
801
  const fetchImpl = config.fetchImpl ?? globalThis.fetch;
795
- const bootstrapUrl = stripTrailingSlash(
802
+ const bootstrapUrl = normalizeBootstrapUrl(
796
803
  config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
797
804
  );
798
805
  let authBaseUrl = "";
@@ -807,23 +814,20 @@ function makePublishableKeyTransport(config) {
807
814
  const response = await fetchImpl(bootstrapUrl, {
808
815
  method: "POST",
809
816
  headers: { "content-type": "application/json" },
810
- body: JSON.stringify({ publishableKey: config.publishableKey })
817
+ body: JSON.stringify({ publishableKey })
811
818
  });
812
819
  if (!response.ok) {
813
- throw Errors.invalidInput(
814
- "publishableKey",
815
- `${bootstrapUrl} failed with HTTP ${response.status}.`
816
- );
820
+ throw await bootstrapResponseError(response, bootstrapUrl);
817
821
  }
818
- const body = await response.json();
822
+ const body = await readBootstrapSuccessBody(response);
819
823
  if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
820
- throw Errors.invalidInput(
824
+ throw bootstrapContractError(
821
825
  "authBaseUrl",
822
826
  "/v1/client/bootstrap returned no authBaseUrl."
823
827
  );
824
828
  }
825
829
  if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
826
- throw Errors.invalidInput(
830
+ throw bootstrapContractError(
827
831
  "convexUrl",
828
832
  "/v1/client/bootstrap returned no convexUrl."
829
833
  );
@@ -835,16 +839,13 @@ function makePublishableKeyTransport(config) {
835
839
  return runtime;
836
840
  })();
837
841
  bootstrapPromise = attempt.catch((err) => {
842
+ const error = normalizeBootstrapThrownError(err);
838
843
  bootstrapPromise = null;
839
844
  lifecycle.setState({
840
845
  status: "error",
841
- error: err instanceof CapxulError ? err : new CapxulError({
842
- code: "UNKNOWN",
843
- message: "Bootstrap failed without a typed CapxulError.",
844
- cause: err
845
- })
846
+ error
846
847
  });
847
- throw err;
848
+ throw error;
848
849
  });
849
850
  return await bootstrapPromise;
850
851
  }
@@ -855,6 +856,7 @@ function makePublishableKeyTransport(config) {
855
856
  get convexUrl() {
856
857
  return convexUrl;
857
858
  },
859
+ ensureRuntime: ensureBootstrap,
858
860
  fetch: async (path, init) => {
859
861
  const resolved = await ensureBootstrap();
860
862
  return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
@@ -865,7 +867,7 @@ function makePublishableKeyTransport(config) {
865
867
  markAuthenticated: ({ dataClient: nextDataClient }) => {
866
868
  const current = lifecycle.getState();
867
869
  if (current.status !== "ready" && current.status !== "authenticated") {
868
- throw Errors.internalError(
870
+ throw internalTransportError(
869
871
  `markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
870
872
  );
871
873
  }
@@ -887,6 +889,24 @@ function makePublishableKeyTransport(config) {
887
889
  function stripTrailingSlash(url) {
888
890
  return url.replace(/\/+$/, "");
889
891
  }
892
+ function normalizeBootstrapUrl(url) {
893
+ const normalized = stripTrailingSlash(url.trim());
894
+ if (!isAbsoluteHttpUrl(normalized)) {
895
+ throw invalidConfigError(
896
+ "bootstrapUrl",
897
+ "publishable-key transport requires an absolute http(s) bootstrapUrl."
898
+ );
899
+ }
900
+ return normalized;
901
+ }
902
+ function isAbsoluteHttpUrl(url) {
903
+ try {
904
+ const parsed = new URL(url);
905
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
906
+ } catch {
907
+ return false;
908
+ }
909
+ }
890
910
  function resolveUrl(authBaseUrl, path) {
891
911
  if (path.startsWith("http://") || path.startsWith("https://")) {
892
912
  return path;
@@ -894,10 +914,142 @@ function resolveUrl(authBaseUrl, path) {
894
914
  return `${authBaseUrl}${path}`;
895
915
  }
896
916
  function assertNever(value) {
897
- throw Errors.internalError(
917
+ throw internalTransportError(
898
918
  `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
899
919
  );
900
920
  }
921
+ function invalidConfigError(field, reason) {
922
+ return new CapxulError({
923
+ code: "INVALID_INPUT",
924
+ message: `Invalid ${field}: ${reason}`,
925
+ details: { source: "sdk-config", field, reason }
926
+ });
927
+ }
928
+ function bootstrapContractError(field, message) {
929
+ return new CapxulError({
930
+ code: "INVALID_INPUT",
931
+ message,
932
+ details: {
933
+ source: "backend-bootstrap",
934
+ phase: "publishable-key-bootstrap",
935
+ field,
936
+ reason: message
937
+ }
938
+ });
939
+ }
940
+ function internalTransportError(reason) {
941
+ return new CapxulError({
942
+ code: "INTERNAL_ERROR",
943
+ message: `Internal error: ${reason}`,
944
+ details: { source: "sdk-transport", reason }
945
+ });
946
+ }
947
+ async function bootstrapResponseError(response, bootstrapUrl) {
948
+ const envelope = await readBootstrapErrorEnvelope(response);
949
+ const wireCode = readNonEmptyString(envelope?.error?.code);
950
+ const normalized = normalizeBootstrapErrorCode(wireCode);
951
+ const message = readNonEmptyString(envelope?.error?.message) ?? `${bootstrapUrl} failed with HTTP ${response.status}.`;
952
+ const backendDetails = readRecord(envelope?.error?.details);
953
+ return new CapxulError({
954
+ code: normalized.code,
955
+ message,
956
+ details: {
957
+ ...backendDetails,
958
+ source: "backend-bootstrap",
959
+ phase: "publishable-key-bootstrap",
960
+ httpStatus: response.status,
961
+ ...normalized.wireCode ? { wireCode: normalized.wireCode } : {}
962
+ },
963
+ operationId: readNonEmptyString(envelope?.error?.operationId),
964
+ correlationId: readNonEmptyString(envelope?.error?.correlationId),
965
+ retryable: typeof envelope?.error?.retryable === "boolean" ? envelope.error.retryable : void 0
966
+ });
967
+ }
968
+ async function readBootstrapErrorEnvelope(response) {
969
+ try {
970
+ const parsed = await response.json();
971
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
972
+ } catch {
973
+ return null;
974
+ }
975
+ }
976
+ async function readBootstrapSuccessBody(response) {
977
+ try {
978
+ const parsed = await response.json();
979
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
980
+ } catch {
981
+ throw bootstrapContractError(
982
+ "body",
983
+ "/v1/client/bootstrap returned invalid JSON."
984
+ );
985
+ }
986
+ }
987
+ function normalizeBootstrapThrownError(error) {
988
+ if (error instanceof CapxulError) return error;
989
+ return new CapxulError({
990
+ code: "NETWORK_ERROR",
991
+ message: "Publishable-key bootstrap network failure.",
992
+ cause: error,
993
+ details: {
994
+ source: "bootstrap-network",
995
+ phase: "publishable-key-bootstrap"
996
+ }
997
+ });
998
+ }
999
+ function normalizeBootstrapErrorCode(wireCode) {
1000
+ if (wireCode === "INTERNAL_SERVER_ERROR") {
1001
+ return { code: "INTERNAL_ERROR", wireCode };
1002
+ }
1003
+ if (wireCode && isCapxulErrorCode(wireCode)) {
1004
+ return { code: wireCode };
1005
+ }
1006
+ return wireCode ? { code: "UNKNOWN", wireCode } : { code: "UNKNOWN" };
1007
+ }
1008
+ var CAPXUL_ERROR_CODES = /* @__PURE__ */ new Set([
1009
+ "NOT_AUTHENTICATED",
1010
+ "EMAIL_DELIVERY_FAILED",
1011
+ "PROFILE_NOT_FOUND",
1012
+ "SMART_ACCOUNT_MISSING",
1013
+ "PLAYER_NOT_FOUND",
1014
+ "ACCOUNT_NOT_FOUND",
1015
+ "PROVIDER_ERROR",
1016
+ "INVALID_INPUT",
1017
+ "ENV_MISSING",
1018
+ "NOT_IMPLEMENTED",
1019
+ "VERIFICATION_REQUIRED",
1020
+ "INSUFFICIENT_BALANCE",
1021
+ "INVALID_RECIPIENT",
1022
+ "TRANSACTION_FAILED",
1023
+ "RATE_LIMITED",
1024
+ "NETWORK_ERROR",
1025
+ "UNKNOWN",
1026
+ "PERMISSION_DENIED",
1027
+ "API_KEY_INVALID",
1028
+ "API_KEY_EXPIRED",
1029
+ "IDEMPOTENCY_CONFLICT",
1030
+ "NOT_FOUND",
1031
+ "OPERATION_CANCELED",
1032
+ "OPERATION_TIMEOUT",
1033
+ "ACTION_REQUIRED",
1034
+ "KYC_REQUIRED",
1035
+ "POLICY_DENIED",
1036
+ "SAFE_NOT_READY",
1037
+ "PROVIDER_UNAVAILABLE",
1038
+ "PROVIDER_REJECTED",
1039
+ "RECONCILIATION_FAILED",
1040
+ "INTERNAL_ERROR",
1041
+ "QUOTE_EXPIRED",
1042
+ "QUOTE_NOT_FOUND"
1043
+ ]);
1044
+ function isCapxulErrorCode(value) {
1045
+ return CAPXUL_ERROR_CODES.has(value);
1046
+ }
1047
+ function readRecord(value) {
1048
+ return typeof value === "object" && value !== null ? value : null;
1049
+ }
1050
+ function readNonEmptyString(value) {
1051
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1052
+ }
901
1053
 
902
1054
  // src/core/auth.ts
903
1055
  function createAuthClient(config = {}) {
@@ -952,13 +1104,16 @@ function createAuthClient(config = {}) {
952
1104
  email: signIn.user.email,
953
1105
  token: signIn.token,
954
1106
  convexJwt,
955
- expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString()
1107
+ expiresAt: new Date(
1108
+ Date.now() + 30 * 24 * 60 * 60 * 1e3
1109
+ ).toISOString()
956
1110
  };
957
1111
  sessionStore.set(session);
958
1112
  if (config.auth?.createDataClient) {
959
1113
  try {
960
1114
  dataClient = await config.auth.createDataClient(session);
961
1115
  mutableConfig(config).data = dataClient;
1116
+ transport.markAuthenticated({ dataClient });
962
1117
  } catch (cause) {
963
1118
  return [
964
1119
  new CapxulError({
@@ -970,13 +1125,76 @@ function createAuthClient(config = {}) {
970
1125
  ];
971
1126
  }
972
1127
  }
973
- return [null, session];
1128
+ if (!dataClient) {
1129
+ return [
1130
+ new CapxulError({
1131
+ code: "NOT_AUTHENTICATED",
1132
+ message: "Auth bootstrap requires an authenticated Convex data client."
1133
+ }),
1134
+ null
1135
+ ];
1136
+ }
1137
+ try {
1138
+ const resolution = await dataClient.mutation(
1139
+ api.authBootstrap.resolveAfterOtp,
1140
+ {
1141
+ email: session.email,
1142
+ sessionToken: session.token
1143
+ }
1144
+ );
1145
+ if (resolution.kind === "existing_member") {
1146
+ return [null, { ...resolution, session }];
1147
+ }
1148
+ return [null, { ...resolution, session }];
1149
+ } catch (cause) {
1150
+ return [fromConvexError(cause), null];
1151
+ }
1152
+ },
1153
+ completeBootstrap: async (input) => {
1154
+ const session = sessionStore.get();
1155
+ const data = dataClient ?? config.data;
1156
+ if (!session || !data) {
1157
+ return [
1158
+ new CapxulError({
1159
+ code: "INVALID_INPUT",
1160
+ message: "completeBootstrap requires the OTP-verified session that issued the bootstrap token."
1161
+ }),
1162
+ null
1163
+ ];
1164
+ }
1165
+ if (input.signerProvider.kind !== "local-private-key") {
1166
+ return [
1167
+ new CapxulError({
1168
+ code: "INVALID_INPUT",
1169
+ message: "completeBootstrap currently supports local-private-key signer providers only."
1170
+ }),
1171
+ null
1172
+ ];
1173
+ }
1174
+ try {
1175
+ const result = await data.mutation(api.authBootstrap.completeBootstrap, {
1176
+ bootstrapToken: input.bootstrapToken,
1177
+ sessionToken: session.token,
1178
+ username: input.username,
1179
+ displayName: input.displayName,
1180
+ countryCode: input.countryCode,
1181
+ signerProvider: input.signerProvider
1182
+ });
1183
+ return [null, { kind: "authenticated", session, ...result }];
1184
+ } catch (cause) {
1185
+ return [
1186
+ fromConvexError(cause),
1187
+ null
1188
+ ];
1189
+ }
974
1190
  },
975
1191
  getSession: async () => [null, sessionStore.get()],
976
1192
  signOut: async () => {
977
1193
  sessionStore.clear();
978
1194
  dataClient = null;
979
1195
  mutableConfig(config).data = void 0;
1196
+ const transport = getTransport();
1197
+ transport?.clearAuth();
980
1198
  return [null, void 0];
981
1199
  },
982
1200
  serviceTokenMint: async () => stub("auth.serviceTokenMint"),
@@ -1030,16 +1248,19 @@ async function postBetterAuth(transport, path, body, code, signal) {
1030
1248
  body: JSON.stringify(body),
1031
1249
  signal
1032
1250
  });
1251
+ const text = await response.text();
1033
1252
  if (!response.ok) {
1253
+ const parsedError = parseBetterAuthError(text);
1034
1254
  return [
1035
1255
  new CapxulError({
1036
- code,
1037
- message: `BetterAuth ${path} failed with HTTP ${response.status}.`
1256
+ code: parsedError.code ?? code,
1257
+ message: parsedError.message ?? `BetterAuth ${path} failed with HTTP ${response.status}.`,
1258
+ details: parsedError.details,
1259
+ retryable: parsedError.retryable
1038
1260
  }),
1039
1261
  null
1040
1262
  ];
1041
1263
  }
1042
- const text = await response.text();
1043
1264
  return [null, text ? JSON.parse(text) : void 0];
1044
1265
  } catch (cause) {
1045
1266
  return [
@@ -1052,6 +1273,36 @@ async function postBetterAuth(transport, path, body, code, signal) {
1052
1273
  ];
1053
1274
  }
1054
1275
  }
1276
+ function parseBetterAuthError(text) {
1277
+ if (!text.trim()) {
1278
+ return {};
1279
+ }
1280
+ try {
1281
+ const body = JSON.parse(text);
1282
+ if (!body || typeof body !== "object") {
1283
+ return {};
1284
+ }
1285
+ const record = body;
1286
+ const nested = record.error && typeof record.error === "object" ? record.error : record;
1287
+ const code = typeof nested.code === "string" ? nested.code : void 0;
1288
+ const message = typeof nested.message === "string" ? nested.message : void 0;
1289
+ const details = nested.details && typeof nested.details === "object" ? nested.details : void 0;
1290
+ const correlationId = typeof nested.correlationId === "string" ? nested.correlationId : void 0;
1291
+ const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
1292
+ return {
1293
+ code: isCapxulErrorCode2(code) ? code : void 0,
1294
+ message,
1295
+ details,
1296
+ correlationId,
1297
+ retryable
1298
+ };
1299
+ } catch {
1300
+ return {};
1301
+ }
1302
+ }
1303
+ function isCapxulErrorCode2(code) {
1304
+ return code === "NOT_AUTHENTICATED" || code === "EMAIL_DELIVERY_FAILED" || code === "INVALID_INPUT" || code === "RATE_LIMITED" || code === "NETWORK_ERROR" || code === "API_KEY_INVALID" || code === "API_KEY_EXPIRED";
1305
+ }
1055
1306
  async function exchangeConvexToken(transport, config, token, signal) {
1056
1307
  const path = config.auth?.convexTokenUrl ?? "/convex/token";
1057
1308
  try {
@@ -2294,7 +2545,7 @@ function createAuthFlowMachine(client) {
2294
2545
  }),
2295
2546
  verifyOtp: fromPromise(
2296
2547
  async ({ input, signal }) => {
2297
- const [error, session] = await client.auth.verifyOtp(
2548
+ const [error, result] = await client.auth.verifyOtp(
2298
2549
  {
2299
2550
  email: input.email,
2300
2551
  otp: input.code
@@ -2302,7 +2553,14 @@ function createAuthFlowMachine(client) {
2302
2553
  { signal }
2303
2554
  );
2304
2555
  if (error) throw error;
2305
- return session;
2556
+ if (result.kind === "bootstrap_required") {
2557
+ throw new CapxulError({
2558
+ code: "ACTION_REQUIRED",
2559
+ message: "OTP verified but auth bootstrap is required. Use createAuthBootstrapFlowMachine for product sign-up.",
2560
+ details: { reason: result.reason }
2561
+ });
2562
+ }
2563
+ return result.session;
2306
2564
  }
2307
2565
  ),
2308
2566
  signOut: fromPromise(async () => {
@@ -2550,6 +2808,345 @@ function emailDomain(email) {
2550
2808
  const domain = email.split("@")[1]?.trim().toLowerCase();
2551
2809
  return domain || "unknown";
2552
2810
  }
2811
+ var initialContext = {
2812
+ email: null,
2813
+ code: null,
2814
+ username: null,
2815
+ signerProvider: null,
2816
+ bootstrapToken: null,
2817
+ bootstrapReason: null,
2818
+ session: null,
2819
+ account: null,
2820
+ safe: null,
2821
+ error: null
2822
+ };
2823
+ function createAuthBootstrapFlowMachine(client) {
2824
+ return setup({
2825
+ types: {},
2826
+ actors: {
2827
+ sendOtp: fromPromise(async ({ input, signal }) => {
2828
+ const [error] = await client.auth.sendOtp(
2829
+ { email: input.email },
2830
+ { signal }
2831
+ );
2832
+ if (error) throw error;
2833
+ }),
2834
+ verifyOtp: fromPromise(
2835
+ async ({ input, signal }) => {
2836
+ const [error, result] = await client.auth.verifyOtp(
2837
+ { email: input.email, otp: input.code },
2838
+ { signal }
2839
+ );
2840
+ if (error) throw error;
2841
+ return result;
2842
+ }
2843
+ ),
2844
+ completeBootstrap: fromPromise(async ({ input }) => {
2845
+ const [error, result] = await client.auth.completeBootstrap(input);
2846
+ if (error) throw error;
2847
+ return result;
2848
+ }),
2849
+ signOut: fromPromise(async () => {
2850
+ const [error] = await client.auth.signOut();
2851
+ if (error) throw error;
2852
+ })
2853
+ },
2854
+ actions: {
2855
+ trackOtpRequested: ({ context }) => {
2856
+ if (!context.email) return;
2857
+ track("auth_otp_requested", {
2858
+ email_domain: emailDomain2(context.email)
2859
+ });
2860
+ },
2861
+ trackFailed: ({ event }) => {
2862
+ track("auth_failed", {
2863
+ auth_type: "email_otp",
2864
+ reason: errorFromEvent2(event).code
2865
+ });
2866
+ },
2867
+ trackTimeoutFailed: () => {
2868
+ track("auth_failed", {
2869
+ auth_type: "email_otp",
2870
+ reason: "timeout"
2871
+ });
2872
+ },
2873
+ trackVerified: () => {
2874
+ track("auth_verified", { auth_type: "email_otp" });
2875
+ },
2876
+ trackBootstrapRequired: ({ context }) => {
2877
+ track("auth_verified", {
2878
+ auth_type: "email_otp",
2879
+ auth_mode: context.bootstrapReason ?? "bootstrap_required"
2880
+ });
2881
+ },
2882
+ identifyAndTrack: ({ context }) => {
2883
+ if (!context.session) return;
2884
+ identify(context.session.authUserId, {
2885
+ email_domain: emailDomain2(context.session.email)
2886
+ });
2887
+ track("auth_identified", {
2888
+ email_domain: emailDomain2(context.session.email)
2889
+ });
2890
+ },
2891
+ trackSignedOut: () => {
2892
+ track("auth_signed_out");
2893
+ }
2894
+ }
2895
+ }).createMachine({
2896
+ id: "authBootstrap",
2897
+ initial: "email",
2898
+ context: initialContext,
2899
+ states: {
2900
+ email: {
2901
+ on: {
2902
+ ENTER_EMAIL: {
2903
+ actions: assign({
2904
+ email: ({ event }) => event.email,
2905
+ error: () => null
2906
+ })
2907
+ },
2908
+ REQUEST_OTP: { target: "sending_otp" }
2909
+ }
2910
+ },
2911
+ sending_otp: {
2912
+ invoke: {
2913
+ src: "sendOtp",
2914
+ input: ({ context }) => ({ email: requireEmail2(context) }),
2915
+ onDone: { target: "otp_requested", actions: "trackOtpRequested" },
2916
+ onError: {
2917
+ target: "otp_requested",
2918
+ actions: [
2919
+ assign({ error: ({ event }) => errorFromEvent2(event) }),
2920
+ "trackFailed"
2921
+ ]
2922
+ }
2923
+ },
2924
+ after: {
2925
+ [FLOW_INVOKE_TIMEOUT_MS]: {
2926
+ target: "otp_requested",
2927
+ actions: [
2928
+ assign({ error: () => timeoutError2("sending_otp") }),
2929
+ "trackTimeoutFailed"
2930
+ ]
2931
+ }
2932
+ }
2933
+ },
2934
+ otp_requested: {
2935
+ on: {
2936
+ ENTER_OTP: {
2937
+ actions: assign({
2938
+ code: ({ event }) => event.code,
2939
+ error: () => null
2940
+ })
2941
+ },
2942
+ VERIFY_OTP: { target: "verifying_otp" },
2943
+ BACK: { target: "email" },
2944
+ RESET: { target: "email", actions: assign(() => initialContext) }
2945
+ }
2946
+ },
2947
+ verifying_otp: {
2948
+ invoke: {
2949
+ src: "verifyOtp",
2950
+ input: ({ context }) => ({
2951
+ email: requireEmail2(context),
2952
+ code: requireCode(context)
2953
+ }),
2954
+ onDone: [
2955
+ {
2956
+ guard: ({ event }) => event.output.kind === "existing_member",
2957
+ target: "authenticated",
2958
+ actions: [
2959
+ assign({
2960
+ session: ({ event }) => event.output.session,
2961
+ account: ({ event }) => event.output.kind === "existing_member" ? event.output.account : null,
2962
+ username: ({ event }) => event.output.kind === "existing_member" ? event.output.username : null,
2963
+ safe: ({ event }) => event.output.kind === "existing_member" ? event.output.safe : null,
2964
+ email: () => null,
2965
+ error: () => null
2966
+ }),
2967
+ "trackVerified",
2968
+ "identifyAndTrack"
2969
+ ]
2970
+ },
2971
+ {
2972
+ target: "bootstrap_required",
2973
+ actions: [
2974
+ assign({
2975
+ session: ({ event }) => event.output.session,
2976
+ bootstrapToken: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.bootstrapToken : null,
2977
+ bootstrapReason: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.reason : null,
2978
+ username: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.username ?? null : null,
2979
+ email: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.email : null,
2980
+ error: () => null
2981
+ }),
2982
+ "trackVerified",
2983
+ "trackBootstrapRequired"
2984
+ ]
2985
+ }
2986
+ ],
2987
+ onError: {
2988
+ target: "otp_requested",
2989
+ actions: [
2990
+ assign({ error: ({ event }) => errorFromEvent2(event) }),
2991
+ "trackFailed"
2992
+ ]
2993
+ }
2994
+ },
2995
+ after: {
2996
+ [FLOW_INVOKE_TIMEOUT_MS]: {
2997
+ target: "otp_requested",
2998
+ actions: [
2999
+ assign({ error: () => timeoutError2("verifying_otp") }),
3000
+ "trackTimeoutFailed"
3001
+ ]
3002
+ }
3003
+ }
3004
+ },
3005
+ bootstrap_required: {
3006
+ on: {
3007
+ ENTER_USERNAME: {
3008
+ actions: assign({
3009
+ username: ({ event }) => event.username,
3010
+ error: () => null
3011
+ })
3012
+ },
3013
+ ENTER_SIGNER_PROVIDER: {
3014
+ actions: assign({
3015
+ signerProvider: ({ event }) => event.signerProvider,
3016
+ error: () => null
3017
+ })
3018
+ },
3019
+ COMPLETE_BOOTSTRAP: { target: "completing_bootstrap" },
3020
+ BACK: { target: "otp_requested" },
3021
+ RESET: { target: "email", actions: assign(() => initialContext) }
3022
+ }
3023
+ },
3024
+ completing_bootstrap: {
3025
+ invoke: {
3026
+ src: "completeBootstrap",
3027
+ input: ({ context }) => ({
3028
+ bootstrapToken: requireBootstrapToken(context),
3029
+ username: requireUsername(context),
3030
+ signerProvider: requireSignerProvider(context)
3031
+ }),
3032
+ onDone: {
3033
+ target: "authenticated",
3034
+ actions: [
3035
+ assign({
3036
+ session: ({ event }) => event.output.session,
3037
+ account: ({ event }) => event.output.account,
3038
+ username: ({ event }) => event.output.username,
3039
+ safe: ({ event }) => event.output.safe,
3040
+ bootstrapToken: () => null,
3041
+ bootstrapReason: () => null,
3042
+ signerProvider: () => null,
3043
+ email: () => null,
3044
+ error: () => null
3045
+ }),
3046
+ "identifyAndTrack"
3047
+ ]
3048
+ },
3049
+ onError: {
3050
+ target: "bootstrap_required",
3051
+ actions: [
3052
+ assign({ error: ({ event }) => errorFromEvent2(event) }),
3053
+ "trackFailed"
3054
+ ]
3055
+ }
3056
+ },
3057
+ after: {
3058
+ [FLOW_INVOKE_TIMEOUT_MS]: {
3059
+ target: "bootstrap_required",
3060
+ actions: [
3061
+ assign({ error: () => timeoutError2("completing_bootstrap") }),
3062
+ "trackTimeoutFailed"
3063
+ ]
3064
+ }
3065
+ }
3066
+ },
3067
+ authenticated: {
3068
+ on: {
3069
+ SIGN_OUT: { target: "signing_out" }
3070
+ }
3071
+ },
3072
+ signing_out: {
3073
+ invoke: {
3074
+ src: "signOut",
3075
+ onDone: {
3076
+ target: "email",
3077
+ actions: [
3078
+ assign(() => initialContext),
3079
+ "trackSignedOut"
3080
+ ]
3081
+ },
3082
+ onError: {
3083
+ target: "error",
3084
+ actions: assign({ error: ({ event }) => errorFromEvent2(event) })
3085
+ }
3086
+ }
3087
+ },
3088
+ error: {
3089
+ on: {
3090
+ RESET: { target: "email", actions: assign(() => initialContext) }
3091
+ }
3092
+ }
3093
+ }
3094
+ });
3095
+ }
3096
+ function requireEmail2(context) {
3097
+ if (!context.email) {
3098
+ throw Errors.invalidInput("email", "Auth bootstrap requires an email.");
3099
+ }
3100
+ return context.email;
3101
+ }
3102
+ function requireCode(context) {
3103
+ if (!context.code) {
3104
+ throw Errors.invalidInput("code", "Auth bootstrap requires an OTP code.");
3105
+ }
3106
+ return context.code;
3107
+ }
3108
+ function requireBootstrapToken(context) {
3109
+ if (!context.bootstrapToken) {
3110
+ throw Errors.invalidInput(
3111
+ "bootstrapToken",
3112
+ "Auth bootstrap requires a continuation token."
3113
+ );
3114
+ }
3115
+ return context.bootstrapToken;
3116
+ }
3117
+ function requireUsername(context) {
3118
+ if (!context.username) {
3119
+ throw Errors.invalidInput("username", "Auth bootstrap requires a username.");
3120
+ }
3121
+ return context.username;
3122
+ }
3123
+ function requireSignerProvider(context) {
3124
+ if (!context.signerProvider) {
3125
+ throw Errors.invalidInput(
3126
+ "signerProvider",
3127
+ "Auth bootstrap requires a signer provider."
3128
+ );
3129
+ }
3130
+ return context.signerProvider;
3131
+ }
3132
+ function errorFromEvent2(event) {
3133
+ const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3134
+ if (cause instanceof CapxulError || cause instanceof CapxulError2) {
3135
+ return cause;
3136
+ }
3137
+ return Errors.providerError("auth", "bootstrap", cause);
3138
+ }
3139
+ function timeoutError2(state) {
3140
+ return Errors.providerError(
3141
+ "auth",
3142
+ "bootstrap",
3143
+ new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
3144
+ );
3145
+ }
3146
+ function emailDomain2(email) {
3147
+ const domain = email.split("@")[1]?.trim().toLowerCase();
3148
+ return domain || "unknown";
3149
+ }
2553
3150
  function createProvisioningMachine(client) {
2554
3151
  return setup({
2555
3152
  types: {},
@@ -2622,13 +3219,13 @@ function createProvisioningMachine(client) {
2622
3219
  },
2623
3220
  onError: {
2624
3221
  target: "error",
2625
- actions: assign({ error: ({ event }) => errorFromEvent2(event) })
3222
+ actions: assign({ error: ({ event }) => errorFromEvent3(event) })
2626
3223
  }
2627
3224
  },
2628
3225
  after: {
2629
3226
  [FLOW_INVOKE_TIMEOUT_MS]: {
2630
3227
  target: "error",
2631
- actions: assign({ error: () => timeoutError2() })
3228
+ actions: assign({ error: () => timeoutError3() })
2632
3229
  }
2633
3230
  }
2634
3231
  },
@@ -2646,7 +3243,7 @@ function createProvisioningMachine(client) {
2646
3243
  * this payload on its `onDone` transition and branches via guards
2647
3244
  * on `event.output.error`.
2648
3245
  */
2649
- output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError2() }
3246
+ output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError3() }
2650
3247
  });
2651
3248
  }
2652
3249
  function requireProvisionInput(context) {
@@ -2658,13 +3255,13 @@ function requireProvisionInput(context) {
2658
3255
  }
2659
3256
  return context.input;
2660
3257
  }
2661
- function errorFromEvent2(event) {
3258
+ function errorFromEvent3(event) {
2662
3259
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
2663
3260
  if (cause instanceof CapxulError) return cause;
2664
3261
  if (cause instanceof CapxulError2) return cause;
2665
3262
  return Errors.providerError("provisioning", "flow", cause);
2666
3263
  }
2667
- function timeoutError2() {
3264
+ function timeoutError3() {
2668
3265
  return Errors.providerError(
2669
3266
  "provisioning",
2670
3267
  "flow",
@@ -2757,7 +3354,7 @@ function createOnboardingFlowMachine(client) {
2757
3354
  error: ({ event }) => extractChildErrorOrFallback(event)
2758
3355
  }),
2759
3356
  assignChildThrown: assign({
2760
- error: ({ event }) => errorFromEvent3(event)
3357
+ error: ({ event }) => errorFromEvent4(event)
2761
3358
  }),
2762
3359
  assignAccountFromChild: assign({
2763
3360
  account: ({ event }) => extractChildAccountOrNull(event)
@@ -2919,7 +3516,7 @@ function extractChildAccountOrNull(event) {
2919
3516
  if (output && "account" in output && output.account) return output.account;
2920
3517
  return null;
2921
3518
  }
2922
- function errorFromEvent3(event) {
3519
+ function errorFromEvent4(event) {
2923
3520
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
2924
3521
  if (cause instanceof CapxulError) return cause;
2925
3522
  if (cause instanceof CapxulError2) return cause;
@@ -2951,6 +3548,7 @@ function createCapxulClient(config = {}) {
2951
3548
  const client = clientWithoutFlows;
2952
3549
  client.flows = {
2953
3550
  auth: () => createAuthFlowMachine(client),
3551
+ authBootstrap: () => createAuthBootstrapFlowMachine(client),
2954
3552
  onboarding: () => createOnboardingFlowMachine(client),
2955
3553
  provisioning: () => createProvisioningMachine(client)
2956
3554
  };
@@ -3055,4 +3653,4 @@ function isWebhookEvent(value) {
3055
3653
  return typeof candidate.id === "string" && typeof candidate.type === "string" && typeof candidate.createdAt === "string" && (candidate.operationId === void 0 || typeof candidate.operationId === "string") && (candidate.correlationId === void 0 || typeof candidate.correlationId === "string") && !!candidate.data && typeof candidate.data === "object" && !Array.isArray(candidate.data);
3056
3654
  }
3057
3655
 
3058
- export { CapxulError, createAuthFlowMachine, createCapxulClient, createLocalSigner, createOnboardingFlowMachine, createProvisioningMachine as createProvisioningFlowMachine, makeHttpTransport, matchAction, matchError, matchStatus, toAccountId, toApiKeyId, toBalanceLedgerEntryId, toDocumentId, toEmail, toExternalAccountId, toKybProfileId, toKycProfileId, toMemberId, toOperationId, toOrganizationId, toPaymentId, toPhoneNumber, toSafeId, toSubAccountId, toTokenTransferId, toTransferId, toTreasuryId, toUsername, toVirtualAccountId, toVirtualCardId, toWebhookEndpointId, toWebhookEventId, toWithdrawalId, tryCatch, verifyWebhook };
3656
+ export { CapxulError, createAuthBootstrapFlowMachine, createAuthFlowMachine, createCapxulClient, createLocalSigner, createOnboardingFlowMachine, createProvisioningMachine as createProvisioningFlowMachine, makeHttpTransport, matchAction, matchError, matchStatus, toAccountId, toApiKeyId, toBalanceLedgerEntryId, toDocumentId, toEmail, toExternalAccountId, toKybProfileId, toKycProfileId, toMemberId, toOperationId, toOrganizationId, toPaymentId, toPhoneNumber, toSafeId, toSubAccountId, toTokenTransferId, toTransferId, toTreasuryId, toUsername, toVirtualAccountId, toVirtualCardId, toWebhookEndpointId, toWebhookEventId, toWithdrawalId, tryCatch, verifyWebhook };