This Val Town project provides a platform from which to demo Glance's cobrowsing technology, designed to showcase key Glance features alongside implementation details relevant to development teams and prospective customers.
The platform demonstrates Glance's distinctive cobrowsing technology through several key features:
Each visitor receives a unique demo URL with personalized content:
// Demo route with data injection
app.get("/:id", async (c) => {
const id = c.req.param("id");
let html = await readFile("/frontend/index.html", import.meta.url);
const response = await fetch(`/api/demo/${id}`);
const initialData = await response.json();
const dataScript = `<script>
window.__INITIAL_DATA__ = ${JSON.stringify(initialData)};
window.__DEMO_ID__ = ${JSON.stringify(id)};
</script>`;
html = html.replace("</head>", `${dataScript}</head>`);
return c.html(html);
});
The platform includes Glance's cobrowsing JavaScript for real-time screen sharing between visitors and agents:
<script id="glance-cobrowse" src="https://www.glancecdn.net/cobrowse/CobrowseJS.ashx?group=24456&site=staging" data-groupid="24456" data-site="staging" data-ws="www.glance.net" data-presence="on" async="{true}" ></script>
Generates personalized PDF receipts with demo interaction history:
export const examplePDF = async (page: any) => {
const actionsStr =
page.properties["Interactions string"]?.formula?.string || null;
const actionsArray = actionsStr ? actionsStr.split(",") : [];
const interactions = actionsArray.map((event) => {
const row = event.split("--");
return {
event: row[0],
"date & time": row[1],
};
});
const invoicePDF = await generatePDF({
pageId: page.id,
customerName: page.properties.Name?.title?.[0]?.plain_text,
agentName: page.properties["Agent name"]?.formula?.string,
items: interactions,
});
return new Response(invoicePDF, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition":
'inline; filename="glance-cobrowse-demo-receipt.pdf"',
},
});
};
Simple event tracking captures user interactions for analytics:
// Client-side event recording
window.recordClick = function (action) {
fetch(`/api/setAction`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
pageId: window.__DEMO_ID__,
action: action,
}),
});
};
This project extends Val Town's recommended approach for using Hono and React together (https://www.val.town/x/std/reactHonoStarter) with additional enterprise features.
├── backend/ # Server-side logic and API routes
│ ├── controllers/ # Business logic for Notion integration
│ ├── crons/ # Scheduled tasks for cache management
│ ├── routes/ # HTTP route handlers
│ │ ├── api/ # JSON API endpoints
│ │ ├── tasks/ # Notion webhook handlers
│ │ └── views/ # HTML page serving
│ └── index.ts # Main Hono application entry point
├── frontend/ # Client-side React application
│ ├── components/ # React components
│ ├── index.html # Main HTML template
│ └── index.tsx # Frontend JavaScript entry point
└── shared/ # Utilities used by both frontend and backend
└── utils/ # Shared helper functions
Handles all server-side operations including API routes, Notion integration, and static file serving. Built with Hono for routing and includes authentication middleware.
React-based user interface with Tailwind CSS styling. Includes the main demo interface, cobrowsing integration, and interactive components.
Contains utilities and functions that work in both browser and server environments, ensuring code reusability across the application.
- Val Town & Deno: Serverless runtime environment with TypeScript support
- Notion Client: Official npm library for Notion API integration
- Hono: Lightweight web framework for routing and middleware
- Blob Storage: Val Town's built-in caching solution for performance optimization
- Cron Jobs: Scheduled tasks to maintain cache freshness
- React/JSX: Frontend interface templates with TypeScript
- Tailwind CSS: Utility-first CSS framework for styling
The frontend uses React 18.2.0 with JSX import source configuration:
/** @jsxImportSource https://esm.sh/react@18.2.0 */
All React dependencies are pinned to ensure compatibility across the platform.
The main application (backend/index.ts
) establishes a clear routing strategy:
const app = new Hono();
// Authentication middleware
app.use("*", auth);
// Static file serving
app.get("/frontend/**/*", (c) => serveFile(c.req.path, import.meta.url));
app.get("/utils/**/*", async (c) => {
return await serveFile("/shared" + c.req.path, import.meta.url);
});
// Route modules
app.route("/api", api); // JSON API endpoints
app.route("/tasks", tasks); // Notion webhook handlers
app.route("/demo", demo); // Demo page serving
Serve JSON data and handle CRUD operations for demo management, cobrowsing status, and user interactions.
Serve personalized demo pages with initial data injection to minimize round-trips.
Handle incoming Notion webhooks for real-time data synchronization.
Contains helper functions that work in both frontend and backend environments:
// Example: Blob key generation for caching
export const blobKeyForDemoCache = async (
importMetaUrl: string,
pageId: string
) => {
return `demo-cache-${pageId}`;
};
Implements a caching layer to ensure responsive demo experiences:
import { blob } from "https://esm.town/v/std/blob";
// Cache demo data
await blob.setJSON(blobKey, {
id: page.id,
cobrowse: page.properties.Cobrowse.checkbox,
});
Automated cache updates run every minute to maintain data freshness:
export default async function (interval: Interval) {
const pages = await notion.databases.query({
database_id: Deno.env.get("GLANCE_DEMOS_DB_ID"),
});
for (const page of pages.results) {
const blobKey = await blobKeyForDemoCache(import.meta.url, page.id);
blob.setJSON(blobKey, {
id: page.id,
cobrowse: page.properties.Cobrowse.checkbox,
});
}
}
The application uses environment variables for secure configuration:
const notion = new Client({
auth: Deno.env.get("NOTION_API_KEY"),
});
const databaseId = Deno.env.get("GLANCE_DEMOS_DB_ID");
Required environment variables:
NOTION_API_KEY
: Notion integration tokenGLANCE_DEMOS_DB_ID
: Notion database ID for demo pagesGLANCE_INTERACTIONS_DB_ID
: Notion database ID for interaction tracking
Routes handle HTTP concerns while controllers manage business logic:
// Route (thin layer)
app.post("/tasks/setPageID", async (c) => {
const body = await c.req.json();
const result = await setPageID(body.pageId, body.newId);
return c.json(result);
});
// Controller (business logic)
export const setPageID = async (pageId: string, newId: string) => {
return await notion.pages.update({
page_id: pageId,
properties: { "Page ID": { rich_text: [{ text: { content: newId } }] } },
});
};
Aspect | Controller | Utility |
---|---|---|
Purpose | Orchestrates business logic and workflows | Provides small, stateless helper functions |
Scope | High-level, involves services or side effects | Low-level, narrow focus (string, date operations) |
State | Works with application or user-specific data | Stateless - input in, output out |
Dependencies | Services, repositories, other controllers | Pure logic, minimal external dependencies |
Different route areas serve specific purposes:
/api/*
: JSON data endpoints for frontend consumption/demo/*
: Personalized demo page serving with data injection/tasks/*
: Notion webhook processing and database updates
This application integrates with Notion to extend its capabilities through automation and logging. The following features are managed by Notion rather than the Val Town codebase:
- Email and Alerting: Slack notifications and email alerts are configured within Notion
- Database Management: Three core Notion databases power the application:
- Glancer Demos Database: Stores demo configurations, visitor information, and personalization data
- Glancer Interactions Database: Warehouses demo events, clicks, and user behavior analytics
- Glancer Agents Database: Manages agent assignments, availability, and contact information
The Val Town application serves as an automation and presentation layer that extends Notion's native capabilities, providing real-time cobrowsing experiences while maintaining all data persistence and workflow management within the Notion ecosystem.