FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
stevekrouse
stevekrouseGlancer3Remix
Public
Like
Glancer3Remix
Home
Code
8
.vscode
1
backend
4
frontend
5
shared
1
.cursorrules
INSTRUCTIONS.md
README.md
deno.json
Branches
1
Pull requests
Remixes
History
Environment variables
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in miliseconds.
Sign up now
Code
/
Code
/
Search
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:

Personalized Demo Experience

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); });

Glance Cobrowsing Integration

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>

Dynamic PDF Generation

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"', }, }); };

Event Tracking

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, }), }); };

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.

├── 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.

Frontend (/frontend)

React-based user interface with Tailwind CSS styling. Includes the main demo interface, cobrowsing integration, and interactive components.

Shared (/shared)

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

Framework and Technology Stack

  • 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

Technical Implementation

React Frontend

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.

Hono Backend Routes

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

API Routes (/api)

Serve JSON data and handle CRUD operations for demo management, cobrowsing status, and user interactions.

View Routes (/demo/:id)

Serve personalized demo pages with initial data injection to minimize round-trips.

Task Routes (/tasks)

Handle incoming Notion webhooks for real-time data synchronization.

Shared Utilities (/shared)

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}`; };

Blob Storage Caching

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, });

Cron Jobs

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, }); } }

Environment Variables

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

Project Conventions

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); }); // 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 vs Utility Distinction

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

Route Area Specialization

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

External Dependencies

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:

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.

Code
.vscodebackendfrontendshared.cursorrulesINSTRUCTIONS.mdREADME.mddeno.json
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.