feat: Initial clean release of CHM
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
这个提交包含在:
@@ -0,0 +1,216 @@
|
||||
import Plugin from '@joplin/lib/services/plugins/Plugin';
|
||||
import BasePluginRunner from '@joplin/lib/services/plugins/BasePluginRunner';
|
||||
import executeSandboxCall from '@joplin/lib/services/plugins/utils/executeSandboxCall';
|
||||
import Global from '@joplin/lib/services/plugins/api/Global';
|
||||
import bridge from '../bridge';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { EventHandlers } from '@joplin/lib/services/plugins/utils/mapEventHandlersToIds';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import getPathToExecutable7Zip from '../../utils/7zip/getPathToExecutable7Zip';
|
||||
import getAssetPath from '../../utils/getAssetPath';
|
||||
// import BackOffHandler from './BackOffHandler';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
const logger = Logger.create('PluginRunner');
|
||||
|
||||
// Electron error messages are useless so wrap the renderer call and print
|
||||
// additional information when an error occurs.
|
||||
function ipcRendererSend(message: string, args: unknown) {
|
||||
try {
|
||||
return ipcRenderer.send(message, args);
|
||||
} catch (error) {
|
||||
logger.error('Could not send IPC message:', message, ': ', args, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
enum PluginMessageTarget {
|
||||
MainWindow = 'mainWindow',
|
||||
Plugin = 'plugin',
|
||||
}
|
||||
|
||||
export interface PluginMessage {
|
||||
target: PluginMessageTarget;
|
||||
pluginId: string;
|
||||
callbackId?: string;
|
||||
path?: string;
|
||||
args?: unknown[];
|
||||
result?: unknown;
|
||||
error?: unknown;
|
||||
mainWindowCallbackId?: string;
|
||||
}
|
||||
|
||||
interface CallbackPromise {
|
||||
resolve: (value: unknown)=> void;
|
||||
reject: (reason: unknown)=> void;
|
||||
}
|
||||
|
||||
let callbackIndex = 1;
|
||||
const callbackPromises: Record<string, CallbackPromise> = {};
|
||||
|
||||
function mapEventIdsToHandlers(pluginId: string, arg: unknown): unknown {
|
||||
if (Array.isArray(arg)) {
|
||||
for (let i = 0; i < arg.length; i++) {
|
||||
arg[i] = mapEventIdsToHandlers(pluginId, arg[i]);
|
||||
}
|
||||
return arg;
|
||||
} else if (typeof arg === 'string' && arg.indexOf('___plugin_event_') === 0) {
|
||||
const eventId = arg;
|
||||
|
||||
return async (...args: unknown[]) => {
|
||||
const callbackId = `cb_${pluginId}_${Date.now()}_${callbackIndex++}`;
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
callbackPromises[callbackId] = { resolve, reject };
|
||||
});
|
||||
|
||||
ipcRendererSend('pluginMessage', {
|
||||
callbackId: callbackId,
|
||||
target: PluginMessageTarget.Plugin,
|
||||
pluginId: pluginId,
|
||||
eventId: eventId,
|
||||
args: args,
|
||||
});
|
||||
|
||||
return promise;
|
||||
};
|
||||
} else if (arg === null) {
|
||||
return null;
|
||||
} else if (arg === undefined) {
|
||||
return undefined;
|
||||
} else if (typeof arg === 'object') {
|
||||
const argAsRecord = arg as Record<string, unknown>;
|
||||
for (const n in argAsRecord) {
|
||||
argAsRecord[n] = mapEventIdsToHandlers(pluginId, argAsRecord[n]);
|
||||
}
|
||||
}
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
export default class PluginRunner extends BasePluginRunner {
|
||||
|
||||
protected eventHandlers_: EventHandlers = {};
|
||||
// private backOffHandlers_: Record<string, BackOffHandler> = {};
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
this.eventHandler = this.eventHandler.bind(this);
|
||||
}
|
||||
|
||||
private async eventHandler(eventHandlerId: string, args: unknown[]) {
|
||||
const cb = this.eventHandlers_[eventHandlerId];
|
||||
return cb(...args);
|
||||
}
|
||||
|
||||
// private backOffHandler(pluginId: string): BackOffHandler {
|
||||
// if (!this.backOffHandlers_[pluginId]) {
|
||||
// this.backOffHandlers_[pluginId] = new BackOffHandler(pluginId);
|
||||
// }
|
||||
// return this.backOffHandlers_[pluginId];
|
||||
// }
|
||||
|
||||
public async run(plugin: Plugin, pluginApi: Global) {
|
||||
const scriptPath = `${Setting.value('tempDir')}/plugin_${plugin.id}.js`;
|
||||
await shim.fsDriver().writeFile(scriptPath, plugin.scriptText, 'utf8');
|
||||
|
||||
const pluginWindow = bridge().newBrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
require('@electron/remote/main').enable(pluginWindow.webContents);
|
||||
|
||||
bridge().electronApp().registerPluginWindow(plugin.id, pluginWindow);
|
||||
|
||||
const libraryData = {
|
||||
pathTo7za: await getPathToExecutable7Zip(),
|
||||
};
|
||||
|
||||
void pluginWindow.loadURL(`${require('url').format({
|
||||
pathname: getAssetPath('services/plugins/plugin_index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true,
|
||||
})}?pluginId=${encodeURIComponent(plugin.id)}&pluginScript=${encodeURIComponent(`file://${scriptPath}`)}&libraryData=${encodeURIComponent(JSON.stringify(libraryData))}`);
|
||||
|
||||
if (plugin.devMode) {
|
||||
pluginWindow.webContents.once('dom-ready', () => {
|
||||
// Need to open with a delay, otherwise it doesn't show up
|
||||
setTimeout(() => {
|
||||
pluginWindow.webContents.openDevTools({ mode: 'detach' });
|
||||
}, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
ipcRenderer.on('pluginMessage', async (_event: import('electron').IpcRendererEvent, message: PluginMessage) => {
|
||||
if (message.target !== PluginMessageTarget.MainWindow) return;
|
||||
if (message.pluginId !== plugin.id) return;
|
||||
|
||||
if (message.mainWindowCallbackId) {
|
||||
const callbackId = message.mainWindowCallbackId;
|
||||
const promise = callbackPromises[callbackId];
|
||||
|
||||
if (!promise) {
|
||||
console.error('Got a callback without matching promise: ', message);
|
||||
return;
|
||||
}
|
||||
|
||||
delete callbackPromises[callbackId];
|
||||
|
||||
if (message.error) {
|
||||
promise.reject(message.error);
|
||||
} else {
|
||||
promise.resolve(message.result);
|
||||
}
|
||||
} else {
|
||||
// Handle notification that a plugin failed to start before
|
||||
// calling register(). Emit 'started' so allPluginsStarted
|
||||
// is not blocked. See: https://github.com/laurent22/joplin/issues/12793
|
||||
if (message.path === '__pluginFailedToStart__') {
|
||||
logger.error(`Plugin "${plugin.id}" failed to start:`, message.args?.[0]);
|
||||
plugin.running = false;
|
||||
plugin.emit('started');
|
||||
return;
|
||||
}
|
||||
|
||||
const mappedArgs = mapEventIdsToHandlers(plugin.id, message.args);
|
||||
const fullPath = `joplin.${message.path}`;
|
||||
|
||||
// Don't log complete HTML code, which can be long, for setHtml calls
|
||||
const debugMappedArgs = fullPath.includes('setHtml') || fullPath.includes('imaging') ? '<hidden>' : mappedArgs;
|
||||
logger.debug(`Got message (3): ${fullPath}`, debugMappedArgs);
|
||||
|
||||
this.recordCallStat(plugin.id);
|
||||
|
||||
// try {
|
||||
// await this.backOffHandler(plugin.id).wait(fullPath, debugMappedArgs);
|
||||
// } catch (error) {
|
||||
// logger.error(error);
|
||||
// return;
|
||||
// }
|
||||
|
||||
let result: unknown = null;
|
||||
let error: Error | null = null;
|
||||
try {
|
||||
result = await executeSandboxCall(plugin.id, pluginApi, fullPath, mappedArgs as unknown[], this.eventHandler);
|
||||
} catch (e) {
|
||||
error = e ? (e as Error) : new Error('Unknown error');
|
||||
}
|
||||
|
||||
ipcRendererSend('pluginMessage', {
|
||||
target: PluginMessageTarget.Plugin,
|
||||
pluginId: plugin.id,
|
||||
pluginCallbackId: message.callbackId,
|
||||
result: result,
|
||||
error: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户