@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.cjs
CHANGED
|
@@ -579,7 +579,12 @@ var Errors = {
|
|
|
579
579
|
"Idempotency key was already used for a different request",
|
|
580
580
|
{ details }
|
|
581
581
|
),
|
|
582
|
-
emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}
|
|
582
|
+
emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
|
|
583
|
+
details
|
|
584
|
+
}),
|
|
585
|
+
rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
|
|
586
|
+
details: { ...details }
|
|
587
|
+
}),
|
|
583
588
|
internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
|
|
584
589
|
/**
|
|
585
590
|
* Verification gate. Surfaced when a request hits a verification
|
|
@@ -652,13 +657,13 @@ function createLifecycle(initial) {
|
|
|
652
657
|
}
|
|
653
658
|
function makeBuildTimeUrlsTransport(config) {
|
|
654
659
|
if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
|
|
655
|
-
throw
|
|
660
|
+
throw invalidConfigError(
|
|
656
661
|
"authBaseUrl",
|
|
657
662
|
"build-time-urls transport requires a non-empty authBaseUrl."
|
|
658
663
|
);
|
|
659
664
|
}
|
|
660
665
|
if (!config.convexUrl || config.convexUrl.trim().length === 0) {
|
|
661
|
-
throw
|
|
666
|
+
throw invalidConfigError(
|
|
662
667
|
"convexUrl",
|
|
663
668
|
"build-time-urls transport requires a non-empty convexUrl."
|
|
664
669
|
);
|
|
@@ -672,6 +677,7 @@ function makeBuildTimeUrlsTransport(config) {
|
|
|
672
677
|
return {
|
|
673
678
|
authBaseUrl,
|
|
674
679
|
convexUrl,
|
|
680
|
+
ensureRuntime: async () => runtime,
|
|
675
681
|
fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
|
|
676
682
|
getState: lifecycle.getState,
|
|
677
683
|
subscribe: lifecycle.subscribe,
|
|
@@ -687,14 +693,15 @@ function makeBuildTimeUrlsTransport(config) {
|
|
|
687
693
|
};
|
|
688
694
|
}
|
|
689
695
|
function makePublishableKeyTransport(config) {
|
|
690
|
-
|
|
691
|
-
|
|
696
|
+
const publishableKey = config.publishableKey?.trim();
|
|
697
|
+
if (!publishableKey) {
|
|
698
|
+
throw invalidConfigError(
|
|
692
699
|
"publishableKey",
|
|
693
700
|
"publishable-key transport requires a non-empty publishableKey."
|
|
694
701
|
);
|
|
695
702
|
}
|
|
696
703
|
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
697
|
-
const bootstrapUrl =
|
|
704
|
+
const bootstrapUrl = normalizeBootstrapUrl(
|
|
698
705
|
config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
|
|
699
706
|
);
|
|
700
707
|
let authBaseUrl = "";
|
|
@@ -709,23 +716,20 @@ function makePublishableKeyTransport(config) {
|
|
|
709
716
|
const response = await fetchImpl(bootstrapUrl, {
|
|
710
717
|
method: "POST",
|
|
711
718
|
headers: { "content-type": "application/json" },
|
|
712
|
-
body: JSON.stringify({ publishableKey
|
|
719
|
+
body: JSON.stringify({ publishableKey })
|
|
713
720
|
});
|
|
714
721
|
if (!response.ok) {
|
|
715
|
-
throw
|
|
716
|
-
"publishableKey",
|
|
717
|
-
`${bootstrapUrl} failed with HTTP ${response.status}.`
|
|
718
|
-
);
|
|
722
|
+
throw await bootstrapResponseError(response, bootstrapUrl);
|
|
719
723
|
}
|
|
720
|
-
const body = await response
|
|
724
|
+
const body = await readBootstrapSuccessBody(response);
|
|
721
725
|
if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
|
|
722
|
-
throw
|
|
726
|
+
throw bootstrapContractError(
|
|
723
727
|
"authBaseUrl",
|
|
724
728
|
"/v1/client/bootstrap returned no authBaseUrl."
|
|
725
729
|
);
|
|
726
730
|
}
|
|
727
731
|
if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
|
|
728
|
-
throw
|
|
732
|
+
throw bootstrapContractError(
|
|
729
733
|
"convexUrl",
|
|
730
734
|
"/v1/client/bootstrap returned no convexUrl."
|
|
731
735
|
);
|
|
@@ -737,16 +741,13 @@ function makePublishableKeyTransport(config) {
|
|
|
737
741
|
return runtime;
|
|
738
742
|
})();
|
|
739
743
|
bootstrapPromise = attempt.catch((err) => {
|
|
744
|
+
const error = normalizeBootstrapThrownError(err);
|
|
740
745
|
bootstrapPromise = null;
|
|
741
746
|
lifecycle.setState({
|
|
742
747
|
status: "error",
|
|
743
|
-
error
|
|
744
|
-
code: "UNKNOWN",
|
|
745
|
-
message: "Bootstrap failed without a typed CapxulError.",
|
|
746
|
-
cause: err
|
|
747
|
-
})
|
|
748
|
+
error
|
|
748
749
|
});
|
|
749
|
-
throw
|
|
750
|
+
throw error;
|
|
750
751
|
});
|
|
751
752
|
return await bootstrapPromise;
|
|
752
753
|
}
|
|
@@ -757,6 +758,7 @@ function makePublishableKeyTransport(config) {
|
|
|
757
758
|
get convexUrl() {
|
|
758
759
|
return convexUrl;
|
|
759
760
|
},
|
|
761
|
+
ensureRuntime: ensureBootstrap,
|
|
760
762
|
fetch: async (path, init) => {
|
|
761
763
|
const resolved = await ensureBootstrap();
|
|
762
764
|
return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
|
|
@@ -767,7 +769,7 @@ function makePublishableKeyTransport(config) {
|
|
|
767
769
|
markAuthenticated: ({ dataClient: nextDataClient }) => {
|
|
768
770
|
const current = lifecycle.getState();
|
|
769
771
|
if (current.status !== "ready" && current.status !== "authenticated") {
|
|
770
|
-
throw
|
|
772
|
+
throw internalTransportError(
|
|
771
773
|
`markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
|
|
772
774
|
);
|
|
773
775
|
}
|
|
@@ -789,6 +791,24 @@ function makePublishableKeyTransport(config) {
|
|
|
789
791
|
function stripTrailingSlash(url) {
|
|
790
792
|
return url.replace(/\/+$/, "");
|
|
791
793
|
}
|
|
794
|
+
function normalizeBootstrapUrl(url) {
|
|
795
|
+
const normalized = stripTrailingSlash(url.trim());
|
|
796
|
+
if (!isAbsoluteHttpUrl(normalized)) {
|
|
797
|
+
throw invalidConfigError(
|
|
798
|
+
"bootstrapUrl",
|
|
799
|
+
"publishable-key transport requires an absolute http(s) bootstrapUrl."
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
return normalized;
|
|
803
|
+
}
|
|
804
|
+
function isAbsoluteHttpUrl(url) {
|
|
805
|
+
try {
|
|
806
|
+
const parsed = new URL(url);
|
|
807
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
808
|
+
} catch {
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
792
812
|
function resolveUrl(authBaseUrl, path) {
|
|
793
813
|
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
794
814
|
return path;
|
|
@@ -796,10 +816,142 @@ function resolveUrl(authBaseUrl, path) {
|
|
|
796
816
|
return `${authBaseUrl}${path}`;
|
|
797
817
|
}
|
|
798
818
|
function assertNever(value) {
|
|
799
|
-
throw
|
|
819
|
+
throw internalTransportError(
|
|
800
820
|
`Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
|
|
801
821
|
);
|
|
802
822
|
}
|
|
823
|
+
function invalidConfigError(field, reason) {
|
|
824
|
+
return new CapxulError({
|
|
825
|
+
code: "INVALID_INPUT",
|
|
826
|
+
message: `Invalid ${field}: ${reason}`,
|
|
827
|
+
details: { source: "sdk-config", field, reason }
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
function bootstrapContractError(field, message) {
|
|
831
|
+
return new CapxulError({
|
|
832
|
+
code: "INVALID_INPUT",
|
|
833
|
+
message,
|
|
834
|
+
details: {
|
|
835
|
+
source: "backend-bootstrap",
|
|
836
|
+
phase: "publishable-key-bootstrap",
|
|
837
|
+
field,
|
|
838
|
+
reason: message
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
function internalTransportError(reason) {
|
|
843
|
+
return new CapxulError({
|
|
844
|
+
code: "INTERNAL_ERROR",
|
|
845
|
+
message: `Internal error: ${reason}`,
|
|
846
|
+
details: { source: "sdk-transport", reason }
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
async function bootstrapResponseError(response, bootstrapUrl) {
|
|
850
|
+
const envelope = await readBootstrapErrorEnvelope(response);
|
|
851
|
+
const wireCode = readNonEmptyString(envelope?.error?.code);
|
|
852
|
+
const normalized = normalizeBootstrapErrorCode(wireCode);
|
|
853
|
+
const message = readNonEmptyString(envelope?.error?.message) ?? `${bootstrapUrl} failed with HTTP ${response.status}.`;
|
|
854
|
+
const backendDetails = readRecord(envelope?.error?.details);
|
|
855
|
+
return new CapxulError({
|
|
856
|
+
code: normalized.code,
|
|
857
|
+
message,
|
|
858
|
+
details: {
|
|
859
|
+
...backendDetails,
|
|
860
|
+
source: "backend-bootstrap",
|
|
861
|
+
phase: "publishable-key-bootstrap",
|
|
862
|
+
httpStatus: response.status,
|
|
863
|
+
...normalized.wireCode ? { wireCode: normalized.wireCode } : {}
|
|
864
|
+
},
|
|
865
|
+
operationId: readNonEmptyString(envelope?.error?.operationId),
|
|
866
|
+
correlationId: readNonEmptyString(envelope?.error?.correlationId),
|
|
867
|
+
retryable: typeof envelope?.error?.retryable === "boolean" ? envelope.error.retryable : void 0
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
async function readBootstrapErrorEnvelope(response) {
|
|
871
|
+
try {
|
|
872
|
+
const parsed = await response.json();
|
|
873
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
874
|
+
} catch {
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
async function readBootstrapSuccessBody(response) {
|
|
879
|
+
try {
|
|
880
|
+
const parsed = await response.json();
|
|
881
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
882
|
+
} catch {
|
|
883
|
+
throw bootstrapContractError(
|
|
884
|
+
"body",
|
|
885
|
+
"/v1/client/bootstrap returned invalid JSON."
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
function normalizeBootstrapThrownError(error) {
|
|
890
|
+
if (error instanceof CapxulError) return error;
|
|
891
|
+
return new CapxulError({
|
|
892
|
+
code: "NETWORK_ERROR",
|
|
893
|
+
message: "Publishable-key bootstrap network failure.",
|
|
894
|
+
cause: error,
|
|
895
|
+
details: {
|
|
896
|
+
source: "bootstrap-network",
|
|
897
|
+
phase: "publishable-key-bootstrap"
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
function normalizeBootstrapErrorCode(wireCode) {
|
|
902
|
+
if (wireCode === "INTERNAL_SERVER_ERROR") {
|
|
903
|
+
return { code: "INTERNAL_ERROR", wireCode };
|
|
904
|
+
}
|
|
905
|
+
if (wireCode && isCapxulErrorCode(wireCode)) {
|
|
906
|
+
return { code: wireCode };
|
|
907
|
+
}
|
|
908
|
+
return wireCode ? { code: "UNKNOWN", wireCode } : { code: "UNKNOWN" };
|
|
909
|
+
}
|
|
910
|
+
var CAPXUL_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
911
|
+
"NOT_AUTHENTICATED",
|
|
912
|
+
"EMAIL_DELIVERY_FAILED",
|
|
913
|
+
"PROFILE_NOT_FOUND",
|
|
914
|
+
"SMART_ACCOUNT_MISSING",
|
|
915
|
+
"PLAYER_NOT_FOUND",
|
|
916
|
+
"ACCOUNT_NOT_FOUND",
|
|
917
|
+
"PROVIDER_ERROR",
|
|
918
|
+
"INVALID_INPUT",
|
|
919
|
+
"ENV_MISSING",
|
|
920
|
+
"NOT_IMPLEMENTED",
|
|
921
|
+
"VERIFICATION_REQUIRED",
|
|
922
|
+
"INSUFFICIENT_BALANCE",
|
|
923
|
+
"INVALID_RECIPIENT",
|
|
924
|
+
"TRANSACTION_FAILED",
|
|
925
|
+
"RATE_LIMITED",
|
|
926
|
+
"NETWORK_ERROR",
|
|
927
|
+
"UNKNOWN",
|
|
928
|
+
"PERMISSION_DENIED",
|
|
929
|
+
"API_KEY_INVALID",
|
|
930
|
+
"API_KEY_EXPIRED",
|
|
931
|
+
"IDEMPOTENCY_CONFLICT",
|
|
932
|
+
"NOT_FOUND",
|
|
933
|
+
"OPERATION_CANCELED",
|
|
934
|
+
"OPERATION_TIMEOUT",
|
|
935
|
+
"ACTION_REQUIRED",
|
|
936
|
+
"KYC_REQUIRED",
|
|
937
|
+
"POLICY_DENIED",
|
|
938
|
+
"SAFE_NOT_READY",
|
|
939
|
+
"PROVIDER_UNAVAILABLE",
|
|
940
|
+
"PROVIDER_REJECTED",
|
|
941
|
+
"RECONCILIATION_FAILED",
|
|
942
|
+
"INTERNAL_ERROR",
|
|
943
|
+
"QUOTE_EXPIRED",
|
|
944
|
+
"QUOTE_NOT_FOUND"
|
|
945
|
+
]);
|
|
946
|
+
function isCapxulErrorCode(value) {
|
|
947
|
+
return CAPXUL_ERROR_CODES.has(value);
|
|
948
|
+
}
|
|
949
|
+
function readRecord(value) {
|
|
950
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
951
|
+
}
|
|
952
|
+
function readNonEmptyString(value) {
|
|
953
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
954
|
+
}
|
|
803
955
|
|
|
804
956
|
// src/core/auth.ts
|
|
805
957
|
function createAuthClient(config = {}) {
|
|
@@ -854,13 +1006,16 @@ function createAuthClient(config = {}) {
|
|
|
854
1006
|
email: signIn.user.email,
|
|
855
1007
|
token: signIn.token,
|
|
856
1008
|
convexJwt,
|
|
857
|
-
expiresAt: new Date(
|
|
1009
|
+
expiresAt: new Date(
|
|
1010
|
+
Date.now() + 30 * 24 * 60 * 60 * 1e3
|
|
1011
|
+
).toISOString()
|
|
858
1012
|
};
|
|
859
1013
|
sessionStore.set(session);
|
|
860
1014
|
if (config.auth?.createDataClient) {
|
|
861
1015
|
try {
|
|
862
1016
|
dataClient = await config.auth.createDataClient(session);
|
|
863
1017
|
mutableConfig(config).data = dataClient;
|
|
1018
|
+
transport.markAuthenticated({ dataClient });
|
|
864
1019
|
} catch (cause) {
|
|
865
1020
|
return [
|
|
866
1021
|
new CapxulError({
|
|
@@ -872,13 +1027,76 @@ function createAuthClient(config = {}) {
|
|
|
872
1027
|
];
|
|
873
1028
|
}
|
|
874
1029
|
}
|
|
875
|
-
|
|
1030
|
+
if (!dataClient) {
|
|
1031
|
+
return [
|
|
1032
|
+
new CapxulError({
|
|
1033
|
+
code: "NOT_AUTHENTICATED",
|
|
1034
|
+
message: "Auth bootstrap requires an authenticated Convex data client."
|
|
1035
|
+
}),
|
|
1036
|
+
null
|
|
1037
|
+
];
|
|
1038
|
+
}
|
|
1039
|
+
try {
|
|
1040
|
+
const resolution = await dataClient.mutation(
|
|
1041
|
+
api.authBootstrap.resolveAfterOtp,
|
|
1042
|
+
{
|
|
1043
|
+
email: session.email,
|
|
1044
|
+
sessionToken: session.token
|
|
1045
|
+
}
|
|
1046
|
+
);
|
|
1047
|
+
if (resolution.kind === "existing_member") {
|
|
1048
|
+
return [null, { ...resolution, session }];
|
|
1049
|
+
}
|
|
1050
|
+
return [null, { ...resolution, session }];
|
|
1051
|
+
} catch (cause) {
|
|
1052
|
+
return [fromConvexError(cause), null];
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
completeBootstrap: async (input) => {
|
|
1056
|
+
const session = sessionStore.get();
|
|
1057
|
+
const data = dataClient ?? config.data;
|
|
1058
|
+
if (!session || !data) {
|
|
1059
|
+
return [
|
|
1060
|
+
new CapxulError({
|
|
1061
|
+
code: "INVALID_INPUT",
|
|
1062
|
+
message: "completeBootstrap requires the OTP-verified session that issued the bootstrap token."
|
|
1063
|
+
}),
|
|
1064
|
+
null
|
|
1065
|
+
];
|
|
1066
|
+
}
|
|
1067
|
+
if (input.signerProvider.kind !== "local-private-key") {
|
|
1068
|
+
return [
|
|
1069
|
+
new CapxulError({
|
|
1070
|
+
code: "INVALID_INPUT",
|
|
1071
|
+
message: "completeBootstrap currently supports local-private-key signer providers only."
|
|
1072
|
+
}),
|
|
1073
|
+
null
|
|
1074
|
+
];
|
|
1075
|
+
}
|
|
1076
|
+
try {
|
|
1077
|
+
const result = await data.mutation(api.authBootstrap.completeBootstrap, {
|
|
1078
|
+
bootstrapToken: input.bootstrapToken,
|
|
1079
|
+
sessionToken: session.token,
|
|
1080
|
+
username: input.username,
|
|
1081
|
+
displayName: input.displayName,
|
|
1082
|
+
countryCode: input.countryCode,
|
|
1083
|
+
signerProvider: input.signerProvider
|
|
1084
|
+
});
|
|
1085
|
+
return [null, { kind: "authenticated", session, ...result }];
|
|
1086
|
+
} catch (cause) {
|
|
1087
|
+
return [
|
|
1088
|
+
fromConvexError(cause),
|
|
1089
|
+
null
|
|
1090
|
+
];
|
|
1091
|
+
}
|
|
876
1092
|
},
|
|
877
1093
|
getSession: async () => [null, sessionStore.get()],
|
|
878
1094
|
signOut: async () => {
|
|
879
1095
|
sessionStore.clear();
|
|
880
1096
|
dataClient = null;
|
|
881
1097
|
mutableConfig(config).data = void 0;
|
|
1098
|
+
const transport = getTransport();
|
|
1099
|
+
transport?.clearAuth();
|
|
882
1100
|
return [null, void 0];
|
|
883
1101
|
},
|
|
884
1102
|
serviceTokenMint: async () => stub("auth.serviceTokenMint"),
|
|
@@ -932,16 +1150,19 @@ async function postBetterAuth(transport, path, body, code, signal) {
|
|
|
932
1150
|
body: JSON.stringify(body),
|
|
933
1151
|
signal
|
|
934
1152
|
});
|
|
1153
|
+
const text = await response.text();
|
|
935
1154
|
if (!response.ok) {
|
|
1155
|
+
const parsedError = parseBetterAuthError(text);
|
|
936
1156
|
return [
|
|
937
1157
|
new CapxulError({
|
|
938
|
-
code,
|
|
939
|
-
message: `BetterAuth ${path} failed with HTTP ${response.status}
|
|
1158
|
+
code: parsedError.code ?? code,
|
|
1159
|
+
message: parsedError.message ?? `BetterAuth ${path} failed with HTTP ${response.status}.`,
|
|
1160
|
+
details: parsedError.details,
|
|
1161
|
+
retryable: parsedError.retryable
|
|
940
1162
|
}),
|
|
941
1163
|
null
|
|
942
1164
|
];
|
|
943
1165
|
}
|
|
944
|
-
const text = await response.text();
|
|
945
1166
|
return [null, text ? JSON.parse(text) : void 0];
|
|
946
1167
|
} catch (cause) {
|
|
947
1168
|
return [
|
|
@@ -954,6 +1175,36 @@ async function postBetterAuth(transport, path, body, code, signal) {
|
|
|
954
1175
|
];
|
|
955
1176
|
}
|
|
956
1177
|
}
|
|
1178
|
+
function parseBetterAuthError(text) {
|
|
1179
|
+
if (!text.trim()) {
|
|
1180
|
+
return {};
|
|
1181
|
+
}
|
|
1182
|
+
try {
|
|
1183
|
+
const body = JSON.parse(text);
|
|
1184
|
+
if (!body || typeof body !== "object") {
|
|
1185
|
+
return {};
|
|
1186
|
+
}
|
|
1187
|
+
const record = body;
|
|
1188
|
+
const nested = record.error && typeof record.error === "object" ? record.error : record;
|
|
1189
|
+
const code = typeof nested.code === "string" ? nested.code : void 0;
|
|
1190
|
+
const message = typeof nested.message === "string" ? nested.message : void 0;
|
|
1191
|
+
const details = nested.details && typeof nested.details === "object" ? nested.details : void 0;
|
|
1192
|
+
const correlationId = typeof nested.correlationId === "string" ? nested.correlationId : void 0;
|
|
1193
|
+
const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
|
|
1194
|
+
return {
|
|
1195
|
+
code: isCapxulErrorCode2(code) ? code : void 0,
|
|
1196
|
+
message,
|
|
1197
|
+
details,
|
|
1198
|
+
correlationId,
|
|
1199
|
+
retryable
|
|
1200
|
+
};
|
|
1201
|
+
} catch {
|
|
1202
|
+
return {};
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
function isCapxulErrorCode2(code) {
|
|
1206
|
+
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";
|
|
1207
|
+
}
|
|
957
1208
|
async function exchangeConvexToken(transport, config, token, signal) {
|
|
958
1209
|
const path = config.auth?.convexTokenUrl ?? "/convex/token";
|
|
959
1210
|
try {
|
|
@@ -2196,7 +2447,7 @@ function createAuthFlowMachine(client) {
|
|
|
2196
2447
|
}),
|
|
2197
2448
|
verifyOtp: xstate.fromPromise(
|
|
2198
2449
|
async ({ input, signal }) => {
|
|
2199
|
-
const [error,
|
|
2450
|
+
const [error, result] = await client.auth.verifyOtp(
|
|
2200
2451
|
{
|
|
2201
2452
|
email: input.email,
|
|
2202
2453
|
otp: input.code
|
|
@@ -2204,7 +2455,14 @@ function createAuthFlowMachine(client) {
|
|
|
2204
2455
|
{ signal }
|
|
2205
2456
|
);
|
|
2206
2457
|
if (error) throw error;
|
|
2207
|
-
|
|
2458
|
+
if (result.kind === "bootstrap_required") {
|
|
2459
|
+
throw new CapxulError({
|
|
2460
|
+
code: "ACTION_REQUIRED",
|
|
2461
|
+
message: "OTP verified but auth bootstrap is required. Use createAuthBootstrapFlowMachine for product sign-up.",
|
|
2462
|
+
details: { reason: result.reason }
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
return result.session;
|
|
2208
2466
|
}
|
|
2209
2467
|
),
|
|
2210
2468
|
signOut: xstate.fromPromise(async () => {
|
|
@@ -2452,6 +2710,345 @@ function emailDomain(email) {
|
|
|
2452
2710
|
const domain = email.split("@")[1]?.trim().toLowerCase();
|
|
2453
2711
|
return domain || "unknown";
|
|
2454
2712
|
}
|
|
2713
|
+
var initialContext = {
|
|
2714
|
+
email: null,
|
|
2715
|
+
code: null,
|
|
2716
|
+
username: null,
|
|
2717
|
+
signerProvider: null,
|
|
2718
|
+
bootstrapToken: null,
|
|
2719
|
+
bootstrapReason: null,
|
|
2720
|
+
session: null,
|
|
2721
|
+
account: null,
|
|
2722
|
+
safe: null,
|
|
2723
|
+
error: null
|
|
2724
|
+
};
|
|
2725
|
+
function createAuthBootstrapFlowMachine(client) {
|
|
2726
|
+
return xstate.setup({
|
|
2727
|
+
types: {},
|
|
2728
|
+
actors: {
|
|
2729
|
+
sendOtp: xstate.fromPromise(async ({ input, signal }) => {
|
|
2730
|
+
const [error] = await client.auth.sendOtp(
|
|
2731
|
+
{ email: input.email },
|
|
2732
|
+
{ signal }
|
|
2733
|
+
);
|
|
2734
|
+
if (error) throw error;
|
|
2735
|
+
}),
|
|
2736
|
+
verifyOtp: xstate.fromPromise(
|
|
2737
|
+
async ({ input, signal }) => {
|
|
2738
|
+
const [error, result] = await client.auth.verifyOtp(
|
|
2739
|
+
{ email: input.email, otp: input.code },
|
|
2740
|
+
{ signal }
|
|
2741
|
+
);
|
|
2742
|
+
if (error) throw error;
|
|
2743
|
+
return result;
|
|
2744
|
+
}
|
|
2745
|
+
),
|
|
2746
|
+
completeBootstrap: xstate.fromPromise(async ({ input }) => {
|
|
2747
|
+
const [error, result] = await client.auth.completeBootstrap(input);
|
|
2748
|
+
if (error) throw error;
|
|
2749
|
+
return result;
|
|
2750
|
+
}),
|
|
2751
|
+
signOut: xstate.fromPromise(async () => {
|
|
2752
|
+
const [error] = await client.auth.signOut();
|
|
2753
|
+
if (error) throw error;
|
|
2754
|
+
})
|
|
2755
|
+
},
|
|
2756
|
+
actions: {
|
|
2757
|
+
trackOtpRequested: ({ context }) => {
|
|
2758
|
+
if (!context.email) return;
|
|
2759
|
+
track("auth_otp_requested", {
|
|
2760
|
+
email_domain: emailDomain2(context.email)
|
|
2761
|
+
});
|
|
2762
|
+
},
|
|
2763
|
+
trackFailed: ({ event }) => {
|
|
2764
|
+
track("auth_failed", {
|
|
2765
|
+
auth_type: "email_otp",
|
|
2766
|
+
reason: errorFromEvent2(event).code
|
|
2767
|
+
});
|
|
2768
|
+
},
|
|
2769
|
+
trackTimeoutFailed: () => {
|
|
2770
|
+
track("auth_failed", {
|
|
2771
|
+
auth_type: "email_otp",
|
|
2772
|
+
reason: "timeout"
|
|
2773
|
+
});
|
|
2774
|
+
},
|
|
2775
|
+
trackVerified: () => {
|
|
2776
|
+
track("auth_verified", { auth_type: "email_otp" });
|
|
2777
|
+
},
|
|
2778
|
+
trackBootstrapRequired: ({ context }) => {
|
|
2779
|
+
track("auth_verified", {
|
|
2780
|
+
auth_type: "email_otp",
|
|
2781
|
+
auth_mode: context.bootstrapReason ?? "bootstrap_required"
|
|
2782
|
+
});
|
|
2783
|
+
},
|
|
2784
|
+
identifyAndTrack: ({ context }) => {
|
|
2785
|
+
if (!context.session) return;
|
|
2786
|
+
identify(context.session.authUserId, {
|
|
2787
|
+
email_domain: emailDomain2(context.session.email)
|
|
2788
|
+
});
|
|
2789
|
+
track("auth_identified", {
|
|
2790
|
+
email_domain: emailDomain2(context.session.email)
|
|
2791
|
+
});
|
|
2792
|
+
},
|
|
2793
|
+
trackSignedOut: () => {
|
|
2794
|
+
track("auth_signed_out");
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
}).createMachine({
|
|
2798
|
+
id: "authBootstrap",
|
|
2799
|
+
initial: "email",
|
|
2800
|
+
context: initialContext,
|
|
2801
|
+
states: {
|
|
2802
|
+
email: {
|
|
2803
|
+
on: {
|
|
2804
|
+
ENTER_EMAIL: {
|
|
2805
|
+
actions: xstate.assign({
|
|
2806
|
+
email: ({ event }) => event.email,
|
|
2807
|
+
error: () => null
|
|
2808
|
+
})
|
|
2809
|
+
},
|
|
2810
|
+
REQUEST_OTP: { target: "sending_otp" }
|
|
2811
|
+
}
|
|
2812
|
+
},
|
|
2813
|
+
sending_otp: {
|
|
2814
|
+
invoke: {
|
|
2815
|
+
src: "sendOtp",
|
|
2816
|
+
input: ({ context }) => ({ email: requireEmail2(context) }),
|
|
2817
|
+
onDone: { target: "otp_requested", actions: "trackOtpRequested" },
|
|
2818
|
+
onError: {
|
|
2819
|
+
target: "otp_requested",
|
|
2820
|
+
actions: [
|
|
2821
|
+
xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
|
|
2822
|
+
"trackFailed"
|
|
2823
|
+
]
|
|
2824
|
+
}
|
|
2825
|
+
},
|
|
2826
|
+
after: {
|
|
2827
|
+
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
2828
|
+
target: "otp_requested",
|
|
2829
|
+
actions: [
|
|
2830
|
+
xstate.assign({ error: () => timeoutError2("sending_otp") }),
|
|
2831
|
+
"trackTimeoutFailed"
|
|
2832
|
+
]
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
},
|
|
2836
|
+
otp_requested: {
|
|
2837
|
+
on: {
|
|
2838
|
+
ENTER_OTP: {
|
|
2839
|
+
actions: xstate.assign({
|
|
2840
|
+
code: ({ event }) => event.code,
|
|
2841
|
+
error: () => null
|
|
2842
|
+
})
|
|
2843
|
+
},
|
|
2844
|
+
VERIFY_OTP: { target: "verifying_otp" },
|
|
2845
|
+
BACK: { target: "email" },
|
|
2846
|
+
RESET: { target: "email", actions: xstate.assign(() => initialContext) }
|
|
2847
|
+
}
|
|
2848
|
+
},
|
|
2849
|
+
verifying_otp: {
|
|
2850
|
+
invoke: {
|
|
2851
|
+
src: "verifyOtp",
|
|
2852
|
+
input: ({ context }) => ({
|
|
2853
|
+
email: requireEmail2(context),
|
|
2854
|
+
code: requireCode(context)
|
|
2855
|
+
}),
|
|
2856
|
+
onDone: [
|
|
2857
|
+
{
|
|
2858
|
+
guard: ({ event }) => event.output.kind === "existing_member",
|
|
2859
|
+
target: "authenticated",
|
|
2860
|
+
actions: [
|
|
2861
|
+
xstate.assign({
|
|
2862
|
+
session: ({ event }) => event.output.session,
|
|
2863
|
+
account: ({ event }) => event.output.kind === "existing_member" ? event.output.account : null,
|
|
2864
|
+
username: ({ event }) => event.output.kind === "existing_member" ? event.output.username : null,
|
|
2865
|
+
safe: ({ event }) => event.output.kind === "existing_member" ? event.output.safe : null,
|
|
2866
|
+
email: () => null,
|
|
2867
|
+
error: () => null
|
|
2868
|
+
}),
|
|
2869
|
+
"trackVerified",
|
|
2870
|
+
"identifyAndTrack"
|
|
2871
|
+
]
|
|
2872
|
+
},
|
|
2873
|
+
{
|
|
2874
|
+
target: "bootstrap_required",
|
|
2875
|
+
actions: [
|
|
2876
|
+
xstate.assign({
|
|
2877
|
+
session: ({ event }) => event.output.session,
|
|
2878
|
+
bootstrapToken: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.bootstrapToken : null,
|
|
2879
|
+
bootstrapReason: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.reason : null,
|
|
2880
|
+
username: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.username ?? null : null,
|
|
2881
|
+
email: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.email : null,
|
|
2882
|
+
error: () => null
|
|
2883
|
+
}),
|
|
2884
|
+
"trackVerified",
|
|
2885
|
+
"trackBootstrapRequired"
|
|
2886
|
+
]
|
|
2887
|
+
}
|
|
2888
|
+
],
|
|
2889
|
+
onError: {
|
|
2890
|
+
target: "otp_requested",
|
|
2891
|
+
actions: [
|
|
2892
|
+
xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
|
|
2893
|
+
"trackFailed"
|
|
2894
|
+
]
|
|
2895
|
+
}
|
|
2896
|
+
},
|
|
2897
|
+
after: {
|
|
2898
|
+
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
2899
|
+
target: "otp_requested",
|
|
2900
|
+
actions: [
|
|
2901
|
+
xstate.assign({ error: () => timeoutError2("verifying_otp") }),
|
|
2902
|
+
"trackTimeoutFailed"
|
|
2903
|
+
]
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
},
|
|
2907
|
+
bootstrap_required: {
|
|
2908
|
+
on: {
|
|
2909
|
+
ENTER_USERNAME: {
|
|
2910
|
+
actions: xstate.assign({
|
|
2911
|
+
username: ({ event }) => event.username,
|
|
2912
|
+
error: () => null
|
|
2913
|
+
})
|
|
2914
|
+
},
|
|
2915
|
+
ENTER_SIGNER_PROVIDER: {
|
|
2916
|
+
actions: xstate.assign({
|
|
2917
|
+
signerProvider: ({ event }) => event.signerProvider,
|
|
2918
|
+
error: () => null
|
|
2919
|
+
})
|
|
2920
|
+
},
|
|
2921
|
+
COMPLETE_BOOTSTRAP: { target: "completing_bootstrap" },
|
|
2922
|
+
BACK: { target: "otp_requested" },
|
|
2923
|
+
RESET: { target: "email", actions: xstate.assign(() => initialContext) }
|
|
2924
|
+
}
|
|
2925
|
+
},
|
|
2926
|
+
completing_bootstrap: {
|
|
2927
|
+
invoke: {
|
|
2928
|
+
src: "completeBootstrap",
|
|
2929
|
+
input: ({ context }) => ({
|
|
2930
|
+
bootstrapToken: requireBootstrapToken(context),
|
|
2931
|
+
username: requireUsername(context),
|
|
2932
|
+
signerProvider: requireSignerProvider(context)
|
|
2933
|
+
}),
|
|
2934
|
+
onDone: {
|
|
2935
|
+
target: "authenticated",
|
|
2936
|
+
actions: [
|
|
2937
|
+
xstate.assign({
|
|
2938
|
+
session: ({ event }) => event.output.session,
|
|
2939
|
+
account: ({ event }) => event.output.account,
|
|
2940
|
+
username: ({ event }) => event.output.username,
|
|
2941
|
+
safe: ({ event }) => event.output.safe,
|
|
2942
|
+
bootstrapToken: () => null,
|
|
2943
|
+
bootstrapReason: () => null,
|
|
2944
|
+
signerProvider: () => null,
|
|
2945
|
+
email: () => null,
|
|
2946
|
+
error: () => null
|
|
2947
|
+
}),
|
|
2948
|
+
"identifyAndTrack"
|
|
2949
|
+
]
|
|
2950
|
+
},
|
|
2951
|
+
onError: {
|
|
2952
|
+
target: "bootstrap_required",
|
|
2953
|
+
actions: [
|
|
2954
|
+
xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
|
|
2955
|
+
"trackFailed"
|
|
2956
|
+
]
|
|
2957
|
+
}
|
|
2958
|
+
},
|
|
2959
|
+
after: {
|
|
2960
|
+
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
2961
|
+
target: "bootstrap_required",
|
|
2962
|
+
actions: [
|
|
2963
|
+
xstate.assign({ error: () => timeoutError2("completing_bootstrap") }),
|
|
2964
|
+
"trackTimeoutFailed"
|
|
2965
|
+
]
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
},
|
|
2969
|
+
authenticated: {
|
|
2970
|
+
on: {
|
|
2971
|
+
SIGN_OUT: { target: "signing_out" }
|
|
2972
|
+
}
|
|
2973
|
+
},
|
|
2974
|
+
signing_out: {
|
|
2975
|
+
invoke: {
|
|
2976
|
+
src: "signOut",
|
|
2977
|
+
onDone: {
|
|
2978
|
+
target: "email",
|
|
2979
|
+
actions: [
|
|
2980
|
+
xstate.assign(() => initialContext),
|
|
2981
|
+
"trackSignedOut"
|
|
2982
|
+
]
|
|
2983
|
+
},
|
|
2984
|
+
onError: {
|
|
2985
|
+
target: "error",
|
|
2986
|
+
actions: xstate.assign({ error: ({ event }) => errorFromEvent2(event) })
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
},
|
|
2990
|
+
error: {
|
|
2991
|
+
on: {
|
|
2992
|
+
RESET: { target: "email", actions: xstate.assign(() => initialContext) }
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
});
|
|
2997
|
+
}
|
|
2998
|
+
function requireEmail2(context) {
|
|
2999
|
+
if (!context.email) {
|
|
3000
|
+
throw Errors.invalidInput("email", "Auth bootstrap requires an email.");
|
|
3001
|
+
}
|
|
3002
|
+
return context.email;
|
|
3003
|
+
}
|
|
3004
|
+
function requireCode(context) {
|
|
3005
|
+
if (!context.code) {
|
|
3006
|
+
throw Errors.invalidInput("code", "Auth bootstrap requires an OTP code.");
|
|
3007
|
+
}
|
|
3008
|
+
return context.code;
|
|
3009
|
+
}
|
|
3010
|
+
function requireBootstrapToken(context) {
|
|
3011
|
+
if (!context.bootstrapToken) {
|
|
3012
|
+
throw Errors.invalidInput(
|
|
3013
|
+
"bootstrapToken",
|
|
3014
|
+
"Auth bootstrap requires a continuation token."
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
return context.bootstrapToken;
|
|
3018
|
+
}
|
|
3019
|
+
function requireUsername(context) {
|
|
3020
|
+
if (!context.username) {
|
|
3021
|
+
throw Errors.invalidInput("username", "Auth bootstrap requires a username.");
|
|
3022
|
+
}
|
|
3023
|
+
return context.username;
|
|
3024
|
+
}
|
|
3025
|
+
function requireSignerProvider(context) {
|
|
3026
|
+
if (!context.signerProvider) {
|
|
3027
|
+
throw Errors.invalidInput(
|
|
3028
|
+
"signerProvider",
|
|
3029
|
+
"Auth bootstrap requires a signer provider."
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
3032
|
+
return context.signerProvider;
|
|
3033
|
+
}
|
|
3034
|
+
function errorFromEvent2(event) {
|
|
3035
|
+
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
3036
|
+
if (cause instanceof CapxulError || cause instanceof CapxulError2) {
|
|
3037
|
+
return cause;
|
|
3038
|
+
}
|
|
3039
|
+
return Errors.providerError("auth", "bootstrap", cause);
|
|
3040
|
+
}
|
|
3041
|
+
function timeoutError2(state) {
|
|
3042
|
+
return Errors.providerError(
|
|
3043
|
+
"auth",
|
|
3044
|
+
"bootstrap",
|
|
3045
|
+
new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
3048
|
+
function emailDomain2(email) {
|
|
3049
|
+
const domain = email.split("@")[1]?.trim().toLowerCase();
|
|
3050
|
+
return domain || "unknown";
|
|
3051
|
+
}
|
|
2455
3052
|
function createProvisioningMachine(client) {
|
|
2456
3053
|
return xstate.setup({
|
|
2457
3054
|
types: {},
|
|
@@ -2524,13 +3121,13 @@ function createProvisioningMachine(client) {
|
|
|
2524
3121
|
},
|
|
2525
3122
|
onError: {
|
|
2526
3123
|
target: "error",
|
|
2527
|
-
actions: xstate.assign({ error: ({ event }) =>
|
|
3124
|
+
actions: xstate.assign({ error: ({ event }) => errorFromEvent3(event) })
|
|
2528
3125
|
}
|
|
2529
3126
|
},
|
|
2530
3127
|
after: {
|
|
2531
3128
|
[FLOW_INVOKE_TIMEOUT_MS]: {
|
|
2532
3129
|
target: "error",
|
|
2533
|
-
actions: xstate.assign({ error: () =>
|
|
3130
|
+
actions: xstate.assign({ error: () => timeoutError3() })
|
|
2534
3131
|
}
|
|
2535
3132
|
}
|
|
2536
3133
|
},
|
|
@@ -2548,7 +3145,7 @@ function createProvisioningMachine(client) {
|
|
|
2548
3145
|
* this payload on its `onDone` transition and branches via guards
|
|
2549
3146
|
* on `event.output.error`.
|
|
2550
3147
|
*/
|
|
2551
|
-
output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error:
|
|
3148
|
+
output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError3() }
|
|
2552
3149
|
});
|
|
2553
3150
|
}
|
|
2554
3151
|
function requireProvisionInput(context) {
|
|
@@ -2560,13 +3157,13 @@ function requireProvisionInput(context) {
|
|
|
2560
3157
|
}
|
|
2561
3158
|
return context.input;
|
|
2562
3159
|
}
|
|
2563
|
-
function
|
|
3160
|
+
function errorFromEvent3(event) {
|
|
2564
3161
|
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
2565
3162
|
if (cause instanceof CapxulError) return cause;
|
|
2566
3163
|
if (cause instanceof CapxulError2) return cause;
|
|
2567
3164
|
return Errors.providerError("provisioning", "flow", cause);
|
|
2568
3165
|
}
|
|
2569
|
-
function
|
|
3166
|
+
function timeoutError3() {
|
|
2570
3167
|
return Errors.providerError(
|
|
2571
3168
|
"provisioning",
|
|
2572
3169
|
"flow",
|
|
@@ -2659,7 +3256,7 @@ function createOnboardingFlowMachine(client) {
|
|
|
2659
3256
|
error: ({ event }) => extractChildErrorOrFallback(event)
|
|
2660
3257
|
}),
|
|
2661
3258
|
assignChildThrown: xstate.assign({
|
|
2662
|
-
error: ({ event }) =>
|
|
3259
|
+
error: ({ event }) => errorFromEvent4(event)
|
|
2663
3260
|
}),
|
|
2664
3261
|
assignAccountFromChild: xstate.assign({
|
|
2665
3262
|
account: ({ event }) => extractChildAccountOrNull(event)
|
|
@@ -2821,7 +3418,7 @@ function extractChildAccountOrNull(event) {
|
|
|
2821
3418
|
if (output && "account" in output && output.account) return output.account;
|
|
2822
3419
|
return null;
|
|
2823
3420
|
}
|
|
2824
|
-
function
|
|
3421
|
+
function errorFromEvent4(event) {
|
|
2825
3422
|
const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
|
|
2826
3423
|
if (cause instanceof CapxulError) return cause;
|
|
2827
3424
|
if (cause instanceof CapxulError2) return cause;
|
|
@@ -2853,6 +3450,7 @@ function createCapxulClient(config = {}) {
|
|
|
2853
3450
|
const client = clientWithoutFlows;
|
|
2854
3451
|
client.flows = {
|
|
2855
3452
|
auth: () => createAuthFlowMachine(client),
|
|
3453
|
+
authBootstrap: () => createAuthBootstrapFlowMachine(client),
|
|
2856
3454
|
onboarding: () => createOnboardingFlowMachine(client),
|
|
2857
3455
|
provisioning: () => createProvisioningMachine(client)
|
|
2858
3456
|
};
|