@partylayer/adapter-bron 0.6.0 → 0.7.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 +46 -3
- package/dist/index.d.mts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +163 -77
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +163 -78
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -42,12 +42,55 @@ npm install @partylayer/adapter-bron
|
|
|
42
42
|
## Usage with SDK
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
|
-
import { createPartyLayer } from '@partylayer/sdk';
|
|
45
|
+
import { createPartyLayer, type BronAdapterConfig } from '@partylayer/sdk';
|
|
46
|
+
|
|
47
|
+
// Supply the OAuth endpoints, public client ID, registered redirect URI and
|
|
48
|
+
// signer API URL issued for your application by Bron.
|
|
49
|
+
export function createBronClient(bron: BronAdapterConfig) {
|
|
50
|
+
return createPartyLayer({
|
|
51
|
+
network: 'devnet',
|
|
52
|
+
app: { name: 'My Canton dApp' },
|
|
53
|
+
bron,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Bron requires application-owned OAuth and signer API configuration. Without
|
|
59
|
+
`bron` configuration, Bron is omitted from the wallet picker. After constructing
|
|
60
|
+
the client with your approved configuration, find Bron in `await client.listWallets()`
|
|
61
|
+
and pass that entry's `walletId` to `client.connect()` from a user action in the browser.
|
|
62
|
+
|
|
63
|
+
Use PKCE for browser flows and never expose a client secret in frontend code.
|
|
64
|
+
Register the callback URL with Bron and follow their onboarding instructions;
|
|
65
|
+
this package does not provision a Bron account or credentials.
|
|
66
|
+
|
|
67
|
+
### Registered callback page
|
|
46
68
|
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
For the explicitly configured legacy OAuth contract, the application's registered
|
|
70
|
+
same-origin redirect page relays the authorization response to the popup opener:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { completeBronAuthorization } from '@partylayer/sdk';
|
|
74
|
+
|
|
75
|
+
await completeBronAuthorization('https://your-app.example/oauth/bron/callback');
|
|
49
76
|
```
|
|
50
77
|
|
|
78
|
+
Use the exact registered URL. Load this helper only on that dedicated callback
|
|
79
|
+
page; avoid analytics and other third-party scripts there. The opener verifies
|
|
80
|
+
the popup source, origin, redirect path, single-use state and S256 PKCE, then
|
|
81
|
+
exchanges the code. Browser client secrets and non-PKCE browser configuration are
|
|
82
|
+
rejected. Authorization is bounded, tokens remain in memory, and disconnect
|
|
83
|
+
invalidates pending authorization. No new PartyLayer wallet package is required.
|
|
84
|
+
|
|
85
|
+
This compatibility configuration requires a vendor-approved OAuth/signer API
|
|
86
|
+
contract. Bron's current [public API authentication documentation](https://developer.bron.org/api-reference/authentication)
|
|
87
|
+
describes signed API-key JWTs; it does not establish a universal per-dApp OAuth
|
|
88
|
+
client or a delegated submission right on an application's participant. Do not
|
|
89
|
+
put those API private keys in browser configuration or infer a submission route
|
|
90
|
+
from a message-signing capability. The callback helper fixes the local OAuth
|
|
91
|
+
completion path; vendor credentials, endpoint compatibility and live acceptance
|
|
92
|
+
remain independently validated integration requirements.
|
|
93
|
+
|
|
51
94
|
---
|
|
52
95
|
|
|
53
96
|
## Links
|
package/dist/index.d.mts
CHANGED
|
@@ -27,7 +27,7 @@ interface BronAuthConfig {
|
|
|
27
27
|
redirectUri: string;
|
|
28
28
|
/** Scopes */
|
|
29
29
|
scopes?: string[];
|
|
30
|
-
/**
|
|
30
|
+
/** PKCE defaults to true and is required in browsers. */
|
|
31
31
|
usePKCE?: boolean;
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -77,10 +77,12 @@ declare class BronAdapter implements WalletAdapter {
|
|
|
77
77
|
readonly name = "Bron";
|
|
78
78
|
private authClient;
|
|
79
79
|
private apiClient;
|
|
80
|
+
private redirectUri;
|
|
81
|
+
private connecting;
|
|
80
82
|
constructor(config: BronAdapterConfig);
|
|
81
83
|
getCapabilities(): CapabilityKey[];
|
|
82
84
|
detectInstalled(): Promise<AdapterDetectResult>;
|
|
83
|
-
connect(ctx: AdapterContext,
|
|
85
|
+
connect(ctx: AdapterContext, opts?: {
|
|
84
86
|
timeoutMs?: number;
|
|
85
87
|
requiredCapabilities?: CapabilityKey[];
|
|
86
88
|
}): Promise<AdapterConnectResult>;
|
|
@@ -97,4 +99,11 @@ declare class BronAdapter implements WalletAdapter {
|
|
|
97
99
|
ledgerApi(ctx: AdapterContext, session: _partylayer_core.Session, params: LedgerApiParams): Promise<LedgerApiResult>;
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Call from the application's registered redirect page. The popup relays only
|
|
104
|
+
* to its own origin; the opener verifies the source, path, state and PKCE.
|
|
105
|
+
* This helper does not register a Bron OAuth client or define vendor endpoints.
|
|
106
|
+
*/
|
|
107
|
+
declare function completeBronAuthorization(redirectUri: string): void;
|
|
108
|
+
|
|
109
|
+
export { BronAdapter, type BronAdapterApiConfig, type BronAdapterConfig, type BronApiConfig, type BronAuthConfig, completeBronAuthorization };
|
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ interface BronAuthConfig {
|
|
|
27
27
|
redirectUri: string;
|
|
28
28
|
/** Scopes */
|
|
29
29
|
scopes?: string[];
|
|
30
|
-
/**
|
|
30
|
+
/** PKCE defaults to true and is required in browsers. */
|
|
31
31
|
usePKCE?: boolean;
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -77,10 +77,12 @@ declare class BronAdapter implements WalletAdapter {
|
|
|
77
77
|
readonly name = "Bron";
|
|
78
78
|
private authClient;
|
|
79
79
|
private apiClient;
|
|
80
|
+
private redirectUri;
|
|
81
|
+
private connecting;
|
|
80
82
|
constructor(config: BronAdapterConfig);
|
|
81
83
|
getCapabilities(): CapabilityKey[];
|
|
82
84
|
detectInstalled(): Promise<AdapterDetectResult>;
|
|
83
|
-
connect(ctx: AdapterContext,
|
|
85
|
+
connect(ctx: AdapterContext, opts?: {
|
|
84
86
|
timeoutMs?: number;
|
|
85
87
|
requiredCapabilities?: CapabilityKey[];
|
|
86
88
|
}): Promise<AdapterConnectResult>;
|
|
@@ -97,4 +99,11 @@ declare class BronAdapter implements WalletAdapter {
|
|
|
97
99
|
ledgerApi(ctx: AdapterContext, session: _partylayer_core.Session, params: LedgerApiParams): Promise<LedgerApiResult>;
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Call from the application's registered redirect page. The popup relays only
|
|
104
|
+
* to its own origin; the opener verifies the source, path, state and PKCE.
|
|
105
|
+
* This helper does not register a Bron OAuth client or define vendor endpoints.
|
|
106
|
+
*/
|
|
107
|
+
declare function completeBronAuthorization(redirectUri: string): void;
|
|
108
|
+
|
|
109
|
+
export { BronAdapter, type BronAdapterApiConfig, type BronAdapterConfig, type BronApiConfig, type BronAuthConfig, completeBronAuthorization };
|
package/dist/index.js
CHANGED
|
@@ -10,15 +10,32 @@ var BronAuthClient = class {
|
|
|
10
10
|
storage;
|
|
11
11
|
tokens = null;
|
|
12
12
|
// In-memory by default
|
|
13
|
+
pending = null;
|
|
14
|
+
exchange = null;
|
|
15
|
+
authorization = new AbortController();
|
|
16
|
+
epoch = 0;
|
|
17
|
+
storageWrites = Promise.resolve();
|
|
18
|
+
persist(action) {
|
|
19
|
+
const write = this.storageWrites.catch(() => {
|
|
20
|
+
}).then(action);
|
|
21
|
+
this.storageWrites = write;
|
|
22
|
+
return write;
|
|
23
|
+
}
|
|
13
24
|
constructor(config, storage) {
|
|
14
|
-
|
|
25
|
+
for (const input of [config.authorizationUrl, config.tokenUrl, config.redirectUri]) {
|
|
26
|
+
const url = new URL(input);
|
|
27
|
+
if (url.username || url.password || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) throw new Error("OAuth requires trusted HTTPS URLs");
|
|
28
|
+
}
|
|
29
|
+
if (!config.clientId?.trim()) throw new Error("OAuth clientId is required");
|
|
30
|
+
if (typeof window !== "undefined" && (config.clientSecret || config.usePKCE === false)) throw new Error("Browser OAuth requires PKCE and must not contain a client secret");
|
|
31
|
+
this.config = { ...config, usePKCE: config.usePKCE ?? true };
|
|
15
32
|
this.storage = storage;
|
|
16
33
|
}
|
|
17
34
|
/**
|
|
18
35
|
* Generate PKCE code verifier and challenge
|
|
19
36
|
*/
|
|
20
37
|
async generatePKCE() {
|
|
21
|
-
const verifier = this.generateRandomString(
|
|
38
|
+
const verifier = this.generateRandomString(32);
|
|
22
39
|
const encoder = new TextEncoder();
|
|
23
40
|
const data = encoder.encode(verifier);
|
|
24
41
|
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
@@ -37,6 +54,9 @@ var BronAuthClient = class {
|
|
|
37
54
|
* Start authorization flow
|
|
38
55
|
*/
|
|
39
56
|
async startAuth() {
|
|
57
|
+
this.cancelAuthorization();
|
|
58
|
+
this.authorization = new AbortController();
|
|
59
|
+
const epoch = this.epoch;
|
|
40
60
|
const state = this.generateRandomString(32);
|
|
41
61
|
const params = new URLSearchParams({
|
|
42
62
|
response_type: "code",
|
|
@@ -49,94 +69,108 @@ var BronAuthClient = class {
|
|
|
49
69
|
const pkce = await this.generatePKCE();
|
|
50
70
|
params.set("code_challenge", pkce.challenge);
|
|
51
71
|
params.set("code_challenge_method", "S256");
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
sessionStorage.setItem("bron_auth_state", state);
|
|
55
|
-
}
|
|
72
|
+
if (epoch !== this.epoch) throw new Error("OAuth authorization cancelled");
|
|
73
|
+
this.pending = { state, verifier: pkce.verifier, expiresAt: Date.now() + 3e5 };
|
|
56
74
|
} else {
|
|
57
|
-
|
|
58
|
-
sessionStorage.setItem("bron_auth_state", state);
|
|
59
|
-
}
|
|
75
|
+
this.pending = { state, expiresAt: Date.now() + 3e5 };
|
|
60
76
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
77
|
+
const url = new URL(this.config.authorizationUrl);
|
|
78
|
+
for (const [key, value] of params) url.searchParams.set(key, value);
|
|
79
|
+
return url.href;
|
|
63
80
|
}
|
|
64
81
|
/**
|
|
65
82
|
* Finish authorization flow with callback URL
|
|
66
83
|
*/
|
|
67
84
|
async finishAuth(callbackUrl) {
|
|
68
85
|
const url = new URL(callbackUrl);
|
|
86
|
+
const redirect = new URL(this.config.redirectUri);
|
|
87
|
+
const pending = this.pending;
|
|
88
|
+
if (url.origin !== redirect.origin || url.pathname !== redirect.pathname || url.hash || url.username || url.password || [...redirect.searchParams].some(([key, value]) => url.searchParams.getAll(key).length !== 1 || url.searchParams.get(key) !== value) || !pending || pending.expiresAt <= Date.now() || url.searchParams.getAll("state").length !== 1 || url.searchParams.get("state") !== pending.state) throw new Error("Invalid or expired OAuth callback state");
|
|
89
|
+
this.pending = null;
|
|
90
|
+
if (url.searchParams.has("error")) throw new Error("OAuth authorization was declined or failed");
|
|
69
91
|
const code = url.searchParams.get("code");
|
|
70
|
-
|
|
71
|
-
const error = url.searchParams.get("error");
|
|
72
|
-
if (error) {
|
|
73
|
-
throw new Error(`OAuth error: ${error}`);
|
|
74
|
-
}
|
|
75
|
-
if (!code) {
|
|
76
|
-
throw new Error("No authorization code in callback");
|
|
77
|
-
}
|
|
78
|
-
if (typeof window !== "undefined") {
|
|
79
|
-
const storedState = sessionStorage.getItem("bron_auth_state");
|
|
80
|
-
if (state !== storedState) {
|
|
81
|
-
throw new Error("State mismatch");
|
|
82
|
-
}
|
|
83
|
-
}
|
|
92
|
+
if (!code || url.searchParams.getAll("code").length !== 1) throw new Error("No unique authorization code in callback");
|
|
84
93
|
const tokenParams = new URLSearchParams({
|
|
85
94
|
grant_type: "authorization_code",
|
|
86
95
|
code,
|
|
87
96
|
redirect_uri: this.config.redirectUri,
|
|
88
97
|
client_id: this.config.clientId
|
|
89
98
|
});
|
|
90
|
-
if (this.config.usePKCE
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
tokenParams.set("code_verifier", verifier);
|
|
94
|
-
sessionStorage.removeItem("bron_pkce_verifier");
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (this.config.clientSecret) {
|
|
98
|
-
tokenParams.set("client_secret", this.config.clientSecret);
|
|
99
|
-
}
|
|
100
|
-
const response = await fetch(this.config.tokenUrl, {
|
|
101
|
-
method: "POST",
|
|
102
|
-
headers: {
|
|
103
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
104
|
-
},
|
|
105
|
-
body: tokenParams.toString()
|
|
106
|
-
});
|
|
107
|
-
if (!response.ok) {
|
|
108
|
-
const error2 = await response.text();
|
|
109
|
-
throw new Error(`Token exchange failed: ${error2}`);
|
|
110
|
-
}
|
|
111
|
-
const tokenData = await response.json();
|
|
112
|
-
const tokens = {
|
|
113
|
-
accessToken: tokenData.access_token,
|
|
114
|
-
refreshToken: tokenData.refresh_token,
|
|
115
|
-
expiresAt: Date.now() + tokenData.expires_in * 1e3,
|
|
116
|
-
tokenType: tokenData.token_type || "Bearer"
|
|
117
|
-
};
|
|
118
|
-
this.tokens = tokens;
|
|
119
|
-
if (this.storage) {
|
|
120
|
-
await this.storage.set("bron_tokens", JSON.stringify(tokens));
|
|
99
|
+
if (this.config.usePKCE) {
|
|
100
|
+
if (!pending.verifier) throw new Error("Missing PKCE verifier");
|
|
101
|
+
tokenParams.set("code_verifier", pending.verifier);
|
|
121
102
|
}
|
|
122
|
-
if (
|
|
123
|
-
|
|
103
|
+
if (this.config.clientSecret) tokenParams.set("client_secret", this.config.clientSecret);
|
|
104
|
+
const epoch = this.epoch;
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
this.exchange = controller;
|
|
107
|
+
const timer = setTimeout(() => controller.abort(), 15e3);
|
|
108
|
+
try {
|
|
109
|
+
const response = await fetch(this.config.tokenUrl, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
112
|
+
body: tokenParams.toString(),
|
|
113
|
+
credentials: "omit",
|
|
114
|
+
cache: "no-store",
|
|
115
|
+
redirect: "error",
|
|
116
|
+
referrerPolicy: "no-referrer",
|
|
117
|
+
signal: controller.signal
|
|
118
|
+
});
|
|
119
|
+
if (!response.ok) throw new Error("OAuth token exchange failed");
|
|
120
|
+
const raw = await response.text();
|
|
121
|
+
if (raw.length > 256e3) throw new Error("OAuth token response is too large");
|
|
122
|
+
const data = JSON.parse(raw);
|
|
123
|
+
if (!data || typeof data.access_token !== "string" || !data.access_token || data.access_token.length > 64e3 || /[\s\p{Cc}]/u.test(data.access_token) || typeof data.token_type !== "string" || data.token_type.toLowerCase() !== "bearer" || typeof data.expires_in !== "number" || !Number.isFinite(data.expires_in) || data.expires_in <= 0 || data.expires_in > 31536e3 || data.refresh_token !== void 0 && (typeof data.refresh_token !== "string" || !data.refresh_token || /\p{Cc}/u.test(data.refresh_token))) {
|
|
124
|
+
throw new Error("Invalid OAuth token response");
|
|
125
|
+
}
|
|
126
|
+
if (controller.signal.aborted || epoch !== this.epoch) throw new Error("OAuth authorization cancelled");
|
|
127
|
+
const tokens = {
|
|
128
|
+
accessToken: data.access_token,
|
|
129
|
+
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
130
|
+
tokenType: "Bearer",
|
|
131
|
+
...data.refresh_token ? { refreshToken: data.refresh_token } : {}
|
|
132
|
+
};
|
|
133
|
+
this.tokens = tokens;
|
|
134
|
+
if (this.storage) await this.persist(async () => {
|
|
135
|
+
if (epoch === this.epoch) await this.storage.set("bron_tokens", JSON.stringify(tokens));
|
|
136
|
+
});
|
|
137
|
+
if (epoch !== this.epoch) throw new Error("OAuth authorization cancelled");
|
|
138
|
+
return tokens;
|
|
139
|
+
} finally {
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
if (this.exchange === controller) this.exchange = null;
|
|
124
142
|
}
|
|
125
|
-
|
|
143
|
+
}
|
|
144
|
+
/** Clear authorization state and abort only the in-flight token exchange. */
|
|
145
|
+
cancelAuthorization() {
|
|
146
|
+
this.authorization.abort();
|
|
147
|
+
this.epoch++;
|
|
148
|
+
this.pending = null;
|
|
149
|
+
this.exchange?.abort();
|
|
150
|
+
this.exchange = null;
|
|
151
|
+
}
|
|
152
|
+
getAuthorizationSignal() {
|
|
153
|
+
return this.authorization.signal;
|
|
126
154
|
}
|
|
127
155
|
/**
|
|
128
156
|
* Get current access token
|
|
129
157
|
*/
|
|
130
158
|
async getAccessToken() {
|
|
159
|
+
const epoch = this.epoch;
|
|
131
160
|
if (this.tokens && Date.now() < this.tokens.expiresAt) {
|
|
132
161
|
return this.tokens.accessToken;
|
|
133
162
|
}
|
|
134
163
|
if (this.storage) {
|
|
135
164
|
const stored = await this.storage.get("bron_tokens");
|
|
165
|
+
if (epoch !== this.epoch) return null;
|
|
136
166
|
if (stored) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
167
|
+
try {
|
|
168
|
+
const tokens = JSON.parse(stored);
|
|
169
|
+
if (typeof tokens.accessToken === "string" && tokens.accessToken && !/[\s\p{Cc}]/u.test(tokens.accessToken) && tokens.tokenType === "Bearer" && Number.isFinite(tokens.expiresAt) && Date.now() < tokens.expiresAt) {
|
|
170
|
+
this.tokens = tokens;
|
|
171
|
+
return tokens.accessToken;
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
140
174
|
}
|
|
141
175
|
}
|
|
142
176
|
}
|
|
@@ -146,9 +180,10 @@ var BronAuthClient = class {
|
|
|
146
180
|
* Logout
|
|
147
181
|
*/
|
|
148
182
|
async logout() {
|
|
183
|
+
this.cancelAuthorization();
|
|
149
184
|
this.tokens = null;
|
|
150
185
|
if (this.storage) {
|
|
151
|
-
await this.storage.remove("bron_tokens");
|
|
186
|
+
await this.persist(() => this.storage.remove("bron_tokens"));
|
|
152
187
|
}
|
|
153
188
|
}
|
|
154
189
|
};
|
|
@@ -270,6 +305,61 @@ var BronApiClient = class {
|
|
|
270
305
|
throw new Error("Polling timeout");
|
|
271
306
|
}
|
|
272
307
|
};
|
|
308
|
+
var CALLBACK = "partylayer:bron:authorization-callback";
|
|
309
|
+
function completeBronAuthorization(redirectUri) {
|
|
310
|
+
if (typeof window === "undefined" || !window.opener) throw new Error("Open this OAuth callback from the connecting application");
|
|
311
|
+
const expected = new URL(redirectUri);
|
|
312
|
+
if (window.location.origin !== expected.origin || window.location.pathname !== expected.pathname) throw new Error("Unexpected OAuth redirect page");
|
|
313
|
+
const callbackUrl = window.location.href;
|
|
314
|
+
window.opener.postMessage({ type: CALLBACK, callbackUrl }, expected.origin);
|
|
315
|
+
window.history.replaceState(null, "", expected.pathname);
|
|
316
|
+
window.close();
|
|
317
|
+
}
|
|
318
|
+
async function authorizeBronPopup(auth, redirectUri, timeoutMs) {
|
|
319
|
+
if (typeof window === "undefined") throw new Error("OAuth flow requires a browser environment");
|
|
320
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 3e5) throw new Error("Invalid OAuth timeout");
|
|
321
|
+
const expected = new URL(redirectUri);
|
|
322
|
+
if (expected.origin !== window.location.origin) throw new Error("Register a same-origin OAuth callback page");
|
|
323
|
+
const url = await auth.startAuth();
|
|
324
|
+
const popup = window.open(url, "partylayer-bron-authorization", "width=500,height=650");
|
|
325
|
+
if (!popup) {
|
|
326
|
+
auth.cancelAuthorization();
|
|
327
|
+
throw new Error("Allow the wallet authorization popup and try again");
|
|
328
|
+
}
|
|
329
|
+
return new Promise((resolve, reject) => {
|
|
330
|
+
const signal = auth.getAuthorizationSignal();
|
|
331
|
+
let callbackReceived = false;
|
|
332
|
+
let finished = false;
|
|
333
|
+
const finish = (error) => {
|
|
334
|
+
if (finished) return;
|
|
335
|
+
finished = true;
|
|
336
|
+
clearTimeout(timer);
|
|
337
|
+
clearInterval(closed);
|
|
338
|
+
window.removeEventListener("message", receive);
|
|
339
|
+
signal.removeEventListener("abort", cancel);
|
|
340
|
+
if (error) auth.cancelAuthorization();
|
|
341
|
+
try {
|
|
342
|
+
popup.close();
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
if (error) reject(error);
|
|
346
|
+
else resolve();
|
|
347
|
+
};
|
|
348
|
+
const receive = (event) => {
|
|
349
|
+
if (finished || callbackReceived || event.source !== popup || event.origin !== expected.origin || !event.data || event.data.type !== CALLBACK || typeof event.data.callbackUrl !== "string") return;
|
|
350
|
+
callbackReceived = true;
|
|
351
|
+
void auth.finishAuth(event.data.callbackUrl).then(() => finish(), (error) => finish(error));
|
|
352
|
+
};
|
|
353
|
+
const cancel = () => finish(new Error("Wallet authorization cancelled"));
|
|
354
|
+
const timer = setTimeout(() => finish(new Error("Wallet authorization timed out")), timeoutMs);
|
|
355
|
+
const closed = setInterval(() => {
|
|
356
|
+
if (!callbackReceived && popup.closed) finish(new core.UserRejectedError("Wallet authorization was closed"));
|
|
357
|
+
}, 250);
|
|
358
|
+
window.addEventListener("message", receive);
|
|
359
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
360
|
+
if (signal.aborted) cancel();
|
|
361
|
+
});
|
|
362
|
+
}
|
|
273
363
|
|
|
274
364
|
// src/bron-adapter.ts
|
|
275
365
|
var BronAdapter = class {
|
|
@@ -277,8 +367,11 @@ var BronAdapter = class {
|
|
|
277
367
|
name = "Bron";
|
|
278
368
|
authClient;
|
|
279
369
|
apiClient;
|
|
370
|
+
redirectUri;
|
|
371
|
+
connecting = false;
|
|
280
372
|
constructor(config) {
|
|
281
373
|
this.authClient = new BronAuthClient(config.auth);
|
|
374
|
+
this.redirectUri = config.auth.redirectUri;
|
|
282
375
|
this.apiClient = new BronApiClient({
|
|
283
376
|
baseUrl: config.api.baseUrl,
|
|
284
377
|
getAccessToken: async () => {
|
|
@@ -308,23 +401,13 @@ var BronAdapter = class {
|
|
|
308
401
|
reason: "Bron is a remote signer service"
|
|
309
402
|
});
|
|
310
403
|
}
|
|
311
|
-
async connect(ctx,
|
|
404
|
+
async connect(ctx, opts) {
|
|
405
|
+
if (this.connecting) throw new Error("A Bron connection is already pending");
|
|
406
|
+
this.connecting = true;
|
|
312
407
|
try {
|
|
313
408
|
const accessToken = await this.authClient.getAccessToken();
|
|
314
409
|
if (!accessToken) {
|
|
315
|
-
|
|
316
|
-
throw new Error("OAuth flow requires browser environment");
|
|
317
|
-
}
|
|
318
|
-
const authUrl = await this.authClient.startAuth();
|
|
319
|
-
const popup = window.open(
|
|
320
|
-
authUrl,
|
|
321
|
-
"Bron Auth",
|
|
322
|
-
"width=500,height=600"
|
|
323
|
-
);
|
|
324
|
-
if (!popup) {
|
|
325
|
-
throw new Error("Failed to open auth popup");
|
|
326
|
-
}
|
|
327
|
-
throw new Error("OAuth callback not implemented in adapter - handle in app");
|
|
410
|
+
await authorizeBronPopup(this.authClient, this.redirectUri, opts?.timeoutMs ?? 12e4);
|
|
328
411
|
}
|
|
329
412
|
const session = await this.apiClient.createSession();
|
|
330
413
|
return {
|
|
@@ -350,6 +433,8 @@ var BronAdapter = class {
|
|
|
350
433
|
phase: "connect",
|
|
351
434
|
transport: "remote"
|
|
352
435
|
});
|
|
436
|
+
} finally {
|
|
437
|
+
this.connecting = false;
|
|
353
438
|
}
|
|
354
439
|
}
|
|
355
440
|
async disconnect(_ctx, _session) {
|
|
@@ -497,5 +582,6 @@ var BronAdapter = class {
|
|
|
497
582
|
};
|
|
498
583
|
|
|
499
584
|
exports.BronAdapter = BronAdapter;
|
|
585
|
+
exports.completeBronAuthorization = completeBronAuthorization;
|
|
500
586
|
//# sourceMappingURL=index.js.map
|
|
501
587
|
//# sourceMappingURL=index.js.map
|