Skip to content

Instantly share code, notes, and snippets.

@bmingles
Last active May 28, 2025 17:54
Show Gist options
  • Save bmingles/da24e80f27a90bcbd732398483873b5d to your computer and use it in GitHub Desktop.
Save bmingles/da24e80f27a90bcbd732398483873b5d to your computer and use it in GitHub Desktop.
Deephaven Theme - Postmessage Apis
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="icon"
type="image/png"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAA9klEQVRYhe3WMUoDQRSH8e+tkw0bdhtj7wXSmUSIVboUVjYeIIU5hOIVBG8SUppSUBAstDSQFKkEWbOQBLPseoR5KWQt3tQf//nBNCO9y8UbihO14/dGP2n5OqFcjtv1gWYTwAHeUYAiL740bYkk2ssBgn3ivzgGMIABKgc4bVhkxbwUPnxdnGXfq+7VULNZc/mDGvAz275OTsI7X7fpDY9zDuaazV0eXFT+BAYwgAEMIOeP20wTXtdunjrhy6k3LFmko9aZZvMwaq6d1INYE0fBLgT8H04haT7frzSb8A+ewAAGMEDlAAdyqwk/OUqBqTcU0n0Av+DQNksxGrVgAAAAAElFTkSuQmCC"
/>
<title>Deephaven Iframe Tester</title>
<style>
html,
body {
background-color: midnightblue;
margin: 0;
font-family: sans-serif;
}
body::before {
color: white;
font-size: 2rem;
position: absolute;
top: 0;
display: flex;
align-items: center;
justify-content: center;
content: 'Parent Container';
width: 100%;
height: 100%;
}
iframe {
display: block;
position: relative;
width: 100vw;
height: 100vh;
border: none;
}
#btnUpdateTheme {
display: flex;
align-items: center;
gap: 0.5rem;
position: absolute;
bottom: 0;
padding: 0.5rem 1rem 0.5rem 0.5rem;
&:before {
content: '';
background: no-repeat left center
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAA9klEQVRYhe3WMUoDQRSH8e+tkw0bdhtj7wXSmUSIVboUVjYeIIU5hOIVBG8SUppSUBAstDSQFKkEWbOQBLPseoR5KWQt3tQf//nBNCO9y8UbihO14/dGP2n5OqFcjtv1gWYTwAHeUYAiL740bYkk2ssBgn3ivzgGMIABKgc4bVhkxbwUPnxdnGXfq+7VULNZc/mDGvAz275OTsI7X7fpDY9zDuaazV0eXFT+BAYwgAEMIOeP20wTXtdunjrhy6k3LFmko9aZZvMwaq6d1INYE0fBLgT8H04haT7frzSb8A+ewAAGMEDlAAdyqwk/OUqBqTcU0n0Av+DQNksxGrVgAAAAAElFTkSuQmCC);
background-size: cover;
width: 20px;
height: 20px;
}
}
</style>
</head>
<body>
<iframe
src="http://localhost:10000/iframe/widget/?name=simple_ticking&theme=external-theme&preloadTransparentTheme=true"
></iframe>
<button id="btnUpdateTheme" onclick="onUpdateClick()">Update Theme</button>
<script>
// Get the origin of the Deephaven server
const dhServerOrigin = new URL(document.querySelector('iframe').src)
.origin;
let baseThemeKey = 'default-dark';
/** Handle postMessage events from the child iframe */
window.addEventListener(
'message',
function onMessage({ data, origin, source }) {
// Ignore messages that are not from our Deephaven server
if (origin !== dhServerOrigin) {
return;
}
// Respond to theme request from Deephaven in the iframe
if (
data.message ===
'io.deephaven.message.ThemeModel.requestExternalTheme'
) {
// Set a custom background color based on the base theme key
const bgColor =
baseThemeKey === 'default-dark' ? 'midnightblue' : 'aliceblue';
source.postMessage(
{
id: data.id,
payload: {
name: 'Iframe External Theme',
baseThemeKey,
cssVars: {
'--dh-color-grid-header-bg': bgColor,
'--dh-color-grid-bg': `color-mix(in srgb, ${bgColor} 70%, black 50%)`,
},
},
},
origin
);
}
}
);
/** Initiate setting the theme from the parent window */
function onUpdateClick() {
const iframeEl = document.querySelector('iframe');
const childWindow = iframeEl.contentWindow;
// Base theme key determines whether base colors are for dark or light theme
baseThemeKey =
baseThemeKey === 'default-dark' ? 'default-light' : 'default-dark';
// Set a custom background color based on the base theme key
const bgColor =
baseThemeKey === 'default-dark' ? 'midnightblue' : 'aliceblue';
childWindow?.postMessage(
{
message: 'io.deephaven.message.ThemeModel.requestSetTheme',
payload: {
name: 'Iframe External Theme',
baseThemeKey,
cssVars: {
'--dh-color-grid-header-bg': bgColor,
'--dh-color-grid-bg': `color-mix(in srgb, ${bgColor} 70%, black 50%)`,
},
},
},
dhServerOrigin
);
}
/** Generate a random hex color */
function getRandomHexColor() {
return `#${Math.floor(Math.random() * 16777215)
.toString(16)
.padStart(6, '0')}`;
}
/** Get light or dark base theme key based on given background hex color */
function getBaseThemeKey(bgHex) {
// Remove '#' if present
bgHex = bgHex.replace('#', '');
// Parse r, g, b values
const r = parseInt(bgHex.substr(0, 2), 16);
const g = parseInt(bgHex.substr(2, 2), 16);
const b = parseInt(bgHex.substr(4, 2), 16);
// Calculate brightness (YIQ formula)
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
// Return black for light backgrounds, white for dark backgrounds
return yiq >= 128 ? 'default-light' : 'default-dark';
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="icon"
type="image/png"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAA9klEQVRYhe3WMUoDQRSH8e+tkw0bdhtj7wXSmUSIVboUVjYeIIU5hOIVBG8SUppSUBAstDSQFKkEWbOQBLPseoR5KWQt3tQf//nBNCO9y8UbihO14/dGP2n5OqFcjtv1gWYTwAHeUYAiL740bYkk2ssBgn3ivzgGMIABKgc4bVhkxbwUPnxdnGXfq+7VULNZc/mDGvAz275OTsI7X7fpDY9zDuaazV0eXFT+BAYwgAEMIOeP20wTXtdunjrhy6k3LFmko9aZZvMwaq6d1INYE0fBLgT8H04haT7frzSb8A+ewAAGMEDlAAdyqwk/OUqBqTcU0n0Av+DQNksxGrVgAAAAAElFTkSuQmCC"
/>
<title>Deephaven Iframe Tester</title>
<style>
html,
body {
position: relative;
background-color: midnightblue;
margin: 0;
height: 100vh;
font-family: sans-serif;
}
body::before {
position: absolute;
color: white;
font-size: 2rem;
top: 0;
display: flex;
align-items: center;
justify-content: center;
content: 'Loading...';
width: 100%;
height: 100%;
}
#loginForm {
display: grid;
position: absolute;
background-color: midnightblue;
color: white;
grid-template-rows: auto auto;
grid-auto-flow: column;
align-content: center;
align-items: center;
justify-content: center;
gap: 6px;
height: 100vh;
width: 100vw;
img {
grid-row: span 2;
height: 110px;
margin: -20px;
}
input {
height: 24px;
&:first-of-type {
grid-column: span 2;
}
}
button {
grid-row: span 2;
height: 66px;
padding: 0 1rem;
}
}
iframe {
display: block;
position: relative;
width: 100vw;
height: 100vh;
border: none;
}
#btnUpdateTheme {
display: flex;
align-items: center;
gap: 0.5rem;
position: absolute;
bottom: 0;
padding: 0.5rem 1rem 0.5rem 0.5rem;
&:before {
content: '';
background: no-repeat left center
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAA9klEQVRYhe3WMUoDQRSH8e+tkw0bdhtj7wXSmUSIVboUVjYeIIU5hOIVBG8SUppSUBAstDSQFKkEWbOQBLPseoR5KWQt3tQf//nBNCO9y8UbihO14/dGP2n5OqFcjtv1gWYTwAHeUYAiL740bYkk2ssBgn3ivzgGMIABKgc4bVhkxbwUPnxdnGXfq+7VULNZc/mDGvAz275OTsI7X7fpDY9zDuaazV0eXFT+BAYwgAEMIOeP20wTXtdunjrhy6k3LFmko9aZZvMwaq6d1INYE0fBLgT8H04haT7frzSb8A+ewAAGMEDlAAdyqwk/OUqBqTcU0n0Av+DQNksxGrVgAAAAAElFTkSuQmCC);
background-size: cover;
width: 20px;
height: 20px;
}
}
</style>
</head>
<body>
<form id="loginForm">
<img
src="data:image/svg+xml;charset=utf-8,%3Csvg%20width%3D%22150%22%20height%3D%22150%22%20viewBox%3D%220%200%20150%20150%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Cpath%20d%3D%22M60%2090H30V120H60V90Z%22%20fill%3D%22%234DCCFA%22%2F%3E%0A%3Cpath%20d%3D%22M90%2030H60V60H90V30Z%22%20fill%3D%22%234DCCFA%22%2F%3E%0A%3Cpath%20d%3D%22M90%2090H60V120H90V90Z%22%20fill%3D%22%23FDD041%22%2F%3E%0A%3Cpath%20d%3D%22M120%2060H90V90H120V60Z%22%20fill%3D%22%23F33666%22%2F%3E%0A%3Cpath%20d%3D%22M30%2030H60V90H30V30Z%22%20fill%3D%22%233D51B3%22%2F%3E%0A%3C%2Fsvg%3E%0A"
/>
<input
autofocus
type="text"
name="serverUrl"
placeholder="Deephaven Server Url"
required
/>
<input type="text" name="username" placeholder="Username" required />
<input type="password" name="password" placeholder="Password" required />
<button type="submit">Login</button>
</form>
<script type="module">
let dhServerUrl; // will be set at login
let baseThemeKey = 'default-dark';
let refreshToken;
// Get DOM elements
const loginForm = document.getElementById('loginForm');
// Register event listeners
loginForm.addEventListener('submit', onLogin);
window.addEventListener('message', onMessage);
/** Handle login form submission */
async function onLogin(event) {
event.preventDefault();
const formData = new FormData(event.target);
dhServerUrl = new URL(formData.get('serverUrl'));
const username = formData.get('username');
const password = formData.get('password');
// Disable the login form elements
Array.from(loginForm.elements).forEach((el) => {
el.disabled = true;
});
await loadJsapi(dhServerUrl);
refreshToken = await getRefreshToken(
dhServerUrl.host,
username,
password
);
loginForm.remove();
await loadIframe(dhServerUrl);
createThemeButton();
}
/** Handle postMessage events from the child iframe */
function onMessage({ data, origin, source }) {
// Ignore messages that are not from our Deephaven server
if (origin !== dhServerUrl.origin) {
return;
}
// Auth token request
if (
data.message === 'io.deephaven.message.IframeContent.authTokenRequest'
) {
source.postMessage({ id: data.id, payload: refreshToken }, origin);
return;
}
// Settings request
if (
data.message === 'io.deephaven.message.IframeContent.settingsRequest'
) {
const newWorkerName = `IframeContentTester - Create Worker - ${crypto.randomUUID()}`;
source.postMessage(
{
id: data.id,
payload: {
newWorkerName,
settings: {
heapSize: 0.5,
language: 'python',
},
isLegacyWorkerKindSupported: false,
showHeader: false,
},
},
origin
);
return;
}
// Theme request
if (
data.message ===
'io.deephaven.message.ThemeModel.requestExternalTheme'
) {
// Set a custom background color based on the base theme key
const bgColor =
baseThemeKey === 'default-dark' ? 'midnightblue' : 'aliceblue';
source.postMessage(
{
id: data.id,
payload: {
name: 'Iframe External Theme',
baseThemeKey,
cssVars: {
'--dh-color-bg': bgColor,
},
},
},
origin
);
return;
}
console.warn('Unsupported message:', data.message);
}
/** Dynamically load the jsapi from the Deephaven server */
async function loadJsapi(dhServerUrl) {
const { promise, resolve } = Promise.withResolvers();
const scriptUrl = new URL(
'irisapi/irisapi.nocache.js',
dhServerUrl.origin
);
const jsApiScript = document.createElement('script');
jsApiScript.src = scriptUrl;
jsApiScript.onload = resolve;
document.body.appendChild(jsApiScript);
return promise;
}
/** Load Deephaven in an iframe */
async function loadIframe(dhServerUrl) {
const { promise, resolve } = Promise.withResolvers();
const iframeUrl = new URL(
'iriside/iframecontent/createworker/?theme=external-theme&preloadTransparentTheme=true',
dhServerUrl.origin
);
const iframe = document.createElement('iframe');
iframe.src = iframeUrl.href;
iframe.onload = resolve;
document.body.prepend(iframe);
return promise;
}
/** Create a button to update the theme */
function createThemeButton() {
const button = document.createElement('button');
button.id = 'btnUpdateTheme';
button.innerText = 'Update Theme';
button.addEventListener('click', onUpdateClick);
document.body.appendChild(button);
}
/** Login and get refresh token */
async function getRefreshToken(host, username, token) {
const wsUrl = `wss://${host}/socket`;
const client = new iris.Client(wsUrl);
// Wait for client to connect
await new Promise((resolve) =>
client.addEventListener(iris.Client.EVENT_CONNECT, resolve)
);
// Register for refresh token updates before loggin in
const refreshTokenPromise = new Promise((resolve) => {
client.addEventListener(
iris.Client.EVENT_REFRESH_TOKEN_UPDATED,
(event) => {
// Destructure the refresh token so that we have a serializable
// object to send to DH
const { bytes, expiry } = event.detail;
resolve({ bytes, expiry });
}
);
});
await client.login({ username, token, type: 'password' });
return refreshTokenPromise;
}
/** Initiate setting the theme from the parent window */
function onUpdateClick() {
const iframeEl = document.querySelector('iframe');
const childWindow = iframeEl.contentWindow;
// Base theme key determines whether base colors are for dark or light theme
baseThemeKey =
baseThemeKey === 'default-dark' ? 'default-light' : 'default-dark';
// Set a custom background color based on the base theme key
const bgColor =
baseThemeKey === 'default-dark' ? 'midnightblue' : 'aliceblue';
childWindow?.postMessage(
{
message: 'io.deephaven.message.ThemeModel.requestSetTheme',
payload: {
name: 'Iframe External Theme',
baseThemeKey,
cssVars: {
'--dh-color-bg': bgColor,
},
},
},
dhServerUrl.origin
);
}
/** Generate a random hex color */
function getRandomHexColor() {
return `#${Math.floor(Math.random() * 16777215)
.toString(16)
.padStart(6, '0')}`;
}
/** Get light or dark base theme key based on given background hex color */
function getBaseThemeKey(bgHex) {
// Remove '#' if present
bgHex = bgHex.replace('#', '');
// Parse r, g, b values
const r = parseInt(bgHex.substr(0, 2), 16);
const g = parseInt(bgHex.substr(2, 2), 16);
const b = parseInt(bgHex.substr(4, 2), 16);
// Calculate brightness (YIQ formula)
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
// Return black for light backgrounds, white for dark backgrounds
return yiq >= 128 ? 'default-light' : 'default-dark';
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment