mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
replace webview-tag to iframe with nodeIntegrationInSubFrames=true
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
405163adbe
commit
b280f4a2dd
@ -5,20 +5,24 @@ import { tracker } from "./tracker";
|
||||
export const clusterIpc = {
|
||||
init: createIpcChannel({
|
||||
channel: "cluster:init",
|
||||
handle: async (clusterId: ClusterId) => {
|
||||
return clusterStore.getById(clusterId)?.pushState();
|
||||
handle: async (clusterId: ClusterId, frameId: number) => {
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
if (cluster) {
|
||||
cluster.frameId = frameId; // save cluster's webFrame.routingId to be able to send push-updates
|
||||
return cluster.pushState();
|
||||
}
|
||||
},
|
||||
}),
|
||||
activate: createIpcChannel({
|
||||
channel: "cluster:activate",
|
||||
handle: async (clusterId: ClusterId = clusterStore.activeClusterId) => {
|
||||
handle: (clusterId: ClusterId) => {
|
||||
return clusterStore.getById(clusterId)?.activate();
|
||||
},
|
||||
}),
|
||||
|
||||
disconnect: createIpcChannel({
|
||||
channel: "cluster:disconnect",
|
||||
handle: (clusterId: ClusterId = clusterStore.activeClusterId) => {
|
||||
handle: (clusterId: ClusterId) => {
|
||||
tracker.event("cluster", "stop");
|
||||
return clusterStore.getById(clusterId)?.disconnect();
|
||||
},
|
||||
@ -29,7 +33,6 @@ export const clusterIpc = {
|
||||
handle: async (clusterId: ClusterId, feature: string, config?: any) => {
|
||||
tracker.event("cluster", "install", feature);
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
|
||||
if (cluster) {
|
||||
await cluster.installFeature(feature, config)
|
||||
} else {
|
||||
|
||||
@ -2,106 +2,46 @@
|
||||
// https://www.electronjs.org/docs/api/ipc-main
|
||||
// https://www.electronjs.org/docs/api/ipc-renderer
|
||||
|
||||
import { ipcMain, ipcRenderer, IpcRendererEvent, WebContents, webContents } from "electron"
|
||||
import { ipcMain, ipcRenderer, WebContents, webContents } from "electron"
|
||||
import logger from "../main/logger";
|
||||
import { getRandId } from "./utils";
|
||||
|
||||
export type IpcChannel = string;
|
||||
|
||||
export enum IpcMode {
|
||||
SYNC = "sync",
|
||||
ASYNC = "async",
|
||||
}
|
||||
|
||||
export interface IpcChannelRequest<A extends any[] = any> {
|
||||
msgId: string;
|
||||
args: A;
|
||||
}
|
||||
|
||||
export interface IpcChannelResponse<T extends any[] = any, E = any> {
|
||||
msgId: string;
|
||||
data?: T;
|
||||
error?: E;
|
||||
}
|
||||
|
||||
export interface IpcChannelOptions {
|
||||
channel: IpcChannel; // main <-> renderer communication channel name
|
||||
mode?: IpcMode; // default: "async", use "sync" as last resort: https://www.electronjs.org/docs/api/ipc-renderer#ipcrenderersendsyncchannel-args
|
||||
handle?: (...args: any[]) => any; // main-process message handler
|
||||
handle?: (...args: any[]) => void; // message handler
|
||||
autoBind?: boolean; // auto-bind message handler in main-process, default: true
|
||||
timeout?: number; // timeout for waiting response from the sender
|
||||
once?: boolean; // todo: add support
|
||||
once?: boolean; // one-time event
|
||||
}
|
||||
|
||||
export function createIpcChannel({ autoBind = true, mode = IpcMode.ASYNC, timeout = 0, handle, channel }: IpcChannelOptions) {
|
||||
channel = `${mode}:${channel}`
|
||||
|
||||
export function createIpcChannel({ autoBind = true, once, timeout = 0, handle, channel }: IpcChannelOptions) {
|
||||
const ipcChannel = {
|
||||
channel: channel,
|
||||
handleInMain: () => {
|
||||
logger.info(`[IPC]: setup channel "${channel}"`);
|
||||
|
||||
ipcMain.on(channel, async (event, req: IpcChannelRequest) => {
|
||||
let resolved = false;
|
||||
const ipcHandler = once ? ipcMain.handleOnce : ipcMain.handle;
|
||||
ipcHandler(channel, async (event, ...args) => {
|
||||
let timerId: any;
|
||||
|
||||
function resolve(res: Partial<IpcChannelResponse>) {
|
||||
if (resolved) return;
|
||||
res.msgId = req.msgId; // return back to sender to be able to handle response
|
||||
resolved = true
|
||||
logger.debug(`[IPC]: sending response to "${channel}"`, res);
|
||||
if (mode === IpcMode.SYNC) {
|
||||
event.returnValue = res;
|
||||
} else {
|
||||
event.reply(channel, res);
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout > 0) {
|
||||
timerId = setTimeout(() => {
|
||||
const timeoutError = new Error(`[IPC]: response timeout in ${timeout}ms`);
|
||||
resolve({ error: timeoutError })
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await handle(...req.args); // todo: maybe exec in separate thread/worker
|
||||
resolve({ data })
|
||||
if (timeout > 0) {
|
||||
timerId = setTimeout(() => {
|
||||
throw new Error(`[IPC]: response timeout in ${timeout}ms`)
|
||||
}, timeout);
|
||||
}
|
||||
return await handle(...args); // todo: maybe exec in separate thread/worker
|
||||
} catch (error) {
|
||||
resolve({
|
||||
error: String(error)
|
||||
})
|
||||
throw error
|
||||
} finally {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
})
|
||||
},
|
||||
invokeFromRenderer: async (...args: any[]) => {
|
||||
const req: IpcChannelRequest = {
|
||||
msgId: getRandId({ prefix: "ipc-msg-id" }),
|
||||
args: args,
|
||||
}
|
||||
if (mode === IpcMode.SYNC) {
|
||||
ipcRenderer.sendSync(channel, req)
|
||||
} else {
|
||||
ipcRenderer.send(channel, req)
|
||||
}
|
||||
return new Promise(async (resolve, reject) => {
|
||||
ipcRenderer.on(channel, function waitResponseHandler(event: IpcRendererEvent, res: IpcChannelResponse) {
|
||||
if (req.msgId === res.msgId) {
|
||||
const meta = { ...req, ...res };
|
||||
if (res.data) {
|
||||
logger.debug(`[IPC]: "${channel}" resolve`, meta);
|
||||
resolve(res.data);
|
||||
}
|
||||
if (res.error) {
|
||||
logger.error(`[IPC]: "${channel}" reject`, meta);
|
||||
reject(res.error);
|
||||
}
|
||||
ipcRenderer.off(channel, waitResponseHandler); // unsubscribe since handled
|
||||
}
|
||||
});
|
||||
})
|
||||
removeHandler() {
|
||||
ipcMain.removeHandler(channel);
|
||||
},
|
||||
invokeFromRenderer: async <T>(...args: any[]): Promise<T> => {
|
||||
return ipcRenderer.invoke(channel, ...args);
|
||||
},
|
||||
}
|
||||
if (autoBind && ipcMain) {
|
||||
@ -112,13 +52,14 @@ export function createIpcChannel({ autoBind = true, mode = IpcMode.ASYNC, timeou
|
||||
|
||||
export interface IpcBroadcastParams<A extends any[] = any> {
|
||||
channel: IpcChannel
|
||||
webContentId?: number; // sends to single webContents view
|
||||
webContentId?: number; // send to single webContents view
|
||||
frameId?: number; // send to inner frame of webContents
|
||||
filter?: (webContent: WebContents) => boolean
|
||||
timeout?: number; // todo: add support
|
||||
args?: A;
|
||||
}
|
||||
|
||||
export function broadcastIpc({ channel, webContentId, filter, args = [] }: IpcBroadcastParams) {
|
||||
export function broadcastIpc({ channel, frameId, webContentId, filter, args = [] }: IpcBroadcastParams) {
|
||||
const singleView = webContentId ? webContents.fromId(webContentId) : null;
|
||||
let views = singleView ? [singleView] : webContents.getAllWebContents();
|
||||
if (filter) {
|
||||
@ -127,6 +68,9 @@ export function broadcastIpc({ channel, webContentId, filter, args = [] }: IpcBr
|
||||
views.forEach(webContent => {
|
||||
const type = webContent.getType();
|
||||
logger.debug(`[IPC]: broadcasting "${channel}" to ${type}=${webContent.id}`, { args });
|
||||
webContent.send(channel, ...[args].flat());
|
||||
webContent.send(channel, ...args);
|
||||
if (frameId) {
|
||||
webContent.sendToFrame(frameId, channel, ...args)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ export interface ClusterState extends ClusterModel {
|
||||
|
||||
export class Cluster implements ClusterModel {
|
||||
public id: ClusterId;
|
||||
public frameId: number;
|
||||
public kubeCtl: Kubectl
|
||||
public contextHandler: ContextHandler;
|
||||
protected kubeconfigManager: KubeconfigManager;
|
||||
@ -389,8 +390,8 @@ export class Cluster implements ClusterModel {
|
||||
pushState = (state = this.getState()): ClusterState => {
|
||||
logger.debug(`[CLUSTER]: push-state`, state);
|
||||
broadcastIpc({
|
||||
// webContentId: viewId, // todo: send to cluster-view only
|
||||
channel: "cluster:state",
|
||||
frameId: this.frameId,
|
||||
args: [state],
|
||||
});
|
||||
return state;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import type { ClusterId } from "../common/cluster-store";
|
||||
import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, shell } from "electron"
|
||||
import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, shell, webContents } from "electron"
|
||||
import { autorun } from "mobx";
|
||||
import { WindowManager } from "./window-manager";
|
||||
import { appName, isMac, issuesTrackerUrl, isWindows, slackUrl } from "../common/vars";
|
||||
@ -30,12 +29,11 @@ export function buildMenu(windowManager: WindowManager) {
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
function navigate(url: string, clusterId?: ClusterId) {
|
||||
function navigate(url: string) {
|
||||
logger.info(`[MENU]: navigating to ${url}`);
|
||||
windowManager.navigate({
|
||||
channel: "menu:navigate",
|
||||
url: url,
|
||||
clusterId: clusterId,
|
||||
})
|
||||
}
|
||||
|
||||
@ -146,33 +144,24 @@ export function buildMenu(windowManager: WindowManager) {
|
||||
label: 'Back',
|
||||
accelerator: 'CmdOrCtrl+[',
|
||||
click() {
|
||||
windowManager.getActiveClusterView()?.goBack();
|
||||
webContents.getFocusedWebContents()?.goBack();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Forward',
|
||||
accelerator: 'CmdOrCtrl+]',
|
||||
click() {
|
||||
windowManager.getActiveClusterView()?.goForward();
|
||||
webContents.getFocusedWebContents()?.goForward();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
click() {
|
||||
windowManager.getActiveClusterView()?.reload();
|
||||
webContents.getFocusedWebContents()?.reload();
|
||||
}
|
||||
},
|
||||
{ role: 'toggleDevTools' },
|
||||
...activeClusterOnly([
|
||||
{
|
||||
accelerator: "CmdOrCtrl+Shift+I",
|
||||
label: "Toggle Dashboard DevTools",
|
||||
click() {
|
||||
windowManager.getActiveClusterView()?.toggleDevTools();
|
||||
}
|
||||
}
|
||||
]),
|
||||
{ type: 'separator' },
|
||||
{ role: 'resetZoom' },
|
||||
{ role: 'zoomIn' },
|
||||
|
||||
@ -28,8 +28,8 @@ export class WindowManager {
|
||||
backgroundColor: "#1e2124",
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInSubFrames: true,
|
||||
enableRemoteModule: true,
|
||||
webviewTag: true,
|
||||
},
|
||||
});
|
||||
this.windowState.manage(this.mainView);
|
||||
@ -50,18 +50,14 @@ export class WindowManager {
|
||||
initMenu(this);
|
||||
}
|
||||
|
||||
navigate({ url, channel, clusterId }: { url: string, channel: string, clusterId?: ClusterId }) {
|
||||
if (clusterId) {
|
||||
this.getClusterView(clusterId)?.send(channel, url);
|
||||
navigate({ url, channel, frameId }: { url: string, channel: string, frameId?: number }) {
|
||||
if (frameId) {
|
||||
this.mainView.webContents.sendToFrame(frameId, channel, url);
|
||||
} else {
|
||||
this.mainView.webContents.send(channel, url);
|
||||
}
|
||||
}
|
||||
|
||||
getActiveClusterView() {
|
||||
return this.getClusterView(this.activeClusterId)
|
||||
}
|
||||
|
||||
getClusterView(clusterId: ClusterId): WebContents {
|
||||
return webContents.getAllWebContents().find(view => {
|
||||
return new URL(view.getURL()).host.split(".")[0] === clusterId;
|
||||
|
||||
@ -32,13 +32,17 @@ import { ErrorBoundary } from "./error-boundary";
|
||||
import { Terminal } from "./dock/terminal";
|
||||
import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store";
|
||||
import logger from "../../main/logger";
|
||||
import { clusterIpc } from "../../common/cluster-ipc";
|
||||
import { webFrame } from "electron";
|
||||
|
||||
@observer
|
||||
export class App extends React.Component {
|
||||
static async init() {
|
||||
logger.info(`[APP]: Init dashboard, clusterId=${getHostedClusterId()}`)
|
||||
const clusterId = getHostedClusterId();
|
||||
logger.info(`[APP]: Init dashboard, clusterId=${clusterId}`)
|
||||
await Terminal.preloadFonts()
|
||||
await getHostedCluster().whenInitialized; // wait for cluster-state before initial render
|
||||
await clusterIpc.init.invokeFromRenderer(clusterId, webFrame.routingId);
|
||||
await getHostedCluster().whenInitialized;
|
||||
}
|
||||
|
||||
get startURL() {
|
||||
|
||||
@ -1,62 +1,37 @@
|
||||
import { ipcRenderer, WebviewTag } from "electron";
|
||||
import { observable, when } from "mobx";
|
||||
import { observable } from "mobx";
|
||||
import { ClusterId, clusterStore } from "../../../common/cluster-store";
|
||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||
import { clusterViewURL, getMatchedCluster, getMatchedClusterId } from "./cluster-view.route"
|
||||
import { navigate } from "../../navigation";
|
||||
import { getMatchedCluster } from "./cluster-view.route"
|
||||
import logger from "../../../main/logger";
|
||||
|
||||
export interface LensView {
|
||||
isLoaded?: boolean
|
||||
clusterId: ClusterId;
|
||||
view: WebviewTag
|
||||
view: HTMLIFrameElement
|
||||
}
|
||||
|
||||
export const lensViews = observable.map<ClusterId, LensView>();
|
||||
|
||||
export async function navigateInClusterView(path: string, clusterId: ClusterId) {
|
||||
// select active cluster in common view
|
||||
if (clusterId !== getMatchedClusterId()) {
|
||||
clusterStore.setActive(clusterId);
|
||||
navigate(clusterViewURL({ params: { clusterId } }));
|
||||
}
|
||||
// navigate in cluster-view when ready
|
||||
await when(() => hasLoadedView(clusterId))
|
||||
ipcRenderer.sendTo(getViewId(clusterId), "menu:navigate", path);
|
||||
}
|
||||
|
||||
export function hasLoadedView(clusterId: ClusterId): boolean {
|
||||
return !!lensViews.get(clusterId)?.isLoaded;
|
||||
}
|
||||
|
||||
export function getViewId(clusterId: ClusterId): number {
|
||||
const webview = lensViews.get(clusterId)?.view
|
||||
if (webview) {
|
||||
return webview.getWebContentsId()
|
||||
}
|
||||
}
|
||||
|
||||
// todo: figure out how to replace <webview>-tag to <iframe> with nodeIntegration=true
|
||||
export function initView(clusterId: ClusterId) {
|
||||
export async function initView(clusterId: ClusterId) {
|
||||
if (!clusterId || lensViews.has(clusterId)) {
|
||||
return;
|
||||
}
|
||||
logger.info(`[CLUSTER-VIEW]: init dashboard, clusterId=${clusterId}`)
|
||||
const parentElem = document.getElementById("lens-views"); // defined in cluster-manager's css-grid
|
||||
const webview = document.createElement("webview");
|
||||
webview.setAttribute("src", `//${clusterId}.${location.host}`)
|
||||
webview.setAttribute("nodeintegration", "true")
|
||||
webview.setAttribute("enableremotemodule", "true")
|
||||
webview.addEventListener("did-finish-load", () => {
|
||||
logger.info(`[CLUSTER-VIEW]: loaded, clusterId=${clusterId}`)
|
||||
clusterIpc.init.invokeFromRenderer(clusterId); // push cluster-state to webview and init render
|
||||
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
await cluster.whenInitialized;
|
||||
const parentElem = document.getElementById("lens-views");
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.name = cluster.preferences.clusterName;
|
||||
iframe.setAttribute("src", `//${clusterId}.${location.host}`)
|
||||
iframe.addEventListener("load", async () => {
|
||||
logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`)
|
||||
lensViews.get(clusterId).isLoaded = true;
|
||||
});
|
||||
webview.addEventListener("did-fail-load", (event) => {
|
||||
logger.error(`[CLUSTER-VIEW]: failed to load, clusterId=${clusterId}`, event)
|
||||
});
|
||||
lensViews.set(clusterId, { clusterId, view: webview });
|
||||
parentElem.appendChild(webview); // add to dom and init cluster-page loading
|
||||
})
|
||||
lensViews.set(clusterId, { clusterId, view: iframe });
|
||||
parentElem.appendChild(iframe);
|
||||
}
|
||||
|
||||
export function refreshViews() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user