# API Gateway 更新說明 * UpdateStremer 的 API 重新命名為 EditMember,參數完全不用改 * getGiftRecord 新增必帶參數(username) 格式範例: ```javascript // 必填欄位 website: "", username: "", uppername: "", key: "", startDate: "", endDate: "", // 選填欄位 streamerAccount: "", // filter 直播主帳號用,格式 streamerAccount or streamerAccount@agent or streamerAccount@agent.masterAgent account: "", // filter 使用者帳號用,格式 account or account@agent or account@agent.masterAgent starttime: "", endtime: "", giftID: "", // filter 指定禮物 page: "", pagelimit: "", ``` ## Client Gateway 更新說明 * client 呼叫 介面為 http://api.{domainName}/api/action/{actionName},參數如下 格式範例: ```json { query: '', Authorization: '' } ``` 客端程式範例: ``` typescript const service = axios.create({ timeout: 5000, headers: { 'Content-Type': 'application/json' } }); // Request interceptors service.interceptors.request.use( config => { const store = Store(); const authorization = store.state.user.userState.Authorization; if (authorization) { config.data['Authorization'] = authorization; } return config; }, error => { Promise.reject(error); } ); ``` * SendGift 的 API query 參數需多一個必填欄位 Authorization 格式範例: ```json { server:'giftSystem', actionName: 'sendGift', query: '{"Authorization": "xx", "giftID":1,"giftCount":1,"memberID":"member@agent.masteragent","gameID":"livestreamer@agent.masteragent","agentID":"agent.masteragent"}' } ``` * actionName 的改名變動 * getStreamerInfo -> getRoomInfo * setStreamerInfo -> setRoomInfo * getPopularStreamer -> getRankingRoomList * 呼叫 client-gateway 的 login 及 loginStreamer 要更新 * 原本是呼叫 accessGateway,現在各別獨立呼叫兩支 API,分別為loginAction 及 loginStreamer ## Websocket 連線訊息格式 ### 系統訊息 系統訊息格式範例: ```json { "context": "api", // 目前系統訊息都統一會使用context為api "data": { // 系統訊息 "code": 40101, "message": "The account is not logged in" } } ``` 其中有以下系統訊息 | code | message | describe | solution | | ----- | ---------------------------------------------------- | ---------------------------- | ---------------------------------------------------- | | 40101 | "The account is not logged in" | 連線沒有進行驗證與登入 | 進行 websocket verify | | 40301 | "The account had been ban" | 該會員被禁止進入聊天室 | 直播主解除 | | 40302 | "The token is not valid" | token 失效或過期 | 重新登入 | | 40303 | "This account has been temporarily banned from chat" | 該會員被短暫禁言 | 等待禁言時間過期 | | 40304 | "This account has been banned from chat" | 該會員被永久禁言 | 直播主解除 | | 40305 | "Authentication for this message failed" | 該聊天訊息驗證失敗 | 確認是否聊天格式中 memberID 與 verify 時帶的是否一樣 | | 40306 | "Only streamer can send url" | 只有直播主允許訊息中夾帶網址 | 無 | | 40308 | "System is maintaining" | 系統進行維護 | 等待維護結束 | > 注意 40308 目前未實作完成 ### 聊天訊息格式說明 <a id="message-format"></a> 聊天訊息可以分用戶聊天訊息與系統廣播訊息其中可以使用資料中 from 來進行分辨 如果是用戶的會是一個標準的 UUID 字串 系統則是為 0 其中訊息格式為 ```json "context": "user", "from": "19b514d9-4ddb-40a9-a25c-5b0e22803ad4", // 用戶連線UUID而系統廣播則為0 "message": 請參考以下訊息格式, // 格式為json "room": "chatroomSystem-chatroom-mabu.mabumaster-ben001@mabu.mabumaster", // 聊天室名稱 "sentAt": 1603210582540 // 發送訊息時間 單位是 micro second ``` 其中格式中 message 可以分為以下幾種 > 注意 這些都需要轉為 json 掛載至訊息格式 message 中 --- ### 聊天訊息類型格式 <a id="chat-message"></a> 資料格式: | key | type | describe | | --- | ---- | -------- | | memberID | string | 會員 ID| | type | string | 聊天訊息需要固定為 "chat"| | role | string | 角色 直播主: "streamer" 會員: "member" | | name | string | 會員暱稱 | | text | string | 訊息 | | textColor | string | 訊息顏色(HEX) | example:s ```json { "memberID": "ben@mabu.mabumaster", "type": "chat", "name": "ben", "text": "Hello", "textColor": "#FFF" } ``` ### 禮物訊息格式 資料格式: | key | type | describe | | --- | ---- | -------- | | type | string | 禮物訊息需要固定為 "gift" | | presenter | string | 贈禮者 memberID | | presenterNickname | string | 贈禮者暱稱 | | recipient | string | 收禮直播主 memberID | | gift | object | 贈送禮物的資訊 {id: 禮物 ID, name: 禮物名稱, animeUrl: 禮物動畫 url(部分路徑), count: 數量}| ### 禮物排行榜訊息格式 資料格式: | key | type | describe | | --- | ---- | -------- | | type | string | 禮物排行榜訊息需要固定為 "giftRank" | | rankState | string | "UP" or "DOWN" or "PLATEAU" or "NEW" | | rankingList | array(object) | { memberId: 會員 ID; nickName: 會員暱稱; rank: 排名; state: 排名上升或下降; rise: 上升或下降名次; score: 分數;} ### 系統廣播訊息格式 資料格式: | key | type | describe | | --- | ---- | -------- | | type | string | 系統廣播訊息需要固定為 "system" | | message | string | 系統訊息 | ### 追隨直播主開台狀態訊息格式 資料格式 | key | type | describe | | --- | ---- | -------- | | type | string | 追隨直播主開台狀態訊息需要固定為 "sub" | | streamer | string | 直播主 memberID | | status | string | 開台狀態 目前只有 "start" "done" | ### 頻道火熱度訊息格式 資料格式 | key | type | describe | | --- | ---- | -------- | | type | string | 頻道火熱度訊息需要固定為 "popularity" | | streamer | string | 直播主 memberID | | value | number | 火熱度數值 | ### 會員追隨直播主訊息格是 資料格式 | key | type | describe | | --- | ---- | -------- | | type | string | 頻道火熱度訊息需要固定為 "follow" | | streamer | string | 直播主memberID | | follower | string | 追隨者memberID| ### 會員進入聊天室訊息格式 資料格式 | key | type | describe | | --- | ---- | -------- | | type | string | 會員進入聊天室訊息需要固定為 "joinRoom" | | member | string | 進入聊天室會員的 memberID | | relationship | Array(number) | 1: 禁言 2: 黑單 3: 管理員 4: 追隨 5: 暫時禁言 | | nickname | string | 進入聊天室會員的 nickname | | accountID | string | 進入聊天室會員的 accountID | | avatar | string | 進入聊天室會員的頭像 | | equipmentBar | string | 進入聊天室會員的勳章 | | vip | string | 進入聊天室會員的vip等級 | | role | string | 進入聊天室會員的角色 | ### 客端顯示會員進入聊天室訊息格式(跟 joinRoom 差異:避免客端頻繁進出刷訊息) 資料格式 | key | type | describe | | --- | ---- | -------- | | type | string | 會員進入聊天室訊息需要固定為 "welcome" | | member | string | 進入聊天室會員的 memberID | | relationship | Array(number) | 1: 禁言 2: 黑單 3: 管理員 4: 追隨 5: 暫時禁言 | | nickname | string | 進入聊天室會員的 nickname | | accountID | string | 進入聊天室會員的 accountID | | avatar | string | 進入聊天室會員的頭像 | | equipmentBar | string | 進入聊天室會員的勳章 | | vip | string | 進入聊天室會員的vip等級 | | role | string | 進入聊天室會員的角色 | ### 會員離開聊天室訊息格式 資料格式 | key | type | describe | | --- | ---- | -------- | | type | string | 會員離開聊天室訊息需要固定為 "leaveRoom" | | member | string | 離開聊天室會員的 memberID | | nickname | string | 離開聊天室會員的 nickname | ### 直播串流狀態訊息格式 資料格式 | key | type | describe | | --- | ---- | -------- | | type | string | 會員離開聊天室訊息需要固定為 "leaveRoom" | | streamer | string | 直播主 memberID | | status | string | "start" or "done" | ## 進入聊天室流程 ``` 會員登入取得token(Authorization) ---> 與chatroomSystem進行連線 ---> 進入聊天室 ---> 進行connection verify ---> 進入聊天室成功 ``` ## 使用 actionheroWebsocketClient 連線 chatroomSystem 1、取得 actionheroWebsocketClient 需要再 html 中掛載腳本 其中需要注意的是 src 需要依據連線環境的域名進行轉換 建議使用 nginx 進行區別環境轉導 ```javascript <script type="text/javascript" src="{{環境domain}}/ChatRoomsSystem/public/javascript/ActionheroWebsocketClient.min.js" ></script> ``` example code: ```typescript function buildChatroomName(agentID: string, streamerMemberID: string): string { return `chatroomSystem-chatroom-${agentID}-${streamerMemberID}`; } const client = new ActionheroWebsocketClient(); client.on("connected", function () { // websocket連線成功 但並不代表有加入聊天室 console.log("connected!"); }); client.on("disconnected", function () { console.log("disconnected :("); }); client.on("reconnect", function () { console.log("reconnect"); }); client.on("reconnecting", function () { console.log("reconnecting"); }); type SystemMessage = { context: "api"; message: { code: number; message: string }; }; client.on("api", (payload: SystemMessage) => { // 系統訊息 詳細麻煩查詢上面 "系統訊息格式" }); type SayMessagePayload = { context: "user"; from: string | 0; room: string; message: string; // JSON sentAt: number; }; client.on("say", function (payload: SayMessagePayload) { const encodeMessage = JSON.parse(payload.message); switch (encodeMessage.type) { case "chat": // 聊天室顯示 聊天訊息 type ChatMessage = { type: "chat"; memberID: string; name: string; text: string; textColor: string; } const { name, text, textColor } = encodeMessage as ChatMessage; break; case "gift": // 聊天室顯示 贈禮訊息 type GiftMessage = { type: "gift"; presenter: string; recipient: string; gift: { id: number; name: string; animeUrl: string; count: number }; }; const { presenter, recipient, gift } = encodeMessage as GiftMessage; break; case "system": // 聊天室顯示 系統訊息 type SystemMessage = { type: "system"; message: string } const { message } = encodeMessage as SystemMessage; break; case "sub": { // 追蹤的直播主開台關台通知 type SubMessage = { type: "follow", streamer: string, status: "start" | "done"; } const { streamer, status } = encodeMessage; break; } case "giftRank": { type RankState = "UP" | "DOWN" | "PLATEAU" | "NEW"; type RankingList = { memberId: string; nickName: string; rank: number; // 排名 state: string; // 與上次排名上升或下降 rise: number; // 上升或下降的名次 score: number; // 排行分數 }; type GiftRankMessage = { type: "giftRank"; rankStateL: RankState; rankingList: RankingList; } const { rankState, rankingList } = encodeMessage as GiftRankMessage; break; } case "popularity": { // 火熱度 type PopularityMessage = { type: "popularity", streamer: string, value: number } const { streamer, value } = encodeMessage as PopularityMessage; break; } case "follow": { // 有人追隨直播主 type FollowMessage = { type: "follow", streamer: string, follower: string, } const { streamer, follower } = encodeMessage as FollowMessage; break; } case "joinRoom": { // 用戶進入聊天室 type JoinRoomMessage = { type: "joinRoom", member: string, // memberID relationship: Array<number> // 1: 禁言 2: 黑單 3: 管理員 4: 追隨 5: 暫時禁言 } const {member, relationship} = encodeMessage as JoinRoomMessage; } case "leaveRoom": { // 用戶離開聊天室 type LeaveRoomMessage = { type: "joinRoom", member: string, // memberID relationship: Array<number> // 1: 禁言 2: 黑單 3: 管理員 4: 追隨 5: 暫時禁言 } const {member, relationship} = encodeMessage as LeaveRoomMessage; } case "streamStatus": { // 串流狀態 type StreamStatus = { type: "streamStatus", streamer: string // 直播主memberID status: "start" | "done" } const {streamer, status} = encodeMessage as StreamStatus } } }); client.connect((error: string, details: string) => { if (error) { // server錯誤訊息 } else { const [account, agentID] = streamerMemberID.split("@") const room = buildChatroomName(agentID, streamerMemberID); client.roomAdd(room, () => { // 與server驗證 如果是遊客的話並不需要登入 client.action("verify", { memberID: "memberID", // user's memberID chatroom: room, Authorization: "memberToken", // 使用者token }); }) } }); function saySomething(text: string): void { client.say( chatroom, JSON.stringify({ type: "chat", memberID: memberID // user's memberID name: nickname // user's nickname, text: "Hello World", textColor: "#FFF" // color HEX code }) ) } // 離開連線 async function leaveWebsocket(): Promise<void>{ await Promise.all([ // 如果有使用event的話需要對應刪除 下列列舉幾個 client.removeAllListeners('connected'), client.removeAllListeners('disconnected'), client.removeAllListeners('say'), // 最後一定要進行斷連線 client.disconnect() ]) } ```