FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
glance
glanceGlimpse
Public
Like
Glimpse
Home
Code
8
.vscode
1
backend
4
frontend
5
shared
1
.cursorrules
INSTRUCTIONS.md
README.md
deno.json
Branches
3
Pull requests
Remixes
1
History
Environment variables
5
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
README.md
Code
/
README.md
Search
5/29/2025
Viewing readonly version of main branch: v11
View latest version
README.md

Glance Cobrowsing Demo Platform

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.

Features

The platform demonstrates Glance's distinctive cobrowsing technology through several key features:

<<<<<<< main

Personalized Demo Experience

Each visitor receives a unique demo URL with personalized content:

Personalized Demo Experience

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

Glance Cobrowsing Integration

The platform includes Glance's cobrowsing JavaScript for real-time screen sharing between visitors and agents:

Glance Cobrowsing Integration

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

Dynamic PDF Generation

Generates personalized PDF receipts with demo interaction history:

<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

Codebase Structure

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

Backend (/backend)

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

Backend (/backend)

Handles all server-side operations including API routes, Notion integration, and static file serving. Built with Hono for routing and includes authentication middleware.

Frontend (/frontend)

main

<<<<<<< main

Frontend (/frontend)

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

Shared (/shared)

Contains utilities and functions that work in both browser and server environments, ensuring code reusability across the application.

Shared (/shared)

main

<<<<<<< main

Framework and Technology Stack

======= 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 =======

Framework and Technology Stack

main

<<<<<<< main

Technical Implementation

=======

  • 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

React Frontend

The frontend uses React 18.2.0 with JSX import source configuration:

Technical Implementation

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

Hono Backend Routes

The main application (backend/index.ts) establishes a clear routing strategy:

Hono Backend Routes

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 // Authentication middleware app.use("*", auth);

API Routes (/api)

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

Blob Storage Caching

Implements a caching layer to ensure responsive demo experiences:

Blob Storage Caching

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

Environment Variables

The application uses environment variables for secure configuration:

Environment Variables

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 token
  • GLANCE_DEMOS_DB_ID: Notion database ID for demo pages
  • GLANCE_INTERACTIONS_DB_ID: Notion database ID for interaction tracking ======= Required environment variables:

main

<<<<<<< main

Project Conventions

=======

  • NOTION_API_KEY: Notion integration token
  • GLANCE_DEMOS_DB_ID: Notion database ID for demo pages
  • GLANCE_INTERACTIONS_DB_ID: Notion database ID for interaction tracking

main

<<<<<<< main

Route and Controller Separation

Routes handle HTTP concerns while controllers manage business logic:

Project Conventions

main

Route and Controller Separation

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

Controller vs Utility Distinction

<<<<<<< main

AspectControllerUtility
PurposeOrchestrates business logic and workflowsProvides small, stateless helper functions
ScopeHigh-level, involves services or side effectsLow-level, narrow focus (string, date operations)
StateWorks with application or user-specific dataStateless - input in, output out
DependenciesServices, repositories, other controllersPure logic, minimal external dependencies
=======
AspectControllerUtility
----------------------------------------------------------------------------------------------------------
PurposeOrchestrates business logic and workflowsProvides small, stateless helper functions
ScopeHigh-level, involves services or side effectsLow-level, narrow focus (string, date operations)
StateWorks with application or user-specific dataStateless - input in, output out
DependenciesServices, repositories, other controllersPure logic, minimal external dependencies

main

<<<<<<< main

Route Area Specialization

Different route areas serve specific purposes:

Route Area Specialization

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

External Dependencies

=======

  • /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:

External Dependencies

main

<<<<<<< main

Notion-Managed Features

  • 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.

Notion-Managed Features

  • 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

Go to top
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Product
FeaturesPricing
Developers
DocsStatusAPI ExamplesNPM Package Examples
Explore
ShowcaseTemplatesNewest ValsTrending ValsNewsletter
Company
AboutBlogCareersBrandhi@val.town
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.