@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/CHANGELOG.md +15 -8
- package/README.md +86 -4
- package/dist/{client-ChfXWMzO.d.ts → client-ChZrdf3R.d.ts} +209 -15
- package/dist/{client-DW_fW9DC.d.cts → client-DG6ODWu6.d.cts} +209 -15
- package/dist/client.cjs +634 -36
- package/dist/client.d.cts +2 -3
- package/dist/client.d.ts +2 -3
- package/dist/client.js +634 -36
- package/dist/index.cjs +635 -36
- package/dist/index.d.cts +11 -33
- package/dist/index.d.ts +11 -33
- package/dist/index.js +635 -37
- package/dist/types-PM4AQRLP.d.ts +1136 -0
- package/dist/types-hfcOE7Oi.d.cts +1136 -0
- package/dist/webhooks.d.cts +1 -2
- package/dist/webhooks.d.ts +1 -2
- package/package.json +7 -7
- package/dist/types-CYvLP5pP.d.ts +0 -213
- package/dist/types-X02RbQ2u.d.cts +0 -213
package/dist/client.js
CHANGED
|
@@ -577,7 +577,12 @@ var Errors = {
|
|
|
577
577
|
"Idempotency key was already used for a different request",
|
|
578
578
|
{ details }
|
|
579
579
|
),
|
|
580
|
-
emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}
|
|
580
|
+
emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
|
|
581
|
+
details
|
|
582
|
+
}),
|
|
583
|
+
rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
|
|
584
|
+
details: { ...details }
|
|
585
|
+
}),
|
|
581
586
|
internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
|
|
582
587
|
/**
|
|
583
588
|
* Verification gate. Surfaced when a request hits a verification
|
|
@@ -650,13 +655,13 @@ function createLifecycle(initial) {
|
|
|
650
655
|
}
|
|
651
656
|
function makeBuildTimeUrlsTransport(config) {
|
|
652
657
|
if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
|
|
653
|
-
throw
|
|
658
|
+
throw invalidConfigError(
|
|
654
659
|
"authBaseUrl",
|
|
655
660
|
"build-time-urls transport requires a non-empty authBaseUrl."
|
|
656
661
|
);
|
|
657
662
|
}
|
|
658
663
|
if (!config.convexUrl || config.convexUrl.trim().length === 0) {
|
|
659
|
-
throw
|
|
664
|
+
throw invalidConfigError(
|
|
660
665
|
"convexUrl",
|
|
661
666
|
"build-time-urls transport requires a non-empty convexUrl."
|
|
662
667
|
);
|
|
@@ -670,6 +675,7 @@ function makeBuildTimeUrlsTransport(config) {
|
|
|
670
675
|
return {
|
|
671
676
|
authBaseUrl,
|
|
672
677
|
convexUrl,
|
|
678
|
+
ensureRuntime: async () => runtime,
|
|
673
679
|
fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
|
|
674
680
|
getState: lifecycle.getState,
|
|
675
681
|
subscribe: lifecycle.subscribe,
|
|
@@ -685,14 +691,15 @@ function makeBuildTimeUrlsTransport(config) {
|
|
|
685
691
|
};
|
|
686
692
|
}
|
|
687
693
|
function makePublishableKeyTransport(config) {
|
|
688
|
-
|
|
689
|
-
|
|
694
|
+
const publishableKey = config.publishableKey?.trim();
|
|
695
|
+
if (!publishableKey) {
|
|
696
|
+
throw invalidConfigError(
|
|
690
697
|
"publishableKey",
|
|
691
698
|
"publishable-key transport requires a non-empty publishableKey."
|
|
692
699
|
);
|
|
693
700
|
}
|
|
694
701
|
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
695
|
-
const bootstrapUrl =
|
|
702
|
+
const bootstrapUrl = normalizeBootstrapUrl(
|
|
696
703
|
config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
|
|
697
704
|
);
|
|
698
705
|
let authBaseUrl = "";
|
|
@@ -707,23 +714,20 @@ function makePublishableKeyTransport(config) {
|
|
|
707
714
|
const response = await fetchImpl(bootstrapUrl, {
|
|
708
715
|
method: "POST",
|
|
709
716
|
headers: { "content-type": "application/json" },
|
|
710
|
-
body: JSON.stringify({ publishableKey
|
|
717
|
+
body: JSON.stringify({ publishableKey })
|
|
711
718
|
});
|
|
712
719
|
if (!response.ok) {
|
|
713
|
-
throw
|
|
714
|
-
"publishableKey",
|
|
715
|
-
`${bootstrapUrl} failed with HTTP ${response.status}.`
|
|
716
|
-
);
|
|
720
|
+
throw await bootstrapResponseError(response, bootstrapUrl);
|
|
717
721
|
}
|
|
718
|
-
const body = await response
|
|
722
|
+
const body = await readBootstrapSuccessBody(response);
|
|
719
723
|
if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
|
|
720
|
-
throw
|
|
724
|
+
throw bootstrapContractError(
|
|
721
725
|
"authBaseUrl",
|
|
722
726
|
"/v1/client/bootstrap returned no authBaseUrl."
|
|
723
727
|
);
|
|
724
728
|
}
|
|
725
729
|
if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
|
|
726
|
-
throw
|
|
730
|
+
throw bootstrapContractError(
|
|
727
731
|
"convexUrl",
|
|
728
732
|
"/v1/client/bootstrap returned no convexUrl."
|
|
729
733
|
);
|
|
@@ -735,16 +739,13 @@ function makePublishableKeyTransport(config) {
|
|
|
735
739
|
return runtime;
|
|
736
740
|
})();
|
|
737
741
|
bootstrapPromise = attempt.catch((err) => {
|
|
742
|
+
const error = normalizeBootstrapThrownError(err);
|
|
738
743
|
bootstrapPromise = null;
|
|
739
744
|
lifecycle.setState({
|
|
740
745
|
status: "error",
|
|
741
|
-
error
|
|
742
|
-
code: "UNKNOWN",
|
|
743
|
-
message: "Bootstrap failed without a typed CapxulError.",
|
|
744
|
-
cause: err
|
|
745
|
-
})
|
|
746
|
+
error
|
|
746
747
|
});
|
|
747
|
-
throw
|
|
748
|
+
throw error;
|
|
748
749
|
});
|
|
749
750
|
return await bootstrapPromise;
|
|
750
751
|
}
|
|
@@ -755,6 +756,7 @@ function makePublishableKeyTransport(config) {
|
|
|
755
756
|
get convexUrl() {
|
|
756
757
|
return convexUrl;
|
|
757
758
|
},
|
|
759
|
+
ensureRuntime: ensureBootstrap,
|
|
758
760
|
fetch: async (path, init) => {
|
|
759
761
|
const resolved = await ensureBootstrap();
|
|
760
762
|
return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
|
|
@@ -765,7 +767,7 @@ function makePublishableKeyTransport(config) {
|
|
|
765
767
|
markAuthenticated: ({ dataClient: nextDataClient }) => {
|
|
766
768
|
const current = lifecycle.getState();
|
|
767
769
|
if (current.status !== "ready" && current.status !== "authenticated") {
|
|
768
|
-
throw
|
|
770
|
+
throw internalTransportError(
|
|
769
771
|
`markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
|
|
770
772
|
);
|
|
771
773
|
}
|
|
@@ -787,6 +789,24 @@ function makePublishableKeyTransport(config) {
|
|
|
787
789
|
function stripTrailingSlash(url) {
|
|
788
790
|
return url.replace(/\/+$/, "");
|
|
789
791
|
}
|
|
792
|
+
function normalizeBootstrapUrl(url) {
|
|
793
|
+
const normalized = stripTrailingSlash(url.trim());
|
|
794
|
+
if (!isAbsoluteHttpUrl(normalized)) {
|
|
795
|
+
throw invalidConfigError(
|
|
796
|
+
"bootstrapUrl",
|
|
797
|
+
"publishable-key transport requires an absolute http(s) bootstrapUrl."
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
return normalized;
|
|
801
|
+
}
|
|
802
|
+
function isAbsoluteHttpUrl(url) {
|
|
803
|
+
try {
|
|
804
|
+
const parsed = new URL(url);
|
|
805
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
806
|
+
} catch {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
790
810
|
function resolveUrl(authBaseUrl, path) {
|
|
791
811
|
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
792
812
|
return path;
|
|
@@ -794,10 +814,142 @@ function resolveUrl(authBaseUrl, path) {
|
|
|
794
814
|
return `${authBaseUrl}${path}`;
|
|
795
815
|
}
|
|
796
816
|
function assertNever(value) {
|
|
797
|
-
throw
|
|
817
|
+
throw internalTransportError(
|
|
798
818
|
`Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
|
|
799
819
|
);
|
|
800
820
|
}
|
|
821
|
+
function invalidConfigError(field, reason) {
|
|
822
|
+
return new CapxulError({
|
|
823
|
+
code: "INVALID_INPUT",
|
|
824
|
+
message: `Invalid ${field}: ${reason}`,
|
|
825
|
+
details: { source: "sdk-config", field, reason }
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
function bootstrapContractError(field, message) {
|
|
829
|
+
return new CapxulError({
|
|
830
|
+
code: "INVALID_INPUT",
|
|
831
|
+
message,
|
|
832
|
+
details: {
|
|
833
|
+
source: "backend-bootstrap",
|
|
834
|
+
phase: "publishable-key-bootstrap",
|
|
835
|
+
field,
|
|
836
|
+
reason: message
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
function internalTransportError(reason) {
|
|
841
|
+
return new CapxulError({
|
|
842
|
+
code: "INTERNAL_ERROR",
|
|
843
|
+
message: `Internal error: ${reason}`,
|
|
844
|
+
details: { source: "sdk-transport", reason }
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
async function bootstrapResponseError(response, bootstrapUrl) {
|
|
848
|
+
const envelope = await readBootstrapErrorEnvelope(response);
|
|
849
|
+
const wireCode = readNonEmptyString(envelope?.error?.code);
|
|
850
|
+
const normalized = normalizeBootstrapErrorCode(wireCode);
|
|
851
|
+
const message = readNonEmptyString(envelope?.error?.message) ?? `${bootstrapUrl} failed with HTTP ${response.status}.`;
|
|
852
|
+
const backendDetails = readRecord(envelope?.error?.details);
|
|
853
|
+
return new CapxulError({
|
|
854
|
+
code: normalized.code,
|
|
855
|
+
message,
|
|
856
|
+
details: {
|
|
857
|
+
...backendDetails,
|
|
858
|
+
source: "backend-bootstrap",
|
|
859
|
+
phase: "publishable-key-bootstrap",
|
|
860
|
+
httpStatus: response.status,
|
|
861
|
+
...normalized.wireCode ? { wireCode: normalized.wireCode } : {}
|
|
862
|
+
},
|
|
863
|
+
operationId: readNonEmptyString(envelope?.error?.operationId),
|
|
864
|
+
correlationId: readNonEmptyString(envelope?.error?.correlationId),
|
|
865
|
+
retryable: typeof envelope?.error?.retryable === "boolean" ? envelope.error.retryable : void 0
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
async function readBootstrapErrorEnvelope(response) {
|
|
869
|
+
try {
|
|
870
|
+
const parsed = await response.json();
|
|
871
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
872
|
+
} catch {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
async function readBootstrapSuccessBody(response) {
|
|
877
|
+
try {
|
|
878
|
+
const parsed = await response.json();
|
|
879
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
880
|
+
} catch {
|
|
881
|
+
throw bootstrapContractError(
|
|
882
|
+
"body",
|
|
883
|
+
"/v1/client/bootstrap returned invalid JSON."
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function normalizeBootstrapThrownError(error) {
|
|
888
|
+
if (error instanceof CapxulError) return error;
|
|
889
|
+
return new CapxulError({
|
|
890
|
+
code: "NETWORK_ERROR",
|
|
891
|
+
message: "Publishable-key bootstrap network failure.",
|
|
892
|
+
cause: error,
|
|
893
|
+
details: {
|
|
894
|
+
source: "bootstrap-network",
|
|
895
|
+
phase: "publishable-key-bootstrap"
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
function normalizeBootstrapErrorCode(wireCode) {
|
|
900
|
+
if (wireCode === "INTERNAL_SERVER_ERROR") {
|
|
901
|
+
return { code: "INTERNAL_ERROR", wireCode };
|
|
902
|
+
}
|
|
903
|
+
if (wireCode && isCapxulErrorCode(wireCode)) {
|
|
904
|
+
return { code: wireCode };
|
|
905
|
+
}
|
|
906
|
+
return wireCode ? { code: "UNKNOWN", wireCode } : { code: "UNKNOWN" };
|
|
907
|
+
}
|
|
908
|
+
var CAPXUL_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
909
|
+
"NOT_AUTHENTICATED",
|
|
910
|
+
"EMAIL_DELIVERY_FAILED",
|
|
911
|
+
"PROFILE_NOT_FOUND",
|
|
912
|
+
"SMART_ACCOUNT_MISSING",
|
|
913
|
+
"PLAYER_NOT_FOUND",
|
|
914
|
+
"ACCOUNT_NOT_FOUND",
|
|
915
|
+
"PROVIDER_ERROR",
|
|
916
|
+
"INVALID_INPUT",
|
|
917
|
+
"ENV_MISSING",
|
|
918
|
+
"NOT_IMPLEMENTED",
|
|
919
|
+
"VERIFICATION_REQUIRED",
|
|
920
|
+
"INSUFFICIENT_BALANCE",
|
|
921
|
+
"INVALID_RECIPIENT",
|
|
922
|
+
"TRANSACTION_FAILED",
|
|
923
|
+
"RATE_LIMITED",
|
|
924
|
+
"NETWORK_ERROR",
|
|
925
|
+
"UNKNOWN",
|
|
926
|
+
"PERMISSION_DENIED",
|
|
927
|
+
"API_KEY_INVALID",
|
|
928
|
+
"API_KEY_EXPIRED",
|
|
929
|
+
"IDEMPOTENCY_CONFLICT",
|
|
930
|
+
"NOT_FOUND",
|
|
931
|
+
"OPERATION_CANCELED",
|
|
932
|
+
"OPERATION_TIMEOUT",
|
|
933
|
+
"ACTION_REQUIRED",
|
|
934
|
+
"KYC_REQUIRED",
|
|
935
|
+
"POLICY_DENIED",
|
|
936
|
+
"SAFE_NOT_READY",
|
|
937
|
+
"PROVIDER_UNAVAILABLE",
|
|
938
|
+
"PROVIDER_REJECTED",
|
|
939
|
+
"RECONCILIATION_FAILED",
|
|
940
|
+
"INTERNAL_ERROR",
|
|
941
|
+
"QUOTE_EXPIRED",
|
|
942
|
+
"QUOTE_NOT_FOUND"
|
|
943
|
+
]);
|
|
944
|
+
function isCapxulErrorCode(value) {
|
|
945
|
+
return CAPXUL_ERROR_CODES.has(value);
|
|
946
|
+
}
|
|
947
|
+
function readRecord(value) {
|
|
948
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
949
|
+
}
|
|
950
|
+
function readNonEmptyString(value) {
|
|
951
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
952
|
+
}
|
|
801
953
|
|
|
802
954
|
// src/core/auth.ts
|
|
803
955
|
function createAuthClient(config = {}) {
|
|
@@ -852,13 +1004,16 @@ function createAuthClient(config = {}) {
|
|
|
852
1004
|
email: signIn.user.email,
|
|
853
1005
|
token: signIn.token,
|
|
854
1006
|
convexJwt,
|
|
855
|
-
expiresAt: new Date(
|
|
1007
|
+
expiresAt: new Date(
|
|
1008
|
+
Date.now() + 30 * 24 * 60 * 60 * 1e3
|
|
1009
|
+
).toISOString()
|
|
856
1010
|
};
|
|
857
1011
|
sessionStore.set(session);
|
|
858
1012
|
if (config.auth?.createDataClient) {
|
|
859
1013
|
try {
|
|
860
1014
|
dataClient = await config.auth.createDataClient(session);
|
|
861
1015
|
mutableConfig(config).data = dataClient;
|
|
1016
|
+
transport.markAuthenticated({ dataClient });
|
|
862
1017
|
} catch (cause) {
|
|
863
1018
|
return [
|
|
864
1019
|
new CapxulError({
|
|
@@ -870,13 +1025,76 @@ function createAuthClient(config = {}) {
|
|
|
870
1025
|
];
|
|
871
1026
|
}
|
|
872
1027
|
}
|
|
873
|
-
|
|
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
|
+
}
|
|
874
1090
|
},
|
|
875
1091
|
getSession: async () => [null, sessionStore.get()],
|
|
876
1092
|
signOut: async () => {
|
|
877
1093
|
sessionStore.clear();
|
|
878
1094
|
dataClient = null;
|
|
879
1095
|
mutableConfig(config).data = void 0;
|
|
1096
|
+
const transport = getTransport();
|
|
1097
|
+
transport?.clearAuth();
|
|
880
1098
|
return [null, void 0];
|
|
881
1099
|
},
|
|
882
1100
|
serviceTokenMint: async () => stub("auth.serviceTokenMint"),
|
|
@@ -930,16 +1148,19 @@ async function postBetterAuth(transport, path, body, code, signal) {
|
|
|
930
1148
|
body: JSON.stringify(body),
|
|
931
1149
|
signal
|
|
932
1150
|
});
|
|
1151
|
+
const text = await response.text();
|
|
933
1152
|
if (!response.ok) {
|
|
1153
|
+
const parsedError = parseBetterAuthError(text);
|
|
934
1154
|
return [
|
|
935
1155
|
new CapxulError({
|
|
936
|
-
code,
|
|
937
|
-
message: `BetterAuth ${path} failed with HTTP ${response.status}
|
|
1156
|
+
code: parsedError.code ?? code,
|
|
1157
|
+
message: parsedError.message ?? `BetterAuth ${path} failed with HTTP ${response.status}.`,
|
|
1158
|
+
details: parsedError.details,
|
|
1159
|
+
retryable: parsedError.retryable
|
|
938
1160
|
}),
|
|
939
1161
|
null
|
|
940
1162
|
];
|
|
941
1163
|
}
|
|
942
|
-
const text = await response.text();
|
|
943
1164
|
return [null, text ? JSON.parse(text) : void 0];
|
|
944
1165
|
} catch (cause) {
|
|
945
1166
|
return [
|
|
@@ -952,6 +1173,36 @@ async function postBetterAuth(transport, path, body, code, signal) {
|
|
|
952
1173
|
];
|
|
953
1174
|
}
|
|
954
1175
|
}
|
|
1176
|
+
function parseBetterAuthError(text) {
|
|
1177
|
+
if (!text.trim()) {
|
|
1178
|
+
return {};
|
|
1179
|
+
}
|
|
1180
|
+
try {
|
|
1181
|
+
const body = JSON.parse(text);
|
|
1182
|
+
if (!body || typeof body !== "object") {
|
|
1183
|
+
return {};
|
|
1184
|
+
}
|
|
1185
|
+
const record = body;
|
|
1186
|
+
const nested = record.error && typeof record.error === "object" ? record.error : record;
|
|
1187
|
+
const code = typeof nested.code === "string" ? nested.code : void 0;
|
|
1188
|
+
const message = typeof nested.message === "string" ? nested.message : void 0;
|
|
1189
|
+
const details = nested.details && typeof nested.details === "object" ? nested.details : void 0;
|
|
1190
|
+
const correlationId = typeof nested.correlationId === "string" ? nested.correlationId : void 0;
|
|
1191
|
+
const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
|
|
1192
|
+
return {
|
|
1193
|
+
code: isCapxulErrorCode2(code) ? code : void 0,
|
|
1194
|
+
message,
|
|
1195
|
+
details,
|
|
1196
|
+
correlationId,
|
|
1197
|
+
retryable
|
|
1198
|
+
};
|
|
1199
|
+
} catch {
|
|
1200
|
+
return {};
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
function isCapxulErrorCode2(code) {
|
|
1204
|
+
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";
|
|
1205
|
+
}
|
|
955
1206
|
async function exchangeConvexToken(transport, config, token, signal) {
|
|
956
1207
|
const path = config.auth?.convexTokenUrl ?? "/convex/token";
|
|
957
1208
|
try {
|
|
@@ -2194,7 +2445,7 @@ function createAuthFlowMachine(client) {
|
|
|
2194
2445
|
}),
|
|
2195
2446
|
verifyOtp: fromPromise(
|
|
2196
2447
|
async ({ input, signal }) => {
|
|
2197
|
-
const [error,
|
|
2448
|
+
const [error, result] = await client.auth.verifyOtp(
|
|
2198
2449
|
{
|
|
2199
2450
|
email: input.email,
|
|
2200
2451
|
otp: input.code
|
|
@@ -2202,7 +2453,14 @@ function createAuthFlowMachine(client) {
|
|
|
2202
2453
|
{ signal }
|
|
2203
2454
|
);
|
|
2204
2455
|
if (error) throw error;
|
|
2205
|
-
|
|
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;
|
|
2206
2464
|
}
|
|
2207
2465
|
),
|
|
2208
2466
|
signOut: fromPromise(async () => {
|
|
@@ -2450,6 +2708,345 @@ function emailDomain(email) {
|
|
|
2450
2708
|
const domain = email.split("@")[1]?.trim().toLowerCase();
|
|
2451
2709
|
return domain || "unknown";
|
|
2452
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
|
+
}
|
|
2453
3050
|
function createProvisioningMachine(client) {
|
|
2454
3051
|
return setup({
|
|
2455
3052
|
types: {},
|
|
@@ -2522,13 +3119,13 @@ function createProvisioningMachine(client) {
|
|
|
2522
3119
|
},
|
|
2523
3120
|
onError: {
|
|
2524
3121
|
target: "error",
|
|
2525
|
-
actions: assign({ error: ({ event }) =>
|
|
3122
|
+
actions: assign({ error: ({ event }) => errorFromEvent3(event) })
|
|
2526
3123
|
}
|
|
2527
3124
|
},
|
|
2528
3125
|
after: {
|
|
2529
3126
|
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
2530
3127
|
target: "error",
|
|
2531
|
-
actions: assign({ error: () =>
|
|
3128
|
+
actions: assign({ error: () => timeoutError3() })
|
|
2532
3129
|
}
|
|
2533
3130
|
}
|
|
2534
3131
|
},
|
|
@@ -2546,7 +3143,7 @@ function createProvisioningMachine(client) {
|
|
|
2546
3143
|
* this payload on its `onDone` transition and branches via guards
|
|
2547
3144
|
* on `event.output.error`.
|
|
2548
3145
|
*/
|
|
2549
|
-
output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error:
|
|
3146
|
+
output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError3() }
|
|
2550
3147
|
});
|
|
2551
3148
|
}
|
|
2552
3149
|
function requireProvisionInput(context) {
|
|
@@ -2558,13 +3155,13 @@ function requireProvisionInput(context) {
|
|
|
2558
3155
|
}
|
|
2559
3156
|
return context.input;
|
|
2560
3157
|
}
|
|
2561
|
-
function
|
|
3158
|
+
function errorFromEvent3(event) {
|
|
2562
3159
|
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
2563
3160
|
if (cause instanceof CapxulError) return cause;
|
|
2564
3161
|
if (cause instanceof CapxulError2) return cause;
|
|
2565
3162
|
return Errors.providerError("provisioning", "flow", cause);
|
|
2566
3163
|
}
|
|
2567
|
-
function
|
|
3164
|
+
function timeoutError3() {
|
|
2568
3165
|
return Errors.providerError(
|
|
2569
3166
|
"provisioning",
|
|
2570
3167
|
"flow",
|
|
@@ -2657,7 +3254,7 @@ function createOnboardingFlowMachine(client) {
|
|
|
2657
3254
|
error: ({ event }) => extractChildErrorOrFallback(event)
|
|
2658
3255
|
}),
|
|
2659
3256
|
assignChildThrown: assign({
|
|
2660
|
-
error: ({ event }) =>
|
|
3257
|
+
error: ({ event }) => errorFromEvent4(event)
|
|
2661
3258
|
}),
|
|
2662
3259
|
assignAccountFromChild: assign({
|
|
2663
3260
|
account: ({ event }) => extractChildAccountOrNull(event)
|
|
@@ -2819,7 +3416,7 @@ function extractChildAccountOrNull(event) {
|
|
|
2819
3416
|
if (output && "account" in output && output.account) return output.account;
|
|
2820
3417
|
return null;
|
|
2821
3418
|
}
|
|
2822
|
-
function
|
|
3419
|
+
function errorFromEvent4(event) {
|
|
2823
3420
|
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
2824
3421
|
if (cause instanceof CapxulError) return cause;
|
|
2825
3422
|
if (cause instanceof CapxulError2) return cause;
|
|
@@ -2851,6 +3448,7 @@ function createCapxulClient(config = {}) {
|
|
|
2851
3448
|
const client = clientWithoutFlows;
|
|
2852
3449
|
client.flows = {
|
|
2853
3450
|
auth: () => createAuthFlowMachine(client),
|
|
3451
|
+
authBootstrap: () => createAuthBootstrapFlowMachine(client),
|
|
2854
3452
|
onboarding: () => createOnboardingFlowMachine(client),
|
|
2855
3453
|
provisioning: () => createProvisioningMachine(client)
|
|
2856
3454
|
};
|