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:
<<<<<<< main
main
<<<<<<< main
// 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);
=======
Each visitor receives a unique demo URL with personalized content:
```typescript
// 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);
>>>>>>> main
});
<<<<<<< main
The platform includes Glance's cobrowsing JavaScript for real-time screen sharing between visitors and agents:
main
<<<<<<< main
<script id="glance-cobrowse" src="https://www.glancecdn.net/cobrowse/CobrowseJS.ashx?group=XXXXX&site=staging" data-groupid="XXXXX" data-site="staging" data-ws="www.glance.net" data-presence="on" async="{true}" ></script>
======= The platform includes Glance's cobrowsing JavaScript for real-time screen sharing between visitors and agents:
main
<<<<<<< main
<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>
main
<<<<<<< main
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("--");
=======
### Dynamic PDF Generation
Generates personalized PDF receipts with demo interaction history:
```typescript
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("--");
>>>>>>> main
return {
event: row[0],
"date & time": row[1],
};
});
<<<<<<< main
const invoicePDF = await generatePDF({
pageId: page.id,
customerName: page.properties.Name?.title?.[0]?.plain_text,
agentName: page.properties["Agent name"]?.formula?.string,
items: interactions
});
=======
const invoicePDF = await generatePDF({
pageId: page.id,
customerName: page.properties.Name?.title?.[0]?.plain_text,
agentName: page.properties["Agent name"]?.formula?.string,
items: interactions,
});
>>>>>>> main
<<<<<<< main
return new Response(invoicePDF, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": 'inline; filename="glance-cobrowse-demo-receipt.pdf"',
},
});
};
======= return new Response(invoicePDF, { headers: { "Content-Type": "application/pdf", "Content-Disposition": 'inline; filename="glance-cobrowse-demo-receipt.pdf"', }, }); };
>>>>>>> main
<<<<<<< main
### Event Tracking
Simple event tracking captures user interactions for analytics:
=======
### Event Tracking
>>>>>>> main
Simple event tracking captures user interactions for analytics:
```typescript
<<<<<<< main
// 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
})
});
};
=======
// 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,
}),
});
};
>>>>>>> main
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.
<<<<<<< main
├── 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
=======
├── 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.
main
<<<<<<< main
Handles all server-side operations including API routes, Notion integration, and static file serving. Built with Hono for routing and includes authentication middleware.
main
<<<<<<< main
React-based user interface with Tailwind CSS styling. Includes the main demo interface, cobrowsing integration, and interactive components.
React-based user interface with Tailwind CSS styling. Includes the main demo interface, cobrowsing integration, and interactive components.
main
<<<<<<< main
Contains utilities and functions that work in both browser and server environments, ensuring code reusability across the application.
main
<<<<<<< main
======= Contains utilities and functions that work in both browser and server environments, ensuring code reusability across the application.
main
<<<<<<< main
- 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 =======
main
<<<<<<< main
=======
- 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
main
<<<<<<< main
main
<<<<<<< main
/** @jsxImportSource https://esm.sh/react@18.2.0 */
=======
### React Frontend
The frontend uses React 18.2.0 with JSX import source configuration:
```typescript
/** @jsxImportSource https://esm.sh/react@18.2.0 */
>>>>>>> main
All React dependencies are pinned to ensure compatibility across the platform.
<<<<<<< main
main
The main application (backend/index.ts
) establishes a clear routing strategy:
main
<<<<<<< main
const app = new Hono();
=======
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
main
main
<<<<<<< main // 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); });
Serve JSON data and handle CRUD operations for demo management, cobrowsing status, and user interactions.
main
<<<<<<< main
// Route modules
app.route("/api", api); // JSON API endpoints
app.route("/tasks", tasks); // Notion webhook handlers
app.route("/demo", demo); // Demo page serving
=======
#### View Routes (`/demo/:id`)
>>>>>>> main
<<<<<<< main
#### API Routes (`/api`)
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.
>>>>>>> main
<<<<<<< main
#### View Routes (`/demo/:id`)
Serve personalized demo pages with initial data injection to minimize round-trips.
=======
#### Task Routes (`/tasks`)
>>>>>>> main
<<<<<<< main
#### Task Routes (`/tasks`)
Handle incoming Notion webhooks for real-time data synchronization.
=======
Handle incoming Notion webhooks for real-time data synchronization.
>>>>>>> main
<<<<<<< main
### Shared Utilities (`/shared`)
Contains helper functions that work in both frontend and backend environments:
=======
### Shared Utilities (`/shared`)
>>>>>>> main
<<<<<<< main
=======
Contains helper functions that work in both frontend and backend environments:
>>>>>>> main
```typescript
<<<<<<< main
// Example: Blob key generation for caching
export const blobKeyForDemoCache = async (importMetaUrl: string, pageId: string) => {
return `demo-cache-${pageId}`;
};
=======
// Example: Blob key generation for caching
export const blobKeyForDemoCache = async (
importMetaUrl: string,
pageId: string
) => {
return `demo-cache-${pageId}`;
};
>>>>>>> main
<<<<<<< main
main
<<<<<<< main
import { blob } from "https://esm.town/v/std/blob";
=======
Implements a caching layer to ensure responsive demo experiences:
>>>>>>> main
<<<<<<< main
// Cache demo data
await blob.setJSON(blobKey, {
id: page.id,
cobrowse: page.properties.Cobrowse.checkbox,
});
=======
import { blob } from "https://esm.town/v/std/blob";
>>>>>>> main
<<<<<<< main
### Cron Jobs
Automated cache updates run every minute to maintain data freshness:
=======
// Cache demo data
await blob.setJSON(blobKey, {
id: page.id,
cobrowse: page.properties.Cobrowse.checkbox,
});
main
<<<<<<< main
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,
});
}
}
=======
### Cron Jobs
Automated cache updates run every minute to maintain data freshness:
```typescript
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,
});
}
}
>>>>>>> main
<<<<<<< main
main
<<<<<<< main
const notion = new Client({
auth: Deno.env.get("NOTION_API_KEY"),
});
=======
The application uses environment variables for secure configuration:
>>>>>>> main
<<<<<<< main
const databaseId = Deno.env.get("GLANCE_DEMOS_DB_ID");
=======
```typescript
const notion = new Client({
auth: Deno.env.get("NOTION_API_KEY"),
});
const databaseId = Deno.env.get("GLANCE_DEMOS_DB_ID");
>>>>>>> main
<<<<<<< main 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 ======= Required environment variables:
main
<<<<<<< main
=======
NOTION_API_KEY
: Notion integration tokenGLANCE_DEMOS_DB_ID
: Notion database ID for demo pagesGLANCE_INTERACTIONS_DB_ID
: Notion database ID for interaction tracking
main
<<<<<<< main
main
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);
});
<<<<<<< main
// 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 } }] } }
});
};
=======
// 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 } }] } },
});
};
>>>>>>> main
<<<<<<< main
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 |
======= | ||
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 |
main
<<<<<<< main
main
<<<<<<< main
/api/*
: JSON data endpoints for frontend consumption/demo/*
: Personalized demo page serving with data injection/tasks/*
: Notion webhook processing and database updates ======= Different route areas serve specific purposes:
main
<<<<<<< main
=======
/api/*
: JSON data endpoints for frontend consumption/demo/*
: Personalized demo page serving with data injection/tasks/*
: Notion webhook processing and database updates
main
<<<<<<< main 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:
main
<<<<<<< main
- 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 ======= 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:
main
<<<<<<< main 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.
- 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.
main