Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions integrations/visitor-claims-webframe-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Visitor Claims Webframe Demo

Dev/test integration for validating that GitBook/GBX passes adaptive visitor context into ContentKit webframes.

The integration exposes one block, `visitorClaimsWebframeDemo`, which renders a self-contained webframe at `/webframe`. The webframe displays:

- The full posted ContentKit webframe state.
- `state.visitor`.
- `state.visitor.claims`.
- `state.visitor.isSet`.
- Regular webframe `data` values, including `label` and `mode`, as part of the full state.

## Test

1. Enable and configure adaptive content for the target site/space.
2. Install this integration in the target space.
3. Add the **Visitor Claims Demo** integration block to a space page.
4. Set visitor input/test values in the GitBook UI.
5. Confirm the webframe displays `visitor.claims` and `visitor.isSet`.
6. Open the browser console and check the `visitor-claims-webframe-demo: received state` log for the raw state object.

If the webframe receives state without visitor context, it shows `No visitor context received.` while still rendering the full state JSON for debugging.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions integrations/visitor-claims-webframe-demo/gitbook-manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: visitor-claims-webframe-demo
title: Visitor Claims Webframe Demo
icon: ./assets/icon.png
organization: QkobkeMSkvQjA19eKq3n
description: Dev/test integration for inspecting ContentKit webframe visitor context.
summary: |
# Overview

This development-only integration renders a ContentKit webframe and displays the full state GitBook posts into it.

# How it works

Add the Visitor Claims Webframe Demo block to a space page. The webframe displays regular ContentKit webframe data alongside any visitor context injected by GitBook, including `visitor.claims` and `visitor.isSet`.
visibility: private
script: ./src/index.tsx
scopes: ['site:visitor:claims']
blocks:
- id: visitorClaimsWebframeDemo
title: Visitor Claims Demo
description: Inspect visitor claims passed into a ContentKit webframe.
21 changes: 21 additions & 0 deletions integrations/visitor-claims-webframe-demo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@gitbook/integration-visitor-claims-webframe-demo",
"version": "0.0.0",
"private": true,
"dependencies": {
"@gitbook/api": "*",
"@gitbook/runtime": "*",
"itty-router": "^2.6.1"
},
"devDependencies": {
"@gitbook/cli": "workspace:*",
"@gitbook/tsconfig": "workspace:*"
},
"scripts": {
"typecheck": "tsc --noEmit",
"check": "gitbook check",
"publish-integrations": "gitbook publish .",
"publish-integrations-staging": "gitbook publish .",
"dev": "gitbook dev ."
}
}
66 changes: 66 additions & 0 deletions integrations/visitor-claims-webframe-demo/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Router } from 'itty-router';

import {
createComponent,
createIntegration,
FetchEventCallback,
RuntimeContext,
} from '@gitbook/runtime';

import { webFrameHTML } from './webframe';

const visitorClaimsWebframeDemo = createComponent({
componentId: 'visitorClaimsWebframeDemo',
async render(_element, { environment }) {
const webframeURL = new URL(`${environment.integration.urls.publicEndpoint}/webframe`);
webframeURL.searchParams.set('v', String(environment.integration.version));

return (
<block>
<webframe
source={{
url: webframeURL.toString(),
}}
data={{
label: 'Visitor claims webframe demo',
mode: 'dev-test',
}}
/>
</block>
);
},
});

const handleFetchEvent: FetchEventCallback<RuntimeContext> = async (request, context) => {
const { environment } = context;
const router = Router({
base: new URL(
environment.spaceInstallation?.urls?.publicEndpoint ||
environment.installation?.urls.publicEndpoint ||
environment.integration.urls.publicEndpoint,
).pathname,
});

router.get(
'/webframe',
async () =>
new Response(webFrameHTML, {
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-store',
},
}),
);

const response = await router.handle(request, context);
if (!response) {
return new Response('No route matching', { status: 404 });
}

return response;
};

export default createIntegration({
fetch: handleFetchEvent,
components: [visitorClaimsWebframeDemo],
});
215 changes: 215 additions & 0 deletions integrations/visitor-claims-webframe-demo/src/webframe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
export const webFrameHTML: string = `
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<title>Visitor claims webframe demo</title>
<style>
:root {
color-scheme: light dark;
font-family:
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif;
background: #f8fafc;
color: #111827;
}

* {
box-sizing: border-box;
}

body {
margin: 0;
padding: 16px;
background: #f8fafc;
}

main {
display: grid;
gap: 12px;
max-width: 960px;
}

h1,
h2,
p {
margin: 0;
}

h1 {
font-size: 18px;
font-weight: 700;
}

h2 {
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
color: #4b5563;
}

.panel {
border: 1px solid #d1d5db;
border-radius: 8px;
background: #ffffff;
padding: 14px;
}

.status {
display: grid;
gap: 4px;
border-left: 4px solid #f59e0b;
}

.status.has-visitor {
border-left-color: #22c55e;
}

.status-title {
font-weight: 700;
}

.status-detail {
color: #4b5563;
font-size: 13px;
}

.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 12px;
}

pre {
margin: 10px 0 0;
padding: 12px;
overflow: auto;
border-radius: 6px;
background: #111827;
color: #f9fafb;
font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
white-space: pre-wrap;
word-break: break-word;
}

code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
:root,
body {
background: #030712;
color: #f9fafb;
}

.panel {
background: #111827;
border-color: #374151;
}

h2,
.status-detail {
color: #d1d5db;
}

pre {
background: #030712;
color: #f9fafb;
border: 1px solid #374151;
}
}
</style>
</head>
<body>
<main>
<section class="panel">
<h1>Visitor claims webframe demo</h1>
</section>

<section class="panel status" data-status-panel>
<p class="status-title" data-status>No webframe state received yet.</p>
</section>

<section class="panel">
<h2>Full webframe state</h2>
<pre data-state>{}</pre>
</section>

<section class="grid">
<article class="panel">
<h2>state.visitor</h2>
<pre data-visitor>null</pre>
</article>
</section>


</main>

<script>
const gitbookWebFrame = window.parent;
const statusPanel = document.querySelector('[data-status-panel]');
const status = document.querySelector('[data-status]');
const stateOutput = document.querySelector('[data-state]');
const visitorOutput = document.querySelector('[data-visitor]');


function sendAction(payload) {
gitbookWebFrame?.postMessage({ action: payload }, '*');
}

function format(value) {
return JSON.stringify(value, null, 2);
}

function resize() {
window.requestAnimationFrame(() => {
const height = Math.max(
document.documentElement.scrollHeight,
document.body.scrollHeight,
120,
);

sendAction({
action: '@webframe.resize',
size: {
height,
aspectRatio: window.innerWidth / height,
},
});
});
}

function renderState(state) {
const visitor = state?.visitor;
const hasVisitor = Boolean(visitor);

stateOutput.textContent = format(state ?? {});
visitorOutput.textContent = format(visitor ?? null);

statusPanel.classList.toggle('has-visitor', hasVisitor);
status.textContent = hasVisitor
? 'Visitor context received.'
: 'No visitor context received.';

resize();
}

window.addEventListener('message', (event) => {
const state = event.data?.state;
if (!state) {
return;
}

console.info('visitor-claims-webframe-demo: received state', state);
renderState(state);
});

sendAction({ action: '@webframe.ready' });
resize();
</script>
</body>
</html>
`;
3 changes: 3 additions & 0 deletions integrations/visitor-claims-webframe-demo/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@gitbook/tsconfig/integration.json"
}
Loading