聊天是 Korat 其他部分共同依赖的那一块。朋友邀你上他那桌,邀请是一张聊天卡片;有人分享一条帖子,它落下来就是一张能点开的卡片;约会里两个人互相喜欢的瞬间,就变成一段真实的对话。因为消息只有一个落点,你也就只有一个地方要看,而消息模块只需要把一件事做好:两个人之间的一条会话,附件种类足够多到你不必换应用。
两个人,一条会话
Korat 的聊天只有一对一:一条私聊里正好两个人,没有任何办法拉进第三个人。多人对话发生在另外两个地方,而它们都不是这个消息模块。群组用帖子来承载:群组帖子带着一个群 id,因此直接继承了信息流那一套评论、@提及和通知——有回复层级、可搜索、长期留存。此外,群组和运动俱乐部都可以有子房间:列在群组内部的 Discord 式文字频道,建在自己的表和自己的权限上,能做的事比一条私聊少得多。下面把限制写全了,因为一个看起来像聊天、实际不是的房间,比没有这个房间更糟。
一对一换来的是深度。你可以引用某条消息回复、发出后再编辑、撤回、把重要的一条置顶,也可以用六个常用表情快速回应,或展开三十个表情的长条。正在输入的提示和已读状态都按你预期的方式工作。
一条消息里能装什么
文字之外还有图片、任意文件,以及语音消息——用设备自带的 MediaRecorder 录成 M4A 容器里的 AAC,在气泡里直接播放。也可以发送位置。此外还有应用其他部分铸出来的系统卡片:分享的帖子、餐桌邀请、以及一次通话的记录。
新消息会给对方的设备推送通知——一对一消息和商家收件箱都一样。它只送到没有被吊销的设备上,而打开那个会话就会清掉那个会话的通知。一个产生了消息却没人被告知的聊天不是安静,是坏了。必须一起说的限制是:网页版没有推送。如果你在浏览器里聊天,你会在打开标签页的时候看见消息,而不是它到达的时候。
通话记录卡片比听上去重要。一通响过又没接的电话本来是看不见的:你事后打开应用,没有任何东西告诉你发生过什么。把通话写进会话,就意味着一段关系的历史是一条可以往上滚的列表,而不是对话在一处、通话记录在另一处。
通话要两边都开了锁才出现
语音和视频通话跑在真实的 WebRTC 上,支持静音、关摄像头、切换前后置、全屏来电界面,以及应用在后台时的来电通知。特别的是它前面那道门:通话许可是双向的,且按会话独立。一条会话里,只有双方都在自己这侧开了锁,通话按钮才会出现;在那之前谁都打不通谁。
这是对“响铃是一个应用能做的最具侵入性的事”这一事实的正面回应。一个从约会或店铺页找到你的陌生人可以发消息,你可以不理;但除非你明确同意,他不能在凌晨两点让你的手机响起来。许可按会话计算,所以对一个人开锁不代表对任何别人开锁;而且它是一把你去开的锁,不是藏在偏好设置深处的一个开关。
两条诚实的限制。私聊只有一对一——一条会话无法变成群组,本页描述的这个消息模块没有多人版本。真正存在的是群组和俱乐部自带的子房间,它是另一套更薄的系统,建在不同的表上:纯文字,能发、能删自己发的,但不能编辑消息、不能针对某条消息回复、不能用表情回应、不能发图片、文件或语音消息,也不能从房间里发起通话。本页任何内容都不应被读成对子房间的描述。
通话没有端到端加密,也没有 TURN 服务器。在两端网络能直接互通时媒体走点对点;但没有中继服务器,某些运营商网络与严格 NAT 的组合就是连不上。通话建立不起来,通常就是这个原因——那是一块我们还没搭起来的基础设施,不是应用里的 bug。
在线状态是心跳,不是长连接
Korat 里的绿点只表示一件具体的事:那个人的客户端在过去九十秒内写过一次 last_seen 时间戳。它是按间隔轮询,不是持久连接,所以省电,也因此很粗。四十秒前刚关掉应用的人看起来还在线;进了隧道的人还没走远就先暗了下去。
我们叫它心跳而不是“在线”,是因为在线状态最容易被当成证据——证明消息被看到了,证明对方在故意不回。九十秒的轮询窗口撑不起这个分量。
找到你要的那条会话,以及怎么处理一个人
两个功能在维持长会话列表的可用性。第一个是会话备注名:把任意会话改成你实际对这个人的称呼,而这个名字只有你看得见。它绝不会漏进信息流、群组、评论或对方的资料页——你存成“房东”的那个人,在所有地方显示的仍是他自己写的名字。Korat 里其他地方的名字都是从资料页实时解析的,正是为了让一处改名不会与另一处失同步。
第二个是标签:你自己创建的彩色标签,成员由你控制,可增可改可删,同时充当会话列表的筛选器,旁边还有未读、官方账号和来自约会这三个内置筛选。搜索同时覆盖会话名和最后一条消息,所以记得半句话就够找回一条会话。会话可以置顶、标为已读或删除。
服务端能看到什么,以及你能对一个人做什么
消息以明文存在服务端,由行级安全策略把一条会话的行限制在两位参与者之内。Korat 的聊天、通话和文件都没有端到端加密,除托管数据库自带的部分外也没有应用层静态加密。如果你要谈的事情需要这些,请换一个为此而生的工具——准确描述我们的威胁模型,比一句安抚的话对你更有用。
至于人这一侧:你可以从会话里或资料页拉黑某人,这会同时隐藏他的会话与帖子,并把他从你的约会卡片堆里移除,你也可以举报某个用户。拉黑是一个动作,在应用的每个角落生效,不需要你逐个界面重复。
落进会话里的那些卡片各有来处:朋友邀你上他那桌,那张桌子来自点餐;有人分享一条帖子,它就是动态里的那一条。
顾客发给店铺的消息走的也是这套会话,只是店家那一侧读到的是商家控制台里的收件箱;扫一张数字名片之后要不要直接说话,同样回到这里。