@aztec/wallet-sdk 0.0.1-commit.f295ac2 → 0.0.1-commit.fc805bf
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/README.md +217 -294
- package/dest/base-wallet/base_wallet.d.ts +20 -7
- package/dest/base-wallet/base_wallet.d.ts.map +1 -1
- package/dest/base-wallet/base_wallet.js +29 -11
- package/dest/crypto.d.ts +59 -50
- package/dest/crypto.d.ts.map +1 -1
- package/dest/crypto.js +202 -108
- package/dest/emoji_alphabet.d.ts +35 -0
- package/dest/emoji_alphabet.d.ts.map +1 -0
- package/dest/emoji_alphabet.js +299 -0
- package/dest/extension/handlers/background_connection_handler.d.ts +158 -0
- package/dest/extension/handlers/background_connection_handler.d.ts.map +1 -0
- package/dest/extension/handlers/background_connection_handler.js +258 -0
- package/dest/extension/handlers/content_script_connection_handler.d.ts +56 -0
- package/dest/extension/handlers/content_script_connection_handler.d.ts.map +1 -0
- package/dest/extension/handlers/content_script_connection_handler.js +174 -0
- package/dest/extension/handlers/index.d.ts +12 -0
- package/dest/extension/handlers/index.d.ts.map +1 -0
- package/dest/extension/handlers/index.js +10 -0
- package/dest/extension/handlers/internal_message_types.d.ts +63 -0
- package/dest/extension/handlers/internal_message_types.d.ts.map +1 -0
- package/dest/extension/handlers/internal_message_types.js +22 -0
- package/dest/extension/provider/extension_provider.d.ts +107 -0
- package/dest/extension/provider/extension_provider.d.ts.map +1 -0
- package/dest/extension/provider/extension_provider.js +160 -0
- package/dest/extension/provider/extension_wallet.d.ts +131 -0
- package/dest/extension/provider/extension_wallet.d.ts.map +1 -0
- package/dest/{providers/extension → extension/provider}/extension_wallet.js +48 -95
- package/dest/extension/provider/index.d.ts +3 -0
- package/dest/extension/provider/index.d.ts.map +1 -0
- package/dest/{providers/extension → extension/provider}/index.js +0 -2
- package/dest/manager/index.d.ts +2 -8
- package/dest/manager/index.d.ts.map +1 -1
- package/dest/manager/index.js +0 -6
- package/dest/manager/types.d.ts +88 -6
- package/dest/manager/types.d.ts.map +1 -1
- package/dest/manager/types.js +17 -1
- package/dest/manager/wallet_manager.d.ts +50 -7
- package/dest/manager/wallet_manager.d.ts.map +1 -1
- package/dest/manager/wallet_manager.js +174 -44
- package/dest/types.d.ts +43 -12
- package/dest/types.d.ts.map +1 -1
- package/dest/types.js +3 -2
- package/package.json +10 -9
- package/src/base-wallet/base_wallet.ts +45 -20
- package/src/crypto.ts +237 -113
- package/src/emoji_alphabet.ts +317 -0
- package/src/extension/handlers/background_connection_handler.ts +423 -0
- package/src/extension/handlers/content_script_connection_handler.ts +246 -0
- package/src/extension/handlers/index.ts +25 -0
- package/src/extension/handlers/internal_message_types.ts +69 -0
- package/src/extension/provider/extension_provider.ts +233 -0
- package/src/{providers/extension → extension/provider}/extension_wallet.ts +52 -110
- package/src/extension/provider/index.ts +7 -0
- package/src/manager/index.ts +2 -10
- package/src/manager/types.ts +91 -5
- package/src/manager/wallet_manager.ts +192 -46
- package/src/types.ts +44 -10
- package/dest/providers/extension/extension_provider.d.ts +0 -63
- package/dest/providers/extension/extension_provider.d.ts.map +0 -1
- package/dest/providers/extension/extension_provider.js +0 -124
- package/dest/providers/extension/extension_wallet.d.ts +0 -155
- package/dest/providers/extension/extension_wallet.d.ts.map +0 -1
- package/dest/providers/extension/index.d.ts +0 -6
- package/dest/providers/extension/index.d.ts.map +0 -1
- package/src/providers/extension/extension_provider.ts +0 -167
- package/src/providers/extension/index.ts +0 -5
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { decrypt, deriveSessionKeys, encrypt, exportPublicKey, generateKeyPair, importPublicKey } from '../../crypto.js';
|
|
2
|
+
import { WalletMessageType } from '../../types.js';
|
|
3
|
+
import { InternalMessageType, MessageOrigin } from './internal_message_types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Handles wallet session flow in the extension background script.
|
|
6
|
+
*
|
|
7
|
+
* This class manages:
|
|
8
|
+
* - Pending discovery requests (before user approval)
|
|
9
|
+
* - Active sessions (after key exchange)
|
|
10
|
+
* - Per-session ECDH key exchange
|
|
11
|
+
* - Message encryption/decryption
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const handler = new BackgroundConnectionHandler(
|
|
16
|
+
* {
|
|
17
|
+
* walletId: 'my-wallet',
|
|
18
|
+
* walletName: 'My Wallet',
|
|
19
|
+
* walletVersion: '1.0.0',
|
|
20
|
+
* },
|
|
21
|
+
* {
|
|
22
|
+
* sendToTab: (tabId, message) => browser.tabs.sendMessage(tabId, message),
|
|
23
|
+
* addContentListener: (handler) => browser.runtime.onMessage.addListener(handler),
|
|
24
|
+
* },
|
|
25
|
+
* {
|
|
26
|
+
* onPendingDiscovery: (discovery) => updateBadge(),
|
|
27
|
+
* onSessionEstablished: (session) => console.log('Connected:', session.sessionId),
|
|
28
|
+
* onWalletMessage: (session, message) => nativePort.postMessage(message),
|
|
29
|
+
* }
|
|
30
|
+
* );
|
|
31
|
+
*
|
|
32
|
+
* handler.initialize();
|
|
33
|
+
* ```
|
|
34
|
+
*/ export class BackgroundConnectionHandler {
|
|
35
|
+
config;
|
|
36
|
+
transport;
|
|
37
|
+
callbacks;
|
|
38
|
+
pendingDiscoveries;
|
|
39
|
+
activeSessions;
|
|
40
|
+
constructor(config, transport, callbacks = {}){
|
|
41
|
+
this.config = config;
|
|
42
|
+
this.transport = transport;
|
|
43
|
+
this.callbacks = callbacks;
|
|
44
|
+
this.pendingDiscoveries = new Map();
|
|
45
|
+
this.activeSessions = new Map();
|
|
46
|
+
this.handleMessage = (message, sender)=>{
|
|
47
|
+
const msg = message;
|
|
48
|
+
if (msg.origin !== MessageOrigin.CONTENT_SCRIPT) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const tabId = sender.tab?.id;
|
|
52
|
+
const tabOrigin = sender.tab?.url ? new URL(sender.tab.url).origin : 'unknown';
|
|
53
|
+
if (!tabId) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const { type, sessionId, content } = msg;
|
|
57
|
+
switch(type){
|
|
58
|
+
case InternalMessageType.DISCOVERY_REQUEST:
|
|
59
|
+
this.handleDiscoveryRequest(content, tabId, tabOrigin);
|
|
60
|
+
break;
|
|
61
|
+
case InternalMessageType.KEY_EXCHANGE_REQUEST:
|
|
62
|
+
if (sessionId) {
|
|
63
|
+
this.handleKeyExchangeRequest(sessionId, content).catch(()=>{
|
|
64
|
+
// Key exchange failed - session won't be established
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
case InternalMessageType.DISCONNECT_REQUEST:
|
|
69
|
+
if (sessionId) {
|
|
70
|
+
this.terminateSession(sessionId);
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case InternalMessageType.SECURE_MESSAGE:
|
|
74
|
+
if (sessionId) {
|
|
75
|
+
void this.handleEncryptedMessage(sessionId, content);
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
initialize() {
|
|
82
|
+
this.transport.addContentListener(this.handleMessage);
|
|
83
|
+
}
|
|
84
|
+
handleMessage;
|
|
85
|
+
getWalletInfo() {
|
|
86
|
+
return {
|
|
87
|
+
id: this.config.walletId,
|
|
88
|
+
name: this.config.walletName,
|
|
89
|
+
version: this.config.walletVersion,
|
|
90
|
+
icon: this.config.walletIcon
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
handleDiscoveryRequest(request, tabId, origin) {
|
|
94
|
+
const discovery = {
|
|
95
|
+
requestId: request.requestId,
|
|
96
|
+
appId: request.appId,
|
|
97
|
+
origin,
|
|
98
|
+
chainInfo: request.chainInfo,
|
|
99
|
+
tabId,
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
status: 'pending'
|
|
102
|
+
};
|
|
103
|
+
this.pendingDiscoveries.set(request.requestId, discovery);
|
|
104
|
+
this.callbacks.onPendingDiscovery?.(discovery);
|
|
105
|
+
}
|
|
106
|
+
approveDiscovery(requestId) {
|
|
107
|
+
const discovery = this.pendingDiscoveries.get(requestId);
|
|
108
|
+
if (!discovery || discovery.status !== 'pending') {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
// The discovery requestId becomes our sessionId
|
|
112
|
+
// This is what will be used internally to correlate
|
|
113
|
+
// content<->background messages
|
|
114
|
+
const sessionId = requestId;
|
|
115
|
+
discovery.status = 'approved';
|
|
116
|
+
this.transport.sendToTab(discovery.tabId, {
|
|
117
|
+
origin: MessageOrigin.BACKGROUND,
|
|
118
|
+
type: InternalMessageType.DISCOVERY_APPROVED,
|
|
119
|
+
sessionId,
|
|
120
|
+
content: this.getWalletInfo()
|
|
121
|
+
});
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
rejectDiscovery(requestId) {
|
|
125
|
+
const discovery = this.pendingDiscoveries.get(requestId);
|
|
126
|
+
if (!discovery || discovery.status !== 'pending') {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
discovery.status = 'rejected';
|
|
130
|
+
this.pendingDiscoveries.delete(requestId);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
async handleKeyExchangeRequest(sessionId, request) {
|
|
134
|
+
const discovery = this.pendingDiscoveries.get(sessionId);
|
|
135
|
+
if (!discovery || discovery.status !== 'approved') {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const keyPair = await generateKeyPair();
|
|
140
|
+
const publicKey = await exportPublicKey(keyPair.publicKey);
|
|
141
|
+
const appPublicKey = await importPublicKey(request.publicKey);
|
|
142
|
+
const sessionKeys = await deriveSessionKeys(keyPair, appPublicKey, false);
|
|
143
|
+
const session = {
|
|
144
|
+
sessionId,
|
|
145
|
+
sharedKey: sessionKeys.encryptionKey,
|
|
146
|
+
verificationHash: sessionKeys.verificationHash,
|
|
147
|
+
tabId: discovery.tabId,
|
|
148
|
+
origin: discovery.origin,
|
|
149
|
+
appId: discovery.appId,
|
|
150
|
+
connectedAt: Date.now(),
|
|
151
|
+
chainInfo: discovery.chainInfo
|
|
152
|
+
};
|
|
153
|
+
this.activeSessions.set(sessionId, session);
|
|
154
|
+
this.pendingDiscoveries.delete(sessionId);
|
|
155
|
+
const response = {
|
|
156
|
+
type: WalletMessageType.KEY_EXCHANGE_RESPONSE,
|
|
157
|
+
requestId: sessionId,
|
|
158
|
+
publicKey
|
|
159
|
+
};
|
|
160
|
+
this.transport.sendToTab(discovery.tabId, {
|
|
161
|
+
origin: MessageOrigin.BACKGROUND,
|
|
162
|
+
type: InternalMessageType.KEY_EXCHANGE_RESPONSE,
|
|
163
|
+
sessionId,
|
|
164
|
+
content: response
|
|
165
|
+
});
|
|
166
|
+
this.callbacks.onSessionEstablished?.(session);
|
|
167
|
+
} catch {
|
|
168
|
+
// Key exchange failed silently - session won't be established
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async handleEncryptedMessage(sessionId, encrypted) {
|
|
172
|
+
const session = this.activeSessions.get(sessionId);
|
|
173
|
+
if (!session) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const message = await decrypt(session.sharedKey, encrypted);
|
|
178
|
+
this.callbacks.onWalletMessage?.(session, message);
|
|
179
|
+
} catch {
|
|
180
|
+
// Decryption failed - ignore malformed message
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async sendResponse(sessionId, response) {
|
|
184
|
+
const session = this.activeSessions.get(sessionId);
|
|
185
|
+
if (!session) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const encrypted = await encrypt(session.sharedKey, JSON.stringify(response));
|
|
190
|
+
this.transport.sendToTab(session.tabId, {
|
|
191
|
+
origin: MessageOrigin.BACKGROUND,
|
|
192
|
+
type: InternalMessageType.SECURE_RESPONSE,
|
|
193
|
+
sessionId,
|
|
194
|
+
content: encrypted
|
|
195
|
+
});
|
|
196
|
+
} catch {
|
|
197
|
+
// Encryption failed - response won't be sent
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
terminateSession(sessionId) {
|
|
201
|
+
const session = this.activeSessions.get(sessionId);
|
|
202
|
+
if (session) {
|
|
203
|
+
// Notify the content script (and ultimately the dApp) that the session is disconnected
|
|
204
|
+
this.transport.sendToTab(session.tabId, {
|
|
205
|
+
origin: MessageOrigin.BACKGROUND,
|
|
206
|
+
type: InternalMessageType.SESSION_DISCONNECTED,
|
|
207
|
+
sessionId
|
|
208
|
+
});
|
|
209
|
+
this.activeSessions.delete(sessionId);
|
|
210
|
+
this.callbacks.onSessionTerminated?.(sessionId);
|
|
211
|
+
// Restore discovery to approved state so user can retry key exchange
|
|
212
|
+
const discovery = {
|
|
213
|
+
requestId: sessionId,
|
|
214
|
+
appId: session.appId,
|
|
215
|
+
origin: session.origin,
|
|
216
|
+
chainInfo: session.chainInfo,
|
|
217
|
+
tabId: session.tabId,
|
|
218
|
+
timestamp: Date.now(),
|
|
219
|
+
status: 'approved'
|
|
220
|
+
};
|
|
221
|
+
this.pendingDiscoveries.set(sessionId, discovery);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
terminateForTab(tabId) {
|
|
225
|
+
for (const [sessionId, session] of this.activeSessions){
|
|
226
|
+
if (session.tabId === tabId) {
|
|
227
|
+
this.terminateSession(sessionId);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const [requestId, discovery] of this.pendingDiscoveries){
|
|
231
|
+
if (discovery.tabId === tabId) {
|
|
232
|
+
this.pendingDiscoveries.delete(requestId);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
clearAll() {
|
|
237
|
+
for (const sessionId of this.activeSessions.keys()){
|
|
238
|
+
this.callbacks.onSessionTerminated?.(sessionId);
|
|
239
|
+
}
|
|
240
|
+
this.activeSessions.clear();
|
|
241
|
+
this.pendingDiscoveries.clear();
|
|
242
|
+
}
|
|
243
|
+
getPendingDiscoveries() {
|
|
244
|
+
return Array.from(this.pendingDiscoveries.values()).filter((d)=>d.status === 'pending');
|
|
245
|
+
}
|
|
246
|
+
getPendingDiscoveryCount() {
|
|
247
|
+
return this.getPendingDiscoveries().length;
|
|
248
|
+
}
|
|
249
|
+
getActiveSessions() {
|
|
250
|
+
return Array.from(this.activeSessions.values());
|
|
251
|
+
}
|
|
252
|
+
getSession(sessionId) {
|
|
253
|
+
return this.activeSessions.get(sessionId);
|
|
254
|
+
}
|
|
255
|
+
getPendingDiscovery(requestId) {
|
|
256
|
+
return this.pendingDiscoveries.get(requestId);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type BackgroundMessage, type ContentScriptMessage } from './internal_message_types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Transport interface for content script communication.
|
|
4
|
+
*/
|
|
5
|
+
export interface ContentScriptTransport {
|
|
6
|
+
/**
|
|
7
|
+
* Send a message to the background script.
|
|
8
|
+
* Typically `browser.runtime.sendMessage`.
|
|
9
|
+
*/
|
|
10
|
+
sendToBackground: (message: ContentScriptMessage) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Register a listener for messages from the background script.
|
|
13
|
+
* Typically `browser.runtime.onMessage.addListener`.
|
|
14
|
+
*/
|
|
15
|
+
addBackgroundListener: (handler: (message: BackgroundMessage) => void) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Handles wallet connection flow in the extension content script.
|
|
19
|
+
*
|
|
20
|
+
* This class manages:
|
|
21
|
+
* - Listening for discovery requests from the page
|
|
22
|
+
* - Creating MessageChannels after discovery approval
|
|
23
|
+
* - Relaying key exchange messages between page and background
|
|
24
|
+
* - Relaying encrypted messages between page and background
|
|
25
|
+
*
|
|
26
|
+
* The content script acts as a pure relay - it never has access to
|
|
27
|
+
* private keys or shared secrets.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const handler = new ContentScriptConnectionHandler({
|
|
32
|
+
* sendToBackground: (message) => browser.runtime.sendMessage(message),
|
|
33
|
+
* addBackgroundListener: (handler) => browser.runtime.onMessage.addListener(handler),
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* handler.start();
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare class ContentScriptConnectionHandler {
|
|
40
|
+
private transport;
|
|
41
|
+
private ports;
|
|
42
|
+
private listening;
|
|
43
|
+
private pageMessageHandler;
|
|
44
|
+
constructor(transport: ContentScriptTransport);
|
|
45
|
+
start(): void;
|
|
46
|
+
private handleBackgroundMessage;
|
|
47
|
+
private handleDiscoveryRequest;
|
|
48
|
+
private handleDiscoveryApproved;
|
|
49
|
+
private handleKeyExchangeResponse;
|
|
50
|
+
private handleSecureResponse;
|
|
51
|
+
private handleSessionDisconnected;
|
|
52
|
+
closeConnection(sessionId: string): void;
|
|
53
|
+
closeAllConnections(): void;
|
|
54
|
+
getConnectionCount(): number;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGVudF9zY3JpcHRfY29ubmVjdGlvbl9oYW5kbGVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvZXh0ZW5zaW9uL2hhbmRsZXJzL2NvbnRlbnRfc2NyaXB0X2Nvbm5lY3Rpb25faGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxPQUFPLEVBQ0wsS0FBSyxpQkFBaUIsRUFDdEIsS0FBSyxvQkFBb0IsRUFHMUIsTUFBTSw2QkFBNkIsQ0FBQztBQW9DckM7O0dBRUc7QUFDSCxNQUFNLFdBQVcsc0JBQXNCO0lBQ3JDOzs7T0FHRztJQUNILGdCQUFnQixFQUFFLENBQUMsT0FBTyxFQUFFLG9CQUFvQixLQUFLLElBQUksQ0FBQztJQUUxRDs7O09BR0c7SUFDSCxxQkFBcUIsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDLE9BQU8sRUFBRSxpQkFBaUIsS0FBSyxJQUFJLEtBQUssSUFBSSxDQUFDO0NBQ2hGO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXFCRztBQUNILHFCQUFhLDhCQUE4QjtJQUs3QixPQUFPLENBQUMsU0FBUztJQUo3QixPQUFPLENBQUMsS0FBSyxDQUFxQztJQUNsRCxPQUFPLENBQUMsU0FBUyxDQUFTO0lBQzFCLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBZ0Q7SUFFMUUsWUFBb0IsU0FBUyxFQUFFLHNCQUFzQixFQUFJO0lBRXpELEtBQUssSUFBSSxJQUFJLENBOEJaO0lBRUQsT0FBTyxDQUFDLHVCQUF1QixDQXFCN0I7SUFFRixPQUFPLENBQUMsc0JBQXNCO0lBUTlCLE9BQU8sQ0FBQyx1QkFBdUI7SUFpRC9CLE9BQU8sQ0FBQyx5QkFBeUI7SUFRakMsT0FBTyxDQUFDLG9CQUFvQjtJQVE1QixPQUFPLENBQUMseUJBQXlCO0lBVWpDLGVBQWUsQ0FBQyxTQUFTLEVBQUUsTUFBTSxHQUFHLElBQUksQ0FNdkM7SUFFRCxtQkFBbUIsSUFBSSxJQUFJLENBSzFCO0lBRUQsa0JBQWtCLElBQUksTUFBTSxDQUUzQjtDQUNGIn0=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content_script_connection_handler.d.ts","sourceRoot":"","sources":["../../../src/extension/handlers/content_script_connection_handler.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAG1B,MAAM,6BAA6B,CAAC;AAoCrC;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAE1D;;;OAGG;IACH,qBAAqB,EAAE,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,KAAK,IAAI,CAAC;CAChF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,8BAA8B;IAK7B,OAAO,CAAC,SAAS;IAJ7B,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,kBAAkB,CAAgD;IAE1E,YAAoB,SAAS,EAAE,sBAAsB,EAAI;IAEzD,KAAK,IAAI,IAAI,CA8BZ;IAED,OAAO,CAAC,uBAAuB,CAqB7B;IAEF,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,uBAAuB;IAiD/B,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,yBAAyB;IAUjC,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAMvC;IAED,mBAAmB,IAAI,IAAI,CAK1B;IAED,kBAAkB,IAAI,MAAM,CAE3B;CACF"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { WalletMessageType } from '../../types.js';
|
|
2
|
+
import { InternalMessageType, MessageOrigin } from './internal_message_types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Handles wallet connection flow in the extension content script.
|
|
5
|
+
*
|
|
6
|
+
* This class manages:
|
|
7
|
+
* - Listening for discovery requests from the page
|
|
8
|
+
* - Creating MessageChannels after discovery approval
|
|
9
|
+
* - Relaying key exchange messages between page and background
|
|
10
|
+
* - Relaying encrypted messages between page and background
|
|
11
|
+
*
|
|
12
|
+
* The content script acts as a pure relay - it never has access to
|
|
13
|
+
* private keys or shared secrets.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const handler = new ContentScriptConnectionHandler({
|
|
18
|
+
* sendToBackground: (message) => browser.runtime.sendMessage(message),
|
|
19
|
+
* addBackgroundListener: (handler) => browser.runtime.onMessage.addListener(handler),
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* handler.start();
|
|
23
|
+
* ```
|
|
24
|
+
*/ export class ContentScriptConnectionHandler {
|
|
25
|
+
transport;
|
|
26
|
+
ports;
|
|
27
|
+
listening;
|
|
28
|
+
pageMessageHandler;
|
|
29
|
+
constructor(transport){
|
|
30
|
+
this.transport = transport;
|
|
31
|
+
this.ports = new Map();
|
|
32
|
+
this.listening = false;
|
|
33
|
+
this.pageMessageHandler = null;
|
|
34
|
+
this.handleBackgroundMessage = (message)=>{
|
|
35
|
+
if (message.origin !== MessageOrigin.BACKGROUND) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const { type, sessionId, content } = message;
|
|
39
|
+
switch(type){
|
|
40
|
+
case InternalMessageType.DISCOVERY_APPROVED:
|
|
41
|
+
this.handleDiscoveryApproved(sessionId, content);
|
|
42
|
+
break;
|
|
43
|
+
case InternalMessageType.KEY_EXCHANGE_RESPONSE:
|
|
44
|
+
this.handleKeyExchangeResponse(sessionId, content);
|
|
45
|
+
break;
|
|
46
|
+
case InternalMessageType.SECURE_RESPONSE:
|
|
47
|
+
this.handleSecureResponse(sessionId, content);
|
|
48
|
+
break;
|
|
49
|
+
case InternalMessageType.SESSION_DISCONNECTED:
|
|
50
|
+
this.handleSessionDisconnected(sessionId);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
start() {
|
|
56
|
+
if (this.listening) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.transport.addBackgroundListener(this.handleBackgroundMessage);
|
|
60
|
+
this.pageMessageHandler = (event)=>{
|
|
61
|
+
if (event.source !== window) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let data;
|
|
65
|
+
try {
|
|
66
|
+
data = JSON.parse(event.data);
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!data?.type) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (data.type === WalletMessageType.DISCOVERY) {
|
|
74
|
+
this.handleDiscoveryRequest(data);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
window.addEventListener('message', this.pageMessageHandler);
|
|
78
|
+
this.listening = true;
|
|
79
|
+
}
|
|
80
|
+
handleBackgroundMessage;
|
|
81
|
+
handleDiscoveryRequest(request) {
|
|
82
|
+
this.transport.sendToBackground({
|
|
83
|
+
origin: MessageOrigin.CONTENT_SCRIPT,
|
|
84
|
+
type: InternalMessageType.DISCOVERY_REQUEST,
|
|
85
|
+
content: request
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
handleDiscoveryApproved(sessionId, walletInfo) {
|
|
89
|
+
const channel = new MessageChannel();
|
|
90
|
+
this.ports.set(sessionId, {
|
|
91
|
+
port: channel.port1,
|
|
92
|
+
sessionId
|
|
93
|
+
});
|
|
94
|
+
channel.port1.onmessage = (event)=>{
|
|
95
|
+
const data = event.data;
|
|
96
|
+
switch(data?.type){
|
|
97
|
+
case WalletMessageType.KEY_EXCHANGE_REQUEST:
|
|
98
|
+
this.transport.sendToBackground({
|
|
99
|
+
origin: MessageOrigin.CONTENT_SCRIPT,
|
|
100
|
+
type: InternalMessageType.KEY_EXCHANGE_REQUEST,
|
|
101
|
+
sessionId,
|
|
102
|
+
content: data
|
|
103
|
+
});
|
|
104
|
+
break;
|
|
105
|
+
case WalletMessageType.DISCONNECT:
|
|
106
|
+
this.transport.sendToBackground({
|
|
107
|
+
origin: MessageOrigin.CONTENT_SCRIPT,
|
|
108
|
+
type: InternalMessageType.DISCONNECT_REQUEST,
|
|
109
|
+
sessionId,
|
|
110
|
+
content: data
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
default:
|
|
114
|
+
this.transport.sendToBackground({
|
|
115
|
+
origin: MessageOrigin.CONTENT_SCRIPT,
|
|
116
|
+
type: InternalMessageType.SECURE_MESSAGE,
|
|
117
|
+
sessionId,
|
|
118
|
+
content: data
|
|
119
|
+
});
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
channel.port1.start();
|
|
124
|
+
const response = {
|
|
125
|
+
type: WalletMessageType.DISCOVERY_RESPONSE,
|
|
126
|
+
requestId: sessionId,
|
|
127
|
+
walletInfo
|
|
128
|
+
};
|
|
129
|
+
window.postMessage(JSON.stringify(response), '*', [
|
|
130
|
+
channel.port2
|
|
131
|
+
]);
|
|
132
|
+
}
|
|
133
|
+
handleKeyExchangeResponse(sessionId, response) {
|
|
134
|
+
const connection = this.ports.get(sessionId);
|
|
135
|
+
if (!connection) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
connection.port.postMessage(response);
|
|
139
|
+
}
|
|
140
|
+
handleSecureResponse(sessionId, content) {
|
|
141
|
+
const connection = this.ports.get(sessionId);
|
|
142
|
+
if (!connection) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
connection.port.postMessage(content);
|
|
146
|
+
}
|
|
147
|
+
handleSessionDisconnected(sessionId) {
|
|
148
|
+
const connection = this.ports.get(sessionId);
|
|
149
|
+
if (!connection) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
connection.port.postMessage({
|
|
153
|
+
type: WalletMessageType.DISCONNECT
|
|
154
|
+
});
|
|
155
|
+
connection.port.close();
|
|
156
|
+
this.ports.delete(sessionId);
|
|
157
|
+
}
|
|
158
|
+
closeConnection(sessionId) {
|
|
159
|
+
const connection = this.ports.get(sessionId);
|
|
160
|
+
if (connection) {
|
|
161
|
+
connection.port.close();
|
|
162
|
+
this.ports.delete(sessionId);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
closeAllConnections() {
|
|
166
|
+
for (const [sessionId, connection] of this.ports){
|
|
167
|
+
connection.port.close();
|
|
168
|
+
this.ports.delete(sessionId);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
getConnectionCount() {
|
|
172
|
+
return this.ports.size;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe exports for extension background and content scripts.
|
|
3
|
+
*
|
|
4
|
+
* This module contains ONLY handlers that work in service worker/content script
|
|
5
|
+
* environments without Node.js polyfills.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
export { BackgroundConnectionHandler, type PendingDiscovery, type ActiveSession, type DiscoveryStatus, type BackgroundConnectionCallbacks, type BackgroundConnectionConfig, type BackgroundTransport, } from './background_connection_handler.js';
|
|
10
|
+
export { ContentScriptConnectionHandler, type ContentScriptTransport } from './content_script_connection_handler.js';
|
|
11
|
+
export { type ContentScriptMessage, type BackgroundMessage, type MessageSender, type MessageOriginType, MessageOrigin, } from './internal_message_types.js';
|
|
12
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9leHRlbnNpb24vaGFuZGxlcnMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7R0FPRztBQUNILE9BQU8sRUFDTCwyQkFBMkIsRUFDM0IsS0FBSyxnQkFBZ0IsRUFDckIsS0FBSyxhQUFhLEVBQ2xCLEtBQUssZUFBZSxFQUNwQixLQUFLLDZCQUE2QixFQUNsQyxLQUFLLDBCQUEwQixFQUMvQixLQUFLLG1CQUFtQixHQUN6QixNQUFNLG9DQUFvQyxDQUFDO0FBQzVDLE9BQU8sRUFBRSw4QkFBOEIsRUFBRSxLQUFLLHNCQUFzQixFQUFFLE1BQU0sd0NBQXdDLENBQUM7QUFDckgsT0FBTyxFQUNMLEtBQUssb0JBQW9CLEVBQ3pCLEtBQUssaUJBQWlCLEVBQ3RCLEtBQUssYUFBYSxFQUNsQixLQUFLLGlCQUFpQixFQUN0QixhQUFhLEdBQ2QsTUFBTSw2QkFBNkIsQ0FBQyJ9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extension/handlers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,2BAA2B,EAC3B,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,6BAA6B,EAClC,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,GACzB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,8BAA8B,EAAE,KAAK,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrH,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,aAAa,GACd,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe exports for extension background and content scripts.
|
|
3
|
+
*
|
|
4
|
+
* This module contains ONLY handlers that work in service worker/content script
|
|
5
|
+
* environments without Node.js polyfills.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/ export { BackgroundConnectionHandler } from './background_connection_handler.js';
|
|
9
|
+
export { ContentScriptConnectionHandler } from './content_script_connection_handler.js';
|
|
10
|
+
export { MessageOrigin } from './internal_message_types.js';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal message types for content script ↔ background communication.
|
|
3
|
+
* These are NOT part of the public wallet protocol - they are implementation
|
|
4
|
+
* details for coordinating between extension components.
|
|
5
|
+
*/
|
|
6
|
+
export declare const InternalMessageType: {
|
|
7
|
+
readonly DISCOVERY_REQUEST: "discovery-request";
|
|
8
|
+
readonly KEY_EXCHANGE_REQUEST: "key-exchange-request";
|
|
9
|
+
readonly SECURE_MESSAGE: "secure-message";
|
|
10
|
+
readonly DISCONNECT_REQUEST: "disconnect-request";
|
|
11
|
+
readonly DISCOVERY_APPROVED: "discovery-approved";
|
|
12
|
+
readonly KEY_EXCHANGE_RESPONSE: "key-exchange-response";
|
|
13
|
+
readonly SECURE_RESPONSE: "secure-response";
|
|
14
|
+
readonly SESSION_DISCONNECTED: "session-disconnected";
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Message origins for internal extension communication.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MessageOrigin: {
|
|
20
|
+
readonly BACKGROUND: "background";
|
|
21
|
+
readonly CONTENT_SCRIPT: "content-script";
|
|
22
|
+
};
|
|
23
|
+
/** Union type of message origins. */
|
|
24
|
+
export type MessageOriginType = (typeof MessageOrigin)[keyof typeof MessageOrigin];
|
|
25
|
+
/**
|
|
26
|
+
* Message sent from content script to background.
|
|
27
|
+
*/
|
|
28
|
+
export interface ContentScriptMessage {
|
|
29
|
+
/** Message source identifier. */
|
|
30
|
+
origin: typeof MessageOrigin.CONTENT_SCRIPT;
|
|
31
|
+
/** Message type. */
|
|
32
|
+
type: string;
|
|
33
|
+
/** Optional session identifier. */
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
/** Optional message payload. */
|
|
36
|
+
content?: unknown;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Message sent from background to content script.
|
|
40
|
+
*/
|
|
41
|
+
export interface BackgroundMessage {
|
|
42
|
+
/** Message source identifier. */
|
|
43
|
+
origin: typeof MessageOrigin.BACKGROUND;
|
|
44
|
+
/** Message type. */
|
|
45
|
+
type: string;
|
|
46
|
+
/** Session identifier. */
|
|
47
|
+
sessionId: string;
|
|
48
|
+
/** Optional message payload. */
|
|
49
|
+
content?: unknown;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Sender information for messages from browser runtime.
|
|
53
|
+
*/
|
|
54
|
+
export interface MessageSender {
|
|
55
|
+
/** Tab information if available. */
|
|
56
|
+
tab?: {
|
|
57
|
+
/** Tab identifier. */
|
|
58
|
+
id?: number;
|
|
59
|
+
/** Tab URL. */
|
|
60
|
+
url?: string;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJuYWxfbWVzc2FnZV90eXBlcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2V4dGVuc2lvbi9oYW5kbGVycy9pbnRlcm5hbF9tZXNzYWdlX3R5cGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7O0dBSUc7QUFDSCxlQUFPLE1BQU0sbUJBQW1COzs7Ozs7Ozs7Q0FXdEIsQ0FBQztBQUVYOztHQUVHO0FBQ0gsZUFBTyxNQUFNLGFBQWE7OztDQUdoQixDQUFDO0FBRVgscUNBQXFDO0FBQ3JDLE1BQU0sTUFBTSxpQkFBaUIsR0FBRyxDQUFDLE9BQU8sYUFBYSxDQUFDLENBQUMsTUFBTSxPQUFPLGFBQWEsQ0FBQyxDQUFDO0FBRW5GOztHQUVHO0FBQ0gsTUFBTSxXQUFXLG9CQUFvQjtJQUNuQyxpQ0FBaUM7SUFDakMsTUFBTSxFQUFFLE9BQU8sYUFBYSxDQUFDLGNBQWMsQ0FBQztJQUM1QyxvQkFBb0I7SUFDcEIsSUFBSSxFQUFFLE1BQU0sQ0FBQztJQUNiLG1DQUFtQztJQUNuQyxTQUFTLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDbkIsZ0NBQWdDO0lBQ2hDLE9BQU8sQ0FBQyxFQUFFLE9BQU8sQ0FBQztDQUNuQjtBQUVEOztHQUVHO0FBQ0gsTUFBTSxXQUFXLGlCQUFpQjtJQUNoQyxpQ0FBaUM7SUFDakMsTUFBTSxFQUFFLE9BQU8sYUFBYSxDQUFDLFVBQVUsQ0FBQztJQUN4QyxvQkFBb0I7SUFDcEIsSUFBSSxFQUFFLE1BQU0sQ0FBQztJQUNiLDBCQUEwQjtJQUMxQixTQUFTLEVBQUUsTUFBTSxDQUFDO0lBQ2xCLGdDQUFnQztJQUNoQyxPQUFPLENBQUMsRUFBRSxPQUFPLENBQUM7Q0FDbkI7QUFFRDs7R0FFRztBQUNILE1BQU0sV0FBVyxhQUFhO0lBQzVCLG9DQUFvQztJQUNwQyxHQUFHLENBQUMsRUFBRTtRQUNKLHNCQUFzQjtRQUN0QixFQUFFLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDWixlQUFlO1FBQ2YsR0FBRyxDQUFDLEVBQUUsTUFBTSxDQUFDO0tBQ2QsQ0FBQztDQUNIIn0=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal_message_types.d.ts","sourceRoot":"","sources":["../../../src/extension/handlers/internal_message_types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;CAWtB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,aAAa;;;CAGhB,CAAC;AAEX,qCAAqC;AACrC,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,iCAAiC;IACjC,MAAM,EAAE,OAAO,aAAa,CAAC,cAAc,CAAC;IAC5C,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,iCAAiC;IACjC,MAAM,EAAE,OAAO,aAAa,CAAC,UAAU,CAAC;IACxC,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,GAAG,CAAC,EAAE;QACJ,sBAAsB;QACtB,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,eAAe;QACf,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;CACH"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal message types for content script ↔ background communication.
|
|
3
|
+
* These are NOT part of the public wallet protocol - they are implementation
|
|
4
|
+
* details for coordinating between extension components.
|
|
5
|
+
*/ export const InternalMessageType = {
|
|
6
|
+
// Content script → Background
|
|
7
|
+
DISCOVERY_REQUEST: 'discovery-request',
|
|
8
|
+
KEY_EXCHANGE_REQUEST: 'key-exchange-request',
|
|
9
|
+
SECURE_MESSAGE: 'secure-message',
|
|
10
|
+
DISCONNECT_REQUEST: 'disconnect-request',
|
|
11
|
+
// Background → Content script
|
|
12
|
+
DISCOVERY_APPROVED: 'discovery-approved',
|
|
13
|
+
KEY_EXCHANGE_RESPONSE: 'key-exchange-response',
|
|
14
|
+
SECURE_RESPONSE: 'secure-response',
|
|
15
|
+
SESSION_DISCONNECTED: 'session-disconnected'
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Message origins for internal extension communication.
|
|
19
|
+
*/ export const MessageOrigin = {
|
|
20
|
+
BACKGROUND: 'background',
|
|
21
|
+
CONTENT_SCRIPT: 'content-script'
|
|
22
|
+
};
|