@enbox/browser 0.1.3 → 0.1.4

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.
@@ -0,0 +1,63 @@
1
+ /**
2
+ * DWeb Connect client — initiates the popup/postMessage connect flow.
3
+ *
4
+ * This is the dapp-side counterpart to the wallet's `/dweb-connect` page.
5
+ * The wallet is opened as a popup window, and communication happens via
6
+ * `window.postMessage`. No relay server, QR codes, or PINs are needed.
7
+ *
8
+ * @module
9
+ */
10
+ import type { ConnectResult } from '@enbox/auth';
11
+ import type { ConnectPermissionRequest } from '@enbox/agent';
12
+ /** Options for initiating a DWeb Connect flow via popup. */
13
+ export interface DWebConnectClientOptions {
14
+ /** Base URL of the wallet app (e.g. "https://wallet.enbox.org"). */
15
+ walletUrl: string;
16
+ /** The DID to pre-select in the wallet's identity picker. */
17
+ did?: string;
18
+ /**
19
+ * Protocol permission requests to send to the wallet.
20
+ * Each entry contains a `protocolDefinition` and `permissionScopes`.
21
+ */
22
+ permissionRequests: ConnectPermissionRequest[];
23
+ /**
24
+ * Timeout in milliseconds. The popup is closed and an error thrown
25
+ * if the wallet doesn't respond within this time.
26
+ * @default 300_000 (5 minutes)
27
+ */
28
+ timeout?: number;
29
+ }
30
+ /**
31
+ * Open a wallet popup and run the DWeb Connect postMessage flow.
32
+ *
33
+ * Protocol:
34
+ * 1. Dapp opens `${walletUrl}/dweb-connect` as a popup.
35
+ * 2. Wallet sends `{ type: 'dweb-connect-loaded' }` when ready.
36
+ * 3. Dapp sends `{ type: 'dweb-connect-authorization-request', did, permissions }`.
37
+ * 4. Wallet shows consent UI, then sends back
38
+ * `{ type: 'dweb-connect-authorization-response', delegateDid, grants }`.
39
+ * 5. Popup closes.
40
+ *
41
+ * @returns The delegate credentials, or `undefined` if the user denied.
42
+ * @throws If the popup is blocked, times out, or the wallet returns an error.
43
+ */
44
+ declare function initClient(options: DWebConnectClientOptions): Promise<ConnectResult | undefined>;
45
+ /**
46
+ * Probe whether a wallet supports a specific DID via a hidden iframe.
47
+ *
48
+ * Sends a `dweb-connect-support-request` message to the wallet and
49
+ * waits for a `dweb-connect-support-response`. Useful for checking
50
+ * multiple wallet URLs to find the one that manages a given DID.
51
+ *
52
+ * @param walletUrl - Base URL of the wallet to probe.
53
+ * @param did - The DID to check.
54
+ * @param timeout - Probe timeout in ms. Default: 5_000 (5 seconds).
55
+ * @returns `true` if the wallet manages the DID, `false` otherwise.
56
+ */
57
+ declare function probeWalletSupport(walletUrl: string, did: string, timeout?: number): Promise<boolean>;
58
+ export declare const DWebConnect: {
59
+ initClient: typeof initClient;
60
+ probeWalletSupport: typeof probeWalletSupport;
61
+ };
62
+ export {};
63
+ //# sourceMappingURL=dweb-connect-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dweb-connect-client.d.ts","sourceRoot":"","sources":["../../src/dweb-connect-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EAAE,wBAAwB,EAAqC,MAAM,cAAc,CAAC;AAEhG,4DAA4D;AAC5D,MAAM,WAAW,wBAAwB;IACvC,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAElB,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,kBAAkB,EAAE,wBAAwB,EAAE,CAAC;IAE/C;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,iBAAe,UAAU,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAgG/F;AAED;;;;;;;;;;;GAWG;AACH,iBAAe,kBAAkB,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,SAAQ,GACd,OAAO,CAAC,OAAO,CAAC,CA+ClB;AAED,eAAO,MAAM,WAAW;;;CAAqC,CAAC"}
@@ -1,2 +1,6 @@
1
1
  export * from './web-features.js';
2
+ export { BrowserConnectHandler, DEFAULT_WALLETS } from './browser-connect-handler.js';
3
+ export type { BrowserConnectHandlerOptions, WalletOption } from './browser-connect-handler.js';
4
+ export { DWebConnect } from './dweb-connect-client.js';
5
+ export type { DWebConnectClientOptions } from './dweb-connect-client.js';
2
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACtF,YAAY,EAAE,4BAA4B,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Wallet selector modal — a self-contained, framework-agnostic UI component.
3
+ *
4
+ * Injected into the DOM as a Shadow DOM element to prevent style conflicts.
5
+ * Inspired by WalletConnect's Web3Modal pattern.
6
+ *
7
+ * @module
8
+ */
9
+ import type { WalletOption } from '../browser-connect-handler.js';
10
+ /** Shows the wallet selector modal and resolves with the chosen wallet URL. */
11
+ export declare function showWalletSelector(wallets: WalletOption[]): Promise<string>;
12
+ //# sourceMappingURL=wallet-selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet-selector.d.ts","sourceRoot":"","sources":["../../../src/ui/wallet-selector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAmK3E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enbox/browser",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Enbox tools and features to use in the browser",
5
5
  "type": "module",
6
6
  "main": "./dist/esm/index.js",
@@ -55,6 +55,8 @@
55
55
  "access": "public"
56
56
  },
57
57
  "dependencies": {
58
+ "@enbox/agent": "0.5.1",
59
+ "@enbox/auth": "0.6.1",
58
60
  "@enbox/dids": "0.1.0"
59
61
  },
60
62
  "devDependencies": {
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Browser connect handler for `@enbox/auth`.
3
+ *
4
+ * Implements the {@link ConnectHandler} interface using browser-native
5
+ * DWeb Connect (popup + postMessage) with a wallet selector modal.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { ConnectPermissionRequest } from '@enbox/agent';
11
+ import type { ConnectHandler, ConnectResult } from '@enbox/auth';
12
+
13
+ import { DWebConnect } from './dweb-connect-client.js';
14
+ import { showWalletSelector } from './ui/wallet-selector.js';
15
+
16
+ /** A wallet entry shown in the wallet selector modal. */
17
+ export interface WalletOption {
18
+ /** Display name (e.g. "Enbox Wallet"). */
19
+ name: string;
20
+
21
+ /** Base URL of the wallet app (e.g. "https://wallet.enbox.org"). */
22
+ url: string;
23
+
24
+ /** Optional icon URL. If omitted, the wallet's favicon is used. */
25
+ icon?: string;
26
+ }
27
+
28
+ /** Default wallets shown in the selector when no overrides are provided. */
29
+ export const DEFAULT_WALLETS: WalletOption[] = [
30
+ { name: 'Enbox Wallet', url: 'https://wallet.enbox.org' },
31
+ ];
32
+
33
+ /** Options for creating a BrowserConnectHandler. */
34
+ export interface BrowserConnectHandlerOptions {
35
+ /**
36
+ * Known wallets shown in the wallet selector modal.
37
+ * If omitted, {@link DEFAULT_WALLETS} is used.
38
+ */
39
+ wallets?: WalletOption[];
40
+
41
+ /**
42
+ * Explicit wallet URL. If provided, the wallet selector is skipped
43
+ * and this URL is used directly.
44
+ */
45
+ walletUrl?: string;
46
+
47
+ /**
48
+ * Timeout in milliseconds for the wallet popup flow.
49
+ * @default 300_000 (5 minutes)
50
+ */
51
+ timeout?: number;
52
+ }
53
+
54
+ /**
55
+ * Create a browser-native connect handler using DWeb Connect
56
+ * (popup + postMessage).
57
+ *
58
+ * This handler:
59
+ * 1. Shows a wallet selector modal (or uses a provided walletUrl).
60
+ * 2. Opens the selected wallet as a popup window.
61
+ * 3. Runs the DWeb Connect postMessage protocol.
62
+ * 4. Returns the delegate credentials on success.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * import { AuthManager } from '@enbox/auth';
67
+ * import { BrowserConnectHandler } from '@enbox/browser';
68
+ *
69
+ * const auth = await AuthManager.create({
70
+ * connectHandler: BrowserConnectHandler(),
71
+ * });
72
+ *
73
+ * const session = await auth.connect({ protocols: [NotesProtocol] });
74
+ * ```
75
+ */
76
+ export function BrowserConnectHandler(
77
+ options: BrowserConnectHandlerOptions = {},
78
+ ): ConnectHandler {
79
+ const {
80
+ wallets = DEFAULT_WALLETS,
81
+ walletUrl: fixedWalletUrl,
82
+ timeout,
83
+ } = options;
84
+
85
+ return {
86
+ async requestAccess(params: {
87
+ permissionRequests: ConnectPermissionRequest[];
88
+ }): Promise<ConnectResult | undefined> {
89
+ // 1. Determine wallet URL.
90
+ let walletUrl = fixedWalletUrl;
91
+ if (!walletUrl) {
92
+ walletUrl = await showWalletSelector(wallets);
93
+ }
94
+
95
+ // 2. Run the DWeb Connect popup flow.
96
+ return DWebConnect.initClient({
97
+ walletUrl,
98
+ permissionRequests: params.permissionRequests,
99
+ timeout,
100
+ });
101
+ },
102
+ };
103
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * DWeb Connect client — initiates the popup/postMessage connect flow.
3
+ *
4
+ * This is the dapp-side counterpart to the wallet's `/dweb-connect` page.
5
+ * The wallet is opened as a popup window, and communication happens via
6
+ * `window.postMessage`. No relay server, QR codes, or PINs are needed.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ import type { ConnectResult } from '@enbox/auth';
12
+ import type { PortableDid } from '@enbox/dids';
13
+ import type { ConnectPermissionRequest, DwnDataEncodedRecordsWriteMessage } from '@enbox/agent';
14
+
15
+ /** Options for initiating a DWeb Connect flow via popup. */
16
+ export interface DWebConnectClientOptions {
17
+ /** Base URL of the wallet app (e.g. "https://wallet.enbox.org"). */
18
+ walletUrl: string;
19
+
20
+ /** The DID to pre-select in the wallet's identity picker. */
21
+ did?: string;
22
+
23
+ /**
24
+ * Protocol permission requests to send to the wallet.
25
+ * Each entry contains a `protocolDefinition` and `permissionScopes`.
26
+ */
27
+ permissionRequests: ConnectPermissionRequest[];
28
+
29
+ /**
30
+ * Timeout in milliseconds. The popup is closed and an error thrown
31
+ * if the wallet doesn't respond within this time.
32
+ * @default 300_000 (5 minutes)
33
+ */
34
+ timeout?: number;
35
+ }
36
+
37
+ /**
38
+ * Open a wallet popup and run the DWeb Connect postMessage flow.
39
+ *
40
+ * Protocol:
41
+ * 1. Dapp opens `${walletUrl}/dweb-connect` as a popup.
42
+ * 2. Wallet sends `{ type: 'dweb-connect-loaded' }` when ready.
43
+ * 3. Dapp sends `{ type: 'dweb-connect-authorization-request', did, permissions }`.
44
+ * 4. Wallet shows consent UI, then sends back
45
+ * `{ type: 'dweb-connect-authorization-response', delegateDid, grants }`.
46
+ * 5. Popup closes.
47
+ *
48
+ * @returns The delegate credentials, or `undefined` if the user denied.
49
+ * @throws If the popup is blocked, times out, or the wallet returns an error.
50
+ */
51
+ async function initClient(options: DWebConnectClientOptions): Promise<ConnectResult | undefined> {
52
+ const {
53
+ walletUrl,
54
+ did,
55
+ permissionRequests,
56
+ timeout = 300_000,
57
+ } = options;
58
+
59
+ if (typeof window === 'undefined') {
60
+ throw new Error(
61
+ '[@enbox/auth] DWeb Connect is only available in browser environments.'
62
+ );
63
+ }
64
+
65
+ // Open the wallet's DWeb Connect page as a popup.
66
+ const popupUrl = new URL('/dweb-connect', walletUrl).toString();
67
+ const popup = window.open(
68
+ popupUrl,
69
+ 'enbox-dweb-connect',
70
+ 'width=500,height=700,menubar=no,toolbar=no,location=no,status=no',
71
+ );
72
+
73
+ if (!popup) {
74
+ throw new Error(
75
+ '[@enbox/auth] Popup blocked. Allow popups for this site to connect to a wallet.'
76
+ );
77
+ }
78
+
79
+ return new Promise<ConnectResult | undefined>((resolve, reject) => {
80
+ let settled = false;
81
+
82
+ const cleanup = (): void => {
83
+ settled = true;
84
+ clearTimeout(timeoutId);
85
+ window.removeEventListener('message', onMessage);
86
+ };
87
+
88
+ // Set up timeout.
89
+ const timeoutId = setTimeout(() => {
90
+ if (!settled) {
91
+ cleanup();
92
+ try { popup.close(); } catch { /* best effort */ }
93
+ reject(new Error(
94
+ '[@enbox/auth] DWeb Connect timed out waiting for wallet response.'
95
+ ));
96
+ }
97
+ }, timeout);
98
+
99
+ // Monitor popup closed without response (user closed the window).
100
+ const pollClosed = setInterval(() => {
101
+ if (popup.closed && !settled) {
102
+ clearInterval(pollClosed);
103
+ cleanup();
104
+ resolve(undefined);
105
+ }
106
+ }, 500);
107
+
108
+ const onMessage = (event: MessageEvent): void => {
109
+ // Validate origin matches the wallet URL.
110
+ const walletOrigin = new URL(walletUrl).origin;
111
+ if (event.origin !== walletOrigin) {return;}
112
+
113
+ const { type } = event.data ?? {};
114
+
115
+ if (type === 'dweb-connect-loaded') {
116
+ // Wallet is ready — send the authorization request.
117
+ popup.postMessage({
118
+ type : 'dweb-connect-authorization-request',
119
+ did,
120
+ permissions : permissionRequests,
121
+ }, walletOrigin);
122
+ return;
123
+ }
124
+
125
+ if (type === 'dweb-connect-authorization-response') {
126
+ clearInterval(pollClosed);
127
+ cleanup();
128
+
129
+ const { delegateDid, grants } = event.data;
130
+
131
+ if (!delegateDid || !grants) {
132
+ // User denied the request.
133
+ resolve(undefined);
134
+ return;
135
+ }
136
+
137
+ resolve({
138
+ delegatePortableDid : delegateDid as PortableDid,
139
+ delegateGrants : grants as DwnDataEncodedRecordsWriteMessage[],
140
+ connectedDid : did ?? delegateDid.uri,
141
+ });
142
+ }
143
+ };
144
+
145
+ window.addEventListener('message', onMessage);
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Probe whether a wallet supports a specific DID via a hidden iframe.
151
+ *
152
+ * Sends a `dweb-connect-support-request` message to the wallet and
153
+ * waits for a `dweb-connect-support-response`. Useful for checking
154
+ * multiple wallet URLs to find the one that manages a given DID.
155
+ *
156
+ * @param walletUrl - Base URL of the wallet to probe.
157
+ * @param did - The DID to check.
158
+ * @param timeout - Probe timeout in ms. Default: 5_000 (5 seconds).
159
+ * @returns `true` if the wallet manages the DID, `false` otherwise.
160
+ */
161
+ async function probeWalletSupport(
162
+ walletUrl: string,
163
+ did: string,
164
+ timeout = 5_000,
165
+ ): Promise<boolean> {
166
+ if (typeof window === 'undefined') {return false;}
167
+
168
+ return new Promise<boolean>((resolve) => {
169
+ const iframe = document.createElement('iframe');
170
+ iframe.style.display = 'none';
171
+ iframe.src = walletUrl;
172
+
173
+ let settled = false;
174
+
175
+ const cleanup = (): void => {
176
+ settled = true;
177
+ window.removeEventListener('message', onMessage);
178
+ try { document.body.removeChild(iframe); } catch { /* best effort */ }
179
+ };
180
+
181
+ const timeoutId = setTimeout(() => {
182
+ if (!settled) {
183
+ cleanup();
184
+ resolve(false);
185
+ }
186
+ }, timeout);
187
+
188
+ const onMessage = (event: MessageEvent): void => {
189
+ const walletOrigin = new URL(walletUrl).origin;
190
+ if (event.origin !== walletOrigin) { return; }
191
+
192
+ const { type, supported } = event.data ?? {};
193
+ if (type === 'dweb-connect-support-response') {
194
+ clearTimeout(timeoutId);
195
+ cleanup();
196
+ resolve(!!supported);
197
+ }
198
+ };
199
+
200
+ window.addEventListener('message', onMessage);
201
+
202
+ iframe.addEventListener('load', () => {
203
+ const walletOrigin = new URL(walletUrl).origin;
204
+ iframe.contentWindow?.postMessage({
205
+ type: 'dweb-connect-support-request',
206
+ did,
207
+ }, walletOrigin);
208
+ });
209
+
210
+ document.body.appendChild(iframe);
211
+ });
212
+ }
213
+
214
+ export const DWebConnect = { initClient, probeWalletSupport };
package/src/index.ts CHANGED
@@ -1 +1,5 @@
1
- export * from './web-features.js';
1
+ export * from './web-features.js';
2
+ export { BrowserConnectHandler, DEFAULT_WALLETS } from './browser-connect-handler.js';
3
+ export type { BrowserConnectHandlerOptions, WalletOption } from './browser-connect-handler.js';
4
+ export { DWebConnect } from './dweb-connect-client.js';
5
+ export type { DWebConnectClientOptions } from './dweb-connect-client.js';