···11-# Build stage - compile via client with Node
22-FROM node:20-alpine AS builder
11+# Dockerfile for Proxy (wabac.js-based)
22+# Multi-stage build: Node.js for building, then slim runtime
33+44+# Build stage - compile proxy with Node
55+FROM node:22-alpine AS builder
3647RUN apk add --no-cache bash
58RUN npm install -g pnpm
69710WORKDIR /build
1111+1212+# Copy workspace config
813COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
914COPY packages/ ./packages/
1010-COPY entrypoints/via-client/ ./entrypoints/via-client/
1111-COPY entrypoints/sidepanel/ ./entrypoints/sidepanel/
1212-COPY proxy/via-html/ ./proxy/via-html/
1313-COPY landing/landing.css landing/fonts.css landing/favicon.ico ./landing/
1414-COPY landing/fonts/ ./landing/fonts/
1515-COPY landing/oauth/client-metadata.json ./landing/oauth/
1616-COPY vite.via.config.ts tsconfig.json ./
1717-COPY scripts/postbuild-via.sh ./scripts/
1515+COPY proxy/ ./proxy/
1616+COPY vite.proxy.config.ts vite.proxy-inject.config.ts vite.proxy.shared.ts tsconfig.json ./
1717+COPY scripts/postbuild-proxy.sh ./scripts/
18181919+# Install dependencies
1920RUN pnpm install --frozen-lockfile
2020-RUN BACKEND_URL=https://seams.so pnpm build:via
21212222-# Runtime stage
2323-FROM python:3.11-slim
2222+# Install proxy dependencies (for wabac.js sw.js)
2323+WORKDIR /build/proxy
2424+RUN npm install
2525+2626+# Build proxy with HMAC secret from build-time secret
2727+# The secret is mounted at /run/secrets/CORS_PROXY_HMAC_SECRET during build
2828+WORKDIR /build
2929+RUN --mount=type=secret,id=CORS_PROXY_HMAC_SECRET \
3030+ VITE_CORS_PROXY_HMAC_SECRET=$(cat /run/secrets/CORS_PROXY_HMAC_SECRET 2>/dev/null || echo "") \
3131+ pnpm build:proxy
24322525-# Install system dependencies
2626-RUN apt-get update && apt-get install -y --no-install-recommends \
2727- curl \
2828- ca-certificates \
2929- && rm -rf /var/lib/apt/lists/*
3333+# Runtime stage - Node.js + Caddy
3434+FROM node:22-alpine
30353131-# Install uv
3232-RUN curl -LsSf https://astral.sh/uv/install.sh | sh
3333-ENV PATH="/root/.local/bin:$PATH"
3636+RUN apk add --no-cache bash curl ca-certificates
34373538# Install Caddy
3636-RUN curl -L "https://caddyserver.com/api/download?os=linux&arch=amd64" -o /usr/local/bin/caddy \
3939+RUN curl -L "https://caddyserver.com/api/download?os=linux&arch=amd64&arm=" -o /usr/local/bin/caddy \
3740 && chmod +x /usr/local/bin/caddy
38413942WORKDIR /app
40434141-# Copy proxy config and collections
4242-COPY proxy/config.yaml ./proxy/
4343-COPY proxy/collections/ ./proxy/collections/
4444+# Copy built static files
4545+COPY --from=builder /build/proxy/dist/ ./dist/
44464545-# Copy built static files from builder
4646-COPY --from=builder /build/proxy/static/ ./proxy/static/
4747+# Copy CORS proxy source and install dependencies
4848+COPY proxy/cors-proxy/ ./cors-proxy/
4949+WORKDIR /app/cors-proxy
5050+RUN npm install
47514852# Copy Caddyfile
4949-COPY Caddyfile ./
5353+WORKDIR /app
5454+COPY proxy/Caddyfile ./
50555156# Create startup script
5257RUN cat <<'EOF' > /app/start.sh
5358#!/bin/bash
5459set -e
55605656-echo "🚀 Starting Seams Proxy..."
6161+echo "Starting Seams Proxy..."
57625858-# Install pywb at runtime using uv (like flake.nix does)
5959-echo "📦 Installing pywb..."
6060-export UV_CACHE_DIR=/root/.cache/uv
6161-export UV_TOOL_BIN_DIR=/root/.local/bin
6262-uv tool install pywb --with setuptools
6363+# Start CORS proxy on port 8083 (Caddy proxies 8082 -> 8083)
6464+cd /app/cors-proxy
6565+echo "Starting CORS proxy on :8083..."
6666+CORS_PROXY_PORT=8083 npx tsx index.ts &
6767+CORS_PID=$!
63686464-# Add local bin to PATH
6565-export PATH=$PATH:/root/.local/bin
6666-6767-# Start wayback (pywb) on port 8081
6868-cd /app/proxy
6969-echo "📦 Starting wayback on :8081"
7070-wayback -p 8081 -b 127.0.0.1 &
7171-WB_PID=$!
7272-7373-# Wait for wayback to initialize
7474-sleep 2
6969+# Wait for CORS proxy to be healthy (up to 30 seconds)
7070+echo "Waiting for CORS proxy to be ready..."
7171+MAX_ATTEMPTS=30
7272+ATTEMPT=0
7373+while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
7474+ if curl -s -o /dev/null -w "%{http_code}" http://localhost:8083/ 2>/dev/null | grep -q "200"; then
7575+ echo "CORS proxy is ready"
7676+ break
7777+ fi
7878+ ATTEMPT=$((ATTEMPT + 1))
7979+ if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
8080+ echo "ERROR: CORS proxy failed to start within ${MAX_ATTEMPTS} seconds"
8181+ exit 1
8282+ fi
8383+ sleep 1
8484+done
75857676-# Start Caddy on port 8082
8686+# Start Caddy (static files on 8081, cors proxy on 8082)
7787cd /app
7878-echo "🌐 Starting Caddy on :8082"
8888+echo "Starting Caddy on :8081 (static) and :8082 (cors proxy)..."
7989caddy run --config /app/Caddyfile --adapter caddyfile
8090EOF
81918292RUN chmod +x /app/start.sh
83938484-EXPOSE 8082
9494+EXPOSE 8081 8082
85958696CMD ["/app/start.sh"]
-98
Dockerfile.sure-client
···11-# Dockerfile for Sure Client Proxy (wabac.js-based)
22-# Multi-stage build: Node.js for building, then slim runtime
33-44-# Build stage - compile sure-client with Node
55-FROM node:22-alpine AS builder
66-77-RUN apk add --no-cache bash
88-RUN npm install -g pnpm
99-1010-WORKDIR /build
1111-1212-# Copy workspace config
1313-COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
1414-COPY packages/ ./packages/
1515-COPY entrypoints/via-client/ ./entrypoints/via-client/
1616-COPY entrypoints/sidepanel/ ./entrypoints/sidepanel/
1717-COPY sure-client-proxy/ ./sure-client-proxy/
1818-COPY vite.sure-client.config.ts vite.sure-client-inject.config.ts vite.sure-client.shared.ts tsconfig.json ./
1919-COPY scripts/postbuild-sure-client.sh ./scripts/
2020-2121-# Install dependencies
2222-RUN pnpm install --frozen-lockfile
2323-2424-# Install sure-client-proxy dependencies (for wabac.js sw.js)
2525-WORKDIR /build/sure-client-proxy
2626-RUN npm install
2727-2828-# Build sure-client with HMAC secret from build-time secret
2929-# The secret is mounted at /run/secrets/CORS_PROXY_HMAC_SECRET during build
3030-WORKDIR /build
3131-RUN --mount=type=secret,id=CORS_PROXY_HMAC_SECRET \
3232- VITE_CORS_PROXY_HMAC_SECRET=$(cat /run/secrets/CORS_PROXY_HMAC_SECRET 2>/dev/null || echo "") \
3333- pnpm build:sure-client
3434-3535-# Runtime stage - Node.js + Caddy
3636-FROM node:22-alpine
3737-3838-RUN apk add --no-cache bash curl ca-certificates
3939-4040-# Install Caddy
4141-RUN curl -L "https://caddyserver.com/api/download?os=linux&arch=amd64&arm=" -o /usr/local/bin/caddy \
4242- && chmod +x /usr/local/bin/caddy
4343-4444-WORKDIR /app
4545-4646-# Copy built static files
4747-COPY --from=builder /build/sure-client-proxy/dist/ ./dist/
4848-4949-# Copy CORS proxy source and install dependencies
5050-COPY sure-client-proxy/cors-proxy/ ./cors-proxy/
5151-WORKDIR /app/cors-proxy
5252-RUN npm install
5353-5454-# Copy Caddyfile
5555-WORKDIR /app
5656-COPY sure-client-proxy/Caddyfile ./
5757-5858-# Create startup script
5959-RUN cat <<'EOF' > /app/start.sh
6060-#!/bin/bash
6161-set -e
6262-6363-echo "Starting Seams Sure Client Proxy..."
6464-6565-# Start CORS proxy on port 8083 (Caddy proxies 8082 -> 8083)
6666-cd /app/cors-proxy
6767-echo "Starting CORS proxy on :8083..."
6868-CORS_PROXY_PORT=8083 npx tsx index.ts &
6969-CORS_PID=$!
7070-7171-# Wait for CORS proxy to be healthy (up to 30 seconds)
7272-echo "Waiting for CORS proxy to be ready..."
7373-MAX_ATTEMPTS=30
7474-ATTEMPT=0
7575-while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
7676- if curl -s -o /dev/null -w "%{http_code}" http://localhost:8083/ 2>/dev/null | grep -q "200"; then
7777- echo "CORS proxy is ready"
7878- break
7979- fi
8080- ATTEMPT=$((ATTEMPT + 1))
8181- if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
8282- echo "ERROR: CORS proxy failed to start within ${MAX_ATTEMPTS} seconds"
8383- exit 1
8484- fi
8585- sleep 1
8686-done
8787-8888-# Start Caddy (static files on 8081, cors proxy on 8082)
8989-cd /app
9090-echo "Starting Caddy on :8081 (static) and :8082 (cors proxy)..."
9191-caddy run --config /app/Caddyfile --adapter caddyfile
9292-EOF
9393-9494-RUN chmod +x /app/start.sh
9595-9696-EXPOSE 8081 8082
9797-9898-CMD ["/app/start.sh"]
+5-4
README.md
···77- **Browser Extension** (TypeScript/WXT) - Chrome/Firefox extension for creating and viewing annotations
88- **Backend Server** (Go) - SQLite-backed indexing service for querying annotations by URL
99- **Landing Page** (`landing/`) - Public feed of recent annotations
1010-- Proxy (`proxy/`) - Pure web based proxy to allow annotations
1010+- **Proxy** (`proxy/`) - Web-based proxy using wabac.js for annotation without extension
11111212## Quick Start
1313···111111- chi (HTTP router)
112112113113**Proxy**:
114114-- `pywb` Server
115115-- Injects browser based sidebar and content script
116116-- Caddy wrapping
114114+- wabac.js (client-side service worker)
115115+- CORS proxy server (Node.js/Hono)
116116+- Injects browser-based sidebar and content script
117117+- Caddy for static files and reverse proxy
117118118119**Infrastructure**:
119120- Nix for reproducible development environments
+51-29
deploy-proxy.sh
···11-#!/usr/bin/env bash
11+#!/bin/bash
22+# Deploy Proxy to Fly.io
33+# Usage: ./deploy-proxy.sh
44+#
55+# HMAC Authentication Setup:
66+# 1. Create .env.secrets file (gitignored) with: CORS_PROXY_HMAC_SECRET=<your-secret>
77+# 2. Or set environment variable: export CORS_PROXY_HMAC_SECRET=<your-secret>
88+# 3. The script will set the Fly secret and use it for build
99+#
1010+# Generate a secret with: openssl rand -base64 32
1111+212set -e
31344-# Define app name
514APP_NAME="sure-seams-so"
66-CONFIG_FILE="fly.proxy.toml"
77-88-echo "Building proxy service"
99-pnpm build:via
1010-1111-echo "➕ Adding static assets to git index (for Nix)..."
1212-git add -f proxy/static
13151414-echo "🏗️ Building Proxy Docker image with Nix..."
1515-nix build .#proxy
1616-1717-echo "➖ Resetting git index..."
1818-git reset proxy/static
1919-2020-echo "📦 Loading image into Docker..."
2121-docker load < result
2222-2323-echo "🏷️ Tagging image for Fly.io registry..."
2424-docker tag seams-proxy:latest registry.fly.io/$APP_NAME:latest
2525-2626-echo "🔐 Authenticating with Fly.io Docker registry..."
2727-flyctl auth docker
1616+echo "Deploying Proxy to Fly.io..."
28172929-echo "⬆️ Pushing image to Fly.io..."
3030-docker push registry.fly.io/$APP_NAME:latest
1818+# Load secrets from .env.secrets if it exists
1919+if [ -f .env.secrets ]; then
2020+ echo "Loading secrets from .env.secrets..."
2121+ set -a
2222+ source .env.secrets
2323+ set +a
2424+fi
31253232-echo "🚀 Deploying to Fly.io..."
3333-flyctl deploy --config $CONFIG_FILE --image registry.fly.io/$APP_NAME:latest
2626+# Check if HMAC secret is available
2727+if [ -n "$CORS_PROXY_HMAC_SECRET" ]; then
2828+ echo "HMAC secret found, enabling authentication..."
2929+3030+ # Ensure the Fly.io app has the runtime secret
3131+ echo "Setting runtime secret on Fly.io..."
3232+ fly secrets set CORS_PROXY_HMAC_SECRET="$CORS_PROXY_HMAC_SECRET" --app "$APP_NAME" --stage
3333+3434+ # Deploy with build-time secret
3535+ echo "Deploying with HMAC authentication..."
3636+ fly deploy --config fly.proxy.toml \
3737+ --build-secret "CORS_PROXY_HMAC_SECRET=$CORS_PROXY_HMAC_SECRET"
3838+else
3939+ echo "WARNING: CORS_PROXY_HMAC_SECRET not set"
4040+ echo "HMAC authentication will be DISABLED"
4141+ echo ""
4242+ echo "To enable HMAC auth:"
4343+ echo " 1. Generate secret: openssl rand -base64 32"
4444+ echo " 2. Create .env.secrets with: CORS_PROXY_HMAC_SECRET=<secret>"
4545+ echo " 3. Re-run this script"
4646+ echo ""
4747+ read -p "Continue without HMAC? (y/N) " -n 1 -r
4848+ echo
4949+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
5050+ exit 1
5151+ fi
5252+5353+ fly deploy --config fly.proxy.toml
5454+fi
34553535-echo "✅ Proxy deployment complete!"
3636-echo "🌐 Visit: https://sure.seams.so"
5656+echo ""
5757+echo "Deployment complete!"
5858+echo "Visit: https://sure.seams.so"
-58
deploy-sure-client.sh
···11-#!/bin/bash
22-# Deploy Sure Client Proxy to Fly.io
33-# Usage: ./deploy-sure-client.sh
44-#
55-# HMAC Authentication Setup:
66-# 1. Create .env.secrets file (gitignored) with: CORS_PROXY_HMAC_SECRET=<your-secret>
77-# 2. Or set environment variable: export CORS_PROXY_HMAC_SECRET=<your-secret>
88-# 3. The script will set the Fly secret and use it for build
99-#
1010-# Generate a secret with: openssl rand -base64 32
1111-1212-set -e
1313-1414-APP_NAME="sure-seams-so"
1515-1616-echo "Deploying Sure Client Proxy to Fly.io..."
1717-1818-# Load secrets from .env.secrets if it exists
1919-if [ -f .env.secrets ]; then
2020- echo "Loading secrets from .env.secrets..."
2121- set -a
2222- source .env.secrets
2323- set +a
2424-fi
2525-2626-# Check if HMAC secret is available
2727-if [ -n "$CORS_PROXY_HMAC_SECRET" ]; then
2828- echo "HMAC secret found, enabling authentication..."
2929-3030- # Ensure the Fly.io app has the runtime secret
3131- echo "Setting runtime secret on Fly.io..."
3232- fly secrets set CORS_PROXY_HMAC_SECRET="$CORS_PROXY_HMAC_SECRET" --app "$APP_NAME" --stage
3333-3434- # Deploy with build-time secret
3535- echo "Deploying with HMAC authentication..."
3636- fly deploy --config fly.sure-client.toml \
3737- --build-secret "CORS_PROXY_HMAC_SECRET=$CORS_PROXY_HMAC_SECRET"
3838-else
3939- echo "WARNING: CORS_PROXY_HMAC_SECRET not set"
4040- echo "HMAC authentication will be DISABLED"
4141- echo ""
4242- echo "To enable HMAC auth:"
4343- echo " 1. Generate secret: openssl rand -base64 32"
4444- echo " 2. Create .env.secrets with: CORS_PROXY_HMAC_SECRET=<secret>"
4545- echo " 3. Re-run this script"
4646- echo ""
4747- read -p "Continue without HMAC? (y/N) " -n 1 -r
4848- echo
4949- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
5050- exit 1
5151- fi
5252-5353- fly deploy --config fly.sure-client.toml
5454-fi
5555-5656-echo ""
5757-echo "Deployment complete!"
5858-echo "Visit: https://sure.seams.so"
+8-19
entrypoints/via-client/main.ts
proxy/src/main.ts
···11-// Via proxy client - handles page interaction and communicates with parent frame (shell)
11+// Proxy client - handles page interaction and communicates with parent frame (shell)
22// This runs inside the wabac.js proxied iframe which cannot access localStorage
33// Uses PostMessageStorageAdapter to request data from shell
44import { PostMessageStorageAdapter, ProxyContentScript, applyHighlights, clearHighlights, generateSelectors, isAllowedOrigin } from '@seams/core';
5566-console.log('[seams-client] Seams via client loaded!');
66+console.log('[seams-client] Seams client loaded!');
7788// Determine the shell origin from the parent frame
99// In production this will be sure.seams.so, in dev it's 127.0.0.1:8081
···7878function getActualUrl(): string {
7979 const proxyUrl = window.location.href;
80808181- // Try to get from wbinfo (wabac.js/pywb metadata object)
8181+ // Try to get from wbinfo (wabac.js metadata object)
8282 // wabac.js injects this with the original URL
8383 const wbinfo = (window as any).wbinfo;
8484 if (wbinfo && wbinfo.url) {
8585 const rawUrl = wbinfo.url;
8686 const normalized = normalizeUrl(rawUrl);
8787- console.log('[seams-via] Got URL from wbinfo:', rawUrl);
8888- console.log('[seams-via] Normalized URL:', normalized);
8787+ console.log('[seams-client] Got URL from wbinfo:', rawUrl);
8888+ console.log('[seams-client] Normalized URL:', normalized);
8989 return normalized;
9090 }
9191···9696 if (wabacMatch) {
9797 const rawUrl = wabacMatch[1];
9898 const normalized = normalizeUrl(rawUrl);
9999- console.log('[seams-via] Extracted URL from wabac.js path:', rawUrl);
100100- console.log('[seams-via] Normalized URL:', normalized);
9999+ console.log('[seams-client] Extracted URL from wabac.js path:', rawUrl);
100100+ console.log('[seams-client] Normalized URL:', normalized);
101101 return normalized;
102102 }
103103104104- // Fallback: parse from pywb proxy URL
105105- // URL format: http://localhost:8081/proxy/https://example.com
106106- const pywbMatch = proxyUrl.match(/\/proxy\/(https?:\/\/.+)/);
107107- if (pywbMatch) {
108108- const rawUrl = pywbMatch[1];
109109- const normalized = normalizeUrl(rawUrl);
110110- console.log('[seams-via] Extracted URL from pywb path:', rawUrl);
111111- console.log('[seams-via] Normalized URL:', normalized);
112112- return normalized;
113113- }
114114-115115- console.warn('[seams-via] Could not extract actual URL, using:', proxyUrl);
104104+ console.warn('[seams-client] Could not extract actual URL, using:', proxyUrl);
116105 return proxyUrl;
117106}
118107
···11-import type { Annotation } from '../types/annotation';
11+import type { Annotation } from '../../types';
22import { escapeHtml } from '../sanitize';
3344let currentPopover: HTMLElement | null = null;
+1-1
packages/core/src/utils/selectors/match.ts
···11import * as textQuote from 'dom-anchor-text-quote';
22import * as textPosition from 'dom-anchor-text-position';
33-import type { Annotation, TextQuoteSelector, TextPositionSelector } from '../types/annotation';
33+import type { Annotation, TextQuoteSelector, TextPositionSelector } from '../../types';
4455/**
66 * Find the DOM Range for an annotation using its selectors
···11+#!/bin/bash
22+# Post-build script for proxy
33+# 1. Flattens nested HTML output from vite
44+# 2. Copies source files (index.html, loadwabac.js, client-metadata.json)
55+66+set -e # Exit on any error
77+88+OUT_DIR="proxy/dist"
99+SRC_DIR="proxy/src"
1010+NESTED_DIR="$OUT_DIR/proxy/html"
1111+1212+# Verify output directory exists
1313+if [ ! -d "$OUT_DIR" ]; then
1414+ echo "ERROR: Output directory $OUT_DIR does not exist"
1515+ exit 1
1616+fi
1717+1818+# Move HTML files from nested vite output to root
1919+if [ -d "$NESTED_DIR" ]; then
2020+ mv "$NESTED_DIR"/*.html "$OUT_DIR/" || { echo "ERROR: Failed to move HTML files"; exit 1; }
2121+ rm -rf "$OUT_DIR/proxy"
2222+ echo "Flattened HTML files"
2323+fi
2424+2525+# Verify source files exist before copying
2626+for file in index.html loadwabac.js client-metadata.json styles.css; do
2727+ if [ ! -f "$SRC_DIR/$file" ]; then
2828+ echo "ERROR: Required source file $SRC_DIR/$file not found"
2929+ exit 1
3030+ fi
3131+done
3232+3333+# Copy source files with error checking
3434+cp "$SRC_DIR/index.html" "$OUT_DIR/" || { echo "ERROR: Failed to copy index.html"; exit 1; }
3535+cp "$SRC_DIR/loadwabac.js" "$OUT_DIR/" || { echo "ERROR: Failed to copy loadwabac.js"; exit 1; }
3636+cp "$SRC_DIR/client-metadata.json" "$OUT_DIR/" || { echo "ERROR: Failed to copy client-metadata.json"; exit 1; }
3737+cp "$SRC_DIR/styles.css" "$OUT_DIR/" || { echo "ERROR: Failed to copy styles.css"; exit 1; }
3838+echo "Copied source files from $SRC_DIR"
3939+4040+# Copy wabac.js service worker
4141+WABAC_SW="proxy/node_modules/@webrecorder/wabac/dist/sw.js"
4242+if [ -f "$WABAC_SW" ]; then
4343+ cp "$WABAC_SW" "$OUT_DIR/" || { echo "ERROR: Failed to copy wabac.js service worker"; exit 1; }
4444+ echo "Copied wabac.js service worker"
4545+else
4646+ echo "ERROR: wabac.js sw.js not found at $WABAC_SW"
4747+ echo "Run 'cd proxy && npm install' to install dependencies"
4848+ exit 1
4949+fi
5050+5151+echo ""
5252+echo "Build complete. Files in $OUT_DIR/:"
5353+ls -la "$OUT_DIR"/*.html "$OUT_DIR"/*.js "$OUT_DIR"/*.json "$OUT_DIR"/*.css 2>/dev/null
-72
scripts/postbuild-sure-client.js
···11-#!/usr/bin/env node
22-/**
33- * Post-build script for sure-client-proxy
44- * Cross-platform alternative to postbuild-sure-client.sh
55- * 1. Flattens nested HTML output from vite
66- * 2. Copies source files (index.html, loadwabac.js, client-metadata.json)
77- */
88-99-const fs = require('fs');
1010-const path = require('path');
1111-1212-const OUT_DIR = 'sure-client-proxy/dist';
1313-const SRC_DIR = 'sure-client-proxy/src';
1414-const NESTED_DIR = path.join(OUT_DIR, 'sure-client-proxy/html');
1515-1616-function copyFile(src, dest) {
1717- fs.copyFileSync(src, dest);
1818- console.log(`Copied: ${src} -> ${dest}`);
1919-}
2020-2121-function main() {
2222- // Verify output directory exists
2323- if (!fs.existsSync(OUT_DIR)) {
2424- console.error(`ERROR: Output directory ${OUT_DIR} does not exist`);
2525- process.exit(1);
2626- }
2727-2828- // Move HTML files from nested vite output to root
2929- if (fs.existsSync(NESTED_DIR)) {
3030- const htmlFiles = fs.readdirSync(NESTED_DIR).filter(f => f.endsWith('.html'));
3131- for (const file of htmlFiles) {
3232- fs.renameSync(path.join(NESTED_DIR, file), path.join(OUT_DIR, file));
3333- }
3434- // Remove empty nested directories
3535- fs.rmSync(path.join(OUT_DIR, 'sure-client-proxy'), { recursive: true });
3636- console.log('Flattened HTML files');
3737- }
3838-3939- // Copy source files
4040- const sourceFiles = ['index.html', 'loadwabac.js', 'client-metadata.json'];
4141- for (const file of sourceFiles) {
4242- const srcPath = path.join(SRC_DIR, file);
4343- if (!fs.existsSync(srcPath)) {
4444- console.error(`ERROR: Required source file ${srcPath} not found`);
4545- process.exit(1);
4646- }
4747- copyFile(srcPath, path.join(OUT_DIR, file));
4848- }
4949-5050- // Copy wabac.js service worker
5151- const wabacSW = 'sure-client-proxy/node_modules/@webrecorder/wabac/dist/sw.js';
5252- if (fs.existsSync(wabacSW)) {
5353- copyFile(wabacSW, path.join(OUT_DIR, 'sw.js'));
5454- console.log('Copied wabac.js service worker');
5555- } else {
5656- console.error(`ERROR: wabac.js sw.js not found at ${wabacSW}`);
5757- console.error("Run 'cd sure-client-proxy && npm install' to install dependencies");
5858- process.exit(1);
5959- }
6060-6161- console.log('');
6262- console.log(`Build complete. Files in ${OUT_DIR}/:`);
6363- const files = fs.readdirSync(OUT_DIR).filter(f =>
6464- f.endsWith('.html') || f.endsWith('.js') || f.endsWith('.json')
6565- );
6666- for (const file of files) {
6767- const stats = fs.statSync(path.join(OUT_DIR, file));
6868- console.log(` ${file} (${stats.size} bytes)`);
6969- }
7070-}
7171-7272-main();
-52
scripts/postbuild-sure-client.sh
···11-#!/bin/bash
22-# Post-build script for sure-client-proxy
33-# 1. Flattens nested HTML output from vite
44-# 2. Copies source files (index.html, loadwabac.js, client-metadata.json)
55-66-set -e # Exit on any error
77-88-OUT_DIR="sure-client-proxy/dist"
99-SRC_DIR="sure-client-proxy/src"
1010-NESTED_DIR="$OUT_DIR/sure-client-proxy/html"
1111-1212-# Verify output directory exists
1313-if [ ! -d "$OUT_DIR" ]; then
1414- echo "ERROR: Output directory $OUT_DIR does not exist"
1515- exit 1
1616-fi
1717-1818-# Move HTML files from nested vite output to root
1919-if [ -d "$NESTED_DIR" ]; then
2020- mv "$NESTED_DIR"/*.html "$OUT_DIR/" || { echo "ERROR: Failed to move HTML files"; exit 1; }
2121- rm -rf "$OUT_DIR/sure-client-proxy"
2222- echo "Flattened HTML files"
2323-fi
2424-2525-# Verify source files exist before copying
2626-for file in index.html loadwabac.js client-metadata.json; do
2727- if [ ! -f "$SRC_DIR/$file" ]; then
2828- echo "ERROR: Required source file $SRC_DIR/$file not found"
2929- exit 1
3030- fi
3131-done
3232-3333-# Copy source files with error checking
3434-cp "$SRC_DIR/index.html" "$OUT_DIR/" || { echo "ERROR: Failed to copy index.html"; exit 1; }
3535-cp "$SRC_DIR/loadwabac.js" "$OUT_DIR/" || { echo "ERROR: Failed to copy loadwabac.js"; exit 1; }
3636-cp "$SRC_DIR/client-metadata.json" "$OUT_DIR/" || { echo "ERROR: Failed to copy client-metadata.json"; exit 1; }
3737-echo "Copied source files from $SRC_DIR"
3838-3939-# Copy wabac.js service worker
4040-WABAC_SW="sure-client-proxy/node_modules/@webrecorder/wabac/dist/sw.js"
4141-if [ -f "$WABAC_SW" ]; then
4242- cp "$WABAC_SW" "$OUT_DIR/" || { echo "ERROR: Failed to copy wabac.js service worker"; exit 1; }
4343- echo "Copied wabac.js service worker"
4444-else
4545- echo "ERROR: wabac.js sw.js not found at $WABAC_SW"
4646- echo "Run 'cd sure-client-proxy && npm install' to install dependencies"
4747- exit 1
4848-fi
4949-5050-echo ""
5151-echo "Build complete. Files in $OUT_DIR/:"
5252-ls -la "$OUT_DIR"/*.html "$OUT_DIR"/*.js "$OUT_DIR"/*.json 2>/dev/null
-31
scripts/postbuild-via.sh
···11-#!/usr/bin/env bash
22-# Post-build script for via proxy - fixes HTML paths and copies to correct location
33-44-set -e
55-66-echo "📝 Post-processing via build..."
77-88-# Copy BUILT HTML files from the nested output directory to static root
99-# Vite outputs to proxy/static/proxy/via-html/ because of input structure
1010-cp proxy/static/proxy/via-html/*.html proxy/static/
1111-1212-# Clean up the nested directory structure created by Vite
1313-rm -rf proxy/static/proxy
1414-1515-# Copy CSS and font files from landing to static root
1616-echo "🎨 Copying shared assets from landing..."
1717-cp landing/landing.css proxy/static/
1818-cp landing/fonts.css proxy/static/
1919-cp landing/favicon.ico proxy/static/
2020-# cp landing/landing.js proxy/static/
2121-mkdir -p proxy/static/fonts
2222-cp landing/fonts/* proxy/static/fonts/
2323-2424-# Copy client-metadata.json from landing to static root (proxy shares the same client ID)
2525-cp landing/oauth/client-metadata.json proxy/static/
2626-2727-# Fix asset paths in HTML files (add /static prefix)
2828-# sed -i 's|src="/seams-|src="/static/seams-|g' proxy/static/*.html
2929-# sed -i 's|href="/assets/|href="/static/assets/|g' proxy/static/*.html
3030-3131-echo "✅ Via build post-processing complete"
-60
scripts/start-via.sh
···11-#!/usr/bin/env bash
22-# Start via proxy development servers
33-44-set -e
55-66-echo "🔨 Building via client scripts (watch mode)..."
77-88-# Export development OAuth configuration for localhost
99-# Note: Use 127.0.0.1 for redirect_uri as required by RFC 8252
1010-# The redirect_uri in the client_id must match the actual redirect_uri used.
1111-export VITE_OAUTH_CLIENT_ID="http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%3A8082%2Foauth%2Fcallback&scope=atproto"
1212-export VITE_OAUTH_REDIRECT_URI="http://127.0.0.1:8082/oauth/callback"
1313-export VITE_OAUTH_SCOPE="atproto"
1414-export BACKEND_URL="http://localhost:8080"
1515-1616-pnpm dev:via &
1717-BUILD_PID=$!
1818-1919-# Wait for initial build
2020-sleep 2
2121-2222-echo ""
2323-echo "🚀 Starting via proxy servers..."
2424-echo ""
2525-2626-# Kill any existing processes on these ports
2727-lsof -ti:8081 | xargs kill -9 2>/dev/null || true
2828-lsof -ti:8082 | xargs kill -9 2>/dev/null || true
2929-3030-# Ensure LD_LIBRARY_PATH is set for pywb (needed for gevent/greenlet)
3131-# This should already be set by the nix shell, but we make sure here
3232-if [ -z "$LD_LIBRARY_PATH" ]; then
3333- echo "⚠️ LD_LIBRARY_PATH not set. Run this from nix shell!"
3434- exit 1
3535-fi
3636-3737-# Start pywb in background (must run from proxy directory)
3838-echo "📦 Starting pywb on port 8081..."
3939-(cd proxy && LD_LIBRARY_PATH="$LD_LIBRARY_PATH" wayback -p 8081) &
4040-PYWB_PID=$!
4141-4242-# Wait for pywb to start
4343-sleep 2
4444-4545-# Start Caddy in foreground
4646-echo "🌐 Starting Caddy on port 8082..."
4747-echo ""
4848-echo "✅ Via proxy ready at http://localhost:8082"
4949-echo ""
5050-echo "Press Ctrl+C to stop all services"
5151-echo ""
5252-5353-# Trap to kill background processes on exit
5454-trap "kill $BUILD_PID $PYWB_PID 2>/dev/null || true" EXIT
5555-5656-# Run Caddy (blocks)
5757-caddy run
5858-5959-# Cleanup
6060-kill $BUILD_PID $PYWB_PID 2>/dev/null || true
sure-client-proxy/.gitignore
proxy/.gitignore
sure-client-proxy/Caddyfile
proxy/Caddyfile
+4-4
sure-client-proxy/README.md
proxy/README.md
···11-# Seams Client-Side Proxy
11+# Seams Web Proxy
2233-A client-side web proxy using wabac.js, replacing the server-side pywb proxy.
33+A client-side web proxy using wabac.js for annotating any webpage without installing an extension.
4455## Architecture
6677-Instead of fetching pages server-side (pywb), this approach uses:
77+This approach uses:
88991. **CORS Proxy** (`cors-proxy/`) - Node.js server that adds CORS headers with SSRF protection
10102. **wabac.js Service Worker** - Intercepts requests in the browser and routes through CORS proxy
···4343## Directory Structure
44444545```
4646-sure-client-proxy/
4646+proxy/
4747├── cors-proxy/
4848│ ├── index.ts # Hono-based CORS proxy server
4949│ ├── package.json
···44 * Tests the full flow of creating an annotation through the proxy client.
55 *
66 * Prerequisites:
77- * 1. Build the proxy client: pnpm build:via
77+ * 1. Build the proxy client: pnpm build:proxy
88 * 2. Start the backend server
99 * 3. Start the proxy servers
1010 * 4. Configure test account in environment:
+1-1
tests/e2e/proxy/highlights.spec.ts
···44 * Tests that annotations are correctly highlighted in proxied content.
55 *
66 * Prerequisites:
77- * 1. Build the proxy client: pnpm build:via
77+ * 1. Build the proxy client: pnpm build:proxy
88 * 2. Start the backend server with test data
99 * 3. Start the proxy servers
1010 */
+3-3
tests/e2e/proxy/sidebar.spec.ts
···44 * Tests that the proxy client correctly displays and manages the sidebar.
55 *
66 * Prerequisites:
77- * 1. Build the proxy client: pnpm build:via
77+ * 1. Build the proxy client: pnpm build:proxy
88 * 2. Start the backend server: pnpm dev:server (or use the webServer config)
99 * 3. Start the proxy servers:
1010- * - CORS proxy: cd sure-client-proxy && npx tsx cors-proxy/index.ts
1111- * - Static server: cd sure-client-proxy && npx serve -p 8081 dist
1010+ * - CORS proxy: cd proxy && npx tsx cors-proxy/index.ts
1111+ * - Static server: cd proxy && npx serve -p 8081 dist
1212 */
13131414import { test, expect } from '@playwright/test';
···11import { defineConfig } from 'vite';
22import path from 'path';
33-import { getEnvDefines } from './vite.sure-client.shared';
33+import { getEnvDefines } from './vite.proxy.shared';
4455// Vite config for the injected seams-client.js script
66// Built as IIFE (self-contained, no ES module imports) for wabac.js injection
···88 base: '/',
99 publicDir: false, // Don't copy public/ to output
1010 build: {
1111- outDir: 'sure-client-proxy/dist',
1111+ outDir: 'proxy/dist',
1212 emptyOutDir: false,
1313 sourcemap: true,
1414 lib: {
1515- entry: path.resolve(__dirname, 'entrypoints/via-client/main.ts'),
1515+ entry: path.resolve(__dirname, 'proxy/src/main.ts'),
1616 name: 'SeamsClient',
1717 fileName: () => 'seams-client.js',
1818 formats: ['iife'],
+6-6
vite.sure-client.config.ts
vite.proxy.config.ts
···11import { defineConfig } from 'vite';
22import path from 'path';
33-import { getEnvDefines } from './vite.sure-client.shared';
33+import { getEnvDefines } from './vite.proxy.shared';
4455-// Vite config for sure-client-proxy
55+// Vite config for proxy
66// Builds the shell (with embedded sidebar) and oauth HTML pages (ES modules)
77-// The seams-client.js is built separately as IIFE (see vite.sure-client-inject.config.ts)
77+// The seams-client.js is built separately as IIFE (see vite.proxy-inject.config.ts)
88export default defineConfig({
99 // Base path - files served from root of static server
1010 base: '/',
1111 publicDir: false, // Don't copy public/ to output
1212 build: {
1313- outDir: 'sure-client-proxy/dist',
1313+ outDir: 'proxy/dist',
1414 emptyOutDir: true,
1515 sourcemap: true,
1616 rollupOptions: {
1717 input: {
1818 // Shell - manages BackgroundWorker, storage, and renders Sidebar directly
1919- 'seams-shell': path.resolve(__dirname, 'sure-client-proxy/html/seams-shell.html'),
1919+ 'seams-shell': path.resolve(__dirname, 'proxy/html/seams-shell.html'),
2020 // OAuth callback page (still needed for popup OAuth flow)
2121- 'oauth-callback': path.resolve(__dirname, 'sure-client-proxy/html/oauth-callback.html'),
2121+ 'oauth-callback': path.resolve(__dirname, 'proxy/html/oauth-callback.html'),
2222 },
2323 output: {
2424 entryFileNames: '[name].js',
+2-2
vite.sure-client.shared.ts
vite.proxy.shared.ts
···11-// Shared configuration for sure-client vite builds
22-// Used by both vite.sure-client.config.ts and vite.sure-client-inject.config.ts
11+// Shared configuration for proxy vite builds
22+// Used by both vite.proxy.config.ts and vite.proxy-inject.config.ts
3344// Development server configuration
55export const DEV_HOST = '127.0.0.1';
-35
vite.via.config.ts
···11-import { defineConfig } from 'vite';
22-import path from 'path';
33-44-// Vite config for production pywb proxy (proxy/static/)
55-// Note: The sidebar is now embedded directly in the shell, not a separate iframe
66-export default defineConfig({
77- base: '/static/',
88- build: {
99- outDir: 'proxy/static',
1010- emptyOutDir: false,
1111- sourcemap: true,
1212- rollupOptions: {
1313- input: {
1414- client: path.resolve(__dirname, 'entrypoints/via-client/main.ts'),
1515- // OAuth callback page (still needed for popup OAuth flow)
1616- 'oauth-callback': path.resolve(__dirname, 'proxy/via-html/oauth-callback.html'),
1717- },
1818- output: {
1919- entryFileNames: 'seams-[name].js',
2020- format: 'es',
2121- },
2222- },
2323- },
2424- resolve: {
2525- alias: {
2626- '@': path.resolve(__dirname, './'),
2727- },
2828- },
2929- define: {
3030- 'import.meta.env.VITE_OAUTH_CLIENT_ID': JSON.stringify(process.env.VITE_OAUTH_CLIENT_ID || 'https://seams.so/oauth/client-metadata.json'),
3131- 'import.meta.env.VITE_OAUTH_REDIRECT_URI': JSON.stringify(process.env.VITE_OAUTH_REDIRECT_URI || 'https://sure.seams.so/oauth-callback.html'),
3232- 'import.meta.env.VITE_OAUTH_SCOPE': JSON.stringify(process.env.VITE_OAUTH_SCOPE || 'atproto transition:generic'),
3333- 'import.meta.env.BACKEND_URL': JSON.stringify(process.env.BACKEND_URL || 'http://localhost:8080'),
3434- },
3535-});