@quanta-intellect/vessel-browser 0.1.45 → 0.1.46
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/out/main/index.js
CHANGED
|
@@ -32,12 +32,14 @@ const defaults = {
|
|
|
32
32
|
premium: {
|
|
33
33
|
status: "free",
|
|
34
34
|
customerId: "",
|
|
35
|
+
verificationToken: "",
|
|
35
36
|
email: "",
|
|
36
37
|
validatedAt: "",
|
|
37
38
|
expiresAt: ""
|
|
38
39
|
}
|
|
39
40
|
};
|
|
40
41
|
const SAVE_DEBOUNCE_MS$3 = 150;
|
|
42
|
+
const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
|
|
41
43
|
const SETTABLE_KEYS = new Set(Object.keys(defaults));
|
|
42
44
|
let settings = null;
|
|
43
45
|
let settingsIssues = [];
|
|
@@ -52,6 +54,80 @@ function getUserDataPath() {
|
|
|
52
54
|
function getSettingsPath() {
|
|
53
55
|
return path.join(getUserDataPath(), "vessel-settings.json");
|
|
54
56
|
}
|
|
57
|
+
function getChatProviderSecretPath() {
|
|
58
|
+
return path.join(getUserDataPath(), CHAT_PROVIDER_SECRET_FILENAME);
|
|
59
|
+
}
|
|
60
|
+
function canUseSafeStorage() {
|
|
61
|
+
try {
|
|
62
|
+
return electron.safeStorage.isEncryptionAvailable();
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function readStoredProviderSecret() {
|
|
68
|
+
try {
|
|
69
|
+
const raw = fs.readFileSync(getChatProviderSecretPath());
|
|
70
|
+
const decoded = canUseSafeStorage() && electron.safeStorage.decryptString ? electron.safeStorage.decryptString(raw) : raw.toString("utf-8");
|
|
71
|
+
const parsed = JSON.parse(decoded);
|
|
72
|
+
if (parsed && typeof parsed === "object" && typeof parsed.providerId === "string" && typeof parsed.apiKey === "string") {
|
|
73
|
+
return parsed;
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function writeStoredProviderSecret(secret) {
|
|
80
|
+
const filePath = getChatProviderSecretPath();
|
|
81
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
82
|
+
const payload = JSON.stringify(secret);
|
|
83
|
+
if (canUseSafeStorage()) {
|
|
84
|
+
const encrypted = electron.safeStorage.encryptString(payload);
|
|
85
|
+
fs.writeFileSync(filePath, encrypted, { mode: 384 });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
fs.writeFileSync(filePath, payload, { mode: 384 });
|
|
89
|
+
}
|
|
90
|
+
function clearStoredProviderSecret() {
|
|
91
|
+
try {
|
|
92
|
+
fs.unlinkSync(getChatProviderSecretPath());
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function mergeChatProviderSecret(provider) {
|
|
97
|
+
if (!provider) return null;
|
|
98
|
+
const stored = readStoredProviderSecret();
|
|
99
|
+
const legacyApiKey = provider.apiKey?.trim() || "";
|
|
100
|
+
const apiKey = stored?.providerId === provider.id ? stored.apiKey : legacyApiKey;
|
|
101
|
+
if (legacyApiKey && stored?.providerId !== provider.id) {
|
|
102
|
+
writeStoredProviderSecret({ providerId: provider.id, apiKey: legacyApiKey });
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
...provider,
|
|
106
|
+
apiKey,
|
|
107
|
+
hasApiKey: Boolean(apiKey)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function buildPersistedSettings(source) {
|
|
111
|
+
return {
|
|
112
|
+
...source,
|
|
113
|
+
chatProvider: source.chatProvider ? {
|
|
114
|
+
...source.chatProvider,
|
|
115
|
+
apiKey: "",
|
|
116
|
+
hasApiKey: source.chatProvider.hasApiKey || Boolean(source.chatProvider.apiKey)
|
|
117
|
+
} : null
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function getRendererSettings() {
|
|
121
|
+
const current = loadSettings();
|
|
122
|
+
return {
|
|
123
|
+
...current,
|
|
124
|
+
chatProvider: current.chatProvider ? {
|
|
125
|
+
...current.chatProvider,
|
|
126
|
+
apiKey: "",
|
|
127
|
+
hasApiKey: Boolean(current.chatProvider.apiKey)
|
|
128
|
+
} : null
|
|
129
|
+
};
|
|
130
|
+
}
|
|
55
131
|
function getSettingsLoadIssues() {
|
|
56
132
|
return settingsIssues.map((issue) => ({ ...issue }));
|
|
57
133
|
}
|
|
@@ -80,6 +156,7 @@ function loadSettings() {
|
|
|
80
156
|
settings = {
|
|
81
157
|
...defaults,
|
|
82
158
|
...parsed,
|
|
159
|
+
chatProvider: mergeChatProviderSecret(parsed.chatProvider ?? null),
|
|
83
160
|
mcpPort: sanitizePort(parsed.mcpPort ?? defaults.mcpPort),
|
|
84
161
|
agentTranscriptMode: parsed.agentTranscriptMode === "off" || parsed.agentTranscriptMode === "summary" || parsed.agentTranscriptMode === "full" ? parsed.agentTranscriptMode : parsed.showAgentTranscript === false ? "off" : defaults.agentTranscriptMode
|
|
85
162
|
};
|
|
@@ -104,7 +181,10 @@ function persistNow$3() {
|
|
|
104
181
|
saveTimer$3 = null;
|
|
105
182
|
}
|
|
106
183
|
return fs.promises.mkdir(path.dirname(getSettingsPath()), { recursive: true }).then(
|
|
107
|
-
() => fs.promises.writeFile(
|
|
184
|
+
() => fs.promises.writeFile(
|
|
185
|
+
getSettingsPath(),
|
|
186
|
+
JSON.stringify(buildPersistedSettings(settings), null, 2)
|
|
187
|
+
)
|
|
108
188
|
).catch((err) => console.error("[Vessel] Failed to save settings:", err));
|
|
109
189
|
}
|
|
110
190
|
function saveSettings() {
|
|
@@ -121,6 +201,30 @@ function setSetting(key, value) {
|
|
|
121
201
|
loadSettings();
|
|
122
202
|
if (key === "mcpPort") {
|
|
123
203
|
settings.mcpPort = sanitizePort(value);
|
|
204
|
+
} else if (key === "chatProvider") {
|
|
205
|
+
const nextProvider = value;
|
|
206
|
+
if (!nextProvider) {
|
|
207
|
+
clearStoredProviderSecret();
|
|
208
|
+
settings.chatProvider = null;
|
|
209
|
+
} else {
|
|
210
|
+
const existingSecret = readStoredProviderSecret();
|
|
211
|
+
const incomingApiKey = nextProvider.apiKey.trim();
|
|
212
|
+
const preserveExisting = !incomingApiKey && nextProvider.hasApiKey === true && existingSecret?.providerId === nextProvider.id;
|
|
213
|
+
const resolvedApiKey = preserveExisting ? existingSecret?.apiKey || "" : incomingApiKey;
|
|
214
|
+
if (resolvedApiKey) {
|
|
215
|
+
writeStoredProviderSecret({
|
|
216
|
+
providerId: nextProvider.id,
|
|
217
|
+
apiKey: resolvedApiKey
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
clearStoredProviderSecret();
|
|
221
|
+
}
|
|
222
|
+
settings.chatProvider = {
|
|
223
|
+
...nextProvider,
|
|
224
|
+
apiKey: resolvedApiKey,
|
|
225
|
+
hasApiKey: Boolean(resolvedApiKey)
|
|
226
|
+
};
|
|
227
|
+
}
|
|
124
228
|
} else {
|
|
125
229
|
settings[key] = value;
|
|
126
230
|
}
|
|
@@ -160,7 +264,31 @@ function checkDomainPolicy(url) {
|
|
|
160
264
|
function matchesDomain(hostname, policyDomain) {
|
|
161
265
|
return hostname === policyDomain || hostname.endsWith("." + policyDomain);
|
|
162
266
|
}
|
|
267
|
+
const ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
268
|
+
function isSafeNavigationURL(url) {
|
|
269
|
+
try {
|
|
270
|
+
const parsed = new URL(url);
|
|
271
|
+
return ALLOWED_SCHEMES.has(parsed.protocol);
|
|
272
|
+
} catch {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function assertSafeURL(url) {
|
|
277
|
+
if (!isSafeNavigationURL(url)) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`Blocked navigation to disallowed URL scheme: ${url.slice(0, 80)}`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function assertPermittedNavigationURL(url) {
|
|
284
|
+
assertSafeURL(url);
|
|
285
|
+
const policyError = checkDomainPolicy(url);
|
|
286
|
+
if (policyError) {
|
|
287
|
+
throw new Error(policyError);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
163
290
|
const MAX_CUSTOM_HISTORY = 50;
|
|
291
|
+
const READER_MODE_DATA_URL_PREFIX = "data:text/html;charset=utf-8,";
|
|
164
292
|
class Tab {
|
|
165
293
|
id;
|
|
166
294
|
view;
|
|
@@ -181,6 +309,32 @@ class Tab {
|
|
|
181
309
|
urlForwardStack = [];
|
|
182
310
|
lastCommittedUrl = "";
|
|
183
311
|
navigatingViaHistory = false;
|
|
312
|
+
isReaderModeDataUrl(url) {
|
|
313
|
+
return this._state.isReaderMode && url.startsWith(READER_MODE_DATA_URL_PREFIX);
|
|
314
|
+
}
|
|
315
|
+
getNavigationBlockReason(url) {
|
|
316
|
+
if (!url) {
|
|
317
|
+
return "Blocked navigation to empty URL";
|
|
318
|
+
}
|
|
319
|
+
if (url.startsWith("about:") || this.isReaderModeDataUrl(url)) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
assertSafeURL(url);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return error instanceof Error ? error.message : "Blocked unsafe navigation";
|
|
326
|
+
}
|
|
327
|
+
return checkDomainPolicy(url);
|
|
328
|
+
}
|
|
329
|
+
guardedLoadURL(url, options) {
|
|
330
|
+
const blockReason = this.getNavigationBlockReason(url);
|
|
331
|
+
if (blockReason) {
|
|
332
|
+
console.warn(`[Tab] ${blockReason}`);
|
|
333
|
+
return blockReason;
|
|
334
|
+
}
|
|
335
|
+
void this.view.webContents.loadURL(url, options);
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
184
338
|
constructor(id, url, onChange, options) {
|
|
185
339
|
this.id = id;
|
|
186
340
|
this.parentWindow = options?.parentWindow;
|
|
@@ -198,10 +352,11 @@ class Tab {
|
|
|
198
352
|
nodeIntegration: false
|
|
199
353
|
}
|
|
200
354
|
});
|
|
355
|
+
const initialUrl = url || "about:blank";
|
|
201
356
|
this._state = {
|
|
202
357
|
id,
|
|
203
358
|
title: "New Tab",
|
|
204
|
-
url:
|
|
359
|
+
url: initialUrl,
|
|
205
360
|
favicon: "",
|
|
206
361
|
isLoading: false,
|
|
207
362
|
canGoBack: false,
|
|
@@ -222,13 +377,22 @@ class Tab {
|
|
|
222
377
|
});
|
|
223
378
|
this.setupListeners();
|
|
224
379
|
if (url) {
|
|
225
|
-
|
|
226
|
-
|
|
380
|
+
const error = this.guardedLoadURL(url);
|
|
381
|
+
if (error) {
|
|
382
|
+
this._state.url = "about:blank";
|
|
383
|
+
} else {
|
|
384
|
+
this.lastCommittedUrl = url;
|
|
385
|
+
}
|
|
227
386
|
}
|
|
228
387
|
}
|
|
229
388
|
setupListeners() {
|
|
230
389
|
const wc = this.view.webContents;
|
|
231
390
|
wc.setWindowOpenHandler(({ url, disposition }) => {
|
|
391
|
+
const error = this.getNavigationBlockReason(url);
|
|
392
|
+
if (error) {
|
|
393
|
+
console.warn(`[Tab] ${error}`);
|
|
394
|
+
return { action: "deny" };
|
|
395
|
+
}
|
|
232
396
|
this.onOpenUrl?.({
|
|
233
397
|
url,
|
|
234
398
|
background: disposition === "background-tab",
|
|
@@ -236,6 +400,18 @@ class Tab {
|
|
|
236
400
|
});
|
|
237
401
|
return { action: "deny" };
|
|
238
402
|
});
|
|
403
|
+
const blockNavigation = (event, url, context) => {
|
|
404
|
+
const error = this.getNavigationBlockReason(url);
|
|
405
|
+
if (!error) return;
|
|
406
|
+
event.preventDefault();
|
|
407
|
+
console.warn(`[Tab] ${context}: ${error}`);
|
|
408
|
+
};
|
|
409
|
+
wc.on("will-navigate", (event, url) => {
|
|
410
|
+
blockNavigation(event, url, "Blocked top-level navigation");
|
|
411
|
+
});
|
|
412
|
+
wc.on("will-redirect", (event, url) => {
|
|
413
|
+
blockNavigation(event, url, "Blocked redirect");
|
|
414
|
+
});
|
|
239
415
|
const syncNavigationState = () => {
|
|
240
416
|
this._state.title = wc.getTitle() || this._state.title || "New Tab";
|
|
241
417
|
this._state.url = wc.getURL() || this._state.url;
|
|
@@ -402,17 +578,12 @@ class Tab {
|
|
|
402
578
|
url = `https://duckduckgo.com/?q=${encodeURIComponent(url)}`;
|
|
403
579
|
}
|
|
404
580
|
}
|
|
405
|
-
if (!/^https?:\/\//i.test(url) && !url.startsWith("about:")) {
|
|
406
|
-
return `Blocked navigation to disallowed URL scheme: ${url.slice(0, 80)}`;
|
|
407
|
-
}
|
|
408
|
-
const policyError = checkDomainPolicy(url);
|
|
409
|
-
if (policyError) return policyError;
|
|
410
581
|
if (postBody) {
|
|
411
582
|
const params = new URLSearchParams();
|
|
412
583
|
for (const [key, value] of Object.entries(postBody)) {
|
|
413
584
|
params.set(key, value);
|
|
414
585
|
}
|
|
415
|
-
this.
|
|
586
|
+
return this.guardedLoadURL(url, {
|
|
416
587
|
method: "POST",
|
|
417
588
|
extraHeaders: "Content-Type: application/x-www-form-urlencoded\r\n",
|
|
418
589
|
postData: [
|
|
@@ -422,27 +593,39 @@ class Tab {
|
|
|
422
593
|
}
|
|
423
594
|
]
|
|
424
595
|
});
|
|
425
|
-
} else {
|
|
426
|
-
this.view.webContents.loadURL(url);
|
|
427
596
|
}
|
|
428
|
-
return
|
|
597
|
+
return this.guardedLoadURL(url);
|
|
429
598
|
}
|
|
430
599
|
goBack() {
|
|
431
600
|
const previousUrl = this.urlHistory.pop();
|
|
432
601
|
if (!previousUrl) return false;
|
|
602
|
+
const currentUrl = this.lastCommittedUrl;
|
|
433
603
|
this.navigatingViaHistory = true;
|
|
434
|
-
this.urlForwardStack.push(
|
|
604
|
+
this.urlForwardStack.push(currentUrl);
|
|
605
|
+
const error = this.guardedLoadURL(previousUrl);
|
|
606
|
+
if (error) {
|
|
607
|
+
this.navigatingViaHistory = false;
|
|
608
|
+
this.urlForwardStack.pop();
|
|
609
|
+
this.urlHistory.push(previousUrl);
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
435
612
|
this.lastCommittedUrl = previousUrl;
|
|
436
|
-
this.view.webContents.loadURL(previousUrl);
|
|
437
613
|
return true;
|
|
438
614
|
}
|
|
439
615
|
goForward() {
|
|
440
616
|
const nextUrl = this.urlForwardStack.pop();
|
|
441
617
|
if (!nextUrl) return false;
|
|
618
|
+
const currentUrl = this.lastCommittedUrl;
|
|
442
619
|
this.navigatingViaHistory = true;
|
|
443
|
-
this.urlHistory.push(
|
|
620
|
+
this.urlHistory.push(currentUrl);
|
|
621
|
+
const error = this.guardedLoadURL(nextUrl);
|
|
622
|
+
if (error) {
|
|
623
|
+
this.navigatingViaHistory = false;
|
|
624
|
+
this.urlHistory.pop();
|
|
625
|
+
this.urlForwardStack.push(nextUrl);
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
444
628
|
this.lastCommittedUrl = nextUrl;
|
|
445
|
-
this.view.webContents.loadURL(nextUrl);
|
|
446
629
|
return true;
|
|
447
630
|
}
|
|
448
631
|
canGoBack() {
|
|
@@ -2446,7 +2629,8 @@ const Channels = {
|
|
|
2446
2629
|
DOWNLOAD_DONE: "download:done",
|
|
2447
2630
|
// Premium
|
|
2448
2631
|
PREMIUM_GET_STATE: "premium:get-state",
|
|
2449
|
-
|
|
2632
|
+
PREMIUM_ACTIVATION_START: "premium:activation-start",
|
|
2633
|
+
PREMIUM_ACTIVATION_VERIFY: "premium:activation-verify",
|
|
2450
2634
|
PREMIUM_CHECKOUT: "premium:checkout",
|
|
2451
2635
|
PREMIUM_PORTAL: "premium:portal",
|
|
2452
2636
|
PREMIUM_RESET: "premium:reset",
|
|
@@ -3382,6 +3566,7 @@ function resetPremium() {
|
|
|
3382
3566
|
const fresh = {
|
|
3383
3567
|
status: "free",
|
|
3384
3568
|
customerId: "",
|
|
3569
|
+
verificationToken: "",
|
|
3385
3570
|
email: "",
|
|
3386
3571
|
validatedAt: "",
|
|
3387
3572
|
expiresAt: ""
|
|
@@ -3414,39 +3599,22 @@ async function getCheckoutUrl(email) {
|
|
|
3414
3599
|
}
|
|
3415
3600
|
}
|
|
3416
3601
|
async function getPortalUrl() {
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
}
|
|
3421
|
-
try {
|
|
3422
|
-
const res = await fetch(`${VERIFICATION_API}/portal`, {
|
|
3423
|
-
method: "POST",
|
|
3424
|
-
headers: { "Content-Type": "application/json" },
|
|
3425
|
-
body: JSON.stringify({ customerId: premium.customerId })
|
|
3426
|
-
});
|
|
3427
|
-
if (!res.ok) {
|
|
3428
|
-
return { ok: false, error: `HTTP ${res.status}` };
|
|
3429
|
-
}
|
|
3430
|
-
const { url } = await res.json();
|
|
3431
|
-
return { ok: true, url };
|
|
3432
|
-
} catch (err) {
|
|
3433
|
-
return {
|
|
3434
|
-
ok: false,
|
|
3435
|
-
error: err instanceof Error ? err.message : "Failed to get portal URL"
|
|
3436
|
-
};
|
|
3437
|
-
}
|
|
3602
|
+
return {
|
|
3603
|
+
ok: false,
|
|
3604
|
+
error: "Billing portal access is temporarily disabled while we harden the premium API. Use the Stripe billing link from your subscription email for now."
|
|
3605
|
+
};
|
|
3438
3606
|
}
|
|
3439
|
-
async function verifySubscription(
|
|
3607
|
+
async function verifySubscription(identifier) {
|
|
3440
3608
|
const current = loadSettings().premium;
|
|
3441
|
-
const
|
|
3442
|
-
if (!
|
|
3609
|
+
const verificationIdentifier = identifier || current.verificationToken || current.customerId;
|
|
3610
|
+
if (!verificationIdentifier) {
|
|
3443
3611
|
return current;
|
|
3444
3612
|
}
|
|
3445
3613
|
try {
|
|
3446
3614
|
const res = await fetch(`${VERIFICATION_API}/verify`, {
|
|
3447
3615
|
method: "POST",
|
|
3448
3616
|
headers: { "Content-Type": "application/json" },
|
|
3449
|
-
body: JSON.stringify({ identifier })
|
|
3617
|
+
body: JSON.stringify({ identifier: verificationIdentifier })
|
|
3450
3618
|
});
|
|
3451
3619
|
if (!res.ok) {
|
|
3452
3620
|
console.warn("[Vessel Premium] Verification API returned", res.status);
|
|
@@ -3455,8 +3623,9 @@ async function verifySubscription(emailOrCustomerId) {
|
|
|
3455
3623
|
const data = await res.json();
|
|
3456
3624
|
const updated = {
|
|
3457
3625
|
status: data.status,
|
|
3458
|
-
customerId: data.customerId,
|
|
3459
|
-
|
|
3626
|
+
customerId: data.customerId || current.customerId,
|
|
3627
|
+
verificationToken: data.verificationToken || verificationIdentifier,
|
|
3628
|
+
email: data.email || current.email,
|
|
3460
3629
|
validatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3461
3630
|
expiresAt: data.expiresAt
|
|
3462
3631
|
};
|
|
@@ -3467,34 +3636,104 @@ async function verifySubscription(emailOrCustomerId) {
|
|
|
3467
3636
|
return current;
|
|
3468
3637
|
}
|
|
3469
3638
|
}
|
|
3470
|
-
async function
|
|
3471
|
-
|
|
3639
|
+
async function requestActivationCode(email) {
|
|
3640
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
3641
|
+
if (!normalizedEmail) {
|
|
3642
|
+
return { ok: false, error: "Email is required" };
|
|
3643
|
+
}
|
|
3644
|
+
try {
|
|
3645
|
+
const res = await fetch(`${VERIFICATION_API}/activate/start`, {
|
|
3646
|
+
method: "POST",
|
|
3647
|
+
headers: { "Content-Type": "application/json" },
|
|
3648
|
+
body: JSON.stringify({ email: normalizedEmail })
|
|
3649
|
+
});
|
|
3650
|
+
const data = await res.json().catch(() => ({}));
|
|
3651
|
+
if (!res.ok || !data.challengeToken) {
|
|
3652
|
+
return {
|
|
3653
|
+
ok: false,
|
|
3654
|
+
error: data.error || `HTTP ${res.status}`
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
return {
|
|
3658
|
+
ok: true,
|
|
3659
|
+
email: normalizedEmail,
|
|
3660
|
+
challengeToken: data.challengeToken
|
|
3661
|
+
};
|
|
3662
|
+
} catch (err) {
|
|
3663
|
+
return {
|
|
3664
|
+
ok: false,
|
|
3665
|
+
error: err instanceof Error ? err.message : "Failed to send code"
|
|
3666
|
+
};
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
async function verifyActivationCode(email, code, challengeToken) {
|
|
3670
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
3671
|
+
const trimmedCode = code.trim();
|
|
3672
|
+
if (!normalizedEmail) {
|
|
3472
3673
|
return { ok: false, state: getPremiumState(), error: "Email is required" };
|
|
3473
3674
|
}
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3675
|
+
if (!trimmedCode) {
|
|
3676
|
+
return { ok: false, state: getPremiumState(), error: "Code is required" };
|
|
3677
|
+
}
|
|
3678
|
+
if (!challengeToken.trim()) {
|
|
3679
|
+
return {
|
|
3680
|
+
ok: false,
|
|
3681
|
+
state: getPremiumState(),
|
|
3682
|
+
error: "Request a new activation code and try again."
|
|
3683
|
+
};
|
|
3684
|
+
}
|
|
3685
|
+
try {
|
|
3686
|
+
const res = await fetch(`${VERIFICATION_API}/activate/verify`, {
|
|
3687
|
+
method: "POST",
|
|
3688
|
+
headers: { "Content-Type": "application/json" },
|
|
3689
|
+
body: JSON.stringify({
|
|
3690
|
+
email: normalizedEmail,
|
|
3691
|
+
code: trimmedCode,
|
|
3692
|
+
challengeToken: challengeToken.trim()
|
|
3693
|
+
})
|
|
3694
|
+
});
|
|
3695
|
+
const data = await res.json().catch(() => ({}));
|
|
3696
|
+
if (!res.ok) {
|
|
3697
|
+
return {
|
|
3698
|
+
ok: false,
|
|
3699
|
+
state: getPremiumState(),
|
|
3700
|
+
error: data.error || `HTTP ${res.status}`
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
const updated = {
|
|
3704
|
+
status: data.status ?? "free",
|
|
3705
|
+
customerId: data.customerId || "",
|
|
3706
|
+
verificationToken: data.verificationToken || "",
|
|
3707
|
+
email: data.email || normalizedEmail,
|
|
3708
|
+
validatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3709
|
+
expiresAt: data.expiresAt || ""
|
|
3710
|
+
};
|
|
3711
|
+
setSetting("premium", updated);
|
|
3712
|
+
return { ok: isPremiumActiveState(updated), state: updated };
|
|
3713
|
+
} catch (err) {
|
|
3714
|
+
return {
|
|
3715
|
+
ok: false,
|
|
3716
|
+
state: getPremiumState(),
|
|
3717
|
+
error: err instanceof Error ? err.message : "Failed to verify code"
|
|
3718
|
+
};
|
|
3477
3719
|
}
|
|
3478
|
-
return {
|
|
3479
|
-
ok: false,
|
|
3480
|
-
state: state2,
|
|
3481
|
-
error: state2.status === "canceled" ? "Subscription is canceled. Resubscribe to continue." : state2.status === "past_due" ? "Subscription payment is past due. Update your payment method." : "No active subscription found for this email."
|
|
3482
|
-
};
|
|
3483
3720
|
}
|
|
3484
3721
|
let revalidationTimer = null;
|
|
3485
3722
|
function startBackgroundRevalidation() {
|
|
3486
3723
|
if (revalidationTimer) return;
|
|
3487
3724
|
const { premium } = loadSettings();
|
|
3488
|
-
|
|
3725
|
+
const identifier = premium.verificationToken || premium.customerId;
|
|
3726
|
+
if (identifier) {
|
|
3489
3727
|
const lastValidated = premium.validatedAt ? new Date(premium.validatedAt).getTime() : 0;
|
|
3490
3728
|
if (Date.now() - lastValidated > REVALIDATION_INTERVAL_MS) {
|
|
3491
|
-
void verifySubscription();
|
|
3729
|
+
void verifySubscription(identifier);
|
|
3492
3730
|
}
|
|
3493
3731
|
}
|
|
3494
3732
|
revalidationTimer = setInterval(() => {
|
|
3495
3733
|
const { premium: p } = loadSettings();
|
|
3496
|
-
|
|
3497
|
-
|
|
3734
|
+
const currentIdentifier = p.verificationToken || p.customerId;
|
|
3735
|
+
if (currentIdentifier) {
|
|
3736
|
+
void verifySubscription(currentIdentifier);
|
|
3498
3737
|
}
|
|
3499
3738
|
}, REVALIDATION_INTERVAL_MS);
|
|
3500
3739
|
}
|
|
@@ -4185,6 +4424,20 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
4185
4424
|
return meta;
|
|
4186
4425
|
}
|
|
4187
4426
|
|
|
4427
|
+
function shouldExposeFieldValue(el) {
|
|
4428
|
+
if (!(el instanceof HTMLInputElement)) return false;
|
|
4429
|
+
var elType = (el.type || "").toLowerCase();
|
|
4430
|
+
if (elType !== "number") return false;
|
|
4431
|
+
var signals = [
|
|
4432
|
+
el.name,
|
|
4433
|
+
el.id,
|
|
4434
|
+
el.getAttribute("placeholder"),
|
|
4435
|
+
el.getAttribute("aria-label"),
|
|
4436
|
+
labelFor(el),
|
|
4437
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
4438
|
+
return /\b(qty|quantity|count|items?)\b/.test(signals);
|
|
4439
|
+
}
|
|
4440
|
+
|
|
4188
4441
|
function serializeInteractive(el, kind) {
|
|
4189
4442
|
var base = {
|
|
4190
4443
|
type: kind,
|
|
@@ -4219,7 +4472,6 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
4219
4472
|
return {
|
|
4220
4473
|
...base,
|
|
4221
4474
|
label: labelFor(el)?.slice(0, 100),
|
|
4222
|
-
value: text(el.value),
|
|
4223
4475
|
options: Array.from(el.options || []).map(function(option) { return { label: text(option.textContent || option.value) || option.value, value: option.value }; }).filter(function(o) { return o.label || o.value; }).slice(0, 25),
|
|
4224
4476
|
required: el.hasAttribute("required") || undefined,
|
|
4225
4477
|
...fieldMeta(el),
|
|
@@ -4231,7 +4483,6 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
4231
4483
|
...base,
|
|
4232
4484
|
label: labelFor(el)?.slice(0, 100),
|
|
4233
4485
|
placeholder: text(el.getAttribute("placeholder")),
|
|
4234
|
-
value: text(el.value),
|
|
4235
4486
|
required: el.hasAttribute("required") || undefined,
|
|
4236
4487
|
...fieldMeta(el),
|
|
4237
4488
|
};
|
|
@@ -4243,7 +4494,7 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
4243
4494
|
label: labelFor(el)?.slice(0, 100),
|
|
4244
4495
|
inputType: text(el.getAttribute("type")),
|
|
4245
4496
|
placeholder: text(el.getAttribute("placeholder")),
|
|
4246
|
-
value: (
|
|
4497
|
+
value: shouldExposeFieldValue(el) ? text(el.value) : undefined,
|
|
4247
4498
|
required: el.hasAttribute("required") || undefined,
|
|
4248
4499
|
...fieldMeta(el),
|
|
4249
4500
|
};
|
|
@@ -4499,7 +4750,7 @@ const SAFE_EXTRACTION_SCRIPT = String.raw`
|
|
|
4499
4750
|
label: labelFor(el)?.slice(0, 100),
|
|
4500
4751
|
inputType: text(el.getAttribute && el.getAttribute("type")),
|
|
4501
4752
|
placeholder: text(el.getAttribute && el.getAttribute("placeholder")),
|
|
4502
|
-
value:
|
|
4753
|
+
value: shouldExposeFieldValue(el) ? text(el.value) : undefined,
|
|
4503
4754
|
options: tag === "select"
|
|
4504
4755
|
? Array.from(el.options || []).map(function(option) { return { label: text(option.textContent || option.value) || option.value, value: option.value }; }).filter(function(o) { return o.label || o.value; }).slice(0, 25)
|
|
4505
4756
|
: undefined,
|
|
@@ -4752,11 +5003,13 @@ function normalizePageContent(value) {
|
|
|
4752
5003
|
function generateReaderHTML(page) {
|
|
4753
5004
|
const escapedTitle = escapeHtml(page.title);
|
|
4754
5005
|
const escapedByline = escapeHtml(page.byline);
|
|
5006
|
+
const renderedContent = renderReaderContent(page);
|
|
4755
5007
|
return `<!DOCTYPE html>
|
|
4756
5008
|
<html lang="en">
|
|
4757
5009
|
<head>
|
|
4758
5010
|
<meta charset="utf-8">
|
|
4759
5011
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5012
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'">
|
|
4760
5013
|
<title>${escapedTitle}</title>
|
|
4761
5014
|
<style>
|
|
4762
5015
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
@@ -4827,13 +5080,20 @@ function generateReaderHTML(page) {
|
|
|
4827
5080
|
<div class="reader-container">
|
|
4828
5081
|
<h1>${escapedTitle}</h1>
|
|
4829
5082
|
${escapedByline ? `<div class="byline">${escapedByline}</div>` : ""}
|
|
4830
|
-
<div class="reader-content">${
|
|
5083
|
+
<div class="reader-content">${renderedContent}</div>
|
|
4831
5084
|
</div>
|
|
4832
5085
|
</body>
|
|
4833
5086
|
</html>`;
|
|
4834
5087
|
}
|
|
5088
|
+
function renderReaderContent(page) {
|
|
5089
|
+
const source = (page.content || page.excerpt || "").trim();
|
|
5090
|
+
if (!source) {
|
|
5091
|
+
return "<p>No readable content was available for this page.</p>";
|
|
5092
|
+
}
|
|
5093
|
+
return source.split(/\n{2,}/).map((block) => block.trim()).filter(Boolean).map((block) => `<p>${escapeHtml(block).replace(/\n/g, "<br>")}</p>`).join("\n");
|
|
5094
|
+
}
|
|
4835
5095
|
function escapeHtml(str) {
|
|
4836
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
5096
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4837
5097
|
}
|
|
4838
5098
|
const mcpStatusChangeListeners = /* @__PURE__ */ new Set();
|
|
4839
5099
|
const runtimeHealthChangeListeners = /* @__PURE__ */ new Set();
|
|
@@ -4927,24 +5187,24 @@ function getVaultPath() {
|
|
|
4927
5187
|
function getKeyPath() {
|
|
4928
5188
|
return path$1.join(getVaultDir(), KEY_FILENAME);
|
|
4929
5189
|
}
|
|
5190
|
+
function assertVaultSecretStorageAvailable() {
|
|
5191
|
+
if (!electron.safeStorage.isEncryptionAvailable()) {
|
|
5192
|
+
throw new Error(
|
|
5193
|
+
"Agent Credential Vault requires OS-backed secret storage. Enable Keychain, DPAPI, or libsecret support and restart Vessel."
|
|
5194
|
+
);
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
4930
5197
|
function getOrCreateEncryptionKey() {
|
|
5198
|
+
assertVaultSecretStorageAvailable();
|
|
4931
5199
|
const keyPath = getKeyPath();
|
|
4932
5200
|
if (fs$1.existsSync(keyPath)) {
|
|
4933
5201
|
const encryptedKey = fs$1.readFileSync(keyPath);
|
|
4934
|
-
|
|
4935
|
-
return electron.safeStorage.decryptString ? Buffer.from(electron.safeStorage.decryptString(encryptedKey), "utf-8") : encryptedKey;
|
|
4936
|
-
}
|
|
4937
|
-
return encryptedKey;
|
|
5202
|
+
return Buffer.from(electron.safeStorage.decryptString(encryptedKey), "utf-8");
|
|
4938
5203
|
}
|
|
4939
5204
|
const key = crypto$2.randomBytes(32);
|
|
4940
5205
|
fs$1.mkdirSync(path$1.dirname(keyPath), { recursive: true });
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
fs$1.writeFileSync(keyPath, encrypted);
|
|
4944
|
-
} else {
|
|
4945
|
-
fs$1.writeFileSync(keyPath, key);
|
|
4946
|
-
fs$1.chmodSync(keyPath, 384);
|
|
4947
|
-
}
|
|
5206
|
+
const encrypted = electron.safeStorage.encryptString(key.toString("utf-8"));
|
|
5207
|
+
fs$1.writeFileSync(keyPath, encrypted, { mode: 384 });
|
|
4948
5208
|
return key;
|
|
4949
5209
|
}
|
|
4950
5210
|
function encrypt(plaintext) {
|
|
@@ -4985,8 +5245,9 @@ function loadVault() {
|
|
|
4985
5245
|
return cachedEntries;
|
|
4986
5246
|
} catch (err) {
|
|
4987
5247
|
console.error("[Vessel Vault] Failed to load vault:", err);
|
|
4988
|
-
|
|
4989
|
-
|
|
5248
|
+
throw new Error(
|
|
5249
|
+
"Could not unlock the Agent Credential Vault. Check that OS secret storage is available and that the stored vault key can be decrypted."
|
|
5250
|
+
);
|
|
4990
5251
|
}
|
|
4991
5252
|
}
|
|
4992
5253
|
function saveVault(entries) {
|
|
@@ -6728,6 +6989,10 @@ function limitItems(items, max = MAX_STRUCTURED_ITEMS) {
|
|
|
6728
6989
|
if (items.length <= max) return items;
|
|
6729
6990
|
return items.slice(0, max);
|
|
6730
6991
|
}
|
|
6992
|
+
function shouldRenderFieldValue(el) {
|
|
6993
|
+
const value = typeof el.value === "string" && el.value.trim() ? el.value.trim() : "";
|
|
6994
|
+
return Boolean(value) && isQuantityLike(el);
|
|
6995
|
+
}
|
|
6731
6996
|
function formatElementMeta(el) {
|
|
6732
6997
|
const meta = [];
|
|
6733
6998
|
if (el.context && el.context !== "content") {
|
|
@@ -6795,7 +7060,7 @@ function formatElementMeta(el) {
|
|
|
6795
7060
|
if (el.description) {
|
|
6796
7061
|
meta.push(`desc="${el.description.slice(0, 80)}"`);
|
|
6797
7062
|
}
|
|
6798
|
-
if (el
|
|
7063
|
+
if (shouldRenderFieldValue(el)) {
|
|
6799
7064
|
meta.push(`value="${el.value.slice(0, 60)}"`);
|
|
6800
7065
|
}
|
|
6801
7066
|
if (el.selector) {
|
|
@@ -6806,7 +7071,7 @@ function formatElementMeta(el) {
|
|
|
6806
7071
|
}
|
|
6807
7072
|
function summarizeElementValue(el) {
|
|
6808
7073
|
const value = typeof el.value === "string" && el.value.trim() ? el.value.trim() : "";
|
|
6809
|
-
if (!value) return null;
|
|
7074
|
+
if (!value || !shouldRenderFieldValue(el)) return null;
|
|
6810
7075
|
if (el.type === "select") {
|
|
6811
7076
|
return { label: "selected", value: value.slice(0, 60) };
|
|
6812
7077
|
}
|
|
@@ -10138,22 +10403,6 @@ function formatDeadLinkMessage(label, result) {
|
|
|
10138
10403
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
10139
10404
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
10140
10405
|
}
|
|
10141
|
-
const ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
10142
|
-
function isSafeNavigationURL(url) {
|
|
10143
|
-
try {
|
|
10144
|
-
const parsed = new URL(url);
|
|
10145
|
-
return ALLOWED_SCHEMES.has(parsed.protocol);
|
|
10146
|
-
} catch {
|
|
10147
|
-
return false;
|
|
10148
|
-
}
|
|
10149
|
-
}
|
|
10150
|
-
function assertSafeURL(url) {
|
|
10151
|
-
if (!isSafeNavigationURL(url)) {
|
|
10152
|
-
throw new Error(
|
|
10153
|
-
`Blocked navigation to disallowed URL scheme: ${url.slice(0, 80)}`
|
|
10154
|
-
);
|
|
10155
|
-
}
|
|
10156
|
-
}
|
|
10157
10406
|
async function captureScreenshot(wc) {
|
|
10158
10407
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
10159
10408
|
await new Promise((resolve) => setTimeout(resolve, 120 * (attempt + 1)));
|
|
@@ -10878,6 +11127,10 @@ function formatCompactToolResult(name, result) {
|
|
|
10878
11127
|
const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS = 1500;
|
|
10879
11128
|
const QUIET_NAVIGATION_WINDOW_MS = 1200;
|
|
10880
11129
|
const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
|
|
11130
|
+
async function loadPermittedUrl$1(wc, url) {
|
|
11131
|
+
assertPermittedNavigationURL(url);
|
|
11132
|
+
await wc.loadURL(url);
|
|
11133
|
+
}
|
|
10881
11134
|
function pageBusyError(action) {
|
|
10882
11135
|
return `Error: Page is still busy; ${action} timed out waiting for page scripts. Retry in a moment.`;
|
|
10883
11136
|
}
|
|
@@ -11987,8 +12240,7 @@ Go back and select a different product.`;
|
|
|
11987
12240
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
11988
12241
|
if (hrefMatch) {
|
|
11989
12242
|
try {
|
|
11990
|
-
|
|
11991
|
-
await wc.loadURL(hrefMatch[1]);
|
|
12243
|
+
await loadPermittedUrl$1(wc, hrefMatch[1]);
|
|
11992
12244
|
await waitForLoad$1(wc, 8e3);
|
|
11993
12245
|
const hrefUrl = wc.getURL();
|
|
11994
12246
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
@@ -12055,8 +12307,7 @@ Go back and select a different product.`;
|
|
|
12055
12307
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
12056
12308
|
if (hrefMatch) {
|
|
12057
12309
|
try {
|
|
12058
|
-
|
|
12059
|
-
await wc.loadURL(hrefMatch[1]);
|
|
12310
|
+
await loadPermittedUrl$1(wc, hrefMatch[1]);
|
|
12060
12311
|
await waitForLoad$1(wc, 8e3);
|
|
12061
12312
|
const hrefUrl = wc.getURL();
|
|
12062
12313
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
@@ -12151,8 +12402,7 @@ ${postActivationOverlayHint}`;
|
|
|
12151
12402
|
const validation = await validateLinkDestination(elInfo.href);
|
|
12152
12403
|
if (validation.status !== "dead") {
|
|
12153
12404
|
try {
|
|
12154
|
-
|
|
12155
|
-
await wc.loadURL(elInfo.href);
|
|
12405
|
+
await loadPermittedUrl$1(wc, elInfo.href);
|
|
12156
12406
|
await waitForLoad$1(wc, 8e3);
|
|
12157
12407
|
const hrefFallbackUrl = wc.getURL();
|
|
12158
12408
|
if (hrefFallbackUrl !== beforeUrl) {
|
|
@@ -13704,8 +13954,7 @@ async function submitForm$1(wc, args) {
|
|
|
13704
13954
|
if (formInfo.params) {
|
|
13705
13955
|
url.search = formInfo.params;
|
|
13706
13956
|
}
|
|
13707
|
-
|
|
13708
|
-
wc.loadURL(url.toString());
|
|
13957
|
+
await loadPermittedUrl$1(wc, url.toString());
|
|
13709
13958
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
13710
13959
|
const afterUrl = wc.getURL();
|
|
13711
13960
|
return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
|
|
@@ -14342,8 +14591,7 @@ async function searchPage(wc, args) {
|
|
|
14342
14591
|
const shortcut = buildSearchShortcut(wc.getURL(), query);
|
|
14343
14592
|
if (shortcut) {
|
|
14344
14593
|
const beforeUrl2 = wc.getURL();
|
|
14345
|
-
|
|
14346
|
-
wc.loadURL(shortcut.url);
|
|
14594
|
+
await loadPermittedUrl$1(wc, shortcut.url);
|
|
14347
14595
|
await waitForPotentialNavigation$1(wc, beforeUrl2, 4e3);
|
|
14348
14596
|
const afterUrl2 = wc.getURL();
|
|
14349
14597
|
const applied = shortcut.appliedFilters.length > 0 ? ` (${shortcut.appliedFilters.join(", ")})` : "";
|
|
@@ -16775,6 +17023,10 @@ function clearMcpAuthFile() {
|
|
|
16775
17023
|
function asTextResponse(text) {
|
|
16776
17024
|
return { content: [{ type: "text", text }] };
|
|
16777
17025
|
}
|
|
17026
|
+
async function loadPermittedUrl(wc, url) {
|
|
17027
|
+
assertPermittedNavigationURL(url);
|
|
17028
|
+
await wc.loadURL(url);
|
|
17029
|
+
}
|
|
16778
17030
|
function asPromptResponse(text) {
|
|
16779
17031
|
return {
|
|
16780
17032
|
messages: [
|
|
@@ -17094,7 +17346,7 @@ async function clickResolvedSelector(wc, selector) {
|
|
|
17094
17346
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
17095
17347
|
if (hrefMatch) {
|
|
17096
17348
|
try {
|
|
17097
|
-
await wc
|
|
17349
|
+
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
17098
17350
|
await waitForLoad(wc, 8e3);
|
|
17099
17351
|
const hrefUrl = wc.getURL();
|
|
17100
17352
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
@@ -17147,7 +17399,7 @@ ${overlayHint2}${actionsSuffix}`;
|
|
|
17147
17399
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
17148
17400
|
if (hrefMatch) {
|
|
17149
17401
|
try {
|
|
17150
|
-
await wc
|
|
17402
|
+
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
17151
17403
|
await waitForLoad(wc, 8e3);
|
|
17152
17404
|
const hrefUrl = wc.getURL();
|
|
17153
17405
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
@@ -18058,8 +18310,7 @@ async function submitForm(wc, index, selector) {
|
|
|
18058
18310
|
if (formInfo.params) {
|
|
18059
18311
|
url.search = formInfo.params;
|
|
18060
18312
|
}
|
|
18061
|
-
|
|
18062
|
-
wc.loadURL(url.toString());
|
|
18313
|
+
await loadPermittedUrl(wc, url.toString());
|
|
18063
18314
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
18064
18315
|
const afterUrl = wc.getURL();
|
|
18065
18316
|
return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
|
|
@@ -22211,7 +22462,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22211
22462
|
return windowState.uiState.settingsOpen;
|
|
22212
22463
|
});
|
|
22213
22464
|
electron.ipcMain.handle(Channels.SETTINGS_GET, () => {
|
|
22214
|
-
return
|
|
22465
|
+
return getRendererSettings();
|
|
22215
22466
|
});
|
|
22216
22467
|
electron.ipcMain.handle(Channels.SETTINGS_HEALTH_GET, () => getRuntimeHealth());
|
|
22217
22468
|
electron.ipcMain.handle(Channels.SETTINGS_SET, async (_, key, value) => {
|
|
@@ -22229,8 +22480,9 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22229
22480
|
await stopMcpServer();
|
|
22230
22481
|
await startMcpServer(tabManager, runtime2, updatedSettings.mcpPort);
|
|
22231
22482
|
}
|
|
22232
|
-
|
|
22233
|
-
|
|
22483
|
+
const rendererSettings = getRendererSettings();
|
|
22484
|
+
sendToRendererViews(Channels.SETTINGS_UPDATE, rendererSettings);
|
|
22485
|
+
return rendererSettings;
|
|
22234
22486
|
});
|
|
22235
22487
|
electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, () => runtime2.getState());
|
|
22236
22488
|
electron.ipcMain.handle(Channels.AGENT_PAUSE, () => runtime2.pause());
|
|
@@ -22445,21 +22697,44 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22445
22697
|
electron.ipcMain.handle(Channels.PREMIUM_GET_STATE, () => {
|
|
22446
22698
|
return getPremiumState();
|
|
22447
22699
|
});
|
|
22448
|
-
electron.ipcMain.handle(Channels.
|
|
22700
|
+
electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (_, email) => {
|
|
22449
22701
|
assertString(email, "email");
|
|
22450
22702
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
|
|
22451
|
-
return { ok: false,
|
|
22703
|
+
return { ok: false, error: "Invalid email format" };
|
|
22452
22704
|
}
|
|
22453
22705
|
trackPremiumFunnel("activation_attempted");
|
|
22454
|
-
const result = await
|
|
22455
|
-
if (result.ok) {
|
|
22456
|
-
trackPremiumFunnel("
|
|
22457
|
-
sendToRendererViews(Channels.PREMIUM_UPDATE, result.state);
|
|
22458
|
-
} else {
|
|
22459
|
-
trackPremiumFunnel("activation_failed", { status: result.state.status });
|
|
22706
|
+
const result = await requestActivationCode(email);
|
|
22707
|
+
if (!result.ok) {
|
|
22708
|
+
trackPremiumFunnel("activation_failed");
|
|
22460
22709
|
}
|
|
22461
22710
|
return result;
|
|
22462
22711
|
});
|
|
22712
|
+
electron.ipcMain.handle(
|
|
22713
|
+
Channels.PREMIUM_ACTIVATION_VERIFY,
|
|
22714
|
+
async (_, email, code, challengeToken) => {
|
|
22715
|
+
assertString(email, "email");
|
|
22716
|
+
assertString(code, "code");
|
|
22717
|
+
assertString(challengeToken, "challengeToken");
|
|
22718
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
|
|
22719
|
+
return {
|
|
22720
|
+
ok: false,
|
|
22721
|
+
state: getPremiumState(),
|
|
22722
|
+
error: "Invalid email format"
|
|
22723
|
+
};
|
|
22724
|
+
}
|
|
22725
|
+
trackPremiumFunnel("activation_attempted");
|
|
22726
|
+
const result = await verifyActivationCode(email, code, challengeToken);
|
|
22727
|
+
if (result.ok) {
|
|
22728
|
+
trackPremiumFunnel("activation_succeeded", {
|
|
22729
|
+
status: result.state.status
|
|
22730
|
+
});
|
|
22731
|
+
sendToRendererViews(Channels.PREMIUM_UPDATE, result.state);
|
|
22732
|
+
} else {
|
|
22733
|
+
trackPremiumFunnel("activation_failed", { status: result.state.status });
|
|
22734
|
+
}
|
|
22735
|
+
return result;
|
|
22736
|
+
}
|
|
22737
|
+
);
|
|
22463
22738
|
electron.ipcMain.handle(Channels.PREMIUM_CHECKOUT, async (_, email) => {
|
|
22464
22739
|
trackPremiumFunnel("checkout_clicked");
|
|
22465
22740
|
const result = await getCheckoutUrl(email);
|