@capxul/sdk 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/{InMemoryAuthCacheAdapter-Rc8tCtml.mjs → InMemoryAuthCacheAdapter-qMpBOGb3.mjs} +15 -2
- package/dist/{create-capxul-client-CPhTPz_9.mjs → create-capxul-client-mArFr_Os.mjs} +675 -532
- package/dist/index.d.mts +25 -64
- package/dist/index.mjs +260 -103
- package/dist/node/index.d.mts +1 -1
- package/dist/node/index.mjs +1 -1
- package/dist/{create-capxul-client-BI-6Za6X.d.mts → observation-Ci8gIQjm.d.mts} +193 -20
- package/dist/{signer-Bj4F-RwT.d.mts → signer-BejoR3bA.d.mts} +116 -1
- package/dist/testing/index.d.mts +3 -1
- package/dist/testing/index.mjs +4 -3
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import {
|
|
1
|
+
import { A as fingerprintPaymentIntent, B as formatTraceparent, C as ClockPortTag, D as AuthClientError, E as authClientPortFromPromiseAdapter, F as sanitizeObservationContext, G as signerFailure, H as readInvocationObservation, I as CAPXUL_FUNCTIONS, J as BASE_SEPOLIA_CHAIN_ID, K as CAPXUL_PAYMENTS_V2_ADDRESS, L as BootstrapEnvelope, M as fromWei, N as OBSERVATION_CONTEXT_HEADER, O as AuthClientPortTag, P as encodeObservationContextHeader, Q as isRestoring, R as EngineeringTelemetryBootstrapPolicy, S as ClockError, T as bootstrapErrorFromCapxul, U as causeChain, V as copyInvocationObservation, W as injectedWalletSigner, X as destination, Z as isClaimed, _ as wireChainId, a as observeFailedResult, b as ConvexCallPortTag, c as captureExceptionSync, d as TelemetryPortTag, g as accountReadErrorFromCapxul, h as AccountReadPortTag, i as observationContextProps, j as toWei, k as version, l as detectAuthCacheAdapter, m as smartAccountErrorFromCapxul, n as postHogProductTelemetry, o as postHogFailureObservation, p as SmartAccountPortTag, q as normalizeBindingEmail, r as CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, s as captureException, t as assembleCapxulClient, u as PostHogTelemetryLayer, v as IdentityPortTag, w as BootstrapPortTag, x as convexCallErrorFromCapxul, y as identityErrorFromCapxul, z as isSettingUpLifecycle } from "./create-capxul-client-mArFr_Os.mjs";
|
|
2
|
+
import { C as toDurationMs, D as toJwtToken, E as toEpochSeconds, F as CAPXUL_ERROR_CODES, I as CapxulError, M as toRoleKey, N as toSessionToken, O as toKycTier, P as decodeConvexError, R as Errors, S as toCurrencyCode, T as toEpochMs, b as toChainId, g as toAllowedOrigin, h as toAddress, j as toPublishableKey, k as toOrgId, m as toAccountId, o as AuthCachePortTag, p as currencySymbolFor, v as toAuthUserId, w as toEmail, x as toCountryCode, z as isCapxulError } from "./InMemoryAuthCacheAdapter-qMpBOGb3.mjs";
|
|
3
3
|
import { keccak256, recoverAddress, stringToHex } from "viem";
|
|
4
4
|
import { Cause, Context, Data, Effect, Exit, Layer, Result, SchemaIssue, SchemaParser, Scope, Tracer } from "effect";
|
|
5
5
|
import { getFunctionName, makeFunctionReference } from "convex/server";
|
|
@@ -8,6 +8,89 @@ import { FetchHttpClient, Headers, HttpClient } from "effect/unstable/http";
|
|
|
8
8
|
import { OtlpExporter, OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
|
|
9
9
|
import { ConvexClient } from "convex/browser";
|
|
10
10
|
import { AccountTypeEnum, ChainTypeEnum, EmbeddedState, Openfort, RecoveryMethod, ThirdPartyOAuthProvider } from "@openfort/openfort-js";
|
|
11
|
+
//#region src/domain/money/format-money.ts
|
|
12
|
+
function formatMoney(money, options = {}) {
|
|
13
|
+
if (!Number.isInteger(money.decimals) || money.decimals < 0) throw Errors.invalidInput("money.decimals", "must be a non-negative integer");
|
|
14
|
+
const symbol = currencySymbolFor(money.currency);
|
|
15
|
+
const maximumFractionDigits = options.grammar === "code" ? 4 : 2;
|
|
16
|
+
const rounded = roundDecimal(money.value, money.decimals, maximumFractionDigits);
|
|
17
|
+
const grouped = rounded.integer.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
18
|
+
const signed = `${rounded.negative ? "-" : ""}${grouped}`;
|
|
19
|
+
if (options.grammar === "code") {
|
|
20
|
+
if (!rounded.hadFraction) return `${signed} ${String(money.currency)}`;
|
|
21
|
+
let displayedFraction = rounded.fraction;
|
|
22
|
+
while (displayedFraction.length > 2 && displayedFraction.endsWith("0")) displayedFraction = displayedFraction.slice(0, -1);
|
|
23
|
+
return `${signed}.${displayedFraction} ${String(money.currency)}`;
|
|
24
|
+
}
|
|
25
|
+
return `${symbol}${signed}.${rounded.fraction}`;
|
|
26
|
+
}
|
|
27
|
+
function roundDecimal(value, decimals, maximumFractionDigits) {
|
|
28
|
+
const match = /^(-?)(\d+)(?:\.(\d+))?$/.exec(value);
|
|
29
|
+
if (match === null) throw Errors.invalidInput("money.value", "must be a base-10 decimal string");
|
|
30
|
+
const integer = match[2] ?? "0";
|
|
31
|
+
const sourceFraction = match[3] ?? "";
|
|
32
|
+
if (sourceFraction.length > decimals) throw Errors.invalidInput("money.value", "fraction exceeds money.decimals");
|
|
33
|
+
const scale = 10n ** BigInt(maximumFractionDigits);
|
|
34
|
+
const keptFraction = sourceFraction.slice(0, maximumFractionDigits).padEnd(maximumFractionDigits, "0");
|
|
35
|
+
let scaled = BigInt(integer) * scale + BigInt(keptFraction || "0");
|
|
36
|
+
if ((sourceFraction[maximumFractionDigits] ?? "0") >= "5") scaled += 1n;
|
|
37
|
+
return {
|
|
38
|
+
negative: match[1] === "-",
|
|
39
|
+
integer: (scaled / scale).toString(),
|
|
40
|
+
fraction: (scaled % scale).toString().padStart(maximumFractionDigits, "0"),
|
|
41
|
+
hadFraction: /[1-9]/.test(sourceFraction)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/domain/money/parse-money.ts
|
|
46
|
+
/**
|
|
47
|
+
* ADR-0023 R1: the reason is a closed code and the ONLY failure vocabulary —
|
|
48
|
+
* the consuming app maps each code to its own sentence. The SDK ships no
|
|
49
|
+
* English for user-text failure.
|
|
50
|
+
*/
|
|
51
|
+
function isMoneyParseError(value) {
|
|
52
|
+
return "kind" in value;
|
|
53
|
+
}
|
|
54
|
+
const AMOUNT_PATTERN = /^(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?$/;
|
|
55
|
+
const ZERO_PATTERN = /^0+(?:\.0+)?$/;
|
|
56
|
+
function parseMoney(input, asset) {
|
|
57
|
+
if (!Number.isInteger(asset.decimals) || asset.decimals < 0) throw Errors.invalidInput("asset.decimals", "must be a non-negative integer");
|
|
58
|
+
const value = input.trim();
|
|
59
|
+
if (value.length === 0) return {
|
|
60
|
+
kind: "error",
|
|
61
|
+
reason: "required"
|
|
62
|
+
};
|
|
63
|
+
if (value.length > 24) return {
|
|
64
|
+
kind: "error",
|
|
65
|
+
reason: "too-long"
|
|
66
|
+
};
|
|
67
|
+
const negative = value.startsWith("-");
|
|
68
|
+
const unsigned = negative ? value.slice(1) : value;
|
|
69
|
+
if (!AMOUNT_PATTERN.test(unsigned)) return {
|
|
70
|
+
kind: "error",
|
|
71
|
+
reason: "invalid-format"
|
|
72
|
+
};
|
|
73
|
+
if (negative) return {
|
|
74
|
+
kind: "error",
|
|
75
|
+
reason: "non-positive"
|
|
76
|
+
};
|
|
77
|
+
const normalized = unsigned.replaceAll(",", "");
|
|
78
|
+
const fraction = normalized.split(".")[1];
|
|
79
|
+
if (fraction !== void 0 && fraction.length > asset.decimals) return {
|
|
80
|
+
kind: "error",
|
|
81
|
+
reason: "too-many-decimals"
|
|
82
|
+
};
|
|
83
|
+
if (ZERO_PATTERN.test(normalized)) return {
|
|
84
|
+
kind: "error",
|
|
85
|
+
reason: "non-positive"
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
currency: asset.currency,
|
|
89
|
+
value: normalized,
|
|
90
|
+
decimals: asset.decimals
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
11
94
|
//#region src/surface/account-providers.ts
|
|
12
95
|
function localPrivateKeyAccountProvider(input) {
|
|
13
96
|
const account = privateKeyToAccount(input.privateKey);
|
|
@@ -53,7 +136,7 @@ function eip1193AccountProvider(input) {
|
|
|
53
136
|
} catch (err) {
|
|
54
137
|
return {
|
|
55
138
|
ok: false,
|
|
56
|
-
error:
|
|
139
|
+
error: signerFailure("injected-eip1193", "eth_accounts", err)
|
|
57
140
|
};
|
|
58
141
|
} finally {
|
|
59
142
|
inFlight = null;
|
|
@@ -78,60 +161,6 @@ function firstAccount(value) {
|
|
|
78
161
|
return typeof first === "string" ? first : null;
|
|
79
162
|
}
|
|
80
163
|
//#endregion
|
|
81
|
-
//#region src/signer.ts
|
|
82
|
-
const EVM_ADDRESS_HEX$1 = /^0x[0-9a-fA-F]{40}$/;
|
|
83
|
-
const ECDSA_SIGNATURE_HEX$1 = /^0x[0-9a-fA-F]{130}$/;
|
|
84
|
-
const SAFE_OP_DIGEST_HEX$1 = /^0x[0-9a-fA-F]{64}$/;
|
|
85
|
-
/**
|
|
86
|
-
* Browser `CapxulSigner` backed by an injected EIP-1193 wallet (MetaMask, etc.).
|
|
87
|
-
* Signs the SafeOp digest via `eth_sign`, then verifies the returned signature
|
|
88
|
-
* recovers the selected account against that raw digest. Wallets that prefix
|
|
89
|
-
* `eth_sign` payloads are rejected before the backend submits an invalid SafeOp.
|
|
90
|
-
* The node key signer lives in `@capxul/sdk/node` (`localPrivateKeySigner`).
|
|
91
|
-
*/
|
|
92
|
-
function injectedWalletSigner(provider) {
|
|
93
|
-
const resolveAddress = async () => {
|
|
94
|
-
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
95
|
-
const first = Array.isArray(accounts) ? accounts[0] : void 0;
|
|
96
|
-
if (typeof first !== "string") throw new Error("injectedWalletSigner: wallet returned no accounts");
|
|
97
|
-
if (!EVM_ADDRESS_HEX$1.test(first)) throw new Error("injectedWalletSigner: wallet returned invalid address format");
|
|
98
|
-
return toAddress(first);
|
|
99
|
-
};
|
|
100
|
-
return {
|
|
101
|
-
source: "injected-eip1193",
|
|
102
|
-
getAddress: resolveAddress,
|
|
103
|
-
async signUserOpHash(hash) {
|
|
104
|
-
if (!SAFE_OP_DIGEST_HEX$1.test(hash)) throw new Error("injectedWalletSigner: SafeOp digest must be a 0x-prefixed 32-byte hex");
|
|
105
|
-
const address = await resolveAddress();
|
|
106
|
-
let signature;
|
|
107
|
-
try {
|
|
108
|
-
signature = await provider.request({
|
|
109
|
-
method: "eth_sign",
|
|
110
|
-
params: [address, hash]
|
|
111
|
-
});
|
|
112
|
-
} catch (cause) {
|
|
113
|
-
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
114
|
-
throw new Error(`injectedWalletSigner: eth_sign failed; enable raw-hash signing for deployment (${detail})`, { cause });
|
|
115
|
-
}
|
|
116
|
-
if (typeof signature !== "string") throw new Error("injectedWalletSigner: wallet returned a non-string signature");
|
|
117
|
-
if (!ECDSA_SIGNATURE_HEX$1.test(signature)) throw new Error("injectedWalletSigner: wallet returned invalid signature format");
|
|
118
|
-
if ((await recoverRawDigestSigner$1({
|
|
119
|
-
hash,
|
|
120
|
-
signature
|
|
121
|
-
})).toLowerCase() !== address.toLowerCase()) throw new Error("injectedWalletSigner: wallet signature did not recover the selected account for the raw SafeOp digest; use a raw-hash-capable wallet or @capxul/sdk/node localPrivateKeySigner for deployed flows");
|
|
122
|
-
return signature;
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
async function recoverRawDigestSigner$1(input) {
|
|
127
|
-
try {
|
|
128
|
-
return toAddress(await recoverAddress(input));
|
|
129
|
-
} catch (cause) {
|
|
130
|
-
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
131
|
-
throw new Error(`injectedWalletSigner: could not verify raw SafeOp digest signature (${detail})`, { cause });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
//#endregion
|
|
135
164
|
//#region src/dev-signer.ts
|
|
136
165
|
const SESSION_KEY = "capxul.session";
|
|
137
166
|
/**
|
|
@@ -1399,9 +1428,27 @@ function mapToCapxulError(operation, err) {
|
|
|
1399
1428
|
function mapToConvexCallError(operation, err) {
|
|
1400
1429
|
return convexCallErrorFromCapxul(operation, mapToCapxulError(operation, err));
|
|
1401
1430
|
}
|
|
1431
|
+
const TRANSPORT_ERROR_CODES = new Set([
|
|
1432
|
+
"EAI_AGAIN",
|
|
1433
|
+
"ECONNREFUSED",
|
|
1434
|
+
"ECONNRESET",
|
|
1435
|
+
"ENOTFOUND",
|
|
1436
|
+
"EPIPE",
|
|
1437
|
+
"ETIMEDOUT",
|
|
1438
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
1439
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
1440
|
+
"UND_ERR_SOCKET"
|
|
1441
|
+
]);
|
|
1402
1442
|
function isTransportError(err) {
|
|
1403
|
-
const
|
|
1404
|
-
|
|
1443
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1444
|
+
let current = err;
|
|
1445
|
+
while (current !== void 0 && !seen.has(current)) {
|
|
1446
|
+
seen.add(current);
|
|
1447
|
+
const code = current.code;
|
|
1448
|
+
if (current.name === "FetchError" || current.name === "NetworkError" || typeof code === "string" && TRANSPORT_ERROR_CODES.has(code)) return true;
|
|
1449
|
+
current = current.cause instanceof Error ? current.cause : void 0;
|
|
1450
|
+
}
|
|
1451
|
+
return false;
|
|
1405
1452
|
}
|
|
1406
1453
|
//#endregion
|
|
1407
1454
|
//#region src/adapters/identity/ConvexIdentityAdapter.ts
|
|
@@ -1678,6 +1725,7 @@ function parseRoleDefinition(json) {
|
|
|
1678
1725
|
...raw.spend.perDay === void 0 ? {} : { perDay: parseRoleMoney(raw.spend.perDay, "perDay") },
|
|
1679
1726
|
...raw.spend.toRecipients === void 0 ? {} : { toRecipients: parseRoleRecipients(raw.spend.toRecipients) }
|
|
1680
1727
|
} },
|
|
1728
|
+
...typeof raw.canSpend === "boolean" ? { canSpend: raw.canSpend } : {},
|
|
1681
1729
|
...typeof raw.canManageMembers === "boolean" ? { canManageMembers: raw.canManageMembers } : {},
|
|
1682
1730
|
...typeof raw.canManageRoles === "boolean" ? { canManageRoles: raw.canManageRoles } : {}
|
|
1683
1731
|
};
|
|
@@ -1848,7 +1896,7 @@ var ConvexOrganizationSetupAdapter = class {
|
|
|
1848
1896
|
async authorizeAndSubmitBootstrap(input) {
|
|
1849
1897
|
const cancelled = cancellation(input.signal);
|
|
1850
1898
|
if (cancelled !== void 0) return cancelled;
|
|
1851
|
-
const signerAddress = await signerResult("getAddress", () => this.#signer.getAddress());
|
|
1899
|
+
const signerAddress = await signerResult(this.#signer.source, "getAddress", () => this.#signer.getAddress());
|
|
1852
1900
|
if (!signerAddress.ok) return signerAddress;
|
|
1853
1901
|
const prepared = await runCall("prepareBootstrap", this.#convex.action(this.#fns.prepareBootstrap, copyInvocationObservation(input, {
|
|
1854
1902
|
orgId: input.orgId,
|
|
@@ -1860,7 +1908,7 @@ var ConvexOrganizationSetupAdapter = class {
|
|
|
1860
1908
|
if (!authority.ok) return authority;
|
|
1861
1909
|
const cancelledAfterPrepare = cancellation(input.signal);
|
|
1862
1910
|
if (cancelledAfterPrepare !== void 0) return cancelledAfterPrepare;
|
|
1863
|
-
const signature = await signerResult("signUserOpHash", () => this.#signer.signUserOpHash(prepared.value.digest));
|
|
1911
|
+
const signature = await signerResult(this.#signer.source, "signUserOpHash", () => this.#signer.signUserOpHash(prepared.value.digest));
|
|
1864
1912
|
if (!signature.ok) return signature;
|
|
1865
1913
|
const cancelledAfterSign = cancellation(input.signal);
|
|
1866
1914
|
if (cancelledAfterSign !== void 0) return cancelledAfterSign;
|
|
@@ -1949,7 +1997,7 @@ function resetSignerSession(signer) {
|
|
|
1949
1997
|
value: void 0
|
|
1950
1998
|
};
|
|
1951
1999
|
} catch (cause) {
|
|
1952
|
-
return fail(
|
|
2000
|
+
return fail(signerFailure(signer.source, "resetSession", cause));
|
|
1953
2001
|
}
|
|
1954
2002
|
}
|
|
1955
2003
|
async function runCall(operation, effect) {
|
|
@@ -1971,14 +2019,14 @@ function publicError(operation, cause) {
|
|
|
1971
2019
|
}
|
|
1972
2020
|
return Errors.providerError("convex-organization", operation, cause);
|
|
1973
2021
|
}
|
|
1974
|
-
async function signerResult(operation, run) {
|
|
2022
|
+
async function signerResult(source, operation, run) {
|
|
1975
2023
|
try {
|
|
1976
2024
|
return {
|
|
1977
2025
|
ok: true,
|
|
1978
2026
|
value: await run()
|
|
1979
2027
|
};
|
|
1980
2028
|
} catch (cause) {
|
|
1981
|
-
return fail(
|
|
2029
|
+
return fail(signerFailure(source, operation, cause));
|
|
1982
2030
|
}
|
|
1983
2031
|
}
|
|
1984
2032
|
function validatePreparedAuthorities(prepared, signerAddress) {
|
|
@@ -2076,6 +2124,10 @@ var ConsoleDiagnosticAdapter = class {
|
|
|
2076
2124
|
function openfortProviderError(operation, cause) {
|
|
2077
2125
|
return cause instanceof CapxulError ? cause : Errors.providerError("openfort", operation, cause, { failure_mode: "unknown" });
|
|
2078
2126
|
}
|
|
2127
|
+
/** Build a named PROVIDER_ERROR. The assembled Core SDK boundary reports it. */
|
|
2128
|
+
function failOpenfort(operation, failure_mode, cause) {
|
|
2129
|
+
return Errors.providerError("openfort", operation, cause, { failure_mode });
|
|
2130
|
+
}
|
|
2079
2131
|
/**
|
|
2080
2132
|
* True when the browser cannot perform Web Crypto — sandboxed iframes, headless
|
|
2081
2133
|
* agent browsers, or non-HTTPS origins. OpenFort's embedded-wallet `configure`
|
|
@@ -2112,30 +2164,77 @@ function clearStaleOpenfortBrowserStorage(publishableKey) {
|
|
|
2112
2164
|
if (scope === void 0) return;
|
|
2113
2165
|
for (const key of OPENFORT_BROWSER_STORAGE_KEYS) localStorage.removeItem(`${scope}.${key}`);
|
|
2114
2166
|
}
|
|
2167
|
+
/**
|
|
2168
|
+
* The Openfort error code that names the stale-user class (#1435).
|
|
2169
|
+
* `getThirdPartyAuthToken` skips `authenticateThirdParty` while a `userId` sits
|
|
2170
|
+
* in scoped storage. A purged `userId` therefore pins every later call to a 401
|
|
2171
|
+
* that `extractApiError` reports as `USER_NOT_FOUND`.
|
|
2172
|
+
*
|
|
2173
|
+
* The set holds one member on purpose. Session-expiry codes (`SESSION_EXPIRED`,
|
|
2174
|
+
* `NOT_LOGGED_IN`, `INVALID_TOKEN`, `REFRESH_TOKEN_ERROR`) are a different
|
|
2175
|
+
* cause, and a bare 401 is a different cause again: `app-env-allowlist` is a
|
|
2176
|
+
* 401 by definition (`packages/errors/src/errors.ts:80-81`). Healing those and
|
|
2177
|
+
* tagging them `stale-openfort-cache` would delete the triage signal the tag
|
|
2178
|
+
* exists to carry.
|
|
2179
|
+
*/
|
|
2180
|
+
const STALE_USER_ERROR_CODES = new Set(["USER_NOT_FOUND"]);
|
|
2181
|
+
/**
|
|
2182
|
+
* The one predicate that opens the heal. It reads the Openfort error code and
|
|
2183
|
+
* never the message text (ADR-0023 R4). It walks the cause chain the way
|
|
2184
|
+
* `isTransportError` walks it in the Convex transport adapter.
|
|
2185
|
+
*
|
|
2186
|
+
* Known ceiling (#1435): a 401 that carries no recognized code is NOT healed.
|
|
2187
|
+
* `extractApiError` keeps the status only on `AuthenticationError`, so such a
|
|
2188
|
+
* payload is reachable. Add the code a live payload shows; do not add a message
|
|
2189
|
+
* match, because that trades one bug class for an ADR-0023 R4 violation.
|
|
2190
|
+
*/
|
|
2191
|
+
function isStaleUserSignal(cause) {
|
|
2192
|
+
for (const link of causeChain(cause)) {
|
|
2193
|
+
const error = link;
|
|
2194
|
+
if (typeof error.error === "string" && STALE_USER_ERROR_CODES.has(error.error) || typeof error.code === "string" && STALE_USER_ERROR_CODES.has(error.code)) return true;
|
|
2195
|
+
}
|
|
2196
|
+
return false;
|
|
2197
|
+
}
|
|
2115
2198
|
function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
|
|
2116
2199
|
const diagnostic = options.diagnostic;
|
|
2117
|
-
const telemetry = options.telemetry;
|
|
2118
2200
|
const authBaseUrl = normalizeBetterAuthBaseUrl(bootstrap.authBaseUrl);
|
|
2201
|
+
let currentStatus = "unknown";
|
|
2202
|
+
const statusListeners = /* @__PURE__ */ new Set();
|
|
2203
|
+
function setStatus(next) {
|
|
2204
|
+
if (currentStatus === next) return;
|
|
2205
|
+
currentStatus = next;
|
|
2206
|
+
for (const listener of statusListeners) try {
|
|
2207
|
+
listener(next);
|
|
2208
|
+
} catch {
|
|
2209
|
+
statusListeners.delete(listener);
|
|
2210
|
+
diagnostic?.trace("openfort.signerStatusListener", {
|
|
2211
|
+
ok: false,
|
|
2212
|
+
failure_mode: "unknown"
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
const statusStore = {
|
|
2217
|
+
status: () => currentStatus,
|
|
2218
|
+
subscribe: (listener) => {
|
|
2219
|
+
statusListeners.add(listener);
|
|
2220
|
+
return () => {
|
|
2221
|
+
statusListeners.delete(listener);
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
};
|
|
2119
2225
|
/**
|
|
2120
2226
|
* Closes the black hole: when the browser has no Web Crypto, OpenFort's
|
|
2121
2227
|
* `configure` would resolve to no address and the failure would be reported
|
|
2122
|
-
* as `unknown`. Detect it before any network
|
|
2123
|
-
* `no-secure-context` on a PROVIDER_ERROR scoped to `configure
|
|
2124
|
-
*
|
|
2228
|
+
* as `unknown`. Detect it before any network or wallet work. Tag it
|
|
2229
|
+
* `no-secure-context` on a PROVIDER_ERROR scoped to `configure`. Add a
|
|
2230
|
+
* DiagnosticPort breadcrumb. The assembled Core SDK boundary reports it.
|
|
2125
2231
|
*/
|
|
2126
2232
|
function failNoSecureContext() {
|
|
2127
2233
|
diagnostic?.trace("openfort.configure", {
|
|
2128
2234
|
ok: false,
|
|
2129
2235
|
failure_mode: "no-secure-context"
|
|
2130
2236
|
});
|
|
2131
|
-
|
|
2132
|
-
if (telemetry) captureExceptionSync(telemetry, error, {
|
|
2133
|
-
layer: "openfort",
|
|
2134
|
-
operation: "configure",
|
|
2135
|
-
provider: "openfort",
|
|
2136
|
-
failure_mode: "no-secure-context"
|
|
2137
|
-
});
|
|
2138
|
-
throw error;
|
|
2237
|
+
throw failOpenfort("configure", "no-secure-context", /* @__PURE__ */ new Error("Web Crypto unavailable: browser is not a secure context"));
|
|
2139
2238
|
}
|
|
2140
2239
|
function betterAuthSessionUrl() {
|
|
2141
2240
|
return `${authBaseUrl}/get-session`;
|
|
@@ -2186,6 +2285,39 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
|
|
|
2186
2285
|
getAccessToken: fetchBetterAuthAccessToken
|
|
2187
2286
|
}
|
|
2188
2287
|
});
|
|
2288
|
+
async function configureEmbeddedWallet(encryptionSession) {
|
|
2289
|
+
await openfort.embeddedWallet.configure({
|
|
2290
|
+
accountType: AccountTypeEnum.EOA,
|
|
2291
|
+
chainType: ChainTypeEnum.EVM,
|
|
2292
|
+
recoveryParams: {
|
|
2293
|
+
recoveryMethod: RecoveryMethod.AUTOMATIC,
|
|
2294
|
+
encryptionSession
|
|
2295
|
+
}
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* ONE heal cycle on the stale-user rejection (#1435). Clear the scoped
|
|
2300
|
+
* storage that pins the dead `userId`, run the existing configure path — it
|
|
2301
|
+
* re-runs third-party auth against the live Better Auth session now that no
|
|
2302
|
+
* `userId` is cached — and retry the read once. Exactly one cycle: a second
|
|
2303
|
+
* rejection is terminal and carries `failure_mode: "stale-openfort-cache"`.
|
|
2304
|
+
* The caller stays in `recovering` throughout; only the outcome moves it.
|
|
2305
|
+
*/
|
|
2306
|
+
async function healStaleOpenfortCache(encryptionSession) {
|
|
2307
|
+
try {
|
|
2308
|
+
clearStaleOpenfortBrowserStorage(bootstrap.openfortPublishableKey);
|
|
2309
|
+
diagnostic?.trace("openfort.storageCleared", { beforeConfigure: false });
|
|
2310
|
+
await configureEmbeddedWallet(encryptionSession);
|
|
2311
|
+
await openfort.embeddedWallet.get();
|
|
2312
|
+
} catch (cause) {
|
|
2313
|
+
diagnostic?.trace("openfort.staleCacheHeal", {
|
|
2314
|
+
ok: false,
|
|
2315
|
+
failure_mode: "stale-openfort-cache"
|
|
2316
|
+
});
|
|
2317
|
+
throw failOpenfort("get", "stale-openfort-cache", cause);
|
|
2318
|
+
}
|
|
2319
|
+
diagnostic?.trace("openfort.staleCacheHeal", { ok: true });
|
|
2320
|
+
}
|
|
2189
2321
|
let walletReadyPromise = null;
|
|
2190
2322
|
function startWalletReady() {
|
|
2191
2323
|
return (async () => {
|
|
@@ -2250,14 +2382,7 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
|
|
|
2250
2382
|
clearStaleOpenfortBrowserStorage(bootstrap.openfortPublishableKey);
|
|
2251
2383
|
diagnostic?.trace("openfort.storageCleared", { beforeConfigure: true });
|
|
2252
2384
|
try {
|
|
2253
|
-
await
|
|
2254
|
-
accountType: AccountTypeEnum.EOA,
|
|
2255
|
-
chainType: ChainTypeEnum.EVM,
|
|
2256
|
-
recoveryParams: {
|
|
2257
|
-
recoveryMethod: RecoveryMethod.AUTOMATIC,
|
|
2258
|
-
encryptionSession: encryptionBody.sessionId
|
|
2259
|
-
}
|
|
2260
|
-
});
|
|
2385
|
+
await configureEmbeddedWallet(encryptionBody.sessionId);
|
|
2261
2386
|
diagnostic?.trace("openfort.configure", { ok: true });
|
|
2262
2387
|
} catch (cause) {
|
|
2263
2388
|
diagnostic?.trace("openfort.configure", {
|
|
@@ -2271,20 +2396,36 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
|
|
|
2271
2396
|
await openfort.embeddedWallet.get();
|
|
2272
2397
|
diagnostic?.trace("openfort.get", { ok: true });
|
|
2273
2398
|
} catch (cause) {
|
|
2399
|
+
if (!isStaleUserSignal(cause)) {
|
|
2400
|
+
diagnostic?.trace("openfort.get", {
|
|
2401
|
+
ok: false,
|
|
2402
|
+
failure_mode: "unknown"
|
|
2403
|
+
});
|
|
2404
|
+
throw openfortProviderError("get", cause);
|
|
2405
|
+
}
|
|
2274
2406
|
diagnostic?.trace("openfort.get", {
|
|
2275
2407
|
ok: false,
|
|
2276
|
-
failure_mode: "
|
|
2408
|
+
failure_mode: "stale-openfort-cache"
|
|
2277
2409
|
});
|
|
2278
|
-
|
|
2410
|
+
await healStaleOpenfortCache(encryptionBody.sessionId);
|
|
2279
2411
|
}
|
|
2280
2412
|
})();
|
|
2281
2413
|
}
|
|
2282
2414
|
async function ensureOpenfortWalletReady() {
|
|
2283
|
-
|
|
2415
|
+
let joined = walletReadyPromise;
|
|
2416
|
+
if (joined === null) {
|
|
2417
|
+
joined = startWalletReady();
|
|
2418
|
+
walletReadyPromise = joined;
|
|
2419
|
+
setStatus("recovering");
|
|
2420
|
+
}
|
|
2284
2421
|
try {
|
|
2285
|
-
await
|
|
2422
|
+
await joined;
|
|
2423
|
+
if (walletReadyPromise === joined) setStatus("ready");
|
|
2286
2424
|
} catch (cause) {
|
|
2287
|
-
walletReadyPromise
|
|
2425
|
+
if (walletReadyPromise === joined) {
|
|
2426
|
+
walletReadyPromise = null;
|
|
2427
|
+
setStatus("unavailable");
|
|
2428
|
+
}
|
|
2288
2429
|
throw cause;
|
|
2289
2430
|
}
|
|
2290
2431
|
}
|
|
@@ -2294,6 +2435,7 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
|
|
|
2294
2435
|
});
|
|
2295
2436
|
return {
|
|
2296
2437
|
...signer,
|
|
2438
|
+
statusStore,
|
|
2297
2439
|
getAddress: async () => {
|
|
2298
2440
|
try {
|
|
2299
2441
|
const address = await signer.getAddress();
|
|
@@ -2311,6 +2453,7 @@ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
|
|
|
2311
2453
|
walletReadyPromise = null;
|
|
2312
2454
|
clearStaleOpenfortBrowserStorage(bootstrap.openfortPublishableKey);
|
|
2313
2455
|
signer.resetAddressCache();
|
|
2456
|
+
setStatus("unknown");
|
|
2314
2457
|
}
|
|
2315
2458
|
};
|
|
2316
2459
|
}
|
|
@@ -2674,16 +2817,24 @@ function refreshConvexAuthOnSession(authClient, refresh) {
|
|
|
2674
2817
|
* particular object rather than on the auth seam itself. Here it wraps
|
|
2675
2818
|
* `AuthClientPort.signOut`, so it holds for every route to a sign-out.
|
|
2676
2819
|
*
|
|
2677
|
-
*
|
|
2678
|
-
*
|
|
2679
|
-
* to `tap` would silently strand an Openfort session on the error path.
|
|
2820
|
+
* Capture the sign-out exit before reset. This preserves the old `try/finally`
|
|
2821
|
+
* order and keeps a reset failure in the typed error channel.
|
|
2680
2822
|
*/
|
|
2681
2823
|
function resetSignerSessionOnSignOut(authClient, resetRef) {
|
|
2682
2824
|
return {
|
|
2683
2825
|
...authClient,
|
|
2684
|
-
signOut: (options) =>
|
|
2685
|
-
|
|
2686
|
-
|
|
2826
|
+
signOut: (options) => Effect.gen(function* () {
|
|
2827
|
+
const exit = yield* Effect.exit(authClient.signOut(options));
|
|
2828
|
+
yield* Effect.try({
|
|
2829
|
+
try: () => resetRef.current?.(),
|
|
2830
|
+
catch: (cause) => new AuthClientError({
|
|
2831
|
+
operation: "signOut",
|
|
2832
|
+
kind: "signer",
|
|
2833
|
+
cause
|
|
2834
|
+
})
|
|
2835
|
+
});
|
|
2836
|
+
return yield* exit;
|
|
2837
|
+
})
|
|
2687
2838
|
};
|
|
2688
2839
|
}
|
|
2689
2840
|
async function createCapxulClient$1(input) {
|
|
@@ -2709,14 +2860,15 @@ async function createCapxulClient$1(input) {
|
|
|
2709
2860
|
if (signer === void 0 && runtime === "browser") signer = createOpenfortBrowserSignerFromBootstrap({
|
|
2710
2861
|
...adapters.value.bootstrap,
|
|
2711
2862
|
authBaseUrl: resolvedAuthBaseUrl
|
|
2712
|
-
}, {
|
|
2713
|
-
diagnostic: new ConsoleDiagnosticAdapter(),
|
|
2714
|
-
telemetry: adapters.value.ports.telemetry
|
|
2715
|
-
});
|
|
2863
|
+
}, { diagnostic: new ConsoleDiagnosticAdapter() });
|
|
2716
2864
|
if (signer !== void 0 && "resetSession" in signer) {
|
|
2717
|
-
const { resetSession } = signer;
|
|
2865
|
+
const { resetSession, source } = signer;
|
|
2718
2866
|
resetSignerSession.current = () => {
|
|
2719
|
-
|
|
2867
|
+
try {
|
|
2868
|
+
resetSession();
|
|
2869
|
+
} catch (cause) {
|
|
2870
|
+
throw signerFailure(source, "resetSession", cause);
|
|
2871
|
+
}
|
|
2720
2872
|
};
|
|
2721
2873
|
}
|
|
2722
2874
|
const client = assembleCapxulClient({
|
|
@@ -2734,6 +2886,7 @@ async function createCapxulClient$1(input) {
|
|
|
2734
2886
|
...input.signal === void 0 ? {} : { signal: input.signal },
|
|
2735
2887
|
...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs },
|
|
2736
2888
|
invokeTimeoutMs: input.invokeTimeoutMs ?? 3e4,
|
|
2889
|
+
...input.observability?.failures === void 0 && input.observation === void 0 ? {} : { failureObservation: input.observability?.failures ?? input.observation },
|
|
2737
2890
|
...input.observability === void 0 ? {} : { hostObservationSnapshot: () => snapshotHostObservability(input.observability) },
|
|
2738
2891
|
effectRunner: {
|
|
2739
2892
|
runSync: Effect.runSyncWith(adapters.value.context),
|
|
@@ -2743,7 +2896,11 @@ async function createCapxulClient$1(input) {
|
|
|
2743
2896
|
const upstreamClose = adapters.value.close;
|
|
2744
2897
|
const close = idempotentClose(async () => {
|
|
2745
2898
|
try {
|
|
2746
|
-
if ("resetSession" in
|
|
2899
|
+
if (signer !== void 0 && "resetSession" in signer) try {
|
|
2900
|
+
signer.resetSession();
|
|
2901
|
+
} catch (cause) {
|
|
2902
|
+
throw signerFailure(signer.source, "resetSession", cause);
|
|
2903
|
+
}
|
|
2747
2904
|
} finally {
|
|
2748
2905
|
await Promise.all([client._internal.close?.(), upstreamClose()]);
|
|
2749
2906
|
}
|
|
@@ -2912,4 +3069,4 @@ async function createCapxulClient(input) {
|
|
|
2912
3069
|
return createCapxulClient$1(input);
|
|
2913
3070
|
}
|
|
2914
3071
|
//#endregion
|
|
2915
|
-
export { CAPXUL_PAYMENTS_V2_ADDRESS, CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, CapxulError, captureException, captureExceptionSync, createCapxulClient, deriveDevPrivateKey, devPrivateKeySigner, eip1193AccountProvider, embeddedSigner, fingerprintPaymentIntent, injectedWalletSigner, isCapxulError, isSettingUpLifecycle, localPrivateKeyAccountProvider, openfortEmbeddedSigner, openfortEmbeddedSignerFromWallet, openfortEmbeddedWalletPort, postHogObservability, destination as resolveIdentityDestination, toCountryCode, toAddress as toEvmAddress };
|
|
3072
|
+
export { CAPXUL_ERROR_CODES, CAPXUL_PAYMENTS_V2_ADDRESS, CAPXUL_SDK_EXPECTED_OUTCOME_EVENT, CapxulError, Errors, captureException, captureExceptionSync, createCapxulClient, deriveDevPrivateKey, devPrivateKeySigner, eip1193AccountProvider, embeddedSigner, fingerprintPaymentIntent, formatMoney, injectedWalletSigner, isCapxulError, isClaimed, isMoneyParseError, isRestoring, isSettingUpLifecycle, localPrivateKeyAccountProvider, openfortEmbeddedSigner, openfortEmbeddedSignerFromWallet, openfortEmbeddedWalletPort, parseMoney, postHogObservability, destination as resolveIdentityDestination, toCountryCode, toAddress as toEvmAddress };
|
package/dist/node/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { O as AuthSession, _ as AuthCacheError, b as CachedJwt, n as CapxulSigner, v as AuthCachePort, y as AuthCachePortTag } from "../signer-BejoR3bA.mjs";
|
|
2
2
|
import { Hex } from "viem";
|
|
3
3
|
import { Effect, FileSystem, Layer, Path } from "effect";
|
|
4
4
|
|
package/dist/node/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AuthCacheError,
|
|
1
|
+
import { a as AuthCacheError, h as toAddress, i as parseCachedJwt, n as BrowserAuthCacheAdapter, o as AuthCachePortTag, r as parseAuthSession, t as InMemoryAuthCacheAdapter } from "../InMemoryAuthCacheAdapter-qMpBOGb3.mjs";
|
|
2
2
|
import { Effect, FileSystem, Layer, Path } from "effect";
|
|
3
3
|
import { privateKeyToAccount } from "viem/accounts";
|
|
4
4
|
import * as os from "node:os";
|