@nextop-os/browser-node 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/chunk-XCKUTY7D.js +852 -0
- package/dist/chunk-XCKUTY7D.js.map +1 -0
- package/dist/electron-main/index.d.ts +17 -31
- package/dist/electron-main/index.js +528 -18
- package/dist/electron-main/index.js.map +1 -1
- package/dist/index.d.ts +110 -3
- package/dist/react/index.d.ts +6 -16
- package/dist/react/index.js +3 -5
- package/dist/workbench/index.d.ts +0 -1
- package/dist/workbench/index.js +1 -2
- package/dist/workbench/index.js.map +1 -1
- package/package.json +7 -4
- package/dist/chunk-VQTJWLWO.js +0 -551
- package/dist/chunk-VQTJWLWO.js.map +0 -1
- package/dist/types-4cyQPaaT.d.ts +0 -110
|
@@ -3,15 +3,460 @@ import {
|
|
|
3
3
|
resolveBrowserNavigationUrl
|
|
4
4
|
} from "../chunk-2ZAMKGGP.js";
|
|
5
5
|
import {
|
|
6
|
-
isBrowserSessionPartitionAllowed
|
|
6
|
+
isBrowserSessionPartitionAllowed,
|
|
7
|
+
resolveBrowserSessionPartition
|
|
7
8
|
} from "../chunk-OTK5YBCK.js";
|
|
8
9
|
|
|
9
|
-
// src/electron-main/
|
|
10
|
-
|
|
10
|
+
// src/electron-main/loopbackPreviewProxy.ts
|
|
11
|
+
import {
|
|
12
|
+
createServer,
|
|
13
|
+
request as httpRequest
|
|
14
|
+
} from "http";
|
|
15
|
+
import { request as httpsRequest } from "https";
|
|
16
|
+
import { createRequire } from "module";
|
|
17
|
+
import { pipeline } from "stream";
|
|
18
|
+
var require2 = createRequire(import.meta.url);
|
|
19
|
+
var wsModule = require2("ws");
|
|
20
|
+
var WebSocket = wsModule;
|
|
21
|
+
var { WebSocketServer } = wsModule;
|
|
22
|
+
var defaultLoopbackPreviewCacheTtlMs = 3e4;
|
|
23
|
+
var loopbackHostPattern = /^(localhost|127(?:\.\d{1,3}){0,3})$/i;
|
|
24
|
+
var hopByHopHeaders = /* @__PURE__ */ new Set([
|
|
25
|
+
"connection",
|
|
26
|
+
"keep-alive",
|
|
27
|
+
"proxy-authenticate",
|
|
28
|
+
"proxy-authorization",
|
|
29
|
+
"proxy-connection",
|
|
30
|
+
"te",
|
|
31
|
+
"trailer",
|
|
32
|
+
"transfer-encoding",
|
|
33
|
+
"upgrade"
|
|
34
|
+
]);
|
|
35
|
+
function createBrowserNodeLoopbackPreviewProxy({
|
|
36
|
+
logger,
|
|
37
|
+
resolveSession,
|
|
38
|
+
routing
|
|
39
|
+
}) {
|
|
40
|
+
const configuredSessions = /* @__PURE__ */ new WeakSet();
|
|
41
|
+
const targetCache = /* @__PURE__ */ new Map();
|
|
42
|
+
const downstreamWebSocketServer = new WebSocketServer({ noServer: true });
|
|
43
|
+
let server = null;
|
|
44
|
+
let serverStartPromise = null;
|
|
45
|
+
const cacheTtlMs = routing.cacheTtlMs ?? defaultLoopbackPreviewCacheTtlMs;
|
|
46
|
+
const fallback = routing.fallback ?? "direct";
|
|
47
|
+
const pruneExpiredTargets = (now) => {
|
|
48
|
+
for (const [cacheKey, cachedTarget] of targetCache.entries()) {
|
|
49
|
+
if (cachedTarget.expiresAt <= now) {
|
|
50
|
+
targetCache.delete(cacheKey);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const start = async () => {
|
|
55
|
+
if (server?.listening) {
|
|
56
|
+
return addressForTesting();
|
|
57
|
+
}
|
|
58
|
+
if (serverStartPromise) {
|
|
59
|
+
return serverStartPromise;
|
|
60
|
+
}
|
|
61
|
+
serverStartPromise = new Promise((resolve, reject) => {
|
|
62
|
+
const nextServer = createServer((request, response) => {
|
|
63
|
+
void handleRequest(request, response);
|
|
64
|
+
});
|
|
65
|
+
nextServer.on("upgrade", (request, socket, head) => {
|
|
66
|
+
void handleUpgrade(request, socket, head);
|
|
67
|
+
});
|
|
68
|
+
const cleanup = () => {
|
|
69
|
+
nextServer.removeListener("error", onError);
|
|
70
|
+
nextServer.removeListener("listening", onListening);
|
|
71
|
+
};
|
|
72
|
+
const onError = (error) => {
|
|
73
|
+
cleanup();
|
|
74
|
+
reject(error);
|
|
75
|
+
};
|
|
76
|
+
const onListening = () => {
|
|
77
|
+
cleanup();
|
|
78
|
+
server = nextServer;
|
|
79
|
+
resolve(addressForTesting());
|
|
80
|
+
};
|
|
81
|
+
nextServer.once("error", onError);
|
|
82
|
+
nextServer.once("listening", onListening);
|
|
83
|
+
nextServer.listen(0, "127.0.0.1");
|
|
84
|
+
}).finally(() => {
|
|
85
|
+
serverStartPromise = null;
|
|
86
|
+
});
|
|
87
|
+
return serverStartPromise;
|
|
88
|
+
};
|
|
89
|
+
const resolveLoopbackTarget = async (originalUrl) => {
|
|
90
|
+
const port = Number(originalUrl.port);
|
|
91
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
pruneExpiredTargets(now);
|
|
96
|
+
const cacheKey = originalUrl.toString();
|
|
97
|
+
const cached = targetCache.get(cacheKey);
|
|
98
|
+
if (cached && cached.expiresAt > now) {
|
|
99
|
+
return cached.target;
|
|
100
|
+
}
|
|
101
|
+
const nextTarget = await Promise.resolve(
|
|
102
|
+
routing.resolver.resolveTarget({
|
|
103
|
+
port,
|
|
104
|
+
url: originalUrl.toString()
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
const target = normalizeLoopbackPreviewTarget(nextTarget);
|
|
108
|
+
targetCache.set(cacheKey, {
|
|
109
|
+
expiresAt: now + Math.max(0, cacheTtlMs),
|
|
110
|
+
target
|
|
111
|
+
});
|
|
112
|
+
return target;
|
|
113
|
+
};
|
|
114
|
+
const configureSession = async (input) => {
|
|
115
|
+
const nextSession = await resolveSession(input);
|
|
116
|
+
if (configuredSessions.has(nextSession)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const address = await start();
|
|
120
|
+
await nextSession.setProxy({
|
|
121
|
+
mode: "fixed_servers",
|
|
122
|
+
proxyBypassRules: "<-loopback>",
|
|
123
|
+
proxyRules: `http=${address};https=${address};ws=${address}`
|
|
124
|
+
});
|
|
125
|
+
configuredSessions.add(nextSession);
|
|
126
|
+
};
|
|
127
|
+
const dispose = async () => {
|
|
128
|
+
targetCache.clear();
|
|
129
|
+
downstreamWebSocketServer.close();
|
|
130
|
+
const activeServer = server;
|
|
131
|
+
server = null;
|
|
132
|
+
if (!activeServer || !activeServer.listening) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
await new Promise((resolve) => {
|
|
136
|
+
activeServer.close(() => resolve());
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
const handleRequest = async (request, response) => {
|
|
140
|
+
const originalUrl = resolveProxyRequestUrl(request);
|
|
141
|
+
if (!originalUrl) {
|
|
142
|
+
response.writeHead(400);
|
|
143
|
+
response.end("invalid proxy request");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (originalUrl.protocol !== "http:" && originalUrl.protocol !== "https:") {
|
|
147
|
+
response.writeHead(400);
|
|
148
|
+
response.end("unsupported proxy request");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const targetContext = await resolveTargetRequestContext(originalUrl);
|
|
152
|
+
if (!targetContext.targetUrl) {
|
|
153
|
+
response.writeHead(502);
|
|
154
|
+
response.end("unable to resolve loopback preview target");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const headers = filterProxyHeaders(request.headers);
|
|
158
|
+
headers.set("host", targetContext.targetUrl.host);
|
|
159
|
+
const forward = targetContext.targetUrl.protocol === "https:" ? httpsRequest : httpRequest;
|
|
160
|
+
const upstream = forward(
|
|
161
|
+
targetContext.targetUrl,
|
|
162
|
+
{
|
|
163
|
+
headers: Object.fromEntries(headers.entries()),
|
|
164
|
+
method: request.method
|
|
165
|
+
},
|
|
166
|
+
(upstreamResponse) => {
|
|
167
|
+
const responseHeaders = filterProxyHeaders(upstreamResponse.headers);
|
|
168
|
+
const location = responseHeaders.get("location");
|
|
169
|
+
if (location && targetContext.loopbackTarget) {
|
|
170
|
+
const rewritten = rewriteLoopbackLocation({
|
|
171
|
+
location,
|
|
172
|
+
originalUrl,
|
|
173
|
+
targetUrl: targetContext.loopbackTarget.targetUrl
|
|
174
|
+
});
|
|
175
|
+
if (rewritten) {
|
|
176
|
+
responseHeaders.set("location", rewritten);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
response.writeHead(
|
|
180
|
+
upstreamResponse.statusCode ?? 502,
|
|
181
|
+
upstreamResponse.statusMessage,
|
|
182
|
+
Object.fromEntries(responseHeaders.entries())
|
|
183
|
+
);
|
|
184
|
+
pipeline(upstreamResponse, response, () => void 0);
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
upstream.on("error", (error) => {
|
|
188
|
+
logger?.warn?.("Browser Node loopback preview request failed", {
|
|
189
|
+
error: normalizeProxyError(error),
|
|
190
|
+
originalUrl: originalUrl.toString(),
|
|
191
|
+
targetUrl: targetContext.targetUrl?.toString() ?? null,
|
|
192
|
+
workspaceId: targetContext.loopbackTarget?.workspaceId ?? null
|
|
193
|
+
});
|
|
194
|
+
if (!response.headersSent) {
|
|
195
|
+
response.writeHead(502);
|
|
196
|
+
}
|
|
197
|
+
response.end(
|
|
198
|
+
`loopback preview upstream request failed: ${normalizeProxyError(error)}`
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
pipeline(request, upstream, () => void 0);
|
|
202
|
+
};
|
|
203
|
+
const handleUpgrade = async (request, socket, head) => {
|
|
204
|
+
const originalUrl = resolveProxyRequestUrl(request, "ws:");
|
|
205
|
+
if (!originalUrl) {
|
|
206
|
+
socket.destroy();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (originalUrl.protocol !== "ws:" && originalUrl.protocol !== "wss:") {
|
|
210
|
+
socket.destroy();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const targetContext = await resolveTargetRequestContext(originalUrl);
|
|
214
|
+
const targetUrl = targetContext.targetUrl;
|
|
215
|
+
if (!targetUrl) {
|
|
216
|
+
socket.destroy();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
downstreamWebSocketServer.handleUpgrade(
|
|
220
|
+
request,
|
|
221
|
+
socket,
|
|
222
|
+
head,
|
|
223
|
+
(downstream) => {
|
|
224
|
+
const headers = Object.fromEntries(
|
|
225
|
+
filterProxyHeaders(request.headers).entries()
|
|
226
|
+
);
|
|
227
|
+
headers.host = targetUrl.host;
|
|
228
|
+
const upstream = new WebSocket(targetUrl, { headers });
|
|
229
|
+
const pendingMessages = [];
|
|
230
|
+
downstream.on("message", (data, isBinary) => {
|
|
231
|
+
if (upstream.readyState === WebSocket.OPEN) {
|
|
232
|
+
upstream.send(data, { binary: isBinary });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
pendingMessages.push({ data, isBinary });
|
|
236
|
+
});
|
|
237
|
+
upstream.once("open", () => {
|
|
238
|
+
for (const nextMessage of pendingMessages.splice(0)) {
|
|
239
|
+
upstream.send(nextMessage.data, { binary: nextMessage.isBinary });
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
upstream.on("message", (data, isBinary) => {
|
|
243
|
+
if (downstream.readyState === WebSocket.OPEN) {
|
|
244
|
+
downstream.send(data, { binary: isBinary });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
upstream.once("close", (code, reason) => {
|
|
248
|
+
if (downstream.readyState === WebSocket.OPEN || downstream.readyState === WebSocket.CONNECTING) {
|
|
249
|
+
downstream.close(normalizeWebSocketCloseCode(code), reason);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
downstream.once("close", (code, reason) => {
|
|
253
|
+
if (upstream.readyState === WebSocket.OPEN || upstream.readyState === WebSocket.CONNECTING) {
|
|
254
|
+
upstream.close(normalizeWebSocketCloseCode(code), reason);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
upstream.once("error", (error) => {
|
|
258
|
+
logger?.warn?.("Browser Node loopback preview websocket failed", {
|
|
259
|
+
error: normalizeProxyError(error),
|
|
260
|
+
originalUrl: originalUrl.toString(),
|
|
261
|
+
targetUrl: targetContext.targetUrl?.toString() ?? null,
|
|
262
|
+
workspaceId: targetContext.loopbackTarget?.workspaceId ?? null
|
|
263
|
+
});
|
|
264
|
+
downstream.close(1011, "loopback preview websocket upstream failed");
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
const resolveTargetRequestContext = async (originalUrl) => {
|
|
270
|
+
if (!isLoopbackUrl(originalUrl)) {
|
|
271
|
+
return {
|
|
272
|
+
loopbackTarget: null,
|
|
273
|
+
targetUrl: cloneUrl(originalUrl)
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
const loopbackTarget = await resolveLoopbackTarget(originalUrl);
|
|
277
|
+
if (loopbackTarget) {
|
|
278
|
+
return {
|
|
279
|
+
loopbackTarget,
|
|
280
|
+
targetUrl: buildTargetRequestUrl(loopbackTarget.targetUrl, originalUrl)
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (fallback === "direct") {
|
|
284
|
+
return {
|
|
285
|
+
loopbackTarget: null,
|
|
286
|
+
targetUrl: cloneUrl(originalUrl)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
loopbackTarget: null,
|
|
291
|
+
targetUrl: null
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
const addressForTesting = () => {
|
|
295
|
+
const address = server?.address();
|
|
296
|
+
if (!address || typeof address === "string") {
|
|
297
|
+
throw new Error("Browser Node loopback preview proxy is not listening");
|
|
298
|
+
}
|
|
299
|
+
return `127.0.0.1:${address.port}`;
|
|
300
|
+
};
|
|
301
|
+
const serverForTesting = () => {
|
|
302
|
+
if (!server) {
|
|
303
|
+
throw new Error("Browser Node loopback preview proxy is not started");
|
|
304
|
+
}
|
|
305
|
+
return server;
|
|
306
|
+
};
|
|
11
307
|
return {
|
|
12
|
-
|
|
308
|
+
addressForTesting,
|
|
309
|
+
configureSession,
|
|
310
|
+
dispose,
|
|
311
|
+
serverForTesting
|
|
13
312
|
};
|
|
14
313
|
}
|
|
314
|
+
function normalizeLoopbackPreviewTarget(input) {
|
|
315
|
+
if (!input) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
const normalizedTargetUrl = input.targetUrl.trim();
|
|
319
|
+
if (normalizedTargetUrl.length === 0) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
const parsed = new URL(normalizedTargetUrl);
|
|
324
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
targetUrl: parsed.toString(),
|
|
329
|
+
workspaceId: input.workspaceId
|
|
330
|
+
};
|
|
331
|
+
} catch {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function resolveProxyRequestUrl(request, defaultProtocol = "http:") {
|
|
336
|
+
const rawUrl = request.url ?? "";
|
|
337
|
+
try {
|
|
338
|
+
if (/^[a-z][a-z\d+\-.]*:\/\//i.test(rawUrl)) {
|
|
339
|
+
return new URL(rawUrl);
|
|
340
|
+
}
|
|
341
|
+
const host = request.headers.host;
|
|
342
|
+
if (typeof host !== "string" || host.trim().length === 0) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const path = rawUrl.startsWith("/") ? rawUrl : `/${rawUrl}`;
|
|
346
|
+
return new URL(`${defaultProtocol}//${host}${path}`);
|
|
347
|
+
} catch {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function filterProxyHeaders(input) {
|
|
352
|
+
const output = new Headers();
|
|
353
|
+
const appendValue = (key, value) => {
|
|
354
|
+
if (hopByHopHeaders.has(key.toLowerCase())) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
output.append(key, value);
|
|
358
|
+
};
|
|
359
|
+
if (input instanceof Headers) {
|
|
360
|
+
input.forEach((value, key) => {
|
|
361
|
+
appendValue(key, value);
|
|
362
|
+
});
|
|
363
|
+
return output;
|
|
364
|
+
}
|
|
365
|
+
for (const [key, value] of Object.entries(input)) {
|
|
366
|
+
if (Array.isArray(value)) {
|
|
367
|
+
for (const nextValue of value) {
|
|
368
|
+
appendValue(key, nextValue);
|
|
369
|
+
}
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (typeof value === "string") {
|
|
373
|
+
appendValue(key, value);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return output;
|
|
377
|
+
}
|
|
378
|
+
function isLoopbackUrl(url) {
|
|
379
|
+
return (url.protocol === "http:" || url.protocol === "https:" || url.protocol === "ws:" || url.protocol === "wss:") && loopbackHostPattern.test(url.hostname) && url.port.length > 0;
|
|
380
|
+
}
|
|
381
|
+
function buildTargetRequestUrl(targetUrl, originalUrl) {
|
|
382
|
+
try {
|
|
383
|
+
const base = new URL(targetUrl);
|
|
384
|
+
const normalizedBasePath = normalizeBasePath(base.pathname);
|
|
385
|
+
const nextUrl = new URL(base.toString());
|
|
386
|
+
nextUrl.pathname = joinBasePath(normalizedBasePath, originalUrl.pathname);
|
|
387
|
+
nextUrl.search = originalUrl.search;
|
|
388
|
+
nextUrl.hash = "";
|
|
389
|
+
if (originalUrl.protocol === "ws:" || originalUrl.protocol === "wss:") {
|
|
390
|
+
nextUrl.protocol = nextUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
391
|
+
}
|
|
392
|
+
return nextUrl;
|
|
393
|
+
} catch {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function rewriteLoopbackLocation({
|
|
398
|
+
location,
|
|
399
|
+
originalUrl,
|
|
400
|
+
targetUrl
|
|
401
|
+
}) {
|
|
402
|
+
try {
|
|
403
|
+
const targetBase = new URL(targetUrl);
|
|
404
|
+
const resolvedLocation = new URL(location, targetBase);
|
|
405
|
+
if (resolvedLocation.origin !== targetBase.origin) {
|
|
406
|
+
return location;
|
|
407
|
+
}
|
|
408
|
+
const basePath = normalizeBasePath(targetBase.pathname);
|
|
409
|
+
const strippedPath = stripBasePath(basePath, resolvedLocation.pathname) ?? resolvedLocation.pathname;
|
|
410
|
+
const loopbackUrl = new URL(originalUrl.origin);
|
|
411
|
+
resolvedLocation.protocol = loopbackUrl.protocol;
|
|
412
|
+
resolvedLocation.host = loopbackUrl.host;
|
|
413
|
+
resolvedLocation.pathname = strippedPath;
|
|
414
|
+
resolvedLocation.username = "";
|
|
415
|
+
resolvedLocation.password = "";
|
|
416
|
+
return resolvedLocation.toString();
|
|
417
|
+
} catch {
|
|
418
|
+
return location;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function normalizeBasePath(pathname) {
|
|
422
|
+
const normalized = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
423
|
+
return normalized.endsWith("/") ? normalized : `${normalized}/`;
|
|
424
|
+
}
|
|
425
|
+
function joinBasePath(basePath, requestPath) {
|
|
426
|
+
const normalizedRequestPath = requestPath === "/" ? "" : requestPath.replace(/^\/+/, "");
|
|
427
|
+
return normalizedRequestPath.length > 0 ? `${basePath}${normalizedRequestPath}`.replace(/\/{2,}/g, "/") : basePath;
|
|
428
|
+
}
|
|
429
|
+
function stripBasePath(basePath, pathname) {
|
|
430
|
+
const normalizedPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
431
|
+
if (normalizedPath === basePath.slice(0, -1)) {
|
|
432
|
+
return "/";
|
|
433
|
+
}
|
|
434
|
+
if (!normalizedPath.startsWith(basePath)) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
const suffix = normalizedPath.slice(basePath.length);
|
|
438
|
+
return suffix.length > 0 ? `/${suffix}` : "/";
|
|
439
|
+
}
|
|
440
|
+
function cloneUrl(url) {
|
|
441
|
+
try {
|
|
442
|
+
return new URL(url.toString());
|
|
443
|
+
} catch {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function normalizeProxyError(error) {
|
|
448
|
+
if (error instanceof Error) {
|
|
449
|
+
const code = typeof error.code === "string" ? ` ${error.code}` : "";
|
|
450
|
+
return `${error.message}${code}`;
|
|
451
|
+
}
|
|
452
|
+
return String(error);
|
|
453
|
+
}
|
|
454
|
+
function normalizeWebSocketCloseCode(code) {
|
|
455
|
+
if (code === 1e3 || code >= 3e3 && code <= 4999) {
|
|
456
|
+
return code;
|
|
457
|
+
}
|
|
458
|
+
return 1e3;
|
|
459
|
+
}
|
|
15
460
|
|
|
16
461
|
// src/electron-main/guestManager.ts
|
|
17
462
|
var browserPreviewMaxWidth = 260;
|
|
@@ -54,15 +499,37 @@ function resizeBrowserPreviewImage(image) {
|
|
|
54
499
|
function isAbortedNavigationError(input) {
|
|
55
500
|
return input.errorCode === abortedNavigationErrorCode || input.errorDescription === "ERR_ABORTED";
|
|
56
501
|
}
|
|
502
|
+
async function applyPreferredColorSchemeToGuest(session, logger, syncPreferredColorScheme, scheme) {
|
|
503
|
+
const contents = session.contents;
|
|
504
|
+
if (!contents || contents.isDestroyed() || !syncPreferredColorScheme || scheme === null || session.appliedColorScheme === scheme) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
try {
|
|
508
|
+
await syncPreferredColorScheme(contents, scheme);
|
|
509
|
+
session.appliedColorScheme = scheme;
|
|
510
|
+
} catch (error) {
|
|
511
|
+
session.appliedColorScheme = null;
|
|
512
|
+
logger?.warn?.("Browser Node failed to sync guest color scheme", {
|
|
513
|
+
error: error instanceof Error ? error.message : String(error),
|
|
514
|
+
nodeId: session.nodeId,
|
|
515
|
+
scheme,
|
|
516
|
+
webContentsId: session.webContentsId
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
57
520
|
function createBrowserGuestManager({
|
|
58
521
|
emit,
|
|
522
|
+
getPreferredColorScheme,
|
|
59
523
|
logger,
|
|
60
524
|
openExternal,
|
|
61
|
-
|
|
62
|
-
resolveWebContents
|
|
525
|
+
prepareSession,
|
|
526
|
+
resolveWebContents,
|
|
527
|
+
syncPreferredColorScheme,
|
|
528
|
+
subscribePreferredColorScheme
|
|
63
529
|
}) {
|
|
64
530
|
const sessions = /* @__PURE__ */ new Map();
|
|
65
531
|
const nodeIdByWebContentsId = /* @__PURE__ */ new Map();
|
|
532
|
+
let preferredColorScheme = getPreferredColorScheme?.() ?? null;
|
|
66
533
|
const getSession = (nodeId, input) => {
|
|
67
534
|
const existing = sessions.get(nodeId);
|
|
68
535
|
if (existing) {
|
|
@@ -78,6 +545,7 @@ function createBrowserGuestManager({
|
|
|
78
545
|
return existing;
|
|
79
546
|
}
|
|
80
547
|
const session = {
|
|
548
|
+
appliedColorScheme: null,
|
|
81
549
|
contents: null,
|
|
82
550
|
desiredUrl: input?.url ?? "about:blank",
|
|
83
551
|
lifecycle: "cold",
|
|
@@ -115,12 +583,26 @@ function createBrowserGuestManager({
|
|
|
115
583
|
}
|
|
116
584
|
session.listeners = [];
|
|
117
585
|
session.contents = null;
|
|
586
|
+
session.appliedColorScheme = null;
|
|
118
587
|
session.webContentsId = null;
|
|
119
588
|
if (webContentsId !== null && nodeIdByWebContentsId.get(webContentsId) === session.nodeId) {
|
|
120
589
|
nodeIdByWebContentsId.delete(webContentsId);
|
|
121
590
|
}
|
|
122
591
|
publishState(session);
|
|
123
592
|
};
|
|
593
|
+
const handlePreferredColorSchemeChange = (scheme) => {
|
|
594
|
+
preferredColorScheme = scheme;
|
|
595
|
+
for (const session of sessions.values()) {
|
|
596
|
+
session.appliedColorScheme = null;
|
|
597
|
+
void applyPreferredColorSchemeToGuest(
|
|
598
|
+
session,
|
|
599
|
+
logger,
|
|
600
|
+
syncPreferredColorScheme,
|
|
601
|
+
scheme
|
|
602
|
+
).catch(() => void 0);
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
const unsubscribePreferredColorScheme = subscribePreferredColorScheme?.(handlePreferredColorSchemeChange) ?? null;
|
|
124
606
|
const attachGuestListeners = (session) => {
|
|
125
607
|
const contents = session.contents;
|
|
126
608
|
if (!contents) {
|
|
@@ -174,18 +656,13 @@ function createBrowserGuestManager({
|
|
|
174
656
|
publishState(session);
|
|
175
657
|
return;
|
|
176
658
|
}
|
|
177
|
-
const routes = await previewRouteResolver.resolveRoutes({
|
|
178
|
-
nodeId: session.nodeId,
|
|
179
|
-
url: resolved.url
|
|
180
|
-
});
|
|
181
|
-
const routedUrl = routes.find((route) => route.sourceUrl === resolved.url)?.targetUrl ?? resolved.url;
|
|
182
659
|
const currentComparable = normalizeBrowserComparableUrl(contents.getURL());
|
|
183
|
-
const nextComparable = normalizeBrowserComparableUrl(
|
|
660
|
+
const nextComparable = normalizeBrowserComparableUrl(resolved.url);
|
|
184
661
|
if (currentComparable && currentComparable === nextComparable) {
|
|
185
662
|
publishState(session);
|
|
186
663
|
return;
|
|
187
664
|
}
|
|
188
|
-
await contents.loadURL(
|
|
665
|
+
await contents.loadURL(resolved.url);
|
|
189
666
|
publishState(session);
|
|
190
667
|
};
|
|
191
668
|
const openExternalFromGuest = (url) => {
|
|
@@ -281,14 +758,19 @@ function createBrowserGuestManager({
|
|
|
281
758
|
session.lifecycle = "active";
|
|
282
759
|
await loadDesiredUrl(session);
|
|
283
760
|
},
|
|
284
|
-
prepareSession(input) {
|
|
761
|
+
async prepareSession(input) {
|
|
762
|
+
await prepareSession?.(input);
|
|
285
763
|
getSession(input.nodeId, {
|
|
286
764
|
profileId: input.profileId,
|
|
287
765
|
sessionMode: input.sessionMode
|
|
288
766
|
});
|
|
289
|
-
return Promise.resolve();
|
|
290
767
|
},
|
|
291
768
|
async registerGuest(input) {
|
|
769
|
+
await prepareSession?.({
|
|
770
|
+
nodeId: input.nodeId,
|
|
771
|
+
profileId: input.profileId,
|
|
772
|
+
sessionMode: input.sessionMode
|
|
773
|
+
});
|
|
292
774
|
const contents = resolveWebContents(input.webContentsId);
|
|
293
775
|
if (!contents || contents.isDestroyed()) {
|
|
294
776
|
throw new Error(
|
|
@@ -318,6 +800,12 @@ function createBrowserGuestManager({
|
|
|
318
800
|
session.lifecycle = "active";
|
|
319
801
|
contents.setWindowOpenHandler?.(({ url }) => openExternalFromGuest(url));
|
|
320
802
|
attachGuestListeners(session);
|
|
803
|
+
await applyPreferredColorSchemeToGuest(
|
|
804
|
+
session,
|
|
805
|
+
logger,
|
|
806
|
+
syncPreferredColorScheme,
|
|
807
|
+
preferredColorScheme
|
|
808
|
+
);
|
|
321
809
|
await loadDesiredUrl(session);
|
|
322
810
|
},
|
|
323
811
|
reload(input) {
|
|
@@ -335,6 +823,9 @@ function createBrowserGuestManager({
|
|
|
335
823
|
session.lifecycle = "cold";
|
|
336
824
|
detachGuest(session);
|
|
337
825
|
return Promise.resolve();
|
|
826
|
+
},
|
|
827
|
+
dispose() {
|
|
828
|
+
unsubscribePreferredColorScheme?.();
|
|
338
829
|
}
|
|
339
830
|
};
|
|
340
831
|
}
|
|
@@ -342,6 +833,19 @@ function createBrowserGuestManager({
|
|
|
342
833
|
// src/electron-main/registerElectronMain.ts
|
|
343
834
|
function registerBrowserNodeElectronMain(input) {
|
|
344
835
|
const managersByWindow = /* @__PURE__ */ new WeakMap();
|
|
836
|
+
const loopbackPreviewProxy = input.loopbackPreviewRouting !== void 0 ? createBrowserNodeLoopbackPreviewProxy({
|
|
837
|
+
logger: input.logger,
|
|
838
|
+
resolveSession: async ({ profileId, sessionMode }) => {
|
|
839
|
+
const { session } = await import("electron");
|
|
840
|
+
return session.fromPartition(
|
|
841
|
+
resolveBrowserSessionPartition({
|
|
842
|
+
profileId,
|
|
843
|
+
sessionMode
|
|
844
|
+
})
|
|
845
|
+
);
|
|
846
|
+
},
|
|
847
|
+
routing: input.loopbackPreviewRouting
|
|
848
|
+
}) : null;
|
|
345
849
|
const resolveManager = (event) => {
|
|
346
850
|
const ownerWindow = input.getOwnerWindow(event);
|
|
347
851
|
if (!ownerWindow) {
|
|
@@ -357,13 +861,21 @@ function registerBrowserNodeElectronMain(input) {
|
|
|
357
861
|
ownerWindow.webContents.send(input.channels.event, browserEvent);
|
|
358
862
|
}
|
|
359
863
|
},
|
|
864
|
+
getPreferredColorScheme: input.getPreferredColorScheme,
|
|
360
865
|
logger: input.logger,
|
|
361
866
|
openExternal: input.openExternal,
|
|
867
|
+
prepareSession: loopbackPreviewProxy !== null ? (payload) => loopbackPreviewProxy.configureSession(payload) : void 0,
|
|
362
868
|
resolveWebContents: (webContentsId) => input.resolveWebContents({
|
|
363
869
|
event,
|
|
364
870
|
ownerWindow,
|
|
365
871
|
webContentsId
|
|
366
|
-
})
|
|
872
|
+
}),
|
|
873
|
+
syncPreferredColorScheme: input.syncPreferredColorScheme,
|
|
874
|
+
subscribePreferredColorScheme: input.subscribePreferredColorScheme
|
|
875
|
+
});
|
|
876
|
+
ownerWindow.once("closed", () => {
|
|
877
|
+
manager.dispose();
|
|
878
|
+
managersByWindow.delete(ownerWindow);
|
|
367
879
|
});
|
|
368
880
|
managersByWindow.set(ownerWindow, manager);
|
|
369
881
|
return manager;
|
|
@@ -499,8 +1011,6 @@ function installBrowserWebviewSecurity({
|
|
|
499
1011
|
};
|
|
500
1012
|
}
|
|
501
1013
|
export {
|
|
502
|
-
createBrowserGuestManager,
|
|
503
|
-
createNoopBrowserNodePreviewRouteResolver,
|
|
504
1014
|
enforceBrowserWebviewSecurity,
|
|
505
1015
|
installBrowserWebviewSecurity,
|
|
506
1016
|
isBrowserNodeWebviewAttach,
|