Skip to content

Commit

Permalink
Fix rendering on JupyterHub 4.1+ (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
krassowski authored Jul 30, 2024
1 parent b9fd743 commit 0149ff0
Showing 1 changed file with 50 additions and 5 deletions.
55 changes: 50 additions & 5 deletions src/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import {
IWidgetTracker
} from '@jupyterlab/apputils';

import { PageConfig } from '@jupyterlab/coreutils';

import { ServerConnection } from '@jupyterlab/services';

import {
ABCWidgetFactory,
DocumentRegistry,
Expand Down Expand Up @@ -37,11 +41,13 @@ export const IPanelPreviewTracker = new Token<IPanelPreviewTracker>(

export interface IOptions extends IFrame.IOptions {
srcdoc?: string | null;
clearSrcDocOnLoad?: boolean;
}

export class CustomIFrame extends IFrame {
constructor(options: IOptions = {}) {
super(options);
this._clearSrcDocOnLoad = options.clearSrcDocOnLoad || false;
this.srcdoc = options.srcdoc || null;
}

Expand All @@ -54,11 +60,24 @@ export class CustomIFrame extends IFrame {
const iframe = this.node.querySelector('iframe')!;
if (value !== null) {
iframe.setAttribute('srcdoc', value);
iframe.addEventListener('load', () => iframe.removeAttribute('srcdoc'));
if (this._clearSrcDocOnLoad) {
iframe.addEventListener('load', () => iframe.removeAttribute('srcdoc'));
}
}
}

get absoluteUrl() {
const iframe = this.node.querySelector('iframe')!;
return iframe.dataset.absoluteUrl ?? '';
}

set absoluteUrl(value: string) {
const iframe = this.node.querySelector('iframe')!;
iframe.dataset.absoluteUrl = value;
}

private _srcdoc!: string | null;
private _clearSrcDocOnLoad: boolean;
}

const CUSTOM_LOADER = `
Expand Down Expand Up @@ -130,11 +149,13 @@ export class PanelPreview extends DocumentWidget<
* @param options The PanelPreview instantiation options.
*/
constructor(options: PanelPreview.IOptions) {
const isJupyterHub = PageConfig.getOption('hubPrefix') !== '';
super({
...options,
content: new CustomIFrame({
srcdoc: CUSTOM_LOADER,
sandbox: ['allow-same-origin', 'allow-scripts', 'allow-downloads']
sandbox: ['allow-same-origin', 'allow-scripts', 'allow-downloads'],
clearSrcDocOnLoad: !isJupyterHub
})
});

Expand Down Expand Up @@ -164,13 +185,37 @@ export class PanelPreview extends DocumentWidget<

const { getPanelUrl, context, renderOnSave } = options;

this.content.url = getPanelUrl(context.path);
const settings = ServerConnection.makeSettings();

const populateIframe = async (path: string) => {
const panelUrl = getPanelUrl(path);
if (isJupyterHub && panelUrl.startsWith(settings.baseUrl)) {
// if running on JupyterHub and the panel preview is served
// from the same domain as JupyterHub, the CSP will forbid
// embedding the page handled by JupyterHub in the iframe,
// thus we need to set the content via the `srcdoc` attribute.
const response = await ServerConnection.makeRequest(
panelUrl,
{},
settings
);
this.content.srcdoc = await response.text();
// Bokeh needs to know the absolute URL to determine the appropriate
// protocol and URL for the websocket; this is set as a data attribute.
this.content.absoluteUrl = panelUrl;
} else {
// this won't work in JupyterHub 4.1+ without relaxing CSP
this.content.url = panelUrl;
}
};

void populateIframe(context.path);
this.content.title.icon = panelIcon;

this._renderOnSave = renderOnSave ?? false;

context.pathChanged.connect(() => {
this.content.url = getPanelUrl(context.path);
context.pathChanged.connect(async () => {
await populateIframe(context.path);
});

const reloadButton = new ToolbarButton({
Expand Down

0 comments on commit 0149ff0

Please sign in to comment.