---
# System prepended metadata

title: API Gateway 更新說明

---

# 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()
  ])
} 
```
