@enbox/browser 0.3.43 → 0.3.44

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.
Files changed (48) hide show
  1. package/dist/browser.mjs +144 -154
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/browser-connect-handler.js +17 -20
  4. package/dist/esm/browser-connect-handler.js.map +1 -1
  5. package/dist/esm/dweb-connect-client.js +1 -1
  6. package/dist/esm/dweb-connect-client.js.map +1 -1
  7. package/dist/esm/index.js +4 -1
  8. package/dist/esm/index.js.map +1 -1
  9. package/dist/esm/relay-connect-runner.js +127 -0
  10. package/dist/esm/relay-connect-runner.js.map +1 -0
  11. package/dist/esm/ui/connect-modal.js +961 -0
  12. package/dist/esm/ui/connect-modal.js.map +1 -0
  13. package/dist/esm/ui/qr.js +468 -0
  14. package/dist/esm/ui/qr.js.map +1 -0
  15. package/dist/esm/ui/wallet-icon.js +55 -0
  16. package/dist/esm/ui/wallet-icon.js.map +1 -0
  17. package/dist/esm/ui/wallet-well-known.js +61 -0
  18. package/dist/esm/ui/wallet-well-known.js.map +1 -0
  19. package/dist/types/browser-connect-handler.d.ts +32 -7
  20. package/dist/types/browser-connect-handler.d.ts.map +1 -1
  21. package/dist/types/dweb-connect-client.d.ts +3 -1
  22. package/dist/types/dweb-connect-client.d.ts.map +1 -1
  23. package/dist/types/index.d.ts +8 -2
  24. package/dist/types/index.d.ts.map +1 -1
  25. package/dist/types/relay-connect-runner.d.ts +76 -0
  26. package/dist/types/relay-connect-runner.d.ts.map +1 -0
  27. package/dist/types/ui/connect-modal.d.ts +108 -0
  28. package/dist/types/ui/connect-modal.d.ts.map +1 -0
  29. package/dist/types/ui/qr.d.ts +55 -0
  30. package/dist/types/ui/qr.d.ts.map +1 -0
  31. package/dist/types/ui/wallet-icon.d.ts +13 -0
  32. package/dist/types/ui/wallet-icon.d.ts.map +1 -0
  33. package/dist/types/ui/wallet-well-known.d.ts +33 -0
  34. package/dist/types/ui/wallet-well-known.d.ts.map +1 -0
  35. package/package.json +3 -1
  36. package/src/browser-connect-handler.ts +51 -21
  37. package/src/dweb-connect-client.ts +1 -1
  38. package/src/index.ts +8 -2
  39. package/src/relay-connect-runner.ts +201 -0
  40. package/src/ui/connect-modal.ts +1167 -0
  41. package/src/ui/qr.ts +549 -0
  42. package/src/ui/wallet-icon.ts +63 -0
  43. package/src/ui/wallet-well-known.ts +57 -0
  44. package/dist/esm/ui/wallet-selector.js +0 -585
  45. package/dist/esm/ui/wallet-selector.js.map +0 -1
  46. package/dist/types/ui/wallet-selector.d.ts +0 -44
  47. package/dist/types/ui/wallet-selector.d.ts.map +0 -1
  48. package/src/ui/wallet-selector.ts +0 -658
package/src/index.ts CHANGED
@@ -104,8 +104,14 @@ export type {
104
104
  DWebConnectRequestMessage,
105
105
  DWebConnectResponseMessage,
106
106
  } from './dweb-connect-messages.js';
107
- export type { WalletSelectorOptions } from './ui/wallet-selector.js';
108
- export { probeWalletWellKnown, showWalletSelector, WALLET_WELL_KNOWN_PATH } from './ui/wallet-selector.js';
107
+ export { fetchWalletWellKnown, probeWalletWellKnown, WALLET_WELL_KNOWN_PATH } from './ui/wallet-well-known.js';
108
+ export type { WalletWellKnownDocument } from './ui/wallet-well-known.js';
109
+ export { discoverWalletConnectServerUrl, runConnectModal } from './ui/connect-modal.js';
110
+ export type { ConnectMethod, ConnectModalDeps, ConnectModalOptions } from './ui/connect-modal.js';
111
+ export { MAX_PIN_ATTEMPTS, RelayConnectCancelledError, runRelayConnect } from './relay-connect-runner.js';
112
+ export type { RelayConnectOptions } from './relay-connect-runner.js';
113
+ export { encodeQr, qrToSvg } from './ui/qr.js';
114
+ export type { QrCode, QrSvgOptions } from './ui/qr.js';
109
115
 
110
116
  // ─── Convenience re-exports for dapps ───────────────────────────
111
117
  //
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Relay connect runner — the dapp side of the QR/deep-link connect flow,
3
+ * with interactive PIN retry.
4
+ *
5
+ * This mirrors the `@enbox/connect` kernel `ConnectClient` step-for-step and
6
+ * is composed entirely from the kernel's exported primitives (`sealRequest`,
7
+ * `openResponse`, `RelayClientTransport`); no cryptography is reimplemented
8
+ * here. It exists as a separate orchestrator for one reason the kernel
9
+ * client cannot express today: **PIN retry against the delivered response**.
10
+ * `ConnectClient` collects the PIN once and opens the response once, so a
11
+ * mistyped code fails the whole handshake — the wallet user would have to
12
+ * scan and approve again. The connect modal instead keeps the sealed
13
+ * response and re-runs `openResponse` per attempt, which is safe because
14
+ * opening is a pure decrypt-and-verify step.
15
+ *
16
+ * If the kernel later grows a PIN-attempts seam, this module collapses into
17
+ * `ConnectClient` usage.
18
+ *
19
+ * @module
20
+ */
21
+
22
+ import type { Jwk } from '@enbox/crypto';
23
+ import type {
24
+ ConnectClientMetadata,
25
+ ConnectPermissionRequest,
26
+ ConnectRequest,
27
+ ConnectResult,
28
+ WalletUriHandoff,
29
+ } from '@enbox/connect';
30
+
31
+ import { DidJwk } from '@enbox/dids';
32
+ import { X25519 } from '@enbox/crypto';
33
+ import {
34
+ CONNECT_DENIED_TOKEN,
35
+ openResponse,
36
+ randomToken,
37
+ RelayClientTransport,
38
+ sealRequest,
39
+ } from '@enbox/connect';
40
+
41
+ /** Maximum interactive PIN attempts before the handshake fails. */
42
+ export const MAX_PIN_ATTEMPTS = 6;
43
+
44
+ /** Thrown internally when a relay handshake is cancelled by the UI. */
45
+ export class RelayConnectCancelledError extends Error {
46
+ constructor() {
47
+ super('[@enbox/browser] Relay connect cancelled.');
48
+ this.name = 'RelayConnectCancelledError';
49
+ }
50
+ }
51
+
52
+ /** Options for {@link runRelayConnect}. */
53
+ export interface RelayConnectOptions {
54
+ /** Relay base URL (the wallet's `connectServerUrl`). */
55
+ connectServerUrl: string;
56
+
57
+ /** Wallet URI the request pointer is attached to (QR / deep-link target). */
58
+ walletUri: string;
59
+
60
+ /** The user-friendly name of the app, shown in the wallet consent UI. */
61
+ appName: string;
62
+
63
+ /** Optional icon URL for the app, shown in the wallet consent UI. */
64
+ appIcon?: string;
65
+
66
+ /** Optional client/environment metadata for wallet session display. */
67
+ clientMetadata?: ConnectClientMetadata;
68
+
69
+ /** DWN protocols and permission scopes being requested. */
70
+ permissionRequests: ConnectPermissionRequest[];
71
+
72
+ /** Total milliseconds to poll the relay for a wallet response. */
73
+ timeoutMs?: number;
74
+
75
+ /** Milliseconds between relay polling attempts. */
76
+ pollIntervalMs?: number;
77
+
78
+ /**
79
+ * Receives the wallet URI handoff (with the request pointer TTL) as soon
80
+ * as the request is delivered — render it as a QR code or deep link.
81
+ */
82
+ onWalletUriReady: (handoff: WalletUriHandoff) => void;
83
+
84
+ /**
85
+ * Collects one PIN attempt from the user. Called again (with the attempt
86
+ * number, 1-based, and the previous failure) after a wrong code, up to
87
+ * {@link MAX_PIN_ATTEMPTS} times.
88
+ */
89
+ requestPin: (attempt: number, previousError?: Error) => Promise<string>;
90
+
91
+ /**
92
+ * Cancellation signal: when it settles, polling stops and the handshake
93
+ * rejects with {@link RelayConnectCancelledError}.
94
+ */
95
+ cancelled?: Promise<never> | undefined;
96
+
97
+ /** Transport factory override (tests). */
98
+ createTransport?: (options: {
99
+ connectServerUrl: string;
100
+ walletUri: string;
101
+ timeoutMs?: number;
102
+ pollIntervalMs?: number;
103
+ }) => Pick<RelayClientTransport, 'requestProfile' | 'deliverRequest' | 'awaitResponse' | 'requiresPin'>;
104
+ }
105
+
106
+ /**
107
+ * Runs one relay-mediated connect handshake end-to-end and returns the
108
+ * delegated credentials, or `undefined` when the user denied in the wallet.
109
+ */
110
+ export async function runRelayConnect(options: RelayConnectOptions): Promise<ConnectResult | undefined> {
111
+ const createTransport = options.createTransport
112
+ ?? ((transportOptions): RelayClientTransport => new RelayClientTransport(transportOptions));
113
+
114
+ const transport = createTransport({
115
+ connectServerUrl : options.connectServerUrl,
116
+ walletUri : options.walletUri,
117
+ timeoutMs : options.timeoutMs,
118
+ pollIntervalMs : options.pollIntervalMs,
119
+ });
120
+
121
+ // Ephemeral client DID for request signing and response addressing, and a
122
+ // fresh X25519 pair the wallet seals the response to — same shape as the
123
+ // kernel `ConnectClient`.
124
+ const clientDid = await DidJwk.create();
125
+ const responsePrivateKey = await X25519.generateKey();
126
+ const responsePublicKey: Jwk = { kty: 'OKP', crv: 'X25519', x: responsePrivateKey.x };
127
+
128
+ const nonce = randomToken();
129
+ const state = randomToken();
130
+
131
+ const profile = await transport.requestProfile(state);
132
+
133
+ const request: ConnectRequest = {
134
+ clientDid : clientDid.uri,
135
+ appName : options.appName,
136
+ appIcon : options.appIcon,
137
+ clientMetadata : options.clientMetadata,
138
+ permissionRequests : options.permissionRequests,
139
+ supportedDidMethods : ['did:dht', 'did:jwk'],
140
+ nonce,
141
+ state,
142
+ responseKey : responsePublicKey,
143
+ reply : profile.reply,
144
+ };
145
+
146
+ const requestJwe = await sealRequest({ request, signer: clientDid, encryption: profile.encryption });
147
+
148
+ const handoff = await transport.deliverRequest(requestJwe);
149
+ if (handoff !== undefined) {
150
+ options.onWalletUriReady(handoff);
151
+ }
152
+
153
+ const race = options.cancelled === undefined
154
+ ? transport.awaitResponse()
155
+ : Promise.race([transport.awaitResponse(), options.cancelled]);
156
+
157
+ const responseCiphertext = await race;
158
+ if (responseCiphertext === CONNECT_DENIED_TOKEN) {
159
+ return undefined;
160
+ }
161
+
162
+ // Interactive PIN loop: opening is a pure decrypt-and-verify step, so a
163
+ // wrong code costs one more attempt, not a whole new wallet approval.
164
+ // Errors raised by the kernel's value checks carry a 'Connect:' prefix and
165
+ // indicate a protocol violation, not a mistyped code — those fail closed.
166
+ let lastError: Error | undefined;
167
+ for (let attempt = 1; attempt <= MAX_PIN_ATTEMPTS; attempt++) {
168
+ const pin = await (options.cancelled === undefined
169
+ ? options.requestPin(attempt, lastError)
170
+ : Promise.race([options.requestPin(attempt, lastError), options.cancelled]));
171
+
172
+ try {
173
+ const response = await openResponse({
174
+ jwe : responseCiphertext,
175
+ recipientPrivateKey : responsePrivateKey,
176
+ expected : { clientDid: clientDid.uri, nonce, state },
177
+ pin,
178
+ });
179
+
180
+ if (response.delegatePortableDid === undefined) {
181
+ throw new Error('Connect: wallet response omitted `delegatePortableDid`.');
182
+ }
183
+
184
+ return {
185
+ delegatePortableDid : response.delegatePortableDid,
186
+ delegateGrants : response.delegateGrants,
187
+ connectedDid : response.providerDid,
188
+ sessionRevocations : response.sessionRevocations,
189
+ };
190
+ } catch (error) {
191
+ const failure = error instanceof Error ? error : new Error(String(error));
192
+ if (failure.message.startsWith('Connect:')) {
193
+ // Structural/value-check failure — retrying the PIN cannot help.
194
+ throw failure;
195
+ }
196
+ lastError = failure;
197
+ }
198
+ }
199
+
200
+ throw new Error(`[@enbox/browser] The pairing code did not match after ${MAX_PIN_ATTEMPTS} attempts.`);
201
+ }