@capxul/sdk-react 2.0.2 → 2.1.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.
|
@@ -81,11 +81,10 @@ const capxulKeys = {
|
|
|
81
81
|
"permissions"
|
|
82
82
|
],
|
|
83
83
|
activity: ["capxul", "activity"],
|
|
84
|
-
activityDetail: (
|
|
84
|
+
activityDetail: (reference) => [
|
|
85
85
|
"capxul",
|
|
86
86
|
"activity",
|
|
87
|
-
|
|
88
|
-
id ?? "pending"
|
|
87
|
+
reference ?? "pending"
|
|
89
88
|
],
|
|
90
89
|
holdings: ["capxul", "holdings"],
|
|
91
90
|
payments: ["capxul", "payments"],
|
package/dist/index.d.mts
CHANGED
|
@@ -69,7 +69,8 @@ type UseCapxulAccountFundReturn = UseMutationResult<{
|
|
|
69
69
|
declare function useCapxulAccountFund(): UseCapxulAccountFundReturn;
|
|
70
70
|
//#endregion
|
|
71
71
|
//#region src/hooks/use-capxul-money.d.ts
|
|
72
|
-
type
|
|
72
|
+
type CapxulPayIntent = PaymentsPayInput;
|
|
73
|
+
type UseCapxulPayReturn = UseMutationResult<Payment, CapxulError, CapxulPayIntent>;
|
|
73
74
|
declare function useCapxulPay(): UseCapxulPayReturn;
|
|
74
75
|
type UseCapxulCreateCommitmentReturn = UseMutationResult<Payment, CapxulError, PaymentsPayInput & {
|
|
75
76
|
readonly timing: Exclude<PaymentTiming, {
|
|
@@ -321,7 +322,7 @@ declare function useCapxulHoldings(): UseQueryResult<CurrentHoldings, CapxulErro
|
|
|
321
322
|
//#endregion
|
|
322
323
|
//#region src/hooks/use-capxul-orgs.d.ts
|
|
323
324
|
/**
|
|
324
|
-
* List the
|
|
325
|
+
* List the Organizations that the current user can access. Binds directly to
|
|
325
326
|
* the locked `capxul.orgs()` SDK method.
|
|
326
327
|
*/
|
|
327
328
|
type UseCapxulOrgsReturn = UseQueryResult<readonly OrgView[], CapxulError>;
|
|
@@ -341,8 +342,8 @@ type UseCapxulOrgMembersOptions = {
|
|
|
341
342
|
readonly enabled?: boolean;
|
|
342
343
|
};
|
|
343
344
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
345
|
+
* List the members of one Organization. Binds directly to
|
|
346
|
+
* `capxul.org(orgId).members()`. Gated by
|
|
346
347
|
* `orgId !== undefined`; callers may additionally gate on canonical identity
|
|
347
348
|
* readiness through `options.enabled`.
|
|
348
349
|
*/
|
|
@@ -354,9 +355,8 @@ type UseCapxulOrgRolesOptions = {
|
|
|
354
355
|
readonly enabled?: boolean;
|
|
355
356
|
};
|
|
356
357
|
/**
|
|
357
|
-
*
|
|
358
|
-
* to
|
|
359
|
-
* `orgId !== undefined`. RED until S2.
|
|
358
|
+
* List the roles for one Organization. Binds directly
|
|
359
|
+
* to `capxul.org(orgId).roles()`. Gated by `orgId !== undefined`.
|
|
360
360
|
*/
|
|
361
361
|
type UseCapxulOrgRolesReturn = UseQueryResult<readonly RoleView[], CapxulError>;
|
|
362
362
|
declare function useCapxulOrgRoles(orgId: OrgId | undefined, options?: UseCapxulOrgRolesOptions): UseCapxulOrgRolesReturn;
|
|
@@ -366,29 +366,26 @@ type UseCapxulOrgTreasuryOptions = {
|
|
|
366
366
|
readonly enabled?: boolean;
|
|
367
367
|
};
|
|
368
368
|
/**
|
|
369
|
-
*
|
|
369
|
+
* Return the Organization treasury Account.
|
|
370
370
|
* NEVER `AccountStatus` (the deploy-readiness ladder, no balance). Binds
|
|
371
|
-
* directly to
|
|
372
|
-
* `orgId !== undefined`. RED until S1.
|
|
371
|
+
* Binds directly to `capxul.org(orgId).treasury()`. Gated by `orgId !== undefined`.
|
|
373
372
|
*/
|
|
374
373
|
type UseCapxulOrgTreasuryReturn = UseQueryResult<Account, CapxulError>;
|
|
375
374
|
declare function useCapxulOrgTreasury(orgId: OrgId | undefined, options?: UseCapxulOrgTreasuryOptions): UseCapxulOrgTreasuryReturn;
|
|
376
375
|
//#endregion
|
|
377
376
|
//#region src/hooks/use-capxul-create-org.d.ts
|
|
378
377
|
/**
|
|
379
|
-
* Create an
|
|
378
|
+
* Create an Organization. Binds directly to the
|
|
380
379
|
* `capxul.createOrg(input)` SDK method. On success, invalidates the org list.
|
|
381
|
-
* RED until S1 — `mutate` rejects with `Errors.notImplemented("org","createOrg")`.
|
|
382
380
|
*/
|
|
383
381
|
type UseCapxulCreateOrgReturn = UseMutationResult<OrgView, CapxulError, CreateOrgInput>;
|
|
384
382
|
declare function useCapxulCreateOrg(): UseCapxulCreateOrgReturn;
|
|
385
383
|
//#endregion
|
|
386
384
|
//#region src/hooks/use-capxul-invite-member.d.ts
|
|
387
385
|
/**
|
|
388
|
-
* Invite a member to an
|
|
389
|
-
*
|
|
386
|
+
* Invite a member to an Organization by email. The hook uses the closed-over `orgId`.
|
|
387
|
+
* It binds directly to
|
|
390
388
|
* `capxul.org(orgId).invite(input)`. On success, invalidates the member list.
|
|
391
|
-
* RED until S3 — `mutate` rejects with `Errors.notImplemented("org","invite")`.
|
|
392
389
|
*/
|
|
393
390
|
type UseCapxulInviteMemberReturn = UseMutationResult<MemberView, CapxulError, InviteMemberInput>;
|
|
394
391
|
declare function useCapxulInviteMember(orgId: OrgId | undefined): UseCapxulInviteMemberReturn;
|
|
@@ -421,7 +418,7 @@ type OnboardingOrigin = {
|
|
|
421
418
|
readonly kind: "organization";
|
|
422
419
|
readonly organizationId: string;
|
|
423
420
|
} | {
|
|
424
|
-
readonly kind: "
|
|
421
|
+
readonly kind: "verification";
|
|
425
422
|
readonly organizationId?: string;
|
|
426
423
|
};
|
|
427
424
|
type OnboardingJourney = {
|
|
@@ -445,7 +442,7 @@ type OnboardingJourneyPosition = {
|
|
|
445
442
|
readonly intent: OnboardingIntent;
|
|
446
443
|
readonly step: OnboardingStep;
|
|
447
444
|
readonly organizationId?: string;
|
|
448
|
-
readonly
|
|
445
|
+
readonly verification: boolean;
|
|
449
446
|
};
|
|
450
447
|
declare function startOnboardingJourney(input: {
|
|
451
448
|
readonly intent: OnboardingIntent;
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { a as useCapxulAuth, c as useCapxulIdentityOrNull, d as capxulKeys, f as useCapxulClientOrNull, i as entered, l as useCapxulSend, n as CapxulOnboardingController, o as useCapxulDestination, p as useCapxul, r as CapxulProvider, s as useCapxulIdentity, t as CapxulAuthenticationController, u as useCapxulTransitions } from "./controllers-
|
|
2
|
+
import { a as useCapxulAuth, c as useCapxulIdentityOrNull, d as capxulKeys, f as useCapxulClientOrNull, i as entered, l as useCapxulSend, n as CapxulOnboardingController, o as useCapxulDestination, p as useCapxul, r as CapxulProvider, s as useCapxulIdentity, t as CapxulAuthenticationController, u as useCapxulTransitions } from "./controllers-Chqyy3HR.mjs";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
+
import { fingerprintPaymentIntent } from "@capxul/sdk";
|
|
5
6
|
//#region ../errors/src/errors.ts
|
|
6
7
|
const CAPXUL_ERROR_CODES = [
|
|
7
8
|
"NOT_AUTHENTICATED",
|
|
@@ -32,7 +33,7 @@ const CAPXUL_ERROR_CODES = [
|
|
|
32
33
|
"WORK_DIED",
|
|
33
34
|
"ACTOR_STOPPED"
|
|
34
35
|
];
|
|
35
|
-
var CapxulError = class extends Error {
|
|
36
|
+
var CapxulError$1 = class extends Error {
|
|
36
37
|
code;
|
|
37
38
|
details;
|
|
38
39
|
correlationId;
|
|
@@ -47,33 +48,33 @@ var CapxulError = class extends Error {
|
|
|
47
48
|
}
|
|
48
49
|
};
|
|
49
50
|
const Errors = {
|
|
50
|
-
notAuthenticated: (message, opts) => new CapxulError("NOT_AUTHENTICATED", message ?? "Not authenticated", opts?.failure_mode ? { details: { failure_mode: opts.failure_mode } } : void 0),
|
|
51
|
-
emailDeliveryFailed: (detail) => new CapxulError("EMAIL_DELIVERY_FAILED", "Failed to send email", { details: { detail } }),
|
|
52
|
-
profileNotFound: (authUserId) => new CapxulError("PROFILE_NOT_FOUND", `Profile not found for user ${authUserId}`, { details: { authUserId } }),
|
|
53
|
-
smartAccountMissing: (authUserId) => new CapxulError("SMART_ACCOUNT_MISSING", "Smart account not provisioned", { details: { authUserId } }),
|
|
54
|
-
playerNotFound: (playerId) => new CapxulError("PLAYER_NOT_FOUND", playerId ? `Openfort player ${playerId} not found` : "Openfort player not found", playerId === void 0 ? void 0 : { details: { playerId } }),
|
|
55
|
-
accountNotFound: (accountId) => new CapxulError("ACCOUNT_NOT_FOUND", accountId ? `Openfort account ${accountId} not found` : "Openfort account not found", accountId === void 0 ? void 0 : { details: { accountId } }),
|
|
51
|
+
notAuthenticated: (message, opts) => new CapxulError$1("NOT_AUTHENTICATED", message ?? "Not authenticated", opts?.failure_mode ? { details: { failure_mode: opts.failure_mode } } : void 0),
|
|
52
|
+
emailDeliveryFailed: (detail) => new CapxulError$1("EMAIL_DELIVERY_FAILED", "Failed to send email", { details: { detail } }),
|
|
53
|
+
profileNotFound: (authUserId) => new CapxulError$1("PROFILE_NOT_FOUND", `Profile not found for user ${authUserId}`, { details: { authUserId } }),
|
|
54
|
+
smartAccountMissing: (authUserId) => new CapxulError$1("SMART_ACCOUNT_MISSING", "Smart account not provisioned", { details: { authUserId } }),
|
|
55
|
+
playerNotFound: (playerId) => new CapxulError$1("PLAYER_NOT_FOUND", playerId ? `Openfort player ${playerId} not found` : "Openfort player not found", playerId === void 0 ? void 0 : { details: { playerId } }),
|
|
56
|
+
accountNotFound: (accountId) => new CapxulError$1("ACCOUNT_NOT_FOUND", accountId ? `Openfort account ${accountId} not found` : "Openfort account not found", accountId === void 0 ? void 0 : { details: { accountId } }),
|
|
56
57
|
providerError: (provider, operation, cause, opts) => {
|
|
57
58
|
const details = {
|
|
58
59
|
provider,
|
|
59
60
|
operation
|
|
60
61
|
};
|
|
61
62
|
if (opts?.failure_mode) details.failure_mode = opts.failure_mode;
|
|
62
|
-
return new CapxulError("PROVIDER_ERROR", `Provider error: ${provider} ${operation}`, {
|
|
63
|
+
return new CapxulError$1("PROVIDER_ERROR", `Provider error: ${provider} ${operation}`, {
|
|
63
64
|
cause,
|
|
64
65
|
details
|
|
65
66
|
});
|
|
66
67
|
},
|
|
67
|
-
capabilityUnavailable: (provider, operation) => new CapxulError("CAPABILITY_UNAVAILABLE", "Provider capability is unavailable", { details: {
|
|
68
|
+
capabilityUnavailable: (provider, operation) => new CapxulError$1("CAPABILITY_UNAVAILABLE", "Provider capability is unavailable", { details: {
|
|
68
69
|
provider,
|
|
69
70
|
operation
|
|
70
71
|
} }),
|
|
71
|
-
invalidInput: (field, reason) => new CapxulError("INVALID_INPUT", `Invalid ${field}: ${reason}`, { details: {
|
|
72
|
+
invalidInput: (field, reason) => new CapxulError$1("INVALID_INPUT", `Invalid ${field}: ${reason}`, { details: {
|
|
72
73
|
field,
|
|
73
74
|
reason
|
|
74
75
|
} }),
|
|
75
|
-
envMissing: (name) => new CapxulError("ENV_MISSING", `Environment variable ${name} not configured`, { details: { name } }),
|
|
76
|
-
notImplemented: (domain, method) => new CapxulError("NOT_IMPLEMENTED", `${domain}.${method} is not yet implemented. This feature is planned for a future release.`, { details: {
|
|
76
|
+
envMissing: (name) => new CapxulError$1("ENV_MISSING", `Environment variable ${name} not configured`, { details: { name } }),
|
|
77
|
+
notImplemented: (domain, method) => new CapxulError$1("NOT_IMPLEMENTED", `${domain}.${method} is not yet implemented. This feature is planned for a future release.`, { details: {
|
|
77
78
|
domain,
|
|
78
79
|
method
|
|
79
80
|
} }),
|
|
@@ -85,7 +86,7 @@ const Errors = {
|
|
|
85
86
|
* message string. The redacted message names the timeout budget; the
|
|
86
87
|
* native `cause` carries the same information for `reportError` fidelity.
|
|
87
88
|
*/
|
|
88
|
-
providerTimeout: (provider, operation, timeoutMs) => new CapxulError("PROVIDER_ERROR", `Provider error: ${provider} ${operation} (timeout exceeded ${timeoutMs}ms)`, {
|
|
89
|
+
providerTimeout: (provider, operation, timeoutMs) => new CapxulError$1("PROVIDER_ERROR", `Provider error: ${provider} ${operation} (timeout exceeded ${timeoutMs}ms)`, {
|
|
89
90
|
details: {
|
|
90
91
|
provider,
|
|
91
92
|
operation,
|
|
@@ -94,14 +95,14 @@ const Errors = {
|
|
|
94
95
|
cause: /* @__PURE__ */ new Error(`timeout: ${operation} exceeded ${timeoutMs}ms`)
|
|
95
96
|
}),
|
|
96
97
|
verificationRequired: (details) => {
|
|
97
|
-
return new CapxulError("VERIFICATION_REQUIRED", "rail" in details ? `Verification is required before ${details.rail} can use ${details.currentKind}.` : `Verification tier ${details.requiredTier} is required.`, { details });
|
|
98
|
+
return new CapxulError$1("VERIFICATION_REQUIRED", "rail" in details ? `Verification is required before ${details.rail} can use ${details.currentKind}.` : `Verification tier ${details.requiredTier} is required.`, { details });
|
|
98
99
|
},
|
|
99
|
-
insufficientBalance: (asset, available, required) => new CapxulError("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, { details: {
|
|
100
|
+
insufficientBalance: (asset, available, required) => new CapxulError$1("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, { details: {
|
|
100
101
|
asset,
|
|
101
102
|
available,
|
|
102
103
|
required
|
|
103
104
|
} }),
|
|
104
|
-
invalidRecipient: (reason) => new CapxulError("INVALID_RECIPIENT", `Invalid recipient: ${reason}`, { details: { reason } }),
|
|
105
|
+
invalidRecipient: (reason) => new CapxulError$1("INVALID_RECIPIENT", `Invalid recipient: ${reason}`, { details: { reason } }),
|
|
105
106
|
/**
|
|
106
107
|
* The org Zodiac Roles modifier REFUSED the spend on-chain (G4 · #547): the
|
|
107
108
|
* member's role condition (per-tx cap, per-day allowance, allowed recipient,
|
|
@@ -112,7 +113,7 @@ const Errors = {
|
|
|
112
113
|
* `disallowed_recipient` / `condition_violation`); leak-safe — no on-chain
|
|
113
114
|
* identifiers ever enter the details.
|
|
114
115
|
*/
|
|
115
|
-
rolePermissionDenied: (details) => new CapxulError("ROLE_PERMISSION_DENIED", `Org role denied this spend on-chain (${details.reason}).`, { details: details.operation === void 0 ? { reason: details.reason } : {
|
|
116
|
+
rolePermissionDenied: (details) => new CapxulError$1("ROLE_PERMISSION_DENIED", `Org role denied this spend on-chain (${details.reason}).`, { details: details.operation === void 0 ? { reason: details.reason } : {
|
|
116
117
|
reason: details.reason,
|
|
117
118
|
operation: details.operation
|
|
118
119
|
} }),
|
|
@@ -123,28 +124,28 @@ const Errors = {
|
|
|
123
124
|
* Roles condition violation) from an inconclusive infra failure. A confirmed
|
|
124
125
|
* revert is the ONLY mode the org spend port may map to a roles denial.
|
|
125
126
|
*/
|
|
126
|
-
transactionFailed: (operation, cause, extra) => new CapxulError("TRANSACTION_FAILED", `Transaction failed: ${operation}`, {
|
|
127
|
+
transactionFailed: (operation, cause, extra) => new CapxulError$1("TRANSACTION_FAILED", `Transaction failed: ${operation}`, {
|
|
127
128
|
cause,
|
|
128
129
|
details: extra?.reason === void 0 ? { operation } : {
|
|
129
130
|
operation,
|
|
130
131
|
reason: extra.reason
|
|
131
132
|
}
|
|
132
133
|
}),
|
|
133
|
-
rateLimited: (details) => new CapxulError("RATE_LIMITED", "Rate limit exceeded", details === void 0 ? void 0 : { details: { ...details } }),
|
|
134
|
-
networkError: (operation, cause) => new CapxulError("NETWORK_ERROR", `Network error during ${operation}`, {
|
|
134
|
+
rateLimited: (details) => new CapxulError$1("RATE_LIMITED", "Rate limit exceeded", details === void 0 ? void 0 : { details: { ...details } }),
|
|
135
|
+
networkError: (operation, cause) => new CapxulError$1("NETWORK_ERROR", `Network error during ${operation}`, {
|
|
135
136
|
cause,
|
|
136
137
|
details: { operation }
|
|
137
138
|
}),
|
|
138
|
-
unknown: (cause) => new CapxulError("UNKNOWN", "Unknown error", { cause }),
|
|
139
|
-
otpExpired: (details) => new CapxulError("OTP_EXPIRED", "Verification code has expired. Request a new one.", details === void 0 ? void 0 : { details: { ...details } }),
|
|
140
|
-
signerRejected: (details) => new CapxulError("SIGNER_REJECTED", "Signer rejected the request.", {
|
|
139
|
+
unknown: (cause) => new CapxulError$1("UNKNOWN", "Unknown error", { cause }),
|
|
140
|
+
otpExpired: (details) => new CapxulError$1("OTP_EXPIRED", "Verification code has expired. Request a new one.", details === void 0 ? void 0 : { details: { ...details } }),
|
|
141
|
+
signerRejected: (details) => new CapxulError$1("SIGNER_REJECTED", "Signer rejected the request.", {
|
|
141
142
|
cause: details.cause,
|
|
142
143
|
details: details.reason === void 0 ? { source: details.source } : {
|
|
143
144
|
source: details.source,
|
|
144
145
|
reason: details.reason
|
|
145
146
|
}
|
|
146
147
|
}),
|
|
147
|
-
cancelled: (details) => new CapxulError("CANCELLED", "Operation was cancelled.", details === void 0 ? void 0 : { details: { ...details } }),
|
|
148
|
+
cancelled: (details) => new CapxulError$1("CANCELLED", "Operation was cancelled.", details === void 0 ? void 0 : { details: { ...details } }),
|
|
148
149
|
/**
|
|
149
150
|
* Method called from a flow state where its precondition fails (TA16). The
|
|
150
151
|
* SDK's method API short-circuits with this error before driving the
|
|
@@ -153,7 +154,7 @@ const Errors = {
|
|
|
153
154
|
* `packages/errors/CONTEXT.md`); `validStates`
|
|
154
155
|
* enumerates the states the method accepts.
|
|
155
156
|
*/
|
|
156
|
-
wrongState: (details) => new CapxulError("WRONG_STATE", `${details.method} called from state '${details.currentState}'; valid states: ${details.validStates.join(", ")}`, { details: {
|
|
157
|
+
wrongState: (details) => new CapxulError$1("WRONG_STATE", `${details.method} called from state '${details.currentState}'; valid states: ${details.validStates.join(", ")}`, { details: {
|
|
157
158
|
...details,
|
|
158
159
|
validStates: [...details.validStates]
|
|
159
160
|
} })
|
|
@@ -242,6 +243,137 @@ async function invalidateMoneyState(queryClient, input) {
|
|
|
242
243
|
await Promise.all(invalidations);
|
|
243
244
|
}
|
|
244
245
|
//#endregion
|
|
246
|
+
//#region src/internal/payment-request-key.ts
|
|
247
|
+
const STORAGE_PREFIX = "capxul.payment.request-key.v3";
|
|
248
|
+
const attemptReleases = /* @__PURE__ */ new Set();
|
|
249
|
+
let pagehideInstalled = false;
|
|
250
|
+
async function storageKey(operation, intent) {
|
|
251
|
+
return `${STORAGE_PREFIX}:${await fingerprintPaymentIntent({
|
|
252
|
+
operation,
|
|
253
|
+
intent
|
|
254
|
+
})}`;
|
|
255
|
+
}
|
|
256
|
+
function paymentLockManager() {
|
|
257
|
+
const manager = navigator.locks;
|
|
258
|
+
if (manager === void 0 || typeof manager.query !== "function") throw new Error("Web Locks are required for payment request keys");
|
|
259
|
+
return manager;
|
|
260
|
+
}
|
|
261
|
+
function attemptLockName(storageSlot, attemptId) {
|
|
262
|
+
return `${storageSlot}:attempt:${attemptId}`;
|
|
263
|
+
}
|
|
264
|
+
async function heldAttemptIds(manager, storageSlot) {
|
|
265
|
+
const prefix = `${storageSlot}:attempt:`;
|
|
266
|
+
return ((await manager.query()).held ?? []).map((lock) => lock.name).filter((name) => name.startsWith(prefix)).map((name) => name.slice(prefix.length));
|
|
267
|
+
}
|
|
268
|
+
async function holdAttemptLock(name) {
|
|
269
|
+
const manager = paymentLockManager();
|
|
270
|
+
let acquired;
|
|
271
|
+
let release;
|
|
272
|
+
const ready = new Promise((resolve) => {
|
|
273
|
+
acquired = resolve;
|
|
274
|
+
});
|
|
275
|
+
const held = manager.request(name, async () => {
|
|
276
|
+
acquired?.();
|
|
277
|
+
await new Promise((resolve) => {
|
|
278
|
+
release = resolve;
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
await Promise.race([ready, held.then(() => Promise.reject(/* @__PURE__ */ new Error("Payment attempt lock ended before acquisition")))]);
|
|
282
|
+
return () => {
|
|
283
|
+
release?.();
|
|
284
|
+
held.catch(() => void 0);
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function releaseAttemptOnPagehide(release) {
|
|
288
|
+
attemptReleases.add(release);
|
|
289
|
+
if (!pagehideInstalled) {
|
|
290
|
+
pagehideInstalled = true;
|
|
291
|
+
window.addEventListener("pagehide", () => {
|
|
292
|
+
for (const releaseAttempt of attemptReleases) releaseAttempt();
|
|
293
|
+
attemptReleases.clear();
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return () => attemptReleases.delete(release);
|
|
297
|
+
}
|
|
298
|
+
function readState(key) {
|
|
299
|
+
const stored = localStorage.getItem(key);
|
|
300
|
+
if (stored === null) return null;
|
|
301
|
+
const state = JSON.parse(stored);
|
|
302
|
+
if (typeof state.key !== "string" || !Array.isArray(state.active) || state.active.some((attempt) => typeof attempt !== "string") || typeof state.resolved !== "boolean") throw new TypeError("Invalid payment request key state");
|
|
303
|
+
return state;
|
|
304
|
+
}
|
|
305
|
+
async function beginPaymentRequestKey(operation, intent) {
|
|
306
|
+
const storageSlot = await storageKey(operation, intent);
|
|
307
|
+
let releaseAttemptLock;
|
|
308
|
+
let forgetPagehideRelease;
|
|
309
|
+
try {
|
|
310
|
+
const attemptId = `attempt_${crypto.randomUUID()}`;
|
|
311
|
+
releaseAttemptLock = await holdAttemptLock(attemptLockName(storageSlot, attemptId));
|
|
312
|
+
forgetPagehideRelease = releaseAttemptOnPagehide(releaseAttemptLock);
|
|
313
|
+
const manager = paymentLockManager();
|
|
314
|
+
const key = await manager.request(storageSlot, async () => {
|
|
315
|
+
let state = readState(storageSlot) ?? {
|
|
316
|
+
key: `pay_${Array.from(crypto.getRandomValues(new Uint8Array(16)), (byte) => byte.toString(16).padStart(2, "0")).join("")}`,
|
|
317
|
+
active: [],
|
|
318
|
+
resolved: false
|
|
319
|
+
};
|
|
320
|
+
const registered = state.active.includes(attemptId);
|
|
321
|
+
const active = await heldAttemptIds(manager, storageSlot);
|
|
322
|
+
if (!registered && active.every((id) => id === attemptId) && state.resolved) state = {
|
|
323
|
+
key: `pay_${crypto.randomUUID()}`,
|
|
324
|
+
active: [],
|
|
325
|
+
resolved: false
|
|
326
|
+
};
|
|
327
|
+
state.active = active;
|
|
328
|
+
localStorage.setItem(storageSlot, JSON.stringify(state));
|
|
329
|
+
return state.key;
|
|
330
|
+
});
|
|
331
|
+
let finished = false;
|
|
332
|
+
return {
|
|
333
|
+
key,
|
|
334
|
+
finish: async (succeeded) => {
|
|
335
|
+
if (finished) return;
|
|
336
|
+
try {
|
|
337
|
+
await manager.request(storageSlot, async () => {
|
|
338
|
+
const state = readState(storageSlot);
|
|
339
|
+
if (state === null || state.key !== key) return;
|
|
340
|
+
const attemptIndex = state.active.indexOf(attemptId);
|
|
341
|
+
if (attemptIndex < 0) return;
|
|
342
|
+
state.active.splice(attemptIndex, 1);
|
|
343
|
+
state.active = (await heldAttemptIds(manager, storageSlot)).filter((id) => id !== attemptId);
|
|
344
|
+
state.resolved = succeeded;
|
|
345
|
+
if (state.active.length === 0 && state.resolved) localStorage.removeItem(storageSlot);
|
|
346
|
+
else localStorage.setItem(storageSlot, JSON.stringify(state));
|
|
347
|
+
});
|
|
348
|
+
finished = true;
|
|
349
|
+
} finally {
|
|
350
|
+
forgetPagehideRelease?.();
|
|
351
|
+
releaseAttemptLock?.();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
} catch (cause) {
|
|
356
|
+
forgetPagehideRelease?.();
|
|
357
|
+
releaseAttemptLock?.();
|
|
358
|
+
throw Errors.providerError("sdk-react", "paymentRequestKey", cause);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async function withPaymentRequestKey(operation, intent, work) {
|
|
362
|
+
const attempt = await beginPaymentRequestKey(operation, intent);
|
|
363
|
+
try {
|
|
364
|
+
const result = await work(attempt.key);
|
|
365
|
+
try {
|
|
366
|
+
await attempt.finish(true);
|
|
367
|
+
} catch {}
|
|
368
|
+
return result;
|
|
369
|
+
} catch (cause) {
|
|
370
|
+
try {
|
|
371
|
+
await attempt.finish(false);
|
|
372
|
+
} catch {}
|
|
373
|
+
throw cause;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
//#endregion
|
|
245
377
|
//#region src/internal/reject-unresolved-actor.ts
|
|
246
378
|
/**
|
|
247
379
|
* Guard for mutation hooks whose variables carry an optional `actor` field
|
|
@@ -261,12 +393,22 @@ function rejectUnresolvedActor(variables, operation) {
|
|
|
261
393
|
//#region src/hooks/use-capxul-money.ts
|
|
262
394
|
function useCapxulPay() {
|
|
263
395
|
const client = useCapxulClientOrNull();
|
|
396
|
+
const identity = useCapxulIdentityOrNull();
|
|
397
|
+
const actorId = identity?.phase === "authenticated" ? identity.session.authUserId : "signed-out";
|
|
398
|
+
const backendScope = client === null ? "pending" : `${client._internal.bootstrap.convexUrl}:${String(client._internal.bootstrap.chainId)}`;
|
|
264
399
|
const queryClient = useQueryClient();
|
|
265
400
|
return useMutation({
|
|
266
401
|
mutationFn: async (input) => {
|
|
267
402
|
rejectUnresolvedActor(input, "payments.pay");
|
|
268
403
|
const bootstrappedClient = requireBootstrappedClient(client, "payments.pay");
|
|
269
|
-
return
|
|
404
|
+
return withPaymentRequestKey("personal-pay", {
|
|
405
|
+
actorId,
|
|
406
|
+
backendScope,
|
|
407
|
+
input
|
|
408
|
+
}, async (requestKey) => unwrapCapxulResult(await bootstrappedClient.payments.pay({
|
|
409
|
+
...input,
|
|
410
|
+
requestKey
|
|
411
|
+
}), bootstrappedClient._internal.telemetry, "mutation"));
|
|
270
412
|
},
|
|
271
413
|
onSuccess: async (payment) => {
|
|
272
414
|
await invalidateMoneyState(queryClient, {
|
|
@@ -322,13 +464,14 @@ function useCapxulRedirectPayment() {
|
|
|
322
464
|
}
|
|
323
465
|
function useCapxulPayments(options) {
|
|
324
466
|
const client = useCapxulClientOrNull();
|
|
467
|
+
const identity = useCapxulIdentityOrNull();
|
|
325
468
|
return useQuery({
|
|
326
469
|
queryKey: capxulKeys.payments,
|
|
327
470
|
queryFn: async () => {
|
|
328
471
|
const bootstrappedClient = requireBootstrappedClient(client, "payments.list");
|
|
329
472
|
return unwrapCapxulResult(await bootstrappedClient.payments.list(), bootstrappedClient._internal.telemetry);
|
|
330
473
|
},
|
|
331
|
-
enabled: client !== null && (options?.enabled ?? true)
|
|
474
|
+
enabled: client !== null && identity?.phase === "authenticated" && identity.account.at === "claimed" && (options?.enabled ?? true)
|
|
332
475
|
});
|
|
333
476
|
}
|
|
334
477
|
function useCapxulPayment(paymentId, options) {
|
|
@@ -347,36 +490,56 @@ function useCapxulPayment(paymentId, options) {
|
|
|
347
490
|
//#region src/hooks/use-capxul-organization-payments.ts
|
|
348
491
|
function useCapxulOrganizationPay(orgId) {
|
|
349
492
|
const client = useCapxulClientOrNull();
|
|
493
|
+
const backendScope = client === null ? "pending" : `${client._internal.bootstrap.convexUrl}:${String(client._internal.bootstrap.chainId)}`;
|
|
350
494
|
const queryClient = useQueryClient();
|
|
351
495
|
return useMutation({
|
|
352
496
|
mutationFn: async (input) => {
|
|
353
497
|
const bootstrapped = requireBootstrappedClient(client, "organizationPayments.pay");
|
|
354
|
-
return
|
|
498
|
+
return withPaymentRequestKey("organization-pay", {
|
|
499
|
+
backendScope,
|
|
500
|
+
orgId,
|
|
501
|
+
...input
|
|
502
|
+
}, async (requestKey) => unwrapCapxulResult(await bootstrapped.org(orgId).payments.pay({
|
|
503
|
+
...input,
|
|
504
|
+
requestKey
|
|
505
|
+
}), bootstrapped._internal.telemetry, "mutation"));
|
|
355
506
|
},
|
|
356
|
-
onSuccess: (payment) =>
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
507
|
+
onSuccess: async (payment) => {
|
|
508
|
+
return invalidateMoneyState(queryClient, {
|
|
509
|
+
actor: {
|
|
510
|
+
kind: "organization",
|
|
511
|
+
organizationId: orgId
|
|
512
|
+
},
|
|
513
|
+
payment
|
|
514
|
+
});
|
|
515
|
+
}
|
|
363
516
|
});
|
|
364
517
|
}
|
|
365
518
|
function useCapxulOrganizationPayBatch(orgId) {
|
|
366
519
|
const client = useCapxulClientOrNull();
|
|
520
|
+
const backendScope = client === null ? "pending" : `${client._internal.bootstrap.convexUrl}:${String(client._internal.bootstrap.chainId)}`;
|
|
367
521
|
const queryClient = useQueryClient();
|
|
368
522
|
return useMutation({
|
|
369
523
|
mutationFn: async (input) => {
|
|
370
524
|
const bootstrapped = requireBootstrappedClient(client, "organizationPayments.payBatch");
|
|
371
|
-
return
|
|
525
|
+
return withPaymentRequestKey("organization-pay-batch", {
|
|
526
|
+
backendScope,
|
|
527
|
+
orgId,
|
|
528
|
+
...input
|
|
529
|
+
}, async (requestKey) => unwrapCapxulResult(await bootstrapped.org(orgId).payments.payBatch({
|
|
530
|
+
...input,
|
|
531
|
+
requestKey
|
|
532
|
+
}), bootstrapped._internal.telemetry, "mutation"));
|
|
372
533
|
},
|
|
373
|
-
onSuccess:
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
534
|
+
onSuccess: async (payments) => {
|
|
535
|
+
return Promise.all(payments.map((payment) => invalidateMoneyState(queryClient, {
|
|
536
|
+
actor: {
|
|
537
|
+
kind: "organization",
|
|
538
|
+
organizationId: orgId
|
|
539
|
+
},
|
|
540
|
+
payment
|
|
541
|
+
})));
|
|
542
|
+
}
|
|
380
543
|
});
|
|
381
544
|
}
|
|
382
545
|
//#endregion
|
|
@@ -439,7 +602,7 @@ function useCapxulActivityDetail(reference) {
|
|
|
439
602
|
const client = useCapxulClientOrNull();
|
|
440
603
|
const identity = useCapxulIdentityOrNull();
|
|
441
604
|
return useQuery({
|
|
442
|
-
queryKey: capxulKeys.activityDetail(reference
|
|
605
|
+
queryKey: capxulKeys.activityDetail(reference),
|
|
443
606
|
queryFn: async () => {
|
|
444
607
|
if (reference === void 0) throw new Error("activity reference is required");
|
|
445
608
|
const bootstrapped = requireBootstrappedClient(client, "activity.get");
|
|
@@ -616,7 +779,7 @@ function loadOnboardingJourney(expectedOwnerId) {
|
|
|
616
779
|
const raw = sessionStorage.getItem(JOURNEY_KEY);
|
|
617
780
|
if (raw === null) return null;
|
|
618
781
|
if (raw.length > MAX_STORED_JOURNEY_LENGTH) return discardInvalidJourney();
|
|
619
|
-
const value = JSON.parse(raw);
|
|
782
|
+
const value = migrateStoredJourney(JSON.parse(raw));
|
|
620
783
|
if (!isValidJourney(value)) return discardInvalidJourney();
|
|
621
784
|
if (expectedOwnerId !== void 0 && value.ownerId !== expectedOwnerId) return discardInvalidJourney();
|
|
622
785
|
if (expectedOwnerId !== void 0) markOnboardingOwnerValidated(expectedOwnerId);
|
|
@@ -625,6 +788,20 @@ function loadOnboardingJourney(expectedOwnerId) {
|
|
|
625
788
|
return discardInvalidJourney();
|
|
626
789
|
}
|
|
627
790
|
}
|
|
791
|
+
function migrateStoredJourney(value) {
|
|
792
|
+
if (!isRecord(value) || !isRecord(value.origin) || value.origin.kind !== "proof") return value;
|
|
793
|
+
const migrated = {
|
|
794
|
+
...value,
|
|
795
|
+
origin: {
|
|
796
|
+
...value.origin,
|
|
797
|
+
kind: "verification"
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
try {
|
|
801
|
+
sessionStorage.setItem(JOURNEY_KEY, JSON.stringify(migrated));
|
|
802
|
+
} catch {}
|
|
803
|
+
return migrated;
|
|
804
|
+
}
|
|
628
805
|
function saveOnboardingJourney(journey) {
|
|
629
806
|
if (!isValidJourney(journey)) {
|
|
630
807
|
discardInvalidJourney();
|
|
@@ -645,17 +822,17 @@ function invalidateOnboardingJourneyObservation() {
|
|
|
645
822
|
}
|
|
646
823
|
/** Where the journey stands — the pure position apps map to their routes. */
|
|
647
824
|
function onboardingJourneyPosition(journey) {
|
|
648
|
-
const
|
|
825
|
+
const verification = journey.origin?.kind === "verification";
|
|
649
826
|
if (journey.organizationId !== void 0) return {
|
|
650
827
|
intent: journey.intent,
|
|
651
828
|
step: "provisioning",
|
|
652
829
|
organizationId: journey.organizationId,
|
|
653
|
-
|
|
830
|
+
verification
|
|
654
831
|
};
|
|
655
832
|
return {
|
|
656
833
|
intent: journey.intent,
|
|
657
834
|
step: journey.step,
|
|
658
|
-
|
|
835
|
+
verification
|
|
659
836
|
};
|
|
660
837
|
}
|
|
661
838
|
/**
|
|
@@ -718,7 +895,7 @@ function isOrigin(value) {
|
|
|
718
895
|
if (!isRecord(value)) return false;
|
|
719
896
|
if (value.kind === "personal") return hasOnlyKeys(value, ["kind"]);
|
|
720
897
|
if (value.kind === "organization") return hasOnlyKeys(value, ["kind", "organizationId"]) && isBoundedId(value.organizationId);
|
|
721
|
-
return value.kind === "
|
|
898
|
+
return value.kind === "verification" && hasOnlyKeys(value, ["kind", "organizationId"]) && (value.organizationId === void 0 || isBoundedId(value.organizationId));
|
|
722
899
|
}
|
|
723
900
|
function isProfileDraft(value) {
|
|
724
901
|
if (!isRecord(value) || !hasOnlyKeys(value, [
|
package/dist/testing/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as CapxulOnboardingController, r as CapxulProvider, t as CapxulAuthenticationController } from "../controllers-
|
|
1
|
+
import { n as CapxulOnboardingController, r as CapxulProvider, t as CapxulAuthenticationController } from "../controllers-Chqyy3HR.mjs";
|
|
2
2
|
import "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { createCapxulTestClient } from "@capxul/sdk/testing";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capxul/sdk-react",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/Xelmar-tech/infrastructure.git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@capxul/sdk": "2.0
|
|
29
|
+
"@capxul/sdk": "2.1.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@tanstack/react-query": "^5.66.9",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"react-dom": "^19.2.6",
|
|
43
43
|
"vite-plus": "0.1.23",
|
|
44
44
|
"vitest": "npm:@voidzero-dev/vite-plus-test@0.1.23",
|
|
45
|
+
"@capxul/errors": "0.0.1",
|
|
45
46
|
"@capxul/types": "0.1.0",
|
|
46
|
-
"@capxul/typescript-config": "0.0.0"
|
|
47
|
-
"@capxul/errors": "0.0.1"
|
|
47
|
+
"@capxul/typescript-config": "0.0.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@tanstack/react-query": "^5.66.9",
|