Closed4

slackを分析する

ハトすけハトすけ

slackを分析してみた。雑多に書く。

web版はreactで作られている
reduxは使ってはおらず、ピュアreactで実装されている?

メッセージのpostはwebsocketではなくてrest
メッセージの取得はwebsocketではなくてrest
インフィニットローディング

get response json

// リクエストが成功したかどうか?
ok: true
// 実際のメッセージ
messages: []
// さらにメッセージがあるかどうか、infinite ローディングに使います。
has_more: true
// なぞ
pin_count: 0
// 気にしなくていいと思う
channel_actions_ts: null
channel_actions_count: 0

message json

// id 
client_msg_id: 'hogehoge'
// 基本 message これ以外なにかあると思う多分
type: 'message'
// 生メッセージ。表示の際はこちらは使われない
text: 'hogehoge'
// ユーザーid
user: 'hogehoge'
// timestamp
ts: 12345
// 編集されたかどうか。booleanではなく、編集されてなかったらそもそもこれがかえってこない
edited: {ts: 12345}
// メンションとかのチーム?
team
// 本命。表示の際はこれが順にrenderされていく
blocks
// emoji
reactions

あるときとないときがある

// typeのさらにsubtype。基本ないが、〇〇さんが招待されました的なメッセージでみる
subtype: 'hogehoge'
// 〇〇さんが招待されましたというメッセージのときにuserIdがはいる
inviter: 'hogehoge'
// ファイルアップロードされたもの
files
// 多分自分がアップロードしたらtrue
upload
// urlプレビューとか
attachments

block json

メッセージのstringがblockとして入ってる。linkやtextやcodeなどに分解される。

"type":"rich_text",
"block_id":"KeE",
"elements":[]

子 elementsの中身

{
  "type":"text",
  "text":"docker \u95a2\u9023",
  "style":{
    "code":true
  }
},
{
  "type":"text",
  "text":"\n\u307e\u3068\u3081\n"
},
{
  "type":"link",
  "url":"https:\/\/example.com\/hogehoge"
},
{
  "type":"text",
  "text":"\n\ndocker \u4f5c\u308b\u969b\u306e\u52d8\u9055\u3044\n"
},
{
  "type":"link",
  "url":"https:\/\/techracho.bpsinc.jp\/hachi8833\/2014_06_16\/17982"
},
{
  "type":"text",
  "text":"\n"
},
{
  "type":"link",
  "url":"https:\/\/phusion.github.io\/baseimage-docker\/"
}
ハトすけハトすけ

post apiみたら、生テキストを送るのではなく、フロントで一旦blockに分解して、送っていた。
お前フロントで処理されていたのか、、、とびっくり。

ハトすけハトすけ

websocketまわり。
チャンネルごとにsocketをつなぐのではなく、全体で1つのsocketをつないでいる。

slackのブログ
https://slack.engineering/flannel-an-application-level-edge-cache-to-make-slack-scale/

おおまかに2通りの使われ方。

  • 存在チェック
  • メッセージが投稿された。リアクションが押されたなどのイベント

リアクションイベント

{
  "type":"reaction_added",
  "user":"hogehoge",
  "item":{
    "type":"message",
    "channel":"チャンネルid",
    "ts":"タイムスタンプ"
  },
  "reaction":"リアクションの:smile:みたいな文字列",
  "item_user":"なぞ",
  "event_ts":"なぞ。なんかのタイムスタンプ",
  "ts":"なんかのタイムスタンプ"
}

メッセージ投稿イベント

blocks: [{type: "rich_text", block_id: "/7YL+",}]
channel: "チャンネルid"
client_msg_id: "メッセージid?"
event_ts: "12345"
source_team: "??"
suppress_notification: false
team: "??"
text: "投稿テスト"
ts: "12345"
type: "message"
user: "ユーザーid"
user_team: "??"

ときどきくるイベント
多分、現在の未読情報とか取得してるのか?

channel: "チャンネルid"
event_ts: "12345"
mention_count: 0
mention_count_display: 0
num_mentions: 0
num_mentions_display: 0
ts: "12345"
type: "channel_marked"
unread_count: 0
unread_count_display: 0
ハトすけハトすけ

結論

reactでなるべく上位の層に、websocketのコネクションを1つだけ作る(ここらへんのノウハウはこちらの神サイトが詳しい)。
websocketは基本送信では使わずに、受信のみに専念する。

  • メッセージの送信
  • リアクション(emoij)の追加や削除
  • イベントap
    などこれらはすべてrest apiによって実現される。

逆に

  • メッセージの受信
  • リアクションが追加されたよ受信
  • だれかがタイピング中だよ受信
  • 現在存在中だよ、送信受信
    はwebsocketによって実現される。

そのた学んだこと

  • メッセージのbodyの中身はブロック単位でそのまま表示できるようにしてあると便利
  • メッセージの解析(リンクあるなとか)はバックエンドではなくフロントエンドにやらせるとAPIが共通になり、バックエンドのロジックもシンプルになる(賢いフロントパターンじゃねとも思う...これくらいならいいのか)。つまりメッセージのpostの際に、そのまま表示できるブロック単位で送信する。
  • 通信bodyはURLエンコードする
このスクラップは2020/12/08にクローズされました