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);
|
||||
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({
|
||||
|
@ -5,6 +5,10 @@ document.addEventListener('click', async (event) => {
|
||||
|
||||
console.info('webview.js: sending message');
|
||||
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
|
||||
const webviewApiPromises_ = {};
|
||||
let viewMessageHandler_ = () => {};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
const webviewApi = {
|
||||
@ -22,6 +23,13 @@ const webviewApi = {
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
onMessage: function(viewMessageHandler) {
|
||||
viewMessageHandler_ = viewMessageHandler;
|
||||
window.postMessage({
|
||||
target: 'postMessageService.registerViewMessageHandler',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
(function() {
|
||||
@ -117,7 +125,7 @@ const webviewApi = {
|
||||
const message = event.message;
|
||||
const promise = webviewApiPromises_[message.responseId];
|
||||
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;
|
||||
}
|
||||
|
||||
@ -127,8 +135,17 @@ const webviewApi = {
|
||||
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) => {
|
||||
if (!event.data || event.data.target !== 'webview') return;
|
||||
|
||||
|
@ -16,13 +16,22 @@ export default function(frameWindow: any, isReady: boolean, pluginId: string, vi
|
||||
if (!frameWindow) return () => {};
|
||||
|
||||
function onMessage_(event: any) {
|
||||
if (!event.data || event.data.target !== 'postMessageService.message') return;
|
||||
|
||||
void PostMessageService.instance().postMessage({
|
||||
pluginId,
|
||||
viewId,
|
||||
...event.data.message,
|
||||
});
|
||||
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({
|
||||
pluginId,
|
||||
viewId,
|
||||
...event.data.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
frameWindow.addEventListener('message', onMessage_);
|
||||
|
@ -27,7 +27,7 @@ import PluginService from './plugins/PluginService';
|
||||
|
||||
const logger = Logger.create('PostMessageService');
|
||||
|
||||
enum MessageParticipant {
|
||||
export enum MessageParticipant {
|
||||
ContentScript = 'contentScript',
|
||||
Plugin = 'plugin',
|
||||
UserWebview = 'userWebview',
|
||||
@ -46,6 +46,8 @@ export interface MessageResponse {
|
||||
|
||||
type MessageResponder = (message: MessageResponse)=> void;
|
||||
|
||||
type ViewMessageHandler = (message: any)=> void;
|
||||
|
||||
interface Message {
|
||||
pluginId: string;
|
||||
contentScriptId: string;
|
||||
@ -60,6 +62,7 @@ export default class PostMessageService {
|
||||
|
||||
private static instance_: PostMessageService;
|
||||
private responders_: Record<string, MessageResponder> = {};
|
||||
private viewMessageHandlers_: Record<string, ViewMessageHandler> = {};
|
||||
|
||||
public static instance(): PostMessageService {
|
||||
if (this.instance_) return this.instance_;
|
||||
@ -68,26 +71,26 @@ export default class PostMessageService {
|
||||
}
|
||||
|
||||
public async postMessage(message: Message) {
|
||||
logger.debug('postMessage:', message);
|
||||
|
||||
let response = null;
|
||||
let error = null;
|
||||
|
||||
if (message.from === MessageParticipant.Plugin && message.to === MessageParticipant.UserWebview) {
|
||||
this.viewMessageHandler(message);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (message.from === MessageParticipant.ContentScript && message.to === MessageParticipant.Plugin) {
|
||||
|
||||
const pluginId = PluginService.instance().pluginIdByContentScriptId(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);
|
||||
|
||||
} else if (message.from === MessageParticipant.UserWebview && message.to === MessageParticipant.Plugin) {
|
||||
|
||||
response = await PluginService.instance().pluginById(message.pluginId).viewController(message.viewId).emitMessage({ message: message.content });
|
||||
|
||||
} else {
|
||||
|
||||
throw new Error(`Unhandled message: ${JSON.stringify(message)}`);
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
error = e;
|
||||
@ -96,8 +99,18 @@ export default class PostMessageService {
|
||||
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) {
|
||||
logger.debug('sendResponse', message, responseContent, error);
|
||||
|
||||
let responder: MessageResponder = null;
|
||||
|
||||
@ -126,6 +139,14 @@ export default class PostMessageService {
|
||||
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) {
|
||||
delete this.responders_[[type, viewId].join(':')];
|
||||
}
|
||||
|
@ -44,4 +44,8 @@ export default class ViewController {
|
||||
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 { ButtonSpec, DialogResult, ViewHandle } from './api/types';
|
||||
const { toSystemSlashes } = require('../../path-utils');
|
||||
import PostMessageService, { MessageParticipant } from '../PostMessageService';
|
||||
|
||||
export enum ContainerType {
|
||||
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> {
|
||||
|
||||
if (!this.messageListener_) return;
|
||||
return this.messageListener_(event.message);
|
||||
}
|
||||
|
@ -44,14 +44,14 @@ export default class JoplinViewsPanels {
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@ -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.
|
||||
*
|
||||
*/
|
||||
public async onMessage(handle: ViewHandle, callback: Function) {
|
||||
public async onMessage(handle: ViewHandle, callback: Function): Promise<void> {
|
||||
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
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user