Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

All : Feature: plugin postMessage to webview #5569

Merged
merged 4 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ export default class JoplinViewsPanels {
*
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;

/**
* Sends a message to the webview.
* The webview must have registered a listener using onMessage(), otherwise the message is ignored.
agerardin marked this conversation as resolved.
Show resolved Hide resolved
*/
agerardin marked this conversation as resolved.
Show resolved Hide resolved
public postMessage(handle: ViewHandle, message: any) : void;

/**
agerardin marked this conversation as resolved.
Show resolved Hide resolved
* Shows the panel
*/
Expand Down
14 changes: 14 additions & 0 deletions packages/app-cli/tests/support/plugins/post_messages/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));

17 changes: 17 additions & 0 deletions packages/app-desktop/services/plugins/UserWebviewIndex.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// This is the API that JS files loaded from the webview can see
const webviewApiPromises_ = {};
let cb_ = () => {};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please give a more descriptive name and add a command to explain what it does.


// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
const webviewApi = {
Expand All @@ -22,6 +23,13 @@ const webviewApi = {

return promise;
},

onMessage: function(cb) {
cb_ = cb;
window.postMessage({
target: 'postMessageService.registerCallback',
});
},
};

(function() {
Expand Down Expand Up @@ -127,8 +135,17 @@ const webviewApi = {
promise.resolve(message.response);
}
},

'postMessageService.plugin_message': (message) => {
if (!cb_) {
console.warn('postMessageService.plugin_message: could not find callback for message', message);
agerardin marked this conversation as resolved.
Show resolved Hide resolved
return;
}
cb_(message);
},
};

// respond to window.postMessage({})
window.addEventListener('message', ((event) => {
if (!event.data || event.data.target !== 'webview') return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.registerCallback') {
PostMessageService.instance().registerCallback(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_);
Expand Down
35 changes: 28 additions & 7 deletions packages/lib/services/PostMessageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -46,6 +46,8 @@ export interface MessageResponse {

type MessageResponder = (message: MessageResponse)=> void;

type Callback = (message: any)=> void;

interface Message {
pluginId: string;
contentScriptId: string;
Expand All @@ -60,6 +62,7 @@ export default class PostMessageService {

private static instance_: PostMessageService;
private responders_: Record<string, MessageResponder> = {};
private callbacks_: Record<string, Callback> = {};
agerardin marked this conversation as resolved.
Show resolved Hide resolved

public static instance(): PostMessageService {
if (this.instance_) return this.instance_;
Expand All @@ -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.callback(message);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

viewMessageHandler

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;
Expand All @@ -96,8 +99,18 @@ export default class PostMessageService {
this.sendResponse(message, response, error);
}

private callback(message: Message) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

viewMessageHandler


const callback = this.callbacks_[[ResponderComponentType.UserWebview, message.viewId].join(':')];
agerardin marked this conversation as resolved.
Show resolved Hide resolved

if (!callback) {
logger.warn('Cannot receive message because no callback was found', message);
}

callback(message.content);
agerardin marked this conversation as resolved.
Show resolved Hide resolved
}

private sendResponse(message: Message, responseContent: any, error: any) {
logger.debug('sendResponse', message, responseContent, error);

let responder: MessageResponder = null;

Expand Down Expand Up @@ -126,6 +139,14 @@ export default class PostMessageService {
this.responders_[[type, viewId].join(':')] = responder;
}

public registerCallback(type: ResponderComponentType, viewId: string, callback: Callback) {
this.callbacks_[[type, viewId].join(':')] = callback;
}

public unregisterCallback(type: ResponderComponentType, viewId: string) {
delete this.callbacks_[[type, viewId].join(':')];
}

public unregisterResponder(type: ResponderComponentType, viewId: string) {
delete this.responders_[[type, viewId].join(':')];
}
Expand Down
4 changes: 4 additions & 0 deletions packages/lib/services/plugins/ViewController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ export default class ViewController {
console.info('Calling ViewController.emitMessage - but not implemented', event);
}

public postMessage(message: any) {
console.log('Calling ViewController.postMessage - but not implemented', message);
agerardin marked this conversation as resolved.
Show resolved Hide resolved
}

}
18 changes: 18 additions & 0 deletions packages/lib/services/plugins/WebviewController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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);
}
Expand Down
8 changes: 8 additions & 0 deletions packages/lib/services/plugins/api/JoplinViewsPanels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ export default class JoplinViewsPanels {
return this.controller(handle).onMessage(callback);
}

/**
* Sends a message to the webview.
* The webview must have registered a listener using onMessage(), otherwise the message is ignored.
*/
public postMessage(handle: ViewHandle, message: any) {
return this.controller(handle).postMessage(message);
}

/**
* Shows the panel
*/
Expand Down
7 changes: 7 additions & 0 deletions packages/plugins/ToggleSidebars/api/JoplinViewsPanels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ export default class JoplinViewsPanels {
*
*/
onMessage(handle: ViewHandle, callback: Function): Promise<void>;

/**
* Sends a message to the webview.
* The webview must have registered a listener using onMessage(), otherwise the message is ignored.
*/
postMessage(handle: ViewHandle, message: any) : void;

/**
* Shows the panel
agerardin marked this conversation as resolved.
Show resolved Hide resolved
*/
Expand Down