聊天是 Korat 其他部分共同依賴的那一塊。朋友邀你上他那桌,邀請是一張聊天卡片;有人分享一條帖子,它落下來就是一張能點開的卡片;約會裡兩個人互相喜歡的瞬間,就變成一段真實的對話。因為訊息只有一個落點,你也就只有一個地方要看,而訊息模組只需要把一件事做好:兩個人之間的一條會話,附件種類足夠多到你不必換應用。
兩個人,一條會話
Korat 的聊天只有一對一:一條私聊里正好兩個人,沒有任何辦法拉進第三個人。多人對話發生在另外兩個地方,而它們都不是這個訊息模組。群組用帖子來承載:群組帖子帶著一個群 id,因此直接繼承了資訊流那一套評論、@提及和通知——有回覆層級、可搜尋、長期留存。此外,群組和運動俱樂部都可以有子房間:列在群組內部的 Discord 式文字頻道,建在自己的表和自己的權限上,能做的事比一條私聊少得多。下面把限制寫全了,因為一個看起來像聊天、實際不是的房間,比沒有這個房間更糟。
一對一換來的是深度。你可以引用某條訊息回覆、發出後再編輯、撤回、把重要的一條置頂,也可以用六個常用表情快速回應,或展開三十個表情的長條。正在輸入的提示和已讀狀態都按你預期的方式工作。
一條訊息裡能裝什麼
文字之外還有圖片、任意檔案,以及語音訊息——用裝置自帶的 MediaRecorder 錄成 M4A 容器裡的 AAC,在氣泡裡直接播放。也可以傳送位置。此外還有應用其他部分鑄出來的系統卡片:分享的帖子、餐桌邀請、以及一次通話的記錄。
新訊息會給對方的裝置推送通知——一對一訊息和商家收件箱都一樣。它只送到沒有被吊銷的裝置上,而開啟那個會話就會清掉那個會話的通知。一個產生了訊息卻沒人被告知的聊天不是安靜,是壞了。必須一起說的限制是:網頁版沒有推送。如果你在瀏覽器裡聊天,你會在開啟標籤頁的時候看見訊息,而不是它到達的時候。
通話記錄卡片比聽上去重要。一通響過又沒接的電話本來是看不見的:你事後開啟應用,沒有任何東西告訴你發生過什麼。把通話寫進會話,就意味著一段關係的歷史是一條可以往上滾的列表,而不是對話在一處、通話記錄在另一處。
通話要兩邊都開了鎖才出現
語音和視訊通話跑在真實的 WebRTC 上,支援靜音、關攝像頭、切換前後置、全屏來電介面,以及應用在後臺時的來電通知。特別的是它前面那道門:通話許可是雙向的,且按會話獨立。一條會話裡,只有雙方都在自己這側開了鎖,通話按鈕才會出現;在那之前誰都打不通誰。
這是對“響鈴是一個應用能做的最具侵入性的事”這一事實的正面回應。一個從約會或店鋪頁找到你的陌生人可以發訊息,你可以不理;但除非你明確同意,他不能在凌晨兩點讓你的手機響起來。許可按會話計算,所以對一個人開鎖不代表對任何別人開鎖;而且它是一把你去開的鎖,不是藏在偏好設定深處的一個開關。
兩條誠實的限制。私聊只有一對一——一條會話無法變成群組,本頁描述的這個訊息模組沒有多人版本。真正存在的是群組和俱樂部自帶的子房間,它是另一套更薄的系統,建在不同的表上:純文字,能發、能刪自己發的,但不能編輯訊息、不能針對某條訊息回覆、不能用表情回應、不能發圖片、檔案或語音訊息,也不能從房間裡發起通話。本頁任何內容都不應被讀成對子房間的描述。
通話沒有端到端加密,也沒有 TURN 伺服器。在兩端網路能直接互通時媒體走點對點;但沒有中繼伺服器,某些運營商網路與嚴格 NAT 的組合就是連不上。通話建立不起來,通常就是這個原因——那是一塊我們還沒搭起來的基礎設施,不是應用裡的 bug。
線上狀態是心跳,不是長連線
Korat 裡的綠點只表示一件具體的事:那個人的客戶端在過去九十秒內寫過一次 last_seen 時間戳。它是按間隔輪詢,不是持久連線,所以省電,也因此很粗。四十秒前剛關掉應用的人看起來還線上;進了隧道的人還沒走遠就先暗了下去。
我們叫它心跳而不是“線上”,是因為線上狀態最容易被當成證據——證明訊息被看到了,證明對方在故意不回。九十秒的輪詢視窗撐不起這個分量。
找到你要的那條會話,以及怎麼處理一個人
兩個功能在維持長會話列表的可用性。第一個是會話備註名:把任意會話改成你實際對這個人的稱呼,而這個名字只有你看得見。它絕不會漏進資訊流、群組、評論或對方的資料頁——你存成“房東”的那個人,在所有地方顯示的仍是他自己寫的名字。Korat 裡其他地方的名字都是從資料頁實時解析的,正是為了讓一處改名不會與另一處失同步。
第二個是標籤:你自己建立的彩色標籤,成員由你控制,可增可改可刪,同時充當會話列表的篩選器,旁邊還有未讀、官方帳號和來自約會這三個內建篩選。搜尋同時覆蓋會話名和最後一條訊息,所以記得半句話就夠找回一條會話。會話可以置頂、標為已讀或刪除。
服務端能看到什麼,以及你能對一個人做什麼
訊息以明文存在服務端,由行級安全策略把一條會話的行限制在兩位參與者之內。Korat 的聊天、通話和檔案都沒有端到端加密,除託管資料庫自帶的部分外也沒有應用層靜態加密。如果你要談的事情需要這些,請換一個為此而生的工具——準確描述我們的威脅模型,比一句安撫的話對你更有用。
至於人這一側:你可以從會話裡或資料頁拉黑某人,這會同時隱藏他的會話與帖子,並把他從你的約會卡片堆裡移除,你也可以舉報某個使用者。拉黑是一個動作,在應用的每個角落生效,不需要你逐個介面重複。
落進會話裡的那些卡片各有來處:朋友邀你上他那桌,那張桌子來自點餐;有人分享一條帖子,它就是動態裡的那一條。
顧客發給店鋪的訊息走的也是這套會話,只是店家那一側讀到的是商家控制檯裡的收件箱;掃一張數字名片之後要不要直接說話,同樣回到這裡。