mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-11 18:24:43 +02:00
Plugins: Allow posting messages from plugin to webview (#5569)
This commit is contained in:
parent
200ba858dd
commit
6b31609338
@ -50,6 +50,20 @@ async function setupWebviewPanel() {
|
|||||||
console.info('PostMessagePlugin (Webview): Responding with:', response);
|
console.info('PostMessagePlugin (Webview): Responding with:', response);
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
panels.show(view, true);
|
||||||
|
|
||||||
|
var intervalID = setInterval(
|
||||||
|
() => {
|
||||||
|
console.info('check if webview is ready...');
|
||||||
|
if(panels.visible(view)) {
|
||||||
|
console.info('plugin: sending message to webview. ');
|
||||||
|
panels.postMessage(view, 'testingPluginMessage');
|
||||||
|
}
|
||||||
|
clearInterval(intervalID);
|
||||||
|
}
|
||||||
|
, 500
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
joplin.plugins.register({
|
joplin.plugins.register({
|
||||||
|
@ -5,6 +5,10 @@ document.addEventListener('click', async (event) => {
|
|||||||
|
|
||||||
console.info('webview.js: sending message');
|
console.info('webview.js: sending message');
|
||||||
const response = await webviewApi.postMessage('testingWebviewMessage');
|
const response = await webviewApi.postMessage('testingWebviewMessage');
|
||||||
console.info('webiew.js: got response:', response);
|
console.info('webview.js: got response:', response);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.info('webview.js: registering message listener');
|
||||||
|
webviewApi.onMessage((message) => console.info('webview.js: got message:', message));
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// This is the API that JS files loaded from the webview can see
|
// This is the API that JS files loaded from the webview can see
|
||||||
const webviewApiPromises_ = {};
|
const webviewApiPromises_ = {};
|
||||||
|
let viewMessageHandler_ = () => {};
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||||
const webviewApi = {
|
const webviewApi = {
|
||||||
@ -22,6 +23,13 @@ const webviewApi = {
|
|||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onMessage: function(viewMessageHandler) {
|
||||||
|
viewMessageHandler_ = viewMessageHandler;
|
||||||
|
window.postMessage({
|
||||||
|
target: 'postMessageService.registerViewMessageHandler',
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
@ -117,7 +125,7 @@ const webviewApi = {
|
|||||||
const message = event.message;
|
const message = event.message;
|
||||||
const promise = webviewApiPromises_[message.responseId];
|
const promise = webviewApiPromises_[message.responseId];
|
||||||
if (!promise) {
|
if (!promise) {
|
||||||
console.warn('postMessageService.response: could not find callback for message', message);
|
console.warn('postMessageService.response: Could not find recorded promise to process message response', message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,8 +135,17 @@ const webviewApi = {
|
|||||||
promise.resolve(message.response);
|
promise.resolve(message.response);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'postMessageService.plugin_message': (message) => {
|
||||||
|
if (!viewMessageHandler_) {
|
||||||
|
console.warn('postMessageService.plugin_message: Could not process message because no onMessage handler was defined', message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
viewMessageHandler_(message);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// respond to window.postMessage({})
|
||||||
window.addEventListener('message', ((event) => {
|
window.addEventListener('message', ((event) => {
|
||||||
if (!event.data || event.data.target !== 'webview') return;
|
if (!event.data || event.data.target !== 'webview') return;
|
||||||
|
|
||||||
|
@ -16,14 +16,23 @@ export default function(frameWindow: any, isReady: boolean, pluginId: string, vi
|
|||||||
if (!frameWindow) return () => {};
|
if (!frameWindow) return () => {};
|
||||||
|
|
||||||
function onMessage_(event: any) {
|
function onMessage_(event: any) {
|
||||||
if (!event.data || event.data.target !== 'postMessageService.message') return;
|
|
||||||
|
|
||||||
|
if (!event.data || !event.data.target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.data.target === 'postMessageService.registerViewMessageHandler') {
|
||||||
|
PostMessageService.instance().registerViewMessageHandler(ResponderComponentType.UserWebview, viewId, (message: MessageResponse) => {
|
||||||
|
postMessage('postMessageService.plugin_message', { message });
|
||||||
|
});
|
||||||
|
} else if (event.data.target === 'postMessageService.message') {
|
||||||
void PostMessageService.instance().postMessage({
|
void PostMessageService.instance().postMessage({
|
||||||
pluginId,
|
pluginId,
|
||||||
viewId,
|
viewId,
|
||||||
...event.data.message,
|
...event.data.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
frameWindow.addEventListener('message', onMessage_);
|
frameWindow.addEventListener('message', onMessage_);
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import PluginService from './plugins/PluginService';
|
|||||||
|
|
||||||
const logger = Logger.create('PostMessageService');
|
const logger = Logger.create('PostMessageService');
|
||||||
|
|
||||||
enum MessageParticipant {
|
export enum MessageParticipant {
|
||||||
ContentScript = 'contentScript',
|
ContentScript = 'contentScript',
|
||||||
Plugin = 'plugin',
|
Plugin = 'plugin',
|
||||||
UserWebview = 'userWebview',
|
UserWebview = 'userWebview',
|
||||||
@ -46,6 +46,8 @@ export interface MessageResponse {
|
|||||||
|
|
||||||
type MessageResponder = (message: MessageResponse)=> void;
|
type MessageResponder = (message: MessageResponse)=> void;
|
||||||
|
|
||||||
|
type ViewMessageHandler = (message: any)=> void;
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
contentScriptId: string;
|
contentScriptId: string;
|
||||||
@ -60,6 +62,7 @@ export default class PostMessageService {
|
|||||||
|
|
||||||
private static instance_: PostMessageService;
|
private static instance_: PostMessageService;
|
||||||
private responders_: Record<string, MessageResponder> = {};
|
private responders_: Record<string, MessageResponder> = {};
|
||||||
|
private viewMessageHandlers_: Record<string, ViewMessageHandler> = {};
|
||||||
|
|
||||||
public static instance(): PostMessageService {
|
public static instance(): PostMessageService {
|
||||||
if (this.instance_) return this.instance_;
|
if (this.instance_) return this.instance_;
|
||||||
@ -68,26 +71,26 @@ export default class PostMessageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async postMessage(message: Message) {
|
public async postMessage(message: Message) {
|
||||||
logger.debug('postMessage:', message);
|
|
||||||
|
|
||||||
let response = null;
|
let response = null;
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
|
if (message.from === MessageParticipant.Plugin && message.to === MessageParticipant.UserWebview) {
|
||||||
|
this.viewMessageHandler(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (message.from === MessageParticipant.ContentScript && message.to === MessageParticipant.Plugin) {
|
if (message.from === MessageParticipant.ContentScript && message.to === MessageParticipant.Plugin) {
|
||||||
|
|
||||||
const pluginId = PluginService.instance().pluginIdByContentScriptId(message.contentScriptId);
|
const pluginId = PluginService.instance().pluginIdByContentScriptId(message.contentScriptId);
|
||||||
if (!pluginId) throw new Error(`Could not find plugin associated with content script "${message.contentScriptId}"`);
|
if (!pluginId) throw new Error(`Could not find plugin associated with content script "${message.contentScriptId}"`);
|
||||||
response = await PluginService.instance().pluginById(pluginId).emitContentScriptMessage(message.contentScriptId, message.content);
|
response = await PluginService.instance().pluginById(pluginId).emitContentScriptMessage(message.contentScriptId, message.content);
|
||||||
|
|
||||||
} else if (message.from === MessageParticipant.UserWebview && message.to === MessageParticipant.Plugin) {
|
} else if (message.from === MessageParticipant.UserWebview && message.to === MessageParticipant.Plugin) {
|
||||||
|
|
||||||
response = await PluginService.instance().pluginById(message.pluginId).viewController(message.viewId).emitMessage({ message: message.content });
|
response = await PluginService.instance().pluginById(message.pluginId).viewController(message.viewId).emitMessage({ message: message.content });
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
throw new Error(`Unhandled message: ${JSON.stringify(message)}`);
|
throw new Error(`Unhandled message: ${JSON.stringify(message)}`);
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
@ -96,8 +99,18 @@ export default class PostMessageService {
|
|||||||
this.sendResponse(message, response, error);
|
this.sendResponse(message, response, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private viewMessageHandler(message: Message) {
|
||||||
|
|
||||||
|
const viewMessageHandler = this.viewMessageHandlers_[[ResponderComponentType.UserWebview, message.viewId].join(':')];
|
||||||
|
|
||||||
|
if (!viewMessageHandler) {
|
||||||
|
logger.warn('Cannot receive message because no viewMessageHandler was found', message);
|
||||||
|
} else {
|
||||||
|
viewMessageHandler(message.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sendResponse(message: Message, responseContent: any, error: any) {
|
private sendResponse(message: Message, responseContent: any, error: any) {
|
||||||
logger.debug('sendResponse', message, responseContent, error);
|
|
||||||
|
|
||||||
let responder: MessageResponder = null;
|
let responder: MessageResponder = null;
|
||||||
|
|
||||||
@ -126,6 +139,14 @@ export default class PostMessageService {
|
|||||||
this.responders_[[type, viewId].join(':')] = responder;
|
this.responders_[[type, viewId].join(':')] = responder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerViewMessageHandler(type: ResponderComponentType, viewId: string, callback: ViewMessageHandler) {
|
||||||
|
this.viewMessageHandlers_[[type, viewId].join(':')] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unregisterViewMessageHandler(type: ResponderComponentType, viewId: string) {
|
||||||
|
delete this.viewMessageHandlers_[[type, viewId].join(':')];
|
||||||
|
}
|
||||||
|
|
||||||
public unregisterResponder(type: ResponderComponentType, viewId: string) {
|
public unregisterResponder(type: ResponderComponentType, viewId: string) {
|
||||||
delete this.responders_[[type, viewId].join(':')];
|
delete this.responders_[[type, viewId].join(':')];
|
||||||
}
|
}
|
||||||
|
@ -44,4 +44,8 @@ export default class ViewController {
|
|||||||
console.info('Calling ViewController.emitMessage - but not implemented', event);
|
console.info('Calling ViewController.emitMessage - but not implemented', event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public postMessage(message: any) {
|
||||||
|
console.info('Calling ViewController.postMessage - but not implemented', message);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import ViewController, { EmitMessageEvent } from './ViewController';
|
|||||||
import shim from '../../shim';
|
import shim from '../../shim';
|
||||||
import { ButtonSpec, DialogResult, ViewHandle } from './api/types';
|
import { ButtonSpec, DialogResult, ViewHandle } from './api/types';
|
||||||
const { toSystemSlashes } = require('../../path-utils');
|
const { toSystemSlashes } = require('../../path-utils');
|
||||||
|
import PostMessageService, { MessageParticipant } from '../PostMessageService';
|
||||||
|
|
||||||
export enum ContainerType {
|
export enum ContainerType {
|
||||||
Panel = 'panel',
|
Panel = 'panel',
|
||||||
@ -103,7 +104,24 @@ export default class WebviewController extends ViewController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public postMessage(message: any) {
|
||||||
|
|
||||||
|
const messageId = `plugin_${Date.now()}${Math.random()}`;
|
||||||
|
|
||||||
|
void PostMessageService.instance().postMessage({
|
||||||
|
pluginId: this.pluginId,
|
||||||
|
viewId: this.handle,
|
||||||
|
contentScriptId: null,
|
||||||
|
from: MessageParticipant.Plugin,
|
||||||
|
to: MessageParticipant.UserWebview,
|
||||||
|
id: messageId,
|
||||||
|
content: message,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public async emitMessage(event: EmitMessageEvent): Promise<any> {
|
public async emitMessage(event: EmitMessageEvent): Promise<any> {
|
||||||
|
|
||||||
if (!this.messageListener_) return;
|
if (!this.messageListener_) return;
|
||||||
return this.messageListener_(event.message);
|
return this.messageListener_(event.message);
|
||||||
}
|
}
|
||||||
|
@ -44,14 +44,14 @@ export default class JoplinViewsPanels {
|
|||||||
/**
|
/**
|
||||||
* Sets the panel webview HTML
|
* Sets the panel webview HTML
|
||||||
*/
|
*/
|
||||||
public async setHtml(handle: ViewHandle, html: string) {
|
public async setHtml(handle: ViewHandle, html: string): Promise<string> {
|
||||||
return this.controller(handle).html = html;
|
return this.controller(handle).html = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds and loads a new JS or CSS files into the panel.
|
* Adds and loads a new JS or CSS files into the panel.
|
||||||
*/
|
*/
|
||||||
public async addScript(handle: ViewHandle, scriptPath: string) {
|
public async addScript(handle: ViewHandle, scriptPath: string): Promise<void> {
|
||||||
return this.controller(handle).addScript(scriptPath);
|
return this.controller(handle).addScript(scriptPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,10 +74,30 @@ export default class JoplinViewsPanels {
|
|||||||
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages) for more details.
|
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages) for more details.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public async onMessage(handle: ViewHandle, callback: Function) {
|
public async onMessage(handle: ViewHandle, callback: Function): Promise<void> {
|
||||||
return this.controller(handle).onMessage(callback);
|
return this.controller(handle).onMessage(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message to the webview.
|
||||||
|
*
|
||||||
|
* The webview must have registered a message handler prior, otherwise the message is ignored. Use;
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* webviewApi.onMessage((message) => { ... });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* - `message` can be any JavaScript object, string or number
|
||||||
|
*
|
||||||
|
* The view API may have only one onMessage handler defined.
|
||||||
|
* This method is fire and forget so no response is returned.
|
||||||
|
*
|
||||||
|
* It is particularly useful when the webview needs to react to events emitted by the plugin or the joplin api.
|
||||||
|
*/
|
||||||
|
public postMessage(handle: ViewHandle, message: any): void {
|
||||||
|
return this.controller(handle).postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the panel
|
* Shows the panel
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user