@capxul/sdk 0.1.0-alpha.8 → 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/client.js CHANGED
@@ -1025,7 +1025,68 @@ function createAuthClient(config = {}) {
1025
1025
  ];
1026
1026
  }
1027
1027
  }
1028
- return [null, session];
1028
+ if (!dataClient) {
1029
+ return [
1030
+ new CapxulError({
1031
+ code: "NOT_AUTHENTICATED",
1032
+ message: "Auth bootstrap requires an authenticated Convex data client."
1033
+ }),
1034
+ null
1035
+ ];
1036
+ }
1037
+ try {
1038
+ const resolution = await dataClient.mutation(
1039
+ api.authBootstrap.resolveAfterOtp,
1040
+ {
1041
+ email: session.email,
1042
+ sessionToken: session.token
1043
+ }
1044
+ );
1045
+ if (resolution.kind === "existing_member") {
1046
+ return [null, { ...resolution, session }];
1047
+ }
1048
+ return [null, { ...resolution, session }];
1049
+ } catch (cause) {
1050
+ return [fromConvexError(cause), null];
1051
+ }
1052
+ },
1053
+ completeBootstrap: async (input) => {
1054
+ const session = sessionStore.get();
1055
+ const data = dataClient ?? config.data;
1056
+ if (!session || !data) {
1057
+ return [
1058
+ new CapxulError({
1059
+ code: "INVALID_INPUT",
1060
+ message: "completeBootstrap requires the OTP-verified session that issued the bootstrap token."
1061
+ }),
1062
+ null
1063
+ ];
1064
+ }
1065
+ if (input.signerProvider.kind !== "local-private-key") {
1066
+ return [
1067
+ new CapxulError({
1068
+ code: "INVALID_INPUT",
1069
+ message: "completeBootstrap currently supports local-private-key signer providers only."
1070
+ }),
1071
+ null
1072
+ ];
1073
+ }
1074
+ try {
1075
+ const result = await data.mutation(api.authBootstrap.completeBootstrap, {
1076
+ bootstrapToken: input.bootstrapToken,
1077
+ sessionToken: session.token,
1078
+ username: input.username,
1079
+ displayName: input.displayName,
1080
+ countryCode: input.countryCode,
1081
+ signerProvider: input.signerProvider
1082
+ });
1083
+ return [null, { kind: "authenticated", session, ...result }];
1084
+ } catch (cause) {
1085
+ return [
1086
+ fromConvexError(cause),
1087
+ null
1088
+ ];
1089
+ }
1029
1090
  },
1030
1091
  getSession: async () => [null, sessionStore.get()],
1031
1092
  signOut: async () => {
@@ -2384,7 +2445,7 @@ function createAuthFlowMachine(client) {
2384
2445
  }),
2385
2446
  verifyOtp: fromPromise(
2386
2447
  async ({ input, signal }) => {
2387
- const [error, session] = await client.auth.verifyOtp(
2448
+ const [error, result] = await client.auth.verifyOtp(
2388
2449
  {
2389
2450
  email: input.email,
2390
2451
  otp: input.code
@@ -2392,7 +2453,14 @@ function createAuthFlowMachine(client) {
2392
2453
  { signal }
2393
2454
  );
2394
2455
  if (error) throw error;
2395
- return session;
2456
+ if (result.kind === "bootstrap_required") {
2457
+ throw new CapxulError({
2458
+ code: "ACTION_REQUIRED",
2459
+ message: "OTP verified but auth bootstrap is required. Use createAuthBootstrapFlowMachine for product sign-up.",
2460
+ details: { reason: result.reason }
2461
+ });
2462
+ }
2463
+ return result.session;
2396
2464
  }
2397
2465
  ),
2398
2466
  signOut: fromPromise(async () => {
@@ -2640,6 +2708,345 @@ function emailDomain(email) {
2640
2708
  const domain = email.split("@")[1]?.trim().toLowerCase();
2641
2709
  return domain || "unknown";
2642
2710
  }
2711
+ var initialContext = {
2712
+ email: null,
2713
+ code: null,
2714
+ username: null,
2715
+ signerProvider: null,
2716
+ bootstrapToken: null,
2717
+ bootstrapReason: null,
2718
+ session: null,
2719
+ account: null,
2720
+ safe: null,
2721
+ error: null
2722
+ };
2723
+ function createAuthBootstrapFlowMachine(client) {
2724
+ return setup({
2725
+ types: {},
2726
+ actors: {
2727
+ sendOtp: fromPromise(async ({ input, signal }) => {
2728
+ const [error] = await client.auth.sendOtp(
2729
+ { email: input.email },
2730
+ { signal }
2731
+ );
2732
+ if (error) throw error;
2733
+ }),
2734
+ verifyOtp: fromPromise(
2735
+ async ({ input, signal }) => {
2736
+ const [error, result] = await client.auth.verifyOtp(
2737
+ { email: input.email, otp: input.code },
2738
+ { signal }
2739
+ );
2740
+ if (error) throw error;
2741
+ return result;
2742
+ }
2743
+ ),
2744
+ completeBootstrap: fromPromise(async ({ input }) => {
2745
+ const [error, result] = await client.auth.completeBootstrap(input);
2746
+ if (error) throw error;
2747
+ return result;
2748
+ }),
2749
+ signOut: fromPromise(async () => {
2750
+ const [error] = await client.auth.signOut();
2751
+ if (error) throw error;
2752
+ })
2753
+ },
2754
+ actions: {
2755
+ trackOtpRequested: ({ context }) => {
2756
+ if (!context.email) return;
2757
+ track("auth_otp_requested", {
2758
+ email_domain: emailDomain2(context.email)
2759
+ });
2760
+ },
2761
+ trackFailed: ({ event }) => {
2762
+ track("auth_failed", {
2763
+ auth_type: "email_otp",
2764
+ reason: errorFromEvent2(event).code
2765
+ });
2766
+ },
2767
+ trackTimeoutFailed: () => {
2768
+ track("auth_failed", {
2769
+ auth_type: "email_otp",
2770
+ reason: "timeout"
2771
+ });
2772
+ },
2773
+ trackVerified: () => {
2774
+ track("auth_verified", { auth_type: "email_otp" });
2775
+ },
2776
+ trackBootstrapRequired: ({ context }) => {
2777
+ track("auth_verified", {
2778
+ auth_type: "email_otp",
2779
+ auth_mode: context.bootstrapReason ?? "bootstrap_required"
2780
+ });
2781
+ },
2782
+ identifyAndTrack: ({ context }) => {
2783
+ if (!context.session) return;
2784
+ identify(context.session.authUserId, {
2785
+ email_domain: emailDomain2(context.session.email)
2786
+ });
2787
+ track("auth_identified", {
2788
+ email_domain: emailDomain2(context.session.email)
2789
+ });
2790
+ },
2791
+ trackSignedOut: () => {
2792
+ track("auth_signed_out");
2793
+ }
2794
+ }
2795
+ }).createMachine({
2796
+ id: "authBootstrap",
2797
+ initial: "email",
2798
+ context: initialContext,
2799
+ states: {
2800
+ email: {
2801
+ on: {
2802
+ ENTER_EMAIL: {
2803
+ actions: assign({
2804
+ email: ({ event }) => event.email,
2805
+ error: () => null
2806
+ })
2807
+ },
2808
+ REQUEST_OTP: { target: "sending_otp" }
2809
+ }
2810
+ },
2811
+ sending_otp: {
2812
+ invoke: {
2813
+ src: "sendOtp",
2814
+ input: ({ context }) => ({ email: requireEmail2(context) }),
2815
+ onDone: { target: "otp_requested", actions: "trackOtpRequested" },
2816
+ onError: {
2817
+ target: "otp_requested",
2818
+ actions: [
2819
+ assign({ error: ({ event }) => errorFromEvent2(event) }),
2820
+ "trackFailed"
2821
+ ]
2822
+ }
2823
+ },
2824
+ after: {
2825
+ [FLOW_INVOKE_TIMEOUT_MS]: {
2826
+ target: "otp_requested",
2827
+ actions: [
2828
+ assign({ error: () => timeoutError2("sending_otp") }),
2829
+ "trackTimeoutFailed"
2830
+ ]
2831
+ }
2832
+ }
2833
+ },
2834
+ otp_requested: {
2835
+ on: {
2836
+ ENTER_OTP: {
2837
+ actions: assign({
2838
+ code: ({ event }) => event.code,
2839
+ error: () => null
2840
+ })
2841
+ },
2842
+ VERIFY_OTP: { target: "verifying_otp" },
2843
+ BACK: { target: "email" },
2844
+ RESET: { target: "email", actions: assign(() => initialContext) }
2845
+ }
2846
+ },
2847
+ verifying_otp: {
2848
+ invoke: {
2849
+ src: "verifyOtp",
2850
+ input: ({ context }) => ({
2851
+ email: requireEmail2(context),
2852
+ code: requireCode(context)
2853
+ }),
2854
+ onDone: [
2855
+ {
2856
+ guard: ({ event }) => event.output.kind === "existing_member",
2857
+ target: "authenticated",
2858
+ actions: [
2859
+ assign({
2860
+ session: ({ event }) => event.output.session,
2861
+ account: ({ event }) => event.output.kind === "existing_member" ? event.output.account : null,
2862
+ username: ({ event }) => event.output.kind === "existing_member" ? event.output.username : null,
2863
+ safe: ({ event }) => event.output.kind === "existing_member" ? event.output.safe : null,
2864
+ email: () => null,
2865
+ error: () => null
2866
+ }),
2867
+ "trackVerified",
2868
+ "identifyAndTrack"
2869
+ ]
2870
+ },
2871
+ {
2872
+ target: "bootstrap_required",
2873
+ actions: [
2874
+ assign({
2875
+ session: ({ event }) => event.output.session,
2876
+ bootstrapToken: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.bootstrapToken : null,
2877
+ bootstrapReason: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.reason : null,
2878
+ username: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.username ?? null : null,
2879
+ email: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.email : null,
2880
+ error: () => null
2881
+ }),
2882
+ "trackVerified",
2883
+ "trackBootstrapRequired"
2884
+ ]
2885
+ }
2886
+ ],
2887
+ onError: {
2888
+ target: "otp_requested",
2889
+ actions: [
2890
+ assign({ error: ({ event }) => errorFromEvent2(event) }),
2891
+ "trackFailed"
2892
+ ]
2893
+ }
2894
+ },
2895
+ after: {
2896
+ [FLOW_INVOKE_TIMEOUT_MS]: {
2897
+ target: "otp_requested",
2898
+ actions: [
2899
+ assign({ error: () => timeoutError2("verifying_otp") }),
2900
+ "trackTimeoutFailed"
2901
+ ]
2902
+ }
2903
+ }
2904
+ },
2905
+ bootstrap_required: {
2906
+ on: {
2907
+ ENTER_USERNAME: {
2908
+ actions: assign({
2909
+ username: ({ event }) => event.username,
2910
+ error: () => null
2911
+ })
2912
+ },
2913
+ ENTER_SIGNER_PROVIDER: {
2914
+ actions: assign({
2915
+ signerProvider: ({ event }) => event.signerProvider,
2916
+ error: () => null
2917
+ })
2918
+ },
2919
+ COMPLETE_BOOTSTRAP: { target: "completing_bootstrap" },
2920
+ BACK: { target: "otp_requested" },
2921
+ RESET: { target: "email", actions: assign(() => initialContext) }
2922
+ }
2923
+ },
2924
+ completing_bootstrap: {
2925
+ invoke: {
2926
+ src: "completeBootstrap",
2927
+ input: ({ context }) => ({
2928
+ bootstrapToken: requireBootstrapToken(context),
2929
+ username: requireUsername(context),
2930
+ signerProvider: requireSignerProvider(context)
2931
+ }),
2932
+ onDone: {
2933
+ target: "authenticated",
2934
+ actions: [
2935
+ assign({
2936
+ session: ({ event }) => event.output.session,
2937
+ account: ({ event }) => event.output.account,
2938
+ username: ({ event }) => event.output.username,
2939
+ safe: ({ event }) => event.output.safe,
2940
+ bootstrapToken: () => null,
2941
+ bootstrapReason: () => null,
2942
+ signerProvider: () => null,
2943
+ email: () => null,
2944
+ error: () => null
2945
+ }),
2946
+ "identifyAndTrack"
2947
+ ]
2948
+ },
2949
+ onError: {
2950
+ target: "bootstrap_required",
2951
+ actions: [
2952
+ assign({ error: ({ event }) => errorFromEvent2(event) }),
2953
+ "trackFailed"
2954
+ ]
2955
+ }
2956
+ },
2957
+ after: {
2958
+ [FLOW_INVOKE_TIMEOUT_MS]: {
2959
+ target: "bootstrap_required",
2960
+ actions: [
2961
+ assign({ error: () => timeoutError2("completing_bootstrap") }),
2962
+ "trackTimeoutFailed"
2963
+ ]
2964
+ }
2965
+ }
2966
+ },
2967
+ authenticated: {
2968
+ on: {
2969
+ SIGN_OUT: { target: "signing_out" }
2970
+ }
2971
+ },
2972
+ signing_out: {
2973
+ invoke: {
2974
+ src: "signOut",
2975
+ onDone: {
2976
+ target: "email",
2977
+ actions: [
2978
+ assign(() => initialContext),
2979
+ "trackSignedOut"
2980
+ ]
2981
+ },
2982
+ onError: {
2983
+ target: "error",
2984
+ actions: assign({ error: ({ event }) => errorFromEvent2(event) })
2985
+ }
2986
+ }
2987
+ },
2988
+ error: {
2989
+ on: {
2990
+ RESET: { target: "email", actions: assign(() => initialContext) }
2991
+ }
2992
+ }
2993
+ }
2994
+ });
2995
+ }
2996
+ function requireEmail2(context) {
2997
+ if (!context.email) {
2998
+ throw Errors.invalidInput("email", "Auth bootstrap requires an email.");
2999
+ }
3000
+ return context.email;
3001
+ }
3002
+ function requireCode(context) {
3003
+ if (!context.code) {
3004
+ throw Errors.invalidInput("code", "Auth bootstrap requires an OTP code.");
3005
+ }
3006
+ return context.code;
3007
+ }
3008
+ function requireBootstrapToken(context) {
3009
+ if (!context.bootstrapToken) {
3010
+ throw Errors.invalidInput(
3011
+ "bootstrapToken",
3012
+ "Auth bootstrap requires a continuation token."
3013
+ );
3014
+ }
3015
+ return context.bootstrapToken;
3016
+ }
3017
+ function requireUsername(context) {
3018
+ if (!context.username) {
3019
+ throw Errors.invalidInput("username", "Auth bootstrap requires a username.");
3020
+ }
3021
+ return context.username;
3022
+ }
3023
+ function requireSignerProvider(context) {
3024
+ if (!context.signerProvider) {
3025
+ throw Errors.invalidInput(
3026
+ "signerProvider",
3027
+ "Auth bootstrap requires a signer provider."
3028
+ );
3029
+ }
3030
+ return context.signerProvider;
3031
+ }
3032
+ function errorFromEvent2(event) {
3033
+ const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3034
+ if (cause instanceof CapxulError || cause instanceof CapxulError2) {
3035
+ return cause;
3036
+ }
3037
+ return Errors.providerError("auth", "bootstrap", cause);
3038
+ }
3039
+ function timeoutError2(state) {
3040
+ return Errors.providerError(
3041
+ "auth",
3042
+ "bootstrap",
3043
+ new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
3044
+ );
3045
+ }
3046
+ function emailDomain2(email) {
3047
+ const domain = email.split("@")[1]?.trim().toLowerCase();
3048
+ return domain || "unknown";
3049
+ }
2643
3050
  function createProvisioningMachine(client) {
2644
3051
  return setup({
2645
3052
  types: {},
@@ -2712,13 +3119,13 @@ function createProvisioningMachine(client) {
2712
3119
  },
2713
3120
  onError: {
2714
3121
  target: "error",
2715
- actions: assign({ error: ({ event }) => errorFromEvent2(event) })
3122
+ actions: assign({ error: ({ event }) => errorFromEvent3(event) })
2716
3123
  }
2717
3124
  },
2718
3125
  after: {
2719
3126
  [FLOW_INVOKE_TIMEOUT_MS]: {
2720
3127
  target: "error",
2721
- actions: assign({ error: () => timeoutError2() })
3128
+ actions: assign({ error: () => timeoutError3() })
2722
3129
  }
2723
3130
  }
2724
3131
  },
@@ -2736,7 +3143,7 @@ function createProvisioningMachine(client) {
2736
3143
  * this payload on its `onDone` transition and branches via guards
2737
3144
  * on `event.output.error`.
2738
3145
  */
2739
- output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError2() }
3146
+ output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError3() }
2740
3147
  });
2741
3148
  }
2742
3149
  function requireProvisionInput(context) {
@@ -2748,13 +3155,13 @@ function requireProvisionInput(context) {
2748
3155
  }
2749
3156
  return context.input;
2750
3157
  }
2751
- function errorFromEvent2(event) {
3158
+ function errorFromEvent3(event) {
2752
3159
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
2753
3160
  if (cause instanceof CapxulError) return cause;
2754
3161
  if (cause instanceof CapxulError2) return cause;
2755
3162
  return Errors.providerError("provisioning", "flow", cause);
2756
3163
  }
2757
- function timeoutError2() {
3164
+ function timeoutError3() {
2758
3165
  return Errors.providerError(
2759
3166
  "provisioning",
2760
3167
  "flow",
@@ -2847,7 +3254,7 @@ function createOnboardingFlowMachine(client) {
2847
3254
  error: ({ event }) => extractChildErrorOrFallback(event)
2848
3255
  }),
2849
3256
  assignChildThrown: assign({
2850
- error: ({ event }) => errorFromEvent3(event)
3257
+ error: ({ event }) => errorFromEvent4(event)
2851
3258
  }),
2852
3259
  assignAccountFromChild: assign({
2853
3260
  account: ({ event }) => extractChildAccountOrNull(event)
@@ -3009,7 +3416,7 @@ function extractChildAccountOrNull(event) {
3009
3416
  if (output && "account" in output && output.account) return output.account;
3010
3417
  return null;
3011
3418
  }
3012
- function errorFromEvent3(event) {
3419
+ function errorFromEvent4(event) {
3013
3420
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3014
3421
  if (cause instanceof CapxulError) return cause;
3015
3422
  if (cause instanceof CapxulError2) return cause;
@@ -3041,6 +3448,7 @@ function createCapxulClient(config = {}) {
3041
3448
  const client = clientWithoutFlows;
3042
3449
  client.flows = {
3043
3450
  auth: () => createAuthFlowMachine(client),
3451
+ authBootstrap: () => createAuthBootstrapFlowMachine(client),
3044
3452
  onboarding: () => createOnboardingFlowMachine(client),
3045
3453
  provisioning: () => createProvisioningMachine(client)
3046
3454
  };