diff --git a/CliClient/app/services/plugins/PluginRunner.ts b/CliClient/app/services/plugins/PluginRunner.ts index 1e06dc320..642914f09 100644 --- a/CliClient/app/services/plugins/PluginRunner.ts +++ b/CliClient/app/services/plugins/PluginRunner.ts @@ -54,15 +54,25 @@ export default class PluginRunner extends BasePluginRunner { }; } - async run(plugin:Plugin, sandbox:Global) { - const vmSandbox = vm.createContext(this.newSandboxProxy(plugin.id, sandbox)); + async run(plugin:Plugin, sandbox:Global):Promise { + return new Promise((resolve:Function, reject:Function) => { + const onStarted = () => { + plugin.off('started', onStarted); + resolve(); + }; - try { - vm.runInContext(plugin.scriptText, vmSandbox); - } catch (error) { - this.logger().error(`In plugin ${plugin.id}:`, error); - return; - } + plugin.on('started', onStarted); + + const vmSandbox = vm.createContext(this.newSandboxProxy(plugin.id, sandbox)); + + try { + vm.runInContext(plugin.scriptText, vmSandbox); + } catch (error) { + reject(error); + // this.logger().error(`In plugin ${plugin.id}:`, error); + // return; + } + }); } } diff --git a/CliClient/tests/services_PluginService.ts b/CliClient/tests/services_PluginService.ts index 64e00d9f9..afec92170 100644 --- a/CliClient/tests/services_PluginService.ts +++ b/CliClient/tests/services_PluginService.ts @@ -65,6 +65,9 @@ describe('services_PluginService', function() { const allFolders = await Folder.all(); expect(allFolders.length).toBe(1); + + // If you have an error here, it might mean you need to run `npm i` from + // the "withExternalModules" folder. Not clear exactly why. expect(allFolders[0].title).toBe(' foo'); })); diff --git a/ReactNativeClient/lib/services/plugins/BasePluginRunner.ts b/ReactNativeClient/lib/services/plugins/BasePluginRunner.ts index 46d383db6..3af88eaab 100644 --- a/ReactNativeClient/lib/services/plugins/BasePluginRunner.ts +++ b/ReactNativeClient/lib/services/plugins/BasePluginRunner.ts @@ -4,7 +4,7 @@ import Global from './api/Global'; export default abstract class BasePluginRunner extends BaseService { - async run(plugin:Plugin, sandbox:Global) { + async run(plugin:Plugin, sandbox:Global):Promise { throw new Error(`Not implemented: ${plugin} / ${sandbox}`); } diff --git a/ReactNativeClient/lib/services/plugins/Plugin.ts b/ReactNativeClient/lib/services/plugins/Plugin.ts index 0f25e8c6d..f7cf32f67 100644 --- a/ReactNativeClient/lib/services/plugins/Plugin.ts +++ b/ReactNativeClient/lib/services/plugins/Plugin.ts @@ -4,6 +4,7 @@ import shim from 'lib/shim'; import { ViewHandle } from './utils/createViewHandle'; import { ContentScriptType } from './api/types'; import Logger from 'lib/Logger'; +const EventEmitter = require('events'); interface ViewControllers { [key:string]: ViewController @@ -29,6 +30,7 @@ export default class Plugin { private viewControllers_:ViewControllers = {}; private contentScripts_:ContentScripts = {}; private dispatch_:Function; + private eventEmitter_:any; constructor(id:string, baseDir:string, manifest:PluginManifest, scriptText:string, logger:Logger, dispatch:Function) { this.id_ = id; @@ -37,6 +39,7 @@ export default class Plugin { this.scriptText_ = scriptText; this.logger_ = logger; this.dispatch_ = dispatch; + this.eventEmitter_ = new EventEmitter(); } public get id():string { @@ -59,11 +62,25 @@ export default class Plugin { return this.baseDir_; } - public registerContentScript(type:ContentScriptType, id:string, path:string) { + on(eventName:string, callback:Function) { + return this.eventEmitter_.on(eventName, callback); + } + + off(eventName:string, callback:Function) { + return this.eventEmitter_.removeListener(eventName, callback); + } + + emit(eventName:string, event:any = null) { + return this.eventEmitter_.emit(eventName, event); + } + + public async registerContentScript(type:ContentScriptType, id:string, path:string) { if (!this.contentScripts_[type]) this.contentScripts_[type] = []; const absolutePath = shim.fsDriver().resolveRelativePathWithinDir(this.baseDir, path); + if (!(await shim.fsDriver().exists(absolutePath))) throw new Error(`Could not find content script at path ${absolutePath}`); + this.contentScripts_[type].push({ id, path: absolutePath }); this.logger_.debug(`Plugin: ${this.id}: Registered content script: ${type}: ${id}: ${absolutePath}`); diff --git a/ReactNativeClient/lib/services/plugins/api/JoplinPlugins.ts b/ReactNativeClient/lib/services/plugins/api/JoplinPlugins.ts index 06155d3d9..116fcdaf6 100644 --- a/ReactNativeClient/lib/services/plugins/api/JoplinPlugins.ts +++ b/ReactNativeClient/lib/services/plugins/api/JoplinPlugins.ts @@ -45,6 +45,7 @@ export default class JoplinPlugins { this.logger.error(`In plugin ${this.plugin.id}:`, newError); }).then(() => { this.logger.info(`Finished running onStart handler: ${this.plugin.id} (Took ${Date.now() - startTime}ms)`); + this.plugin.emit('started'); }); } }