@enbox/browser 0.3.39 → 0.3.40
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 +16 -16
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/browser-connect-handler.js +3 -3
- package/dist/esm/browser-connect-handler.js.map +1 -1
- package/dist/esm/dweb-connect-client.js +267 -190
- package/dist/esm/dweb-connect-client.js.map +1 -1
- package/dist/esm/dweb-connect-messages.js +54 -0
- package/dist/esm/dweb-connect-messages.js.map +1 -0
- package/dist/esm/dweb-connect-wallet.js +209 -0
- package/dist/esm/dweb-connect-wallet.js.map +1 -0
- package/dist/esm/index.js +9 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/types/dweb-connect-client.d.ts +123 -59
- package/dist/types/dweb-connect-client.d.ts.map +1 -1
- package/dist/types/dweb-connect-messages.d.ts +64 -0
- package/dist/types/dweb-connect-messages.d.ts.map +1 -0
- package/dist/types/dweb-connect-wallet.d.ts +101 -0
- package/dist/types/dweb-connect-wallet.d.ts.map +1 -0
- package/dist/types/index.d.ts +8 -4
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +9 -6
- package/src/browser-connect-handler.ts +4 -4
- package/src/dweb-connect-client.ts +327 -251
- package/src/dweb-connect-messages.ts +91 -0
- package/src/dweb-connect-wallet.ts +258 -0
- package/src/index.ts +24 -5
- package/dist/esm/dweb-connect-crypto.js +0 -112
- package/dist/esm/dweb-connect-crypto.js.map +0 -1
- package/dist/types/dweb-connect-crypto.d.ts +0 -52
- package/dist/types/dweb-connect-crypto.d.ts.map +0 -1
- package/src/dweb-connect-crypto.ts +0 -156
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DWeb Connect client —
|
|
2
|
+
* DWeb Connect client — the dapp side of the popup/postMessage connect flow.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* {@link PopupClientTransport} implements the `@enbox/connect` kernel's
|
|
5
|
+
* `ConnectTransport` over a wallet popup:
|
|
6
|
+
*
|
|
7
|
+
* 1. The dapp opens `${walletUrl}/dweb-connect` as a popup window.
|
|
8
|
+
* 2. The wallet emits a `loaded` beacon carrying a fresh ephemeral X25519
|
|
9
|
+
* public key and its origin.
|
|
10
|
+
* 3. The kernel seals the signed connect request to that key (ECDH-ES,
|
|
11
|
+
* `apv` bound to the wallet origin) and the transport posts the
|
|
12
|
+
* ciphertext to the popup.
|
|
13
|
+
* 4. The wallet posts back the sealed response JWE (or the deny token),
|
|
14
|
+
* which the kernel opens and value-checks.
|
|
15
|
+
*
|
|
16
|
+
* Only ciphertext crosses the postMessage channel. Every message is checked
|
|
17
|
+
* against the wallet origin derived from the configured wallet URL (never
|
|
18
|
+
* `'*'`), and against the popup window as `event.source`.
|
|
7
19
|
*
|
|
8
20
|
* @module
|
|
9
21
|
*/
|
|
@@ -16,207 +28,272 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
16
28
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
17
29
|
});
|
|
18
30
|
};
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
import { assertX25519PublicJwk, CONNECT_DENIED_TOKEN, ConnectClient } from '@enbox/connect';
|
|
32
|
+
import { DWEB_CONNECT_LOADED_MESSAGE_TYPE, DWEB_CONNECT_PATH, DWEB_CONNECT_REQUEST_MESSAGE_TYPE, DWEB_CONNECT_RESPONSE_MESSAGE_TYPE, getTrustedMessage, } from './dweb-connect-messages.js';
|
|
33
|
+
/** Default budget for the whole popup handshake (beacon + user consent + response). */
|
|
34
|
+
const DEFAULT_TIMEOUT_MS = 300000;
|
|
35
|
+
/** Interval at which the transport checks whether the user closed the popup. */
|
|
36
|
+
const CLOSED_POLL_INTERVAL_MS = 500;
|
|
37
|
+
/** Window name given to the wallet popup. */
|
|
38
|
+
const POPUP_WINDOW_NAME = 'enbox-dweb-connect';
|
|
39
|
+
/** Window features given to the wallet popup. */
|
|
40
|
+
const POPUP_WINDOW_FEATURES = 'width=500,height=700,menubar=no,toolbar=no,location=no,status=no';
|
|
41
|
+
/** Guards against calling browser-only entry points outside a browser. */
|
|
42
|
+
function assertBrowserEnvironment() {
|
|
43
|
+
if (typeof window === 'undefined') {
|
|
44
|
+
throw new Error('[@enbox/browser] DWeb Connect is only available in browser environments.');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Thrown when the user closes the wallet popup before the wallet finished
|
|
49
|
+
* loading (i.e. before its `loaded` beacon arrived). Callers typically treat
|
|
50
|
+
* this the same as a user denial.
|
|
51
|
+
*/
|
|
52
|
+
export class PopupWindowClosedError extends Error {
|
|
53
|
+
constructor() {
|
|
54
|
+
super('[@enbox/browser] Wallet popup was closed before the wallet finished loading.');
|
|
55
|
+
this.name = 'PopupWindowClosedError';
|
|
56
|
+
}
|
|
26
57
|
}
|
|
27
58
|
/**
|
|
28
|
-
*
|
|
59
|
+
* Popup/postMessage {@link ConnectTransport} for browser dapps.
|
|
29
60
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* 5. Popup closes.
|
|
61
|
+
* Single-use: construct a new transport per handshake. The **constructor**
|
|
62
|
+
* opens the wallet popup synchronously — so `window.open` runs inside the user
|
|
63
|
+
* gesture, ahead of the kernel's awaited key generation, and survives strict
|
|
64
|
+
* popup blockers; `requestProfile()` then awaits the wallet's `loaded` beacon;
|
|
65
|
+
* `deliverRequest()` posts the sealed request ciphertext; `awaitResponse()`
|
|
66
|
+
* resolves with the sealed response JWE, or the deny token when the user denies
|
|
67
|
+
* or closes the popup after the handshake was established.
|
|
38
68
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
69
|
+
* Security properties:
|
|
70
|
+
* - `targetOrigin` of every outbound `postMessage` is pinned to the origin
|
|
71
|
+
* derived from `walletUrl` — never `'*'`.
|
|
72
|
+
* - Inbound messages are dropped unless `event.origin` equals the pinned
|
|
73
|
+
* wallet origin AND `event.source` is the popup window this transport opened.
|
|
74
|
+
* - The beacon's `walletOrigin` field must equal the pinned origin, and its
|
|
75
|
+
* `walletEpk` must be a public-only X25519 JWK; a malformed beacon from the
|
|
76
|
+
* genuine popup fails the handshake closed.
|
|
41
77
|
*/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
78
|
+
export class PopupClientTransport {
|
|
79
|
+
constructor(options) {
|
|
80
|
+
var _a;
|
|
81
|
+
/** {@inheritDoc ConnectTransport.requiresPin} */
|
|
82
|
+
this.requiresPin = false;
|
|
83
|
+
this._settled = false;
|
|
84
|
+
this._walletEpkReceived = false;
|
|
85
|
+
assertBrowserEnvironment();
|
|
86
|
+
this._walletOrigin = new URL(options.walletUrl).origin;
|
|
87
|
+
this._timeoutMs = (_a = options.timeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_TIMEOUT_MS;
|
|
88
|
+
// Open the wallet popup SYNCHRONOUSLY, in the user-gesture call stack,
|
|
89
|
+
// before the kernel's awaited `DidJwk.create()` / `X25519.generateKey()`
|
|
90
|
+
// run. Strict popup blockers (Safari) reject a `window.open` issued once
|
|
91
|
+
// the stack has yielded to a prior `await`, so opening it here — instead of
|
|
92
|
+
// inside the awaited `requestProfile()` — is what keeps the flow usable.
|
|
93
|
+
const popupUrl = new URL(DWEB_CONNECT_PATH, options.walletUrl).toString();
|
|
94
|
+
const popup = window.open(popupUrl, POPUP_WINDOW_NAME, POPUP_WINDOW_FEATURES);
|
|
51
95
|
if (!popup) {
|
|
52
96
|
throw new Error('[@enbox/browser] Popup blocked. Allow popups for this site to connect to a wallet.');
|
|
53
97
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// Validate origin matches the wallet URL.
|
|
100
|
-
const walletOrigin = new URL(walletUrl).origin;
|
|
101
|
-
if (event.origin !== walletOrigin) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const { type } = (_a = event.data) !== null && _a !== void 0 ? _a : {};
|
|
105
|
-
if (type === 'dweb-connect-loaded') {
|
|
106
|
-
// Wallet is ready — send the authorization request with
|
|
107
|
-
// ephemeral public key and optional app metadata.
|
|
108
|
-
popup.postMessage({
|
|
109
|
-
type: 'dweb-connect-authorization-request',
|
|
110
|
-
did,
|
|
111
|
-
permissions: permissionRequests,
|
|
112
|
-
ephemeralPublicKey: dappPublicKeyBase64url,
|
|
113
|
-
appName,
|
|
114
|
-
appIcon,
|
|
115
|
-
clientMetadata: collectBrowserClientMetadata(),
|
|
116
|
-
portableIdentity,
|
|
117
|
-
}, walletOrigin);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (type === 'dweb-connect-authorization-response') {
|
|
121
|
-
clearInterval(pollClosed);
|
|
122
|
-
cleanup();
|
|
123
|
-
// If the wallet sent an explicit error, treat as denied.
|
|
124
|
-
if (event.data.error) {
|
|
125
|
-
resolve(undefined);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
// Require an encrypted response. The dapp always sends an
|
|
129
|
-
// ephemeralPublicKey, so a plaintext response means either the
|
|
130
|
-
// wallet is outdated or something intercepted the flow.
|
|
131
|
-
const encrypted = event.data.encryptedPayload;
|
|
132
|
-
if (!encrypted) {
|
|
133
|
-
resolve(undefined);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
decryptPostMessagePayload(encrypted, dappKeyPair).then((payload) => {
|
|
137
|
-
var _a, _b, _c;
|
|
138
|
-
const p = payload;
|
|
139
|
-
if (!p.delegateDid || !p.grants) {
|
|
140
|
-
resolve(undefined);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
resolve({
|
|
144
|
-
delegatePortableDid: p.delegateDid,
|
|
145
|
-
delegateGrants: p.grants,
|
|
146
|
-
connectedDid: (_b = (_a = p.connectedDid) !== null && _a !== void 0 ? _a : did) !== null && _b !== void 0 ? _b : p.delegateDid.uri,
|
|
147
|
-
sessionRevocations: (_c = p.sessionRevocations) !== null && _c !== void 0 ? _c : undefined,
|
|
148
|
-
});
|
|
149
|
-
}).catch(() => {
|
|
150
|
-
// Decryption failed — treat as denied.
|
|
151
|
-
resolve(undefined);
|
|
152
|
-
});
|
|
153
|
-
}
|
|
98
|
+
this._popup = popup;
|
|
99
|
+
this._walletEpkPromise = new Promise((resolve, reject) => {
|
|
100
|
+
this._resolveWalletEpk = resolve;
|
|
101
|
+
this._rejectWalletEpk = reject;
|
|
102
|
+
});
|
|
103
|
+
// Created up-front so a response (or deny) arriving before
|
|
104
|
+
// `awaitResponse()` is called is never dropped. The no-op catch marks
|
|
105
|
+
// early rejections (timeout, popup closed during load) as handled until
|
|
106
|
+
// the kernel attaches its own handlers via `awaitResponse()`.
|
|
107
|
+
this._responsePromise = new Promise((resolve, reject) => {
|
|
108
|
+
this._resolveResponse = resolve;
|
|
109
|
+
this._rejectResponse = reject;
|
|
110
|
+
});
|
|
111
|
+
this._responsePromise.catch(() => undefined);
|
|
112
|
+
// Listen synchronously, before the wallet page can emit its `loaded`
|
|
113
|
+
// beacon, so the beacon is never missed while the kernel awaits key
|
|
114
|
+
// generation ahead of `requestProfile()`.
|
|
115
|
+
this._messageListener = (event) => { this.onMessage(event); };
|
|
116
|
+
window.addEventListener('message', this._messageListener);
|
|
117
|
+
this._timeoutId = setTimeout(() => {
|
|
118
|
+
this.fail(new Error('[@enbox/browser] DWeb Connect timed out waiting for wallet response.'));
|
|
119
|
+
}, this._timeoutMs);
|
|
120
|
+
this._closedPollId = setInterval(() => { this.onClosedPoll(); }, CLOSED_POLL_INTERVAL_MS);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Awaits the already-open popup's `loaded` beacon, then returns the popup
|
|
124
|
+
* channel profile: ECDH-ES encryption to the beacon's ephemeral key with the
|
|
125
|
+
* pinned wallet origin as the `apv` binding, a `post_message` reply
|
|
126
|
+
* descriptor, and the client-supplied `state` correlator.
|
|
127
|
+
*
|
|
128
|
+
* The popup was opened synchronously at construction, so this method only
|
|
129
|
+
* awaits the beacon — it never issues the blocker-sensitive `window.open`.
|
|
130
|
+
*
|
|
131
|
+
* @param state - The handshake correlator minted by the {@link ConnectClient}.
|
|
132
|
+
* @throws If the user closes the popup before the beacon arrives
|
|
133
|
+
* ({@link PopupWindowClosedError}), the beacon is malformed, or on
|
|
134
|
+
* timeout.
|
|
135
|
+
*/
|
|
136
|
+
requestProfile(state) {
|
|
137
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
138
|
+
const walletEpk = yield this._walletEpkPromise;
|
|
139
|
+
return {
|
|
140
|
+
encryption: { mode: 'ecdh-es', walletEpk, walletOrigin: this._walletOrigin },
|
|
141
|
+
reply: { mode: 'post_message' },
|
|
142
|
+
state,
|
|
154
143
|
};
|
|
155
|
-
window.addEventListener('message', onMessage);
|
|
156
144
|
});
|
|
157
|
-
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Posts the sealed request ciphertext to the wallet popup with the
|
|
148
|
+
* `targetOrigin` pinned to the wallet origin.
|
|
149
|
+
*/
|
|
150
|
+
deliverRequest(jwe) {
|
|
151
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
152
|
+
this._popup.postMessage({ type: DWEB_CONNECT_REQUEST_MESSAGE_TYPE, jwe }, this._walletOrigin);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Awaits the wallet's response ciphertext: the sealed response JWE, or the
|
|
157
|
+
* deny token when the user denies in the wallet or closes the popup.
|
|
158
|
+
* Rejects on timeout.
|
|
159
|
+
*/
|
|
160
|
+
awaitResponse() {
|
|
161
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
162
|
+
return yield this._responsePromise;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
onMessage(event) {
|
|
166
|
+
const message = getTrustedMessage(event, this._walletOrigin, this._popup);
|
|
167
|
+
if (message === undefined) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (message.type === DWEB_CONNECT_LOADED_MESSAGE_TYPE) {
|
|
171
|
+
this.onLoadedBeacon(message);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (message.type === DWEB_CONNECT_RESPONSE_MESSAGE_TYPE) {
|
|
175
|
+
this.onResponseMessage(message);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
onLoadedBeacon(message) {
|
|
179
|
+
var _a;
|
|
180
|
+
if (this._settled || this._walletEpkReceived) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// The beacon passed the origin and source checks, so a malformed payload
|
|
184
|
+
// is a protocol violation by the genuine popup — fail closed. The X25519
|
|
185
|
+
// public-JWK shape check is delegated to the connect kernel.
|
|
186
|
+
let walletEpk;
|
|
187
|
+
try {
|
|
188
|
+
assertX25519PublicJwk(message.walletEpk);
|
|
189
|
+
walletEpk = message.walletEpk;
|
|
190
|
+
}
|
|
191
|
+
catch (_b) {
|
|
192
|
+
this.fail(new Error('[@enbox/browser] Wallet loaded beacon did not carry a valid X25519 public key.'));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (message.walletOrigin !== this._walletOrigin) {
|
|
196
|
+
this.fail(new Error('[@enbox/browser] Wallet loaded beacon origin does not match the configured wallet origin.'));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
this._walletEpkReceived = true;
|
|
200
|
+
(_a = this._resolveWalletEpk) === null || _a === void 0 ? void 0 : _a.call(this, walletEpk);
|
|
201
|
+
}
|
|
202
|
+
onResponseMessage(message) {
|
|
203
|
+
var _a, _b;
|
|
204
|
+
if (this._settled) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (typeof message.payload !== 'string') {
|
|
208
|
+
this.fail(new Error('[@enbox/browser] Wallet response message did not carry a ciphertext payload.'));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
this._settled = true;
|
|
212
|
+
this.cleanup();
|
|
213
|
+
// No-op on the normal path; unblocks `requestProfile()` if a response
|
|
214
|
+
// somehow arrives before the beacon.
|
|
215
|
+
(_a = this._rejectWalletEpk) === null || _a === void 0 ? void 0 : _a.call(this, new Error('[@enbox/browser] Wallet responded before the handshake was established.'));
|
|
216
|
+
(_b = this._resolveResponse) === null || _b === void 0 ? void 0 : _b.call(this, message.payload);
|
|
217
|
+
}
|
|
218
|
+
onClosedPoll() {
|
|
219
|
+
var _a;
|
|
220
|
+
if (this._settled || this._popup.closed !== true) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!this._walletEpkReceived) {
|
|
224
|
+
// Closed before the wallet ever loaded — the handshake cannot proceed.
|
|
225
|
+
this.fail(new PopupWindowClosedError());
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Closed after the handshake was established — treat as a user denial.
|
|
229
|
+
this._settled = true;
|
|
230
|
+
this.cleanup();
|
|
231
|
+
(_a = this._resolveResponse) === null || _a === void 0 ? void 0 : _a.call(this, CONNECT_DENIED_TOKEN);
|
|
232
|
+
}
|
|
233
|
+
fail(error) {
|
|
234
|
+
var _a, _b;
|
|
235
|
+
if (this._settled) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this._settled = true;
|
|
239
|
+
this.cleanup();
|
|
240
|
+
try {
|
|
241
|
+
this._popup.close();
|
|
242
|
+
}
|
|
243
|
+
catch ( /* best effort */_c) { /* best effort */ }
|
|
244
|
+
(_a = this._rejectWalletEpk) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
245
|
+
(_b = this._rejectResponse) === null || _b === void 0 ? void 0 : _b.call(this, error);
|
|
246
|
+
}
|
|
247
|
+
cleanup() {
|
|
248
|
+
clearTimeout(this._timeoutId);
|
|
249
|
+
clearInterval(this._closedPollId);
|
|
250
|
+
if (this._messageListener !== undefined) {
|
|
251
|
+
window.removeEventListener('message', this._messageListener);
|
|
252
|
+
this._messageListener = undefined;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/** Collects self-reported environment hints for the wallet's session display. */
|
|
257
|
+
function collectBrowserClientMetadata() {
|
|
258
|
+
const resolvedOptions = Intl.DateTimeFormat().resolvedOptions();
|
|
259
|
+
const languages = Array.isArray(globalThis.navigator.languages)
|
|
260
|
+
? globalThis.navigator.languages.filter((language) => typeof language === 'string' && language.length > 0)
|
|
261
|
+
: undefined;
|
|
262
|
+
return Object.assign(Object.assign({ origin: globalThis.location.origin, userAgent: globalThis.navigator.userAgent, platform: globalThis.navigator.platform, language: globalThis.navigator.language }, (languages && languages.length > 0 ? { languages } : {})), (resolvedOptions.timeZone ? { timezone: resolvedOptions.timeZone } : {}));
|
|
158
263
|
}
|
|
159
264
|
/**
|
|
160
|
-
*
|
|
265
|
+
* Runs one DWeb Connect popup handshake end-to-end: opens the wallet popup,
|
|
266
|
+
* drives the `@enbox/connect` kernel client over a {@link PopupClientTransport},
|
|
267
|
+
* and returns the delegated credentials.
|
|
161
268
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* @param walletUrl - Base URL of the wallet to probe.
|
|
167
|
-
* @param did - The DID to check.
|
|
168
|
-
* @param timeout - Probe timeout in ms. Default: 5_000 (5 seconds).
|
|
169
|
-
* @returns `true` if the wallet manages the DID, `false` otherwise.
|
|
269
|
+
* @returns The delegate credentials, or `undefined` when the user denied the
|
|
270
|
+
* request or closed the popup.
|
|
271
|
+
* @throws If the popup is blocked, the handshake times out, or the wallet
|
|
272
|
+
* returns an invalid response.
|
|
170
273
|
*/
|
|
171
|
-
function
|
|
172
|
-
return __awaiter(this,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const iframe = document.createElement('iframe');
|
|
178
|
-
iframe.style.display = 'none';
|
|
179
|
-
iframe.src = walletUrl;
|
|
180
|
-
let settled = false;
|
|
181
|
-
const cleanup = () => {
|
|
182
|
-
settled = true;
|
|
183
|
-
window.removeEventListener('message', onMessage);
|
|
184
|
-
try {
|
|
185
|
-
document.body.removeChild(iframe);
|
|
186
|
-
}
|
|
187
|
-
catch ( /* best effort */_a) { /* best effort */ }
|
|
188
|
-
};
|
|
189
|
-
const timeoutId = setTimeout(() => {
|
|
190
|
-
if (!settled) {
|
|
191
|
-
cleanup();
|
|
192
|
-
resolve(false);
|
|
193
|
-
}
|
|
194
|
-
}, timeout);
|
|
195
|
-
const onMessage = (event) => {
|
|
196
|
-
var _a;
|
|
197
|
-
const walletOrigin = new URL(walletUrl).origin;
|
|
198
|
-
if (event.origin !== walletOrigin) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
const { type, supported } = (_a = event.data) !== null && _a !== void 0 ? _a : {};
|
|
202
|
-
if (type === 'dweb-connect-support-response') {
|
|
203
|
-
clearTimeout(timeoutId);
|
|
204
|
-
cleanup();
|
|
205
|
-
resolve(!!supported);
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
window.addEventListener('message', onMessage);
|
|
209
|
-
iframe.addEventListener('load', () => {
|
|
210
|
-
var _a;
|
|
211
|
-
const walletOrigin = new URL(walletUrl).origin;
|
|
212
|
-
(_a = iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage({
|
|
213
|
-
type: 'dweb-connect-support-request',
|
|
214
|
-
did,
|
|
215
|
-
}, walletOrigin);
|
|
216
|
-
});
|
|
217
|
-
document.body.appendChild(iframe);
|
|
274
|
+
export function connectViaPopup(options) {
|
|
275
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
276
|
+
var _a;
|
|
277
|
+
const transport = new PopupClientTransport({
|
|
278
|
+
walletUrl: options.walletUrl,
|
|
279
|
+
timeoutMs: options.timeout,
|
|
218
280
|
});
|
|
281
|
+
const client = new ConnectClient({ transport });
|
|
282
|
+
try {
|
|
283
|
+
return yield client.connect({
|
|
284
|
+
appName: (_a = options.appName) !== null && _a !== void 0 ? _a : globalThis.location.host,
|
|
285
|
+
appIcon: options.appIcon,
|
|
286
|
+
clientMetadata: collectBrowserClientMetadata(),
|
|
287
|
+
permissionRequests: options.permissionRequests,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
if (error instanceof PopupWindowClosedError) {
|
|
292
|
+
// The user closed the wallet before it loaded — same as a denial.
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
219
297
|
});
|
|
220
298
|
}
|
|
221
|
-
export const DWebConnect = { initClient, probeWalletSupport };
|
|
222
299
|
//# sourceMappingURL=dweb-connect-client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dweb-connect-client.js","sourceRoot":"","sources":["../../src/dweb-connect-client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"dweb-connect-client.js","sourceRoot":"","sources":["../../src/dweb-connect-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;;;;;;;;AAWH,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE5F,OAAO,EACL,gCAAgC,EAChC,iBAAiB,EACjB,iCAAiC,EACjC,kCAAkC,EAClC,iBAAiB,GAClB,MAAM,4BAA4B,CAAC;AAEpC,uFAAuF;AACvF,MAAM,kBAAkB,GAAG,MAAO,CAAC;AAEnC,gFAAgF;AAChF,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAEpC,6CAA6C;AAC7C,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AAE/C,iDAAiD;AACjD,MAAM,qBAAqB,GAAG,kEAAkE,CAAC;AAEjG,0EAA0E;AAC1E,SAAS,wBAAwB;IAC/B,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/C;QACE,KAAK,CAAC,8EAA8E,CAAC,CAAC;QACtF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAeD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,oBAAoB;IAsB/B,YAAY,OAAoC;;QArBhD,iDAAiD;QACjC,gBAAW,GAAY,KAAK,CAAC;QAMrC,aAAQ,GAAG,KAAK,CAAC;QACjB,uBAAkB,GAAG,KAAK,CAAC;QAcjC,wBAAwB,EAAE,CAAC;QAE3B,IAAI,CAAC,aAAa,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;QACvD,IAAI,CAAC,UAAU,GAAG,MAAA,OAAO,CAAC,SAAS,mCAAI,kBAAkB,CAAC;QAE1D,uEAAuE;QACvE,yEAAyE;QACzE,yEAAyE;QACzE,4EAA4E;QAC5E,yEAAyE;QACzE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,iBAAiB,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC1E,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,iBAAiB,EAAE,qBAAqB,CAAC,CAAC;QAC9E,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAAC;QACxG,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QAEpB,IAAI,CAAC,iBAAiB,GAAG,IAAI,OAAO,CAAM,CAAC,OAAO,EAAE,MAAM,EAAQ,EAAE;YAClE,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC;YACjC,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,2DAA2D;QAC3D,sEAAsE;QACtE,wEAAwE;QACxE,8DAA8D;QAC9D,IAAI,CAAC,gBAAgB,GAAG,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAQ,EAAE;YACpE,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC;YAChC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,GAAc,EAAE,CAAC,SAAS,CAAC,CAAC;QAExD,qEAAqE;QACrE,oEAAoE;QACpE,0CAA0C;QAC1C,IAAI,CAAC,gBAAgB,GAAG,CAAC,KAAmB,EAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAClF,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAE1D,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAS,EAAE;YACtC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC,CAAC;QAC/F,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAEpB,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,GAAS,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,uBAAuB,CAAC,CAAC;IAClG,CAAC;IAED;;;;;;;;;;;;;OAaG;IACU,cAAc,CAAC,KAAa;;YACvC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC;YAE/C,OAAO;gBACL,UAAU,EAAG,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,IAAI,CAAC,aAAa,EAAE;gBAC7E,KAAK,EAAQ,EAAE,IAAI,EAAE,cAAc,EAAE;gBACrC,KAAK;aACN,CAAC;QACJ,CAAC;KAAA;IAED;;;OAGG;IACU,cAAc,CAAC,GAAW;;YACrC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,iCAAiC,EAAE,GAAG,EAAE,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QAChG,CAAC;KAAA;IAED;;;;OAIG;IACU,aAAa;;YACxB,OAAO,MAAM,IAAI,CAAC,gBAAgB,CAAC;QACrC,CAAC;KAAA;IAEO,SAAS,CAAC,KAAmB;QACnC,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1E,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAAC,OAAO;QAAC,CAAC;QAEtC,IAAI,OAAO,CAAC,IAAI,KAAK,gCAAgC,EAAE,CAAC;YACtD,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;YAC7B,OAAO;QACT,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,KAAK,kCAAkC,EAAE,CAAC;YACxD,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAEO,cAAc,CAAC,OAAgC;;QACrD,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAAC,OAAO;QAAC,CAAC;QAEzD,yEAAyE;QACzE,yEAAyE;QACzE,6DAA6D;QAC7D,IAAI,SAAc,CAAC;QACnB,IAAI,CAAC;YACH,qBAAqB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YACzC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QAChC,CAAC;QAAC,WAAM,CAAC;YACP,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,gFAAgF,CAAC,CAAC,CAAC;YACvG,OAAO;QACT,CAAC;QAED,IAAI,OAAO,CAAC,YAAY,KAAK,IAAI,CAAC,aAAa,EAAE,CAAC;YAChD,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,2FAA2F,CAAC,CAAC,CAAC;YAClH,OAAO;QACT,CAAC;QAED,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAC/B,MAAA,IAAI,CAAC,iBAAiB,qDAAG,SAAS,CAAC,CAAC;IACtC,CAAC;IAEO,iBAAiB,CAAC,OAAgC;;QACxD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAAC,OAAO;QAAC,CAAC;QAE9B,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACxC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC,CAAC;YACrG,OAAO;QACT,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,sEAAsE;QACtE,qCAAqC;QACrC,MAAA,IAAI,CAAC,gBAAgB,qDAAG,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC,CAAC;QAC9G,MAAA,IAAI,CAAC,gBAAgB,qDAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAEO,YAAY;;QAClB,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YAAC,OAAO;QAAC,CAAC;QAE7D,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC7B,uEAAuE;YACvE,IAAI,CAAC,IAAI,CAAC,IAAI,sBAAsB,EAAE,CAAC,CAAC;YACxC,OAAO;QACT,CAAC;QAED,uEAAuE;QACvE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,MAAA,IAAI,CAAC,gBAAgB,qDAAG,oBAAoB,CAAC,CAAC;IAChD,CAAC;IAEO,IAAI,CAAC,KAAY;;QACvB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAAC,OAAO;QAAC,CAAC;QAC9B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,IAAI,CAAC;YAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAAC,CAAC;QAAC,QAAQ,iBAAiB,IAAnB,CAAC,CAAC,iBAAiB,CAAC,CAAC;QACxD,MAAA,IAAI,CAAC,gBAAgB,qDAAG,KAAK,CAAC,CAAC;QAC/B,MAAA,IAAI,CAAC,eAAe,qDAAG,KAAK,CAAC,CAAC;IAChC,CAAC;IAEO,OAAO;QACb,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9B,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAClC,IAAI,IAAI,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;YACxC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAC7D,IAAI,CAAC,gBAAgB,GAAG,SAAS,CAAC;QACpC,CAAC;IACH,CAAC;CACF;AA6BD,iFAAiF;AACjF,SAAS,4BAA4B;IACnC,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,eAAe,EAAE,CAAC;IAChE,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC;QAC7D,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QAC1G,CAAC,CAAC,SAAS,CAAC;IAEd,qCACE,MAAM,EAAM,UAAU,CAAC,QAAQ,CAAC,MAAM,EACtC,SAAS,EAAG,UAAU,CAAC,SAAS,CAAC,SAAS,EAC1C,QAAQ,EAAI,UAAU,CAAC,SAAS,CAAC,QAAQ,EACzC,QAAQ,EAAI,UAAU,CAAC,SAAS,CAAC,QAAQ,IACtC,CAAC,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GACxD,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,eAAe,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAC3E;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAgB,eAAe,CAAC,OAA4B;;;QAChE,MAAM,SAAS,GAAG,IAAI,oBAAoB,CAAC;YACzC,SAAS,EAAG,OAAO,CAAC,SAAS;YAC7B,SAAS,EAAG,OAAO,CAAC,OAAO;SAC5B,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;QAEhD,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,CAAC,OAAO,CAAC;gBAC1B,OAAO,EAAc,MAAA,OAAO,CAAC,OAAO,mCAAI,UAAU,CAAC,QAAQ,CAAC,IAAI;gBAChE,OAAO,EAAc,OAAO,CAAC,OAAO;gBACpC,cAAc,EAAO,4BAA4B,EAAE;gBACnD,kBAAkB,EAAG,OAAO,CAAC,kBAAkB;aAChD,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,sBAAsB,EAAE,CAAC;gBAC5C,kEAAkE;gBAClE,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CAAA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire shapes for the DWeb Connect popup/postMessage channel.
|
|
3
|
+
*
|
|
4
|
+
* The dapp and the wallet exchange exactly three message kinds over
|
|
5
|
+
* `window.postMessage`:
|
|
6
|
+
*
|
|
7
|
+
* 1. `loaded` beacon (wallet → dapp): announces the wallet page is ready and
|
|
8
|
+
* carries a fresh ephemeral X25519 public key plus the wallet's origin.
|
|
9
|
+
* 2. `request` (dapp → wallet): the sealed connect request JWE — ciphertext
|
|
10
|
+
* only, encrypted to the beacon key with ECDH-ES and the wallet origin
|
|
11
|
+
* bound into the JWE `apv` header.
|
|
12
|
+
* 3. `response` (wallet → dapp): the sealed connect response JWE, or the
|
|
13
|
+
* literal deny token — again ciphertext only.
|
|
14
|
+
*
|
|
15
|
+
* Plaintext never crosses the channel, and both sides pin `targetOrigin`
|
|
16
|
+
* and verify `event.origin` / `event.source` on every message.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/** Path of the wallet's DWeb Connect page, opened as a popup by the dapp. */
|
|
21
|
+
export const DWEB_CONNECT_PATH = '/dweb-connect';
|
|
22
|
+
/** Message type of the wallet's `loaded` beacon (wallet → dapp). */
|
|
23
|
+
export const DWEB_CONNECT_LOADED_MESSAGE_TYPE = 'enbox-connect-loaded';
|
|
24
|
+
/** Message type carrying the sealed connect request JWE (dapp → wallet). */
|
|
25
|
+
export const DWEB_CONNECT_REQUEST_MESSAGE_TYPE = 'enbox-connect-request';
|
|
26
|
+
/** Message type carrying the sealed connect response JWE or deny token (wallet → dapp). */
|
|
27
|
+
export const DWEB_CONNECT_RESPONSE_MESSAGE_TYPE = 'enbox-connect-response';
|
|
28
|
+
/**
|
|
29
|
+
* Applies the shared inbound-message trust filter for the popup channel:
|
|
30
|
+
* pins the message to the expected `event.origin`, pins its `event.source` to
|
|
31
|
+
* the expected window, and requires a non-null object payload.
|
|
32
|
+
*
|
|
33
|
+
* Returns the payload cast to a plain record when every check passes, or
|
|
34
|
+
* `undefined` when the message must be dropped. Both the dapp-side and
|
|
35
|
+
* wallet-side transports early-return on `undefined` before inspecting any
|
|
36
|
+
* message field — this is the security-critical filter that keeps hostile
|
|
37
|
+
* origins and stray windows off the channel.
|
|
38
|
+
*/
|
|
39
|
+
export function getTrustedMessage(event, expectedOrigin, expectedSource) {
|
|
40
|
+
// Origin pinning: only messages from the expected origin count.
|
|
41
|
+
if (event.origin !== expectedOrigin) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
// Source pinning: the message must come from the expected window.
|
|
45
|
+
if (event.source !== expectedSource) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const data = event.data;
|
|
49
|
+
if (typeof data !== 'object' || data === null) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return data;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=dweb-connect-messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dweb-connect-messages.js","sourceRoot":"","sources":["../../src/dweb-connect-messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAEjD,oEAAoE;AACpE,MAAM,CAAC,MAAM,gCAAgC,GAAG,sBAAsB,CAAC;AAEvE,4EAA4E;AAC5E,MAAM,CAAC,MAAM,iCAAiC,GAAG,uBAAuB,CAAC;AAEzE,2FAA2F;AAC3F,MAAM,CAAC,MAAM,kCAAkC,GAAG,wBAAwB,CAAC;AAgC3E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAmB,EACnB,cAAsB,EACtB,cAAkC;IAElC,gEAAgE;IAChE,IAAI,KAAK,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;QAAC,OAAO,SAAS,CAAC;IAAC,CAAC;IAE1D,kEAAkE;IAClE,IAAI,KAAK,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;QAAC,OAAO,SAAS,CAAC;IAAC,CAAC;IAE1D,MAAM,IAAI,GAAY,KAAK,CAAC,IAAI,CAAC;IACjC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAAC,OAAO,SAAS,CAAC;IAAC,CAAC;IAEpE,OAAO,IAA+B,CAAC;AACzC,CAAC"}
|