Communication between a host page and a JupyterLite instance running in an IFrame#
When a JupyterLite instance is embedded in a website via an IFrame, it may be relevant to establish a communication channel between the host page and the instance.
In the following, we build a frontend extension that allows an instance of JupyterLite located in an IFrame to receive and process a theme change order triggered by a button on the host page. The instance is also able to send a message back to the host page.
This extension is first built for JupyterLab and then for JupyterLite.
Create a development environment and initialize the project#
It’s a good practice to create a specific environment for the development of the extension. Several methods are possible. Here we use conda, a package and environment manager. The installation procedure for conda is here.
Follow the JupyterLab tutorial to create an environment:
conda create -n jupyterlab-iframe-ext --override-channels --strict-channel-priority -c conda-forge -c nodefaults jupyterlab=3 cookiecutter nodejs jupyterlite-core
conda activate jupyterlab-iframe-ext
Create a directory in your workspace, move to this directory, then generate an extension template using cookiecutter:
cookiecutter https://github.com/jupyterlab/extension-cookiecutter-ts
Select kind:
1 - frontend
2 - server
3 - theme
Choose from 1, 2, 3 [1]: 1
author_name [My Name]:
author_email [me@test.com]:
labextension_name [myextension]: jupyterlab-iframe-bridge-example
python_name [jupyterlab-iframe-bridge-example]: jupyterlab-iframe-bridge-example
project_short_description [A JupyterLab extension.]: Communication between a host page and an instance of JupyterLab located in an IFrame
has_settings [n]: n
has_binder [n]: n
test [y]: n
repository [https://github.com/github_username/jupyterlab-iframe-bridge-example]:
Finally, install the dependencies and the extension (empty for now) into the
environment, then create a symbolic link from JupyterLab to the source code in order to
avoid running pip install
every time a modification is made:
cd jupyterlab-iframe-bridge-example
pip install -ve .
jupyter labextension develop --overwrite .
Extension development#
Modify the file jupyterlab-iframe-bridge-example/src/index.ts
with a text editor.
As the host page will ask the IFrame to change the theme, import the plugin that supports themes management:
import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application';
import { IThemeManager } from '@jupyterlab/apputils';
The code of the plugin
object must also be modified:
/**
* Initialization data for the jupyterlab-iframe-bridge-example extension.
*/
const plugin: JupyterFrontEndPlugin<void> = {
id: 'jupyterlab-iframe-bridge-example:plugin',
autoStart: true,
requires: [IThemeManager],
activate: (app: JupyterFrontEnd, themeManager: IThemeManager) => {
console.log('JupyterLab extension jupyterlab-iframe-bridge-example is activated!');
/* Incoming messages management */
window.addEventListener('message', (event) => {
if (event.data.type === 'from-host-to-iframe') {
console.log('Message received in the iframe:', event.data);
if (themeManager.theme === 'JupyterLab Dark') {
themeManager.setTheme('JupyterLab Light');
} else {
themeManager.setTheme('JupyterLab Dark');
}
}
});
/* Outgoing messages management */
const notifyThemeChanged = (): void => {
const message = { type: 'from-iframe-to-host', theme: themeManager.theme };
window.parent.postMessage(message, '*');
console.log('Message sent to the host:', message);
};
themeManager.themeChanged.connect(notifyThemeChanged);
},
};
export default plugin;
The themeManager
object implements the IThemeManager
interface whose
documentation
lists the accessible properties and methods.
Two situations are to be distinguished: the reception of a message sent from the host page and the sending of a message to the host page.
For the first situation, we use the themeManager.theme
property to identify the
current theme and the themeManager.setTheme
method to change it. The theme change is
triggered when the IFrame receives a message whose type is from-host-to-iframe
.
For the second situation, the themeManager.themeChanged
property is used. This signal
is fired when the theme has actually changed. It is used to notify the host page through
the postMessage
method
(documentation).
From JupyterLab extension to JupyterLite extension#
Start by installing the dependencies shown in the previous code:
jlpm add @jupyterlab/apputils
jlpm add @jupyterlab/application
Build JupyterLab extension:
jlpm run build
The JupyterLab extension is created! The following command checks if it is correctly loaded in the environment:
jupyter labextension list
Move to a directory where the extension should be tested and run the build of this extension for JupyterLite:
mkdir examples
cd examples
jupyter lite build --output-dir lite
The following lines show that the extension has been correctly built:
...
federated_extensions:copy:ext:jupyterlab-iframe-bridge-example
. pre_build:federated_extensions:copy:ext:jupyterlab-iframe-bridge-example
...
A jupyterlab-iframe-bridge-example/examples/lite/
directory containing everything
needed for JupyterLite to work is created (notice the presence of our extension in the
extensions
subdirectory).
Test the extension#
To test the communication between a host page and an IFrame containing JupyterLite,
create a file jupyterlab-iframe-bridge-example/examples/index.html
. Edit this file
with the following code:
<html>
<title>Example bridge between a host app and JupyterLite</title>
<body>
<script type="text/javascript">
/* Outgoing messages */
function toggle() {
window.frames.jupyterlab.postMessage({ type: 'from-host-to-iframe' });
}
/* Incoming messages */
window.addEventListener('message', (event) => {
if (event.data.type === 'from-iframe-to-host') {
document.getElementById('chosenTheme').innerText = event.data.theme;
}
});
</script>
<h2>Below is a JupyterLite site running in an IFrame</h2>
<p>
Click the following button sends a message to the JupyterLab IFrame to toggle the
theme.
</p>
<p>The IFrame indicates that the current theme is: <em id="chosenTheme"></em></p>
<input type="button" value="Toggle the JupyterLab Theme" onclick="toggle()" />
<iframe
name="jupyterlab"
src="lite/"
width="100%"
height="600px"
sandbox="allow-scripts allow-same-origin"
></iframe>
</body>
</html>
When the user clicks on the button, the toggle
function sends a message to the IFrame
via the postMessage
method. This message is intercepted by our extension which changes
the theme. Moreover, when the host page receives a message from the IFrame notifying of
an effective theme change, it displays it to the user.
In order to visualize this process, launch a minimalist server from the examples
directory:
cd examples
python -m http.server -b 127.0.0.1
In a browser, at the address http://127.0.0.1:8000
, you should be able to notice the
communication between the host page and the IFrame (refresh the browser if necessary):
In addition, the browser console should display messages similar to the following: