feat: isolated plugin system (#2742)

This commit is contained in:
Himself65
2023-06-09 16:43:46 +08:00
committed by GitHub
parent af6f431c15
commit f2ac2e5b84
51 changed files with 489 additions and 209 deletions
+2
View File
@@ -7,6 +7,7 @@ import { registerEvents } from './events';
import { registerHandlers } from './handlers';
import { logger } from './logger';
import { restoreOrCreateWindow } from './main-window';
import { registerPlugin } from './plugin';
import { registerProtocol } from './protocol';
import { registerUpdater } from './updater';
@@ -60,6 +61,7 @@ app
.then(registerProtocol)
.then(registerHandlers)
.then(registerEvents)
.then(registerPlugin)
.then(restoreOrCreateWindow)
.then(createApplicationMenu)
.then(registerUpdater)
+57
View File
@@ -0,0 +1,57 @@
import { join } from 'node:path';
import { Worker } from 'node:worker_threads';
import { AsyncCall } from 'async-call-rpc';
import { ipcMain } from 'electron';
import { ThreadWorkerChannel } from './utils';
declare global {
// fixme(himself65):
// remove this when bookmark block plugin is migrated to plugin-infra
// eslint-disable-next-line no-var
var asyncCall: Record<string, (...args: any) => PromiseLike<any>>;
}
export async function registerPlugin() {
const pluginWorkerPath = join(__dirname, './workers/plugin.worker.js');
const asyncCall = AsyncCall<
Record<string, (...args: any) => PromiseLike<any>>
>(
{},
{
channel: new ThreadWorkerChannel(new Worker(pluginWorkerPath)),
}
);
globalThis.asyncCall = asyncCall;
await import('@toeverything/plugin-infra/manager').then(
({ rootStore, affinePluginsAtom }) => {
const bookmarkPluginPath = join(
process.env.PLUGIN_DIR ?? '../../plugins',
'./bookmark-block/index.mjs'
);
import(bookmarkPluginPath);
let dispose: () => void = () => {
// noop
};
rootStore.sub(affinePluginsAtom, () => {
dispose();
const plugins = rootStore.get(affinePluginsAtom);
Object.values(plugins).forEach(plugin => {
plugin.definition.commands.forEach(command => {
ipcMain.handle(command, (event, ...args) =>
asyncCall[command](...args)
);
});
});
dispose = () => {
Object.values(plugins).forEach(plugin => {
plugin.definition.commands.forEach(command => {
ipcMain.removeHandler(command);
});
});
};
});
}
);
}
+8 -9
View File
@@ -1,17 +1,9 @@
import { join } from 'node:path';
import { app, BrowserWindow, nativeTheme } from 'electron';
import type { NamespaceHandlers } from '../type';
import { isMacOS } from '../utils';
import { getGoogleOauthCode } from './google-auth';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const handlers = require(join(
process.env.PLUGIN_DIR ?? '../../plugins',
'./bookmark-block/server'
)).default as NamespaceHandlers;
export const uiHandlers = {
handleThemeChange: async (_, theme: (typeof nativeTheme)['themeSource']) => {
nativeTheme.themeSource = theme;
@@ -47,5 +39,12 @@ export const uiHandlers = {
getGoogleOauthCode: async () => {
return getGoogleOauthCode();
},
...handlers,
/**
* @deprecated Remove this when bookmark block plugin is migrated to plugin-infra
*/
getBookmarkDataByLink: async (_, link: string) => {
return globalThis.asyncCall[
'com.blocksuite.bookmark-block.get-bookmark-data-by-link'
](link);
},
} satisfies NamespaceHandlers;
+34
View File
@@ -1,3 +1,7 @@
import type { MessagePort, Worker } from 'node:worker_threads';
import type { EventBasedChannel } from 'async-call-rpc';
export function getTime() {
return new Date().getTime();
}
@@ -9,3 +13,33 @@ export const isMacOS = () => {
export const isWindows = () => {
return process.platform === 'win32';
};
export class ThreadWorkerChannel implements EventBasedChannel {
constructor(private worker: Worker) {}
on(listener: (data: unknown) => void) {
this.worker.addListener('message', listener);
return () => {
this.worker.removeListener('message', listener);
};
}
send(data: unknown) {
this.worker.postMessage(data);
}
}
export class MessagePortChannel implements EventBasedChannel {
constructor(private port: MessagePort) {}
on(listener: (data: unknown) => void) {
this.port.addListener('message', listener);
return () => {
this.port.removeListener('message', listener);
};
}
send(data: unknown) {
this.port.postMessage(data);
}
}
@@ -0,0 +1,43 @@
import { join } from 'node:path';
import { parentPort } from 'node:worker_threads';
import { AsyncCall } from 'async-call-rpc';
import { MessagePortChannel } from '../utils';
const commandProxy: Record<string, (...args: any[]) => Promise<any>> = {};
if (!parentPort) {
throw new Error('parentPort is undefined');
}
AsyncCall(commandProxy, {
channel: new MessagePortChannel(parentPort),
});
import('@toeverything/plugin-infra/manager').then(
({ rootStore, affinePluginsAtom }) => {
const bookmarkPluginPath = join(
process.env.PLUGIN_DIR ?? '../../../plugins',
'./bookmark-block/index.mjs'
);
import(bookmarkPluginPath);
rootStore.sub(affinePluginsAtom, () => {
const plugins = rootStore.get(affinePluginsAtom);
Object.values(plugins).forEach(plugin => {
if (plugin.serverAdapter) {
plugin.serverAdapter({
registerCommand: (command, fn) => {
console.log('register command', command);
commandProxy[command] = fn;
},
unregisterCommand: command => {
delete commandProxy[command];
},
});
}
});
});
}
);