Skip to content

Commit

Permalink
Merge pull request #5098 from mook-as/extensions/expose-to-app
Browse files Browse the repository at this point in the history
RDX: Expose API to main window
  • Loading branch information
ericpromislow authored Jul 5, 2023
2 parents dc2a7b7 + df2a2f0 commit 120c0a1
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 24 deletions.
2 changes: 2 additions & 0 deletions pkg/rancher-desktop/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import BackendProgress from '@pkg/components/BackendProgress.vue';
import Header from '@pkg/components/Header.vue';
import Nav from '@pkg/components/Nav.vue';
import TheTitle from '@pkg/components/TheTitle.vue';
import initExtensions from '@pkg/preload/extensions';
import { ipcRenderer } from '@pkg/utils/ipcRenderer';
import { mainRoutes } from '@pkg/window';
Expand Down Expand Up @@ -74,6 +75,7 @@ export default {
},
beforeMount() {
initExtensions();
ipcRenderer.on('k8s-check-state', (event, state) => {
this.$store.dispatch('k8sManager/setK8sState', state);
});
Expand Down
31 changes: 27 additions & 4 deletions pkg/rancher-desktop/main/extensions/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ type IpcMainEventHandler<K extends keyof IpcMainInvokeEvents> =

type ReadableChildProcess = ChildProcessByStdio<null, Readable, Readable>;

/**
* EXTENSION_APP is a fake extension ID the signifies the other end is not a
* real extension, but instead is our main application.
*/
const EXTENSION_APP = '<app>';

export class ExtensionManagerImpl implements ExtensionManager {
/**
* Known extensions. Keyed by the image (excluding tag), then the tag.
Expand Down Expand Up @@ -185,6 +191,11 @@ export class ExtensionManagerImpl implements ExtensionManager {

this.setMainHandler('extensions/vm/http-fetch', async(event, config) => {
const extensionId = this.getExtensionIdFromEvent(event);

if (extensionId === EXTENSION_APP) {
throw new Error('HTTP fetch from main app not implemented yet');
}

const extension = await this.getExtension(extensionId) as ExtensionImpl;
let url: URL;

Expand Down Expand Up @@ -388,12 +399,17 @@ export class ExtensionManagerImpl implements ExtensionManager {
protected getExtensionIdFromEvent(event: IpcMainEvent | IpcMainInvokeEvent): string {
const origin = new URL(event.senderFrame.origin);

return Buffer.from(origin.hostname, 'hex').toString();
return origin.protocol === 'app:' ? EXTENSION_APP : Buffer.from(origin.hostname, 'hex').toString();
}

/** Spawn a process in the host context. */
protected async spawnHost(event: IpcMainEvent | IpcMainInvokeEvent, options: SpawnOptions): Promise<ReadableChildProcess> {
const extensionId = this.getExtensionIdFromEvent(event);

if (extensionId === EXTENSION_APP) {
throw new Error(`spawning a process from the main application is not implemented yet: ${ options.command.join(' ') }`);
}

const extension = await this.getExtension(extensionId) as ExtensionImpl;

if (!extension) {
Expand All @@ -412,10 +428,13 @@ export class ExtensionManagerImpl implements ExtensionManager {
/** Spawn a process in the docker-cli context. */
protected async spawnDockerCli(event: IpcMainEvent | IpcMainInvokeEvent, options: SpawnOptions): Promise<ReadableChildProcess> {
const extensionId = this.getExtensionIdFromEvent(event);
const extension = await this.getExtension(extensionId) as ExtensionImpl;

if (!extension) {
throw new Error(`Could not find calling extension ${ extensionId }`);
if (extensionId !== EXTENSION_APP) {
const extension = await this.getExtension(extensionId) as ExtensionImpl;

if (!extension) {
throw new Error(`Could not find calling extension ${ extensionId }`);
}
}

return this.client.runClient(
Expand All @@ -428,6 +447,10 @@ export class ExtensionManagerImpl implements ExtensionManager {
/** Spawn a process in the container context. */
protected async spawnContainer(event: IpcMainEvent | IpcMainInvokeEvent, options: SpawnOptions): Promise<ReadableChildProcess> {
const extensionId = this.getExtensionIdFromEvent(event);

if (extensionId === EXTENSION_APP) {
throw new Error(`Spawning a container command is not implemented for the main app: ${ options.command.join(' ') }`);
}
const extension = await this.getExtension(extensionId) as ExtensionImpl;

if (!extension) {
Expand Down
57 changes: 37 additions & 20 deletions pkg/rancher-desktop/preload/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ interface execProcess {
/**
* The identifier for the extension (the name of the image).
*/
const extensionId = decodeURIComponent(location.hostname.replace(/(..)/g, '%$1'));
const extensionId = location.protocol === 'app:' ? '<app>' : decodeURIComponent(location.hostname.replace(/(..)/g, '%$1'));

/**
* The processes that are waiting to complete, keyed by the process ID.
Expand Down Expand Up @@ -502,28 +502,45 @@ class Client implements v1.DockerDesktopClient {
}

export default function initExtensions(): void {
if (document.location.protocol === 'x-rd-extension:') {
switch (document.location.protocol) {
case 'x-rd-extension:': {
const hostInfo: { arch: string, hostname: string } = JSON.parse(process.argv.slice(-1).pop() ?? '{}');
const ddClient = new Client(hostInfo);

window.addEventListener('unload', () => {
function canClose(proc: execProcess): proc is execProcess & v1.ExecProcess {
return 'close' in proc;
}

for (const [id, proc] of Object.entries(outstandingProcesses)) {
if (canClose(proc)) {
try {
proc.close();
} catch (ex) {
console.debug(`failed to close process ${ id }:`, ex);
}
}
}
Electron.contextBridge.exposeInMainWorld('ddClient', new Client(hostInfo));
break;
}
case 'app:': {
console.log(process);
import('os').then(({ arch, hostname }) => {
Object.defineProperty(window, 'ddClient', {
value: new Client({ arch: arch(), hostname: hostname() }),
configurable: true,
enumerable: true,
writable: true,
});
});

Electron.contextBridge.exposeInMainWorld('ddClient', ddClient);
} else {
break;
}
default: {
console.debug(`Not adding extension API to ${ document.location.protocol }`);

return;
}
}

window.addEventListener('unload', () => {
function canClose(proc: execProcess): proc is execProcess & v1.ExecProcess {
return 'close' in proc;
}

for (const [id, proc] of Object.entries(outstandingProcesses)) {
if (canClose(proc)) {
try {
proc.close();
} catch (ex) {
console.debug(`failed to close process ${ id }:`, ex);
}
}
}
});
}

0 comments on commit 120c0a1

Please sign in to comment.