mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-04-21 17:47:22 +00:00
chore: add preload module for Webviews
part of https://github.com/containers/podman-desktop/issues/5140 Signed-off-by: Florent Benoit <fbenoit@redhat.com>
This commit is contained in:
parent
9c1aea6817
commit
ac6daec4a3
10 changed files with 595 additions and 4 deletions
|
|
@ -57,6 +57,7 @@
|
|||
"ignorePatterns": [
|
||||
"packages/preload/exposedInMainWorld.d.ts",
|
||||
"packages/preload-docker-extension/exposedInDockerExtension.d.ts",
|
||||
"packages/preload-webview/exposedInWebview.d.ts",
|
||||
"node_modules/**",
|
||||
"**/dist/**"
|
||||
],
|
||||
|
|
|
|||
11
package.json
11
package.json
|
|
@ -21,7 +21,7 @@
|
|||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run build:main && npm run build:preload && npm run build:preload-docker-extension && npm run build:renderer && npm run build:extensions",
|
||||
"build": "npm run build:main && npm run build:preload && npm run build:preload-docker-extension && npm run build:preload-webview && npm run build:renderer && npm run build:extensions",
|
||||
"build:main": "cd ./packages/main && vite build",
|
||||
"build:extensions": "npm run build:extensions:compose && npm run build:extensions:docker && npm run build:extensions:lima && npm run build:extensions:podman && npm run build:extensions:kubecontext && npm run build:extensions:kind && npm run build:extensions:registries && npm run build:extensions:kubectl-cli",
|
||||
"build:extensions:compose": "cd ./extensions/compose && npm run build",
|
||||
|
|
@ -35,14 +35,15 @@
|
|||
"build:extension-api": "cd ./packages/extension-api && vite build",
|
||||
"build:preload": "cd ./packages/preload && vite build",
|
||||
"build:preload-docker-extension": "cd ./packages/preload-docker-extension && vite build",
|
||||
"build:preload:types": "dts-cb -i \"packages/preload/tsconfig.json\" -o \"packages/preload/exposedInMainWorld.d.ts\" && dts-cb -i \"packages/preload-docker-extension/tsconfig.json\" -o \"packages/preload-docker-extension/exposedInDockerExtension.d.ts\"",
|
||||
"build:preload-webview": "cd ./packages/preload-webview && vite build",
|
||||
"build:preload:types": "dts-cb -i \"packages/preload/tsconfig.json\" -o \"packages/preload/exposedInMainWorld.d.ts\" && dts-cb -i \"packages/preload-docker-extension/tsconfig.json\" -o \"packages/preload-docker-extension/exposedInDockerExtension.d.ts\" && dts-cb -i \"packages/preload-webview/tsconfig.json\" -o \"packages/preload-webview/exposedInWebview.d.ts\"",
|
||||
"build:renderer": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite -c packages/renderer/vite.config.js build",
|
||||
"compile": "cross-env MODE=production npm run build && electron-builder build --config .electron-builder.config.cjs --dir --config.asar=false",
|
||||
"compile:next": "cross-env MODE=production npm run build && electron-builder build --publish always --config .electron-builder.config.cjs",
|
||||
"compile:pull-request": "cross-env MODE=production npm run build && electron-builder build --publish never --config .electron-builder.config.cjs",
|
||||
"compile:current": "cross-env MODE=production npm run build && electron-builder build --config .electron-builder.config.cjs",
|
||||
"test": "npm run test:unit && npm run test:e2e",
|
||||
"test:unit": "npm run test:main && npm run test:preload && npm run test:preload-docker-extension && npm run test:renderer && npm run test:tools && npm run test:extensions",
|
||||
"test:unit": "npm run test:main && npm run test:preload && npm run test:preload-docker-extension && npm run test:preload-webview && npm run test:renderer && npm run test:tools && npm run test:extensions",
|
||||
"test:e2e": "npm run test:e2e:build && npm run test:e2e:run",
|
||||
"test:e2e:build": "cross-env NODE_ENV=development MODE=development DEBUG=pw:browser npm run build",
|
||||
"test:e2e:run": "xvfb-maybe --auto-servernum --server-args='-screen 0 1280x960x24' -- vitest run tests/src/ --pool=threads --poolOptions.threads.singleThread --no-file-parallelism",
|
||||
|
|
@ -53,6 +54,7 @@
|
|||
"test:main": "vitest run -r packages/main --passWithNoTests --coverage",
|
||||
"test:preload": "vitest run -r packages/preload --passWithNoTests --coverage",
|
||||
"test:preload-docker-extension": "vitest run -r packages/preload-docker-extension --passWithNoTests --coverage",
|
||||
"test:preload-webview": "vitest run -r packages/preload-webview --coverage",
|
||||
"test:extensions": "vitest run -r extensions --passWithNoTests --coverage && npm run test:extensions:compose && npm run test:extensions:kind && npm run test:extensions:docker && npm run test:extensions:lima && npm run test:extensions:kube && npm run test:extensions:podman && npm run test:extensions:registries && npm run test:extensions:kubectl-cli",
|
||||
"test:extensions:kind": "vitest run -r extensions/kind --passWithNoTests --coverage ",
|
||||
"test:extensions:compose": "vitest run -r extensions/compose --passWithNoTests --coverage",
|
||||
|
|
@ -77,6 +79,7 @@
|
|||
"typecheck:main": "tsc --noEmit -p packages/main/tsconfig.json",
|
||||
"typecheck:preload": "tsc --noEmit -p packages/preload/tsconfig.json",
|
||||
"typecheck:preload-dd-extension": "tsc --noEmit -p packages/preload-docker-extension/tsconfig.json",
|
||||
"typecheck:preload-webview": "tsc --noEmit -p packages/preload-webview/tsconfig.json",
|
||||
"typecheck:renderer": "npm run build:preload:types && tsc --noEmit -p packages/renderer/tsconfig.json",
|
||||
"typecheck:extensions": "npm run typecheck:extensions:compose && npm run typecheck:extensions:kind && npm run typecheck:extensions:docker && npm run typecheck:extensions:lima && npm run typecheck:extensions:kube-context && npm run typecheck:extensions:podman && npm run typecheck:extensions:registries && npm run typecheck:extensions:kubectl-cli",
|
||||
"typecheck:extensions:kind": "tsc --noEmit --project extensions/kind",
|
||||
|
|
@ -87,7 +90,7 @@
|
|||
"typecheck:extensions:podman": "tsc --noEmit --project extensions/podman",
|
||||
"typecheck:extensions:registries": "tsc --noEmit --project extensions/registries",
|
||||
"typecheck:extensions:kubectl-cli": "tsc --noEmit --project extensions/kubectl-cli",
|
||||
"typecheck": "npm run typecheck:main && npm run typecheck:preload && npm run typecheck:renderer && npm run typecheck:preload-dd-extension && npm run typecheck:extensions",
|
||||
"typecheck": "npm run typecheck:main && npm run typecheck:preload && npm run typecheck:renderer && npm run typecheck:preload-dd-extension && npm run typecheck:preload-webview && npm run typecheck:extensions",
|
||||
"website:build": "cd website && yarn run docusaurus build",
|
||||
"website:prod": "cd website && yarn run docusaurus build && yarn serve",
|
||||
"website:dev": "cd website && yarn run docusaurus start",
|
||||
|
|
|
|||
1
packages/preload-webview/.gitignore
vendored
Normal file
1
packages/preload-webview/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
exposedInWebview.d.ts
|
||||
57
packages/preload-webview/src/index.spec.ts
Normal file
57
packages/preload-webview/src/index.spec.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/**********************************************************************
|
||||
* Copyright (C) 2024 Red Hat, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
***********************************************************************/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { beforeEach, expect, test, vi } from 'vitest';
|
||||
import { init } from '.';
|
||||
import * as webviewPreload from './webview-preload';
|
||||
|
||||
vi.mock('./webview-preload', async () => {
|
||||
return {
|
||||
WebviewPreload: vi.fn().mockImplementation(() => {
|
||||
return {
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('check call constructor with correct web id by parsing window.location.search', () => {
|
||||
(window as any).location = {
|
||||
search: '?webviewId=123',
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
// expect constructor has been called with the correct webviewID
|
||||
expect(webviewPreload.WebviewPreload).toHaveBeenCalledWith('123');
|
||||
});
|
||||
|
||||
test('check error if invalid window.location.search', () => {
|
||||
(window as any).location = {
|
||||
search: '',
|
||||
};
|
||||
|
||||
// expect failure as webviewId is not defined
|
||||
expect(() => init()).toThrow('The webviewId is not defined');
|
||||
});
|
||||
41
packages/preload-webview/src/index.ts
Normal file
41
packages/preload-webview/src/index.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**********************************************************************
|
||||
* Copyright (C) 2024 Red Hat, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
***********************************************************************/
|
||||
|
||||
import { WebviewPreload } from './webview-preload';
|
||||
|
||||
/**
|
||||
* @module preload
|
||||
*/
|
||||
export const init = () => {
|
||||
// parse the query string and grab the webviewId parameter
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
const webviewId = urlParams.get('webviewId') ?? undefined;
|
||||
|
||||
if (!webviewId) {
|
||||
throw new Error('The webviewId is not defined');
|
||||
}
|
||||
// create the webviewPreload object and call the init method
|
||||
const webviewPreload = new WebviewPreload(webviewId);
|
||||
webviewPreload.init().catch((error: unknown) => console.error('Error while initializing the exposure', error));
|
||||
};
|
||||
|
||||
// do not call init methd in case of testing
|
||||
if (!process.env.VITEST) {
|
||||
init();
|
||||
}
|
||||
217
packages/preload-webview/src/webview-preload.spec.ts
Normal file
217
packages/preload-webview/src/webview-preload.spec.ts
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
/**********************************************************************
|
||||
* Copyright (C) 2024 Red Hat, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
***********************************************************************/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import type { MockInstance } from 'vitest';
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { WebviewPreload } from './webview-preload';
|
||||
import type { WebviewInfo } from '../../main/src/plugin/api/webview-info';
|
||||
import type { IpcRendererEvent } from 'electron';
|
||||
import { ipcRenderer, contextBridge } from 'electron';
|
||||
|
||||
let webviewPreload: TestWebwiewPreload;
|
||||
|
||||
class TestWebwiewPreload extends WebviewPreload {
|
||||
async getWebviews(): Promise<WebviewInfo[]> {
|
||||
return super.getWebviews();
|
||||
}
|
||||
buildApi(): unknown {
|
||||
return super.buildApi();
|
||||
}
|
||||
ipcRendererOn(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void) {
|
||||
super.ipcRendererOn(channel, listener);
|
||||
}
|
||||
async ipcInvoke(channel: string, ...args: unknown[]): Promise<unknown> {
|
||||
return super.ipcInvoke(channel, ...args);
|
||||
}
|
||||
changeContent() {
|
||||
super.changeContent();
|
||||
}
|
||||
postWebviewMessage(message: unknown) {
|
||||
super.postWebviewMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
const webviewInfo: WebviewInfo = {
|
||||
id: '123',
|
||||
viewType: 'test',
|
||||
sourcePath: 'testPath',
|
||||
icon: 'testIcon',
|
||||
name: 'test',
|
||||
html: '<html>hello world</html>',
|
||||
uuid: '12-12-12-12',
|
||||
state: { foo: 'bar' },
|
||||
};
|
||||
|
||||
vi.mock('electron', async () => {
|
||||
return {
|
||||
contextBridge: {
|
||||
exposeInMainWorld: vi.fn(),
|
||||
},
|
||||
ipcRenderer: {
|
||||
on: vi.fn(),
|
||||
emit: vi.fn(),
|
||||
handle: vi.fn(),
|
||||
invoke: vi.fn(),
|
||||
},
|
||||
ipcMain: {
|
||||
on: vi.fn(),
|
||||
emit: vi.fn(),
|
||||
handle: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
let spyIpcRendererOn: MockInstance<
|
||||
[channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void],
|
||||
void
|
||||
>;
|
||||
let spyBuildApi: MockInstance<[], unknown>;
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
webviewPreload = new TestWebwiewPreload('123');
|
||||
// mock the window object
|
||||
(window as any).addEventListener = vi.fn();
|
||||
|
||||
// override the getWebviews method
|
||||
const spyGetWebviews = vi.spyOn(webviewPreload, 'getWebviews');
|
||||
spyGetWebviews.mockResolvedValue([webviewInfo]);
|
||||
|
||||
// override buildApi method
|
||||
spyBuildApi = vi.spyOn(webviewPreload, 'buildApi');
|
||||
spyBuildApi.mockReturnValue(() => {});
|
||||
|
||||
// override ipcRendererOn
|
||||
spyIpcRendererOn = vi.spyOn(webviewPreload, 'ipcRendererOn');
|
||||
spyIpcRendererOn.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
test('check init method', async () => {
|
||||
await webviewPreload.init();
|
||||
|
||||
// check it adds addEventListener to the window object
|
||||
expect(window.addEventListener).toHaveBeenCalledWith('DOMContentLoaded', expect.any(Function));
|
||||
|
||||
// check exposure of the function to javascript
|
||||
expect(vi.mocked(contextBridge.exposeInMainWorld)).toHaveBeenCalledWith(
|
||||
'acquirePodmanDesktopApi',
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
// check we register 2 event listener on ipcRenderer
|
||||
expect(spyIpcRendererOn).toHaveBeenCalledWith('webview-post-message', expect.any(Function));
|
||||
expect(spyIpcRendererOn).toHaveBeenCalledWith('webview-update-html', expect.any(Function));
|
||||
});
|
||||
|
||||
describe('ipcInvoke', () => {
|
||||
test('check custom ipcInvoke method', async () => {
|
||||
// override the ipcRenderer.invoke method
|
||||
const spyIpcRendererInvoke = vi.spyOn(ipcRenderer, 'invoke');
|
||||
|
||||
const fakeResult = 'foo';
|
||||
// fake remote implementation sending no error and foo as result
|
||||
spyIpcRendererInvoke.mockImplementation(() => Promise.resolve({ result: fakeResult, error: undefined }));
|
||||
|
||||
const result = await webviewPreload.ipcInvoke('test', 'arg1');
|
||||
|
||||
expect(result).toStrictEqual(fakeResult);
|
||||
expect(spyIpcRendererInvoke).toHaveBeenCalledWith('test', 'arg1');
|
||||
});
|
||||
|
||||
test('check custom ipcInvoke method with error', async () => {
|
||||
// override the ipcRenderer.invoke method
|
||||
const spyIpcRendererInvoke = vi.spyOn(ipcRenderer, 'invoke');
|
||||
|
||||
const fakeError = new Error('dummy error');
|
||||
// fake remote implementation sending no error and foo as result
|
||||
spyIpcRendererInvoke.mockImplementation(() => Promise.resolve({ result: undefined, error: fakeError }));
|
||||
|
||||
await expect(webviewPreload.ipcInvoke('test', 'arg1')).rejects.toThrow('dummy error');
|
||||
|
||||
expect(spyIpcRendererInvoke).toHaveBeenCalledWith('test', 'arg1');
|
||||
});
|
||||
});
|
||||
|
||||
test('check changeContent', async () => {
|
||||
// spy document.write method
|
||||
const spyDocumentWrite = vi.spyOn(document, 'write');
|
||||
|
||||
// override window.addEventListener to keep the callback
|
||||
const spyAddEventListener = vi.spyOn(window, 'addEventListener');
|
||||
|
||||
// call changeContent it should not do anything as we're missing all conditions
|
||||
webviewPreload.changeContent();
|
||||
|
||||
// check document.write method has not been called
|
||||
expect(spyDocumentWrite).not.toHaveBeenCalled();
|
||||
|
||||
// call init to set the webviewInfo
|
||||
await webviewPreload.init();
|
||||
|
||||
const callback: any = spyAddEventListener.mock.calls[0][1];
|
||||
|
||||
// call the callback that should call changeContent as we'll have two mandatory fields
|
||||
callback();
|
||||
|
||||
// wait timeout execute
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// check the document.write method has been called
|
||||
expect(spyDocumentWrite).toHaveBeenCalledWith(`<!DOCTYPE html>
|
||||
<html style="font-family: Montserrat;"><head></head><body>hello world</body></html>`);
|
||||
});
|
||||
|
||||
test('check buildApi', async () => {
|
||||
// spy postWebviewMessage
|
||||
const spyPostWebviewMessage = vi.spyOn(webviewPreload, 'postWebviewMessage');
|
||||
|
||||
// spy ipcInvoke
|
||||
const spyIpcInvoke = vi.spyOn(webviewPreload, 'ipcInvoke');
|
||||
spyIpcInvoke.mockImplementation(() => Promise.resolve({ result: undefined, error: undefined }));
|
||||
|
||||
// remove spy on buildApi
|
||||
spyBuildApi.mockRestore();
|
||||
|
||||
// init to set the webviewInfo
|
||||
await webviewPreload.init();
|
||||
|
||||
const buildFunction: any = webviewPreload.buildApi();
|
||||
|
||||
// call the build function
|
||||
const podmanDesktopApi = buildFunction();
|
||||
|
||||
// check the podmanDesktopApi object is returned
|
||||
//get state that should be the one from the webviewInfo
|
||||
expect(podmanDesktopApi.getState()).toStrictEqual(webviewInfo.state);
|
||||
|
||||
//post message
|
||||
podmanDesktopApi.postMessage('test');
|
||||
|
||||
expect(spyPostWebviewMessage).toHaveBeenCalledWith({ command: 'onmessage', data: 'test' });
|
||||
|
||||
// clear calls on spyIpcInvoke
|
||||
spyIpcInvoke.mockClear();
|
||||
|
||||
//set state
|
||||
const newFakeState = { updated: 'state' };
|
||||
podmanDesktopApi.setState(newFakeState);
|
||||
|
||||
// check ipcInvoke has been called
|
||||
expect(spyIpcInvoke).toHaveBeenCalledWith('webviewRegistry:update-state', webviewInfo.id, newFakeState);
|
||||
});
|
||||
147
packages/preload-webview/src/webview-preload.ts
Normal file
147
packages/preload-webview/src/webview-preload.ts
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/**********************************************************************
|
||||
* Copyright (C) 2024 Red Hat, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
***********************************************************************/
|
||||
|
||||
import type { WebviewInfo } from '../../main/src/plugin/api/webview-info';
|
||||
|
||||
import type { IpcRendererEvent } from 'electron';
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
|
||||
interface ErrorMessage {
|
||||
name: string;
|
||||
message: string;
|
||||
extra: unknown;
|
||||
}
|
||||
|
||||
export class WebviewPreload {
|
||||
#webviewId: string;
|
||||
#webviewInfo: WebviewInfo | undefined;
|
||||
#domLoaded: boolean = false;
|
||||
#acquiredApi: boolean = false;
|
||||
|
||||
constructor(webviewId: string) {
|
||||
this.#webviewId = webviewId;
|
||||
}
|
||||
|
||||
protected decodeError(error: ErrorMessage) {
|
||||
const e = new Error(error.message);
|
||||
e.name = error.name;
|
||||
Object.assign(e, error.extra);
|
||||
return e;
|
||||
}
|
||||
|
||||
protected async ipcInvoke(channel: string, ...args: unknown[]): Promise<unknown> {
|
||||
const { error, result } = await ipcRenderer.invoke(channel, ...args);
|
||||
if (error) {
|
||||
throw this.decodeError(error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected postWebviewMessage(message: unknown) {
|
||||
this.ipcInvoke('webviewRegistry:post-message', this.#webviewInfo?.id, message).catch((error: unknown) =>
|
||||
console.error('Error while posting message', error),
|
||||
);
|
||||
}
|
||||
|
||||
protected changeContent() {
|
||||
if (!this.#webviewInfo) {
|
||||
return;
|
||||
}
|
||||
if (!this.#domLoaded) {
|
||||
return;
|
||||
}
|
||||
let webviewHtmlContent = '';
|
||||
if (this.#webviewInfo) {
|
||||
webviewHtmlContent = this.#webviewInfo.html;
|
||||
}
|
||||
// use a timeout to perform the update
|
||||
setTimeout(() => {
|
||||
const webviewContentHtml = new DOMParser().parseFromString(webviewHtmlContent, 'text/html');
|
||||
|
||||
webviewContentHtml.documentElement.style.setProperty('font-family', 'Montserrat');
|
||||
|
||||
const htmlContent = '<!DOCTYPE html>\n' + webviewContentHtml.documentElement.outerHTML;
|
||||
|
||||
document.open();
|
||||
document.write(htmlContent);
|
||||
document.close();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// build the function that will be exposed to the webview for getState/postMessage/setState
|
||||
protected buildApi(): unknown {
|
||||
return () => {
|
||||
// initialize the state from the webview
|
||||
let state: unknown = this.#webviewInfo?.state ?? {};
|
||||
if (this.#acquiredApi) {
|
||||
throw new Error('An instance of the Podman Desktop API has already been acquired');
|
||||
}
|
||||
// can only be called once;
|
||||
this.#acquiredApi = true;
|
||||
return Object.freeze({
|
||||
getState: () => {
|
||||
return state;
|
||||
},
|
||||
postMessage: (msg: unknown) => {
|
||||
return this.postWebviewMessage({ command: 'onmessage', data: msg });
|
||||
},
|
||||
setState: async (newState: unknown) => {
|
||||
state = newState;
|
||||
// need to send back the state to the main process
|
||||
this.ipcInvoke('webviewRegistry:update-state', this.#webviewInfo?.id, newState).catch((error: unknown) => {
|
||||
console.error('Error while updating the state', error);
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
protected getWebviews(): Promise<WebviewInfo[]> {
|
||||
return this.ipcInvoke('webviewRegistry:listWebviews') as Promise<WebviewInfo[]>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected ipcRendererOn(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void) {
|
||||
ipcRenderer.on(channel, listener);
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
this.#domLoaded = true;
|
||||
this.changeContent();
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('acquirePodmanDesktopApi', this.buildApi());
|
||||
|
||||
const webviews: WebviewInfo[] = await this.getWebviews();
|
||||
this.#webviewInfo = webviews.find(webview => webview.id === this.#webviewId);
|
||||
this.changeContent();
|
||||
|
||||
// broadcast messages from the main process to the webview
|
||||
this.ipcRendererOn('webview-post-message', (_, target: { message: unknown }) => {
|
||||
window.dispatchEvent(new MessageEvent('message', { data: target.message }));
|
||||
});
|
||||
|
||||
this.ipcRendererOn('webview-update-html', (_, html) => {
|
||||
if (this.#webviewInfo) {
|
||||
this.#webviewInfo.html = html;
|
||||
this.changeContent();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
20
packages/preload-webview/tsconfig.json
Normal file
20
packages/preload-webview/tsconfig.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"sourceMap": false,
|
||||
"moduleResolution": "Node",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"isolatedModules": true,
|
||||
|
||||
"types": ["node"],
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"/@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "exposedInWebview.d.ts", "../../types/**/*.d.ts"]
|
||||
}
|
||||
65
packages/preload-webview/vite.config.js
Normal file
65
packages/preload-webview/vite.config.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/**********************************************************************
|
||||
* Copyright (C) 2024 Red Hat, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
***********************************************************************/
|
||||
|
||||
import { chrome } from '../../.electron-vendors.cache.json';
|
||||
import { join } from 'path';
|
||||
import { builtinModules } from 'module';
|
||||
import { coverageConfig } from '../../vitest-shared-extensions.config';
|
||||
|
||||
const PACKAGE_ROOT = __dirname;
|
||||
const PACKAGE_NAME = 'preload-webview';
|
||||
|
||||
/**
|
||||
* @type {import('vite').UserConfig}
|
||||
* @see https://vitejs.dev/config/
|
||||
*/
|
||||
const config = {
|
||||
mode: process.env.MODE,
|
||||
root: PACKAGE_ROOT,
|
||||
envDir: process.cwd(),
|
||||
resolve: {
|
||||
alias: {
|
||||
'/@/': join(PACKAGE_ROOT, 'src') + '/',
|
||||
},
|
||||
},
|
||||
build: {
|
||||
sourcemap: 'inline',
|
||||
target: `chrome${chrome}`,
|
||||
outDir: 'dist',
|
||||
assetsDir: '.',
|
||||
minify: process.env.MODE !== 'development',
|
||||
lib: {
|
||||
entry: 'src/index.ts',
|
||||
formats: ['cjs'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['electron', ...builtinModules.flatMap(p => [p, `node:${p}`])],
|
||||
output: {
|
||||
entryFileNames: '[name].cjs',
|
||||
},
|
||||
},
|
||||
emptyOutDir: true,
|
||||
reportCompressedSize: false,
|
||||
},
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
...coverageConfig(PACKAGE_ROOT, PACKAGE_NAME),
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -1,5 +1,23 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**********************************************************************
|
||||
* Copyright (C) 2022-2024 Red Hat, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
***********************************************************************/
|
||||
|
||||
import { createServer, build, createLogger } from 'vite';
|
||||
import electronPath from 'electron';
|
||||
import { spawn } from 'child_process';
|
||||
|
|
@ -141,6 +159,26 @@ const setupPreloadDockerExtensionPackageWatcher = ({ ws }) =>
|
|||
},
|
||||
});
|
||||
|
||||
const setupPreloadWebviewPackageWatcher = ({ ws }) =>
|
||||
getWatcher({
|
||||
name: 'reload-page-on-preload-webview-package-change',
|
||||
configFile: 'packages/preload-webview/vite.config.js',
|
||||
writeBundle() {
|
||||
// Generating exposedInWebview.d.ts when preload package is changed.
|
||||
generateAsync({
|
||||
input: 'packages/preload-webview/tsconfig.json',
|
||||
output: 'packages/preload-webview/exposedInWebview.d.ts',
|
||||
});
|
||||
|
||||
if (ws) {
|
||||
ws.send({
|
||||
type: 'full-reload',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Start or restart App when source files are changed
|
||||
* @param {{ws: import('vite').WebSocketServer}} WebSocketServer
|
||||
|
|
@ -192,6 +230,7 @@ const setupExtensionApiWatcher = name => {
|
|||
}
|
||||
await setupPreloadPackageWatcher(viteDevServer);
|
||||
await setupPreloadDockerExtensionPackageWatcher(viteDevServer);
|
||||
await setupPreloadWebviewPackageWatcher(viteDevServer);
|
||||
await setupMainPackageWatcher(viteDevServer);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
|
|
|||
Loading…
Reference in a new issue