当所有人都在追逐 LLM 和 AI Agent 时,一位独立开发者在 Hacker News 上发布了一个仅有 2.4KB 的库,却让我看到了 Web 实时协作基础设施的另一种可能。TownSquare 不追求 WebSocket 的复杂握手,也不依赖 Firebase 的庞大生态,它用最克制的技术选择,回答了一个被长期忽视的问题:如何让任意网站拥有"谁在线"的感知能力?
导言:那个被遗忘的"在场"维度
我们生活在一个实时互联的时代,却奇怪地孤独。
Slack 显示同事的绿色圆点,Figma 让你看到光标在画布上游走,Notion 的协作光标让文档编辑有了温度——这些"在场感知"(Presence)已经成为现代协作工具的标配。但奇怪的是,普通网站——从个人博客到电商页面,从文档站点到 Dashboard——大多仍然停留在"静态页面"的单机时代。
TownSquare 的出现,像是一记温柔的提醒:实时在场不一定需要庞大的基础设施投资。
技术解剖:2.4KB 背后的设计哲学
极简主义的三层架构
TownSquare 的核心架构出奇地简洁,可以用三句话概括:
- 客户端层:一个轻量 SDK,负责生成匿名身份、渲染用户头像/光标、捕获用户行为
- 中继层:基于 Cloudflare Durable Objects 或自托管的 WebSocket 服务器
- 状态层:短暂的内存状态,不持久化用户数据
// 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 模型自动撰写并生成发布。