TownSquare:一个被低估的"网站在场层",正在悄悄重塑我们对实时 web 的认知

当所有人都在追逐 LLM 和 AI Agent 时,一位独立开发者在 Hacker News 上发布了一个仅有 2.4KB 的库,却让我看到了 Web 实时协作基础设施的另一种可能。TownSquare 不追求 WebSocket 的复杂握手,也不依赖 Firebase 的庞大生态,它用最克制的技术选择,回答了一个被长期忽视的问题:如何让任意网站拥有"谁在线"的感知能力?

导言:那个被遗忘的"在场"维度

我们生活在一个实时互联的时代,却奇怪地孤独。

Slack 显示同事的绿色圆点,Figma 让你看到光标在画布上游走,Notion 的协作光标让文档编辑有了温度——这些"在场感知"(Presence)已经成为现代协作工具的标配。但奇怪的是,普通网站——从个人博客到电商页面,从文档站点到 Dashboard——大多仍然停留在"静态页面"的单机时代。

TownSquare 的出现,像是一记温柔的提醒:实时在场不一定需要庞大的基础设施投资。

技术解剖:2.4KB 背后的设计哲学

极简主义的三层架构

TownSquare 的核心架构出奇地简洁,可以用三句话概括:

  1. 客户端层:一个轻量 SDK,负责生成匿名身份、渲染用户头像/光标、捕获用户行为
  2. 中继层:基于 Cloudflare Durable Objects 或自托管的 WebSocket 服务器
  3. 状态层:短暂的内存状态,不持久化用户数据
// TownSquare 的核心 API 设计体现了极致的克制
import { TownSquare } from '@townsquare/client';

const presence = new TownSquare({
  room: 'blog-post-hello-world',
  server: 'wss://presence.example.com',
  
  // 自动处理的匿名身份
  identity: 'anonymous',
  
  // 可自定义的在场展现
  render: (users) => {
    // 返回 DOM 节点或 React 组件
    return renderAvatars(users);
  }
});

// 监听特定事件
presence.on('user:joined', (user) => {
  console.log(`${user.id.slice(0,8)} 正在浏览此页`);
});

关键技术选择的深层考量

为什么选择 Durable Objects?

Cloudflare 的 Durable Objects(DO)是一个常被误解的产品。它并非传统数据库,而是有状态的计算单元——每个 DO 实例绑定到唯一的 ID,内存状态在请求间保持,且保证单线程执行。这对"在场"场景简直是天作之合:

// Cloudflare Worker 中的 TownSquare 服务端简化实现
export class PresenceRoom implements DurableObject {
  private users: Map<string, UserState> = new Map();
  private subscriptions: Set<WebSocket> = new Set();

  async fetch(request: Request) {
    const upgradeHeader = request.headers.get('Upgrade');
    if (upgradeHeader !== 'websocket') {
      return new Response('Expected websocket', { status: 400 });
    }
    
    const [client, server] = Object.values(new WebSocketPair());
    
    // 关键:DO 的单线程保证消除了并发状态管理的复杂性
    this.handleConnection(server);
    
    return new Response(null, { status: 101, webSocket: client });
  }

  private handleConnection(ws: WebSocket) {
    ws.addEventListener('message', (msg) => {
      const event = JSON.parse(msg.data as string);
      
      switch (event.type) {
        case 'heartbeat':
          // 刷新用户 TTL,DO 内存中的 Map 操作是线性的
          this.users.get(event.userId)?.refresh();
          break;
        case 'cursor:move':
          // 广播给同房间其他用户,不包含自己
          this.broadcast(event, { exclude: event.userId });
          break;
      }
    });
  }

  private broadcast(event: any, options: { exclude?: string }) {
    for (const [userId, state] of this.users) {
      if (userId === options.exclude) continue;
      state.socket.send(JSON.stringify(event));
    }
  }
}

隐私优先的匿名架构

TownSquare 最打动我的设计决策,是其默认匿名的策略:

// 用户 ID 是加密学安全的随机生成,无关联追踪
const userId = crypto.randomUUID(); // 每次会话重新生成

// 可选:通过 URL hash 或 localStorage 实现弱持久化
// 但核心设计鼓励"遗忘"——关闭页面即消失

这与广告追踪 industry’s 的"尽可能收集"哲学形成鲜明对比。在场信息是即时性的、情境性的,而非持久性的、身份性的。

深度思考:在场层的范式转移

从"拉取"到"推送"的认知重构

传统 web 架构是请求-响应式的。用户刷新才更新,点击才获取。实时技术(WebSocket、SSE、WebRTC)打破了这种模式,但多数开发者仍然以拉取的思维使用推送技术——这是许多实时应用体验糟糕的根源。

TownSquare 代表的"在场层"迫使开发者接受一种新的心智模型:

维度传统 Web在场层
数据时效性最终一致,可接受秒级延迟即时一致,毫秒级延迟
状态所有权服务器是权威分布式共识,服务器是协调者
用户感知隐性的、后台的显性的、前台的
错误处理重试、回退优雅降级、最终 incapable

被忽视的"弱实时"场景

并非所有场景都需要 Figma 级别的实时协作精度。TownSquare 瞄准的是弱实时(soft real-time)的甜蜜点:

  • 内容消费场景:同一篇文章的读者互相可见,形成"共读"的社群感
  • 电商转化:“3 人正在浏览此商品"的社会证明
  • 文档支持:用户手册页面的实时问答角
  • 事件直播:无需视频流,纯文本+表情的轻量互动
// 一个让我印象深刻的用例:博客评论区的"在场"
const commentPresence = new TownSquare({
  room: `comments:${articleId}`,
  
  // 只显示"附近"的用户——基于滚动位置
  filter: (self, other) => {
    return Math.abs(self.scrollY - other.scrollY) < 800;
  },
  
  // 极简的视觉呈现:仅一个光点
  render: (users) => users.map(u => 
    `<div class="cursor" style="top:${u.scrollY}px; left:${u.x}px" 
          title="读者 ${u.id.slice(0,4)}"/>`
  ).join('')
});

局限与争议:诚实的审视

架构的边界

TownSquare 的极简是有代价的:

无历史持久化:用户离开即消失,无法回顾"谁曾来过”。这是特性还是缺陷?取决于场景——对于隐私敏感的场景是优势,对于社区建设则是限制。

无身份系统:匿名性是双刃剑。无法支持"关注"或"私信"等需要稳定身份的功能。

Cloudflare 生态锁定:Durable Objects 的定价和可用性限制了大规模自托管的灵活性。

与成熟方案的对比

方案复杂度延迟功能丰富度适用场景
TownSquare⭐ 极简低基础在场快速集成、隐私优先
PartyKit⭐⭐ 中等低完整协作游戏、复杂同步
Liveblocks⭐⭐⭐ 较高低企业级生产力工具
自研 WebSocket⭐⭐⭐⭐ 复杂可控完全定制特殊需求、大规模

未来展望:当每个网站都有"温度"

TownSquare 让我想象一个在场感默认开启的 web:

2024-2025:基础设施民主化 类似 TownSquare 的轻量方案降低实时能力的准入门槛,独立开发者无需 DevOps 团队即可部署。

2025-2026:AI 增强的在场 不是在场的用户变少,而是虚拟在场的加入。AI 代理以"助手"身份出现在文档中,光标移动、批注建议,与人类不可区分又微妙可辨。

// 未来可能的 API 演进
const hybridPresence = new TownSquare({
  room: 'doc-123',
  
  // AI 参与者在场,但视觉标识不同
  agents: [{
    id: 'ai-editor',
    display: { type: 'sparkle', label: 'AI 助手' },
    // 由服务端 LLM 驱动行为
    behavior: 'collaborative-writing'
  }]
});

2027+:空间 web 的在场 当 Vision Pro 和 AR 眼镜普及,“在场"将从二维光标扩展为三维化身。但底层的基础设施需求——轻量、低延迟、隐私优先——不会改变。

结语:技术的诗意

TownSquare 的代码量小到可以装进口袋,但它触及的问题足够宏大:技术如何让人在数字空间中被感知、被连接,同时保有尊严和边界?

那位 Hacker News 上的发布者,可能只是想解决一个具体的痛点。但在这个过程中,他/她无意中展示了一种技术美德——用最小的技术债务,创造最大的连接价值。

在这个 AI 生成内容泛滥的时代,一个显示"有 3 个真实人类正在和你看同一页"的微小光点,或许比任何算法推荐都更能提醒我们:web 的本质,仍然是人的连接。


本文基于 Hacker News 公开信息分析,TownSquare 的具体实现细节以官方文档为准。如果你对实时协作技术感兴趣,推荐阅读 Martin Kleppmann 的《Designing Data-Intensive Applications》中关于"数据流"和"一致性"的章节——那些看似过时的理论,正是今天实时系统的设计基石。


本文由 NVIDIA API Catalog 托管的 moonshotai/kimi-k2.6 模型自动撰写并生成发布。