@enbox/browser 0.3.52 → 0.3.54
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/dist/browser.mjs +14 -14
- package/dist/browser.mjs.map +3 -3
- package/dist/esm/browser-connect-handler.js +2 -0
- package/dist/esm/browser-connect-handler.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/relay-connect-runner.js +13 -7
- package/dist/esm/relay-connect-runner.js.map +1 -1
- package/dist/esm/ui/connect-modal.js +34 -7
- package/dist/esm/ui/connect-modal.js.map +1 -1
- package/dist/types/browser-connect-handler.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/relay-connect-runner.d.ts +6 -1
- package/dist/types/relay-connect-runner.d.ts.map +1 -1
- package/dist/types/ui/connect-modal.d.ts +8 -1
- package/dist/types/ui/connect-modal.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/browser-connect-handler.ts +8 -3
- package/src/index.ts +13 -1
- package/src/relay-connect-runner.ts +23 -7
- package/src/ui/connect-modal.ts +61 -22
package/src/index.ts
CHANGED
|
@@ -49,6 +49,11 @@ export type {
|
|
|
49
49
|
export {
|
|
50
50
|
AuthManager,
|
|
51
51
|
AuthSession,
|
|
52
|
+
SESSION_EXPIRED_ERROR_CODE,
|
|
53
|
+
SESSION_REVOKED_ERROR_CODE,
|
|
54
|
+
computeConnectionStatus,
|
|
55
|
+
isSessionExpiredError,
|
|
56
|
+
isSessionInvalidError,
|
|
52
57
|
normalizeProtocolRequests,
|
|
53
58
|
WalletConnect,
|
|
54
59
|
} from '@enbox/auth/browser';
|
|
@@ -58,16 +63,23 @@ export type {
|
|
|
58
63
|
AuthEventHandler,
|
|
59
64
|
AuthManagerOptions,
|
|
60
65
|
AuthState,
|
|
66
|
+
ComputeConnectionStatusOptions,
|
|
67
|
+
ConnectionMonitorOptions,
|
|
68
|
+
ConnectionState,
|
|
69
|
+
ConnectionStatus,
|
|
70
|
+
ConnectionStatusGrant,
|
|
61
71
|
ConnectHandler,
|
|
62
72
|
ConnectOptions,
|
|
63
73
|
ConnectResult,
|
|
64
74
|
DisconnectOptions,
|
|
75
|
+
GetConnectionStatusOptions,
|
|
65
76
|
HandlerConnectOptions,
|
|
66
77
|
ImportFromPortableOptions,
|
|
67
78
|
VaultConnectOptions,
|
|
68
79
|
Permission,
|
|
69
80
|
PortableIdentity,
|
|
70
81
|
ProtocolRequest,
|
|
82
|
+
RefreshOptions,
|
|
71
83
|
RegistrationOptions,
|
|
72
84
|
StorageAdapter,
|
|
73
85
|
SyncOption,
|
|
@@ -80,7 +92,7 @@ export type {
|
|
|
80
92
|
// transports: opening/sealing envelopes and the deny token.
|
|
81
93
|
|
|
82
94
|
export { CONNECT_DENIED_TOKEN, ConnectProvider } from '@enbox/connect';
|
|
83
|
-
export type { ConnectApproval, ConnectPermissionRequest, ConnectRequest } from '@enbox/connect';
|
|
95
|
+
export type { ConnectApproval, ConnectPermissionRequest, ConnectRequest, ConnectRequestType } from '@enbox/connect';
|
|
84
96
|
|
|
85
97
|
// ─── Browser-specific exports ───────────────────────────────────
|
|
86
98
|
//
|
|
@@ -20,10 +20,12 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import type { Jwk } from '@enbox/crypto';
|
|
23
|
+
import type { PortableDid } from '@enbox/dids';
|
|
23
24
|
import type {
|
|
24
25
|
ConnectClientMetadata,
|
|
25
26
|
ConnectPermissionRequest,
|
|
26
27
|
ConnectRequest,
|
|
28
|
+
ConnectRequestType,
|
|
27
29
|
ConnectResult,
|
|
28
30
|
WalletUriHandoff,
|
|
29
31
|
} from '@enbox/connect';
|
|
@@ -35,6 +37,7 @@ import {
|
|
|
35
37
|
openResponse,
|
|
36
38
|
randomToken,
|
|
37
39
|
RelayClientTransport,
|
|
40
|
+
resolveDelegatePortableDid,
|
|
38
41
|
sealRequest,
|
|
39
42
|
} from '@enbox/connect';
|
|
40
43
|
|
|
@@ -69,6 +72,12 @@ export interface RelayConnectOptions {
|
|
|
69
72
|
/** DWN protocols and permission scopes being requested. */
|
|
70
73
|
permissionRequests: ConnectPermissionRequest[];
|
|
71
74
|
|
|
75
|
+
/** Existing delegate credentials reused by a refresh request. */
|
|
76
|
+
delegatePortableDid?: PortableDid;
|
|
77
|
+
|
|
78
|
+
/** User-facing request purpose. Absent means a normal connect. */
|
|
79
|
+
requestType?: ConnectRequestType;
|
|
80
|
+
|
|
72
81
|
/** Total milliseconds to poll the relay for a wallet response. */
|
|
73
82
|
timeoutMs?: number;
|
|
74
83
|
|
|
@@ -147,6 +156,10 @@ export function visibilityAwareSleep(ms: number): Promise<void> {
|
|
|
147
156
|
* delegated credentials, or `undefined` when the user denied in the wallet.
|
|
148
157
|
*/
|
|
149
158
|
export async function runRelayConnect(options: RelayConnectOptions): Promise<ConnectResult | undefined> {
|
|
159
|
+
if (options.requestType === 'refresh' && options.delegatePortableDid === undefined) {
|
|
160
|
+
throw new Error('Connect: refresh requests require an existing `delegatePortableDid`.');
|
|
161
|
+
}
|
|
162
|
+
|
|
150
163
|
const createTransport = options.createTransport
|
|
151
164
|
?? ((transportOptions): RelayClientTransport => new RelayClientTransport({
|
|
152
165
|
...transportOptions,
|
|
@@ -179,6 +192,8 @@ export async function runRelayConnect(options: RelayConnectOptions): Promise<Con
|
|
|
179
192
|
appIcon : options.appIcon,
|
|
180
193
|
clientMetadata : options.clientMetadata,
|
|
181
194
|
permissionRequests : options.permissionRequests,
|
|
195
|
+
delegateDid : options.delegatePortableDid?.uri,
|
|
196
|
+
requestType : options.requestType,
|
|
182
197
|
supportedDidMethods : ['did:dht', 'did:jwk'],
|
|
183
198
|
nonce,
|
|
184
199
|
state,
|
|
@@ -220,9 +235,10 @@ export async function runRelayConnect(options: RelayConnectOptions): Promise<Con
|
|
|
220
235
|
pin,
|
|
221
236
|
});
|
|
222
237
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
238
|
+
const delegatePortableDid = resolveDelegatePortableDid({
|
|
239
|
+
localDelegatePortableDid: options.delegatePortableDid,
|
|
240
|
+
response,
|
|
241
|
+
});
|
|
226
242
|
|
|
227
243
|
// Best-effort completion signal so the wallet can flip its pairing
|
|
228
244
|
// screen to a confirmed "connected" state instead of leaving the user
|
|
@@ -230,10 +246,10 @@ export async function runRelayConnect(options: RelayConnectOptions): Promise<Con
|
|
|
230
246
|
void transport.confirmComplete?.().catch((): undefined => undefined);
|
|
231
247
|
|
|
232
248
|
return {
|
|
233
|
-
delegatePortableDid
|
|
234
|
-
delegateGrants
|
|
235
|
-
connectedDid
|
|
236
|
-
sessionRevocations
|
|
249
|
+
delegatePortableDid,
|
|
250
|
+
delegateGrants : response.delegateGrants,
|
|
251
|
+
connectedDid : response.providerDid,
|
|
252
|
+
sessionRevocations : response.sessionRevocations,
|
|
237
253
|
};
|
|
238
254
|
} catch (error) {
|
|
239
255
|
const failure = error instanceof Error ? error : new Error(String(error));
|
package/src/ui/connect-modal.ts
CHANGED
|
@@ -37,8 +37,9 @@
|
|
|
37
37
|
* @module
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
+
import type { PortableDid } from '@enbox/dids';
|
|
40
41
|
import type { WalletOption } from '../browser-connect-handler.js';
|
|
41
|
-
import type { ConnectPermissionRequest, ConnectResult, WalletUriHandoff } from '@enbox/connect';
|
|
42
|
+
import type { ConnectPermissionRequest, ConnectRequestType, ConnectResult, WalletUriHandoff } from '@enbox/connect';
|
|
42
43
|
|
|
43
44
|
import { ConnectClient } from '@enbox/connect';
|
|
44
45
|
|
|
@@ -157,6 +158,12 @@ export interface ConnectModalOptions {
|
|
|
157
158
|
*/
|
|
158
159
|
walletUrl?: string;
|
|
159
160
|
|
|
161
|
+
/** User-facing mode. Absent means a normal connect. */
|
|
162
|
+
mode?: ConnectRequestType;
|
|
163
|
+
|
|
164
|
+
/** Existing delegate credentials reused by a refresh request. */
|
|
165
|
+
delegatePortableDid?: PortableDid;
|
|
166
|
+
|
|
160
167
|
/** Preferred method on open; overrides the remembered choice. */
|
|
161
168
|
preferredMethod?: ConnectMethod;
|
|
162
169
|
|
|
@@ -215,6 +222,8 @@ export interface ConnectModalDeps {
|
|
|
215
222
|
appName: string;
|
|
216
223
|
appIcon?: string;
|
|
217
224
|
timeoutMs?: number;
|
|
225
|
+
delegatePortableDid?: PortableDid;
|
|
226
|
+
requestType?: ConnectRequestType;
|
|
218
227
|
}) => Promise<ConnectResult | undefined>;
|
|
219
228
|
|
|
220
229
|
/** Resolves a wallet origin to its relay `connectServerUrl` (or undefined). */
|
|
@@ -246,6 +255,8 @@ async function runPopupConnect(options: {
|
|
|
246
255
|
appName: string;
|
|
247
256
|
appIcon?: string;
|
|
248
257
|
timeoutMs?: number;
|
|
258
|
+
delegatePortableDid?: PortableDid;
|
|
259
|
+
requestType?: ConnectRequestType;
|
|
249
260
|
}): Promise<ConnectResult | undefined> {
|
|
250
261
|
// The transport constructor calls `window.open` synchronously — callers
|
|
251
262
|
// must invoke this inside the user-gesture call stack.
|
|
@@ -256,10 +267,12 @@ async function runPopupConnect(options: {
|
|
|
256
267
|
const client = new ConnectClient({ transport });
|
|
257
268
|
|
|
258
269
|
return await client.connect({
|
|
259
|
-
appName
|
|
260
|
-
appIcon
|
|
261
|
-
clientMetadata
|
|
262
|
-
permissionRequests
|
|
270
|
+
appName : options.appName,
|
|
271
|
+
appIcon : options.appIcon,
|
|
272
|
+
clientMetadata : collectBrowserClientMetadata(),
|
|
273
|
+
permissionRequests : options.permissionRequests,
|
|
274
|
+
delegatePortableDid : options.delegatePortableDid,
|
|
275
|
+
requestType : options.requestType,
|
|
263
276
|
});
|
|
264
277
|
}
|
|
265
278
|
|
|
@@ -295,7 +308,7 @@ function readLastChoice(storage: ConnectModalDeps['storage']): LastChoice | unde
|
|
|
295
308
|
return undefined;
|
|
296
309
|
}
|
|
297
310
|
const parsed = JSON.parse(raw) as Partial<LastChoice>;
|
|
298
|
-
if ((parsed.method === 'phone' || parsed.method === 'browser') &&
|
|
311
|
+
if ((parsed.method === 'phone' || parsed.method === 'browser') && isValidRememberedWalletUrl(parsed.walletUrl)) {
|
|
299
312
|
return { method: parsed.method, walletUrl: parsed.walletUrl };
|
|
300
313
|
}
|
|
301
314
|
} catch { /* unreadable storage — behave as first visit */ }
|
|
@@ -345,6 +358,9 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
345
358
|
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
346
359
|
throw new Error('[@enbox/browser] The connect modal is only available in browser environments.');
|
|
347
360
|
}
|
|
361
|
+
if (options.mode === 'refresh' && options.delegatePortableDid === undefined) {
|
|
362
|
+
throw new Error('Connect: refresh requests require an existing `delegatePortableDid`.');
|
|
363
|
+
}
|
|
348
364
|
|
|
349
365
|
const deps: ConnectModalDeps = { ...defaultDeps(), ...options.deps };
|
|
350
366
|
const wallets = dedupeWalletsByUrl(options.wallets ?? []);
|
|
@@ -352,13 +368,18 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
352
368
|
const appName = options.appName ?? window.location.host;
|
|
353
369
|
const relayWalletPath = options.relayWalletPath ?? '/connect/app';
|
|
354
370
|
const isMobile = deps.isMobile();
|
|
371
|
+
const refreshing = options.mode === 'refresh';
|
|
355
372
|
|
|
356
373
|
const lastChoice = rememberChoice ? readLastChoice(deps.storage) : undefined;
|
|
357
374
|
|
|
358
375
|
// Wallet resolution order: explicit option → remembered → catalog head.
|
|
359
|
-
const
|
|
376
|
+
const rememberedWalletUrl = lastChoice !== undefined
|
|
377
|
+
&& (refreshing || walletInCatalog(wallets, lastChoice.walletUrl))
|
|
378
|
+
? lastChoice.walletUrl
|
|
379
|
+
: undefined;
|
|
380
|
+
const lockedWallet = options.walletUrl !== undefined || (refreshing && rememberedWalletUrl !== undefined);
|
|
360
381
|
let walletUrl = options.walletUrl
|
|
361
|
-
??
|
|
382
|
+
?? rememberedWalletUrl
|
|
362
383
|
?? wallets[0]?.url;
|
|
363
384
|
|
|
364
385
|
// Method resolution order: explicit option → remembered → phone.
|
|
@@ -380,7 +401,8 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
380
401
|
modal.className = 'modal';
|
|
381
402
|
modal.setAttribute('role', 'dialog');
|
|
382
403
|
modal.setAttribute('aria-modal', 'true');
|
|
383
|
-
|
|
404
|
+
const title = refreshing ? `Reconnect to ${appName}` : `Connect to ${appName}`;
|
|
405
|
+
modal.setAttribute('aria-label', title);
|
|
384
406
|
|
|
385
407
|
// ── Theme ──────────────────────────────────────────────────
|
|
386
408
|
// System light/dark is pure CSS (`prefers-color-scheme`), so it tracks
|
|
@@ -489,7 +511,7 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
489
511
|
const cancelModal = (): void => settle(() => reject(new Error('[@enbox/browser] Connect cancelled.')));
|
|
490
512
|
|
|
491
513
|
// ── Zones ──────────────────────────────────────────────────
|
|
492
|
-
modal.appendChild(buildHeader(
|
|
514
|
+
modal.appendChild(buildHeader(title, options.appIcon, cancelModal));
|
|
493
515
|
|
|
494
516
|
const stage = document.createElement('div');
|
|
495
517
|
stage.className = 'stage';
|
|
@@ -839,14 +861,16 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
839
861
|
try {
|
|
840
862
|
const result = await deps.runRelay({
|
|
841
863
|
connectServerUrl,
|
|
842
|
-
walletUri
|
|
864
|
+
walletUri : new URL(relayWalletPath, origin).toString(),
|
|
843
865
|
appName,
|
|
844
|
-
appIcon
|
|
845
|
-
clientMetadata
|
|
846
|
-
permissionRequests
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
866
|
+
appIcon : options.appIcon,
|
|
867
|
+
clientMetadata : collectBrowserClientMetadata(),
|
|
868
|
+
permissionRequests : options.permissionRequests,
|
|
869
|
+
delegatePortableDid : options.delegatePortableDid,
|
|
870
|
+
requestType : options.mode,
|
|
871
|
+
timeoutMs : options.timeout,
|
|
872
|
+
cancelled : session.cancelled,
|
|
873
|
+
onWalletUriReady : (handoff): void => {
|
|
850
874
|
if (session.active && !settled) {
|
|
851
875
|
renderQr(handoff);
|
|
852
876
|
scheduleRemint(handoff.expiresIn);
|
|
@@ -907,10 +931,12 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
907
931
|
try {
|
|
908
932
|
deps.runPopup({
|
|
909
933
|
walletUrl,
|
|
910
|
-
permissionRequests
|
|
934
|
+
permissionRequests : options.permissionRequests,
|
|
911
935
|
appName,
|
|
912
|
-
appIcon
|
|
913
|
-
timeoutMs
|
|
936
|
+
appIcon : options.appIcon,
|
|
937
|
+
timeoutMs : options.timeout,
|
|
938
|
+
delegatePortableDid : options.delegatePortableDid,
|
|
939
|
+
requestType : options.mode,
|
|
914
940
|
})
|
|
915
941
|
.then((result) => {
|
|
916
942
|
if (settled) { return; }
|
|
@@ -1226,6 +1252,19 @@ function walletInCatalog(wallets: WalletOption[], url: string): boolean {
|
|
|
1226
1252
|
return wallets.some((wallet) => wallet.url === url);
|
|
1227
1253
|
}
|
|
1228
1254
|
|
|
1255
|
+
function isValidRememberedWalletUrl(value: unknown): value is string {
|
|
1256
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
1257
|
+
return false;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
try {
|
|
1261
|
+
const url = new URL(value);
|
|
1262
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
1263
|
+
} catch {
|
|
1264
|
+
return false;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1229
1268
|
/** Drops catalog entries that repeat an earlier wallet's URL. */
|
|
1230
1269
|
function dedupeWalletsByUrl(wallets: WalletOption[]): WalletOption[] {
|
|
1231
1270
|
return wallets.filter((wallet, index) => wallets.findIndex((candidate) => candidate.url === wallet.url) === index);
|
|
@@ -1268,7 +1307,7 @@ function stageLinkButton(label: string, onClick: () => void): HTMLButtonElement
|
|
|
1268
1307
|
return button;
|
|
1269
1308
|
}
|
|
1270
1309
|
|
|
1271
|
-
function buildHeader(
|
|
1310
|
+
function buildHeader(titleText: string, appIcon: string | undefined, onCancel: () => void): HTMLDivElement {
|
|
1272
1311
|
const header = document.createElement('div');
|
|
1273
1312
|
header.className = 'header';
|
|
1274
1313
|
|
|
@@ -1287,7 +1326,7 @@ function buildHeader(appName: string, appIcon: string | undefined, onCancel: ()
|
|
|
1287
1326
|
}
|
|
1288
1327
|
|
|
1289
1328
|
const title = document.createElement('h2');
|
|
1290
|
-
title.textContent =
|
|
1329
|
+
title.textContent = titleText;
|
|
1291
1330
|
identity.appendChild(title);
|
|
1292
1331
|
|
|
1293
1332
|
const closeBtn = document.createElement('button');
|