Skip to content

Commit

Permalink
Change to a two-way async messaging between webviews and controller p…
Browse files Browse the repository at this point in the history
…ages
  • Loading branch information
khawkins committed Oct 18, 2023
1 parent 10b9f07 commit 0ce4e3b
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 55 deletions.
8 changes: 7 additions & 1 deletion resources/instructions/briefcase.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
Expand All @@ -20,6 +20,12 @@
If you've already configured a briefcase, move on to the next step.
</p>
<button id="okButton">OK</button>
<script>
const okButtonElement = document.getElementById('okButton');
okButtonElement.addEventListener('click', () => {
webviewMessaging.sendMessageRequest('okButton');
});
</script>
<script src="--- MESSAGING_SCRIPT_SRC ---"></script>
</body>
</html>
44 changes: 27 additions & 17 deletions resources/instructions/landingpage.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Landing Page Customization</title>
</head>

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Landing Page Customization</title>
</head>

<body>
<p id="message">A default landing page file (landing_page.json) has been created in the staticresources directory.
See <a href="https://help.salesforce.com/s/articleView?id=sf.salesforce_app_plus_offline_landing_page.htm&type=5">Customize The Landing Page</a>
for more information on how to modify the landing page file.
</p>
<button id="okButton">OK</button>
<script src="--- MESSAGING_SCRIPT_SRC ---"></script>
</body>

</html>
<body>
<p id="message">
A default landing page file (landing_page.json) has been created in
the staticresources directory. See
<a
href="https://help.salesforce.com/s/articleView?id=sf.salesforce_app_plus_offline_landing_page.htm&type=5"
>
Customize The Landing Page
</a>
for more information on how to modify the landing page file.
</p>
<button id="okButton">OK</button>
<script>
const okButtonElement = document.getElementById('okButton');
okButtonElement.addEventListener('click', () => {
webviewMessaging.sendMessageRequest('okButton');
});
</script>
<script src="--- MESSAGING_SCRIPT_SRC ---"></script>
</body>
</html>
8 changes: 7 additions & 1 deletion resources/instructions/projectBootstrapAcknowledgment.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
Expand All @@ -13,6 +13,12 @@
time to resume.
</p>
<button id="okButton">OK</button>
<script>
const okButtonElement = document.getElementById('okButton');
okButtonElement.addEventListener('click', () => {
webviewMessaging.sendMessageRequest('okButton');
});
</script>
<script src="--- MESSAGING_SCRIPT_SRC ---"></script>
</body>
</html>
10 changes: 9 additions & 1 deletion resources/instructions/projectBootstrapChoice.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
Expand All @@ -14,6 +14,14 @@ <h1>Welcome to the Offline App Onboarding Wizard</h1>
</p>
<button id="createNewButton">Create New Project</button>
<button id="openExistingButton">Open Existing Project</button>
<script>
for (const buttonId of ['createNewButton', 'openExistingButton']) {
const buttonElement = document.getElementById(buttonId);
buttonElement.addEventListener('click', () => {
webviewMessaging.sendMessageRequest(buttonId);
});
}
</script>
<script src="--- MESSAGING_SCRIPT_SRC ---"></script>
</body>
</html>
8 changes: 7 additions & 1 deletion resources/instructions/salesforcemobileapp.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
Expand All @@ -21,6 +21,12 @@
own.
</p>
<button id="okButton">OK</button>
<script>
const okButtonElement = document.getElementById('okButton');
okButtonElement.addEventListener('click', () => {
webviewMessaging.sendMessageRequest('okButton');
});
</script>
<script src="--- MESSAGING_SCRIPT_SRC ---"></script>
</body>
</html>
54 changes: 35 additions & 19 deletions resources/instructions/webviewMessaging.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,39 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/

const vscode = acquireVsCodeApi();
// ------------------
// Callback Messaging
// ------------------
//
// Async, callback-based messaging mechanism for clients
//
const webviewMessaging = (function () {
const vscode = acquireVsCodeApi();
let requestId = 0;
const asyncMessageCallbacks = {};

// Set up button event handlers and message passing.
const buttons = document.getElementsByTagName("button");
if (!buttons || buttons.length === 0) {
console.error("No buttons found! No event handlers will be created.");
} else {
for (const button of buttons) {
const buttonId = button.getAttribute("id");
if (!buttonId) {
console.error(
"Button has no id value! No event handler will be created."
);
} else {
button.addEventListener("click", () => {
vscode.postMessage({ button: buttonId });
});
}
}
}
window.addEventListener('message', (event) => {
const message = event.data;
if (message.callbackId && asyncMessageCallbacks[message.callbackId]) {
const callback = asyncMessageCallbacks[message.callbackId];
delete asyncMessageCallbacks[message.callbackId];
delete message.callbackId;
callback(message);
}
});

return {
sendMessageRequest: function (type, data, callback) {
let message;
if (callback) {
const asyncMessageRequestId = requestId++;
asyncMessageCallbacks[asyncMessageRequestId] = callback;

message = { type, callbackId: asyncMessageRequestId, ...data };
} else {
message = { type, ...data };
}
vscode.postMessage(message);
}
};
})();
2 changes: 1 addition & 1 deletion src/commands/wizard/authorizeCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class AuthorizeCommand {
if (!result || result.title === l10n.t('No')) {
return Promise.resolve(false);
} else {
await commands.executeCommand('sfdx.force.auth.web.login');
await commands.executeCommand('sfdx.org.login.web');
await window.showInformationMessage(
l10n.t(
"Once you've authorized your Org, click here to continue."
Expand Down
6 changes: 3 additions & 3 deletions src/commands/wizard/configureProjectCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class DefaultProjectConfigurationProcessor
'resources/instructions/projectBootstrapAcknowledgment.html',
[
{
buttonId: 'okButton',
type: 'okButton',
action: async (panel) => {
panel.dispose();
return resolve();
Expand Down Expand Up @@ -98,13 +98,13 @@ export class DefaultProjectConfigurationProcessor
'resources/instructions/projectBootstrapChoice.html',
[
{
buttonId: 'createNewButton',
type: 'createNewButton',
action: (panel) => {
createChoice(panel);
}
},
{
buttonId: 'openExistingButton',
type: 'openExistingButton',
action: (panel) => {
openChoice(panel);
}
Expand Down
54 changes: 43 additions & 11 deletions src/webviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ export const MESSAGING_SCRIPT_PATH_DEMARCATOR = '--- MESSAGING_SCRIPT_SRC ---';
export const MESSAGING_JS_PATH = 'resources/instructions/webviewMessaging.js';
const INSTRUCTION_VIEW_TYPE = 'instructionsView';

export type ButtonAction = {
buttonId: string;
action: (panel: vscode.WebviewPanel) => void;
export type WebviewMessageCallback = (responseData?: object) => void;

export type WebviewMessageHandler = {
type: string;
action: (
panel: vscode.WebviewPanel,
data?: object,
callback?: WebviewMessageCallback
) => void;
};

export class InstructionsWebviewProvider {
Expand All @@ -27,8 +33,9 @@ export class InstructionsWebviewProvider {
public showInstructionWebview(
title: string,
contentPath: string,
buttonActions: ButtonAction[]
messageHandlers: WebviewMessageHandler[]
) {
this.validateMessageHanders(messageHandlers);
const panel = vscode.window.createWebviewPanel(
INSTRUCTION_VIEW_TYPE,
title,
Expand All @@ -40,12 +47,24 @@ export class InstructionsWebviewProvider {
);

panel.webview.onDidReceiveMessage((data) => {
const clickedButtonId = data.button;
const buttonAction = buttonActions.find((action) => {
return action.buttonId === clickedButtonId;
});
if (buttonAction) {
buttonAction.action(panel);
const responsiveHandlers = messageHandlers.filter(
(messageHandler) => data.type === messageHandler.type
);
if (responsiveHandlers.length > 0) {
const handler = responsiveHandlers[0];
let callback: WebviewMessageCallback | undefined;
if (data.callbackId) {
const returnedCallbackId = data.callbackId;
delete data.callbackId;
callback = (responseData?: object) => {
const fullResponseMessage = {
callbackId: returnedCallbackId,
...responseData
};
panel.webview.postMessage(fullResponseMessage);
};
}
handler.action(panel, data, callback);
}
});

Expand Down Expand Up @@ -82,7 +101,7 @@ export class InstructionsWebviewProvider {
new InstructionsWebviewProvider(extensionUri);
provider.showInstructionWebview(title, contentPath, [
{
buttonId: 'okButton',
type: 'okButton',
action: (panel) => {
panel.dispose();
return resolve();
Expand Down Expand Up @@ -119,4 +138,17 @@ export class InstructionsWebviewProvider {
return contentPath;
}
}

private validateMessageHanders(messageHandlers: WebviewMessageHandler[]) {
const handlerMap: { [type: string]: boolean } = {};
for (const handler of messageHandlers) {
if (handlerMap[handler.type] === true) {
throw new Error(
`There can be only one message handler per type. There are at least two handlers with type '${handler.type}'.`
);
} else {
handlerMap[handler.type] = true;
}
}
}
}

0 comments on commit 0ce4e3b

Please sign in to comment.