@hanzo/browser-extension 1.9.31 → 1.9.33
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/package.json +2 -1
- package/src/answer/AnswerEngine.tsx +553 -0
- package/src/manifest-firefox.json +10 -2
- package/src/manifest.json +17 -2
- package/src/newtab.css +277 -0
- package/src/newtab.html +13 -0
- package/src/popup.html +28 -0
- package/dist/browser-extension/README.md +0 -43
- package/dist/browser-extension/background-firefox.js +0 -4556
- package/dist/browser-extension/background.js +0 -7245
- package/dist/browser-extension/browser-control.js +0 -1317
- package/dist/browser-extension/chrome/ai-worker.js +0 -571
- package/dist/browser-extension/chrome/background.js +0 -7245
- package/dist/browser-extension/chrome/callback.html +0 -17
- package/dist/browser-extension/chrome/content-script.js +0 -2789
- package/dist/browser-extension/chrome/icon128.png +0 -0
- package/dist/browser-extension/chrome/icon16.png +0 -0
- package/dist/browser-extension/chrome/icon48.png +0 -0
- package/dist/browser-extension/chrome/manifest.json +0 -66
- package/dist/browser-extension/chrome/popup.css +0 -761
- package/dist/browser-extension/chrome/popup.html +0 -353
- package/dist/browser-extension/chrome/popup.js +0 -1650
- package/dist/browser-extension/chrome/sidebar.css +0 -1792
- package/dist/browser-extension/chrome/sidebar.html +0 -463
- package/dist/browser-extension/chrome/sidebar.js +0 -26541
- package/dist/browser-extension/cli.js +0 -233
- package/dist/browser-extension/firefox/ai-worker.js +0 -571
- package/dist/browser-extension/firefox/background.js +0 -4556
- package/dist/browser-extension/firefox/callback.html +0 -17
- package/dist/browser-extension/firefox/content-script.js +0 -2789
- package/dist/browser-extension/firefox/icon128.png +0 -0
- package/dist/browser-extension/firefox/icon16.png +0 -0
- package/dist/browser-extension/firefox/icon48.png +0 -0
- package/dist/browser-extension/firefox/manifest.json +0 -77
- package/dist/browser-extension/firefox/popup.css +0 -761
- package/dist/browser-extension/firefox/popup.html +0 -353
- package/dist/browser-extension/firefox/popup.js +0 -1650
- package/dist/browser-extension/firefox/sidebar.css +0 -1792
- package/dist/browser-extension/firefox/sidebar.html +0 -463
- package/dist/browser-extension/firefox/sidebar.js +0 -26541
- package/dist/browser-extension/icon128.png +0 -0
- package/dist/browser-extension/icon16.png +0 -0
- package/dist/browser-extension/icon48.png +0 -0
- package/dist/browser-extension/manifest.json +0 -66
- package/dist/browser-extension/package.json +0 -41
- package/dist/browser-extension/popup.js +0 -1650
- package/dist/browser-extension/safari/Info.plist +0 -21
- package/dist/browser-extension/safari/ai-worker.js +0 -571
- package/dist/browser-extension/safari/background.js +0 -7245
- package/dist/browser-extension/safari/callback.html +0 -17
- package/dist/browser-extension/safari/content-script.js +0 -2789
- package/dist/browser-extension/safari/icon128.png +0 -0
- package/dist/browser-extension/safari/icon16.png +0 -0
- package/dist/browser-extension/safari/icon48.png +0 -0
- package/dist/browser-extension/safari/popup.css +0 -761
- package/dist/browser-extension/safari/popup.html +0 -353
- package/dist/browser-extension/safari/popup.js +0 -1650
- package/dist/browser-extension/safari/sidebar.css +0 -1792
- package/dist/browser-extension/safari/sidebar.html +0 -463
- package/dist/browser-extension/safari/sidebar.js +0 -26541
- package/dist/browser-extension/sidebar.js +0 -26541
- package/dist/browser-extension/webgpu-ai.js +0 -568
|
@@ -1,4556 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
(() => {
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
10
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
|
|
29
|
-
// ../../node_modules/.pnpm/webextension-polyfill@0.12.0/node_modules/webextension-polyfill/dist/browser-polyfill.js
|
|
30
|
-
var require_browser_polyfill = __commonJS({
|
|
31
|
-
"../../node_modules/.pnpm/webextension-polyfill@0.12.0/node_modules/webextension-polyfill/dist/browser-polyfill.js"(exports, module) {
|
|
32
|
-
(function(global, factory) {
|
|
33
|
-
if (typeof define === "function" && define.amd) {
|
|
34
|
-
define("webextension-polyfill", ["module"], factory);
|
|
35
|
-
} else if (typeof exports !== "undefined") {
|
|
36
|
-
factory(module);
|
|
37
|
-
} else {
|
|
38
|
-
var mod = {
|
|
39
|
-
exports: {}
|
|
40
|
-
};
|
|
41
|
-
factory(mod);
|
|
42
|
-
global.browser = mod.exports;
|
|
43
|
-
}
|
|
44
|
-
})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : exports, function(module2) {
|
|
45
|
-
"use strict";
|
|
46
|
-
if (!(globalThis.chrome && globalThis.chrome.runtime && globalThis.chrome.runtime.id)) {
|
|
47
|
-
throw new Error("This script should only be loaded in a browser extension.");
|
|
48
|
-
}
|
|
49
|
-
if (!(globalThis.browser && globalThis.browser.runtime && globalThis.browser.runtime.id)) {
|
|
50
|
-
const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received.";
|
|
51
|
-
const wrapAPIs = (extensionAPIs) => {
|
|
52
|
-
const apiMetadata = {
|
|
53
|
-
"alarms": {
|
|
54
|
-
"clear": {
|
|
55
|
-
"minArgs": 0,
|
|
56
|
-
"maxArgs": 1
|
|
57
|
-
},
|
|
58
|
-
"clearAll": {
|
|
59
|
-
"minArgs": 0,
|
|
60
|
-
"maxArgs": 0
|
|
61
|
-
},
|
|
62
|
-
"get": {
|
|
63
|
-
"minArgs": 0,
|
|
64
|
-
"maxArgs": 1
|
|
65
|
-
},
|
|
66
|
-
"getAll": {
|
|
67
|
-
"minArgs": 0,
|
|
68
|
-
"maxArgs": 0
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
"bookmarks": {
|
|
72
|
-
"create": {
|
|
73
|
-
"minArgs": 1,
|
|
74
|
-
"maxArgs": 1
|
|
75
|
-
},
|
|
76
|
-
"get": {
|
|
77
|
-
"minArgs": 1,
|
|
78
|
-
"maxArgs": 1
|
|
79
|
-
},
|
|
80
|
-
"getChildren": {
|
|
81
|
-
"minArgs": 1,
|
|
82
|
-
"maxArgs": 1
|
|
83
|
-
},
|
|
84
|
-
"getRecent": {
|
|
85
|
-
"minArgs": 1,
|
|
86
|
-
"maxArgs": 1
|
|
87
|
-
},
|
|
88
|
-
"getSubTree": {
|
|
89
|
-
"minArgs": 1,
|
|
90
|
-
"maxArgs": 1
|
|
91
|
-
},
|
|
92
|
-
"getTree": {
|
|
93
|
-
"minArgs": 0,
|
|
94
|
-
"maxArgs": 0
|
|
95
|
-
},
|
|
96
|
-
"move": {
|
|
97
|
-
"minArgs": 2,
|
|
98
|
-
"maxArgs": 2
|
|
99
|
-
},
|
|
100
|
-
"remove": {
|
|
101
|
-
"minArgs": 1,
|
|
102
|
-
"maxArgs": 1
|
|
103
|
-
},
|
|
104
|
-
"removeTree": {
|
|
105
|
-
"minArgs": 1,
|
|
106
|
-
"maxArgs": 1
|
|
107
|
-
},
|
|
108
|
-
"search": {
|
|
109
|
-
"minArgs": 1,
|
|
110
|
-
"maxArgs": 1
|
|
111
|
-
},
|
|
112
|
-
"update": {
|
|
113
|
-
"minArgs": 2,
|
|
114
|
-
"maxArgs": 2
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
"browserAction": {
|
|
118
|
-
"disable": {
|
|
119
|
-
"minArgs": 0,
|
|
120
|
-
"maxArgs": 1,
|
|
121
|
-
"fallbackToNoCallback": true
|
|
122
|
-
},
|
|
123
|
-
"enable": {
|
|
124
|
-
"minArgs": 0,
|
|
125
|
-
"maxArgs": 1,
|
|
126
|
-
"fallbackToNoCallback": true
|
|
127
|
-
},
|
|
128
|
-
"getBadgeBackgroundColor": {
|
|
129
|
-
"minArgs": 1,
|
|
130
|
-
"maxArgs": 1
|
|
131
|
-
},
|
|
132
|
-
"getBadgeText": {
|
|
133
|
-
"minArgs": 1,
|
|
134
|
-
"maxArgs": 1
|
|
135
|
-
},
|
|
136
|
-
"getPopup": {
|
|
137
|
-
"minArgs": 1,
|
|
138
|
-
"maxArgs": 1
|
|
139
|
-
},
|
|
140
|
-
"getTitle": {
|
|
141
|
-
"minArgs": 1,
|
|
142
|
-
"maxArgs": 1
|
|
143
|
-
},
|
|
144
|
-
"openPopup": {
|
|
145
|
-
"minArgs": 0,
|
|
146
|
-
"maxArgs": 0
|
|
147
|
-
},
|
|
148
|
-
"setBadgeBackgroundColor": {
|
|
149
|
-
"minArgs": 1,
|
|
150
|
-
"maxArgs": 1,
|
|
151
|
-
"fallbackToNoCallback": true
|
|
152
|
-
},
|
|
153
|
-
"setBadgeText": {
|
|
154
|
-
"minArgs": 1,
|
|
155
|
-
"maxArgs": 1,
|
|
156
|
-
"fallbackToNoCallback": true
|
|
157
|
-
},
|
|
158
|
-
"setIcon": {
|
|
159
|
-
"minArgs": 1,
|
|
160
|
-
"maxArgs": 1
|
|
161
|
-
},
|
|
162
|
-
"setPopup": {
|
|
163
|
-
"minArgs": 1,
|
|
164
|
-
"maxArgs": 1,
|
|
165
|
-
"fallbackToNoCallback": true
|
|
166
|
-
},
|
|
167
|
-
"setTitle": {
|
|
168
|
-
"minArgs": 1,
|
|
169
|
-
"maxArgs": 1,
|
|
170
|
-
"fallbackToNoCallback": true
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
"browsingData": {
|
|
174
|
-
"remove": {
|
|
175
|
-
"minArgs": 2,
|
|
176
|
-
"maxArgs": 2
|
|
177
|
-
},
|
|
178
|
-
"removeCache": {
|
|
179
|
-
"minArgs": 1,
|
|
180
|
-
"maxArgs": 1
|
|
181
|
-
},
|
|
182
|
-
"removeCookies": {
|
|
183
|
-
"minArgs": 1,
|
|
184
|
-
"maxArgs": 1
|
|
185
|
-
},
|
|
186
|
-
"removeDownloads": {
|
|
187
|
-
"minArgs": 1,
|
|
188
|
-
"maxArgs": 1
|
|
189
|
-
},
|
|
190
|
-
"removeFormData": {
|
|
191
|
-
"minArgs": 1,
|
|
192
|
-
"maxArgs": 1
|
|
193
|
-
},
|
|
194
|
-
"removeHistory": {
|
|
195
|
-
"minArgs": 1,
|
|
196
|
-
"maxArgs": 1
|
|
197
|
-
},
|
|
198
|
-
"removeLocalStorage": {
|
|
199
|
-
"minArgs": 1,
|
|
200
|
-
"maxArgs": 1
|
|
201
|
-
},
|
|
202
|
-
"removePasswords": {
|
|
203
|
-
"minArgs": 1,
|
|
204
|
-
"maxArgs": 1
|
|
205
|
-
},
|
|
206
|
-
"removePluginData": {
|
|
207
|
-
"minArgs": 1,
|
|
208
|
-
"maxArgs": 1
|
|
209
|
-
},
|
|
210
|
-
"settings": {
|
|
211
|
-
"minArgs": 0,
|
|
212
|
-
"maxArgs": 0
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
"commands": {
|
|
216
|
-
"getAll": {
|
|
217
|
-
"minArgs": 0,
|
|
218
|
-
"maxArgs": 0
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
"contextMenus": {
|
|
222
|
-
"remove": {
|
|
223
|
-
"minArgs": 1,
|
|
224
|
-
"maxArgs": 1
|
|
225
|
-
},
|
|
226
|
-
"removeAll": {
|
|
227
|
-
"minArgs": 0,
|
|
228
|
-
"maxArgs": 0
|
|
229
|
-
},
|
|
230
|
-
"update": {
|
|
231
|
-
"minArgs": 2,
|
|
232
|
-
"maxArgs": 2
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
"cookies": {
|
|
236
|
-
"get": {
|
|
237
|
-
"minArgs": 1,
|
|
238
|
-
"maxArgs": 1
|
|
239
|
-
},
|
|
240
|
-
"getAll": {
|
|
241
|
-
"minArgs": 1,
|
|
242
|
-
"maxArgs": 1
|
|
243
|
-
},
|
|
244
|
-
"getAllCookieStores": {
|
|
245
|
-
"minArgs": 0,
|
|
246
|
-
"maxArgs": 0
|
|
247
|
-
},
|
|
248
|
-
"remove": {
|
|
249
|
-
"minArgs": 1,
|
|
250
|
-
"maxArgs": 1
|
|
251
|
-
},
|
|
252
|
-
"set": {
|
|
253
|
-
"minArgs": 1,
|
|
254
|
-
"maxArgs": 1
|
|
255
|
-
}
|
|
256
|
-
},
|
|
257
|
-
"devtools": {
|
|
258
|
-
"inspectedWindow": {
|
|
259
|
-
"eval": {
|
|
260
|
-
"minArgs": 1,
|
|
261
|
-
"maxArgs": 2,
|
|
262
|
-
"singleCallbackArg": false
|
|
263
|
-
}
|
|
264
|
-
},
|
|
265
|
-
"panels": {
|
|
266
|
-
"create": {
|
|
267
|
-
"minArgs": 3,
|
|
268
|
-
"maxArgs": 3,
|
|
269
|
-
"singleCallbackArg": true
|
|
270
|
-
},
|
|
271
|
-
"elements": {
|
|
272
|
-
"createSidebarPane": {
|
|
273
|
-
"minArgs": 1,
|
|
274
|
-
"maxArgs": 1
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
},
|
|
279
|
-
"downloads": {
|
|
280
|
-
"cancel": {
|
|
281
|
-
"minArgs": 1,
|
|
282
|
-
"maxArgs": 1
|
|
283
|
-
},
|
|
284
|
-
"download": {
|
|
285
|
-
"minArgs": 1,
|
|
286
|
-
"maxArgs": 1
|
|
287
|
-
},
|
|
288
|
-
"erase": {
|
|
289
|
-
"minArgs": 1,
|
|
290
|
-
"maxArgs": 1
|
|
291
|
-
},
|
|
292
|
-
"getFileIcon": {
|
|
293
|
-
"minArgs": 1,
|
|
294
|
-
"maxArgs": 2
|
|
295
|
-
},
|
|
296
|
-
"open": {
|
|
297
|
-
"minArgs": 1,
|
|
298
|
-
"maxArgs": 1,
|
|
299
|
-
"fallbackToNoCallback": true
|
|
300
|
-
},
|
|
301
|
-
"pause": {
|
|
302
|
-
"minArgs": 1,
|
|
303
|
-
"maxArgs": 1
|
|
304
|
-
},
|
|
305
|
-
"removeFile": {
|
|
306
|
-
"minArgs": 1,
|
|
307
|
-
"maxArgs": 1
|
|
308
|
-
},
|
|
309
|
-
"resume": {
|
|
310
|
-
"minArgs": 1,
|
|
311
|
-
"maxArgs": 1
|
|
312
|
-
},
|
|
313
|
-
"search": {
|
|
314
|
-
"minArgs": 1,
|
|
315
|
-
"maxArgs": 1
|
|
316
|
-
},
|
|
317
|
-
"show": {
|
|
318
|
-
"minArgs": 1,
|
|
319
|
-
"maxArgs": 1,
|
|
320
|
-
"fallbackToNoCallback": true
|
|
321
|
-
}
|
|
322
|
-
},
|
|
323
|
-
"extension": {
|
|
324
|
-
"isAllowedFileSchemeAccess": {
|
|
325
|
-
"minArgs": 0,
|
|
326
|
-
"maxArgs": 0
|
|
327
|
-
},
|
|
328
|
-
"isAllowedIncognitoAccess": {
|
|
329
|
-
"minArgs": 0,
|
|
330
|
-
"maxArgs": 0
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
"history": {
|
|
334
|
-
"addUrl": {
|
|
335
|
-
"minArgs": 1,
|
|
336
|
-
"maxArgs": 1
|
|
337
|
-
},
|
|
338
|
-
"deleteAll": {
|
|
339
|
-
"minArgs": 0,
|
|
340
|
-
"maxArgs": 0
|
|
341
|
-
},
|
|
342
|
-
"deleteRange": {
|
|
343
|
-
"minArgs": 1,
|
|
344
|
-
"maxArgs": 1
|
|
345
|
-
},
|
|
346
|
-
"deleteUrl": {
|
|
347
|
-
"minArgs": 1,
|
|
348
|
-
"maxArgs": 1
|
|
349
|
-
},
|
|
350
|
-
"getVisits": {
|
|
351
|
-
"minArgs": 1,
|
|
352
|
-
"maxArgs": 1
|
|
353
|
-
},
|
|
354
|
-
"search": {
|
|
355
|
-
"minArgs": 1,
|
|
356
|
-
"maxArgs": 1
|
|
357
|
-
}
|
|
358
|
-
},
|
|
359
|
-
"i18n": {
|
|
360
|
-
"detectLanguage": {
|
|
361
|
-
"minArgs": 1,
|
|
362
|
-
"maxArgs": 1
|
|
363
|
-
},
|
|
364
|
-
"getAcceptLanguages": {
|
|
365
|
-
"minArgs": 0,
|
|
366
|
-
"maxArgs": 0
|
|
367
|
-
}
|
|
368
|
-
},
|
|
369
|
-
"identity": {
|
|
370
|
-
"launchWebAuthFlow": {
|
|
371
|
-
"minArgs": 1,
|
|
372
|
-
"maxArgs": 1
|
|
373
|
-
}
|
|
374
|
-
},
|
|
375
|
-
"idle": {
|
|
376
|
-
"queryState": {
|
|
377
|
-
"minArgs": 1,
|
|
378
|
-
"maxArgs": 1
|
|
379
|
-
}
|
|
380
|
-
},
|
|
381
|
-
"management": {
|
|
382
|
-
"get": {
|
|
383
|
-
"minArgs": 1,
|
|
384
|
-
"maxArgs": 1
|
|
385
|
-
},
|
|
386
|
-
"getAll": {
|
|
387
|
-
"minArgs": 0,
|
|
388
|
-
"maxArgs": 0
|
|
389
|
-
},
|
|
390
|
-
"getSelf": {
|
|
391
|
-
"minArgs": 0,
|
|
392
|
-
"maxArgs": 0
|
|
393
|
-
},
|
|
394
|
-
"setEnabled": {
|
|
395
|
-
"minArgs": 2,
|
|
396
|
-
"maxArgs": 2
|
|
397
|
-
},
|
|
398
|
-
"uninstallSelf": {
|
|
399
|
-
"minArgs": 0,
|
|
400
|
-
"maxArgs": 1
|
|
401
|
-
}
|
|
402
|
-
},
|
|
403
|
-
"notifications": {
|
|
404
|
-
"clear": {
|
|
405
|
-
"minArgs": 1,
|
|
406
|
-
"maxArgs": 1
|
|
407
|
-
},
|
|
408
|
-
"create": {
|
|
409
|
-
"minArgs": 1,
|
|
410
|
-
"maxArgs": 2
|
|
411
|
-
},
|
|
412
|
-
"getAll": {
|
|
413
|
-
"minArgs": 0,
|
|
414
|
-
"maxArgs": 0
|
|
415
|
-
},
|
|
416
|
-
"getPermissionLevel": {
|
|
417
|
-
"minArgs": 0,
|
|
418
|
-
"maxArgs": 0
|
|
419
|
-
},
|
|
420
|
-
"update": {
|
|
421
|
-
"minArgs": 2,
|
|
422
|
-
"maxArgs": 2
|
|
423
|
-
}
|
|
424
|
-
},
|
|
425
|
-
"pageAction": {
|
|
426
|
-
"getPopup": {
|
|
427
|
-
"minArgs": 1,
|
|
428
|
-
"maxArgs": 1
|
|
429
|
-
},
|
|
430
|
-
"getTitle": {
|
|
431
|
-
"minArgs": 1,
|
|
432
|
-
"maxArgs": 1
|
|
433
|
-
},
|
|
434
|
-
"hide": {
|
|
435
|
-
"minArgs": 1,
|
|
436
|
-
"maxArgs": 1,
|
|
437
|
-
"fallbackToNoCallback": true
|
|
438
|
-
},
|
|
439
|
-
"setIcon": {
|
|
440
|
-
"minArgs": 1,
|
|
441
|
-
"maxArgs": 1
|
|
442
|
-
},
|
|
443
|
-
"setPopup": {
|
|
444
|
-
"minArgs": 1,
|
|
445
|
-
"maxArgs": 1,
|
|
446
|
-
"fallbackToNoCallback": true
|
|
447
|
-
},
|
|
448
|
-
"setTitle": {
|
|
449
|
-
"minArgs": 1,
|
|
450
|
-
"maxArgs": 1,
|
|
451
|
-
"fallbackToNoCallback": true
|
|
452
|
-
},
|
|
453
|
-
"show": {
|
|
454
|
-
"minArgs": 1,
|
|
455
|
-
"maxArgs": 1,
|
|
456
|
-
"fallbackToNoCallback": true
|
|
457
|
-
}
|
|
458
|
-
},
|
|
459
|
-
"permissions": {
|
|
460
|
-
"contains": {
|
|
461
|
-
"minArgs": 1,
|
|
462
|
-
"maxArgs": 1
|
|
463
|
-
},
|
|
464
|
-
"getAll": {
|
|
465
|
-
"minArgs": 0,
|
|
466
|
-
"maxArgs": 0
|
|
467
|
-
},
|
|
468
|
-
"remove": {
|
|
469
|
-
"minArgs": 1,
|
|
470
|
-
"maxArgs": 1
|
|
471
|
-
},
|
|
472
|
-
"request": {
|
|
473
|
-
"minArgs": 1,
|
|
474
|
-
"maxArgs": 1
|
|
475
|
-
}
|
|
476
|
-
},
|
|
477
|
-
"runtime": {
|
|
478
|
-
"getBackgroundPage": {
|
|
479
|
-
"minArgs": 0,
|
|
480
|
-
"maxArgs": 0
|
|
481
|
-
},
|
|
482
|
-
"getPlatformInfo": {
|
|
483
|
-
"minArgs": 0,
|
|
484
|
-
"maxArgs": 0
|
|
485
|
-
},
|
|
486
|
-
"openOptionsPage": {
|
|
487
|
-
"minArgs": 0,
|
|
488
|
-
"maxArgs": 0
|
|
489
|
-
},
|
|
490
|
-
"requestUpdateCheck": {
|
|
491
|
-
"minArgs": 0,
|
|
492
|
-
"maxArgs": 0
|
|
493
|
-
},
|
|
494
|
-
"sendMessage": {
|
|
495
|
-
"minArgs": 1,
|
|
496
|
-
"maxArgs": 3
|
|
497
|
-
},
|
|
498
|
-
"sendNativeMessage": {
|
|
499
|
-
"minArgs": 2,
|
|
500
|
-
"maxArgs": 2
|
|
501
|
-
},
|
|
502
|
-
"setUninstallURL": {
|
|
503
|
-
"minArgs": 1,
|
|
504
|
-
"maxArgs": 1
|
|
505
|
-
}
|
|
506
|
-
},
|
|
507
|
-
"sessions": {
|
|
508
|
-
"getDevices": {
|
|
509
|
-
"minArgs": 0,
|
|
510
|
-
"maxArgs": 1
|
|
511
|
-
},
|
|
512
|
-
"getRecentlyClosed": {
|
|
513
|
-
"minArgs": 0,
|
|
514
|
-
"maxArgs": 1
|
|
515
|
-
},
|
|
516
|
-
"restore": {
|
|
517
|
-
"minArgs": 0,
|
|
518
|
-
"maxArgs": 1
|
|
519
|
-
}
|
|
520
|
-
},
|
|
521
|
-
"storage": {
|
|
522
|
-
"local": {
|
|
523
|
-
"clear": {
|
|
524
|
-
"minArgs": 0,
|
|
525
|
-
"maxArgs": 0
|
|
526
|
-
},
|
|
527
|
-
"get": {
|
|
528
|
-
"minArgs": 0,
|
|
529
|
-
"maxArgs": 1
|
|
530
|
-
},
|
|
531
|
-
"getBytesInUse": {
|
|
532
|
-
"minArgs": 0,
|
|
533
|
-
"maxArgs": 1
|
|
534
|
-
},
|
|
535
|
-
"remove": {
|
|
536
|
-
"minArgs": 1,
|
|
537
|
-
"maxArgs": 1
|
|
538
|
-
},
|
|
539
|
-
"set": {
|
|
540
|
-
"minArgs": 1,
|
|
541
|
-
"maxArgs": 1
|
|
542
|
-
}
|
|
543
|
-
},
|
|
544
|
-
"managed": {
|
|
545
|
-
"get": {
|
|
546
|
-
"minArgs": 0,
|
|
547
|
-
"maxArgs": 1
|
|
548
|
-
},
|
|
549
|
-
"getBytesInUse": {
|
|
550
|
-
"minArgs": 0,
|
|
551
|
-
"maxArgs": 1
|
|
552
|
-
}
|
|
553
|
-
},
|
|
554
|
-
"sync": {
|
|
555
|
-
"clear": {
|
|
556
|
-
"minArgs": 0,
|
|
557
|
-
"maxArgs": 0
|
|
558
|
-
},
|
|
559
|
-
"get": {
|
|
560
|
-
"minArgs": 0,
|
|
561
|
-
"maxArgs": 1
|
|
562
|
-
},
|
|
563
|
-
"getBytesInUse": {
|
|
564
|
-
"minArgs": 0,
|
|
565
|
-
"maxArgs": 1
|
|
566
|
-
},
|
|
567
|
-
"remove": {
|
|
568
|
-
"minArgs": 1,
|
|
569
|
-
"maxArgs": 1
|
|
570
|
-
},
|
|
571
|
-
"set": {
|
|
572
|
-
"minArgs": 1,
|
|
573
|
-
"maxArgs": 1
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
},
|
|
577
|
-
"tabs": {
|
|
578
|
-
"captureVisibleTab": {
|
|
579
|
-
"minArgs": 0,
|
|
580
|
-
"maxArgs": 2
|
|
581
|
-
},
|
|
582
|
-
"create": {
|
|
583
|
-
"minArgs": 1,
|
|
584
|
-
"maxArgs": 1
|
|
585
|
-
},
|
|
586
|
-
"detectLanguage": {
|
|
587
|
-
"minArgs": 0,
|
|
588
|
-
"maxArgs": 1
|
|
589
|
-
},
|
|
590
|
-
"discard": {
|
|
591
|
-
"minArgs": 0,
|
|
592
|
-
"maxArgs": 1
|
|
593
|
-
},
|
|
594
|
-
"duplicate": {
|
|
595
|
-
"minArgs": 1,
|
|
596
|
-
"maxArgs": 1
|
|
597
|
-
},
|
|
598
|
-
"executeScript": {
|
|
599
|
-
"minArgs": 1,
|
|
600
|
-
"maxArgs": 2
|
|
601
|
-
},
|
|
602
|
-
"get": {
|
|
603
|
-
"minArgs": 1,
|
|
604
|
-
"maxArgs": 1
|
|
605
|
-
},
|
|
606
|
-
"getCurrent": {
|
|
607
|
-
"minArgs": 0,
|
|
608
|
-
"maxArgs": 0
|
|
609
|
-
},
|
|
610
|
-
"getZoom": {
|
|
611
|
-
"minArgs": 0,
|
|
612
|
-
"maxArgs": 1
|
|
613
|
-
},
|
|
614
|
-
"getZoomSettings": {
|
|
615
|
-
"minArgs": 0,
|
|
616
|
-
"maxArgs": 1
|
|
617
|
-
},
|
|
618
|
-
"goBack": {
|
|
619
|
-
"minArgs": 0,
|
|
620
|
-
"maxArgs": 1
|
|
621
|
-
},
|
|
622
|
-
"goForward": {
|
|
623
|
-
"minArgs": 0,
|
|
624
|
-
"maxArgs": 1
|
|
625
|
-
},
|
|
626
|
-
"highlight": {
|
|
627
|
-
"minArgs": 1,
|
|
628
|
-
"maxArgs": 1
|
|
629
|
-
},
|
|
630
|
-
"insertCSS": {
|
|
631
|
-
"minArgs": 1,
|
|
632
|
-
"maxArgs": 2
|
|
633
|
-
},
|
|
634
|
-
"move": {
|
|
635
|
-
"minArgs": 2,
|
|
636
|
-
"maxArgs": 2
|
|
637
|
-
},
|
|
638
|
-
"query": {
|
|
639
|
-
"minArgs": 1,
|
|
640
|
-
"maxArgs": 1
|
|
641
|
-
},
|
|
642
|
-
"reload": {
|
|
643
|
-
"minArgs": 0,
|
|
644
|
-
"maxArgs": 2
|
|
645
|
-
},
|
|
646
|
-
"remove": {
|
|
647
|
-
"minArgs": 1,
|
|
648
|
-
"maxArgs": 1
|
|
649
|
-
},
|
|
650
|
-
"removeCSS": {
|
|
651
|
-
"minArgs": 1,
|
|
652
|
-
"maxArgs": 2
|
|
653
|
-
},
|
|
654
|
-
"sendMessage": {
|
|
655
|
-
"minArgs": 2,
|
|
656
|
-
"maxArgs": 3
|
|
657
|
-
},
|
|
658
|
-
"setZoom": {
|
|
659
|
-
"minArgs": 1,
|
|
660
|
-
"maxArgs": 2
|
|
661
|
-
},
|
|
662
|
-
"setZoomSettings": {
|
|
663
|
-
"minArgs": 1,
|
|
664
|
-
"maxArgs": 2
|
|
665
|
-
},
|
|
666
|
-
"update": {
|
|
667
|
-
"minArgs": 1,
|
|
668
|
-
"maxArgs": 2
|
|
669
|
-
}
|
|
670
|
-
},
|
|
671
|
-
"topSites": {
|
|
672
|
-
"get": {
|
|
673
|
-
"minArgs": 0,
|
|
674
|
-
"maxArgs": 0
|
|
675
|
-
}
|
|
676
|
-
},
|
|
677
|
-
"webNavigation": {
|
|
678
|
-
"getAllFrames": {
|
|
679
|
-
"minArgs": 1,
|
|
680
|
-
"maxArgs": 1
|
|
681
|
-
},
|
|
682
|
-
"getFrame": {
|
|
683
|
-
"minArgs": 1,
|
|
684
|
-
"maxArgs": 1
|
|
685
|
-
}
|
|
686
|
-
},
|
|
687
|
-
"webRequest": {
|
|
688
|
-
"handlerBehaviorChanged": {
|
|
689
|
-
"minArgs": 0,
|
|
690
|
-
"maxArgs": 0
|
|
691
|
-
}
|
|
692
|
-
},
|
|
693
|
-
"windows": {
|
|
694
|
-
"create": {
|
|
695
|
-
"minArgs": 0,
|
|
696
|
-
"maxArgs": 1
|
|
697
|
-
},
|
|
698
|
-
"get": {
|
|
699
|
-
"minArgs": 1,
|
|
700
|
-
"maxArgs": 2
|
|
701
|
-
},
|
|
702
|
-
"getAll": {
|
|
703
|
-
"minArgs": 0,
|
|
704
|
-
"maxArgs": 1
|
|
705
|
-
},
|
|
706
|
-
"getCurrent": {
|
|
707
|
-
"minArgs": 0,
|
|
708
|
-
"maxArgs": 1
|
|
709
|
-
},
|
|
710
|
-
"getLastFocused": {
|
|
711
|
-
"minArgs": 0,
|
|
712
|
-
"maxArgs": 1
|
|
713
|
-
},
|
|
714
|
-
"remove": {
|
|
715
|
-
"minArgs": 1,
|
|
716
|
-
"maxArgs": 1
|
|
717
|
-
},
|
|
718
|
-
"update": {
|
|
719
|
-
"minArgs": 2,
|
|
720
|
-
"maxArgs": 2
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
};
|
|
724
|
-
if (Object.keys(apiMetadata).length === 0) {
|
|
725
|
-
throw new Error("api-metadata.json has not been included in browser-polyfill");
|
|
726
|
-
}
|
|
727
|
-
class DefaultWeakMap extends WeakMap {
|
|
728
|
-
constructor(createItem, items = void 0) {
|
|
729
|
-
super(items);
|
|
730
|
-
this.createItem = createItem;
|
|
731
|
-
}
|
|
732
|
-
get(key) {
|
|
733
|
-
if (!this.has(key)) {
|
|
734
|
-
this.set(key, this.createItem(key));
|
|
735
|
-
}
|
|
736
|
-
return super.get(key);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
const isThenable = (value) => {
|
|
740
|
-
return value && typeof value === "object" && typeof value.then === "function";
|
|
741
|
-
};
|
|
742
|
-
const makeCallback = (promise, metadata) => {
|
|
743
|
-
return (...callbackArgs) => {
|
|
744
|
-
if (extensionAPIs.runtime.lastError) {
|
|
745
|
-
promise.reject(new Error(extensionAPIs.runtime.lastError.message));
|
|
746
|
-
} else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) {
|
|
747
|
-
promise.resolve(callbackArgs[0]);
|
|
748
|
-
} else {
|
|
749
|
-
promise.resolve(callbackArgs);
|
|
750
|
-
}
|
|
751
|
-
};
|
|
752
|
-
};
|
|
753
|
-
const pluralizeArguments = (numArgs) => numArgs == 1 ? "argument" : "arguments";
|
|
754
|
-
const wrapAsyncFunction = (name, metadata) => {
|
|
755
|
-
return function asyncFunctionWrapper(target, ...args) {
|
|
756
|
-
if (args.length < metadata.minArgs) {
|
|
757
|
-
throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
|
|
758
|
-
}
|
|
759
|
-
if (args.length > metadata.maxArgs) {
|
|
760
|
-
throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
|
|
761
|
-
}
|
|
762
|
-
return new Promise((resolve, reject) => {
|
|
763
|
-
if (metadata.fallbackToNoCallback) {
|
|
764
|
-
try {
|
|
765
|
-
target[name](...args, makeCallback({
|
|
766
|
-
resolve,
|
|
767
|
-
reject
|
|
768
|
-
}, metadata));
|
|
769
|
-
} catch (cbError) {
|
|
770
|
-
console.warn(`${name} API method doesn't seem to support the callback parameter, falling back to call it without a callback: `, cbError);
|
|
771
|
-
target[name](...args);
|
|
772
|
-
metadata.fallbackToNoCallback = false;
|
|
773
|
-
metadata.noCallback = true;
|
|
774
|
-
resolve();
|
|
775
|
-
}
|
|
776
|
-
} else if (metadata.noCallback) {
|
|
777
|
-
target[name](...args);
|
|
778
|
-
resolve();
|
|
779
|
-
} else {
|
|
780
|
-
target[name](...args, makeCallback({
|
|
781
|
-
resolve,
|
|
782
|
-
reject
|
|
783
|
-
}, metadata));
|
|
784
|
-
}
|
|
785
|
-
});
|
|
786
|
-
};
|
|
787
|
-
};
|
|
788
|
-
const wrapMethod = (target, method, wrapper) => {
|
|
789
|
-
return new Proxy(method, {
|
|
790
|
-
apply(targetMethod, thisObj, args) {
|
|
791
|
-
return wrapper.call(thisObj, target, ...args);
|
|
792
|
-
}
|
|
793
|
-
});
|
|
794
|
-
};
|
|
795
|
-
let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
|
|
796
|
-
const wrapObject = (target, wrappers = {}, metadata = {}) => {
|
|
797
|
-
let cache = /* @__PURE__ */ Object.create(null);
|
|
798
|
-
let handlers = {
|
|
799
|
-
has(proxyTarget2, prop) {
|
|
800
|
-
return prop in target || prop in cache;
|
|
801
|
-
},
|
|
802
|
-
get(proxyTarget2, prop, receiver) {
|
|
803
|
-
if (prop in cache) {
|
|
804
|
-
return cache[prop];
|
|
805
|
-
}
|
|
806
|
-
if (!(prop in target)) {
|
|
807
|
-
return void 0;
|
|
808
|
-
}
|
|
809
|
-
let value = target[prop];
|
|
810
|
-
if (typeof value === "function") {
|
|
811
|
-
if (typeof wrappers[prop] === "function") {
|
|
812
|
-
value = wrapMethod(target, target[prop], wrappers[prop]);
|
|
813
|
-
} else if (hasOwnProperty(metadata, prop)) {
|
|
814
|
-
let wrapper = wrapAsyncFunction(prop, metadata[prop]);
|
|
815
|
-
value = wrapMethod(target, target[prop], wrapper);
|
|
816
|
-
} else {
|
|
817
|
-
value = value.bind(target);
|
|
818
|
-
}
|
|
819
|
-
} else if (typeof value === "object" && value !== null && (hasOwnProperty(wrappers, prop) || hasOwnProperty(metadata, prop))) {
|
|
820
|
-
value = wrapObject(value, wrappers[prop], metadata[prop]);
|
|
821
|
-
} else if (hasOwnProperty(metadata, "*")) {
|
|
822
|
-
value = wrapObject(value, wrappers[prop], metadata["*"]);
|
|
823
|
-
} else {
|
|
824
|
-
Object.defineProperty(cache, prop, {
|
|
825
|
-
configurable: true,
|
|
826
|
-
enumerable: true,
|
|
827
|
-
get() {
|
|
828
|
-
return target[prop];
|
|
829
|
-
},
|
|
830
|
-
set(value2) {
|
|
831
|
-
target[prop] = value2;
|
|
832
|
-
}
|
|
833
|
-
});
|
|
834
|
-
return value;
|
|
835
|
-
}
|
|
836
|
-
cache[prop] = value;
|
|
837
|
-
return value;
|
|
838
|
-
},
|
|
839
|
-
set(proxyTarget2, prop, value, receiver) {
|
|
840
|
-
if (prop in cache) {
|
|
841
|
-
cache[prop] = value;
|
|
842
|
-
} else {
|
|
843
|
-
target[prop] = value;
|
|
844
|
-
}
|
|
845
|
-
return true;
|
|
846
|
-
},
|
|
847
|
-
defineProperty(proxyTarget2, prop, desc) {
|
|
848
|
-
return Reflect.defineProperty(cache, prop, desc);
|
|
849
|
-
},
|
|
850
|
-
deleteProperty(proxyTarget2, prop) {
|
|
851
|
-
return Reflect.deleteProperty(cache, prop);
|
|
852
|
-
}
|
|
853
|
-
};
|
|
854
|
-
let proxyTarget = Object.create(target);
|
|
855
|
-
return new Proxy(proxyTarget, handlers);
|
|
856
|
-
};
|
|
857
|
-
const wrapEvent = (wrapperMap) => ({
|
|
858
|
-
addListener(target, listener, ...args) {
|
|
859
|
-
target.addListener(wrapperMap.get(listener), ...args);
|
|
860
|
-
},
|
|
861
|
-
hasListener(target, listener) {
|
|
862
|
-
return target.hasListener(wrapperMap.get(listener));
|
|
863
|
-
},
|
|
864
|
-
removeListener(target, listener) {
|
|
865
|
-
target.removeListener(wrapperMap.get(listener));
|
|
866
|
-
}
|
|
867
|
-
});
|
|
868
|
-
const onRequestFinishedWrappers = new DefaultWeakMap((listener) => {
|
|
869
|
-
if (typeof listener !== "function") {
|
|
870
|
-
return listener;
|
|
871
|
-
}
|
|
872
|
-
return function onRequestFinished(req) {
|
|
873
|
-
const wrappedReq = wrapObject(req, {}, {
|
|
874
|
-
getContent: {
|
|
875
|
-
minArgs: 0,
|
|
876
|
-
maxArgs: 0
|
|
877
|
-
}
|
|
878
|
-
});
|
|
879
|
-
listener(wrappedReq);
|
|
880
|
-
};
|
|
881
|
-
});
|
|
882
|
-
const onMessageWrappers = new DefaultWeakMap((listener) => {
|
|
883
|
-
if (typeof listener !== "function") {
|
|
884
|
-
return listener;
|
|
885
|
-
}
|
|
886
|
-
return function onMessage(message, sender, sendResponse) {
|
|
887
|
-
let didCallSendResponse = false;
|
|
888
|
-
let wrappedSendResponse;
|
|
889
|
-
let sendResponsePromise = new Promise((resolve) => {
|
|
890
|
-
wrappedSendResponse = function(response) {
|
|
891
|
-
didCallSendResponse = true;
|
|
892
|
-
resolve(response);
|
|
893
|
-
};
|
|
894
|
-
});
|
|
895
|
-
let result;
|
|
896
|
-
try {
|
|
897
|
-
result = listener(message, sender, wrappedSendResponse);
|
|
898
|
-
} catch (err) {
|
|
899
|
-
result = Promise.reject(err);
|
|
900
|
-
}
|
|
901
|
-
const isResultThenable = result !== true && isThenable(result);
|
|
902
|
-
if (result !== true && !isResultThenable && !didCallSendResponse) {
|
|
903
|
-
return false;
|
|
904
|
-
}
|
|
905
|
-
const sendPromisedResult = (promise) => {
|
|
906
|
-
promise.then((msg) => {
|
|
907
|
-
sendResponse(msg);
|
|
908
|
-
}, (error) => {
|
|
909
|
-
let message2;
|
|
910
|
-
if (error && (error instanceof Error || typeof error.message === "string")) {
|
|
911
|
-
message2 = error.message;
|
|
912
|
-
} else {
|
|
913
|
-
message2 = "An unexpected error occurred";
|
|
914
|
-
}
|
|
915
|
-
sendResponse({
|
|
916
|
-
__mozWebExtensionPolyfillReject__: true,
|
|
917
|
-
message: message2
|
|
918
|
-
});
|
|
919
|
-
}).catch((err) => {
|
|
920
|
-
console.error("Failed to send onMessage rejected reply", err);
|
|
921
|
-
});
|
|
922
|
-
};
|
|
923
|
-
if (isResultThenable) {
|
|
924
|
-
sendPromisedResult(result);
|
|
925
|
-
} else {
|
|
926
|
-
sendPromisedResult(sendResponsePromise);
|
|
927
|
-
}
|
|
928
|
-
return true;
|
|
929
|
-
};
|
|
930
|
-
});
|
|
931
|
-
const wrappedSendMessageCallback = ({
|
|
932
|
-
reject,
|
|
933
|
-
resolve
|
|
934
|
-
}, reply) => {
|
|
935
|
-
if (extensionAPIs.runtime.lastError) {
|
|
936
|
-
if (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) {
|
|
937
|
-
resolve();
|
|
938
|
-
} else {
|
|
939
|
-
reject(new Error(extensionAPIs.runtime.lastError.message));
|
|
940
|
-
}
|
|
941
|
-
} else if (reply && reply.__mozWebExtensionPolyfillReject__) {
|
|
942
|
-
reject(new Error(reply.message));
|
|
943
|
-
} else {
|
|
944
|
-
resolve(reply);
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => {
|
|
948
|
-
if (args.length < metadata.minArgs) {
|
|
949
|
-
throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
|
|
950
|
-
}
|
|
951
|
-
if (args.length > metadata.maxArgs) {
|
|
952
|
-
throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
|
|
953
|
-
}
|
|
954
|
-
return new Promise((resolve, reject) => {
|
|
955
|
-
const wrappedCb = wrappedSendMessageCallback.bind(null, {
|
|
956
|
-
resolve,
|
|
957
|
-
reject
|
|
958
|
-
});
|
|
959
|
-
args.push(wrappedCb);
|
|
960
|
-
apiNamespaceObj.sendMessage(...args);
|
|
961
|
-
});
|
|
962
|
-
};
|
|
963
|
-
const staticWrappers = {
|
|
964
|
-
devtools: {
|
|
965
|
-
network: {
|
|
966
|
-
onRequestFinished: wrapEvent(onRequestFinishedWrappers)
|
|
967
|
-
}
|
|
968
|
-
},
|
|
969
|
-
runtime: {
|
|
970
|
-
onMessage: wrapEvent(onMessageWrappers),
|
|
971
|
-
onMessageExternal: wrapEvent(onMessageWrappers),
|
|
972
|
-
sendMessage: wrappedSendMessage.bind(null, "sendMessage", {
|
|
973
|
-
minArgs: 1,
|
|
974
|
-
maxArgs: 3
|
|
975
|
-
})
|
|
976
|
-
},
|
|
977
|
-
tabs: {
|
|
978
|
-
sendMessage: wrappedSendMessage.bind(null, "sendMessage", {
|
|
979
|
-
minArgs: 2,
|
|
980
|
-
maxArgs: 3
|
|
981
|
-
})
|
|
982
|
-
}
|
|
983
|
-
};
|
|
984
|
-
const settingMetadata = {
|
|
985
|
-
clear: {
|
|
986
|
-
minArgs: 1,
|
|
987
|
-
maxArgs: 1
|
|
988
|
-
},
|
|
989
|
-
get: {
|
|
990
|
-
minArgs: 1,
|
|
991
|
-
maxArgs: 1
|
|
992
|
-
},
|
|
993
|
-
set: {
|
|
994
|
-
minArgs: 1,
|
|
995
|
-
maxArgs: 1
|
|
996
|
-
}
|
|
997
|
-
};
|
|
998
|
-
apiMetadata.privacy = {
|
|
999
|
-
network: {
|
|
1000
|
-
"*": settingMetadata
|
|
1001
|
-
},
|
|
1002
|
-
services: {
|
|
1003
|
-
"*": settingMetadata
|
|
1004
|
-
},
|
|
1005
|
-
websites: {
|
|
1006
|
-
"*": settingMetadata
|
|
1007
|
-
}
|
|
1008
|
-
};
|
|
1009
|
-
return wrapObject(extensionAPIs, staticWrappers, apiMetadata);
|
|
1010
|
-
};
|
|
1011
|
-
module2.exports = wrapAPIs(chrome);
|
|
1012
|
-
} else {
|
|
1013
|
-
module2.exports = globalThis.browser;
|
|
1014
|
-
}
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
1017
|
-
});
|
|
1018
|
-
|
|
1019
|
-
// src/background-firefox.ts
|
|
1020
|
-
var import_webextension_polyfill = __toESM(require_browser_polyfill());
|
|
1021
|
-
|
|
1022
|
-
// src/shared/zap.ts
|
|
1023
|
-
var ZAP_MAGIC = new Uint8Array([90, 65, 80, 1]);
|
|
1024
|
-
var MSG_REQUEST = 16;
|
|
1025
|
-
function encodeZapMessage(type, payload) {
|
|
1026
|
-
const json = JSON.stringify(payload);
|
|
1027
|
-
const encoder = new TextEncoder();
|
|
1028
|
-
const data = encoder.encode(json);
|
|
1029
|
-
const buf = new ArrayBuffer(4 + 1 + 4 + data.length);
|
|
1030
|
-
const view = new DataView(buf);
|
|
1031
|
-
new Uint8Array(buf, 0, 4).set(ZAP_MAGIC);
|
|
1032
|
-
view.setUint8(4, type);
|
|
1033
|
-
view.setUint32(5, data.length, false);
|
|
1034
|
-
new Uint8Array(buf, 9).set(data);
|
|
1035
|
-
return buf;
|
|
1036
|
-
}
|
|
1037
|
-
function createZapManager() {
|
|
1038
|
-
return {
|
|
1039
|
-
state: {
|
|
1040
|
-
connected: false,
|
|
1041
|
-
mcps: [],
|
|
1042
|
-
extensionId: `hanzo-ext-${Date.now().toString(36)}`
|
|
1043
|
-
},
|
|
1044
|
-
connections: /* @__PURE__ */ new Map(),
|
|
1045
|
-
pendingRequests: /* @__PURE__ */ new Map(),
|
|
1046
|
-
requestIdCounter: 0
|
|
1047
|
-
};
|
|
1048
|
-
}
|
|
1049
|
-
function zapRequest(mgr, mcpId, method, params = {}) {
|
|
1050
|
-
const ws = mgr.connections.get(mcpId);
|
|
1051
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
1052
|
-
return Promise.reject(new Error(`MCP ${mcpId} not connected`));
|
|
1053
|
-
}
|
|
1054
|
-
const id = `req-${++mgr.requestIdCounter}`;
|
|
1055
|
-
return new Promise((resolve, reject) => {
|
|
1056
|
-
const timer = setTimeout(() => {
|
|
1057
|
-
mgr.pendingRequests.delete(id);
|
|
1058
|
-
reject(new Error(`ZAP request timeout: ${method}`));
|
|
1059
|
-
}, 3e4);
|
|
1060
|
-
mgr.pendingRequests.set(id, {
|
|
1061
|
-
resolve: (r) => {
|
|
1062
|
-
clearTimeout(timer);
|
|
1063
|
-
resolve(r);
|
|
1064
|
-
},
|
|
1065
|
-
reject: (e) => {
|
|
1066
|
-
clearTimeout(timer);
|
|
1067
|
-
reject(e);
|
|
1068
|
-
}
|
|
1069
|
-
});
|
|
1070
|
-
ws.send(encodeZapMessage(MSG_REQUEST, { id, method, params }));
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1073
|
-
async function zapCallTool(mgr, name, args = {}, targetMcpId) {
|
|
1074
|
-
if (targetMcpId) {
|
|
1075
|
-
return zapRequest(mgr, targetMcpId, "tools/call", { name, arguments: args });
|
|
1076
|
-
}
|
|
1077
|
-
for (const mcp of mgr.state.mcps) {
|
|
1078
|
-
if (mcp.tools.includes(name)) {
|
|
1079
|
-
return zapRequest(mgr, mcp.id, "tools/call", { name, arguments: args });
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
throw new Error(`Tool not found on any ZAP-connected MCP: ${name}`);
|
|
1083
|
-
}
|
|
1084
|
-
function hasZapTool(mgr, name) {
|
|
1085
|
-
return mgr.state.mcps.some((mcp) => mcp.tools.includes(name));
|
|
1086
|
-
}
|
|
1087
|
-
function zapListResources(mgr, mcpId) {
|
|
1088
|
-
return zapRequest(mgr, mcpId, "resources/list");
|
|
1089
|
-
}
|
|
1090
|
-
function zapReadResource(mgr, mcpId, uri) {
|
|
1091
|
-
return zapRequest(mgr, mcpId, "resources/read", { uri });
|
|
1092
|
-
}
|
|
1093
|
-
function zapListPrompts(mgr, mcpId) {
|
|
1094
|
-
return zapRequest(mgr, mcpId, "prompts/list");
|
|
1095
|
-
}
|
|
1096
|
-
function zapGetPrompt(mgr, mcpId, name, args) {
|
|
1097
|
-
return zapRequest(mgr, mcpId, "prompts/get", { name, arguments: args });
|
|
1098
|
-
}
|
|
1099
|
-
function handleZapMessage(action, request, sendResponse, mgr, browserName, version) {
|
|
1100
|
-
switch (action) {
|
|
1101
|
-
case "zap.status":
|
|
1102
|
-
sendResponse({
|
|
1103
|
-
success: true,
|
|
1104
|
-
zap: {
|
|
1105
|
-
connected: mgr.state.connected,
|
|
1106
|
-
mcps: mgr.state.mcps.map((m) => ({
|
|
1107
|
-
id: m.id,
|
|
1108
|
-
name: m.name,
|
|
1109
|
-
url: m.url,
|
|
1110
|
-
toolCount: m.tools.length
|
|
1111
|
-
}))
|
|
1112
|
-
}
|
|
1113
|
-
});
|
|
1114
|
-
return true;
|
|
1115
|
-
case "zap.discover":
|
|
1116
|
-
case "zap.connect":
|
|
1117
|
-
sendResponse({ success: false, error: "native-zap: connection is via the ai.hanzo.zap native host (no discovery)" });
|
|
1118
|
-
return true;
|
|
1119
|
-
case "zap.callTool": {
|
|
1120
|
-
const toolName = request.name || request.tool;
|
|
1121
|
-
const toolArgs = request.args || {};
|
|
1122
|
-
if (!toolName) {
|
|
1123
|
-
sendResponse({ success: false, error: "Missing tool name" });
|
|
1124
|
-
return true;
|
|
1125
|
-
}
|
|
1126
|
-
zapCallTool(mgr, toolName, toolArgs, request.mcpId).then((result) => {
|
|
1127
|
-
sendResponse({ success: true, result });
|
|
1128
|
-
}).catch((e) => {
|
|
1129
|
-
sendResponse({ success: false, error: e.message });
|
|
1130
|
-
});
|
|
1131
|
-
return true;
|
|
1132
|
-
}
|
|
1133
|
-
case "zap.listTools": {
|
|
1134
|
-
const tools = mgr.state.mcps.flatMap(
|
|
1135
|
-
(m) => m.tools.map((t) => ({ name: t, mcpId: m.id, mcpName: m.name }))
|
|
1136
|
-
);
|
|
1137
|
-
sendResponse({ success: true, tools });
|
|
1138
|
-
return true;
|
|
1139
|
-
}
|
|
1140
|
-
case "zap.listResources": {
|
|
1141
|
-
const mcpId = request.mcpId || mgr.state.mcps[0]?.id;
|
|
1142
|
-
if (!mcpId) {
|
|
1143
|
-
sendResponse({ success: false, error: "No MCP connected" });
|
|
1144
|
-
return true;
|
|
1145
|
-
}
|
|
1146
|
-
zapListResources(mgr, mcpId).then((result) => {
|
|
1147
|
-
sendResponse({ success: true, result });
|
|
1148
|
-
}).catch((e) => {
|
|
1149
|
-
sendResponse({ success: false, error: e.message });
|
|
1150
|
-
});
|
|
1151
|
-
return true;
|
|
1152
|
-
}
|
|
1153
|
-
case "zap.readResource": {
|
|
1154
|
-
const mcpId = request.mcpId || mgr.state.mcps[0]?.id;
|
|
1155
|
-
if (!mcpId) {
|
|
1156
|
-
sendResponse({ success: false, error: "No MCP connected" });
|
|
1157
|
-
return true;
|
|
1158
|
-
}
|
|
1159
|
-
if (!request.uri) {
|
|
1160
|
-
sendResponse({ success: false, error: "Missing uri" });
|
|
1161
|
-
return true;
|
|
1162
|
-
}
|
|
1163
|
-
zapReadResource(mgr, mcpId, request.uri).then((result) => {
|
|
1164
|
-
sendResponse({ success: true, result });
|
|
1165
|
-
}).catch((e) => {
|
|
1166
|
-
sendResponse({ success: false, error: e.message });
|
|
1167
|
-
});
|
|
1168
|
-
return true;
|
|
1169
|
-
}
|
|
1170
|
-
case "zap.listPrompts": {
|
|
1171
|
-
const mcpId = request.mcpId || mgr.state.mcps[0]?.id;
|
|
1172
|
-
if (!mcpId) {
|
|
1173
|
-
sendResponse({ success: false, error: "No MCP connected" });
|
|
1174
|
-
return true;
|
|
1175
|
-
}
|
|
1176
|
-
zapListPrompts(mgr, mcpId).then((result) => {
|
|
1177
|
-
sendResponse({ success: true, result });
|
|
1178
|
-
}).catch((e) => {
|
|
1179
|
-
sendResponse({ success: false, error: e.message });
|
|
1180
|
-
});
|
|
1181
|
-
return true;
|
|
1182
|
-
}
|
|
1183
|
-
case "zap.getPrompt": {
|
|
1184
|
-
const mcpId = request.mcpId || mgr.state.mcps[0]?.id;
|
|
1185
|
-
if (!mcpId) {
|
|
1186
|
-
sendResponse({ success: false, error: "No MCP connected" });
|
|
1187
|
-
return true;
|
|
1188
|
-
}
|
|
1189
|
-
if (!request.name) {
|
|
1190
|
-
sendResponse({ success: false, error: "Missing prompt name" });
|
|
1191
|
-
return true;
|
|
1192
|
-
}
|
|
1193
|
-
zapGetPrompt(mgr, mcpId, request.name, request.args).then((result) => {
|
|
1194
|
-
sendResponse({ success: true, result });
|
|
1195
|
-
}).catch((e) => {
|
|
1196
|
-
sendResponse({ success: false, error: e.message });
|
|
1197
|
-
});
|
|
1198
|
-
return true;
|
|
1199
|
-
}
|
|
1200
|
-
default:
|
|
1201
|
-
return false;
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
// src/shared/native-zap.ts
|
|
1206
|
-
var HELLO = 1;
|
|
1207
|
-
var WELCOME = 2;
|
|
1208
|
-
var ERROR = 7;
|
|
1209
|
-
var ROUTE = 16;
|
|
1210
|
-
var RESPONSE = 17;
|
|
1211
|
-
var ROLE_PROVIDER = 1;
|
|
1212
|
-
function b64encode(bytes) {
|
|
1213
|
-
let s = "";
|
|
1214
|
-
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
|
1215
|
-
return btoa(s);
|
|
1216
|
-
}
|
|
1217
|
-
function b64decode(s) {
|
|
1218
|
-
const bin = atob(s);
|
|
1219
|
-
const u = new Uint8Array(bin.length);
|
|
1220
|
-
for (let i = 0; i < bin.length; i++) u[i] = bin.charCodeAt(i);
|
|
1221
|
-
return u;
|
|
1222
|
-
}
|
|
1223
|
-
function putU16(a, n) {
|
|
1224
|
-
a.push(n & 255, n >> 8 & 255);
|
|
1225
|
-
}
|
|
1226
|
-
function putStr(a, s) {
|
|
1227
|
-
const b = new TextEncoder().encode(s);
|
|
1228
|
-
putU16(a, b.length);
|
|
1229
|
-
for (let i = 0; i < b.length; i++) a.push(b[i]);
|
|
1230
|
-
}
|
|
1231
|
-
function encodeHello(role, brand, caps) {
|
|
1232
|
-
const a = [role];
|
|
1233
|
-
putStr(a, brand);
|
|
1234
|
-
putU16(a, caps.length);
|
|
1235
|
-
for (const c of caps) putStr(a, c);
|
|
1236
|
-
return new Uint8Array(a);
|
|
1237
|
-
}
|
|
1238
|
-
function decodeCmd(p) {
|
|
1239
|
-
const dv = new DataView(p.buffer, p.byteOffset, p.byteLength);
|
|
1240
|
-
let o = 0;
|
|
1241
|
-
const ml = dv.getUint16(o, true);
|
|
1242
|
-
o += 2;
|
|
1243
|
-
const method = new TextDecoder().decode(p.subarray(o, o + ml));
|
|
1244
|
-
o += ml;
|
|
1245
|
-
const n = dv.getUint16(o, true);
|
|
1246
|
-
o += 2;
|
|
1247
|
-
const params = {};
|
|
1248
|
-
for (let i = 0; i < n; i++) {
|
|
1249
|
-
const kl = dv.getUint16(o, true);
|
|
1250
|
-
o += 2;
|
|
1251
|
-
const k = new TextDecoder().decode(p.subarray(o, o + kl));
|
|
1252
|
-
o += kl;
|
|
1253
|
-
const vl = dv.getUint32(o, true);
|
|
1254
|
-
o += 4;
|
|
1255
|
-
params[k] = new TextDecoder().decode(p.subarray(o, o + vl));
|
|
1256
|
-
o += vl;
|
|
1257
|
-
}
|
|
1258
|
-
return { method, params };
|
|
1259
|
-
}
|
|
1260
|
-
var RECONNECT_MS = 3e3;
|
|
1261
|
-
function connectNativeZap(state, brand, caps, dispatch, log = console.log) {
|
|
1262
|
-
const rt = (typeof browser !== "undefined" ? browser : chrome).runtime;
|
|
1263
|
-
const prior = state.port;
|
|
1264
|
-
state.port = null;
|
|
1265
|
-
if (prior) {
|
|
1266
|
-
try {
|
|
1267
|
-
prior.disconnect();
|
|
1268
|
-
} catch {
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
let port;
|
|
1272
|
-
try {
|
|
1273
|
-
port = rt.connectNative("ai.hanzo.zap");
|
|
1274
|
-
} catch (e) {
|
|
1275
|
-
state.connected = false;
|
|
1276
|
-
log("[Hanzo/ZAP] native host ai.hanzo.zap is not installed");
|
|
1277
|
-
return;
|
|
1278
|
-
}
|
|
1279
|
-
state.port = port;
|
|
1280
|
-
const id = state.providerId;
|
|
1281
|
-
const post = (t, to, payload) => port.postMessage({ t, flags: 0, from: id, to, payload: b64encode(payload) });
|
|
1282
|
-
post(HELLO, "", encodeHello(ROLE_PROVIDER, brand, caps));
|
|
1283
|
-
port.onMessage.addListener(async (msg) => {
|
|
1284
|
-
const payload = msg.payload ? b64decode(msg.payload) : new Uint8Array(0);
|
|
1285
|
-
switch (msg.t) {
|
|
1286
|
-
case WELCOME:
|
|
1287
|
-
state.connected = true;
|
|
1288
|
-
log(`[Hanzo/ZAP] native host connected; registered ${id}`);
|
|
1289
|
-
break;
|
|
1290
|
-
case ERROR:
|
|
1291
|
-
log("[Hanzo/ZAP] " + new TextDecoder().decode(payload));
|
|
1292
|
-
break;
|
|
1293
|
-
case ROUTE: {
|
|
1294
|
-
const { method, params } = decodeCmd(payload);
|
|
1295
|
-
let out;
|
|
1296
|
-
try {
|
|
1297
|
-
const r = await dispatch(method, params);
|
|
1298
|
-
const s = typeof r === "string" ? r : JSON.stringify(r ?? null);
|
|
1299
|
-
out = new TextEncoder().encode(s);
|
|
1300
|
-
} catch (e) {
|
|
1301
|
-
out = new TextEncoder().encode("ERR:" + (e?.message || String(e)));
|
|
1302
|
-
}
|
|
1303
|
-
port.postMessage({ t: RESPONSE, flags: 0, from: id, to: msg.from, payload: b64encode(out) });
|
|
1304
|
-
break;
|
|
1305
|
-
}
|
|
1306
|
-
default:
|
|
1307
|
-
break;
|
|
1308
|
-
}
|
|
1309
|
-
});
|
|
1310
|
-
port.onDisconnect.addListener(() => {
|
|
1311
|
-
if (state.port !== port) return;
|
|
1312
|
-
state.connected = false;
|
|
1313
|
-
state.port = null;
|
|
1314
|
-
const err = rt.lastError?.message;
|
|
1315
|
-
log("[Hanzo/ZAP] native host disconnected" + (err ? ": " + err : ""));
|
|
1316
|
-
setTimeout(() => connectNativeZap(state, brand, caps, dispatch, log), RECONNECT_MS);
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
// src/shared/evaluable.ts
|
|
1321
|
-
function pickEvaluable(params) {
|
|
1322
|
-
const p = params ?? {};
|
|
1323
|
-
const raw = p.expression ?? p.code ?? p.script ?? p.function ?? p.js ?? "";
|
|
1324
|
-
return typeof raw === "string" ? raw : String(raw);
|
|
1325
|
-
}
|
|
1326
|
-
function wrapEvaluable(code) {
|
|
1327
|
-
const t = (code ?? "").trim();
|
|
1328
|
-
if (!t) return "undefined";
|
|
1329
|
-
if (/^(\(?\s*(async\s+)?function\b|\(?\s*\([^)]*\)\s*=>|\(\s*async\b)/.test(t)) return t;
|
|
1330
|
-
const looksStatement = /[;\n]/.test(t) || /^(const|let|var|if|for|while|switch|return|throw|await|try)\b/.test(t);
|
|
1331
|
-
if (!looksStatement) return t;
|
|
1332
|
-
let body = t.replace(/;\s*$/, "");
|
|
1333
|
-
const cut = Math.max(body.lastIndexOf(";"), body.lastIndexOf("\n"));
|
|
1334
|
-
const head = cut >= 0 ? body.slice(0, cut + 1) : "";
|
|
1335
|
-
const last = (cut >= 0 ? body.slice(cut + 1) : body).trim();
|
|
1336
|
-
if (last && !/^(return|const|let|var|if|for|while|switch|throw|}|\/\/)/.test(last)) {
|
|
1337
|
-
body = head + " return " + last;
|
|
1338
|
-
}
|
|
1339
|
-
return `(async()=>{ ${body} })()`;
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
// src/shared/rag.ts
|
|
1343
|
-
var DEFAULT_RAG_TOP_K = 5;
|
|
1344
|
-
async function getRagConfig(storage) {
|
|
1345
|
-
return new Promise((resolve) => {
|
|
1346
|
-
storage.get([
|
|
1347
|
-
"hanzo_rag_endpoint",
|
|
1348
|
-
"hanzo_rag_api_key",
|
|
1349
|
-
"hanzo_rag_kb",
|
|
1350
|
-
"hanzo_rag_top_k",
|
|
1351
|
-
"hanzo_rag_include_tab_context",
|
|
1352
|
-
"hanzo_rag_use_zap"
|
|
1353
|
-
], (result) => {
|
|
1354
|
-
const parsedTopK = Number.parseInt(String(result.hanzo_rag_top_k ?? DEFAULT_RAG_TOP_K), 10);
|
|
1355
|
-
resolve({
|
|
1356
|
-
endpoint: String(result.hanzo_rag_endpoint || "").trim(),
|
|
1357
|
-
apiKey: String(result.hanzo_rag_api_key || "").trim(),
|
|
1358
|
-
knowledgeBase: String(result.hanzo_rag_kb || "").trim(),
|
|
1359
|
-
topK: Number.isFinite(parsedTopK) ? Math.max(1, Math.min(parsedTopK, 20)) : DEFAULT_RAG_TOP_K,
|
|
1360
|
-
includeTabContext: result.hanzo_rag_include_tab_context !== false,
|
|
1361
|
-
useZapMemory: result.hanzo_rag_use_zap !== false
|
|
1362
|
-
});
|
|
1363
|
-
});
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
function normalizeRagSnippets(raw) {
|
|
1367
|
-
if (!raw) return [];
|
|
1368
|
-
const candidates = [];
|
|
1369
|
-
if (Array.isArray(raw)) candidates.push(...raw);
|
|
1370
|
-
if (Array.isArray(raw?.snippets)) candidates.push(...raw.snippets);
|
|
1371
|
-
if (Array.isArray(raw?.documents)) candidates.push(...raw.documents);
|
|
1372
|
-
if (Array.isArray(raw?.items)) candidates.push(...raw.items);
|
|
1373
|
-
if (Array.isArray(raw?.results)) candidates.push(...raw.results);
|
|
1374
|
-
if (Array.isArray(raw?.memories)) candidates.push(...raw.memories);
|
|
1375
|
-
if (Array.isArray(raw?.matches)) candidates.push(...raw.matches);
|
|
1376
|
-
if (!candidates.length && typeof raw?.content === "string") {
|
|
1377
|
-
candidates.push({ content: raw.content, source: raw.source || "memory" });
|
|
1378
|
-
}
|
|
1379
|
-
const snippets = candidates.map((item) => {
|
|
1380
|
-
const content = String(
|
|
1381
|
-
item?.content ?? item?.text ?? item?.snippet ?? item?.body ?? item?.value ?? ""
|
|
1382
|
-
).trim();
|
|
1383
|
-
if (!content) return null;
|
|
1384
|
-
return {
|
|
1385
|
-
content,
|
|
1386
|
-
title: item?.title ? String(item.title) : void 0,
|
|
1387
|
-
source: item?.source ? String(item.source) : void 0,
|
|
1388
|
-
score: typeof item?.score === "number" ? item.score : void 0,
|
|
1389
|
-
url: item?.url ? String(item.url) : void 0
|
|
1390
|
-
};
|
|
1391
|
-
}).filter((item) => !!item);
|
|
1392
|
-
return snippets.slice(0, 20);
|
|
1393
|
-
}
|
|
1394
|
-
async function queryRagFromZap(params, mgr) {
|
|
1395
|
-
if (!params.useZapMemory || !mgr.state.connected || !hasZapTool(mgr, "memory")) {
|
|
1396
|
-
return [];
|
|
1397
|
-
}
|
|
1398
|
-
const payloads = [
|
|
1399
|
-
{
|
|
1400
|
-
action: "query",
|
|
1401
|
-
query: params.query,
|
|
1402
|
-
top_k: params.topK,
|
|
1403
|
-
limit: params.topK,
|
|
1404
|
-
kb: params.knowledgeBase || void 0,
|
|
1405
|
-
page_context: params.pageContext
|
|
1406
|
-
},
|
|
1407
|
-
{
|
|
1408
|
-
query: params.query,
|
|
1409
|
-
topK: params.topK,
|
|
1410
|
-
limit: params.topK,
|
|
1411
|
-
knowledge_base: params.knowledgeBase || void 0,
|
|
1412
|
-
context: params.pageContext
|
|
1413
|
-
}
|
|
1414
|
-
];
|
|
1415
|
-
for (const payload of payloads) {
|
|
1416
|
-
try {
|
|
1417
|
-
const raw = await zapCallTool(mgr, "memory", payload, params.mcpId);
|
|
1418
|
-
const snippets = normalizeRagSnippets(raw);
|
|
1419
|
-
if (snippets.length) {
|
|
1420
|
-
return snippets.map((snippet) => ({
|
|
1421
|
-
...snippet,
|
|
1422
|
-
source: snippet.source || "zap-memory"
|
|
1423
|
-
}));
|
|
1424
|
-
}
|
|
1425
|
-
} catch {
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
return [];
|
|
1429
|
-
}
|
|
1430
|
-
async function queryRagFromEndpoint(params) {
|
|
1431
|
-
if (!params.endpoint) return [];
|
|
1432
|
-
const headers = {
|
|
1433
|
-
"Content-Type": "application/json"
|
|
1434
|
-
};
|
|
1435
|
-
if (params.apiKey) {
|
|
1436
|
-
headers.Authorization = `Bearer ${params.apiKey}`;
|
|
1437
|
-
}
|
|
1438
|
-
const response = await fetch(params.endpoint, {
|
|
1439
|
-
method: "POST",
|
|
1440
|
-
headers,
|
|
1441
|
-
body: JSON.stringify({
|
|
1442
|
-
query: params.query,
|
|
1443
|
-
top_k: params.topK,
|
|
1444
|
-
knowledge_base: params.knowledgeBase || void 0,
|
|
1445
|
-
page_context: params.includeTabContext ? params.pageContext : void 0,
|
|
1446
|
-
source: "hanzo-browser-extension"
|
|
1447
|
-
})
|
|
1448
|
-
});
|
|
1449
|
-
if (!response.ok) {
|
|
1450
|
-
const text = await response.text();
|
|
1451
|
-
throw new Error(`RAG endpoint error ${response.status}: ${text || "Unknown error"}`);
|
|
1452
|
-
}
|
|
1453
|
-
const raw = await response.json();
|
|
1454
|
-
return normalizeRagSnippets(raw).map((snippet) => ({
|
|
1455
|
-
...snippet,
|
|
1456
|
-
source: snippet.source || "rag-endpoint"
|
|
1457
|
-
}));
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
// src/shared/config.ts
|
|
1461
|
-
var IAM_LOGIN_URL = "https://hanzo.id";
|
|
1462
|
-
var IAM_API_URL = "https://iam.hanzo.ai";
|
|
1463
|
-
var IAM_API_PATH = "/v1/iam";
|
|
1464
|
-
var DEFAULT_CLIENT_ID = "hanzo-browser";
|
|
1465
|
-
var DEFAULT_SCOPES = "openid profile email";
|
|
1466
|
-
var DEFAULT_REDIRECT_URI = "https://hanzo.ai/callback";
|
|
1467
|
-
var STORAGE_KEYS = {
|
|
1468
|
-
accessToken: "hanzo_iam_access_token",
|
|
1469
|
-
refreshToken: "hanzo_iam_refresh_token",
|
|
1470
|
-
idToken: "hanzo_iam_id_token",
|
|
1471
|
-
expiresAt: "hanzo_iam_expires_at",
|
|
1472
|
-
user: "hanzo_iam_user"
|
|
1473
|
-
};
|
|
1474
|
-
var API_BASE_URL = "https://api.hanzo.ai";
|
|
1475
|
-
var KMS_API_URL = "https://kms.hanzo.ai";
|
|
1476
|
-
var LOCAL_PROVIDER_DEFAULTS = {
|
|
1477
|
-
ollama: { name: "Ollama", url: "http://localhost:11434", probePorts: [11434] },
|
|
1478
|
-
lmstudio: { name: "LM Studio", url: "http://localhost:1234/v1", probePorts: [1234] },
|
|
1479
|
-
"hanzo-desktop": { name: "Hanzo Desktop", url: "http://localhost:11435", probePorts: [11435, 9550] },
|
|
1480
|
-
"hanzo-node": { name: "Hanzo Node", url: "http://localhost:3690", probePorts: [3690, 8080] }
|
|
1481
|
-
};
|
|
1482
|
-
var DEFAULT_CDP_BRIDGE_PORT = 9224;
|
|
1483
|
-
var MODEL_CACHE_TTL_MS = 60 * 1e3;
|
|
1484
|
-
var CONFIG_STORAGE_MAP = {
|
|
1485
|
-
iamLoginUrl: { key: "hanzo_config_iam_login_url", fallback: IAM_LOGIN_URL },
|
|
1486
|
-
iamApiUrl: { key: "hanzo_config_iam_api_url", fallback: IAM_API_URL },
|
|
1487
|
-
apiBaseUrl: { key: "hanzo_config_api_base", fallback: API_BASE_URL },
|
|
1488
|
-
kmsApiUrl: { key: "hanzo_config_kms_url", fallback: KMS_API_URL },
|
|
1489
|
-
clientId: { key: "hanzo_config_client_id", fallback: DEFAULT_CLIENT_ID },
|
|
1490
|
-
scopes: { key: "hanzo_config_scopes", fallback: DEFAULT_SCOPES },
|
|
1491
|
-
redirectUri: { key: "hanzo_config_redirect_uri", fallback: DEFAULT_REDIRECT_URI },
|
|
1492
|
-
cdpBridgePort: { key: "hanzo_config_cdp_bridge_port", fallback: DEFAULT_CDP_BRIDGE_PORT }
|
|
1493
|
-
};
|
|
1494
|
-
async function loadRuntimeConfig(storage) {
|
|
1495
|
-
const keys = Object.values(CONFIG_STORAGE_MAP).map((v) => v.key);
|
|
1496
|
-
const result = await storage.get(keys);
|
|
1497
|
-
const config = {};
|
|
1498
|
-
for (const [field, { key, fallback }] of Object.entries(CONFIG_STORAGE_MAP)) {
|
|
1499
|
-
const stored = result[key];
|
|
1500
|
-
if (stored !== void 0 && stored !== null && stored !== "") {
|
|
1501
|
-
config[field] = typeof fallback === "number" ? Number(stored) : String(stored);
|
|
1502
|
-
} else {
|
|
1503
|
-
config[field] = fallback;
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
return config;
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
// src/shared/auth.ts
|
|
1510
|
-
function generateRandomString(length) {
|
|
1511
|
-
const array = new Uint8Array(length);
|
|
1512
|
-
crypto.getRandomValues(array);
|
|
1513
|
-
return Array.from(array, (b) => b.toString(36).padStart(2, "0")).join("").slice(0, length);
|
|
1514
|
-
}
|
|
1515
|
-
async function sha256(plain) {
|
|
1516
|
-
return crypto.subtle.digest("SHA-256", new TextEncoder().encode(plain));
|
|
1517
|
-
}
|
|
1518
|
-
function base64UrlEncode(buffer) {
|
|
1519
|
-
const bytes = new Uint8Array(buffer);
|
|
1520
|
-
let binary = "";
|
|
1521
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
1522
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1523
|
-
}
|
|
1524
|
-
async function generatePkceChallenge() {
|
|
1525
|
-
const codeVerifier = generateRandomString(64);
|
|
1526
|
-
const hash = await sha256(codeVerifier);
|
|
1527
|
-
const codeChallenge = base64UrlEncode(hash);
|
|
1528
|
-
return { codeVerifier, codeChallenge };
|
|
1529
|
-
}
|
|
1530
|
-
var _runtimeConfig = null;
|
|
1531
|
-
async function getRuntimeConfig(storage) {
|
|
1532
|
-
if (!_runtimeConfig) {
|
|
1533
|
-
_runtimeConfig = await loadRuntimeConfig(storage);
|
|
1534
|
-
}
|
|
1535
|
-
return _runtimeConfig;
|
|
1536
|
-
}
|
|
1537
|
-
async function storeTokens(storage, tokens) {
|
|
1538
|
-
const data = {
|
|
1539
|
-
[STORAGE_KEYS.accessToken]: tokens.access_token
|
|
1540
|
-
};
|
|
1541
|
-
if (tokens.refresh_token) data[STORAGE_KEYS.refreshToken] = tokens.refresh_token;
|
|
1542
|
-
if (tokens.id_token) data[STORAGE_KEYS.idToken] = tokens.id_token;
|
|
1543
|
-
if (tokens.expires_in) {
|
|
1544
|
-
data[STORAGE_KEYS.expiresAt] = Date.now() + tokens.expires_in * 1e3;
|
|
1545
|
-
}
|
|
1546
|
-
await storage.set(data);
|
|
1547
|
-
}
|
|
1548
|
-
async function getStoredTokens(storage) {
|
|
1549
|
-
const result = await storage.get([
|
|
1550
|
-
STORAGE_KEYS.accessToken,
|
|
1551
|
-
STORAGE_KEYS.refreshToken,
|
|
1552
|
-
STORAGE_KEYS.expiresAt
|
|
1553
|
-
]);
|
|
1554
|
-
return {
|
|
1555
|
-
accessToken: result[STORAGE_KEYS.accessToken] || null,
|
|
1556
|
-
refreshToken: result[STORAGE_KEYS.refreshToken] || null,
|
|
1557
|
-
expiresAt: result[STORAGE_KEYS.expiresAt] || null
|
|
1558
|
-
};
|
|
1559
|
-
}
|
|
1560
|
-
function openAuthTab(tabs, authorizeUrl, redirectUriPrefix) {
|
|
1561
|
-
return new Promise((resolve, reject) => {
|
|
1562
|
-
let authTabId;
|
|
1563
|
-
const timeout = setTimeout(() => {
|
|
1564
|
-
cleanup();
|
|
1565
|
-
reject(new Error("Login timed out"));
|
|
1566
|
-
}, 3e5);
|
|
1567
|
-
function cleanup() {
|
|
1568
|
-
clearTimeout(timeout);
|
|
1569
|
-
tabs.onUpdated.removeListener(onTabUpdated);
|
|
1570
|
-
tabs.onRemoved.removeListener(onTabRemoved);
|
|
1571
|
-
}
|
|
1572
|
-
function onTabUpdated(tabId, changeInfo) {
|
|
1573
|
-
if (tabId !== authTabId || !changeInfo.url) return;
|
|
1574
|
-
if (changeInfo.url.startsWith(redirectUriPrefix)) {
|
|
1575
|
-
cleanup();
|
|
1576
|
-
tabs.remove(tabId).catch(() => {
|
|
1577
|
-
});
|
|
1578
|
-
resolve(changeInfo.url);
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
function onTabRemoved(tabId) {
|
|
1582
|
-
if (tabId !== authTabId) return;
|
|
1583
|
-
cleanup();
|
|
1584
|
-
reject(new Error("Login cancelled"));
|
|
1585
|
-
}
|
|
1586
|
-
tabs.onUpdated.addListener(onTabUpdated);
|
|
1587
|
-
tabs.onRemoved.addListener(onTabRemoved);
|
|
1588
|
-
tabs.create({ url: authorizeUrl }).then((tab) => {
|
|
1589
|
-
authTabId = tab.id;
|
|
1590
|
-
}).catch((err) => {
|
|
1591
|
-
cleanup();
|
|
1592
|
-
reject(err);
|
|
1593
|
-
});
|
|
1594
|
-
});
|
|
1595
|
-
}
|
|
1596
|
-
async function login(adapter) {
|
|
1597
|
-
const config = await getRuntimeConfig(adapter.storage);
|
|
1598
|
-
const { codeVerifier, codeChallenge } = await generatePkceChallenge();
|
|
1599
|
-
const state = generateRandomString(32);
|
|
1600
|
-
const redirectUri = config.redirectUri;
|
|
1601
|
-
const authorizeUrl = new URL(`${config.iamLoginUrl}${IAM_API_PATH}/oauth/authorize`);
|
|
1602
|
-
authorizeUrl.searchParams.set("client_id", config.clientId);
|
|
1603
|
-
authorizeUrl.searchParams.set("response_type", "code");
|
|
1604
|
-
authorizeUrl.searchParams.set("redirect_uri", redirectUri);
|
|
1605
|
-
authorizeUrl.searchParams.set("code_challenge", codeChallenge);
|
|
1606
|
-
authorizeUrl.searchParams.set("code_challenge_method", "S256");
|
|
1607
|
-
authorizeUrl.searchParams.set("scope", config.scopes);
|
|
1608
|
-
authorizeUrl.searchParams.set("state", state);
|
|
1609
|
-
const callbackUrl = await openAuthTab(adapter.tabs, authorizeUrl.toString(), redirectUri);
|
|
1610
|
-
const url = new URL(callbackUrl);
|
|
1611
|
-
if (url.searchParams.get("state") !== state) throw new Error("State mismatch \u2014 possible CSRF");
|
|
1612
|
-
const code = url.searchParams.get("code");
|
|
1613
|
-
const directToken = url.searchParams.get("access_token");
|
|
1614
|
-
if (directToken) {
|
|
1615
|
-
await storeTokens(adapter.storage, {
|
|
1616
|
-
access_token: directToken,
|
|
1617
|
-
refresh_token: url.searchParams.get("refresh_token") || void 0,
|
|
1618
|
-
token_type: "Bearer"
|
|
1619
|
-
});
|
|
1620
|
-
} else if (code) {
|
|
1621
|
-
const tokenResponse = await fetch(`${config.iamApiUrl}${IAM_API_PATH}/oauth/token`, {
|
|
1622
|
-
method: "POST",
|
|
1623
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1624
|
-
body: new URLSearchParams({
|
|
1625
|
-
grant_type: "authorization_code",
|
|
1626
|
-
client_id: config.clientId,
|
|
1627
|
-
code,
|
|
1628
|
-
redirect_uri: redirectUri,
|
|
1629
|
-
code_verifier: codeVerifier
|
|
1630
|
-
}).toString()
|
|
1631
|
-
});
|
|
1632
|
-
if (!tokenResponse.ok) throw new Error(`Token exchange failed: ${await tokenResponse.text()}`);
|
|
1633
|
-
await storeTokens(adapter.storage, await tokenResponse.json());
|
|
1634
|
-
} else {
|
|
1635
|
-
const error = url.searchParams.get("error_description") || url.searchParams.get("error") || "No authorization code or token";
|
|
1636
|
-
throw new Error(error);
|
|
1637
|
-
}
|
|
1638
|
-
const { accessToken } = await getStoredTokens(adapter.storage);
|
|
1639
|
-
if (!accessToken) throw new Error("Login succeeded but no token was stored");
|
|
1640
|
-
const user = await fetchUserInfo(adapter.storage, accessToken);
|
|
1641
|
-
await adapter.storage.set({ [STORAGE_KEYS.user]: user });
|
|
1642
|
-
return user;
|
|
1643
|
-
}
|
|
1644
|
-
async function signup(adapter) {
|
|
1645
|
-
const config = await getRuntimeConfig(adapter.storage);
|
|
1646
|
-
await adapter.tabs.create({ url: `${config.iamLoginUrl}/signup` });
|
|
1647
|
-
}
|
|
1648
|
-
async function logout(storage) {
|
|
1649
|
-
await storage.remove(Object.values(STORAGE_KEYS));
|
|
1650
|
-
}
|
|
1651
|
-
async function getValidAccessToken(storage) {
|
|
1652
|
-
const { accessToken, refreshToken, expiresAt } = await getStoredTokens(storage);
|
|
1653
|
-
if (!accessToken) return null;
|
|
1654
|
-
if (expiresAt && Date.now() < expiresAt - 6e4) return accessToken;
|
|
1655
|
-
if (refreshToken) {
|
|
1656
|
-
try {
|
|
1657
|
-
return await refreshAccessToken(storage, refreshToken);
|
|
1658
|
-
} catch {
|
|
1659
|
-
await logout(storage);
|
|
1660
|
-
return null;
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
return accessToken;
|
|
1664
|
-
}
|
|
1665
|
-
async function refreshAccessToken(storage, refreshToken) {
|
|
1666
|
-
const config = await getRuntimeConfig(storage);
|
|
1667
|
-
const response = await fetch(`${config.iamApiUrl}${IAM_API_PATH}/oauth/token`, {
|
|
1668
|
-
method: "POST",
|
|
1669
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1670
|
-
body: new URLSearchParams({
|
|
1671
|
-
grant_type: "refresh_token",
|
|
1672
|
-
client_id: config.clientId,
|
|
1673
|
-
refresh_token: refreshToken
|
|
1674
|
-
}).toString()
|
|
1675
|
-
});
|
|
1676
|
-
if (!response.ok) throw new Error("Token refresh failed");
|
|
1677
|
-
const tokens = await response.json();
|
|
1678
|
-
await storeTokens(storage, tokens);
|
|
1679
|
-
return tokens.access_token;
|
|
1680
|
-
}
|
|
1681
|
-
async function fetchUserInfo(storage, accessToken) {
|
|
1682
|
-
const config = await getRuntimeConfig(storage);
|
|
1683
|
-
const headers = { Authorization: `Bearer ${accessToken}` };
|
|
1684
|
-
const response = await fetch(`${config.iamApiUrl}${IAM_API_PATH}/oauth/userinfo`, { headers });
|
|
1685
|
-
if (!response.ok) throw new Error("Failed to fetch user info");
|
|
1686
|
-
return response.json();
|
|
1687
|
-
}
|
|
1688
|
-
async function getUserInfo(storage) {
|
|
1689
|
-
const result = await storage.get([STORAGE_KEYS.user]);
|
|
1690
|
-
return result[STORAGE_KEYS.user] || null;
|
|
1691
|
-
}
|
|
1692
|
-
async function getAuthStatus(storage) {
|
|
1693
|
-
const token = await getValidAccessToken(storage);
|
|
1694
|
-
if (!token) {
|
|
1695
|
-
return { authenticated: false, user: null };
|
|
1696
|
-
}
|
|
1697
|
-
const user = await getUserInfo(storage);
|
|
1698
|
-
return { authenticated: true, user };
|
|
1699
|
-
}
|
|
1700
|
-
function getBrowser() {
|
|
1701
|
-
return globalThis.browser ?? globalThis.chrome;
|
|
1702
|
-
}
|
|
1703
|
-
function webExtensionAdapter() {
|
|
1704
|
-
const b = getBrowser();
|
|
1705
|
-
return {
|
|
1706
|
-
storage: {
|
|
1707
|
-
// Promise-returning on every runtime once the polyfill is loaded.
|
|
1708
|
-
get: (keys) => Promise.resolve(b.storage.local.get(keys)),
|
|
1709
|
-
set: (items) => Promise.resolve(b.storage.local.set(items)),
|
|
1710
|
-
remove: (keys) => Promise.resolve(b.storage.local.remove(keys))
|
|
1711
|
-
},
|
|
1712
|
-
tabs: {
|
|
1713
|
-
create: (opts) => Promise.resolve(b.tabs.create(opts)),
|
|
1714
|
-
remove: (tabId) => Promise.resolve(b.tabs.remove(tabId)),
|
|
1715
|
-
onUpdated: b.tabs.onUpdated,
|
|
1716
|
-
onRemoved: b.tabs.onRemoved
|
|
1717
|
-
}
|
|
1718
|
-
};
|
|
1719
|
-
}
|
|
1720
|
-
function firefoxAdapter() {
|
|
1721
|
-
return webExtensionAdapter();
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
// src/shared/tab-id.ts
|
|
1725
|
-
function parseTabId(raw) {
|
|
1726
|
-
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
|
1727
|
-
if (typeof raw !== "string") return null;
|
|
1728
|
-
if (raw === "") return null;
|
|
1729
|
-
const m = raw.match(/^(?:tab-)?(\d+)$/);
|
|
1730
|
-
if (!m) return null;
|
|
1731
|
-
const n = Number(m[1]);
|
|
1732
|
-
return Number.isFinite(n) ? n : null;
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
// src/background-firefox.ts
|
|
1736
|
-
var BROWSER_NAME = "firefox";
|
|
1737
|
-
var VERSION = typeof browser !== "undefined" ? browser.runtime.getManifest().version : "0";
|
|
1738
|
-
var PRIVILEGED_URL_PATTERNS = [
|
|
1739
|
-
/^about:/i,
|
|
1740
|
-
// about:debugging, about:addons, about:config…
|
|
1741
|
-
/^moz-extension:/i,
|
|
1742
|
-
// any extension's own pages
|
|
1743
|
-
/^chrome:/i,
|
|
1744
|
-
// chrome://… (Firefox accepts these too)
|
|
1745
|
-
/^view-source:/i,
|
|
1746
|
-
/^resource:/i,
|
|
1747
|
-
/^file:/i,
|
|
1748
|
-
// local files unless explicit perm granted
|
|
1749
|
-
/^https?:\/\/addons\.mozilla\.org\b/i
|
|
1750
|
-
// AMO blocks WebExtensions by policy
|
|
1751
|
-
];
|
|
1752
|
-
function isPrivilegedTab(tab) {
|
|
1753
|
-
if (!tab) return false;
|
|
1754
|
-
const url = tab.url || tab.pendingUrl || "";
|
|
1755
|
-
if (!url) {
|
|
1756
|
-
return true;
|
|
1757
|
-
}
|
|
1758
|
-
return PRIVILEGED_URL_PATTERNS.some((rx) => rx.test(url));
|
|
1759
|
-
}
|
|
1760
|
-
var zapMgr = createZapManager();
|
|
1761
|
-
var controlSession = {
|
|
1762
|
-
active: false,
|
|
1763
|
-
tabId: null,
|
|
1764
|
-
task: null,
|
|
1765
|
-
startedAt: null
|
|
1766
|
-
};
|
|
1767
|
-
var HanzoFirefoxExtension = class {
|
|
1768
|
-
constructor() {
|
|
1769
|
-
this.wsConnection = null;
|
|
1770
|
-
this.cdpPort = 9223;
|
|
1771
|
-
this.reconnectTimeout = 3e3;
|
|
1772
|
-
console.log("[Hanzo] Firefox extension initializing...");
|
|
1773
|
-
this.connect();
|
|
1774
|
-
}
|
|
1775
|
-
connect() {
|
|
1776
|
-
const url = `ws://localhost:${this.cdpPort}/cdp`;
|
|
1777
|
-
console.log(`[Hanzo] Connecting to ${url}`);
|
|
1778
|
-
this.wsConnection = new WebSocket(url);
|
|
1779
|
-
this.wsConnection.onopen = () => {
|
|
1780
|
-
console.log("[Hanzo] WebSocket CONNECTED");
|
|
1781
|
-
this.register();
|
|
1782
|
-
};
|
|
1783
|
-
this.wsConnection.onmessage = async (event) => {
|
|
1784
|
-
try {
|
|
1785
|
-
const message = JSON.parse(event.data);
|
|
1786
|
-
if (message.type === "extension-request") {
|
|
1787
|
-
console.log("[Hanzo] Extension request:", message.action);
|
|
1788
|
-
const result = await this.handleExtensionRequest(message.action, message.params || {});
|
|
1789
|
-
this.send({ type: "extension-response", id: message.id, result });
|
|
1790
|
-
return;
|
|
1791
|
-
}
|
|
1792
|
-
console.log("[Hanzo] Received:", message.method || "response");
|
|
1793
|
-
const response = await this.handleCommand(message);
|
|
1794
|
-
this.send(response);
|
|
1795
|
-
} catch (e) {
|
|
1796
|
-
console.error("[Hanzo] Message handler error:", e);
|
|
1797
|
-
this.send({ id: 0, error: { code: -1, message: String(e) } });
|
|
1798
|
-
}
|
|
1799
|
-
};
|
|
1800
|
-
this.wsConnection.onerror = (error) => {
|
|
1801
|
-
console.error("[Hanzo] WebSocket error:", error);
|
|
1802
|
-
};
|
|
1803
|
-
this.wsConnection.onclose = () => {
|
|
1804
|
-
console.log("[Hanzo] Disconnected, reconnecting...");
|
|
1805
|
-
setTimeout(() => this.connect(), this.reconnectTimeout);
|
|
1806
|
-
};
|
|
1807
|
-
}
|
|
1808
|
-
register() {
|
|
1809
|
-
this.send({
|
|
1810
|
-
type: "register",
|
|
1811
|
-
role: "cdp-provider",
|
|
1812
|
-
browser: BROWSER_NAME,
|
|
1813
|
-
// 'firefox'
|
|
1814
|
-
capabilities: [
|
|
1815
|
-
"navigate",
|
|
1816
|
-
"screenshot",
|
|
1817
|
-
"click",
|
|
1818
|
-
"dblclick",
|
|
1819
|
-
"hover",
|
|
1820
|
-
"type",
|
|
1821
|
-
"evaluate",
|
|
1822
|
-
"tabs",
|
|
1823
|
-
"fill",
|
|
1824
|
-
"clear",
|
|
1825
|
-
"reload",
|
|
1826
|
-
"goBack",
|
|
1827
|
-
"goForward",
|
|
1828
|
-
"select",
|
|
1829
|
-
"check",
|
|
1830
|
-
"uncheck",
|
|
1831
|
-
"press_key",
|
|
1832
|
-
"content",
|
|
1833
|
-
"snapshot",
|
|
1834
|
-
"getText",
|
|
1835
|
-
"getHTML",
|
|
1836
|
-
"getAttribute",
|
|
1837
|
-
"getElementInfo",
|
|
1838
|
-
"getPageInfo",
|
|
1839
|
-
"querySelectorAll",
|
|
1840
|
-
"waitForSelector",
|
|
1841
|
-
"waitForNavigation",
|
|
1842
|
-
// CSP-safe text/label primitives + composite waits (1.9.16+)
|
|
1843
|
-
"clickByText",
|
|
1844
|
-
"fillByLabel",
|
|
1845
|
-
"findByText",
|
|
1846
|
-
"listForm",
|
|
1847
|
-
"press",
|
|
1848
|
-
"scroll",
|
|
1849
|
-
"waitForText",
|
|
1850
|
-
"waitForMutation",
|
|
1851
|
-
"submitForm",
|
|
1852
|
-
"uploadFile",
|
|
1853
|
-
"dialogAccept",
|
|
1854
|
-
"observe",
|
|
1855
|
-
"observeRead",
|
|
1856
|
-
"observeStop",
|
|
1857
|
-
// Coordinate-level Input.* (1.9.16+)
|
|
1858
|
-
"Input.dispatchMouseEvent",
|
|
1859
|
-
"Input.scrollWheel",
|
|
1860
|
-
// DOM.* + Page.* aliases (1.9.16+)
|
|
1861
|
-
"DOM.querySelector",
|
|
1862
|
-
"DOM.scrollIntoView",
|
|
1863
|
-
"DOM.focus",
|
|
1864
|
-
"Page.printToPDF",
|
|
1865
|
-
// Existing legacy
|
|
1866
|
-
"fetch",
|
|
1867
|
-
"cookies",
|
|
1868
|
-
"localStorage",
|
|
1869
|
-
"injectScript",
|
|
1870
|
-
"injectCSS",
|
|
1871
|
-
"createTab",
|
|
1872
|
-
"closeTab",
|
|
1873
|
-
"activateTab",
|
|
1874
|
-
"history",
|
|
1875
|
-
"observeMutations",
|
|
1876
|
-
"computedStyles",
|
|
1877
|
-
"boundingRects",
|
|
1878
|
-
"setHTML",
|
|
1879
|
-
"setText",
|
|
1880
|
-
"setAttribute",
|
|
1881
|
-
"removeAttribute",
|
|
1882
|
-
"setStyle",
|
|
1883
|
-
"addClass",
|
|
1884
|
-
"removeClass",
|
|
1885
|
-
"insertElement",
|
|
1886
|
-
"removeElement",
|
|
1887
|
-
"monitor",
|
|
1888
|
-
"audit"
|
|
1889
|
-
]
|
|
1890
|
-
});
|
|
1891
|
-
}
|
|
1892
|
-
send(data) {
|
|
1893
|
-
if (this.wsConnection?.readyState === WebSocket.OPEN) {
|
|
1894
|
-
this.wsConnection.send(JSON.stringify(data));
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
async handleCommand(message) {
|
|
1898
|
-
const { id, method, params = {} } = message;
|
|
1899
|
-
try {
|
|
1900
|
-
const result = await this.executeMethod(method, params);
|
|
1901
|
-
return { id, result };
|
|
1902
|
-
} catch (e) {
|
|
1903
|
-
return { id, error: { code: -1, message: String(e) } };
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
/**
|
|
1907
|
-
* Run JS in a page tab and return its result, with a hard timeout.
|
|
1908
|
-
*
|
|
1909
|
-
* Firefox MV3 removed `browser.tabs.executeScript` (the string-based
|
|
1910
|
-
* MV2 API). The replacement is `browser.scripting.executeScript({...})`
|
|
1911
|
-
* which only accepts a function (or files), not a code string. To keep
|
|
1912
|
-
* the existing call sites that pass dynamic strings working, we inject
|
|
1913
|
-
* a one-line trampoline `(c) => Function(c)()` and forward the user's
|
|
1914
|
-
* code as an argument, executing in the page's MAIN world (so the
|
|
1915
|
-
* page's own CSP applies and `Function` is allowed on most sites).
|
|
1916
|
-
*
|
|
1917
|
-
* Falls back to MV2 `tabs.executeScript` on browsers / runtimes that
|
|
1918
|
-
* still have it (older Firefox, Chrome MV2 which we don't ship but
|
|
1919
|
-
* don't want to break).
|
|
1920
|
-
*/
|
|
1921
|
-
executeScriptWithTimeout(tabId, code, timeoutMs = 1e4) {
|
|
1922
|
-
const exec = (async () => {
|
|
1923
|
-
const scripting = browser.scripting;
|
|
1924
|
-
if (scripting?.executeScript) {
|
|
1925
|
-
const runIn = async (world) => {
|
|
1926
|
-
const results = await scripting.executeScript({
|
|
1927
|
-
target: { tabId },
|
|
1928
|
-
world,
|
|
1929
|
-
args: [code],
|
|
1930
|
-
func: (codeStr) => {
|
|
1931
|
-
try {
|
|
1932
|
-
return Function(codeStr)();
|
|
1933
|
-
} catch (e) {
|
|
1934
|
-
return { __hanzo_error: e?.message || String(e) };
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
});
|
|
1938
|
-
return Array.isArray(results) ? results.map((r) => r?.result) : [results];
|
|
1939
|
-
};
|
|
1940
|
-
const main = await runIn("MAIN");
|
|
1941
|
-
const blocked = main.length > 0 && main.every(
|
|
1942
|
-
(r) => r && typeof r === "object" && r.__hanzo_error && /CSP|unsafe-eval|Content Security|Function|eval/i.test(String(r.__hanzo_error))
|
|
1943
|
-
);
|
|
1944
|
-
if (!blocked) return main;
|
|
1945
|
-
return runIn("ISOLATED");
|
|
1946
|
-
}
|
|
1947
|
-
const legacy = browser.tabs?.executeScript;
|
|
1948
|
-
if (legacy) return legacy.call(browser.tabs, tabId, { code });
|
|
1949
|
-
throw new Error("No executeScript API available (need MV3 scripting permission)");
|
|
1950
|
-
})();
|
|
1951
|
-
return Promise.race([
|
|
1952
|
-
exec,
|
|
1953
|
-
new Promise(
|
|
1954
|
-
(_, reject) => setTimeout(() => reject(new Error("executeScript timeout")), timeoutMs)
|
|
1955
|
-
)
|
|
1956
|
-
]);
|
|
1957
|
-
}
|
|
1958
|
-
/**
|
|
1959
|
-
* Resolve the tab ID to operate on. Order of precedence:
|
|
1960
|
-
*
|
|
1961
|
-
* 1. params.tabId — number, or "tab-NNN" string from getTargets, or "NNN".
|
|
1962
|
-
* 2. params.targetId — same as tabId but kept for CDP-style callers.
|
|
1963
|
-
* 3. params.tabIndex — index into the current window's tab list.
|
|
1964
|
-
* 4. Fall back to the active tab in the current window.
|
|
1965
|
-
*
|
|
1966
|
-
* Without (1) or (2) the bridge always picks the OS-active tab, which is
|
|
1967
|
-
* almost never what an automation client wants when many windows are open.
|
|
1968
|
-
*/
|
|
1969
|
-
async resolveTab(params) {
|
|
1970
|
-
const rawId = params.tabId ?? params.targetId;
|
|
1971
|
-
if (rawId !== void 0 && rawId !== null) {
|
|
1972
|
-
const id = parseTabId(rawId);
|
|
1973
|
-
if (id !== null) {
|
|
1974
|
-
try {
|
|
1975
|
-
return await browser.tabs.get(id);
|
|
1976
|
-
} catch {
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
if (typeof params.tabIndex === "number") {
|
|
1981
|
-
const tabs = await browser.tabs.query({ currentWindow: true });
|
|
1982
|
-
const tab = tabs[params.tabIndex];
|
|
1983
|
-
if (tab) return tab;
|
|
1984
|
-
}
|
|
1985
|
-
const [activeTab] = await browser.tabs.query({ active: true, currentWindow: true });
|
|
1986
|
-
if (activeTab) return activeTab;
|
|
1987
|
-
throw new Error("No active tab");
|
|
1988
|
-
}
|
|
1989
|
-
/**
|
|
1990
|
-
* Run JS in the page via executeScript and return the serialized result.
|
|
1991
|
-
* Bumped to throw on empty/missing results so undefined → {} can no
|
|
1992
|
-
* longer hide a privileged-tab or CSP failure (1.9.2).
|
|
1993
|
-
*/
|
|
1994
|
-
async runInPage(tabId, code, timeoutMs = 1e4) {
|
|
1995
|
-
let tab;
|
|
1996
|
-
try {
|
|
1997
|
-
tab = await browser.tabs.get(tabId);
|
|
1998
|
-
} catch {
|
|
1999
|
-
}
|
|
2000
|
-
if (tab && isPrivilegedTab(tab)) {
|
|
2001
|
-
throw new Error(
|
|
2002
|
-
`Cannot run JS in privileged tab ${tabId} (${tab.url}). Activate a regular http(s) tab via tabId/tab_id parameter.`
|
|
2003
|
-
);
|
|
2004
|
-
}
|
|
2005
|
-
const wrappedCode = `
|
|
2006
|
-
try {
|
|
2007
|
-
var __r = (function() { ${code} })();
|
|
2008
|
-
return __r === undefined ? null : __r;
|
|
2009
|
-
} catch(__e) {
|
|
2010
|
-
return { __hanzo_error: __e.message || String(__e) };
|
|
2011
|
-
}
|
|
2012
|
-
`;
|
|
2013
|
-
const results = await this.executeScriptWithTimeout(tabId, wrappedCode, timeoutMs);
|
|
2014
|
-
if (!Array.isArray(results) || results.length === 0) {
|
|
2015
|
-
throw new Error(
|
|
2016
|
-
`executeScript returned no frames for tab ${tabId} \u2014 page may be privileged, in a process the extension can't access, or unloaded.`
|
|
2017
|
-
);
|
|
2018
|
-
}
|
|
2019
|
-
const result = results[0];
|
|
2020
|
-
if (result === void 0) {
|
|
2021
|
-
throw new Error(
|
|
2022
|
-
`executeScript ran but returned no value (frame errored outside the wrapped try/catch \u2014 most often page CSP blocking Function()). Tab ${tabId}.`
|
|
2023
|
-
);
|
|
2024
|
-
}
|
|
2025
|
-
if (result && typeof result === "object" && result.__hanzo_error) {
|
|
2026
|
-
throw new Error(result.__hanzo_error);
|
|
2027
|
-
}
|
|
2028
|
-
return result;
|
|
2029
|
-
}
|
|
2030
|
-
/**
|
|
2031
|
-
* CSP-safe alternative to runInPage. Passes a real function reference
|
|
2032
|
-
* (and serializable args) to scripting.executeScript. Because no
|
|
2033
|
-
* Function() or eval() is constructed at runtime, page CSP that
|
|
2034
|
-
* blocks 'unsafe-eval' does NOT block this path. Works on .gov, on
|
|
2035
|
-
* sites that ship strict default-src 'self', and on github.com.
|
|
2036
|
-
*
|
|
2037
|
-
* Use this for any DOM read/write the bridge needs to do. The
|
|
2038
|
-
* `fn` argument is a normal JS function — it is serialized by
|
|
2039
|
-
* Firefox/Chrome's scripting API and executed in the MAIN world.
|
|
2040
|
-
*
|
|
2041
|
-
* Added in 1.9.16 alongside the CDP-shaped decomplected surface.
|
|
2042
|
-
*/
|
|
2043
|
-
async runFunc(tabId, fn, args = [], timeoutMs = 1e4) {
|
|
2044
|
-
let tab;
|
|
2045
|
-
try {
|
|
2046
|
-
tab = await browser.tabs.get(tabId);
|
|
2047
|
-
} catch {
|
|
2048
|
-
}
|
|
2049
|
-
if (tab && isPrivilegedTab(tab)) {
|
|
2050
|
-
throw new Error(
|
|
2051
|
-
`Cannot run JS in privileged tab ${tabId} (${tab.url}).`
|
|
2052
|
-
);
|
|
2053
|
-
}
|
|
2054
|
-
const scripting = browser.scripting;
|
|
2055
|
-
if (!scripting?.executeScript) {
|
|
2056
|
-
throw new Error("scripting.executeScript not available (need MV3 scripting permission)");
|
|
2057
|
-
}
|
|
2058
|
-
const exec = (async () => {
|
|
2059
|
-
const runIn = async (world) => {
|
|
2060
|
-
const results = await scripting.executeScript({
|
|
2061
|
-
target: { tabId },
|
|
2062
|
-
world,
|
|
2063
|
-
args,
|
|
2064
|
-
func: fn
|
|
2065
|
-
});
|
|
2066
|
-
return Array.isArray(results) ? results : [results];
|
|
2067
|
-
};
|
|
2068
|
-
let out;
|
|
2069
|
-
try {
|
|
2070
|
-
out = await runIn("MAIN");
|
|
2071
|
-
} catch (e) {
|
|
2072
|
-
out = await runIn("ISOLATED");
|
|
2073
|
-
}
|
|
2074
|
-
if (!Array.isArray(out) || out.length === 0) {
|
|
2075
|
-
throw new Error(
|
|
2076
|
-
`runFunc returned no frames for tab ${tabId} \u2014 page may be privileged or unloaded.`
|
|
2077
|
-
);
|
|
2078
|
-
}
|
|
2079
|
-
const first = out[0] || {};
|
|
2080
|
-
if (first.error) {
|
|
2081
|
-
throw new Error(`runFunc page-side error: ${first.error}`);
|
|
2082
|
-
}
|
|
2083
|
-
const result = first.result;
|
|
2084
|
-
if (result && typeof result === "object" && result.__hanzo_error) {
|
|
2085
|
-
throw new Error(result.__hanzo_error);
|
|
2086
|
-
}
|
|
2087
|
-
return result === void 0 ? null : result;
|
|
2088
|
-
})();
|
|
2089
|
-
return Promise.race([
|
|
2090
|
-
exec,
|
|
2091
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("runFunc timeout")), timeoutMs))
|
|
2092
|
-
]);
|
|
2093
|
-
}
|
|
2094
|
-
/**
|
|
2095
|
-
* Public dispatcher for ZAP-server-initiated requests. Mirrors
|
|
2096
|
-
* `CDPBridge.dispatchMethod` on the Chrome side so background-ZAP wiring
|
|
2097
|
-
* is symmetric across browsers. Calls into the same canonical
|
|
2098
|
-
* ``executeMethod`` switch already used by the legacy WS path.
|
|
2099
|
-
*/
|
|
2100
|
-
async dispatchMethod(method, params) {
|
|
2101
|
-
const result = await this.executeMethod(method, params || {});
|
|
2102
|
-
if (result && typeof result === "object" && "error" in result && result.error) {
|
|
2103
|
-
const err = new Error(String(result.error));
|
|
2104
|
-
throw err;
|
|
2105
|
-
}
|
|
2106
|
-
return result;
|
|
2107
|
-
}
|
|
2108
|
-
async executeMethod(method, params) {
|
|
2109
|
-
let tab;
|
|
2110
|
-
try {
|
|
2111
|
-
tab = await this.resolveTab(params);
|
|
2112
|
-
} catch {
|
|
2113
|
-
tab = null;
|
|
2114
|
-
}
|
|
2115
|
-
const tabId = tab?.id ?? void 0;
|
|
2116
|
-
switch (method) {
|
|
2117
|
-
// =====================================================================
|
|
2118
|
-
// Target / Tab management
|
|
2119
|
-
// =====================================================================
|
|
2120
|
-
case "Target.getTargets": {
|
|
2121
|
-
const allTabs = await browser.tabs.query({});
|
|
2122
|
-
return {
|
|
2123
|
-
targetInfos: allTabs.map((t) => ({
|
|
2124
|
-
targetId: `tab-${t.id}`,
|
|
2125
|
-
type: "page",
|
|
2126
|
-
title: t.title || "",
|
|
2127
|
-
url: t.url || ""
|
|
2128
|
-
}))
|
|
2129
|
-
};
|
|
2130
|
-
}
|
|
2131
|
-
case "Target.createTarget": {
|
|
2132
|
-
const newTab = await browser.tabs.create({ url: params.url || "about:blank" });
|
|
2133
|
-
return { targetId: `tab-${newTab.id}`, tabId: newTab.id };
|
|
2134
|
-
}
|
|
2135
|
-
case "Target.closeTarget": {
|
|
2136
|
-
const closeId = parseTabId(params.targetId ?? params.tabId);
|
|
2137
|
-
if (closeId !== null) await browser.tabs.remove(closeId);
|
|
2138
|
-
return { success: true };
|
|
2139
|
-
}
|
|
2140
|
-
case "Target.activateTarget": {
|
|
2141
|
-
const activateId = parseTabId(params.targetId ?? params.tabId);
|
|
2142
|
-
if (activateId !== null) await browser.tabs.update(activateId, { active: true });
|
|
2143
|
-
return { success: true };
|
|
2144
|
-
}
|
|
2145
|
-
// =====================================================================
|
|
2146
|
-
// Tab info — native APIs, never block on page content
|
|
2147
|
-
// =====================================================================
|
|
2148
|
-
case "hanzo.url": {
|
|
2149
|
-
return { result: { value: tab?.url || "" } };
|
|
2150
|
-
}
|
|
2151
|
-
case "hanzo.title": {
|
|
2152
|
-
return { result: { value: tab?.title || "" } };
|
|
2153
|
-
}
|
|
2154
|
-
case "hanzo.tabInfo": {
|
|
2155
|
-
return {
|
|
2156
|
-
result: {
|
|
2157
|
-
value: {
|
|
2158
|
-
id: tab?.id,
|
|
2159
|
-
url: tab?.url,
|
|
2160
|
-
title: tab?.title,
|
|
2161
|
-
status: tab?.status,
|
|
2162
|
-
favIconUrl: tab?.favIconUrl
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
};
|
|
2166
|
-
}
|
|
2167
|
-
case "hanzo.getTabs":
|
|
2168
|
-
case "hanzo.listTabs": {
|
|
2169
|
-
const tabs = await browser.tabs.query({});
|
|
2170
|
-
return {
|
|
2171
|
-
tabs: tabs.map((t) => ({
|
|
2172
|
-
id: t.id,
|
|
2173
|
-
url: t.url,
|
|
2174
|
-
title: t.title,
|
|
2175
|
-
active: t.active
|
|
2176
|
-
}))
|
|
2177
|
-
};
|
|
2178
|
-
}
|
|
2179
|
-
case "hanzo.getHistory": {
|
|
2180
|
-
const maxResults = params.maxResults || 25;
|
|
2181
|
-
try {
|
|
2182
|
-
const items = await browser.history.search({ text: "", maxResults, startTime: 0 });
|
|
2183
|
-
return { history: items.map((h) => ({ url: h.url, title: h.title, lastVisitTime: h.lastVisitTime })) };
|
|
2184
|
-
} catch {
|
|
2185
|
-
return { history: [], error: "history API not available" };
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
// =====================================================================
|
|
2189
|
-
// Navigation
|
|
2190
|
-
// =====================================================================
|
|
2191
|
-
case "Page.navigate": {
|
|
2192
|
-
if (!tabId) throw new Error("No active tab");
|
|
2193
|
-
this.notifyOverlay(tabId, "ai.control.status", { text: `Navigating to ${params.url}` });
|
|
2194
|
-
await browser.tabs.update(tabId, { url: params.url });
|
|
2195
|
-
return { frameId: "main" };
|
|
2196
|
-
}
|
|
2197
|
-
case "Page.reload": {
|
|
2198
|
-
if (!tabId) throw new Error("No active tab");
|
|
2199
|
-
await browser.tabs.reload(tabId);
|
|
2200
|
-
return { success: true };
|
|
2201
|
-
}
|
|
2202
|
-
case "Page.goBack": {
|
|
2203
|
-
if (!tabId) throw new Error("No active tab");
|
|
2204
|
-
await browser.tabs.goBack(tabId);
|
|
2205
|
-
return { success: true };
|
|
2206
|
-
}
|
|
2207
|
-
case "Page.goForward": {
|
|
2208
|
-
if (!tabId) throw new Error("No active tab");
|
|
2209
|
-
await browser.tabs.goForward(tabId);
|
|
2210
|
-
return { success: true };
|
|
2211
|
-
}
|
|
2212
|
-
case "hanzo.waitForNavigation": {
|
|
2213
|
-
if (!tabId) throw new Error("No active tab");
|
|
2214
|
-
const navTimeout = params.timeout || 3e4;
|
|
2215
|
-
return new Promise((resolve) => {
|
|
2216
|
-
let done = false;
|
|
2217
|
-
const timer = setTimeout(() => {
|
|
2218
|
-
if (!done) {
|
|
2219
|
-
done = true;
|
|
2220
|
-
browser.webNavigation.onCompleted.removeListener(onNav);
|
|
2221
|
-
resolve({ success: false, error: "timeout" });
|
|
2222
|
-
}
|
|
2223
|
-
}, navTimeout);
|
|
2224
|
-
function onNav(details) {
|
|
2225
|
-
if (details.tabId === tabId && details.frameId === 0) {
|
|
2226
|
-
done = true;
|
|
2227
|
-
clearTimeout(timer);
|
|
2228
|
-
browser.webNavigation.onCompleted.removeListener(onNav);
|
|
2229
|
-
resolve({ success: true, url: details.url });
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
browser.webNavigation.onCompleted.addListener(onNav);
|
|
2233
|
-
});
|
|
2234
|
-
}
|
|
2235
|
-
case "Page.waitForLoadState": {
|
|
2236
|
-
if (!tabId) throw new Error("No active tab");
|
|
2237
|
-
const loadTimeout = params.timeout || 3e4;
|
|
2238
|
-
const start = Date.now();
|
|
2239
|
-
while (Date.now() - start < loadTimeout) {
|
|
2240
|
-
const t = await browser.tabs.get(tabId);
|
|
2241
|
-
if (t.status === "complete") return { success: true };
|
|
2242
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
2243
|
-
}
|
|
2244
|
-
return { success: false, error: "timeout" };
|
|
2245
|
-
}
|
|
2246
|
-
// =====================================================================
|
|
2247
|
-
// JavaScript evaluation
|
|
2248
|
-
// =====================================================================
|
|
2249
|
-
case "Runtime.evaluate":
|
|
2250
|
-
case "evaluate": {
|
|
2251
|
-
if (!tabId) throw new Error("No active tab");
|
|
2252
|
-
const timeout = params.timeout || 3e4;
|
|
2253
|
-
const evalCode = `
|
|
2254
|
-
return Promise.resolve(${wrapEvaluable(pickEvaluable(params))}).then(function(__v){
|
|
2255
|
-
if (__v === undefined || __v === null) return null;
|
|
2256
|
-
try { return JSON.parse(JSON.stringify(__v)); } catch(e) { return String(__v); }
|
|
2257
|
-
}, function(__e){
|
|
2258
|
-
return { __hanzo_error: (__e && __e.message) || String(__e) };
|
|
2259
|
-
});
|
|
2260
|
-
`;
|
|
2261
|
-
let value;
|
|
2262
|
-
let evalError = null;
|
|
2263
|
-
try {
|
|
2264
|
-
value = await this.runInPage(tabId, evalCode, timeout);
|
|
2265
|
-
} catch (e) {
|
|
2266
|
-
evalError = e?.message || String(e);
|
|
2267
|
-
value = null;
|
|
2268
|
-
}
|
|
2269
|
-
const safeValue = value === void 0 ? null : value;
|
|
2270
|
-
const cdpType = safeValue === null ? "object" : Array.isArray(safeValue) ? "object" : typeof safeValue;
|
|
2271
|
-
const out = {
|
|
2272
|
-
result: { type: cdpType, value: safeValue },
|
|
2273
|
-
value: safeValue
|
|
2274
|
-
};
|
|
2275
|
-
if (evalError !== null) {
|
|
2276
|
-
out.error = evalError;
|
|
2277
|
-
out.result = {
|
|
2278
|
-
type: "object",
|
|
2279
|
-
value: null,
|
|
2280
|
-
subtype: "error",
|
|
2281
|
-
description: evalError
|
|
2282
|
-
};
|
|
2283
|
-
out.exceptionDetails = { text: evalError };
|
|
2284
|
-
}
|
|
2285
|
-
return out;
|
|
2286
|
-
}
|
|
2287
|
-
// =====================================================================
|
|
2288
|
-
// Screenshots
|
|
2289
|
-
//
|
|
2290
|
-
// Firefox is strict about the captureVisibleTab signature: it wants
|
|
2291
|
-
// `(windowId, options)`. The Chrome MV3 polyfill accepted just
|
|
2292
|
-
// `(options)` so a lot of cross-browser code passes opts as the first
|
|
2293
|
-
// arg, which Firefox interprets as a windowId integer and silently
|
|
2294
|
-
// returns the empty string. Resolve the *target* tab's windowId
|
|
2295
|
-
// explicitly so the screenshot is always of the right window.
|
|
2296
|
-
// =====================================================================
|
|
2297
|
-
case "Page.captureScreenshot":
|
|
2298
|
-
case "hanzo.screenshot": {
|
|
2299
|
-
const format = params.format || "jpeg";
|
|
2300
|
-
const quality = params.quality || (format === "jpeg" ? 80 : void 0);
|
|
2301
|
-
const opts = { format };
|
|
2302
|
-
if (quality !== void 0) opts.quality = quality;
|
|
2303
|
-
let activatedFor = null;
|
|
2304
|
-
let prevActiveId;
|
|
2305
|
-
if (tab && !tab.active) {
|
|
2306
|
-
const [prev] = await browser.tabs.query({ active: true, windowId: tab.windowId });
|
|
2307
|
-
prevActiveId = prev?.id;
|
|
2308
|
-
await browser.tabs.update(tab.id, { active: true });
|
|
2309
|
-
activatedFor = tab.id ?? null;
|
|
2310
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
2311
|
-
}
|
|
2312
|
-
let dataUrl;
|
|
2313
|
-
try {
|
|
2314
|
-
dataUrl = await browser.tabs.captureVisibleTab(tab?.windowId ?? null, opts);
|
|
2315
|
-
} finally {
|
|
2316
|
-
if (activatedFor !== null && prevActiveId) {
|
|
2317
|
-
await browser.tabs.update(prevActiveId, { active: true }).catch(() => {
|
|
2318
|
-
});
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
if (!dataUrl) throw new Error("captureVisibleTab returned empty result");
|
|
2322
|
-
const base64 = dataUrl.replace(/^data:image\/\w+;base64,/, "");
|
|
2323
|
-
return { data: base64, format, size: base64.length };
|
|
2324
|
-
}
|
|
2325
|
-
// =====================================================================
|
|
2326
|
-
// Accessibility snapshot
|
|
2327
|
-
// =====================================================================
|
|
2328
|
-
case "Accessibility.getFullAXTree": {
|
|
2329
|
-
if (!tabId) throw new Error("No active tab");
|
|
2330
|
-
const tree = await this.runInPage(tabId, `
|
|
2331
|
-
function buildTree(el, depth) {
|
|
2332
|
-
if (depth > 5 || !el) return null;
|
|
2333
|
-
var role = el.getAttribute && el.getAttribute('role') || el.tagName?.toLowerCase() || '';
|
|
2334
|
-
var name = el.getAttribute && (el.getAttribute('aria-label') || el.getAttribute('alt') || el.getAttribute('title') || el.textContent?.trim()?.slice(0, 80)) || '';
|
|
2335
|
-
var node = { role: role, name: name };
|
|
2336
|
-
if (el.children && el.children.length > 0 && depth < 5) {
|
|
2337
|
-
node.children = [];
|
|
2338
|
-
for (var i = 0; i < Math.min(el.children.length, 50); i++) {
|
|
2339
|
-
var child = buildTree(el.children[i], depth + 1);
|
|
2340
|
-
if (child) node.children.push(child);
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
return node;
|
|
2344
|
-
}
|
|
2345
|
-
return buildTree(document.body, 0);
|
|
2346
|
-
`);
|
|
2347
|
-
return { tree };
|
|
2348
|
-
}
|
|
2349
|
-
// =====================================================================
|
|
2350
|
-
// Click / Interaction
|
|
2351
|
-
// =====================================================================
|
|
2352
|
-
case "hanzo.click": {
|
|
2353
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2354
|
-
this.notifyOverlay(tabId, "ai.control.highlight", { selector: params.selector });
|
|
2355
|
-
this.notifyOverlay(tabId, "ai.control.status", { text: `Clicking ${params.selector}` });
|
|
2356
|
-
const result = await this.runFunc(tabId, (selector) => {
|
|
2357
|
-
const el = document.querySelector(selector);
|
|
2358
|
-
if (!el) return { clicked: false, reason: "not-found" };
|
|
2359
|
-
try {
|
|
2360
|
-
el.scrollIntoView({ block: "center", behavior: "instant" });
|
|
2361
|
-
} catch (e) {
|
|
2362
|
-
}
|
|
2363
|
-
const r = el.getBoundingClientRect();
|
|
2364
|
-
const x = r.left + r.width / 2;
|
|
2365
|
-
const y = r.top + r.height / 2;
|
|
2366
|
-
const init = {
|
|
2367
|
-
bubbles: true,
|
|
2368
|
-
cancelable: true,
|
|
2369
|
-
composed: true,
|
|
2370
|
-
view: window,
|
|
2371
|
-
button: 0,
|
|
2372
|
-
buttons: 1,
|
|
2373
|
-
clientX: x,
|
|
2374
|
-
clientY: y,
|
|
2375
|
-
screenX: x,
|
|
2376
|
-
screenY: y,
|
|
2377
|
-
detail: 1
|
|
2378
|
-
};
|
|
2379
|
-
try {
|
|
2380
|
-
el.dispatchEvent(new MouseEvent("mouseover", init));
|
|
2381
|
-
el.dispatchEvent(new MouseEvent("mouseenter", init));
|
|
2382
|
-
el.dispatchEvent(new MouseEvent("mousemove", init));
|
|
2383
|
-
el.dispatchEvent(new MouseEvent("mousedown", init));
|
|
2384
|
-
try {
|
|
2385
|
-
el.focus({ preventScroll: true });
|
|
2386
|
-
} catch (e) {
|
|
2387
|
-
}
|
|
2388
|
-
el.dispatchEvent(new MouseEvent("mouseup", { ...init, buttons: 0 }));
|
|
2389
|
-
el.dispatchEvent(new MouseEvent("click", { ...init, buttons: 0 }));
|
|
2390
|
-
return { clicked: true, tag: el.tagName, x, y, isTrusted: false };
|
|
2391
|
-
} catch (e) {
|
|
2392
|
-
try {
|
|
2393
|
-
el.click();
|
|
2394
|
-
return { clicked: true, fallback: "el.click()", isTrusted: false };
|
|
2395
|
-
} catch (e2) {
|
|
2396
|
-
return { clicked: false, error: String(e2) };
|
|
2397
|
-
}
|
|
2398
|
-
}
|
|
2399
|
-
}, [params.selector]);
|
|
2400
|
-
return { success: !!(result && result.clicked), detail: result };
|
|
2401
|
-
}
|
|
2402
|
-
case "hanzo.dblclick": {
|
|
2403
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2404
|
-
const sel = this.escapeSelector(params.selector);
|
|
2405
|
-
this.notifyOverlay(tabId, "ai.control.highlight", { selector: params.selector });
|
|
2406
|
-
await this.runInPage(tabId, `
|
|
2407
|
-
var el = document.querySelector('${sel}');
|
|
2408
|
-
if (el) {
|
|
2409
|
-
el.dispatchEvent(new MouseEvent('dblclick', {bubbles: true, cancelable: true}));
|
|
2410
|
-
}
|
|
2411
|
-
`);
|
|
2412
|
-
return { success: true };
|
|
2413
|
-
}
|
|
2414
|
-
case "hanzo.hover": {
|
|
2415
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2416
|
-
const sel = this.escapeSelector(params.selector);
|
|
2417
|
-
this.notifyOverlay(tabId, "ai.control.highlight", { selector: params.selector });
|
|
2418
|
-
await this.runInPage(tabId, `
|
|
2419
|
-
var el = document.querySelector('${sel}');
|
|
2420
|
-
if (el) {
|
|
2421
|
-
el.dispatchEvent(new MouseEvent('mouseenter', {bubbles: true}));
|
|
2422
|
-
el.dispatchEvent(new MouseEvent('mouseover', {bubbles: true}));
|
|
2423
|
-
el.dispatchEvent(new MouseEvent('mousemove', {bubbles: true}));
|
|
2424
|
-
}
|
|
2425
|
-
`);
|
|
2426
|
-
return { success: true };
|
|
2427
|
-
}
|
|
2428
|
-
// =====================================================================
|
|
2429
|
-
// Form input
|
|
2430
|
-
// =====================================================================
|
|
2431
|
-
case "hanzo.fill": {
|
|
2432
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2433
|
-
this.notifyOverlay(tabId, "ai.control.highlight", { selector: params.selector });
|
|
2434
|
-
this.notifyOverlay(tabId, "ai.control.status", { text: `Filling ${params.selector}` });
|
|
2435
|
-
const ok = await this.runFunc(tabId, (selector, value) => {
|
|
2436
|
-
const el = document.querySelector(selector);
|
|
2437
|
-
if (!el) return false;
|
|
2438
|
-
try {
|
|
2439
|
-
el.scrollIntoView({ block: "center", behavior: "instant" });
|
|
2440
|
-
} catch (e) {
|
|
2441
|
-
}
|
|
2442
|
-
try {
|
|
2443
|
-
el.focus();
|
|
2444
|
-
} catch (e) {
|
|
2445
|
-
}
|
|
2446
|
-
const tag = el.tagName;
|
|
2447
|
-
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") {
|
|
2448
|
-
let setter = null;
|
|
2449
|
-
try {
|
|
2450
|
-
if (tag === "TEXTAREA") {
|
|
2451
|
-
setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value")?.set;
|
|
2452
|
-
} else if (tag === "INPUT") {
|
|
2453
|
-
setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
|
|
2454
|
-
} else if (tag === "SELECT") {
|
|
2455
|
-
setter = Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype, "value")?.set;
|
|
2456
|
-
}
|
|
2457
|
-
} catch (e) {
|
|
2458
|
-
setter = null;
|
|
2459
|
-
}
|
|
2460
|
-
try {
|
|
2461
|
-
if (setter) setter.call(el, value);
|
|
2462
|
-
else el.value = value;
|
|
2463
|
-
} catch (e) {
|
|
2464
|
-
el.value = value;
|
|
2465
|
-
}
|
|
2466
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2467
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
2468
|
-
return true;
|
|
2469
|
-
}
|
|
2470
|
-
if (el.isContentEditable) {
|
|
2471
|
-
el.textContent = value;
|
|
2472
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2473
|
-
return true;
|
|
2474
|
-
}
|
|
2475
|
-
return false;
|
|
2476
|
-
}, [params.selector, params.value || ""]);
|
|
2477
|
-
return { success: !!ok };
|
|
2478
|
-
}
|
|
2479
|
-
case "hanzo.type": {
|
|
2480
|
-
if (!tabId) throw new Error("No active tab");
|
|
2481
|
-
const text = this.escapeValue(params.text || "");
|
|
2482
|
-
const typeSel = params.selector ? this.escapeSelector(params.selector) : null;
|
|
2483
|
-
await this.runInPage(tabId, `
|
|
2484
|
-
var el = ${typeSel ? `document.querySelector('${typeSel}')` : "document.activeElement"};
|
|
2485
|
-
if (el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable)) {
|
|
2486
|
-
el.focus();
|
|
2487
|
-
if (el.isContentEditable) {
|
|
2488
|
-
document.execCommand('insertText', false, '${text}');
|
|
2489
|
-
} else {
|
|
2490
|
-
el.value += '${text}';
|
|
2491
|
-
el.dispatchEvent(new Event('input', {bubbles: true}));
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
`);
|
|
2495
|
-
return { success: true };
|
|
2496
|
-
}
|
|
2497
|
-
case "hanzo.clear": {
|
|
2498
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2499
|
-
const ok = await this.runFunc(tabId, (selector) => {
|
|
2500
|
-
const el = document.querySelector(selector);
|
|
2501
|
-
if (!el) return false;
|
|
2502
|
-
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set || Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value")?.set;
|
|
2503
|
-
if (setter) setter.call(el, "");
|
|
2504
|
-
else el.value = "";
|
|
2505
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2506
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
2507
|
-
return true;
|
|
2508
|
-
}, [params.selector]);
|
|
2509
|
-
return { success: !!ok };
|
|
2510
|
-
}
|
|
2511
|
-
case "hanzo.select": {
|
|
2512
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2513
|
-
const sel = this.escapeSelector(params.selector);
|
|
2514
|
-
const optVal = this.escapeValue(params.value || "");
|
|
2515
|
-
await this.runInPage(tabId, `
|
|
2516
|
-
var el = document.querySelector('${sel}');
|
|
2517
|
-
if (el && el.tagName === 'SELECT') {
|
|
2518
|
-
el.value = '${optVal}';
|
|
2519
|
-
el.dispatchEvent(new Event('change', {bubbles: true}));
|
|
2520
|
-
}
|
|
2521
|
-
`);
|
|
2522
|
-
return { success: true };
|
|
2523
|
-
}
|
|
2524
|
-
case "hanzo.check": {
|
|
2525
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2526
|
-
const ok = await this.runFunc(tabId, (selector) => {
|
|
2527
|
-
const el = document.querySelector(selector);
|
|
2528
|
-
if (!el) return false;
|
|
2529
|
-
try {
|
|
2530
|
-
el.scrollIntoView({ block: "center", behavior: "instant" });
|
|
2531
|
-
} catch (e) {
|
|
2532
|
-
}
|
|
2533
|
-
if (!el.checked) el.click();
|
|
2534
|
-
return true;
|
|
2535
|
-
}, [params.selector]);
|
|
2536
|
-
return { success: !!ok };
|
|
2537
|
-
}
|
|
2538
|
-
case "hanzo.uncheck": {
|
|
2539
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2540
|
-
const ok = await this.runFunc(tabId, (selector) => {
|
|
2541
|
-
const el = document.querySelector(selector);
|
|
2542
|
-
if (!el) return false;
|
|
2543
|
-
try {
|
|
2544
|
-
el.scrollIntoView({ block: "center", behavior: "instant" });
|
|
2545
|
-
} catch (e) {
|
|
2546
|
-
}
|
|
2547
|
-
if (el.checked) el.click();
|
|
2548
|
-
return true;
|
|
2549
|
-
}, [params.selector]);
|
|
2550
|
-
return { success: !!ok };
|
|
2551
|
-
}
|
|
2552
|
-
case "Input.dispatchKeyEvent": {
|
|
2553
|
-
if (!tabId) throw new Error("No active tab");
|
|
2554
|
-
const key = params.key || "";
|
|
2555
|
-
const keyType = params.type || "keyDown";
|
|
2556
|
-
await this.runInPage(tabId, `
|
|
2557
|
-
var el = document.activeElement || document.body;
|
|
2558
|
-
el.dispatchEvent(new KeyboardEvent('${keyType}', {key: '${this.escapeValue(key)}', bubbles: true}));
|
|
2559
|
-
if ('${keyType}' === 'keyDown') {
|
|
2560
|
-
el.dispatchEvent(new KeyboardEvent('keypress', {key: '${this.escapeValue(key)}', bubbles: true}));
|
|
2561
|
-
el.dispatchEvent(new KeyboardEvent('keyup', {key: '${this.escapeValue(key)}', bubbles: true}));
|
|
2562
|
-
}
|
|
2563
|
-
`);
|
|
2564
|
-
return { success: true };
|
|
2565
|
-
}
|
|
2566
|
-
// =====================================================================
|
|
2567
|
-
// DOM Read
|
|
2568
|
-
// =====================================================================
|
|
2569
|
-
case "hanzo.getText": {
|
|
2570
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2571
|
-
const text = await this.runFunc(tabId, (selector) => {
|
|
2572
|
-
const el = document.querySelector(selector);
|
|
2573
|
-
return el ? el.textContent || "" : null;
|
|
2574
|
-
}, [params.selector]);
|
|
2575
|
-
return { result: { value: text } };
|
|
2576
|
-
}
|
|
2577
|
-
case "hanzo.getHTML": {
|
|
2578
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2579
|
-
const outer = params.outer !== false;
|
|
2580
|
-
const html = await this.runFunc(tabId, (selector, useOuter) => {
|
|
2581
|
-
const el = document.querySelector(selector);
|
|
2582
|
-
if (!el) return null;
|
|
2583
|
-
return useOuter ? el.outerHTML : el.innerHTML;
|
|
2584
|
-
}, [params.selector, outer]);
|
|
2585
|
-
return { result: { value: html } };
|
|
2586
|
-
}
|
|
2587
|
-
case "hanzo.getAttribute": {
|
|
2588
|
-
if (!tabId || !params.selector || !params.attr) throw new Error("Missing params");
|
|
2589
|
-
const sel = this.escapeSelector(params.selector);
|
|
2590
|
-
const attr = this.escapeValue(params.attr);
|
|
2591
|
-
const val = await this.runInPage(tabId, `
|
|
2592
|
-
var el = document.querySelector('${sel}');
|
|
2593
|
-
return el ? el.getAttribute('${attr}') : null;
|
|
2594
|
-
`);
|
|
2595
|
-
return { result: { value: val } };
|
|
2596
|
-
}
|
|
2597
|
-
case "hanzo.getElementInfo": {
|
|
2598
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2599
|
-
const sel = this.escapeSelector(params.selector);
|
|
2600
|
-
const info = await this.runInPage(tabId, `
|
|
2601
|
-
var el = document.querySelector('${sel}');
|
|
2602
|
-
if (!el) return null;
|
|
2603
|
-
var rect = el.getBoundingClientRect();
|
|
2604
|
-
return {
|
|
2605
|
-
tagName: el.tagName, id: el.id, className: el.className,
|
|
2606
|
-
textContent: (el.textContent || '').trim().slice(0, 200),
|
|
2607
|
-
value: el.value, type: el.type, href: el.href, src: el.src,
|
|
2608
|
-
checked: el.checked, disabled: el.disabled,
|
|
2609
|
-
visible: !!(rect.width && rect.height),
|
|
2610
|
-
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
|
|
2611
|
-
};
|
|
2612
|
-
`);
|
|
2613
|
-
return { result: { value: info } };
|
|
2614
|
-
}
|
|
2615
|
-
case "hanzo.getPageInfo": {
|
|
2616
|
-
if (!tabId) throw new Error("No active tab");
|
|
2617
|
-
const pageInfo = await this.runInPage(tabId, `
|
|
2618
|
-
return {
|
|
2619
|
-
title: document.title,
|
|
2620
|
-
url: location.href,
|
|
2621
|
-
readyState: document.readyState,
|
|
2622
|
-
contentType: document.contentType,
|
|
2623
|
-
characterSet: document.characterSet,
|
|
2624
|
-
documentElement: { scrollWidth: document.documentElement.scrollWidth, scrollHeight: document.documentElement.scrollHeight },
|
|
2625
|
-
body: { scrollWidth: document.body?.scrollWidth, scrollHeight: document.body?.scrollHeight },
|
|
2626
|
-
viewport: { width: window.innerWidth, height: window.innerHeight }
|
|
2627
|
-
};
|
|
2628
|
-
`);
|
|
2629
|
-
return { result: { value: pageInfo } };
|
|
2630
|
-
}
|
|
2631
|
-
case "hanzo.querySelectorAll": {
|
|
2632
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2633
|
-
const elements = await this.runFunc(tabId, (selector, limit) => {
|
|
2634
|
-
const els = document.querySelectorAll(selector);
|
|
2635
|
-
const out = [];
|
|
2636
|
-
const n = Math.min(els.length, limit);
|
|
2637
|
-
for (let i = 0; i < n; i++) {
|
|
2638
|
-
const el = els[i];
|
|
2639
|
-
const rect = el.getBoundingClientRect();
|
|
2640
|
-
out.push({
|
|
2641
|
-
index: i,
|
|
2642
|
-
tagName: el.tagName,
|
|
2643
|
-
id: el.id || "",
|
|
2644
|
-
className: typeof el.className === "string" ? el.className : "",
|
|
2645
|
-
name: el.getAttribute("name") || "",
|
|
2646
|
-
type: el.getAttribute("type") || "",
|
|
2647
|
-
role: el.getAttribute("role") || "",
|
|
2648
|
-
ariaLabel: el.getAttribute("aria-label") || "",
|
|
2649
|
-
value: el.value || "",
|
|
2650
|
-
href: el.href || "",
|
|
2651
|
-
textContent: (el.textContent || "").trim().slice(0, 200),
|
|
2652
|
-
visible: !!(rect.width && rect.height)
|
|
2653
|
-
});
|
|
2654
|
-
}
|
|
2655
|
-
return out;
|
|
2656
|
-
}, [params.selector, params.limit || 100]);
|
|
2657
|
-
return { elements: elements || [] };
|
|
2658
|
-
}
|
|
2659
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2660
|
-
// CDP-shaped decomplected surface (1.9.16+). See dist/API.md for the
|
|
2661
|
-
// canonical reference. Three orthogonal layers:
|
|
2662
|
-
// 1) Wire transport (JSON over WS/HTTP)
|
|
2663
|
-
// 2) CDP-shaped canonical primitives (Page.* DOM.* Input.* ...)
|
|
2664
|
-
// 3) hanzo.* ergonomic aliases that compose canonical primitives
|
|
2665
|
-
// Every DOM-touching method goes through runFunc → executeScript with
|
|
2666
|
-
// a direct function reference. NEVER Function() / eval(). That is what
|
|
2667
|
-
// makes them CSP-safe on .gov, github.com, and any strict-CSP SPA.
|
|
2668
|
-
// ─────────────────────────────────────────────────────────────────
|
|
2669
|
-
case "Input.dispatchMouseEvent": {
|
|
2670
|
-
if (!tabId) throw new Error("No active tab");
|
|
2671
|
-
const result = await this.runFunc(tabId, (p) => {
|
|
2672
|
-
const { type, x, y, button = 0, clickCount = 1, modifiers = 0 } = p;
|
|
2673
|
-
const target = document.elementFromPoint(x, y);
|
|
2674
|
-
if (!target) return { delivered: false, reason: "no element at point" };
|
|
2675
|
-
const mods = {
|
|
2676
|
-
altKey: !!(modifiers & 1),
|
|
2677
|
-
ctrlKey: !!(modifiers & 2),
|
|
2678
|
-
metaKey: !!(modifiers & 4),
|
|
2679
|
-
shiftKey: !!(modifiers & 8)
|
|
2680
|
-
};
|
|
2681
|
-
const init = {
|
|
2682
|
-
bubbles: true,
|
|
2683
|
-
cancelable: true,
|
|
2684
|
-
view: window,
|
|
2685
|
-
button,
|
|
2686
|
-
buttons: type === "mousePressed" ? 1 : 0,
|
|
2687
|
-
clientX: x,
|
|
2688
|
-
clientY: y,
|
|
2689
|
-
screenX: x,
|
|
2690
|
-
screenY: y,
|
|
2691
|
-
detail: clickCount,
|
|
2692
|
-
...mods
|
|
2693
|
-
};
|
|
2694
|
-
const map = { mouseMoved: "mousemove", mousePressed: "mousedown", mouseReleased: "mouseup" };
|
|
2695
|
-
target.dispatchEvent(new MouseEvent(map[type] || type.toLowerCase(), init));
|
|
2696
|
-
if (type === "mousePressed" && clickCount >= 1) {
|
|
2697
|
-
target.dispatchEvent(new MouseEvent("click", init));
|
|
2698
|
-
}
|
|
2699
|
-
return { delivered: true, target: target.tagName, x, y };
|
|
2700
|
-
}, [params]);
|
|
2701
|
-
return result;
|
|
2702
|
-
}
|
|
2703
|
-
case "Input.scrollWheel": {
|
|
2704
|
-
if (!tabId) throw new Error("No active tab");
|
|
2705
|
-
const result = await this.runFunc(tabId, (p) => {
|
|
2706
|
-
const { x = 0, y = 0, deltaX = 0, deltaY = 0 } = p;
|
|
2707
|
-
const target = x || y ? document.elementFromPoint(x, y) : document.scrollingElement || document.body;
|
|
2708
|
-
if (!target) return { delivered: false };
|
|
2709
|
-
target.dispatchEvent(new WheelEvent("wheel", {
|
|
2710
|
-
bubbles: true,
|
|
2711
|
-
cancelable: true,
|
|
2712
|
-
deltaX,
|
|
2713
|
-
deltaY,
|
|
2714
|
-
clientX: x,
|
|
2715
|
-
clientY: y
|
|
2716
|
-
}));
|
|
2717
|
-
if (target === document.scrollingElement || target === document.body) {
|
|
2718
|
-
window.scrollBy({ left: deltaX, top: deltaY, behavior: "instant" });
|
|
2719
|
-
}
|
|
2720
|
-
return { delivered: true };
|
|
2721
|
-
}, [params]);
|
|
2722
|
-
return result;
|
|
2723
|
-
}
|
|
2724
|
-
case "Page.printToPDF": {
|
|
2725
|
-
if (!tabId) throw new Error("No active tab");
|
|
2726
|
-
await this.runFunc(tabId, () => {
|
|
2727
|
-
window.print();
|
|
2728
|
-
return true;
|
|
2729
|
-
}, []);
|
|
2730
|
-
return {
|
|
2731
|
-
opened: true,
|
|
2732
|
-
note: "Firefox extension cannot programmatically save PDF. For headless PDF export use Page.captureScreenshot with fullPage=true and convert PNG\u2192PDF on the client side."
|
|
2733
|
-
};
|
|
2734
|
-
}
|
|
2735
|
-
case "DOM.querySelector": {
|
|
2736
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2737
|
-
const element = await this.runFunc(tabId, (selector) => {
|
|
2738
|
-
const el = document.querySelector(selector);
|
|
2739
|
-
if (!el) return null;
|
|
2740
|
-
const rect = el.getBoundingClientRect();
|
|
2741
|
-
return {
|
|
2742
|
-
tagName: el.tagName,
|
|
2743
|
-
id: el.id || "",
|
|
2744
|
-
className: typeof el.className === "string" ? el.className : "",
|
|
2745
|
-
textContent: (el.textContent || "").trim().slice(0, 200),
|
|
2746
|
-
value: el.value || "",
|
|
2747
|
-
visible: !!(rect.width && rect.height),
|
|
2748
|
-
rect: { x: rect.x, y: rect.y, w: rect.width, h: rect.height }
|
|
2749
|
-
};
|
|
2750
|
-
}, [params.selector]);
|
|
2751
|
-
return { element };
|
|
2752
|
-
}
|
|
2753
|
-
case "DOM.scrollIntoView": {
|
|
2754
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2755
|
-
const ok = await this.runFunc(tabId, (selector) => {
|
|
2756
|
-
const el = document.querySelector(selector);
|
|
2757
|
-
if (!el) return false;
|
|
2758
|
-
el.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
2759
|
-
return true;
|
|
2760
|
-
}, [params.selector]);
|
|
2761
|
-
return { success: !!ok };
|
|
2762
|
-
}
|
|
2763
|
-
case "DOM.focus": {
|
|
2764
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
2765
|
-
const ok = await this.runFunc(tabId, (selector) => {
|
|
2766
|
-
const el = document.querySelector(selector);
|
|
2767
|
-
if (!el) return false;
|
|
2768
|
-
el.focus();
|
|
2769
|
-
return true;
|
|
2770
|
-
}, [params.selector]);
|
|
2771
|
-
return { success: !!ok };
|
|
2772
|
-
}
|
|
2773
|
-
case "hanzo.clickByText": {
|
|
2774
|
-
if (!tabId || !params.text) throw new Error("No active tab or text");
|
|
2775
|
-
const result = await this.runFunc(tabId, (text, scope) => {
|
|
2776
|
-
const norm = (s) => (s || "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
2777
|
-
const want = norm(text);
|
|
2778
|
-
const tags = scope === "all" ? "button, a, input[type=button], input[type=submit], input[type=radio], input[type=checkbox], label, [role=button], [role=radio], [role=checkbox], [role=tab], [role=menuitem]" : "button, a, label, [role=button], [role=radio], [role=checkbox], [role=tab], [role=menuitem]";
|
|
2779
|
-
const els = Array.from(document.querySelectorAll(tags));
|
|
2780
|
-
const matches = els.filter((el) => {
|
|
2781
|
-
const t = norm(el.innerText || el.value || el.getAttribute("aria-label") || "");
|
|
2782
|
-
return t === want || t.includes(want);
|
|
2783
|
-
});
|
|
2784
|
-
const exact = matches.find((el) => norm(el.innerText || el.value || el.getAttribute("aria-label") || "") === want) || matches[0];
|
|
2785
|
-
if (!exact) return { clicked: false, candidates: matches.length };
|
|
2786
|
-
try {
|
|
2787
|
-
exact.scrollIntoView({ block: "center", behavior: "instant" });
|
|
2788
|
-
} catch (e) {
|
|
2789
|
-
}
|
|
2790
|
-
exact.click();
|
|
2791
|
-
return { clicked: true, tag: exact.tagName, text: (exact.innerText || exact.value || "").slice(0, 100) };
|
|
2792
|
-
}, [params.text, params.scope || "default"]);
|
|
2793
|
-
return result || { clicked: false };
|
|
2794
|
-
}
|
|
2795
|
-
case "hanzo.fillByLabel": {
|
|
2796
|
-
if (!tabId || !params.label) throw new Error("No active tab or label");
|
|
2797
|
-
const result = await this.runFunc(tabId, (labelText, value) => {
|
|
2798
|
-
const norm = (s) => (s || "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
2799
|
-
const want = norm(labelText);
|
|
2800
|
-
const labels = Array.from(document.querySelectorAll("label"));
|
|
2801
|
-
const lbl = labels.find((l) => norm(l.innerText) === want) || labels.find((l) => norm(l.innerText).includes(want));
|
|
2802
|
-
let input = null;
|
|
2803
|
-
if (lbl) {
|
|
2804
|
-
const forId = lbl.getAttribute("for");
|
|
2805
|
-
if (forId) input = document.getElementById(forId);
|
|
2806
|
-
if (!input) input = lbl.querySelector("input, textarea, select");
|
|
2807
|
-
}
|
|
2808
|
-
if (!input) {
|
|
2809
|
-
const all = Array.from(document.querySelectorAll("input, textarea, select"));
|
|
2810
|
-
input = all.find(
|
|
2811
|
-
(el) => norm(el.getAttribute("aria-label") || "") === want || norm(el.getAttribute("placeholder") || "") === want || norm(el.getAttribute("name") || "") === want || norm(el.getAttribute("id") || "") === want
|
|
2812
|
-
) || null;
|
|
2813
|
-
}
|
|
2814
|
-
if (!input) return { filled: false };
|
|
2815
|
-
try {
|
|
2816
|
-
input.scrollIntoView({ block: "center", behavior: "instant" });
|
|
2817
|
-
} catch (e) {
|
|
2818
|
-
}
|
|
2819
|
-
input.focus();
|
|
2820
|
-
const tag = input.tagName;
|
|
2821
|
-
if (tag === "INPUT" || tag === "TEXTAREA") {
|
|
2822
|
-
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set || Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value")?.set;
|
|
2823
|
-
if (setter) setter.call(input, value);
|
|
2824
|
-
else input.value = value;
|
|
2825
|
-
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2826
|
-
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
2827
|
-
return { filled: true, name: input.name || input.id };
|
|
2828
|
-
}
|
|
2829
|
-
if (tag === "SELECT") {
|
|
2830
|
-
input.value = value;
|
|
2831
|
-
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
2832
|
-
return { filled: true, name: input.name || input.id };
|
|
2833
|
-
}
|
|
2834
|
-
return { filled: false };
|
|
2835
|
-
}, [params.label, params.value || ""]);
|
|
2836
|
-
return result || { filled: false };
|
|
2837
|
-
}
|
|
2838
|
-
case "hanzo.findByText": {
|
|
2839
|
-
if (!tabId || !params.text) throw new Error("Missing text");
|
|
2840
|
-
const result = await this.runFunc(tabId, (text, scope, limit) => {
|
|
2841
|
-
const norm = (s) => (s || "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
2842
|
-
const want = norm(text);
|
|
2843
|
-
const tagSel = scope === "all" ? "*" : "button, a, label, input, select, textarea, [role=button], [role=radio], [role=checkbox], [role=tab], [role=menuitem], h1, h2, h3, h4, h5, h6, p, span, li, td";
|
|
2844
|
-
const els = Array.from(document.querySelectorAll(tagSel));
|
|
2845
|
-
const matches = [];
|
|
2846
|
-
for (let i = 0; i < els.length && matches.length < limit; i++) {
|
|
2847
|
-
const el = els[i];
|
|
2848
|
-
const t = norm(el.innerText || el.value || el.getAttribute("aria-label") || "");
|
|
2849
|
-
if (!t) continue;
|
|
2850
|
-
if (t === want || t.includes(want)) {
|
|
2851
|
-
const rect = el.getBoundingClientRect();
|
|
2852
|
-
matches.push({
|
|
2853
|
-
index: i,
|
|
2854
|
-
tag: el.tagName,
|
|
2855
|
-
id: el.id || "",
|
|
2856
|
-
className: typeof el.className === "string" ? el.className : "",
|
|
2857
|
-
text: t.slice(0, 200),
|
|
2858
|
-
exact: t === want,
|
|
2859
|
-
rect: { x: rect.x, y: rect.y, w: rect.width, h: rect.height },
|
|
2860
|
-
visible: !!(rect.width && rect.height)
|
|
2861
|
-
});
|
|
2862
|
-
}
|
|
2863
|
-
}
|
|
2864
|
-
matches.sort((a, b) => (b.exact ? 1 : 0) - (a.exact ? 1 : 0));
|
|
2865
|
-
return matches;
|
|
2866
|
-
}, [params.text, params.scope || "default", params.limit || 50]);
|
|
2867
|
-
return { matches: result || [] };
|
|
2868
|
-
}
|
|
2869
|
-
case "hanzo.listForm": {
|
|
2870
|
-
if (!tabId) throw new Error("No active tab");
|
|
2871
|
-
const inputs = await this.runFunc(tabId, () => {
|
|
2872
|
-
const all = Array.from(document.querySelectorAll("input, textarea, select, button, label, [role=radio], [role=checkbox], [role=button]"));
|
|
2873
|
-
return all.slice(0, 200).map((el, i) => {
|
|
2874
|
-
const rect = el.getBoundingClientRect();
|
|
2875
|
-
const lblFor = el.id ? document.querySelector(`label[for="${el.id}"]`) : null;
|
|
2876
|
-
return {
|
|
2877
|
-
i,
|
|
2878
|
-
tag: el.tagName,
|
|
2879
|
-
type: el.getAttribute("type") || "",
|
|
2880
|
-
name: el.getAttribute("name") || "",
|
|
2881
|
-
id: el.id || "",
|
|
2882
|
-
role: el.getAttribute("role") || "",
|
|
2883
|
-
ariaLabel: el.getAttribute("aria-label") || "",
|
|
2884
|
-
placeholder: el.getAttribute("placeholder") || "",
|
|
2885
|
-
value: el.value || "",
|
|
2886
|
-
checked: !!el.checked,
|
|
2887
|
-
labelText: (lblFor?.innerText || "").trim().slice(0, 200),
|
|
2888
|
-
innerText: (el.innerText || "").trim().slice(0, 200),
|
|
2889
|
-
visible: !!(rect.width && rect.height)
|
|
2890
|
-
};
|
|
2891
|
-
});
|
|
2892
|
-
}, []);
|
|
2893
|
-
return { inputs: inputs || [] };
|
|
2894
|
-
}
|
|
2895
|
-
case "hanzo.press": {
|
|
2896
|
-
if (!tabId || !params.key) throw new Error("Missing key");
|
|
2897
|
-
const result = await this.runFunc(tabId, (key, modifiers) => {
|
|
2898
|
-
const el = document.activeElement || document.body;
|
|
2899
|
-
const mods = {
|
|
2900
|
-
altKey: !!modifiers?.alt,
|
|
2901
|
-
ctrlKey: !!modifiers?.ctrl,
|
|
2902
|
-
metaKey: !!modifiers?.meta,
|
|
2903
|
-
shiftKey: !!modifiers?.shift
|
|
2904
|
-
};
|
|
2905
|
-
const keyCodeMap = {
|
|
2906
|
-
Enter: 13,
|
|
2907
|
-
Tab: 9,
|
|
2908
|
-
Escape: 27,
|
|
2909
|
-
Backspace: 8,
|
|
2910
|
-
Delete: 46,
|
|
2911
|
-
ArrowUp: 38,
|
|
2912
|
-
ArrowDown: 40,
|
|
2913
|
-
ArrowLeft: 37,
|
|
2914
|
-
ArrowRight: 39,
|
|
2915
|
-
Home: 36,
|
|
2916
|
-
End: 35,
|
|
2917
|
-
PageUp: 33,
|
|
2918
|
-
PageDown: 34,
|
|
2919
|
-
" ": 32
|
|
2920
|
-
};
|
|
2921
|
-
const keyCode = keyCodeMap[key] ?? (key.length === 1 ? key.toUpperCase().charCodeAt(0) : 0);
|
|
2922
|
-
const init = { key, code: key, keyCode, which: keyCode, bubbles: true, cancelable: true, ...mods };
|
|
2923
|
-
el.dispatchEvent(new KeyboardEvent("keydown", init));
|
|
2924
|
-
if (key.length === 1) el.dispatchEvent(new KeyboardEvent("keypress", init));
|
|
2925
|
-
el.dispatchEvent(new KeyboardEvent("keyup", init));
|
|
2926
|
-
if (key === "Enter" && el.tagName === "INPUT") {
|
|
2927
|
-
const form = el.form;
|
|
2928
|
-
if (form) {
|
|
2929
|
-
try {
|
|
2930
|
-
form.requestSubmit?.() || form.submit?.();
|
|
2931
|
-
} catch (e) {
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
|
-
}
|
|
2935
|
-
return { delivered: true, target: el.tagName, keyCode };
|
|
2936
|
-
}, [params.key, params.modifiers || {}]);
|
|
2937
|
-
return result;
|
|
2938
|
-
}
|
|
2939
|
-
case "hanzo.scroll": {
|
|
2940
|
-
if (!tabId) throw new Error("No active tab");
|
|
2941
|
-
const result = await this.runFunc(tabId, (target, amount) => {
|
|
2942
|
-
if (target === "top") {
|
|
2943
|
-
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
2944
|
-
return { scrolled: "top" };
|
|
2945
|
-
}
|
|
2946
|
-
if (target === "bottom") {
|
|
2947
|
-
window.scrollTo({ top: document.body?.scrollHeight || 0, behavior: "smooth" });
|
|
2948
|
-
return { scrolled: "bottom" };
|
|
2949
|
-
}
|
|
2950
|
-
if (typeof target === "string" && target) {
|
|
2951
|
-
const el = document.querySelector(target);
|
|
2952
|
-
if (el) {
|
|
2953
|
-
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
2954
|
-
return { scrolled: "into-view", selector: target };
|
|
2955
|
-
}
|
|
2956
|
-
return { scrolled: null, error: "selector not found", selector: target };
|
|
2957
|
-
}
|
|
2958
|
-
if (amount && (amount.x || amount.y)) {
|
|
2959
|
-
window.scrollBy({ left: amount.x || 0, top: amount.y || 0, behavior: "smooth" });
|
|
2960
|
-
return { scrolled: "by", ...amount };
|
|
2961
|
-
}
|
|
2962
|
-
return { scrolled: null, error: "no target or amount" };
|
|
2963
|
-
}, [params.target || params.selector || null, params.amount || null]);
|
|
2964
|
-
return result;
|
|
2965
|
-
}
|
|
2966
|
-
case "hanzo.waitForText": {
|
|
2967
|
-
if (!tabId || !params.text) throw new Error("Missing text");
|
|
2968
|
-
const timeoutMs = params.timeout || 15e3;
|
|
2969
|
-
const interval = params.interval || 250;
|
|
2970
|
-
const start = Date.now();
|
|
2971
|
-
while (Date.now() - start < timeoutMs) {
|
|
2972
|
-
const found = await this.runFunc(tabId, (text) => {
|
|
2973
|
-
const norm = (s) => (s || "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
2974
|
-
return norm(document.body?.innerText || "").includes(norm(text));
|
|
2975
|
-
}, [params.text]);
|
|
2976
|
-
if (found) return { found: true, elapsed: Date.now() - start };
|
|
2977
|
-
await new Promise((r) => setTimeout(r, interval));
|
|
2978
|
-
}
|
|
2979
|
-
return { found: false, timedOut: true, elapsed: Date.now() - start };
|
|
2980
|
-
}
|
|
2981
|
-
case "hanzo.waitForMutation": {
|
|
2982
|
-
if (!tabId || !params.selector) throw new Error("Missing selector");
|
|
2983
|
-
const timeoutMs = params.timeout || 15e3;
|
|
2984
|
-
const result = await this.runFunc(tabId, (selector, timeout) => {
|
|
2985
|
-
return new Promise((resolve) => {
|
|
2986
|
-
const existing = document.querySelector(selector);
|
|
2987
|
-
if (existing) {
|
|
2988
|
-
resolve({ found: true, immediate: true });
|
|
2989
|
-
return;
|
|
2990
|
-
}
|
|
2991
|
-
const observer = new MutationObserver(() => {
|
|
2992
|
-
const el = document.querySelector(selector);
|
|
2993
|
-
if (el) {
|
|
2994
|
-
observer.disconnect();
|
|
2995
|
-
resolve({ found: true, immediate: false });
|
|
2996
|
-
}
|
|
2997
|
-
});
|
|
2998
|
-
observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true });
|
|
2999
|
-
setTimeout(() => {
|
|
3000
|
-
observer.disconnect();
|
|
3001
|
-
resolve({ found: false, timedOut: true });
|
|
3002
|
-
}, timeout);
|
|
3003
|
-
});
|
|
3004
|
-
}, [params.selector, timeoutMs]);
|
|
3005
|
-
return result;
|
|
3006
|
-
}
|
|
3007
|
-
case "hanzo.submitForm": {
|
|
3008
|
-
if (!tabId) throw new Error("No active tab");
|
|
3009
|
-
const result = await this.runFunc(tabId, (hint) => {
|
|
3010
|
-
const norm = (s) => (s || "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
3011
|
-
const intents = hint ? [hint] : ["submit", "continue", "next", "save", "send", "agree", "accept", "ok"];
|
|
3012
|
-
const wantList = intents.map(norm);
|
|
3013
|
-
const candidates = Array.from(document.querySelectorAll(
|
|
3014
|
-
"button, input[type=submit], input[type=button], a[role=button], [role=button], a.btn"
|
|
3015
|
-
));
|
|
3016
|
-
for (const want of wantList) {
|
|
3017
|
-
for (const c of candidates) {
|
|
3018
|
-
const t = norm(c.innerText || c.value || c.getAttribute("aria-label") || "");
|
|
3019
|
-
if (t === want) {
|
|
3020
|
-
try {
|
|
3021
|
-
c.scrollIntoView({ block: "center" });
|
|
3022
|
-
} catch (e) {
|
|
3023
|
-
}
|
|
3024
|
-
c.click();
|
|
3025
|
-
return { submitted: true, intent: want, tag: c.tagName, text: t.slice(0, 100), exact: true };
|
|
3026
|
-
}
|
|
3027
|
-
}
|
|
3028
|
-
for (const c of candidates) {
|
|
3029
|
-
const t = norm(c.innerText || c.value || c.getAttribute("aria-label") || "");
|
|
3030
|
-
if (t.includes(want)) {
|
|
3031
|
-
try {
|
|
3032
|
-
c.scrollIntoView({ block: "center" });
|
|
3033
|
-
} catch (e) {
|
|
3034
|
-
}
|
|
3035
|
-
c.click();
|
|
3036
|
-
return { submitted: true, intent: want, tag: c.tagName, text: t.slice(0, 100), exact: false };
|
|
3037
|
-
}
|
|
3038
|
-
}
|
|
3039
|
-
}
|
|
3040
|
-
return { submitted: false, candidates: candidates.length, intents };
|
|
3041
|
-
}, [params.intent || null]);
|
|
3042
|
-
return result;
|
|
3043
|
-
}
|
|
3044
|
-
case "hanzo.uploadFile": {
|
|
3045
|
-
if (!tabId || !params.selector || !params.content || !params.name) {
|
|
3046
|
-
throw new Error("Need selector, name, content (base64)");
|
|
3047
|
-
}
|
|
3048
|
-
const result = await this.runFunc(tabId, (selector, name, mimeType, contentB64) => {
|
|
3049
|
-
const el = document.querySelector(selector);
|
|
3050
|
-
if (!el) return { uploaded: false, error: "selector not found" };
|
|
3051
|
-
if (el.tagName !== "INPUT" || el.type !== "file") return { uploaded: false, error: "not a file input" };
|
|
3052
|
-
const binStr = atob(contentB64);
|
|
3053
|
-
const bytes = new Uint8Array(binStr.length);
|
|
3054
|
-
for (let i = 0; i < binStr.length; i++) bytes[i] = binStr.charCodeAt(i);
|
|
3055
|
-
const file = new File([bytes], name, { type: mimeType });
|
|
3056
|
-
const dt = new DataTransfer();
|
|
3057
|
-
dt.items.add(file);
|
|
3058
|
-
el.files = dt.files;
|
|
3059
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
3060
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
3061
|
-
return { uploaded: true, name, size: bytes.byteLength };
|
|
3062
|
-
}, [params.selector, params.name, params.mimeType || "application/octet-stream", params.content]);
|
|
3063
|
-
return result;
|
|
3064
|
-
}
|
|
3065
|
-
case "hanzo.dialogAccept": {
|
|
3066
|
-
if (!tabId) throw new Error("No active tab");
|
|
3067
|
-
await this.runFunc(tabId, (acceptText) => {
|
|
3068
|
-
const W = window;
|
|
3069
|
-
W.__hanzo_dialogText = acceptText || "";
|
|
3070
|
-
W.alert = () => true;
|
|
3071
|
-
W.confirm = () => true;
|
|
3072
|
-
W.prompt = () => W.__hanzo_dialogText || "";
|
|
3073
|
-
return true;
|
|
3074
|
-
}, [params.text || ""]);
|
|
3075
|
-
return { armed: true };
|
|
3076
|
-
}
|
|
3077
|
-
case "hanzo.observe": {
|
|
3078
|
-
if (!tabId || !params.name) throw new Error("Missing name");
|
|
3079
|
-
await this.runFunc(tabId, (name, selector, opts) => {
|
|
3080
|
-
const W = window;
|
|
3081
|
-
W.__hanzo_obs = W.__hanzo_obs || {};
|
|
3082
|
-
if (W.__hanzo_obs[name]?.observer) W.__hanzo_obs[name].observer.disconnect();
|
|
3083
|
-
W.__hanzo_obs[name] = { events: [], observer: null };
|
|
3084
|
-
const target = selector ? document.querySelector(selector) : document.documentElement;
|
|
3085
|
-
if (!target) return false;
|
|
3086
|
-
const obs = new MutationObserver((mutations) => {
|
|
3087
|
-
for (const m of mutations) {
|
|
3088
|
-
W.__hanzo_obs[name].events.push({
|
|
3089
|
-
type: m.type,
|
|
3090
|
-
target: m.target?.tagName || "",
|
|
3091
|
-
added: m.addedNodes?.length || 0,
|
|
3092
|
-
removed: m.removedNodes?.length || 0,
|
|
3093
|
-
attr: m.attributeName || null,
|
|
3094
|
-
ts: Date.now()
|
|
3095
|
-
});
|
|
3096
|
-
if (W.__hanzo_obs[name].events.length > 500) W.__hanzo_obs[name].events.shift();
|
|
3097
|
-
}
|
|
3098
|
-
});
|
|
3099
|
-
obs.observe(target, opts || { childList: true, subtree: true, attributes: true });
|
|
3100
|
-
W.__hanzo_obs[name].observer = obs;
|
|
3101
|
-
return true;
|
|
3102
|
-
}, [params.name, params.selector || null, params.options || null]);
|
|
3103
|
-
return { started: true, name: params.name };
|
|
3104
|
-
}
|
|
3105
|
-
case "hanzo.observeRead": {
|
|
3106
|
-
if (!tabId || !params.name) throw new Error("Missing name");
|
|
3107
|
-
const events = await this.runFunc(tabId, (name, clear) => {
|
|
3108
|
-
const W = window;
|
|
3109
|
-
const entry = W.__hanzo_obs?.[name];
|
|
3110
|
-
if (!entry) return [];
|
|
3111
|
-
const out = entry.events.slice();
|
|
3112
|
-
if (clear !== false) entry.events.length = 0;
|
|
3113
|
-
return out;
|
|
3114
|
-
}, [params.name, params.clear !== false]);
|
|
3115
|
-
return { events: events || [] };
|
|
3116
|
-
}
|
|
3117
|
-
case "hanzo.observeStop": {
|
|
3118
|
-
if (!tabId || !params.name) throw new Error("Missing name");
|
|
3119
|
-
await this.runFunc(tabId, (name) => {
|
|
3120
|
-
const W = window;
|
|
3121
|
-
const entry = W.__hanzo_obs?.[name];
|
|
3122
|
-
if (entry?.observer) entry.observer.disconnect();
|
|
3123
|
-
if (W.__hanzo_obs) delete W.__hanzo_obs[name];
|
|
3124
|
-
return true;
|
|
3125
|
-
}, [params.name]);
|
|
3126
|
-
return { stopped: true, name: params.name };
|
|
3127
|
-
}
|
|
3128
|
-
case "hanzo.getComputedStyles": {
|
|
3129
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
3130
|
-
const sel = this.escapeSelector(params.selector);
|
|
3131
|
-
const props = params.properties || ["display", "visibility", "opacity", "position", "color", "backgroundColor", "fontSize"];
|
|
3132
|
-
const propsStr = JSON.stringify(props);
|
|
3133
|
-
const styles = await this.runInPage(tabId, `
|
|
3134
|
-
var el = document.querySelector('${sel}');
|
|
3135
|
-
if (!el) return null;
|
|
3136
|
-
var cs = window.getComputedStyle(el);
|
|
3137
|
-
var props = ${propsStr};
|
|
3138
|
-
var result = {};
|
|
3139
|
-
props.forEach(function(p) { result[p] = cs.getPropertyValue(p) || cs[p]; });
|
|
3140
|
-
return result;
|
|
3141
|
-
`);
|
|
3142
|
-
return { result: { value: styles } };
|
|
3143
|
-
}
|
|
3144
|
-
case "hanzo.getBoundingRects": {
|
|
3145
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
3146
|
-
const sel = this.escapeSelector(params.selector);
|
|
3147
|
-
const rects = await this.runInPage(tabId, `
|
|
3148
|
-
var el = document.querySelector('${sel}');
|
|
3149
|
-
if (!el) return null;
|
|
3150
|
-
var r = el.getBoundingClientRect();
|
|
3151
|
-
return { x: r.x, y: r.y, width: r.width, height: r.height, top: r.top, right: r.right, bottom: r.bottom, left: r.left };
|
|
3152
|
-
`);
|
|
3153
|
-
return { result: { value: rects } };
|
|
3154
|
-
}
|
|
3155
|
-
// =====================================================================
|
|
3156
|
-
// DOM Write
|
|
3157
|
-
// =====================================================================
|
|
3158
|
-
case "hanzo.setText": {
|
|
3159
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
3160
|
-
const sel = this.escapeSelector(params.selector);
|
|
3161
|
-
const txt = this.escapeValue(params.text || "");
|
|
3162
|
-
await this.runInPage(tabId, `
|
|
3163
|
-
var el = document.querySelector('${sel}');
|
|
3164
|
-
if (el) el.textContent = '${txt}';
|
|
3165
|
-
`);
|
|
3166
|
-
return { success: true };
|
|
3167
|
-
}
|
|
3168
|
-
case "hanzo.setHTML": {
|
|
3169
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
3170
|
-
const sel = this.escapeSelector(params.selector);
|
|
3171
|
-
const html = this.escapeValue(params.html || "");
|
|
3172
|
-
const outer = params.outer === true;
|
|
3173
|
-
await this.runInPage(tabId, `
|
|
3174
|
-
var el = document.querySelector('${sel}');
|
|
3175
|
-
if (el) ${outer ? `el.outerHTML = '${html}'` : `el.innerHTML = '${html}'`};
|
|
3176
|
-
`);
|
|
3177
|
-
return { success: true };
|
|
3178
|
-
}
|
|
3179
|
-
case "hanzo.setAttribute": {
|
|
3180
|
-
if (!tabId || !params.selector || !params.attr) throw new Error("Missing params");
|
|
3181
|
-
const sel = this.escapeSelector(params.selector);
|
|
3182
|
-
const attr = this.escapeValue(params.attr);
|
|
3183
|
-
const val = this.escapeValue(params.value || "");
|
|
3184
|
-
await this.runInPage(tabId, `
|
|
3185
|
-
var el = document.querySelector('${sel}');
|
|
3186
|
-
if (el) el.setAttribute('${attr}', '${val}');
|
|
3187
|
-
`);
|
|
3188
|
-
return { success: true };
|
|
3189
|
-
}
|
|
3190
|
-
case "hanzo.removeAttribute": {
|
|
3191
|
-
if (!tabId || !params.selector || !params.attr) throw new Error("Missing params");
|
|
3192
|
-
const sel = this.escapeSelector(params.selector);
|
|
3193
|
-
const attr = this.escapeValue(params.attr);
|
|
3194
|
-
await this.runInPage(tabId, `
|
|
3195
|
-
var el = document.querySelector('${sel}');
|
|
3196
|
-
if (el) el.removeAttribute('${attr}');
|
|
3197
|
-
`);
|
|
3198
|
-
return { success: true };
|
|
3199
|
-
}
|
|
3200
|
-
case "hanzo.setStyle": {
|
|
3201
|
-
if (!tabId || !params.selector || !params.styles) throw new Error("Missing params");
|
|
3202
|
-
const sel = this.escapeSelector(params.selector);
|
|
3203
|
-
const stylesJson = JSON.stringify(params.styles);
|
|
3204
|
-
await this.runInPage(tabId, `
|
|
3205
|
-
var el = document.querySelector('${sel}');
|
|
3206
|
-
if (el) {
|
|
3207
|
-
var styles = ${stylesJson};
|
|
3208
|
-
for (var k in styles) { if (styles.hasOwnProperty(k)) el.style[k] = styles[k]; }
|
|
3209
|
-
}
|
|
3210
|
-
`);
|
|
3211
|
-
return { success: true };
|
|
3212
|
-
}
|
|
3213
|
-
case "hanzo.addClass": {
|
|
3214
|
-
if (!tabId || !params.selector || !params.classNames) throw new Error("Missing params");
|
|
3215
|
-
const sel = this.escapeSelector(params.selector);
|
|
3216
|
-
const classes = Array.isArray(params.classNames) ? params.classNames : [params.classNames];
|
|
3217
|
-
const classesJson = JSON.stringify(classes);
|
|
3218
|
-
await this.runInPage(tabId, `
|
|
3219
|
-
var el = document.querySelector('${sel}');
|
|
3220
|
-
if (el) el.classList.add.apply(el.classList, ${classesJson});
|
|
3221
|
-
`);
|
|
3222
|
-
return { success: true };
|
|
3223
|
-
}
|
|
3224
|
-
case "hanzo.removeClass": {
|
|
3225
|
-
if (!tabId || !params.selector || !params.classNames) throw new Error("Missing params");
|
|
3226
|
-
const sel = this.escapeSelector(params.selector);
|
|
3227
|
-
const classes = Array.isArray(params.classNames) ? params.classNames : [params.classNames];
|
|
3228
|
-
const classesJson = JSON.stringify(classes);
|
|
3229
|
-
await this.runInPage(tabId, `
|
|
3230
|
-
var el = document.querySelector('${sel}');
|
|
3231
|
-
if (el) el.classList.remove.apply(el.classList, ${classesJson});
|
|
3232
|
-
`);
|
|
3233
|
-
return { success: true };
|
|
3234
|
-
}
|
|
3235
|
-
case "hanzo.insertElement": {
|
|
3236
|
-
if (!tabId || !params.selector || !params.html) throw new Error("Missing params");
|
|
3237
|
-
const sel = this.escapeSelector(params.selector);
|
|
3238
|
-
const html = this.escapeValue(params.html);
|
|
3239
|
-
const position = params.position || "beforeend";
|
|
3240
|
-
await this.runInPage(tabId, `
|
|
3241
|
-
var el = document.querySelector('${sel}');
|
|
3242
|
-
if (el) el.insertAdjacentHTML('${position}', '${html}');
|
|
3243
|
-
`);
|
|
3244
|
-
return { success: true };
|
|
3245
|
-
}
|
|
3246
|
-
case "hanzo.removeElement": {
|
|
3247
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
3248
|
-
const sel = this.escapeSelector(params.selector);
|
|
3249
|
-
await this.runInPage(tabId, `
|
|
3250
|
-
var el = document.querySelector('${sel}');
|
|
3251
|
-
if (el) el.remove();
|
|
3252
|
-
`);
|
|
3253
|
-
return { success: true };
|
|
3254
|
-
}
|
|
3255
|
-
// =====================================================================
|
|
3256
|
-
// DOM Observation
|
|
3257
|
-
// =====================================================================
|
|
3258
|
-
case "hanzo.observeMutations": {
|
|
3259
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
3260
|
-
const sel = this.escapeSelector(params.selector);
|
|
3261
|
-
const duration = params.duration || 3e3;
|
|
3262
|
-
const mutations = await this.runInPage(tabId, `
|
|
3263
|
-
return new Promise(function(resolve) {
|
|
3264
|
-
var el = document.querySelector('${sel}') || document.body;
|
|
3265
|
-
var records = [];
|
|
3266
|
-
var obs = new MutationObserver(function(muts) {
|
|
3267
|
-
muts.forEach(function(m) {
|
|
3268
|
-
records.push({
|
|
3269
|
-
type: m.type,
|
|
3270
|
-
target: m.target.tagName + (m.target.id ? '#' + m.target.id : ''),
|
|
3271
|
-
addedNodes: m.addedNodes.length,
|
|
3272
|
-
removedNodes: m.removedNodes.length,
|
|
3273
|
-
attributeName: m.attributeName
|
|
3274
|
-
});
|
|
3275
|
-
});
|
|
3276
|
-
});
|
|
3277
|
-
obs.observe(el, { childList: true, attributes: true, subtree: true, characterData: true });
|
|
3278
|
-
setTimeout(function() { obs.disconnect(); resolve(records.slice(0, 50)); }, ${Math.min(duration, 1e4)});
|
|
3279
|
-
});
|
|
3280
|
-
`, duration + 2e3);
|
|
3281
|
-
return { mutations: mutations || [] };
|
|
3282
|
-
}
|
|
3283
|
-
// =====================================================================
|
|
3284
|
-
// Wait for selector
|
|
3285
|
-
// =====================================================================
|
|
3286
|
-
case "hanzo.waitForSelector": {
|
|
3287
|
-
if (!tabId || !params.selector) throw new Error("No active tab or selector");
|
|
3288
|
-
const sel = this.escapeSelector(params.selector);
|
|
3289
|
-
const waitTimeout = params.timeout || 1e4;
|
|
3290
|
-
const found = await this.runInPage(tabId, `
|
|
3291
|
-
return new Promise(function(resolve) {
|
|
3292
|
-
var el = document.querySelector('${sel}');
|
|
3293
|
-
if (el) { resolve(true); return; }
|
|
3294
|
-
var obs = new MutationObserver(function() {
|
|
3295
|
-
var el = document.querySelector('${sel}');
|
|
3296
|
-
if (el) { obs.disconnect(); resolve(true); }
|
|
3297
|
-
});
|
|
3298
|
-
obs.observe(document.body, { childList: true, subtree: true });
|
|
3299
|
-
setTimeout(function() { obs.disconnect(); resolve(false); }, ${Math.min(waitTimeout, 3e4)});
|
|
3300
|
-
});
|
|
3301
|
-
`, waitTimeout + 2e3);
|
|
3302
|
-
return { found: !!found };
|
|
3303
|
-
}
|
|
3304
|
-
// =====================================================================
|
|
3305
|
-
// Inject script / CSS
|
|
3306
|
-
// =====================================================================
|
|
3307
|
-
case "hanzo.injectScript": {
|
|
3308
|
-
if (!tabId || !params.url) throw new Error("No active tab or url");
|
|
3309
|
-
const scriptUrl = this.escapeValue(params.url);
|
|
3310
|
-
await this.runInPage(tabId, `
|
|
3311
|
-
return new Promise(function(resolve) {
|
|
3312
|
-
var s = document.createElement('script');
|
|
3313
|
-
s.src = '${scriptUrl}';
|
|
3314
|
-
s.onload = function() { resolve(true); };
|
|
3315
|
-
s.onerror = function() { resolve(false); };
|
|
3316
|
-
document.head.appendChild(s);
|
|
3317
|
-
});
|
|
3318
|
-
`, 15e3);
|
|
3319
|
-
return { success: true };
|
|
3320
|
-
}
|
|
3321
|
-
case "hanzo.injectCSS": {
|
|
3322
|
-
if (!tabId || !params.css) throw new Error("No active tab or css");
|
|
3323
|
-
const css = this.escapeValue(params.css);
|
|
3324
|
-
await this.runInPage(tabId, `
|
|
3325
|
-
var style = document.createElement('style');
|
|
3326
|
-
style.textContent = '${css}';
|
|
3327
|
-
document.head.appendChild(style);
|
|
3328
|
-
`);
|
|
3329
|
-
return { success: true };
|
|
3330
|
-
}
|
|
3331
|
-
// =====================================================================
|
|
3332
|
-
// Storage / Cookies
|
|
3333
|
-
// =====================================================================
|
|
3334
|
-
case "hanzo.getLocalStorage": {
|
|
3335
|
-
if (!tabId) throw new Error("No active tab");
|
|
3336
|
-
const key = params.key ? this.escapeValue(params.key) : null;
|
|
3337
|
-
const storageVal = await this.runInPage(
|
|
3338
|
-
tabId,
|
|
3339
|
-
key ? `return localStorage.getItem('${key}');` : `
|
|
3340
|
-
var result = {};
|
|
3341
|
-
for (var i = 0; i < localStorage.length; i++) {
|
|
3342
|
-
var k = localStorage.key(i);
|
|
3343
|
-
result[k] = localStorage.getItem(k);
|
|
3344
|
-
}
|
|
3345
|
-
return result;
|
|
3346
|
-
`
|
|
3347
|
-
);
|
|
3348
|
-
return { result: { value: storageVal } };
|
|
3349
|
-
}
|
|
3350
|
-
case "hanzo.setLocalStorage": {
|
|
3351
|
-
if (!tabId || !params.key) throw new Error("No active tab or key");
|
|
3352
|
-
const key = this.escapeValue(params.key);
|
|
3353
|
-
const val = this.escapeValue(params.value || "");
|
|
3354
|
-
await this.runInPage(tabId, `localStorage.setItem('${key}', '${val}');`);
|
|
3355
|
-
return { success: true };
|
|
3356
|
-
}
|
|
3357
|
-
case "hanzo.getCookies": {
|
|
3358
|
-
try {
|
|
3359
|
-
const url = tab?.url || "";
|
|
3360
|
-
const cookies = await browser.cookies.getAll({ url });
|
|
3361
|
-
return { cookies: cookies.map((c) => ({ name: c.name, value: c.value, domain: c.domain, path: c.path, secure: c.secure, httpOnly: c.httpOnly })) };
|
|
3362
|
-
} catch {
|
|
3363
|
-
return { cookies: [], error: "cookies API not available" };
|
|
3364
|
-
}
|
|
3365
|
-
}
|
|
3366
|
-
// =====================================================================
|
|
3367
|
-
// Fetch through page context (uses page cookies/auth)
|
|
3368
|
-
// =====================================================================
|
|
3369
|
-
case "hanzo.fetch": {
|
|
3370
|
-
if (!tabId || !params.url) throw new Error("No active tab or url");
|
|
3371
|
-
const fetchUrl = this.escapeValue(params.url);
|
|
3372
|
-
const fetchOpts = params.options || {};
|
|
3373
|
-
const optsJson = JSON.stringify(fetchOpts);
|
|
3374
|
-
const fetchResult = await this.runInPage(tabId, `
|
|
3375
|
-
return fetch('${fetchUrl}', ${optsJson}).then(function(r) {
|
|
3376
|
-
return r.text().then(function(body) {
|
|
3377
|
-
return { status: r.status, statusText: r.statusText, ok: r.ok, body: body.slice(0, 50000) };
|
|
3378
|
-
});
|
|
3379
|
-
});
|
|
3380
|
-
`, 3e4);
|
|
3381
|
-
return fetchResult || { error: "fetch failed" };
|
|
3382
|
-
}
|
|
3383
|
-
// =====================================================================
|
|
3384
|
-
// Control session
|
|
3385
|
-
// =====================================================================
|
|
3386
|
-
case "hanzo.control.start": {
|
|
3387
|
-
const targetTabId = await this.resolveControlTabId(params.tabId);
|
|
3388
|
-
if (targetTabId) {
|
|
3389
|
-
await this.startControlSession(targetTabId, params.task);
|
|
3390
|
-
return { success: true };
|
|
3391
|
-
}
|
|
3392
|
-
throw new Error("No active tab");
|
|
3393
|
-
}
|
|
3394
|
-
case "hanzo.control.stop": {
|
|
3395
|
-
await this.stopControlSession();
|
|
3396
|
-
return { success: true };
|
|
3397
|
-
}
|
|
3398
|
-
// =====================================================================
|
|
3399
|
-
// Browser info
|
|
3400
|
-
// =====================================================================
|
|
3401
|
-
case "Browser.getVersion": {
|
|
3402
|
-
return {
|
|
3403
|
-
protocolVersion: "1.3",
|
|
3404
|
-
product: "Hanzo Firefox Extension",
|
|
3405
|
-
userAgent: navigator.userAgent
|
|
3406
|
-
};
|
|
3407
|
-
}
|
|
3408
|
-
default:
|
|
3409
|
-
console.warn("[Hanzo] Unknown method:", method);
|
|
3410
|
-
return { error: `Unknown method: ${method}` };
|
|
3411
|
-
}
|
|
3412
|
-
}
|
|
3413
|
-
/**
|
|
3414
|
-
* Handle extension-request messages (monitor, audit, ollama, hub, etc.)
|
|
3415
|
-
* These are routed via sendToExtension() from the CDP bridge.
|
|
3416
|
-
*/
|
|
3417
|
-
async handleExtensionRequest(action, params) {
|
|
3418
|
-
const tabId = params.tabId || await this.getActiveTabId() || void 0;
|
|
3419
|
-
if (action.startsWith("monitor.")) {
|
|
3420
|
-
if (!tabId) return { error: "No active tab for monitoring" };
|
|
3421
|
-
switch (action) {
|
|
3422
|
-
case "monitor.consoleLogs":
|
|
3423
|
-
return this.runInPage(tabId, `
|
|
3424
|
-
return (window.__hanzo_console_logs || []).slice(-50);
|
|
3425
|
-
`).then((logs) => ({ logs: logs || [] })).catch(() => ({ logs: [] }));
|
|
3426
|
-
case "monitor.consoleErrors":
|
|
3427
|
-
return this.runInPage(tabId, `
|
|
3428
|
-
return (window.__hanzo_console_errors || []).slice(-50);
|
|
3429
|
-
`).then((errors) => ({ errors: errors || [] })).catch(() => ({ errors: [] }));
|
|
3430
|
-
case "monitor.networkLogs":
|
|
3431
|
-
case "monitor.networkErrors":
|
|
3432
|
-
case "monitor.networkSuccess":
|
|
3433
|
-
return { logs: [], note: "Network monitoring requires devtools API (not available in MV3 extensions)" };
|
|
3434
|
-
case "monitor.wipeLogs":
|
|
3435
|
-
return this.runInPage(tabId, `
|
|
3436
|
-
window.__hanzo_console_logs = [];
|
|
3437
|
-
window.__hanzo_console_errors = [];
|
|
3438
|
-
`).then(() => ({ success: true })).catch(() => ({ success: false }));
|
|
3439
|
-
case "monitor.start":
|
|
3440
|
-
return this.runInPage(tabId, `
|
|
3441
|
-
if (!window.__hanzo_monitor_active) {
|
|
3442
|
-
window.__hanzo_console_logs = [];
|
|
3443
|
-
window.__hanzo_console_errors = [];
|
|
3444
|
-
var origLog = console.log, origErr = console.error, origWarn = console.warn;
|
|
3445
|
-
console.log = function() { window.__hanzo_console_logs.push({type:'log', args: Array.from(arguments).map(String), ts: Date.now()}); origLog.apply(console, arguments); };
|
|
3446
|
-
console.error = function() { window.__hanzo_console_errors.push({type:'error', args: Array.from(arguments).map(String), ts: Date.now()}); origErr.apply(console, arguments); };
|
|
3447
|
-
console.warn = function() { window.__hanzo_console_logs.push({type:'warn', args: Array.from(arguments).map(String), ts: Date.now()}); origWarn.apply(console, arguments); };
|
|
3448
|
-
window.__hanzo_monitor_active = true;
|
|
3449
|
-
}
|
|
3450
|
-
`).then(() => ({ success: true })).catch(() => ({ success: false }));
|
|
3451
|
-
case "monitor.stop":
|
|
3452
|
-
return { success: true };
|
|
3453
|
-
case "monitor.status":
|
|
3454
|
-
return { active: false };
|
|
3455
|
-
default:
|
|
3456
|
-
return { error: `Unknown monitor action: ${action}` };
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3459
|
-
if (action.startsWith("audit.")) {
|
|
3460
|
-
if (!tabId) return { error: "No active tab for audit" };
|
|
3461
|
-
switch (action) {
|
|
3462
|
-
case "audit.accessibility":
|
|
3463
|
-
return this.runInPage(tabId, `
|
|
3464
|
-
var issues = [];
|
|
3465
|
-
document.querySelectorAll('img').forEach(function(img) { if (!img.alt) issues.push({type:'missing-alt', el:img.tagName, src:img.src?.slice(0,80)}); });
|
|
3466
|
-
document.querySelectorAll('a').forEach(function(a) { if (!a.textContent?.trim() && !a.getAttribute('aria-label')) issues.push({type:'empty-link', href:a.href?.slice(0,80)}); });
|
|
3467
|
-
document.querySelectorAll('input,textarea,select').forEach(function(el) {
|
|
3468
|
-
if (!el.getAttribute('aria-label') && !el.labels?.length && !el.placeholder) issues.push({type:'unlabeled-input', name:el.name, id:el.id});
|
|
3469
|
-
});
|
|
3470
|
-
return { issues: issues.slice(0, 50), total: issues.length };
|
|
3471
|
-
`).then((r) => ({ audit: r })).catch((e) => ({ error: e.message }));
|
|
3472
|
-
case "audit.performance":
|
|
3473
|
-
return this.runInPage(tabId, `
|
|
3474
|
-
var p = performance;
|
|
3475
|
-
var nav = p.getEntriesByType('navigation')[0] || {};
|
|
3476
|
-
return {
|
|
3477
|
-
domContentLoaded: nav.domContentLoadedEventEnd,
|
|
3478
|
-
load: nav.loadEventEnd,
|
|
3479
|
-
resources: p.getEntriesByType('resource').length,
|
|
3480
|
-
domNodes: document.querySelectorAll('*').length
|
|
3481
|
-
};
|
|
3482
|
-
`).then((r) => ({ audit: r })).catch((e) => ({ error: e.message }));
|
|
3483
|
-
case "audit.seo":
|
|
3484
|
-
return this.runInPage(tabId, `
|
|
3485
|
-
return {
|
|
3486
|
-
title: document.title,
|
|
3487
|
-
metaDescription: document.querySelector('meta[name="description"]')?.content || null,
|
|
3488
|
-
h1Count: document.querySelectorAll('h1').length,
|
|
3489
|
-
canonical: document.querySelector('link[rel="canonical"]')?.href || null,
|
|
3490
|
-
ogTitle: document.querySelector('meta[property="og:title"]')?.content || null,
|
|
3491
|
-
ogDescription: document.querySelector('meta[property="og:description"]')?.content || null
|
|
3492
|
-
};
|
|
3493
|
-
`).then((r) => ({ audit: r })).catch((e) => ({ error: e.message }));
|
|
3494
|
-
default:
|
|
3495
|
-
return { error: `Audit type not implemented: ${action}` };
|
|
3496
|
-
}
|
|
3497
|
-
}
|
|
3498
|
-
if (action === "debugger.mode") {
|
|
3499
|
-
return { error: "Debugger mode requires devtools API (not available in MV3)" };
|
|
3500
|
-
}
|
|
3501
|
-
if (action.startsWith("ollama.") || action.startsWith("local.") || action.startsWith("hub.")) {
|
|
3502
|
-
return { error: `${action} not yet implemented in Firefox extension` };
|
|
3503
|
-
}
|
|
3504
|
-
try {
|
|
3505
|
-
return await this.executeMethod(action, params || {});
|
|
3506
|
-
} catch (e) {
|
|
3507
|
-
return { error: e && e.message || String(e) };
|
|
3508
|
-
}
|
|
3509
|
-
}
|
|
3510
|
-
notifyOverlay(tabId, action, data) {
|
|
3511
|
-
browser.tabs.sendMessage(tabId, { action, ...data }).catch(() => {
|
|
3512
|
-
});
|
|
3513
|
-
}
|
|
3514
|
-
async getActiveTabId() {
|
|
3515
|
-
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
|
|
3516
|
-
return tab?.id ?? null;
|
|
3517
|
-
}
|
|
3518
|
-
async resolveControlTabId(tabId) {
|
|
3519
|
-
if (typeof tabId === "number") return tabId;
|
|
3520
|
-
if (controlSession.active && controlSession.tabId !== null) return controlSession.tabId;
|
|
3521
|
-
return this.getActiveTabId();
|
|
3522
|
-
}
|
|
3523
|
-
async startControlSession(tabId, task) {
|
|
3524
|
-
if (controlSession.active && controlSession.tabId !== null && controlSession.tabId !== tabId) {
|
|
3525
|
-
this.notifyOverlay(controlSession.tabId, "ai.control.stop", {});
|
|
3526
|
-
}
|
|
3527
|
-
controlSession.active = true;
|
|
3528
|
-
controlSession.tabId = tabId;
|
|
3529
|
-
controlSession.task = task || "AI is controlling this page";
|
|
3530
|
-
controlSession.startedAt = Date.now();
|
|
3531
|
-
this.notifyOverlay(tabId, "ai.control.start", { task: controlSession.task });
|
|
3532
|
-
}
|
|
3533
|
-
async stopControlSession() {
|
|
3534
|
-
if (controlSession.active && controlSession.tabId !== null) {
|
|
3535
|
-
this.notifyOverlay(controlSession.tabId, "ai.control.stop", {});
|
|
3536
|
-
}
|
|
3537
|
-
controlSession.active = false;
|
|
3538
|
-
controlSession.tabId = null;
|
|
3539
|
-
controlSession.task = null;
|
|
3540
|
-
controlSession.startedAt = null;
|
|
3541
|
-
}
|
|
3542
|
-
escapeSelector(selector) {
|
|
3543
|
-
return selector.replace(/'/g, "\\'");
|
|
3544
|
-
}
|
|
3545
|
-
escapeValue(value) {
|
|
3546
|
-
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
|
|
3547
|
-
}
|
|
3548
|
-
isBridgeConnected() {
|
|
3549
|
-
return this.wsConnection !== null && this.wsConnection.readyState === WebSocket.OPEN;
|
|
3550
|
-
}
|
|
3551
|
-
};
|
|
3552
|
-
var IAM_V1 = `${IAM_API_URL}${IAM_API_PATH}`;
|
|
3553
|
-
var ffAdapter = firefoxAdapter();
|
|
3554
|
-
async function getValidAccessToken2() {
|
|
3555
|
-
return getValidAccessToken(ffAdapter.storage);
|
|
3556
|
-
}
|
|
3557
|
-
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
3558
|
-
(async () => {
|
|
3559
|
-
if (typeof request.action === "string" && request.action.startsWith("zap.")) {
|
|
3560
|
-
if (handleZapMessage(request.action, request, sendResponse, zapMgr, BROWSER_NAME, VERSION)) {
|
|
3561
|
-
return;
|
|
3562
|
-
}
|
|
3563
|
-
}
|
|
3564
|
-
switch (request.action) {
|
|
3565
|
-
// =======================================================================
|
|
3566
|
-
// Auth (Hanzo IAM OAuth2 + PKCE)
|
|
3567
|
-
// =======================================================================
|
|
3568
|
-
case "auth.login":
|
|
3569
|
-
try {
|
|
3570
|
-
const user = await login(ffAdapter);
|
|
3571
|
-
sendResponse({ success: true, user });
|
|
3572
|
-
} catch (e) {
|
|
3573
|
-
sendResponse({ success: false, error: e.message });
|
|
3574
|
-
}
|
|
3575
|
-
break;
|
|
3576
|
-
case "auth.signup":
|
|
3577
|
-
try {
|
|
3578
|
-
await signup(ffAdapter);
|
|
3579
|
-
sendResponse({ success: true });
|
|
3580
|
-
} catch (e) {
|
|
3581
|
-
sendResponse({ success: false, error: e.message });
|
|
3582
|
-
}
|
|
3583
|
-
break;
|
|
3584
|
-
case "auth.logout":
|
|
3585
|
-
await logout(ffAdapter.storage);
|
|
3586
|
-
sendResponse({ success: true });
|
|
3587
|
-
break;
|
|
3588
|
-
case "auth.status": {
|
|
3589
|
-
const status = await getAuthStatus(ffAdapter.storage);
|
|
3590
|
-
sendResponse({
|
|
3591
|
-
success: true,
|
|
3592
|
-
authenticated: status.authenticated,
|
|
3593
|
-
user: status.user
|
|
3594
|
-
});
|
|
3595
|
-
break;
|
|
3596
|
-
}
|
|
3597
|
-
case "auth.getToken": {
|
|
3598
|
-
const r = await browser.storage.local.get([STORAGE_KEYS.accessToken]);
|
|
3599
|
-
sendResponse({ success: !!r[STORAGE_KEYS.accessToken], token: r[STORAGE_KEYS.accessToken] || null });
|
|
3600
|
-
break;
|
|
3601
|
-
}
|
|
3602
|
-
// =======================================================================
|
|
3603
|
-
// RAG (uses shared module — now with ZAP memory support)
|
|
3604
|
-
// =======================================================================
|
|
3605
|
-
case "rag.query": {
|
|
3606
|
-
try {
|
|
3607
|
-
const config = await getRagConfig(browser.storage.local);
|
|
3608
|
-
const topKInput = Number.parseInt(String(request.topK ?? config.topK), 10);
|
|
3609
|
-
const params = {
|
|
3610
|
-
query: String(request.query || "").trim(),
|
|
3611
|
-
topK: Number.isFinite(topKInput) ? Math.max(1, Math.min(topKInput, 20)) : 5,
|
|
3612
|
-
knowledgeBase: String(request.knowledgeBase ?? config.knowledgeBase ?? "").trim(),
|
|
3613
|
-
includeTabContext: request.includeTabContext !== void 0 ? !!request.includeTabContext : config.includeTabContext,
|
|
3614
|
-
useZapMemory: request.useZapMemory !== void 0 ? !!request.useZapMemory : config.useZapMemory,
|
|
3615
|
-
endpoint: String(request.endpoint ?? config.endpoint ?? "").trim(),
|
|
3616
|
-
apiKey: String(request.apiKey ?? config.apiKey ?? "").trim(),
|
|
3617
|
-
mcpId: request.mcpId ? String(request.mcpId) : void 0,
|
|
3618
|
-
pageContext: request.pageContext && typeof request.pageContext === "object" ? {
|
|
3619
|
-
url: request.pageContext.url ? String(request.pageContext.url) : void 0,
|
|
3620
|
-
title: request.pageContext.title ? String(request.pageContext.title) : void 0
|
|
3621
|
-
} : void 0
|
|
3622
|
-
};
|
|
3623
|
-
if (!params.query) {
|
|
3624
|
-
sendResponse({ success: false, error: "Query is required" });
|
|
3625
|
-
break;
|
|
3626
|
-
}
|
|
3627
|
-
let snippets = await queryRagFromZap(params, zapMgr);
|
|
3628
|
-
let source = snippets.length ? "zap-memory" : "none";
|
|
3629
|
-
if (!snippets.length && params.endpoint) {
|
|
3630
|
-
snippets = await queryRagFromEndpoint(params);
|
|
3631
|
-
source = snippets.length ? "rag-endpoint" : source;
|
|
3632
|
-
}
|
|
3633
|
-
sendResponse({
|
|
3634
|
-
success: true,
|
|
3635
|
-
source,
|
|
3636
|
-
snippets: snippets.slice(0, params.topK)
|
|
3637
|
-
});
|
|
3638
|
-
} catch (e) {
|
|
3639
|
-
sendResponse({ success: false, error: e.message });
|
|
3640
|
-
}
|
|
3641
|
-
break;
|
|
3642
|
-
}
|
|
3643
|
-
// =======================================================================
|
|
3644
|
-
// Cloud Chat
|
|
3645
|
-
// =======================================================================
|
|
3646
|
-
case "chat.listModels": {
|
|
3647
|
-
try {
|
|
3648
|
-
const token = await getValidAccessToken2();
|
|
3649
|
-
if (!token) {
|
|
3650
|
-
sendResponse({ success: false, error: "Not authenticated" });
|
|
3651
|
-
break;
|
|
3652
|
-
}
|
|
3653
|
-
const resp = await fetch(`${API_BASE_URL}/v1/models`, {
|
|
3654
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
3655
|
-
signal: AbortSignal.timeout(1e4)
|
|
3656
|
-
});
|
|
3657
|
-
const data = await resp.json();
|
|
3658
|
-
sendResponse({ success: true, models: data.data || data.models || data || [] });
|
|
3659
|
-
} catch (e) {
|
|
3660
|
-
sendResponse({ success: false, error: e.message });
|
|
3661
|
-
}
|
|
3662
|
-
break;
|
|
3663
|
-
}
|
|
3664
|
-
case "chat.complete": {
|
|
3665
|
-
try {
|
|
3666
|
-
const token = await getValidAccessToken2();
|
|
3667
|
-
if (!token) {
|
|
3668
|
-
sendResponse({ success: false, error: "Not authenticated" });
|
|
3669
|
-
break;
|
|
3670
|
-
}
|
|
3671
|
-
const messages = (Array.isArray(request.messages) ? request.messages : []).filter((m) => m && typeof m.content === "string" && typeof m.role === "string").map((m) => ({
|
|
3672
|
-
role: m.role === "system" || m.role === "assistant" ? m.role : "user",
|
|
3673
|
-
content: String(m.content)
|
|
3674
|
-
}));
|
|
3675
|
-
if (!messages.length) {
|
|
3676
|
-
sendResponse({ success: false, error: "messages are required" });
|
|
3677
|
-
break;
|
|
3678
|
-
}
|
|
3679
|
-
const model = String(request.model || "auto");
|
|
3680
|
-
const body = { model, messages, stream: false };
|
|
3681
|
-
if (typeof request.temperature === "number") body.temperature = request.temperature;
|
|
3682
|
-
if (typeof request.max_tokens === "number") body.max_tokens = Math.max(64, Math.min(request.max_tokens, 16384));
|
|
3683
|
-
const resp = await fetch(`${API_BASE_URL}/v1/chat/completions`, {
|
|
3684
|
-
method: "POST",
|
|
3685
|
-
headers: {
|
|
3686
|
-
"Content-Type": "application/json",
|
|
3687
|
-
Authorization: `Bearer ${token}`
|
|
3688
|
-
},
|
|
3689
|
-
body: JSON.stringify(body),
|
|
3690
|
-
signal: AbortSignal.timeout(12e4)
|
|
3691
|
-
});
|
|
3692
|
-
if (!resp.ok) {
|
|
3693
|
-
const text = await resp.text();
|
|
3694
|
-
throw new Error(`Chat API error ${resp.status}: ${text}`);
|
|
3695
|
-
}
|
|
3696
|
-
const data = await resp.json();
|
|
3697
|
-
const content = data.choices?.[0]?.message?.content || "";
|
|
3698
|
-
sendResponse({ success: true, content, model: data.model || model });
|
|
3699
|
-
} catch (e) {
|
|
3700
|
-
sendResponse({ success: false, error: e.message });
|
|
3701
|
-
}
|
|
3702
|
-
break;
|
|
3703
|
-
}
|
|
3704
|
-
case "chat.cancel":
|
|
3705
|
-
sendResponse({ success: true });
|
|
3706
|
-
break;
|
|
3707
|
-
// =======================================================================
|
|
3708
|
-
// Models
|
|
3709
|
-
// =======================================================================
|
|
3710
|
-
case "models.list": {
|
|
3711
|
-
try {
|
|
3712
|
-
const token = await getValidAccessToken2();
|
|
3713
|
-
if (!token) {
|
|
3714
|
-
try {
|
|
3715
|
-
const pubResp = await fetch(`${API_BASE_URL}/v1/models`, { signal: AbortSignal.timeout(5e3) });
|
|
3716
|
-
const pubData = await pubResp.json();
|
|
3717
|
-
sendResponse({ success: true, models: pubData.data || [], authenticated: false });
|
|
3718
|
-
} catch {
|
|
3719
|
-
sendResponse({ success: true, models: [], authenticated: false });
|
|
3720
|
-
}
|
|
3721
|
-
break;
|
|
3722
|
-
}
|
|
3723
|
-
const resp = await fetch(`${API_BASE_URL}/v1/models`, {
|
|
3724
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
3725
|
-
signal: AbortSignal.timeout(1e4)
|
|
3726
|
-
});
|
|
3727
|
-
const data = await resp.json();
|
|
3728
|
-
sendResponse({ success: true, models: data.data || data.models || [], authenticated: true });
|
|
3729
|
-
} catch (e) {
|
|
3730
|
-
sendResponse({ success: false, error: e.message });
|
|
3731
|
-
}
|
|
3732
|
-
break;
|
|
3733
|
-
}
|
|
3734
|
-
// =======================================================================
|
|
3735
|
-
// WebGPU / Local AI
|
|
3736
|
-
// =======================================================================
|
|
3737
|
-
case "checkWebGPU": {
|
|
3738
|
-
try {
|
|
3739
|
-
if (typeof navigator === "undefined" || !navigator.gpu) {
|
|
3740
|
-
sendResponse({ available: false });
|
|
3741
|
-
break;
|
|
3742
|
-
}
|
|
3743
|
-
const adapter = await navigator.gpu.requestAdapter();
|
|
3744
|
-
sendResponse({
|
|
3745
|
-
available: !!adapter,
|
|
3746
|
-
adapter: adapter?.name || "Unknown GPU",
|
|
3747
|
-
models: []
|
|
3748
|
-
});
|
|
3749
|
-
} catch {
|
|
3750
|
-
sendResponse({ available: false });
|
|
3751
|
-
}
|
|
3752
|
-
break;
|
|
3753
|
-
}
|
|
3754
|
-
case "runLocalAI":
|
|
3755
|
-
sendResponse({ success: false, error: "WebGPU inference not available in Firefox background" });
|
|
3756
|
-
break;
|
|
3757
|
-
case "listAgents":
|
|
3758
|
-
sendResponse({ success: true, agents: [] });
|
|
3759
|
-
break;
|
|
3760
|
-
case "stopAgent":
|
|
3761
|
-
sendResponse({ success: false, error: "Agent workers are not available in Firefox background mode" });
|
|
3762
|
-
break;
|
|
3763
|
-
case "launchAIWorker":
|
|
3764
|
-
sendResponse({ success: false, error: "launchAIWorker not implemented in Firefox background mode" });
|
|
3765
|
-
break;
|
|
3766
|
-
// =======================================================================
|
|
3767
|
-
// Ollama / Local LLM (portable — pure HTTP)
|
|
3768
|
-
// =======================================================================
|
|
3769
|
-
case "ollama.discover": {
|
|
3770
|
-
try {
|
|
3771
|
-
const urls = ["http://localhost:11434", "http://127.0.0.1:11434"];
|
|
3772
|
-
let found = false;
|
|
3773
|
-
let ollamaUrl = "";
|
|
3774
|
-
for (const u of urls) {
|
|
3775
|
-
try {
|
|
3776
|
-
const resp = await fetch(u, { signal: AbortSignal.timeout(2e3) });
|
|
3777
|
-
if (resp.ok) {
|
|
3778
|
-
found = true;
|
|
3779
|
-
ollamaUrl = u;
|
|
3780
|
-
break;
|
|
3781
|
-
}
|
|
3782
|
-
} catch {
|
|
3783
|
-
}
|
|
3784
|
-
}
|
|
3785
|
-
sendResponse({ success: true, discovered: found, url: ollamaUrl, available: found });
|
|
3786
|
-
} catch (e) {
|
|
3787
|
-
sendResponse({ success: false, error: e.message });
|
|
3788
|
-
}
|
|
3789
|
-
break;
|
|
3790
|
-
}
|
|
3791
|
-
case "ollama.health": {
|
|
3792
|
-
try {
|
|
3793
|
-
const stored = await browser.storage.local.get(["ollamaUrl"]);
|
|
3794
|
-
const url = stored.ollamaUrl || "http://localhost:11434";
|
|
3795
|
-
const resp = await fetch(url, { signal: AbortSignal.timeout(3e3) });
|
|
3796
|
-
sendResponse({ success: true, available: resp.ok, url });
|
|
3797
|
-
} catch (e) {
|
|
3798
|
-
sendResponse({ success: false, available: false, error: e.message });
|
|
3799
|
-
}
|
|
3800
|
-
break;
|
|
3801
|
-
}
|
|
3802
|
-
case "ollama.models": {
|
|
3803
|
-
try {
|
|
3804
|
-
const stored = await browser.storage.local.get(["ollamaUrl"]);
|
|
3805
|
-
const url = stored.ollamaUrl || "http://localhost:11434";
|
|
3806
|
-
const resp = await fetch(`${url}/api/tags`, { signal: AbortSignal.timeout(5e3) });
|
|
3807
|
-
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
3808
|
-
const data = await resp.json();
|
|
3809
|
-
sendResponse({ success: true, models: data.models || [] });
|
|
3810
|
-
} catch (e) {
|
|
3811
|
-
sendResponse({ success: false, error: e.message });
|
|
3812
|
-
}
|
|
3813
|
-
break;
|
|
3814
|
-
}
|
|
3815
|
-
case "ollama.chat": {
|
|
3816
|
-
try {
|
|
3817
|
-
const stored = await browser.storage.local.get(["ollamaUrl"]);
|
|
3818
|
-
const url = stored.ollamaUrl || "http://localhost:11434";
|
|
3819
|
-
const resp = await fetch(`${url}/api/chat`, {
|
|
3820
|
-
method: "POST",
|
|
3821
|
-
headers: { "Content-Type": "application/json" },
|
|
3822
|
-
body: JSON.stringify({
|
|
3823
|
-
model: request.model,
|
|
3824
|
-
messages: request.messages,
|
|
3825
|
-
stream: false,
|
|
3826
|
-
...request.options || {}
|
|
3827
|
-
}),
|
|
3828
|
-
signal: AbortSignal.timeout(12e4)
|
|
3829
|
-
});
|
|
3830
|
-
if (!resp.ok) throw new Error(`Ollama ${resp.status}`);
|
|
3831
|
-
const data = await resp.json();
|
|
3832
|
-
sendResponse({ success: true, content: data.message?.content || "" });
|
|
3833
|
-
} catch (e) {
|
|
3834
|
-
sendResponse({ success: false, error: e.message });
|
|
3835
|
-
}
|
|
3836
|
-
break;
|
|
3837
|
-
}
|
|
3838
|
-
case "ollama.generate": {
|
|
3839
|
-
try {
|
|
3840
|
-
const stored = await browser.storage.local.get(["ollamaUrl"]);
|
|
3841
|
-
const url = stored.ollamaUrl || "http://localhost:11434";
|
|
3842
|
-
const resp = await fetch(`${url}/api/generate`, {
|
|
3843
|
-
method: "POST",
|
|
3844
|
-
headers: { "Content-Type": "application/json" },
|
|
3845
|
-
body: JSON.stringify({
|
|
3846
|
-
model: request.model,
|
|
3847
|
-
prompt: request.prompt,
|
|
3848
|
-
stream: false,
|
|
3849
|
-
...request.options || {}
|
|
3850
|
-
}),
|
|
3851
|
-
signal: AbortSignal.timeout(12e4)
|
|
3852
|
-
});
|
|
3853
|
-
if (!resp.ok) throw new Error(`Ollama ${resp.status}`);
|
|
3854
|
-
const data = await resp.json();
|
|
3855
|
-
sendResponse({ success: true, content: data.response || "" });
|
|
3856
|
-
} catch (e) {
|
|
3857
|
-
sendResponse({ success: false, error: e.message });
|
|
3858
|
-
}
|
|
3859
|
-
break;
|
|
3860
|
-
}
|
|
3861
|
-
case "ollama.embed": {
|
|
3862
|
-
try {
|
|
3863
|
-
const stored = await browser.storage.local.get(["ollamaUrl"]);
|
|
3864
|
-
const url = stored.ollamaUrl || "http://localhost:11434";
|
|
3865
|
-
const resp = await fetch(`${url}/api/embeddings`, {
|
|
3866
|
-
method: "POST",
|
|
3867
|
-
headers: { "Content-Type": "application/json" },
|
|
3868
|
-
body: JSON.stringify({ model: request.model, prompt: request.input }),
|
|
3869
|
-
signal: AbortSignal.timeout(3e4)
|
|
3870
|
-
});
|
|
3871
|
-
if (!resp.ok) throw new Error(`Ollama ${resp.status}`);
|
|
3872
|
-
const data = await resp.json();
|
|
3873
|
-
sendResponse({ success: true, embedding: data.embedding || [] });
|
|
3874
|
-
} catch (e) {
|
|
3875
|
-
sendResponse({ success: false, error: e.message });
|
|
3876
|
-
}
|
|
3877
|
-
break;
|
|
3878
|
-
}
|
|
3879
|
-
case "ollama.status": {
|
|
3880
|
-
const stored = await browser.storage.local.get(["ollamaUrl"]);
|
|
3881
|
-
sendResponse({ success: true, url: stored.ollamaUrl || "http://localhost:11434", available: false });
|
|
3882
|
-
break;
|
|
3883
|
-
}
|
|
3884
|
-
case "ollama.setUrl": {
|
|
3885
|
-
await browser.storage.local.set({ ollamaUrl: request.url });
|
|
3886
|
-
sendResponse({ success: true });
|
|
3887
|
-
break;
|
|
3888
|
-
}
|
|
3889
|
-
case "ollama.pull": {
|
|
3890
|
-
try {
|
|
3891
|
-
const stored = await browser.storage.local.get(["ollamaUrl"]);
|
|
3892
|
-
const url = stored.ollamaUrl || "http://localhost:11434";
|
|
3893
|
-
fetch(`${url}/api/pull`, {
|
|
3894
|
-
method: "POST",
|
|
3895
|
-
headers: { "Content-Type": "application/json" },
|
|
3896
|
-
body: JSON.stringify({ name: request.model, stream: false })
|
|
3897
|
-
}).then(() => {
|
|
3898
|
-
browser.storage.local.set({ "ollama.pull.progress": { model: request.model, status: "done", timestamp: Date.now() } });
|
|
3899
|
-
}).catch((e) => {
|
|
3900
|
-
browser.storage.local.set({ "ollama.pull.progress": { model: request.model, status: "error", error: e.message, timestamp: Date.now() } });
|
|
3901
|
-
});
|
|
3902
|
-
sendResponse({ success: true, started: true });
|
|
3903
|
-
} catch (e) {
|
|
3904
|
-
sendResponse({ success: false, error: e.message });
|
|
3905
|
-
}
|
|
3906
|
-
break;
|
|
3907
|
-
}
|
|
3908
|
-
case "ollama.delete": {
|
|
3909
|
-
try {
|
|
3910
|
-
const stored = await browser.storage.local.get(["ollamaUrl"]);
|
|
3911
|
-
const url = stored.ollamaUrl || "http://localhost:11434";
|
|
3912
|
-
await fetch(`${url}/api/delete`, {
|
|
3913
|
-
method: "DELETE",
|
|
3914
|
-
headers: { "Content-Type": "application/json" },
|
|
3915
|
-
body: JSON.stringify({ name: request.model })
|
|
3916
|
-
});
|
|
3917
|
-
sendResponse({ success: true });
|
|
3918
|
-
} catch (e) {
|
|
3919
|
-
sendResponse({ success: false, error: e.message });
|
|
3920
|
-
}
|
|
3921
|
-
break;
|
|
3922
|
-
}
|
|
3923
|
-
// =======================================================================
|
|
3924
|
-
// Local LLM Provider (unified — Ollama / LM Studio / Hanzo Desktop)
|
|
3925
|
-
// =======================================================================
|
|
3926
|
-
case "local.chat": {
|
|
3927
|
-
try {
|
|
3928
|
-
const provider = request.provider || "ollama";
|
|
3929
|
-
const endpoint = request.endpoint;
|
|
3930
|
-
const model = request.model;
|
|
3931
|
-
const messages = request.messages;
|
|
3932
|
-
const options = request.options || {};
|
|
3933
|
-
let result;
|
|
3934
|
-
if (provider === "ollama" && !endpoint) {
|
|
3935
|
-
const stored = await browser.storage.local.get(["ollamaUrl"]);
|
|
3936
|
-
const url = stored.ollamaUrl || "http://localhost:11434";
|
|
3937
|
-
const resp = await fetch(`${url}/api/chat`, {
|
|
3938
|
-
method: "POST",
|
|
3939
|
-
headers: { "Content-Type": "application/json" },
|
|
3940
|
-
body: JSON.stringify({ model, messages, stream: false, ...options }),
|
|
3941
|
-
signal: AbortSignal.timeout(12e4)
|
|
3942
|
-
});
|
|
3943
|
-
if (!resp.ok) throw new Error(`Ollama ${resp.status}`);
|
|
3944
|
-
const data = await resp.json();
|
|
3945
|
-
result = data.message?.content || "";
|
|
3946
|
-
} else if (provider === "anthropic") {
|
|
3947
|
-
const baseUrl = endpoint || "https://api.anthropic.com";
|
|
3948
|
-
const systemMsg = messages.find((m) => m.role === "system");
|
|
3949
|
-
const chatMsgs = messages.filter((m) => m.role !== "system");
|
|
3950
|
-
const body = { model, max_tokens: options.num_predict || 4096, messages: chatMsgs, stream: false };
|
|
3951
|
-
if (systemMsg) body.system = systemMsg.content;
|
|
3952
|
-
if (options.temperature !== void 0) body.temperature = options.temperature;
|
|
3953
|
-
const resp = await fetch(`${baseUrl}/v1/messages`, {
|
|
3954
|
-
method: "POST",
|
|
3955
|
-
headers: { "Content-Type": "application/json", "x-api-key": request.apiKey || "", "anthropic-version": "2023-06-01" },
|
|
3956
|
-
body: JSON.stringify(body),
|
|
3957
|
-
signal: AbortSignal.timeout(12e4)
|
|
3958
|
-
});
|
|
3959
|
-
if (!resp.ok) throw new Error(`Anthropic: ${resp.status} ${await resp.text()}`);
|
|
3960
|
-
const data = await resp.json();
|
|
3961
|
-
result = data.content?.[0]?.text || "";
|
|
3962
|
-
} else {
|
|
3963
|
-
const lpCfg = LOCAL_PROVIDER_DEFAULTS[provider];
|
|
3964
|
-
const baseUrl = endpoint || (lpCfg ? `${lpCfg.url}/v1` : "http://localhost:8080/v1");
|
|
3965
|
-
const headers = { "Content-Type": "application/json" };
|
|
3966
|
-
if (request.apiKey) headers["Authorization"] = `Bearer ${request.apiKey}`;
|
|
3967
|
-
const body = { model, messages, stream: false };
|
|
3968
|
-
if (options.temperature !== void 0) body.temperature = options.temperature;
|
|
3969
|
-
if (options.num_predict !== void 0) body.max_tokens = options.num_predict;
|
|
3970
|
-
const resp = await fetch(`${baseUrl}/chat/completions`, {
|
|
3971
|
-
method: "POST",
|
|
3972
|
-
headers,
|
|
3973
|
-
body: JSON.stringify(body),
|
|
3974
|
-
signal: AbortSignal.timeout(12e4)
|
|
3975
|
-
});
|
|
3976
|
-
if (!resp.ok) throw new Error(`${provider}: ${resp.status} ${await resp.text()}`);
|
|
3977
|
-
const data = await resp.json();
|
|
3978
|
-
result = data.choices?.[0]?.message?.content || "";
|
|
3979
|
-
}
|
|
3980
|
-
sendResponse({ success: true, content: result });
|
|
3981
|
-
} catch (e) {
|
|
3982
|
-
sendResponse({ success: false, error: e.message });
|
|
3983
|
-
}
|
|
3984
|
-
break;
|
|
3985
|
-
}
|
|
3986
|
-
case "local.models": {
|
|
3987
|
-
try {
|
|
3988
|
-
const lpCfg2 = LOCAL_PROVIDER_DEFAULTS[request.provider];
|
|
3989
|
-
const endpoint = request.endpoint || (request.provider === "ollama" ? null : lpCfg2 ? `${lpCfg2.url}/v1` : request.endpoint || "http://localhost:8080/v1");
|
|
3990
|
-
if (!endpoint) {
|
|
3991
|
-
const stored = await browser.storage.local.get(["ollamaUrl"]);
|
|
3992
|
-
const url = stored.ollamaUrl || LOCAL_PROVIDER_DEFAULTS.ollama.url;
|
|
3993
|
-
const resp2 = await fetch(`${url}/api/tags`, { signal: AbortSignal.timeout(5e3) });
|
|
3994
|
-
if (!resp2.ok) throw new Error(`HTTP ${resp2.status}`);
|
|
3995
|
-
const data2 = await resp2.json();
|
|
3996
|
-
sendResponse({ success: true, models: (data2.models || []).map((m) => ({ id: m.name, name: m.name, size: m.size, details: m.details })) });
|
|
3997
|
-
break;
|
|
3998
|
-
}
|
|
3999
|
-
const headers = {};
|
|
4000
|
-
if (request.apiKey) headers["Authorization"] = `Bearer ${request.apiKey}`;
|
|
4001
|
-
const resp = await fetch(`${endpoint}/models`, { headers, signal: AbortSignal.timeout(5e3) });
|
|
4002
|
-
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
4003
|
-
const data = await resp.json();
|
|
4004
|
-
sendResponse({ success: true, models: data.data || data.models || [] });
|
|
4005
|
-
} catch (e) {
|
|
4006
|
-
sendResponse({ success: false, error: e.message });
|
|
4007
|
-
}
|
|
4008
|
-
break;
|
|
4009
|
-
}
|
|
4010
|
-
case "local.discover": {
|
|
4011
|
-
const providers = [];
|
|
4012
|
-
try {
|
|
4013
|
-
const stored2 = await browser.storage.local.get(["ollamaUrl"]);
|
|
4014
|
-
const url = stored2.ollamaUrl || LOCAL_PROVIDER_DEFAULTS.ollama.url;
|
|
4015
|
-
const resp = await fetch(`${url}/api/tags`, { signal: AbortSignal.timeout(2e3) });
|
|
4016
|
-
if (resp.ok) {
|
|
4017
|
-
const data = await resp.json();
|
|
4018
|
-
providers.push({ name: "ollama", url, available: true, models: (data.models || []).map((m) => m.name) });
|
|
4019
|
-
} else {
|
|
4020
|
-
providers.push({ name: "ollama", url, available: false });
|
|
4021
|
-
}
|
|
4022
|
-
} catch {
|
|
4023
|
-
providers.push({ name: "ollama", url: LOCAL_PROVIDER_DEFAULTS.ollama.url, available: false });
|
|
4024
|
-
}
|
|
4025
|
-
{
|
|
4026
|
-
const lmsCfg = LOCAL_PROVIDER_DEFAULTS.lmstudio;
|
|
4027
|
-
try {
|
|
4028
|
-
const resp = await fetch(`${lmsCfg.url}/models`, { signal: AbortSignal.timeout(2e3) });
|
|
4029
|
-
if (resp.ok) {
|
|
4030
|
-
const data = await resp.json();
|
|
4031
|
-
providers.push({ name: "lmstudio", url: lmsCfg.url, available: true, models: (data.data || []).map((m) => m.id) });
|
|
4032
|
-
} else {
|
|
4033
|
-
providers.push({ name: "lmstudio", url: lmsCfg.url, available: false });
|
|
4034
|
-
}
|
|
4035
|
-
} catch {
|
|
4036
|
-
providers.push({ name: "lmstudio", url: lmsCfg.url, available: false });
|
|
4037
|
-
}
|
|
4038
|
-
}
|
|
4039
|
-
{
|
|
4040
|
-
const hdCfg = LOCAL_PROVIDER_DEFAULTS["hanzo-desktop"];
|
|
4041
|
-
for (const port of hdCfg.probePorts) {
|
|
4042
|
-
try {
|
|
4043
|
-
const resp = await fetch(`http://localhost:${port}/api/tags`, { signal: AbortSignal.timeout(2e3) });
|
|
4044
|
-
if (resp.ok) {
|
|
4045
|
-
const data = await resp.json();
|
|
4046
|
-
providers.push({ name: "hanzo-desktop", url: `http://localhost:${port}`, available: true, models: (data.models || []).map((m) => m.name) });
|
|
4047
|
-
break;
|
|
4048
|
-
}
|
|
4049
|
-
} catch {
|
|
4050
|
-
}
|
|
4051
|
-
}
|
|
4052
|
-
if (!providers.find((p) => p.name === "hanzo-desktop")) {
|
|
4053
|
-
providers.push({ name: "hanzo-desktop", url: hdCfg.url, available: false });
|
|
4054
|
-
}
|
|
4055
|
-
}
|
|
4056
|
-
const stored = await browser.storage.local.get(["customLLMEndpoints"]);
|
|
4057
|
-
const customs = stored.customLLMEndpoints || [];
|
|
4058
|
-
for (const c of customs) {
|
|
4059
|
-
try {
|
|
4060
|
-
const headers = {};
|
|
4061
|
-
if (c.apiKey) headers["Authorization"] = `Bearer ${c.apiKey}`;
|
|
4062
|
-
const resp = await fetch(`${c.url}/models`, { headers, signal: AbortSignal.timeout(3e3) });
|
|
4063
|
-
if (resp.ok) {
|
|
4064
|
-
const data = await resp.json();
|
|
4065
|
-
providers.push({ name: c.name || "custom", url: c.url, available: true, models: (data.data || data.models || []).map((m) => m.id || m.name) });
|
|
4066
|
-
} else {
|
|
4067
|
-
providers.push({ name: c.name || "custom", url: c.url, available: false });
|
|
4068
|
-
}
|
|
4069
|
-
} catch {
|
|
4070
|
-
providers.push({ name: c.name || "custom", url: c.url, available: false });
|
|
4071
|
-
}
|
|
4072
|
-
}
|
|
4073
|
-
sendResponse({ success: true, providers });
|
|
4074
|
-
break;
|
|
4075
|
-
}
|
|
4076
|
-
// =======================================================================
|
|
4077
|
-
// Provider Management
|
|
4078
|
-
// =======================================================================
|
|
4079
|
-
case "provider.save": {
|
|
4080
|
-
try {
|
|
4081
|
-
const stored = await browser.storage.local.get(["customLLMEndpoints"]);
|
|
4082
|
-
const endpoints = stored.customLLMEndpoints || [];
|
|
4083
|
-
const existing = endpoints.findIndex((e) => e.name === request.name || e.url === request.url);
|
|
4084
|
-
const entry = { name: request.name, url: request.url, apiKey: request.apiKey };
|
|
4085
|
-
if (existing >= 0) endpoints[existing] = entry;
|
|
4086
|
-
else endpoints.push(entry);
|
|
4087
|
-
await browser.storage.local.set({ customLLMEndpoints: endpoints });
|
|
4088
|
-
sendResponse({ success: true });
|
|
4089
|
-
} catch (e) {
|
|
4090
|
-
sendResponse({ success: false, error: e.message });
|
|
4091
|
-
}
|
|
4092
|
-
break;
|
|
4093
|
-
}
|
|
4094
|
-
case "provider.remove": {
|
|
4095
|
-
try {
|
|
4096
|
-
const stored = await browser.storage.local.get(["customLLMEndpoints"]);
|
|
4097
|
-
const endpoints = (stored.customLLMEndpoints || []).filter(
|
|
4098
|
-
(e) => e.name !== request.name && e.url !== request.url
|
|
4099
|
-
);
|
|
4100
|
-
await browser.storage.local.set({ customLLMEndpoints: endpoints });
|
|
4101
|
-
sendResponse({ success: true });
|
|
4102
|
-
} catch (e) {
|
|
4103
|
-
sendResponse({ success: false, error: e.message });
|
|
4104
|
-
}
|
|
4105
|
-
break;
|
|
4106
|
-
}
|
|
4107
|
-
case "provider.test": {
|
|
4108
|
-
try {
|
|
4109
|
-
const headers = {};
|
|
4110
|
-
if (request.apiKey) headers["Authorization"] = `Bearer ${request.apiKey}`;
|
|
4111
|
-
const resp = await fetch(`${request.url}/models`, { headers, signal: AbortSignal.timeout(5e3) });
|
|
4112
|
-
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
4113
|
-
const data = await resp.json();
|
|
4114
|
-
sendResponse({ success: true, models: (data.data || data.models || []).map((m) => m.id || m.name) });
|
|
4115
|
-
} catch (e) {
|
|
4116
|
-
sendResponse({ success: false, error: e.message });
|
|
4117
|
-
}
|
|
4118
|
-
break;
|
|
4119
|
-
}
|
|
4120
|
-
// =======================================================================
|
|
4121
|
-
// Settings (browser.storage.local)
|
|
4122
|
-
// =======================================================================
|
|
4123
|
-
case "settings.get": {
|
|
4124
|
-
try {
|
|
4125
|
-
const keys = request.keys || [
|
|
4126
|
-
"selectedProvider",
|
|
4127
|
-
"selectedModel",
|
|
4128
|
-
"customLLMEndpoints",
|
|
4129
|
-
"ollamaUrl",
|
|
4130
|
-
"anthropicApiKey",
|
|
4131
|
-
"openaiApiKey",
|
|
4132
|
-
"hanzoApiKey",
|
|
4133
|
-
"mcpPort",
|
|
4134
|
-
"cdpPort",
|
|
4135
|
-
"zapPorts",
|
|
4136
|
-
"activeOrgId",
|
|
4137
|
-
"browserBackend"
|
|
4138
|
-
];
|
|
4139
|
-
const result = await browser.storage.local.get(keys);
|
|
4140
|
-
sendResponse({ success: true, settings: result });
|
|
4141
|
-
} catch (e) {
|
|
4142
|
-
sendResponse({ success: false, error: e.message });
|
|
4143
|
-
}
|
|
4144
|
-
break;
|
|
4145
|
-
}
|
|
4146
|
-
case "settings.set": {
|
|
4147
|
-
try {
|
|
4148
|
-
await browser.storage.local.set(request.settings);
|
|
4149
|
-
sendResponse({ success: true });
|
|
4150
|
-
} catch (e) {
|
|
4151
|
-
sendResponse({ success: false, error: e.message });
|
|
4152
|
-
}
|
|
4153
|
-
break;
|
|
4154
|
-
}
|
|
4155
|
-
case "settings.export": {
|
|
4156
|
-
try {
|
|
4157
|
-
const all = await browser.storage.local.get(null);
|
|
4158
|
-
const safe = {};
|
|
4159
|
-
for (const [k, v] of Object.entries(all)) {
|
|
4160
|
-
if (k.startsWith("hanzo_iam_")) continue;
|
|
4161
|
-
safe[k] = v;
|
|
4162
|
-
}
|
|
4163
|
-
sendResponse({ success: true, settings: safe });
|
|
4164
|
-
} catch (e) {
|
|
4165
|
-
sendResponse({ success: false, error: e.message });
|
|
4166
|
-
}
|
|
4167
|
-
break;
|
|
4168
|
-
}
|
|
4169
|
-
case "settings.import": {
|
|
4170
|
-
try {
|
|
4171
|
-
const settings = request.settings || {};
|
|
4172
|
-
delete settings.hanzo_iam_access_token;
|
|
4173
|
-
delete settings.hanzo_iam_refresh_token;
|
|
4174
|
-
delete settings.hanzo_iam_id_token;
|
|
4175
|
-
await browser.storage.local.set(settings);
|
|
4176
|
-
sendResponse({ success: true });
|
|
4177
|
-
} catch (e) {
|
|
4178
|
-
sendResponse({ success: false, error: e.message });
|
|
4179
|
-
}
|
|
4180
|
-
break;
|
|
4181
|
-
}
|
|
4182
|
-
case "settings.sync":
|
|
4183
|
-
sendResponse({ success: true });
|
|
4184
|
-
break;
|
|
4185
|
-
case "config.save": {
|
|
4186
|
-
try {
|
|
4187
|
-
if (request.key) {
|
|
4188
|
-
await browser.storage.local.set({ [request.key]: request.value });
|
|
4189
|
-
}
|
|
4190
|
-
sendResponse({ success: true });
|
|
4191
|
-
} catch (e) {
|
|
4192
|
-
sendResponse({ success: false, error: e.message });
|
|
4193
|
-
}
|
|
4194
|
-
break;
|
|
4195
|
-
}
|
|
4196
|
-
// =======================================================================
|
|
4197
|
-
// API Key Management
|
|
4198
|
-
// =======================================================================
|
|
4199
|
-
case "apikey.save": {
|
|
4200
|
-
try {
|
|
4201
|
-
await browser.storage.local.set({ [`apikey_${request.provider}`]: request.apiKey });
|
|
4202
|
-
sendResponse({ success: true });
|
|
4203
|
-
} catch (e) {
|
|
4204
|
-
sendResponse({ success: false, error: e.message });
|
|
4205
|
-
}
|
|
4206
|
-
break;
|
|
4207
|
-
}
|
|
4208
|
-
case "apikey.get": {
|
|
4209
|
-
try {
|
|
4210
|
-
const key = `apikey_${request.provider}`;
|
|
4211
|
-
const stored = await browser.storage.local.get([key]);
|
|
4212
|
-
sendResponse({ success: true, apiKey: stored[key] || null });
|
|
4213
|
-
} catch (e) {
|
|
4214
|
-
sendResponse({ success: false, error: e.message });
|
|
4215
|
-
}
|
|
4216
|
-
break;
|
|
4217
|
-
}
|
|
4218
|
-
case "apikey.list": {
|
|
4219
|
-
try {
|
|
4220
|
-
const all = await browser.storage.local.get(null);
|
|
4221
|
-
const keys = [];
|
|
4222
|
-
for (const [k] of Object.entries(all)) {
|
|
4223
|
-
if (k.startsWith("apikey_")) {
|
|
4224
|
-
keys.push({ provider: k.replace("apikey_", ""), hasKey: true });
|
|
4225
|
-
}
|
|
4226
|
-
}
|
|
4227
|
-
sendResponse({ success: true, keys });
|
|
4228
|
-
} catch (e) {
|
|
4229
|
-
sendResponse({ success: false, error: e.message });
|
|
4230
|
-
}
|
|
4231
|
-
break;
|
|
4232
|
-
}
|
|
4233
|
-
// =======================================================================
|
|
4234
|
-
// Organization Switcher
|
|
4235
|
-
// =======================================================================
|
|
4236
|
-
case "org.list": {
|
|
4237
|
-
try {
|
|
4238
|
-
const token = await getValidAccessToken2();
|
|
4239
|
-
if (!token) {
|
|
4240
|
-
sendResponse({ success: false, error: "Not authenticated" });
|
|
4241
|
-
break;
|
|
4242
|
-
}
|
|
4243
|
-
const resp = await fetch(`${IAM_V1}/get-account`, {
|
|
4244
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
4245
|
-
signal: AbortSignal.timeout(5e3)
|
|
4246
|
-
});
|
|
4247
|
-
if (!resp.ok) throw new Error(`IAM: ${resp.status}`);
|
|
4248
|
-
const data = await resp.json();
|
|
4249
|
-
const user = data.data || data;
|
|
4250
|
-
const orgs = [];
|
|
4251
|
-
if (user.owner) {
|
|
4252
|
-
orgs.push({ id: user.owner, name: user.owner, displayName: user.owner });
|
|
4253
|
-
}
|
|
4254
|
-
try {
|
|
4255
|
-
const orgsResp = await fetch(`${IAM_V1}/get-organizations`, {
|
|
4256
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
4257
|
-
signal: AbortSignal.timeout(5e3)
|
|
4258
|
-
});
|
|
4259
|
-
if (orgsResp.ok) {
|
|
4260
|
-
const orgsData = await orgsResp.json();
|
|
4261
|
-
for (const org of orgsData.data || orgsData || []) {
|
|
4262
|
-
if (!orgs.find((o) => o.id === org.name)) {
|
|
4263
|
-
orgs.push({ id: org.name, name: org.name, displayName: org.displayName || org.name });
|
|
4264
|
-
}
|
|
4265
|
-
}
|
|
4266
|
-
}
|
|
4267
|
-
} catch {
|
|
4268
|
-
}
|
|
4269
|
-
const stored = await browser.storage.local.get(["activeOrgId"]);
|
|
4270
|
-
sendResponse({
|
|
4271
|
-
success: true,
|
|
4272
|
-
orgs,
|
|
4273
|
-
activeOrgId: stored.activeOrgId || orgs[0]?.id || null,
|
|
4274
|
-
user: { name: user.displayName || user.name, email: user.email, avatar: user.avatar }
|
|
4275
|
-
});
|
|
4276
|
-
} catch (e) {
|
|
4277
|
-
sendResponse({ success: false, error: e.message });
|
|
4278
|
-
}
|
|
4279
|
-
break;
|
|
4280
|
-
}
|
|
4281
|
-
case "org.switch": {
|
|
4282
|
-
try {
|
|
4283
|
-
await browser.storage.local.set({ activeOrgId: request.orgId });
|
|
4284
|
-
sendResponse({ success: true, activeOrgId: request.orgId });
|
|
4285
|
-
} catch (e) {
|
|
4286
|
-
sendResponse({ success: false, error: e.message });
|
|
4287
|
-
}
|
|
4288
|
-
break;
|
|
4289
|
-
}
|
|
4290
|
-
// =======================================================================
|
|
4291
|
-
// Model Hub (portable — pure HTTP/fetch)
|
|
4292
|
-
// =======================================================================
|
|
4293
|
-
case "hub.search": {
|
|
4294
|
-
try {
|
|
4295
|
-
const query = encodeURIComponent(request.query || "");
|
|
4296
|
-
const limit = request.limit || 10;
|
|
4297
|
-
const resp = await fetch(
|
|
4298
|
-
`https://huggingface.co/api/models?search=${query}&limit=${limit}&sort=downloads&direction=-1`,
|
|
4299
|
-
{ signal: AbortSignal.timeout(1e4) }
|
|
4300
|
-
);
|
|
4301
|
-
if (!resp.ok) throw new Error(`HuggingFace ${resp.status}`);
|
|
4302
|
-
sendResponse({ success: true, models: await resp.json() });
|
|
4303
|
-
} catch (e) {
|
|
4304
|
-
sendResponse({ success: false, error: e.message });
|
|
4305
|
-
}
|
|
4306
|
-
break;
|
|
4307
|
-
}
|
|
4308
|
-
case "hub.model": {
|
|
4309
|
-
try {
|
|
4310
|
-
const resp = await fetch(`https://huggingface.co/api/models/${request.modelId}`, { signal: AbortSignal.timeout(1e4) });
|
|
4311
|
-
if (!resp.ok) throw new Error(`HuggingFace ${resp.status}`);
|
|
4312
|
-
sendResponse({ success: true, model: await resp.json() });
|
|
4313
|
-
} catch (e) {
|
|
4314
|
-
sendResponse({ success: false, error: e.message });
|
|
4315
|
-
}
|
|
4316
|
-
break;
|
|
4317
|
-
}
|
|
4318
|
-
case "hub.recommended":
|
|
4319
|
-
sendResponse({ success: true, models: [] });
|
|
4320
|
-
break;
|
|
4321
|
-
case "hub.searchOllama": {
|
|
4322
|
-
try {
|
|
4323
|
-
const resp = await fetch(`https://ollama.com/api/tags?q=${encodeURIComponent(request.query || "")}`, { signal: AbortSignal.timeout(1e4) });
|
|
4324
|
-
if (resp.ok) {
|
|
4325
|
-
sendResponse({ success: true, models: await resp.json() });
|
|
4326
|
-
} else {
|
|
4327
|
-
sendResponse({ success: true, models: [] });
|
|
4328
|
-
}
|
|
4329
|
-
} catch {
|
|
4330
|
-
sendResponse({ success: true, models: [] });
|
|
4331
|
-
}
|
|
4332
|
-
break;
|
|
4333
|
-
}
|
|
4334
|
-
case "hub.searchGGUF":
|
|
4335
|
-
case "hub.searchMLX":
|
|
4336
|
-
case "hub.modelCard":
|
|
4337
|
-
case "hub.modelStats":
|
|
4338
|
-
case "hub.allModels":
|
|
4339
|
-
sendResponse({ success: true, models: [], card: null, stats: null, cloud: [], local: [], recommended: [] });
|
|
4340
|
-
break;
|
|
4341
|
-
case "hub.downloadOllama": {
|
|
4342
|
-
try {
|
|
4343
|
-
const stored = await browser.storage.local.get(["ollamaUrl"]);
|
|
4344
|
-
const url = stored.ollamaUrl || "http://localhost:11434";
|
|
4345
|
-
fetch(`${url}/api/pull`, {
|
|
4346
|
-
method: "POST",
|
|
4347
|
-
headers: { "Content-Type": "application/json" },
|
|
4348
|
-
body: JSON.stringify({ name: request.modelName, stream: false })
|
|
4349
|
-
}).catch(() => {
|
|
4350
|
-
});
|
|
4351
|
-
sendResponse({ success: true });
|
|
4352
|
-
} catch (e) {
|
|
4353
|
-
sendResponse({ success: false, error: e.message });
|
|
4354
|
-
}
|
|
4355
|
-
break;
|
|
4356
|
-
}
|
|
4357
|
-
// =======================================================================
|
|
4358
|
-
// Element Selected (forward to ZAP-connected MCPs)
|
|
4359
|
-
// =======================================================================
|
|
4360
|
-
case "elementSelected": {
|
|
4361
|
-
if (zapMgr.state.connected) {
|
|
4362
|
-
for (const [mcpId] of zapMgr.connections) {
|
|
4363
|
-
zapRequest(zapMgr, mcpId, "notifications/elementSelected", request.data || {}).catch(() => {
|
|
4364
|
-
});
|
|
4365
|
-
}
|
|
4366
|
-
}
|
|
4367
|
-
sendResponse({ success: true });
|
|
4368
|
-
break;
|
|
4369
|
-
}
|
|
4370
|
-
// =======================================================================
|
|
4371
|
-
// Page Overlay (forwarded to content script)
|
|
4372
|
-
// =======================================================================
|
|
4373
|
-
case "page.overlay.toggle":
|
|
4374
|
-
case "page.overlay.show":
|
|
4375
|
-
case "page.overlay.hide":
|
|
4376
|
-
case "page.overlay.status": {
|
|
4377
|
-
try {
|
|
4378
|
-
const tabId = typeof request.tabId === "number" ? request.tabId : sender?.tab?.id ?? await hanzoExtension.getActiveTabId();
|
|
4379
|
-
if (!tabId) {
|
|
4380
|
-
sendResponse({ success: false, error: "No target tab found" });
|
|
4381
|
-
break;
|
|
4382
|
-
}
|
|
4383
|
-
const response = await browser.tabs.sendMessage(tabId, { action: request.action }).catch(() => ({}));
|
|
4384
|
-
sendResponse({ success: true, tabId, ...response || {} });
|
|
4385
|
-
} catch (e) {
|
|
4386
|
-
sendResponse({ success: false, error: e.message });
|
|
4387
|
-
}
|
|
4388
|
-
break;
|
|
4389
|
-
}
|
|
4390
|
-
// =======================================================================
|
|
4391
|
-
// Takeover (control session management)
|
|
4392
|
-
// =======================================================================
|
|
4393
|
-
case "hanzo.takeover.start": {
|
|
4394
|
-
const takeoverTabId = request.tabId || controlSession.tabId || await hanzoExtension.getActiveTabId();
|
|
4395
|
-
if (!takeoverTabId) {
|
|
4396
|
-
sendResponse({ success: false, error: "No target tab found" });
|
|
4397
|
-
break;
|
|
4398
|
-
}
|
|
4399
|
-
await hanzoExtension.startControlSession(takeoverTabId, request.task);
|
|
4400
|
-
sendResponse({ success: true });
|
|
4401
|
-
break;
|
|
4402
|
-
}
|
|
4403
|
-
case "hanzo.takeover.end": {
|
|
4404
|
-
await hanzoExtension.stopControlSession();
|
|
4405
|
-
sendResponse({ success: true });
|
|
4406
|
-
break;
|
|
4407
|
-
}
|
|
4408
|
-
case "hanzo.takeover.cursor": {
|
|
4409
|
-
const cursorTabId = controlSession.tabId || await hanzoExtension.getActiveTabId();
|
|
4410
|
-
if (cursorTabId) {
|
|
4411
|
-
browser.tabs.sendMessage(cursorTabId, {
|
|
4412
|
-
action: "ai.control.cursor",
|
|
4413
|
-
x: request.x,
|
|
4414
|
-
y: request.y
|
|
4415
|
-
}).catch(() => {
|
|
4416
|
-
});
|
|
4417
|
-
}
|
|
4418
|
-
sendResponse({ success: true });
|
|
4419
|
-
break;
|
|
4420
|
-
}
|
|
4421
|
-
// =======================================================================
|
|
4422
|
-
// Bridge & Usage
|
|
4423
|
-
// =======================================================================
|
|
4424
|
-
case "bridge.status": {
|
|
4425
|
-
const bridgeConnected = hanzoExtension.isBridgeConnected();
|
|
4426
|
-
sendResponse({
|
|
4427
|
-
success: true,
|
|
4428
|
-
connected: bridgeConnected,
|
|
4429
|
-
browsers: bridgeConnected ? ["firefox"] : []
|
|
4430
|
-
});
|
|
4431
|
-
break;
|
|
4432
|
-
}
|
|
4433
|
-
case "usage.metrics":
|
|
4434
|
-
sendResponse({ success: true, metrics: { chatRequests: 0, inputTokens: 0, outputTokens: 0, dedupeHits: 0, canceledRequests: 0 } });
|
|
4435
|
-
break;
|
|
4436
|
-
case "inspector.pickResult":
|
|
4437
|
-
sendResponse({ success: true });
|
|
4438
|
-
break;
|
|
4439
|
-
// =======================================================================
|
|
4440
|
-
// AI Control Overlay (forwarded to content script)
|
|
4441
|
-
// =======================================================================
|
|
4442
|
-
case "ai.control.start":
|
|
4443
|
-
case "ai.control.cursor":
|
|
4444
|
-
case "ai.control.highlight":
|
|
4445
|
-
case "ai.control.status": {
|
|
4446
|
-
const targetTabId = request.tabId || controlSession.tabId || await hanzoExtension.getActiveTabId();
|
|
4447
|
-
if (!targetTabId) {
|
|
4448
|
-
sendResponse({ success: false, error: "No target tab found" });
|
|
4449
|
-
break;
|
|
4450
|
-
}
|
|
4451
|
-
if (request.action !== "ai.control.start" && controlSession.active && controlSession.tabId && targetTabId !== controlSession.tabId) {
|
|
4452
|
-
sendResponse({ success: false, error: `Control locked to tab ${controlSession.tabId}` });
|
|
4453
|
-
break;
|
|
4454
|
-
}
|
|
4455
|
-
if (request.action === "ai.control.start") {
|
|
4456
|
-
await hanzoExtension.startControlSession(targetTabId, request.task);
|
|
4457
|
-
} else {
|
|
4458
|
-
browser.tabs.sendMessage(targetTabId, request).catch(() => {
|
|
4459
|
-
});
|
|
4460
|
-
}
|
|
4461
|
-
sendResponse({ success: true, tabId: targetTabId });
|
|
4462
|
-
break;
|
|
4463
|
-
}
|
|
4464
|
-
case "ai.control.stop":
|
|
4465
|
-
await hanzoExtension.stopControlSession();
|
|
4466
|
-
sendResponse({ success: true });
|
|
4467
|
-
break;
|
|
4468
|
-
case "ai.control.cancel":
|
|
4469
|
-
await hanzoExtension.stopControlSession();
|
|
4470
|
-
sendResponse({ success: true });
|
|
4471
|
-
break;
|
|
4472
|
-
// =======================================================================
|
|
4473
|
-
// CDP-specific stubs (not available in Firefox — chrome.debugger is Chrome-only)
|
|
4474
|
-
// =======================================================================
|
|
4475
|
-
case "cdp.attach":
|
|
4476
|
-
case "cdp.detach":
|
|
4477
|
-
case "cdp.send":
|
|
4478
|
-
case "cdp.screenshot":
|
|
4479
|
-
case "cdp.click":
|
|
4480
|
-
case "cdp.fill":
|
|
4481
|
-
case "cdp.navigate":
|
|
4482
|
-
case "cdp.evaluate":
|
|
4483
|
-
sendResponse({ success: false, error: "CDP debugger is not available in Firefox. Use browser automation methods instead." });
|
|
4484
|
-
break;
|
|
4485
|
-
// Monitor stubs (CDP-dependent in Chrome, limited in Firefox)
|
|
4486
|
-
case "monitor.start":
|
|
4487
|
-
case "monitor.stop":
|
|
4488
|
-
case "monitor.consoleLogs":
|
|
4489
|
-
case "monitor.consoleErrors":
|
|
4490
|
-
case "monitor.networkLogs":
|
|
4491
|
-
case "monitor.networkErrors":
|
|
4492
|
-
case "monitor.networkSuccess":
|
|
4493
|
-
case "monitor.allNetwork":
|
|
4494
|
-
case "monitor.wipeLogs":
|
|
4495
|
-
case "monitor.config":
|
|
4496
|
-
case "monitor.status":
|
|
4497
|
-
sendResponse({ success: false, error: "Page monitoring requires CDP (Chrome DevTools Protocol) which is not available in Firefox." });
|
|
4498
|
-
break;
|
|
4499
|
-
// Audit stubs
|
|
4500
|
-
case "audit.accessibility":
|
|
4501
|
-
case "audit.performance":
|
|
4502
|
-
case "audit.seo":
|
|
4503
|
-
case "audit.bestPractices":
|
|
4504
|
-
case "audit.full":
|
|
4505
|
-
case "audit.nextjs":
|
|
4506
|
-
case "audit.debugger":
|
|
4507
|
-
case "debugger.mode":
|
|
4508
|
-
sendResponse({ success: false, error: "Page audits require CDP which is not available in Firefox." });
|
|
4509
|
-
break;
|
|
4510
|
-
// Tab filesystem (requires CDP bridge)
|
|
4511
|
-
case "readTabFS":
|
|
4512
|
-
case "writeTabFS":
|
|
4513
|
-
case "listTabFS":
|
|
4514
|
-
sendResponse({ success: false, error: "Tab filesystem requires CDP bridge." });
|
|
4515
|
-
break;
|
|
4516
|
-
default:
|
|
4517
|
-
sendResponse({ success: false, error: `Unknown action: ${request.action}` });
|
|
4518
|
-
break;
|
|
4519
|
-
}
|
|
4520
|
-
})();
|
|
4521
|
-
return true;
|
|
4522
|
-
});
|
|
4523
|
-
var hanzoExtension = new HanzoFirefoxExtension();
|
|
4524
|
-
var BROWSER_CAPS = ["browser.tabs", "browser.navigate", "browser.dom", "browser.screenshot", "browser.input"];
|
|
4525
|
-
var nativeZap = {
|
|
4526
|
-
connected: false,
|
|
4527
|
-
port: null,
|
|
4528
|
-
providerId: `browser:${BROWSER_NAME}/default`
|
|
4529
|
-
};
|
|
4530
|
-
var browserDispatch = (method, params) => hanzoExtension.dispatchMethod(method, params || {});
|
|
4531
|
-
connectNativeZap(nativeZap, "hanzo", BROWSER_CAPS, browserDispatch, console.log);
|
|
4532
|
-
browser.runtime.onConnect.addListener((port) => {
|
|
4533
|
-
if (port.name !== "zap-keepalive") return;
|
|
4534
|
-
port.onMessage.addListener(() => {
|
|
4535
|
-
});
|
|
4536
|
-
});
|
|
4537
|
-
var KEEP_ALIVE_ALARM = "hanzo-zap-keepalive";
|
|
4538
|
-
try {
|
|
4539
|
-
browser.alarms.create(KEEP_ALIVE_ALARM, { periodInMinutes: 0.25 });
|
|
4540
|
-
browser.alarms.onAlarm.addListener((alarm) => {
|
|
4541
|
-
if (alarm.name !== KEEP_ALIVE_ALARM) return;
|
|
4542
|
-
if (!nativeZap.connected) {
|
|
4543
|
-
console.log("[Hanzo/ZAP] keep-alive: reconnecting native host");
|
|
4544
|
-
connectNativeZap(nativeZap, "hanzo", BROWSER_CAPS, browserDispatch, console.log);
|
|
4545
|
-
}
|
|
4546
|
-
});
|
|
4547
|
-
} catch (e) {
|
|
4548
|
-
console.warn("[Hanzo] alarms unavailable:", e);
|
|
4549
|
-
}
|
|
4550
|
-
browser.tabs.onRemoved.addListener((tabId) => {
|
|
4551
|
-
if (controlSession.active && controlSession.tabId === tabId) {
|
|
4552
|
-
void hanzoExtension.stopControlSession();
|
|
4553
|
-
}
|
|
4554
|
-
});
|
|
4555
|
-
console.log("[Hanzo] Firefox background script loaded");
|
|
4556
|
-
})();
|