@enbox/browser 0.3.71 → 0.3.72
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 +3 -1
- package/dist/esm/browser-connect-handler.js.map +1 -1
- package/dist/esm/relay-connect-runner.js +9 -1
- package/dist/esm/relay-connect-runner.js.map +1 -1
- package/dist/esm/ui/connect-modal.js +134 -16
- package/dist/esm/ui/connect-modal.js.map +1 -1
- package/dist/types/browser-connect-handler.d.ts +8 -2
- package/dist/types/browser-connect-handler.d.ts.map +1 -1
- package/dist/types/relay-connect-runner.d.ts +4 -0
- package/dist/types/relay-connect-runner.d.ts.map +1 -1
- package/dist/types/ui/connect-modal.d.ts +24 -11
- package/dist/types/ui/connect-modal.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/browser-connect-handler.ts +13 -2
- package/src/relay-connect-runner.ts +17 -2
- package/src/ui/connect-modal.ts +187 -28
|
@@ -96,8 +96,9 @@ export interface ConnectModalOptions {
|
|
|
96
96
|
/** Preferred method on open; overrides the remembered choice. */
|
|
97
97
|
preferredMethod?: ConnectMethod;
|
|
98
98
|
/**
|
|
99
|
-
* Remember the successful method + wallet in localStorage
|
|
100
|
-
* the
|
|
99
|
+
* Remember the successful method + wallet in localStorage. Refreshes reuse
|
|
100
|
+
* the route saved for their delegate DID; new connections use the latest
|
|
101
|
+
* successful route as a convenience.
|
|
101
102
|
* @default true
|
|
102
103
|
*/
|
|
103
104
|
rememberChoice?: boolean;
|
|
@@ -105,6 +106,13 @@ export interface ConnectModalOptions {
|
|
|
105
106
|
appName?: string;
|
|
106
107
|
/** Icon URL of the requesting application. */
|
|
107
108
|
appIcon?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Stable application identifier hint available to the wallet during approval.
|
|
111
|
+
* Defaults to the browser's canonical origin.
|
|
112
|
+
*/
|
|
113
|
+
applicationId?: string;
|
|
114
|
+
/** Wallet profile DID that a refresh must renew. */
|
|
115
|
+
expectedProviderDid?: string;
|
|
108
116
|
/**
|
|
109
117
|
* Relay base URL override for the phone path. When omitted, each wallet's
|
|
110
118
|
* `connectServerUrl` is discovered from its `/.well-known/enbox-connect`
|
|
@@ -134,15 +142,7 @@ export interface ConnectModalDeps {
|
|
|
134
142
|
/** Runs one relay handshake (phone path). */
|
|
135
143
|
runRelay: typeof runRelayConnect;
|
|
136
144
|
/** Runs one popup handshake (browser path); must open the popup synchronously. */
|
|
137
|
-
runPopup:
|
|
138
|
-
walletUrl: string;
|
|
139
|
-
permissionRequests: ConnectPermissionRequest[];
|
|
140
|
-
appName: string;
|
|
141
|
-
appIcon?: string;
|
|
142
|
-
timeoutMs?: number;
|
|
143
|
-
delegatePortableDid?: PortableDid;
|
|
144
|
-
requestType?: ConnectRequestType;
|
|
145
|
-
}) => Promise<ConnectResult | undefined>;
|
|
145
|
+
runPopup: typeof runPopupConnect;
|
|
146
146
|
/** Resolves a wallet origin to its relay `connectServerUrl` (or undefined). */
|
|
147
147
|
discoverConnectServerUrl: (walletOrigin: string) => Promise<string | undefined>;
|
|
148
148
|
/** Validates a pasted custom wallet URL. */
|
|
@@ -158,6 +158,18 @@ export interface ConnectModalDeps {
|
|
|
158
158
|
* same machinery the wallet selector uses for custom-URL validation).
|
|
159
159
|
*/
|
|
160
160
|
export declare function discoverWalletConnectServerUrl(walletOrigin: string): Promise<string | undefined>;
|
|
161
|
+
/** Runs one popup handshake without collapsing early-close into a denial. */
|
|
162
|
+
declare function runPopupConnect(options: {
|
|
163
|
+
walletUrl: string;
|
|
164
|
+
permissionRequests: ConnectPermissionRequest[];
|
|
165
|
+
appName: string;
|
|
166
|
+
appIcon?: string;
|
|
167
|
+
applicationId?: string;
|
|
168
|
+
timeoutMs?: number;
|
|
169
|
+
delegatePortableDid?: PortableDid;
|
|
170
|
+
requestType?: ConnectRequestType;
|
|
171
|
+
expectedProviderDid?: string;
|
|
172
|
+
}): Promise<ConnectResult | undefined>;
|
|
161
173
|
/**
|
|
162
174
|
* Opens the connect modal and resolves when the session ends.
|
|
163
175
|
*
|
|
@@ -167,4 +179,5 @@ export declare function discoverWalletConnectServerUrl(walletOrigin: string): Pr
|
|
|
167
179
|
* user dismisses the error.
|
|
168
180
|
*/
|
|
169
181
|
export declare function runConnectModal(options: ConnectModalOptions): Promise<ConnectResult | undefined>;
|
|
182
|
+
export {};
|
|
170
183
|
//# sourceMappingURL=connect-modal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect-modal.d.ts","sourceRoot":"","sources":["../../../src/ui/connect-modal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,aAAa,EAAoB,MAAM,gBAAgB,CAAC;AAYpH,OAAO,EAA8B,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAEzF,6CAA6C;AAC7C,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"connect-modal.d.ts","sourceRoot":"","sources":["../../../src/ui/connect-modal.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enbox/browser",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.72",
|
|
4
4
|
"description": "Enbox tools and features to use in the browser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/esm/index.js",
|
|
@@ -66,15 +66,15 @@
|
|
|
66
66
|
"access": "public"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@enbox/agent": "0.8.
|
|
70
|
-
"@enbox/api": "0.6.
|
|
71
|
-
"@enbox/auth": "0.6.
|
|
69
|
+
"@enbox/agent": "0.8.41",
|
|
70
|
+
"@enbox/api": "0.6.80",
|
|
71
|
+
"@enbox/auth": "0.6.87",
|
|
72
72
|
"@enbox/common": "0.1.6",
|
|
73
|
-
"@enbox/connect": "0.1.
|
|
73
|
+
"@enbox/connect": "0.1.21",
|
|
74
74
|
"@enbox/crypto": "0.1.9",
|
|
75
75
|
"@enbox/dids": "0.1.10",
|
|
76
|
-
"@enbox/dwn-sdk-js": "0.4.
|
|
77
|
-
"@enbox/protocols": "0.2.
|
|
76
|
+
"@enbox/dwn-sdk-js": "0.4.25",
|
|
77
|
+
"@enbox/protocols": "0.2.107"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@types/qrcode": "^1.5.6",
|
|
@@ -107,6 +107,12 @@ export interface BrowserConnectHandlerOptions {
|
|
|
107
107
|
*/
|
|
108
108
|
appIcon?: string;
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Stable application identifier hint available to the wallet during approval.
|
|
112
|
+
* Defaults to the browser's canonical origin.
|
|
113
|
+
*/
|
|
114
|
+
applicationId?: string;
|
|
115
|
+
|
|
110
116
|
/**
|
|
111
117
|
* Preferred connect method on open: `'phone'` (QR / deep link via the
|
|
112
118
|
* relay) or `'browser'` (wallet popup on this device). Overrides the
|
|
@@ -116,8 +122,9 @@ export interface BrowserConnectHandlerOptions {
|
|
|
116
122
|
preferredMethod?: ConnectMethod;
|
|
117
123
|
|
|
118
124
|
/**
|
|
119
|
-
* Remember the successful method + wallet in localStorage
|
|
120
|
-
* the
|
|
125
|
+
* Remember the successful method + wallet in localStorage. Refreshes reuse
|
|
126
|
+
* the route saved for their delegate DID; new connections use the latest
|
|
127
|
+
* successful route as a convenience.
|
|
121
128
|
* @default true
|
|
122
129
|
*/
|
|
123
130
|
rememberChoice?: boolean;
|
|
@@ -176,6 +183,7 @@ export function BrowserConnectHandler(
|
|
|
176
183
|
timeout,
|
|
177
184
|
appName,
|
|
178
185
|
appIcon,
|
|
186
|
+
applicationId,
|
|
179
187
|
preferredMethod,
|
|
180
188
|
rememberChoice,
|
|
181
189
|
connectServerUrl,
|
|
@@ -188,6 +196,7 @@ export function BrowserConnectHandler(
|
|
|
188
196
|
permissionRequests: ConnectPermissionRequest[];
|
|
189
197
|
delegatePortableDid?: PortableDid;
|
|
190
198
|
requestType?: ConnectRequestType;
|
|
199
|
+
expectedProviderDid?: string;
|
|
191
200
|
}): Promise<ConnectResult | undefined> {
|
|
192
201
|
return runConnectModal({
|
|
193
202
|
wallets,
|
|
@@ -196,12 +205,14 @@ export function BrowserConnectHandler(
|
|
|
196
205
|
rememberChoice,
|
|
197
206
|
appName,
|
|
198
207
|
appIcon : appIcon ?? `${window.location.origin}/favicon.ico`,
|
|
208
|
+
applicationId,
|
|
199
209
|
connectServerUrl,
|
|
200
210
|
relayWalletPath,
|
|
201
211
|
timeout,
|
|
202
212
|
theme,
|
|
203
213
|
mode : params.requestType,
|
|
204
214
|
delegatePortableDid : params.delegatePortableDid,
|
|
215
|
+
expectedProviderDid : params.expectedProviderDid,
|
|
205
216
|
permissionRequests : params.permissionRequests,
|
|
206
217
|
});
|
|
207
218
|
},
|
|
@@ -66,6 +66,9 @@ export interface RelayConnectOptions {
|
|
|
66
66
|
/** Optional icon URL for the app, shown in the wallet consent UI. */
|
|
67
67
|
appIcon?: string;
|
|
68
68
|
|
|
69
|
+
/** Stable application identifier hint available to the wallet during approval. */
|
|
70
|
+
applicationId?: string;
|
|
71
|
+
|
|
69
72
|
/** Optional client/environment metadata for wallet session display. */
|
|
70
73
|
clientMetadata?: ConnectClientMetadata;
|
|
71
74
|
|
|
@@ -78,6 +81,9 @@ export interface RelayConnectOptions {
|
|
|
78
81
|
/** User-facing request purpose. Absent means a normal connect. */
|
|
79
82
|
requestType?: ConnectRequestType;
|
|
80
83
|
|
|
84
|
+
/** Wallet profile DID that a refresh must renew. */
|
|
85
|
+
expectedProviderDid?: string;
|
|
86
|
+
|
|
81
87
|
/** Total milliseconds to poll the relay for a wallet response. */
|
|
82
88
|
timeoutMs?: number;
|
|
83
89
|
|
|
@@ -193,10 +199,12 @@ export async function runRelayConnect(options: RelayConnectOptions): Promise<Con
|
|
|
193
199
|
clientDid : clientDid.uri,
|
|
194
200
|
appName : options.appName,
|
|
195
201
|
appIcon : options.appIcon,
|
|
202
|
+
applicationId : options.applicationId,
|
|
196
203
|
clientMetadata : options.clientMetadata,
|
|
197
204
|
permissionRequests : options.permissionRequests,
|
|
198
205
|
delegateDid : options.delegatePortableDid?.uri,
|
|
199
206
|
requestType : options.requestType,
|
|
207
|
+
expectedProviderDid : options.expectedProviderDid,
|
|
200
208
|
supportedDidMethods : ['did:dht', 'did:jwk'],
|
|
201
209
|
nonce,
|
|
202
210
|
state,
|
|
@@ -243,6 +251,7 @@ export async function runRelayConnect(options: RelayConnectOptions): Promise<Con
|
|
|
243
251
|
nonce,
|
|
244
252
|
state,
|
|
245
253
|
pin,
|
|
254
|
+
expectedProviderDid : options.expectedProviderDid,
|
|
246
255
|
delegatePortableDid : options.delegatePortableDid,
|
|
247
256
|
confirmComplete : transport.confirmComplete?.bind(transport),
|
|
248
257
|
});
|
|
@@ -274,6 +283,7 @@ async function attemptPinOpen(params: {
|
|
|
274
283
|
nonce: string;
|
|
275
284
|
state: string;
|
|
276
285
|
pin: string;
|
|
286
|
+
expectedProviderDid: string | undefined;
|
|
277
287
|
delegatePortableDid: PortableDid | undefined;
|
|
278
288
|
confirmComplete: (() => Promise<void>) | undefined;
|
|
279
289
|
}): Promise<PinAttemptOutcome> {
|
|
@@ -281,8 +291,13 @@ async function attemptPinOpen(params: {
|
|
|
281
291
|
const response = await openResponse({
|
|
282
292
|
jwe : params.responseCiphertext,
|
|
283
293
|
recipientPrivateKey : params.responsePrivateKey,
|
|
284
|
-
expected : {
|
|
285
|
-
|
|
294
|
+
expected : {
|
|
295
|
+
clientDid : params.clientDid,
|
|
296
|
+
nonce : params.nonce,
|
|
297
|
+
state : params.state,
|
|
298
|
+
providerDid : params.expectedProviderDid,
|
|
299
|
+
},
|
|
300
|
+
pin: params.pin,
|
|
286
301
|
});
|
|
287
302
|
|
|
288
303
|
const delegatePortableDid = resolveDelegatePortableDid({
|
package/src/ui/connect-modal.ts
CHANGED
|
@@ -59,6 +59,12 @@ export type ConnectMethod = 'phone' | 'browser';
|
|
|
59
59
|
/** localStorage key remembering the last successful method + wallet. */
|
|
60
60
|
const LAST_CHOICE_STORAGE_KEY = 'enbox:connect:lastChoice';
|
|
61
61
|
|
|
62
|
+
/** localStorage key remembering the exact wallet route for each delegate/profile pair. */
|
|
63
|
+
const SESSION_CHOICES_STORAGE_KEY = 'enbox:connect:sessionChoices';
|
|
64
|
+
|
|
65
|
+
/** Bounds local reconnect provenance retained by one dapp origin. */
|
|
66
|
+
const MAX_SESSION_CHOICES = 20;
|
|
67
|
+
|
|
62
68
|
/**
|
|
63
69
|
* Safety margin subtracted from the relay pointer TTL before re-minting.
|
|
64
70
|
*
|
|
@@ -168,8 +174,9 @@ export interface ConnectModalOptions {
|
|
|
168
174
|
preferredMethod?: ConnectMethod;
|
|
169
175
|
|
|
170
176
|
/**
|
|
171
|
-
* Remember the successful method + wallet in localStorage
|
|
172
|
-
* the
|
|
177
|
+
* Remember the successful method + wallet in localStorage. Refreshes reuse
|
|
178
|
+
* the route saved for their delegate DID; new connections use the latest
|
|
179
|
+
* successful route as a convenience.
|
|
173
180
|
* @default true
|
|
174
181
|
*/
|
|
175
182
|
rememberChoice?: boolean;
|
|
@@ -180,6 +187,15 @@ export interface ConnectModalOptions {
|
|
|
180
187
|
/** Icon URL of the requesting application. */
|
|
181
188
|
appIcon?: string;
|
|
182
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Stable application identifier hint available to the wallet during approval.
|
|
192
|
+
* Defaults to the browser's canonical origin.
|
|
193
|
+
*/
|
|
194
|
+
applicationId?: string;
|
|
195
|
+
|
|
196
|
+
/** Wallet profile DID that a refresh must renew. */
|
|
197
|
+
expectedProviderDid?: string;
|
|
198
|
+
|
|
183
199
|
/**
|
|
184
200
|
* Relay base URL override for the phone path. When omitted, each wallet's
|
|
185
201
|
* `connectServerUrl` is discovered from its `/.well-known/enbox-connect`
|
|
@@ -216,15 +232,7 @@ export interface ConnectModalDeps {
|
|
|
216
232
|
runRelay: typeof runRelayConnect;
|
|
217
233
|
|
|
218
234
|
/** Runs one popup handshake (browser path); must open the popup synchronously. */
|
|
219
|
-
runPopup:
|
|
220
|
-
walletUrl: string;
|
|
221
|
-
permissionRequests: ConnectPermissionRequest[];
|
|
222
|
-
appName: string;
|
|
223
|
-
appIcon?: string;
|
|
224
|
-
timeoutMs?: number;
|
|
225
|
-
delegatePortableDid?: PortableDid;
|
|
226
|
-
requestType?: ConnectRequestType;
|
|
227
|
-
}) => Promise<ConnectResult | undefined>;
|
|
235
|
+
runPopup: typeof runPopupConnect;
|
|
228
236
|
|
|
229
237
|
/** Resolves a wallet origin to its relay `connectServerUrl` (or undefined). */
|
|
230
238
|
discoverConnectServerUrl: (walletOrigin: string) => Promise<string | undefined>;
|
|
@@ -254,9 +262,11 @@ async function runPopupConnect(options: {
|
|
|
254
262
|
permissionRequests: ConnectPermissionRequest[];
|
|
255
263
|
appName: string;
|
|
256
264
|
appIcon?: string;
|
|
265
|
+
applicationId?: string;
|
|
257
266
|
timeoutMs?: number;
|
|
258
267
|
delegatePortableDid?: PortableDid;
|
|
259
268
|
requestType?: ConnectRequestType;
|
|
269
|
+
expectedProviderDid?: string;
|
|
260
270
|
}): Promise<ConnectResult | undefined> {
|
|
261
271
|
// The transport constructor calls `window.open` synchronously — callers
|
|
262
272
|
// must invoke this inside the user-gesture call stack.
|
|
@@ -269,10 +279,12 @@ async function runPopupConnect(options: {
|
|
|
269
279
|
return await client.connect({
|
|
270
280
|
appName : options.appName,
|
|
271
281
|
appIcon : options.appIcon,
|
|
282
|
+
applicationId : options.applicationId,
|
|
272
283
|
clientMetadata : collectBrowserClientMetadata(),
|
|
273
284
|
permissionRequests : options.permissionRequests,
|
|
274
285
|
delegatePortableDid : options.delegatePortableDid,
|
|
275
286
|
requestType : options.requestType,
|
|
287
|
+
expectedProviderDid : options.expectedProviderDid,
|
|
276
288
|
});
|
|
277
289
|
}
|
|
278
290
|
|
|
@@ -301,6 +313,12 @@ interface LastChoice {
|
|
|
301
313
|
walletUrl: string;
|
|
302
314
|
}
|
|
303
315
|
|
|
316
|
+
/** Stable reconnect provenance for one delegated app session. */
|
|
317
|
+
interface SessionChoice extends LastChoice {
|
|
318
|
+
delegateDid: string;
|
|
319
|
+
providerDid: string;
|
|
320
|
+
}
|
|
321
|
+
|
|
304
322
|
function readLastChoice(storage: ConnectModalDeps['storage']): LastChoice | undefined {
|
|
305
323
|
try {
|
|
306
324
|
const raw = storage?.getItem(LAST_CHOICE_STORAGE_KEY);
|
|
@@ -321,6 +339,78 @@ function writeLastChoice(storage: ConnectModalDeps['storage'], choice: LastChoic
|
|
|
321
339
|
} catch { /* best effort */ }
|
|
322
340
|
}
|
|
323
341
|
|
|
342
|
+
function isSessionChoice(value: unknown): value is SessionChoice {
|
|
343
|
+
if (typeof value !== 'object' || value === null) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
const choice = value as Partial<SessionChoice>;
|
|
347
|
+
return (choice.method === 'phone' || choice.method === 'browser')
|
|
348
|
+
&& isValidRememberedWalletUrl(choice.walletUrl)
|
|
349
|
+
&& typeof choice.delegateDid === 'string'
|
|
350
|
+
&& choice.delegateDid.length > 0
|
|
351
|
+
&& typeof choice.providerDid === 'string'
|
|
352
|
+
&& choice.providerDid.length > 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function readSessionChoices(storage: ConnectModalDeps['storage']): SessionChoice[] {
|
|
356
|
+
try {
|
|
357
|
+
const raw = storage?.getItem(SESSION_CHOICES_STORAGE_KEY);
|
|
358
|
+
if (raw === null || raw === undefined) {
|
|
359
|
+
return [];
|
|
360
|
+
}
|
|
361
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
362
|
+
return Array.isArray(parsed) ? parsed.filter(isSessionChoice).slice(-MAX_SESSION_CHOICES) : [];
|
|
363
|
+
} catch {
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function readSessionChoice(
|
|
369
|
+
storage: ConnectModalDeps['storage'],
|
|
370
|
+
delegateDid: string | undefined,
|
|
371
|
+
expectedProviderDid: string | undefined,
|
|
372
|
+
): SessionChoice | undefined {
|
|
373
|
+
if (delegateDid === undefined) {
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
376
|
+
const choices = readSessionChoices(storage);
|
|
377
|
+
for (let index = choices.length - 1; index >= 0; index--) {
|
|
378
|
+
const choice = choices[index];
|
|
379
|
+
if (choice.delegateDid === delegateDid
|
|
380
|
+
&& (expectedProviderDid === undefined || choice.providerDid === expectedProviderDid)) {
|
|
381
|
+
return choice;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function writeSessionChoice(storage: ConnectModalDeps['storage'], choice: SessionChoice): void {
|
|
388
|
+
try {
|
|
389
|
+
const choices = readSessionChoices(storage).filter(entry =>
|
|
390
|
+
entry.delegateDid !== choice.delegateDid || entry.providerDid !== choice.providerDid
|
|
391
|
+
);
|
|
392
|
+
choices.push(choice);
|
|
393
|
+
storage?.setItem(SESSION_CHOICES_STORAGE_KEY, JSON.stringify(choices.slice(-MAX_SESSION_CHOICES)));
|
|
394
|
+
} catch { /* best effort */ }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function removeSessionChoice(
|
|
398
|
+
storage: ConnectModalDeps['storage'],
|
|
399
|
+
delegateDid: string,
|
|
400
|
+
providerDid: string,
|
|
401
|
+
): void {
|
|
402
|
+
try {
|
|
403
|
+
const choices = readSessionChoices(storage).filter(choice =>
|
|
404
|
+
choice.delegateDid !== delegateDid || choice.providerDid !== providerDid
|
|
405
|
+
);
|
|
406
|
+
if (choices.length === 0) {
|
|
407
|
+
storage?.removeItem(SESSION_CHOICES_STORAGE_KEY);
|
|
408
|
+
} else {
|
|
409
|
+
storage?.setItem(SESSION_CHOICES_STORAGE_KEY, JSON.stringify(choices));
|
|
410
|
+
}
|
|
411
|
+
} catch { /* best effort */ }
|
|
412
|
+
}
|
|
413
|
+
|
|
324
414
|
/** One cancellable relay session. */
|
|
325
415
|
interface RelaySession {
|
|
326
416
|
cancelled: Promise<never>;
|
|
@@ -379,24 +469,37 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
379
469
|
const wallets = dedupeWalletsByUrl(options.wallets ?? []);
|
|
380
470
|
const rememberChoice = options.rememberChoice ?? true;
|
|
381
471
|
const appName = options.appName ?? window.location.host;
|
|
382
|
-
const
|
|
472
|
+
const applicationId = options.applicationId ?? window.location.origin;
|
|
383
473
|
const isMobile = deps.isMobile();
|
|
384
474
|
const refreshing = options.mode === 'refresh';
|
|
385
475
|
|
|
386
476
|
const lastChoice = rememberChoice ? readLastChoice(deps.storage) : undefined;
|
|
477
|
+
let savedRoute = rememberChoice && refreshing
|
|
478
|
+
? readSessionChoice(
|
|
479
|
+
deps.storage,
|
|
480
|
+
options.delegatePortableDid?.uri,
|
|
481
|
+
options.expectedProviderDid,
|
|
482
|
+
)
|
|
483
|
+
: undefined;
|
|
484
|
+
const expectedProviderDid = options.expectedProviderDid ?? savedRoute?.providerDid;
|
|
485
|
+
const relayWalletPath = options.relayWalletPath ?? '/connect/app';
|
|
486
|
+
const savedRouteEscapeLabel = options.walletUrl === undefined
|
|
487
|
+
? 'Choose another wallet or method'
|
|
488
|
+
: 'Choose another method';
|
|
387
489
|
|
|
388
|
-
//
|
|
490
|
+
// Refresh routes resolve by delegate first; general preferences remain a
|
|
491
|
+
// best-effort fallback for sessions created before route-aware storage.
|
|
389
492
|
const rememberedWalletUrl = lastChoice !== undefined
|
|
390
493
|
&& (refreshing || walletInCatalog(wallets, lastChoice.walletUrl))
|
|
391
494
|
? lastChoice.walletUrl
|
|
392
495
|
: undefined;
|
|
393
|
-
|
|
394
|
-
|
|
496
|
+
let walletUrl = savedRoute?.walletUrl
|
|
497
|
+
?? options.walletUrl
|
|
395
498
|
?? rememberedWalletUrl
|
|
396
499
|
?? wallets[0]?.url;
|
|
397
500
|
|
|
398
|
-
//
|
|
399
|
-
let method: ConnectMethod = options.preferredMethod ?? lastChoice?.method ?? 'phone';
|
|
501
|
+
// An exact refresh route wins over generic handler and global preferences.
|
|
502
|
+
let method: ConnectMethod = savedRoute?.method ?? options.preferredMethod ?? lastChoice?.method ?? 'phone';
|
|
400
503
|
|
|
401
504
|
return new Promise<ConnectResult | undefined>((resolve, reject) => {
|
|
402
505
|
// ── Host + shadow root ─────────────────────────────────────
|
|
@@ -517,8 +620,14 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
517
620
|
};
|
|
518
621
|
|
|
519
622
|
const succeed = (result: ConnectResult): void => {
|
|
520
|
-
if (rememberChoice) {
|
|
521
|
-
writeLastChoice(deps.storage, { method, walletUrl
|
|
623
|
+
if (rememberChoice && walletUrl !== undefined) {
|
|
624
|
+
writeLastChoice(deps.storage, { method, walletUrl });
|
|
625
|
+
writeSessionChoice(deps.storage, {
|
|
626
|
+
delegateDid : result.delegatePortableDid.uri,
|
|
627
|
+
providerDid : result.connectedDid,
|
|
628
|
+
method,
|
|
629
|
+
walletUrl,
|
|
630
|
+
});
|
|
522
631
|
}
|
|
523
632
|
renderConnected();
|
|
524
633
|
setTimeout(settleWith(settle, () => resolve(result)), 1_200);
|
|
@@ -703,15 +812,47 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
703
812
|
);
|
|
704
813
|
};
|
|
705
814
|
|
|
815
|
+
const escapeSavedRoute = (): void => {
|
|
816
|
+
if (savedRoute === undefined) {
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
relaySession?.cancel();
|
|
821
|
+
relaySession = undefined;
|
|
822
|
+
clearRemint();
|
|
823
|
+
removeSessionChoice(deps.storage, savedRoute.delegateDid, savedRoute.providerDid);
|
|
824
|
+
savedRoute = undefined;
|
|
825
|
+
if (options.walletUrl !== undefined) {
|
|
826
|
+
walletUrl = options.walletUrl;
|
|
827
|
+
}
|
|
828
|
+
renderFooter();
|
|
829
|
+
setStage(
|
|
830
|
+
el('p', 'stage-caption', 'Choose another way to reconnect.'),
|
|
831
|
+
el('p', 'stage-subline', 'We’ll still require the same wallet profile used for this session.'),
|
|
832
|
+
);
|
|
833
|
+
};
|
|
834
|
+
|
|
706
835
|
const renderError = (message: string, error?: Error): void => {
|
|
707
836
|
setStage(
|
|
708
837
|
el('p', 'stage-caption', message),
|
|
709
838
|
stageButton('Start over', () => { void startMethod(); }),
|
|
839
|
+
...(savedRoute !== undefined
|
|
840
|
+
? [stageLinkButton(savedRouteEscapeLabel, escapeSavedRoute)]
|
|
841
|
+
: []),
|
|
710
842
|
stageLinkButton('Close', settleWith(settle, () => reject(error ?? new Error(`[@enbox/browser] ${message}`)))),
|
|
711
843
|
);
|
|
712
844
|
};
|
|
713
845
|
|
|
714
846
|
const renderPhoneUnavailable = (): void => {
|
|
847
|
+
if (savedRoute !== undefined) {
|
|
848
|
+
setStage(
|
|
849
|
+
el('p', 'stage-caption', 'Your saved wallet connection is unavailable right now.'),
|
|
850
|
+
el('p', 'stage-subline', 'Try again in a moment to reconnect the same session.'),
|
|
851
|
+
stageButton('Try again', () => { void startPhone(); }),
|
|
852
|
+
stageLinkButton(savedRouteEscapeLabel, escapeSavedRoute),
|
|
853
|
+
);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
715
856
|
setStage(
|
|
716
857
|
el('p', 'stage-caption', 'This wallet can’t connect by phone here.'),
|
|
717
858
|
el('p', 'stage-subline', 'You can continue in this browser instead.'),
|
|
@@ -724,12 +865,24 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
724
865
|
const popupSurface = isMobile ? 'tab' : 'window';
|
|
725
866
|
|
|
726
867
|
const renderPopupPrompt = (): void => {
|
|
868
|
+
const walletName = walletUrl !== undefined ? walletByUrl(walletUrl).name : 'your wallet';
|
|
869
|
+
const exactReconnect = savedRoute !== undefined;
|
|
870
|
+
const connectDescription = isMobile
|
|
871
|
+
? 'Your wallet opens in a new tab to approve this connection — no code needed.'
|
|
872
|
+
: 'Your wallet opens in a small window to approve this connection.';
|
|
873
|
+
const description = exactReconnect
|
|
874
|
+
? `We’ll open the same ${walletName} profile used for this session.`
|
|
875
|
+
: connectDescription;
|
|
876
|
+
const connectButtonLabel = isMobile ? 'Open wallet' : 'Open wallet window';
|
|
877
|
+
const buttonLabel = exactReconnect
|
|
878
|
+
? `Open ${walletName} ${popupSurface}`
|
|
879
|
+
: connectButtonLabel;
|
|
727
880
|
setStage(
|
|
728
|
-
el('p', 'stage-caption',
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
stageButton(
|
|
881
|
+
el('p', 'stage-caption', exactReconnect
|
|
882
|
+
? `Reconnect with ${walletName}`
|
|
883
|
+
: 'Connect with a wallet in this browser'),
|
|
884
|
+
el('p', 'stage-subline', description),
|
|
885
|
+
stageButton(buttonLabel, () => { startPopup(); }),
|
|
733
886
|
);
|
|
734
887
|
};
|
|
735
888
|
|
|
@@ -747,7 +900,9 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
747
900
|
? `Your browser blocked the wallet ${popupSurface}.`
|
|
748
901
|
: `The wallet ${popupSurface} was closed.`),
|
|
749
902
|
stageButton(blocked ? 'Open it now' : `Reopen ${popupSurface}`, () => { startPopup(); }),
|
|
750
|
-
|
|
903
|
+
...(savedRoute !== undefined
|
|
904
|
+
? [stageLinkButton(savedRouteEscapeLabel, escapeSavedRoute)]
|
|
905
|
+
: [stageLinkButton(isMobile ? 'Use a code instead' : 'Use your phone instead', () => { void switchMethod('phone'); })]),
|
|
751
906
|
);
|
|
752
907
|
};
|
|
753
908
|
|
|
@@ -833,10 +988,12 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
833
988
|
walletUri : new URL(relayWalletPath, origin).toString(),
|
|
834
989
|
appName,
|
|
835
990
|
appIcon : options.appIcon,
|
|
991
|
+
applicationId,
|
|
836
992
|
clientMetadata : collectBrowserClientMetadata(),
|
|
837
993
|
permissionRequests : options.permissionRequests,
|
|
838
994
|
delegatePortableDid : options.delegatePortableDid,
|
|
839
995
|
requestType : options.mode,
|
|
996
|
+
expectedProviderDid,
|
|
840
997
|
timeoutMs : options.timeout,
|
|
841
998
|
cancelled : session.cancelled,
|
|
842
999
|
onWalletUriReady : (handoff): void => {
|
|
@@ -907,9 +1064,11 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
907
1064
|
permissionRequests : options.permissionRequests,
|
|
908
1065
|
appName,
|
|
909
1066
|
appIcon : options.appIcon,
|
|
1067
|
+
applicationId,
|
|
910
1068
|
timeoutMs : options.timeout,
|
|
911
1069
|
delegatePortableDid : options.delegatePortableDid,
|
|
912
1070
|
requestType : options.mode,
|
|
1071
|
+
expectedProviderDid,
|
|
913
1072
|
});
|
|
914
1073
|
if (settled) { return; }
|
|
915
1074
|
if (result === undefined) {
|
|
@@ -945,7 +1104,7 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
945
1104
|
const renderFooter = (): void => {
|
|
946
1105
|
footer.replaceChildren();
|
|
947
1106
|
|
|
948
|
-
if (
|
|
1107
|
+
if (savedRoute === undefined && options.walletUrl === undefined) {
|
|
949
1108
|
composeWalletRow();
|
|
950
1109
|
const switcher = buildWalletSwitcher();
|
|
951
1110
|
if (switcher.label !== undefined) {
|
|
@@ -958,7 +1117,7 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
958
1117
|
const row = document.createElement('div');
|
|
959
1118
|
row.className = 'footer-row';
|
|
960
1119
|
|
|
961
|
-
if (method === 'phone') {
|
|
1120
|
+
if (method === 'phone' && savedRoute === undefined) {
|
|
962
1121
|
const alt = document.createElement('button');
|
|
963
1122
|
alt.className = 'footer-link method-link';
|
|
964
1123
|
// On a phone both methods open the wallet on this device — what the
|
|
@@ -967,7 +1126,7 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
|
|
|
967
1126
|
// startPopup runs synchronously in this click handler.
|
|
968
1127
|
alt.addEventListener('click', () => { startPopup(); });
|
|
969
1128
|
row.appendChild(alt);
|
|
970
|
-
} else {
|
|
1129
|
+
} else if (method === 'browser' && savedRoute === undefined) {
|
|
971
1130
|
const alt = document.createElement('button');
|
|
972
1131
|
alt.className = 'footer-link method-link';
|
|
973
1132
|
alt.textContent = isMobile ? 'Use a code instead →' : 'Use your phone instead →';
|