@nextop-os/browser-node 0.0.8
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 +18 -0
- package/dist/assets/workspace-dock-website.png +0 -0
- package/dist/bridge/index.d.ts +15 -0
- package/dist/bridge/index.js +9 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/chunk-2ZAMKGGP.js +66 -0
- package/dist/chunk-2ZAMKGGP.js.map +1 -0
- package/dist/chunk-IO5AJ2R5.js +111 -0
- package/dist/chunk-IO5AJ2R5.js.map +1 -0
- package/dist/chunk-JRJTAIK5.js +83 -0
- package/dist/chunk-JRJTAIK5.js.map +1 -0
- package/dist/chunk-OTK5YBCK.js +27 -0
- package/dist/chunk-OTK5YBCK.js.map +1 -0
- package/dist/chunk-VQTJWLWO.js +551 -0
- package/dist/chunk-VQTJWLWO.js.map +1 -0
- package/dist/electron-main/index.d.ts +125 -0
- package/dist/electron-main/index.js +509 -0
- package/dist/electron-main/index.js.map +1 -0
- package/dist/electron-preload/index.d.ts +11 -0
- package/dist/electron-preload/index.js +29 -0
- package/dist/electron-preload/index.js.map +1 -0
- package/dist/i18n/index.d.ts +18 -0
- package/dist/i18n/index.js +13 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +169 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +46 -0
- package/dist/react/index.js +12 -0
- package/dist/react/index.js.map +1 -0
- package/dist/types-4cyQPaaT.d.ts +110 -0
- package/dist/types-Bmzz1Q84.d.ts +32 -0
- package/dist/workbench/index.d.ts +34 -0
- package/dist/workbench/index.js +132 -0
- package/dist/workbench/index.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { B as BrowserNodeActivationInput, j as BrowserNodeNodeIdInput, b as BrowserNodeDebugDump, i as BrowserNodeNavigateInput, l as BrowserNodePrepareSessionInput, m as BrowserNodeRegisterGuestInput, r as BrowserNodeUnregisterGuestInput, f as BrowserNodeEvent } from '../types-4cyQPaaT.js';
|
|
2
|
+
import { BrowserWindow, WebPreferences, WebContents } from 'electron';
|
|
3
|
+
|
|
4
|
+
interface BrowserNodePreviewRoute {
|
|
5
|
+
readonly sourceUrl: string;
|
|
6
|
+
readonly targetUrl: string;
|
|
7
|
+
}
|
|
8
|
+
interface BrowserNodePreviewRouteResolver {
|
|
9
|
+
resolveRoutes(input: {
|
|
10
|
+
nodeId: string;
|
|
11
|
+
url: string;
|
|
12
|
+
}): BrowserNodePreviewRoute[] | Promise<BrowserNodePreviewRoute[]>;
|
|
13
|
+
}
|
|
14
|
+
declare function createNoopBrowserNodePreviewRouteResolver(): BrowserNodePreviewRouteResolver;
|
|
15
|
+
|
|
16
|
+
interface BrowserGuestManager {
|
|
17
|
+
activate(input: BrowserNodeActivationInput): Promise<void>;
|
|
18
|
+
capturePreview(input: BrowserNodeNodeIdInput): Promise<string | null>;
|
|
19
|
+
close(input: BrowserNodeNodeIdInput): Promise<void>;
|
|
20
|
+
debugDump(input: BrowserNodeNodeIdInput): BrowserNodeDebugDump | null;
|
|
21
|
+
goBack(input: BrowserNodeNodeIdInput): Promise<void>;
|
|
22
|
+
goForward(input: BrowserNodeNodeIdInput): Promise<void>;
|
|
23
|
+
navigate(input: BrowserNodeNavigateInput): Promise<void>;
|
|
24
|
+
prepareSession(input: BrowserNodePrepareSessionInput): Promise<void>;
|
|
25
|
+
registerGuest(input: BrowserNodeRegisterGuestInput): Promise<void>;
|
|
26
|
+
reload(input: BrowserNodeNodeIdInput): Promise<void>;
|
|
27
|
+
unregisterGuest(input: BrowserNodeUnregisterGuestInput): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
interface BrowserGuestManagerInput {
|
|
30
|
+
emit: (event: BrowserNodeEvent) => void;
|
|
31
|
+
logger?: BrowserNodeElectronLogger;
|
|
32
|
+
openExternal: (url: string) => Promise<void> | void;
|
|
33
|
+
previewRouteResolver?: BrowserNodePreviewRouteResolver;
|
|
34
|
+
resolveWebContents: (webContentsId: number) => BrowserGuestWebContents | null;
|
|
35
|
+
}
|
|
36
|
+
interface BrowserNodeElectronLogger {
|
|
37
|
+
debug?(message: string, metadata?: Record<string, unknown>): void;
|
|
38
|
+
warn?(message: string, metadata?: Record<string, unknown>): void;
|
|
39
|
+
}
|
|
40
|
+
interface BrowserGuestWebContents {
|
|
41
|
+
readonly id?: number;
|
|
42
|
+
canGoBack(): boolean;
|
|
43
|
+
canGoForward(): boolean;
|
|
44
|
+
capturePage?(): Promise<BrowserGuestNativeImage>;
|
|
45
|
+
getTitle(): string;
|
|
46
|
+
getURL(): string;
|
|
47
|
+
goBack(): void;
|
|
48
|
+
goForward(): void;
|
|
49
|
+
isDestroyed(): boolean;
|
|
50
|
+
isLoading(): boolean;
|
|
51
|
+
loadURL(url: string): Promise<void>;
|
|
52
|
+
off(event: string, listener: (...args: unknown[]) => void): this;
|
|
53
|
+
on(event: string, listener: (...args: unknown[]) => void): this;
|
|
54
|
+
reload(): void;
|
|
55
|
+
setWindowOpenHandler?(handler: (details: {
|
|
56
|
+
url: string;
|
|
57
|
+
}) => {
|
|
58
|
+
action: "allow" | "deny";
|
|
59
|
+
}): void;
|
|
60
|
+
}
|
|
61
|
+
interface BrowserGuestNativeImage {
|
|
62
|
+
getSize?(): {
|
|
63
|
+
height: number;
|
|
64
|
+
width: number;
|
|
65
|
+
};
|
|
66
|
+
isEmpty?(): boolean;
|
|
67
|
+
resize?(options: {
|
|
68
|
+
height?: number;
|
|
69
|
+
quality?: "best" | "good" | "better" | "nearest";
|
|
70
|
+
width?: number;
|
|
71
|
+
}): BrowserGuestNativeImage;
|
|
72
|
+
toDataURL(): string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
declare function createBrowserGuestManager({ emit, logger, openExternal, previewRouteResolver, resolveWebContents }: BrowserGuestManagerInput): BrowserGuestManager;
|
|
76
|
+
|
|
77
|
+
interface BrowserNodeElectronMainChannels {
|
|
78
|
+
readonly activate: string;
|
|
79
|
+
readonly capturePreview?: string;
|
|
80
|
+
readonly close: string;
|
|
81
|
+
readonly debugDump?: string;
|
|
82
|
+
readonly event: string;
|
|
83
|
+
readonly goBack: string;
|
|
84
|
+
readonly goForward: string;
|
|
85
|
+
readonly navigate: string;
|
|
86
|
+
readonly prepareSession: string;
|
|
87
|
+
readonly registerGuest: string;
|
|
88
|
+
readonly reload: string;
|
|
89
|
+
readonly unregisterGuest: string;
|
|
90
|
+
}
|
|
91
|
+
interface RegisterBrowserNodeElectronMainInput {
|
|
92
|
+
readonly channels: BrowserNodeElectronMainChannels;
|
|
93
|
+
readonly getOwnerWindow: (event: unknown) => BrowserWindow | null;
|
|
94
|
+
readonly logger?: BrowserNodeElectronLogger;
|
|
95
|
+
readonly openExternal: (url: string) => Promise<void> | void;
|
|
96
|
+
readonly registerHandler: <TPayload, TResult>(channel: string, handler: (event: unknown, payload: TPayload) => Promise<TResult> | TResult) => void;
|
|
97
|
+
readonly resolveWebContents: (input: {
|
|
98
|
+
event: unknown;
|
|
99
|
+
ownerWindow: BrowserWindow;
|
|
100
|
+
webContentsId: number;
|
|
101
|
+
}) => BrowserGuestWebContents | null;
|
|
102
|
+
}
|
|
103
|
+
declare function registerBrowserNodeElectronMain(input: RegisterBrowserNodeElectronMainInput): void;
|
|
104
|
+
|
|
105
|
+
interface BrowserWebviewSecurityInput {
|
|
106
|
+
params: Record<string, string>;
|
|
107
|
+
webPreferences: WebPreferences;
|
|
108
|
+
}
|
|
109
|
+
interface BrowserWebviewSecurityResult {
|
|
110
|
+
allowed: boolean;
|
|
111
|
+
reason: string | null;
|
|
112
|
+
}
|
|
113
|
+
type BrowserNodeWebviewMatcher = (params: Record<string, string>) => boolean;
|
|
114
|
+
declare function isBrowserNodeWebviewAttach(params: Record<string, string>): boolean;
|
|
115
|
+
declare function enforceBrowserWebviewSecurity({ params, webPreferences }: BrowserWebviewSecurityInput): BrowserWebviewSecurityResult;
|
|
116
|
+
interface InstallBrowserWebviewSecurityInput {
|
|
117
|
+
contents: WebContents;
|
|
118
|
+
logger?: BrowserNodeElectronLogger;
|
|
119
|
+
onGuestAttached?: (guestContents: WebContents) => void;
|
|
120
|
+
openExternal: (url: string) => Promise<void> | void;
|
|
121
|
+
shouldHandleWebview?: BrowserNodeWebviewMatcher;
|
|
122
|
+
}
|
|
123
|
+
declare function installBrowserWebviewSecurity({ contents, logger, onGuestAttached, openExternal, shouldHandleWebview }: InstallBrowserWebviewSecurityInput): () => void;
|
|
124
|
+
|
|
125
|
+
export { type BrowserGuestManager, type BrowserGuestManagerInput, type BrowserGuestNativeImage, type BrowserGuestWebContents, type BrowserNodeElectronLogger, type BrowserNodeElectronMainChannels, type BrowserNodePreviewRoute, type BrowserNodePreviewRouteResolver, type BrowserNodeWebviewMatcher, type BrowserWebviewSecurityInput, type BrowserWebviewSecurityResult, type InstallBrowserWebviewSecurityInput, type RegisterBrowserNodeElectronMainInput, createBrowserGuestManager, createNoopBrowserNodePreviewRouteResolver, enforceBrowserWebviewSecurity, installBrowserWebviewSecurity, isBrowserNodeWebviewAttach, registerBrowserNodeElectronMain };
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeBrowserComparableUrl,
|
|
3
|
+
resolveBrowserNavigationUrl
|
|
4
|
+
} from "../chunk-2ZAMKGGP.js";
|
|
5
|
+
import {
|
|
6
|
+
isBrowserSessionPartitionAllowed
|
|
7
|
+
} from "../chunk-OTK5YBCK.js";
|
|
8
|
+
|
|
9
|
+
// src/electron-main/previewRoute.ts
|
|
10
|
+
function createNoopBrowserNodePreviewRouteResolver() {
|
|
11
|
+
return {
|
|
12
|
+
resolveRoutes: () => []
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/electron-main/guestManager.ts
|
|
17
|
+
var browserPreviewMaxWidth = 260;
|
|
18
|
+
var browserPreviewMaxHeight = 170;
|
|
19
|
+
var abortedNavigationErrorCode = -3;
|
|
20
|
+
function resolveBrowserNodeUrlError(resolved) {
|
|
21
|
+
if (resolved.errorCode === "invalid-url") {
|
|
22
|
+
return { code: "invalid-url" };
|
|
23
|
+
}
|
|
24
|
+
if (resolved.errorCode === "unsupported-protocol") {
|
|
25
|
+
return {
|
|
26
|
+
code: "unsupported-protocol",
|
|
27
|
+
params: resolved.errorParams
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return { code: "unsupported-url" };
|
|
31
|
+
}
|
|
32
|
+
function resizeBrowserPreviewImage(image) {
|
|
33
|
+
if (image.isEmpty?.() === true || !image.resize || !image.getSize) {
|
|
34
|
+
return image;
|
|
35
|
+
}
|
|
36
|
+
const size = image.getSize();
|
|
37
|
+
if (size.width <= 0 || size.height <= 0) {
|
|
38
|
+
return image;
|
|
39
|
+
}
|
|
40
|
+
const scale = Math.min(
|
|
41
|
+
1,
|
|
42
|
+
browserPreviewMaxWidth / size.width,
|
|
43
|
+
browserPreviewMaxHeight / size.height
|
|
44
|
+
);
|
|
45
|
+
if (scale >= 1) {
|
|
46
|
+
return image;
|
|
47
|
+
}
|
|
48
|
+
return image.resize({
|
|
49
|
+
height: Math.max(1, Math.round(size.height * scale)),
|
|
50
|
+
quality: "good",
|
|
51
|
+
width: Math.max(1, Math.round(size.width * scale))
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function isAbortedNavigationError(input) {
|
|
55
|
+
return input.errorCode === abortedNavigationErrorCode || input.errorDescription === "ERR_ABORTED";
|
|
56
|
+
}
|
|
57
|
+
function createBrowserGuestManager({
|
|
58
|
+
emit,
|
|
59
|
+
logger,
|
|
60
|
+
openExternal,
|
|
61
|
+
previewRouteResolver = createNoopBrowserNodePreviewRouteResolver(),
|
|
62
|
+
resolveWebContents
|
|
63
|
+
}) {
|
|
64
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
65
|
+
const nodeIdByWebContentsId = /* @__PURE__ */ new Map();
|
|
66
|
+
const getSession = (nodeId, input) => {
|
|
67
|
+
const existing = sessions.get(nodeId);
|
|
68
|
+
if (existing) {
|
|
69
|
+
if (input?.profileId !== void 0) {
|
|
70
|
+
existing.profileId = input.profileId;
|
|
71
|
+
}
|
|
72
|
+
if (input?.sessionMode !== void 0) {
|
|
73
|
+
existing.sessionMode = input.sessionMode;
|
|
74
|
+
}
|
|
75
|
+
if (input?.url !== void 0) {
|
|
76
|
+
existing.desiredUrl = input.url;
|
|
77
|
+
}
|
|
78
|
+
return existing;
|
|
79
|
+
}
|
|
80
|
+
const session = {
|
|
81
|
+
contents: null,
|
|
82
|
+
desiredUrl: input?.url ?? "about:blank",
|
|
83
|
+
lifecycle: "cold",
|
|
84
|
+
listeners: [],
|
|
85
|
+
nodeId,
|
|
86
|
+
profileId: input?.profileId ?? null,
|
|
87
|
+
sessionMode: input?.sessionMode ?? "shared",
|
|
88
|
+
webContentsId: null
|
|
89
|
+
};
|
|
90
|
+
sessions.set(nodeId, session);
|
|
91
|
+
return session;
|
|
92
|
+
};
|
|
93
|
+
const publishState = (session) => {
|
|
94
|
+
const contents = session.contents && !session.contents.isDestroyed() ? session.contents : null;
|
|
95
|
+
emit({
|
|
96
|
+
canGoBack: contents ? contents.canGoBack() : false,
|
|
97
|
+
canGoForward: contents ? contents.canGoForward() : false,
|
|
98
|
+
isAttachedToWindow: Boolean(contents),
|
|
99
|
+
isLoading: contents ? contents.isLoading() : false,
|
|
100
|
+
isOccluded: session.lifecycle === "cold",
|
|
101
|
+
lifecycle: session.lifecycle,
|
|
102
|
+
nodeId: session.nodeId,
|
|
103
|
+
title: contents ? contents.getTitle() || null : null,
|
|
104
|
+
type: "state",
|
|
105
|
+
url: contents ? contents.getURL() || session.desiredUrl : session.desiredUrl
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
const detachGuest = (session) => {
|
|
109
|
+
const contents = session.contents;
|
|
110
|
+
const webContentsId = session.webContentsId;
|
|
111
|
+
if (contents) {
|
|
112
|
+
for (const record of session.listeners) {
|
|
113
|
+
contents.off(record.event, record.listener);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
session.listeners = [];
|
|
117
|
+
session.contents = null;
|
|
118
|
+
session.webContentsId = null;
|
|
119
|
+
if (webContentsId !== null && nodeIdByWebContentsId.get(webContentsId) === session.nodeId) {
|
|
120
|
+
nodeIdByWebContentsId.delete(webContentsId);
|
|
121
|
+
}
|
|
122
|
+
publishState(session);
|
|
123
|
+
};
|
|
124
|
+
const attachGuestListeners = (session) => {
|
|
125
|
+
const contents = session.contents;
|
|
126
|
+
if (!contents) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const onStateChange = () => publishState(session);
|
|
130
|
+
const onFailLoad = (...args) => {
|
|
131
|
+
const errorCode = typeof args[1] === "number" ? args[1] : void 0;
|
|
132
|
+
const errorDescription = typeof args[2] === "string" ? args[2] : void 0;
|
|
133
|
+
if (isAbortedNavigationError({ errorCode, errorDescription })) {
|
|
134
|
+
publishState(session);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
emit({
|
|
138
|
+
code: "navigation-failed",
|
|
139
|
+
diagnosticMessage: errorDescription,
|
|
140
|
+
nodeId: session.nodeId,
|
|
141
|
+
params: errorCode === void 0 ? void 0 : { errorCode },
|
|
142
|
+
type: "error"
|
|
143
|
+
});
|
|
144
|
+
publishState(session);
|
|
145
|
+
};
|
|
146
|
+
const onDestroyed = () => detachGuest(session);
|
|
147
|
+
const records = [
|
|
148
|
+
{ event: "did-start-loading", listener: onStateChange },
|
|
149
|
+
{ event: "did-stop-loading", listener: onStateChange },
|
|
150
|
+
{ event: "did-navigate", listener: onStateChange },
|
|
151
|
+
{ event: "did-navigate-in-page", listener: onStateChange },
|
|
152
|
+
{ event: "page-title-updated", listener: onStateChange },
|
|
153
|
+
{ event: "did-fail-load", listener: onFailLoad },
|
|
154
|
+
{ event: "destroyed", listener: onDestroyed }
|
|
155
|
+
];
|
|
156
|
+
for (const record of records) {
|
|
157
|
+
contents.on(record.event, record.listener);
|
|
158
|
+
}
|
|
159
|
+
session.listeners = records;
|
|
160
|
+
};
|
|
161
|
+
const loadDesiredUrl = async (session) => {
|
|
162
|
+
const contents = session.contents;
|
|
163
|
+
if (!contents || contents.isDestroyed()) {
|
|
164
|
+
publishState(session);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const resolved = resolveBrowserNavigationUrl(session.desiredUrl);
|
|
168
|
+
if (!resolved.url) {
|
|
169
|
+
emit({
|
|
170
|
+
...resolveBrowserNodeUrlError(resolved),
|
|
171
|
+
nodeId: session.nodeId,
|
|
172
|
+
type: "error"
|
|
173
|
+
});
|
|
174
|
+
publishState(session);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
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
|
+
const currentComparable = normalizeBrowserComparableUrl(contents.getURL());
|
|
183
|
+
const nextComparable = normalizeBrowserComparableUrl(routedUrl);
|
|
184
|
+
if (currentComparable && currentComparable === nextComparable) {
|
|
185
|
+
publishState(session);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
await contents.loadURL(routedUrl);
|
|
189
|
+
publishState(session);
|
|
190
|
+
};
|
|
191
|
+
const openExternalFromGuest = (url) => {
|
|
192
|
+
const resolved = resolveBrowserNavigationUrl(url);
|
|
193
|
+
if (resolved.url) {
|
|
194
|
+
void Promise.resolve(openExternal(resolved.url)).catch(
|
|
195
|
+
(error) => {
|
|
196
|
+
logger?.warn?.("Browser Node openExternal failed", {
|
|
197
|
+
error: error instanceof Error ? error.message : String(error)
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return { action: "deny" };
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
async activate(input) {
|
|
206
|
+
const resolved = resolveBrowserNavigationUrl(input.url);
|
|
207
|
+
if (!resolved.url) {
|
|
208
|
+
throw new Error("Browser Node rejected navigation URL");
|
|
209
|
+
}
|
|
210
|
+
const session = getSession(input.nodeId, {
|
|
211
|
+
profileId: input.profileId,
|
|
212
|
+
sessionMode: input.sessionMode,
|
|
213
|
+
url: resolved.url
|
|
214
|
+
});
|
|
215
|
+
session.lifecycle = "active";
|
|
216
|
+
await loadDesiredUrl(session);
|
|
217
|
+
},
|
|
218
|
+
async capturePreview(input) {
|
|
219
|
+
const session = sessions.get(input.nodeId);
|
|
220
|
+
const contents = session?.contents && !session.contents.isDestroyed() ? session.contents : null;
|
|
221
|
+
if (!contents?.capturePage) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
const image = await contents.capturePage();
|
|
225
|
+
if (image.isEmpty?.() === true) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return resizeBrowserPreviewImage(image).toDataURL();
|
|
229
|
+
},
|
|
230
|
+
close(input) {
|
|
231
|
+
const session = sessions.get(input.nodeId);
|
|
232
|
+
if (session) {
|
|
233
|
+
detachGuest(session);
|
|
234
|
+
sessions.delete(input.nodeId);
|
|
235
|
+
}
|
|
236
|
+
emit({ nodeId: input.nodeId, type: "closed" });
|
|
237
|
+
return Promise.resolve();
|
|
238
|
+
},
|
|
239
|
+
debugDump(input) {
|
|
240
|
+
const session = sessions.get(input.nodeId);
|
|
241
|
+
if (!session) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const contents = session.contents && !session.contents.isDestroyed() ? session.contents : null;
|
|
245
|
+
return {
|
|
246
|
+
canGoBack: contents ? contents.canGoBack() : false,
|
|
247
|
+
canGoForward: contents ? contents.canGoForward() : false,
|
|
248
|
+
currentUrl: contents ? contents.getURL() : null,
|
|
249
|
+
desiredUrl: session.desiredUrl,
|
|
250
|
+
isAttachedToWindow: Boolean(contents),
|
|
251
|
+
isLoading: contents ? contents.isLoading() : false,
|
|
252
|
+
lifecycle: session.lifecycle,
|
|
253
|
+
nodeId: session.nodeId,
|
|
254
|
+
profileId: session.profileId,
|
|
255
|
+
sessionMode: session.sessionMode,
|
|
256
|
+
title: contents ? contents.getTitle() : null,
|
|
257
|
+
webContentsDestroyed: session.contents ? session.contents.isDestroyed() : null,
|
|
258
|
+
webContentsId: session.webContentsId
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
goBack(input) {
|
|
262
|
+
const contents = sessions.get(input.nodeId)?.contents;
|
|
263
|
+
if (contents && !contents.isDestroyed() && contents.canGoBack()) {
|
|
264
|
+
contents.goBack();
|
|
265
|
+
}
|
|
266
|
+
return Promise.resolve();
|
|
267
|
+
},
|
|
268
|
+
goForward(input) {
|
|
269
|
+
const contents = sessions.get(input.nodeId)?.contents;
|
|
270
|
+
if (contents && !contents.isDestroyed() && contents.canGoForward()) {
|
|
271
|
+
contents.goForward();
|
|
272
|
+
}
|
|
273
|
+
return Promise.resolve();
|
|
274
|
+
},
|
|
275
|
+
async navigate(input) {
|
|
276
|
+
const resolved = resolveBrowserNavigationUrl(input.url);
|
|
277
|
+
if (!resolved.url) {
|
|
278
|
+
throw new Error("Browser Node rejected navigation URL");
|
|
279
|
+
}
|
|
280
|
+
const session = getSession(input.nodeId, { url: resolved.url });
|
|
281
|
+
session.lifecycle = "active";
|
|
282
|
+
await loadDesiredUrl(session);
|
|
283
|
+
},
|
|
284
|
+
prepareSession(input) {
|
|
285
|
+
getSession(input.nodeId, {
|
|
286
|
+
profileId: input.profileId,
|
|
287
|
+
sessionMode: input.sessionMode
|
|
288
|
+
});
|
|
289
|
+
return Promise.resolve();
|
|
290
|
+
},
|
|
291
|
+
async registerGuest(input) {
|
|
292
|
+
const contents = resolveWebContents(input.webContentsId);
|
|
293
|
+
if (!contents || contents.isDestroyed()) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Browser Node guest ${input.webContentsId} is not available`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
const ownerNodeId = nodeIdByWebContentsId.get(input.webContentsId);
|
|
299
|
+
if (ownerNodeId && ownerNodeId !== input.nodeId) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Browser Node guest ${input.webContentsId} is already registered`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const session = getSession(input.nodeId, {
|
|
305
|
+
profileId: input.profileId,
|
|
306
|
+
sessionMode: input.sessionMode
|
|
307
|
+
});
|
|
308
|
+
if (session.webContentsId === input.webContentsId && session.contents === contents) {
|
|
309
|
+
publishState(session);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (session.contents && session.contents !== contents) {
|
|
313
|
+
detachGuest(session);
|
|
314
|
+
}
|
|
315
|
+
session.contents = contents;
|
|
316
|
+
session.webContentsId = input.webContentsId;
|
|
317
|
+
nodeIdByWebContentsId.set(input.webContentsId, input.nodeId);
|
|
318
|
+
session.lifecycle = "active";
|
|
319
|
+
contents.setWindowOpenHandler?.(({ url }) => openExternalFromGuest(url));
|
|
320
|
+
attachGuestListeners(session);
|
|
321
|
+
await loadDesiredUrl(session);
|
|
322
|
+
},
|
|
323
|
+
reload(input) {
|
|
324
|
+
const contents = sessions.get(input.nodeId)?.contents;
|
|
325
|
+
if (contents && !contents.isDestroyed()) {
|
|
326
|
+
contents.reload();
|
|
327
|
+
}
|
|
328
|
+
return Promise.resolve();
|
|
329
|
+
},
|
|
330
|
+
unregisterGuest(input) {
|
|
331
|
+
const session = sessions.get(input.nodeId);
|
|
332
|
+
if (!session || session.webContentsId !== input.webContentsId) {
|
|
333
|
+
return Promise.resolve();
|
|
334
|
+
}
|
|
335
|
+
session.lifecycle = "cold";
|
|
336
|
+
detachGuest(session);
|
|
337
|
+
return Promise.resolve();
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/electron-main/registerElectronMain.ts
|
|
343
|
+
function registerBrowserNodeElectronMain(input) {
|
|
344
|
+
const managersByWindow = /* @__PURE__ */ new WeakMap();
|
|
345
|
+
const resolveManager = (event) => {
|
|
346
|
+
const ownerWindow = input.getOwnerWindow(event);
|
|
347
|
+
if (!ownerWindow) {
|
|
348
|
+
throw new Error("Browser Node IPC requires an owner window");
|
|
349
|
+
}
|
|
350
|
+
const existing = managersByWindow.get(ownerWindow);
|
|
351
|
+
if (existing) {
|
|
352
|
+
return existing;
|
|
353
|
+
}
|
|
354
|
+
const manager = createBrowserGuestManager({
|
|
355
|
+
emit(browserEvent) {
|
|
356
|
+
if (!ownerWindow.isDestroyed()) {
|
|
357
|
+
ownerWindow.webContents.send(input.channels.event, browserEvent);
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
logger: input.logger,
|
|
361
|
+
openExternal: input.openExternal,
|
|
362
|
+
resolveWebContents: (webContentsId) => input.resolveWebContents({
|
|
363
|
+
event,
|
|
364
|
+
ownerWindow,
|
|
365
|
+
webContentsId
|
|
366
|
+
})
|
|
367
|
+
});
|
|
368
|
+
managersByWindow.set(ownerWindow, manager);
|
|
369
|
+
return manager;
|
|
370
|
+
};
|
|
371
|
+
input.registerHandler(
|
|
372
|
+
input.channels.prepareSession,
|
|
373
|
+
(event, payload) => resolveManager(event).prepareSession(
|
|
374
|
+
payload
|
|
375
|
+
)
|
|
376
|
+
);
|
|
377
|
+
input.registerHandler(
|
|
378
|
+
input.channels.activate,
|
|
379
|
+
(event, payload) => resolveManager(event).activate(payload)
|
|
380
|
+
);
|
|
381
|
+
if (input.channels.capturePreview) {
|
|
382
|
+
input.registerHandler(
|
|
383
|
+
input.channels.capturePreview,
|
|
384
|
+
(event, payload) => resolveManager(event).capturePreview(payload)
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
input.registerHandler(
|
|
388
|
+
input.channels.registerGuest,
|
|
389
|
+
(event, payload) => resolveManager(event).registerGuest(
|
|
390
|
+
payload
|
|
391
|
+
)
|
|
392
|
+
);
|
|
393
|
+
input.registerHandler(
|
|
394
|
+
input.channels.unregisterGuest,
|
|
395
|
+
(event, payload) => resolveManager(event).unregisterGuest(
|
|
396
|
+
payload
|
|
397
|
+
)
|
|
398
|
+
);
|
|
399
|
+
input.registerHandler(
|
|
400
|
+
input.channels.navigate,
|
|
401
|
+
(event, payload) => resolveManager(event).navigate(payload)
|
|
402
|
+
);
|
|
403
|
+
input.registerHandler(
|
|
404
|
+
input.channels.goBack,
|
|
405
|
+
(event, payload) => resolveManager(event).goBack(payload)
|
|
406
|
+
);
|
|
407
|
+
input.registerHandler(
|
|
408
|
+
input.channels.goForward,
|
|
409
|
+
(event, payload) => resolveManager(event).goForward(payload)
|
|
410
|
+
);
|
|
411
|
+
input.registerHandler(
|
|
412
|
+
input.channels.reload,
|
|
413
|
+
(event, payload) => resolveManager(event).reload(payload)
|
|
414
|
+
);
|
|
415
|
+
input.registerHandler(
|
|
416
|
+
input.channels.close,
|
|
417
|
+
(event, payload) => resolveManager(event).close(payload)
|
|
418
|
+
);
|
|
419
|
+
if (input.channels.debugDump) {
|
|
420
|
+
input.registerHandler(
|
|
421
|
+
input.channels.debugDump,
|
|
422
|
+
(event, payload) => resolveManager(event).debugDump(payload)
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/electron-main/webviewSecurity.ts
|
|
428
|
+
function isBrowserNodeWebviewAttach(params) {
|
|
429
|
+
return params["data-browser-node-webview"] === "true" || isBrowserSessionPartitionAllowed(params.partition);
|
|
430
|
+
}
|
|
431
|
+
function enforceBrowserWebviewSecurity({
|
|
432
|
+
params,
|
|
433
|
+
webPreferences
|
|
434
|
+
}) {
|
|
435
|
+
webPreferences.allowRunningInsecureContent = false;
|
|
436
|
+
webPreferences.contextIsolation = true;
|
|
437
|
+
webPreferences.javascript = true;
|
|
438
|
+
webPreferences.nodeIntegration = false;
|
|
439
|
+
webPreferences.plugins = false;
|
|
440
|
+
webPreferences.sandbox = true;
|
|
441
|
+
webPreferences.webSecurity = true;
|
|
442
|
+
delete webPreferences.preload;
|
|
443
|
+
const partition = params.partition;
|
|
444
|
+
if (!partition || !isBrowserSessionPartitionAllowed(partition)) {
|
|
445
|
+
return {
|
|
446
|
+
allowed: false,
|
|
447
|
+
reason: "Unsupported Browser Node session partition"
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
const resolved = resolveBrowserNavigationUrl(params.src ?? "about:blank");
|
|
451
|
+
if (!resolved.url) {
|
|
452
|
+
return {
|
|
453
|
+
allowed: false,
|
|
454
|
+
reason: "Unsupported browser URL"
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
params.src = resolved.url;
|
|
458
|
+
return { allowed: true, reason: null };
|
|
459
|
+
}
|
|
460
|
+
function installBrowserWebviewSecurity({
|
|
461
|
+
contents,
|
|
462
|
+
logger,
|
|
463
|
+
onGuestAttached,
|
|
464
|
+
openExternal,
|
|
465
|
+
shouldHandleWebview = isBrowserNodeWebviewAttach
|
|
466
|
+
}) {
|
|
467
|
+
let pendingBrowserAttachCount = 0;
|
|
468
|
+
const handleWillAttachWebview = (event, webPreferences, params) => {
|
|
469
|
+
if (!shouldHandleWebview(params)) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
pendingBrowserAttachCount += 1;
|
|
473
|
+
const result = enforceBrowserWebviewSecurity({ params, webPreferences });
|
|
474
|
+
if (!result.allowed) {
|
|
475
|
+
pendingBrowserAttachCount = Math.max(0, pendingBrowserAttachCount - 1);
|
|
476
|
+
logger?.warn?.("Browser Node webview blocked", { reason: result.reason });
|
|
477
|
+
event.preventDefault();
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
const handleDidAttachWebview = (_event, guestContents) => {
|
|
481
|
+
if (pendingBrowserAttachCount <= 0) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
pendingBrowserAttachCount -= 1;
|
|
485
|
+
onGuestAttached?.(guestContents);
|
|
486
|
+
guestContents.setWindowOpenHandler(({ url }) => {
|
|
487
|
+
const resolved = resolveBrowserNavigationUrl(url);
|
|
488
|
+
if (resolved.url) {
|
|
489
|
+
void Promise.resolve(openExternal(resolved.url)).catch(() => void 0);
|
|
490
|
+
}
|
|
491
|
+
return { action: "deny" };
|
|
492
|
+
});
|
|
493
|
+
};
|
|
494
|
+
contents.on("will-attach-webview", handleWillAttachWebview);
|
|
495
|
+
contents.on("did-attach-webview", handleDidAttachWebview);
|
|
496
|
+
return () => {
|
|
497
|
+
contents.off("will-attach-webview", handleWillAttachWebview);
|
|
498
|
+
contents.off("did-attach-webview", handleDidAttachWebview);
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
export {
|
|
502
|
+
createBrowserGuestManager,
|
|
503
|
+
createNoopBrowserNodePreviewRouteResolver,
|
|
504
|
+
enforceBrowserWebviewSecurity,
|
|
505
|
+
installBrowserWebviewSecurity,
|
|
506
|
+
isBrowserNodeWebviewAttach,
|
|
507
|
+
registerBrowserNodeElectronMain
|
|
508
|
+
};
|
|
509
|
+
//# sourceMappingURL=index.js.map
|