@sentinel-it/meowmeow-sdk 1.0.0-rc.6207efd

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sentinel IT
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # MeowMeow SDK
2
+
3
+ 可嵌入式群組聊天室 JavaScript / TypeScript SDK
4
+
5
+ [API Reference](api-docs/index.md)
6
+
7
+ ---
8
+
9
+ # 快速開始
10
+
11
+ ## 安裝
12
+
13
+ **npm / pnpm(ESM)**
14
+
15
+ ```bash
16
+ npm install @sentinel-it/meowmeow-sdk
17
+ # 或
18
+ pnpm add @sentinel-it/meowmeow-sdk
19
+ ```
20
+
21
+ **CDN(IIFE)**
22
+
23
+ ```html
24
+ <script src="https://cdn.jsdelivr.net/npm/@sentinel-it/meowmeow-sdk@latest/dist/meowmeow-sdk.js"></script>
25
+ <script>
26
+ const { MeowMeow, MeowMeowClient } = MeowMeowSDK
27
+ </script>
28
+ ```
29
+
30
+ ## Token
31
+
32
+ 呼叫 SDK 前,後端須向 MeowMeow 服務端取得使用者 JWT Token 並傳給前端。
33
+
34
+ 若提供 `onTokenRefresh`,SDK 會在 Token 到期前 30 秒自動呼叫以取得新 Token,連線不會中斷。若未提供,Token 過期後連線將會斷開。
35
+
36
+ ---
37
+
38
+ ## MeowMeow — 嵌入聊天室 UI
39
+
40
+ `MeowMeow` 可直接將聊天室 Widget 嵌入頁面。
41
+
42
+ ```ts
43
+ import { MeowMeow } from '@sentinel-it/meowmeow-sdk'
44
+
45
+ const chat = new MeowMeow({
46
+ token: 'YOUR_JWT_TOKEN',
47
+ locale: 'zh',
48
+ title: '客服聊天室',
49
+ onTokenRefresh: async () => {
50
+ const res = await fetch('/api/chat-token')
51
+ const { token } = await res.json()
52
+ return token
53
+ },
54
+ })
55
+
56
+ await chat.mount()
57
+ chat.show()
58
+ ```
59
+
60
+ ### 初始化選項
61
+
62
+ | 選項 | 型別 | 必填 | 說明 |
63
+ |------|------|:----:|------|
64
+ | `token` | `string` | ✓ | 使用者 JWT Token |
65
+ | `locale` | `string` | | 介面語言,預設 `'en-US'`,支援 `en-US`、`zh-Hant`、`zh-Hans`、`vi-VN`、`th-TH`、`id-ID`、`pt-BR`、`my-MM`、`hi-IN`、`ru-RU`、`ko-KR`、`ms-MY`、`fil-PH` |
66
+ | `theme` | `'p1' \| 'p2'` | | 介面主題,預設 `'p1'`,傳入無效值自動回退到 `'p1'` |
67
+ | `title` | `string \| Record<string, string>` | | 聊天室標題,可傳入語系對照物件 |
68
+ | `onTokenRefresh` | `() => Promise<string>` | | Token 刷新回呼 |
69
+
70
+ `title` 可傳入語系對照物件:
71
+
72
+ ```ts
73
+ title: {
74
+ 'en-US': 'Chat Room', // 英語 ( 預設 )
75
+ 'zh-Hant': '聊天室', // 繁體中文
76
+ 'zh-Hans': '聊天室', // 簡體中文
77
+ 'vi-VN': 'Phòng trò chuyện', // 越南語
78
+ 'th-TH': 'ห้องแชท', // 泰語
79
+ 'id-ID': 'Ruang Obrolan', // 印尼語
80
+ 'pt-BR': 'Sala de bate-papo', // 葡萄牙語 ( 巴西 )
81
+ 'my-MM': 'ချတ်ခန်း', // 緬甸語
82
+ 'hi-IN': 'चैट रूम', // 印地語
83
+ 'ru-RU': 'Чат', // 俄語
84
+ 'ko-KR': '채팅방', // 韓國語
85
+ 'ms-MY': 'Bilik Sembang', // 馬來語
86
+ 'fil-PH': 'Silid ng Chat', // 菲律賓語
87
+ }
88
+ ```
89
+
90
+ ### 方法
91
+
92
+ | 方法 | 說明 |
93
+ |------|------|
94
+ | `mount(): Promise<void>` | 掛載 Widget 至 DOM |
95
+ | `unmount(): void` | 卸載並釋放資源 |
96
+ | `show(): void` | 顯示 Widget |
97
+ | `hide(): void` | 隱藏 Widget |
98
+ | `join(roomId: string): Promise<void>` | 加入指定聊天室(需先呼叫 `mount()`) |
99
+ | `setLocale(locale: string): void` | 動態切換介面語言(`en-US`、`zh-Hant`、`zh-Hans`、`vi-VN`、`th-TH`、`id-ID`、`pt-BR`、`my-MM`、`hi-IN`、`ru-RU`、`ko-KR`、`ms-MY`、`fil-PH`) |
100
+ | `setTheme(theme: 'p1' \| 'p2'): void` | 動態切換介面主題,傳入無效值自動回退到 `'p1'` |
101
+
102
+ ### 事件
103
+
104
+ 使用 `chat.on(event, handler)` 監聽。
105
+
106
+ | 事件 | Payload | 說明 |
107
+ |------|---------|------|
108
+ | `ready` | — | Widget 掛載完成,可開始互動 |
109
+
110
+ ### CSS 自訂參數
111
+
112
+ Widget 使用 Shadow DOM 掛載,可在宿主元素的 CSS 中覆寫下列 CSS 自訂屬性來調整佈局:
113
+
114
+ | 參數 | 預設值 | 說明 |
115
+ |------|--------|------|
116
+ | `--meowmeow-z-index` | `auto` | 宿主元素的 `z-index` |
117
+ | `--meowmeow-mobile-preview-bottom` | `56px` | Mobile:預覽訊息條的底部距離 |
118
+ | `--meowmeow-mobile-room-top` | `313px` | Mobile:聊天室面板的頂部距離 |
119
+ | `--meowmeow-desktop-toggle-size` | `70px` | Desktop:開關按鈕的尺寸(寬高) |
120
+ | `--meowmeow-desktop-toggle-right` | `200px` | Desktop:開關按鈕距右側的距離 |
121
+ | `--meowmeow-desktop-toggle-bottom` | `250px` | Desktop:開關按鈕距底部的距離 |
122
+ | `--meowmeow-desktop-room-width` | `375px` | Desktop:聊天室面板的寬度 |
123
+ | `--meowmeow-desktop-room-height` | `586px` | Desktop:聊天室面板的高度 |
124
+
125
+ ```css
126
+ meowmeow-widget {
127
+ --meowmeow-z-index: 1000;
128
+ --meowmeow-desktop-toggle-right: 32px;
129
+ --meowmeow-desktop-toggle-bottom: 32px;
130
+ }
131
+ ```
132
+
133
+ ---
134
+
135
+ ## 無頭模式(`MeowMeowClient`)
136
+
137
+ 若不需要內建 UI,可直接使用 `MeowMeowClient`。
138
+
139
+ ```ts
140
+ import { MeowMeowClient } from '@sentinel-it/meowmeow-sdk'
141
+
142
+ const client = new MeowMeowClient({
143
+ token: 'YOUR_JWT_TOKEN',
144
+ onTokenRefresh: async () => fetchNewToken(),
145
+ })
146
+
147
+ const result = await client.join('YOUR_ROOM_ID')
148
+ if (!result.ok) {
149
+ console.error('加入失敗:', result.reason)
150
+ return
151
+ }
152
+
153
+ const { room } = result
154
+
155
+ room.on('message', (msg) => {
156
+ if (msg.pending) return // 樂觀更新,等待伺服器確認
157
+ if (msg.errorCode) return // 傳送失敗
158
+ console.log(msg.userName, msg.content)
159
+ })
160
+
161
+ // 頁面卸載時釋放資源
162
+ client.dispose()
163
+ ```
164
+
165
+ ### 初始化選項
166
+
167
+ | 選項 | 型別 | 必填 | 說明 |
168
+ |------|------|:----:|------|
169
+ | `token` | `string` | ✓ | 使用者 JWT Token |
170
+ | `onTokenRefresh` | `() => Promise<string>` | | Token 刷新回呼 |
171
+
172
+ ### 方法
173
+
174
+ | 方法 | 說明 |
175
+ |------|------|
176
+ | `join(roomId): Promise<JoinResult>` | 建立連線並加入聊天室,不拋出例外,以 `result.ok` 判斷成功 |
177
+ | `leaveRoom(): Promise<LeaveResult>` | 離開聊天室(WebSocket 連線保留),不拋出例外 |
178
+ | `dispose(): void` | 關閉連線並釋放所有資源 |
179
+
180
+ **`JoinResult`**
181
+
182
+ ```ts
183
+ type JoinResult =
184
+ | { ok: true; room: Room }
185
+ | { ok: false; reason: JoinFailReason }
186
+
187
+ type JoinFailReason =
188
+ | 'skipped' // 被後續操作取代
189
+ | 'timeout' // 等待回應逾時
190
+ | 'disconnected' // 連線中斷且不重連
191
+ | 'disposed' // client 已被 dispose
192
+ | 'room_unavailable' // 加入期間聊天室變為不可用
193
+ | WsErrorCode // 伺服器回傳錯誤(如 ROOM_NOT_FOUND)
194
+ ```
195
+
196
+ **`LeaveResult`**
197
+
198
+ ```ts
199
+ type LeaveResult =
200
+ | { ok: true }
201
+ | { ok: false; reason: LeaveFailReason }
202
+
203
+ type LeaveFailReason =
204
+ | 'skipped' // 被後續操作取代
205
+ | 'disconnected' // 連線中斷且不重連
206
+ | 'disposed' // client 已被 dispose
207
+ | WsErrorCode // 伺服器回傳錯誤
208
+ ```
209
+
210
+ ### 屬性
211
+
212
+ | 屬性 | 型別 | 說明 |
213
+ |------|------|------|
214
+ | `connectionStatus` | `ConnectionStatus` | 目前連線狀態 |
215
+ | `currentUser` | `User \| null` | 目前已連線的使用者資訊 |
216
+ | `currentRoomId` | `string \| null` | 目前所在的聊天室 ID |
217
+ | `isMuted` | `boolean` | 是否被禁言 |
218
+ | `mutedUntil` | `string \| null` | 禁言到期時間(ISO 8601) |
219
+ | `isBanned` | `boolean` | 是否被封禁 |
220
+
221
+ ### 事件
222
+
223
+ 使用 `client.on(event, handler)` 監聽。
224
+
225
+ | 事件 | Payload | 說明 |
226
+ |------|---------|------|
227
+ | `connected` | `{ user }` | WebSocket 連線成功 |
228
+ | `disconnected` | `{ reason, willReconnect }` | 連線斷開 |
229
+ | `reconnecting` | `{ attempt }` | 正在重新連線,`attempt` 為第幾次嘗試 |
230
+ | `reconnected` | — | 重連成功 |
231
+ | `banned` | `{ until }` | 使用者被封禁 |
232
+ | `unbanned` | — | 封禁解除 |
233
+ | `room_unavailable` | `{ roomId }` | 聊天室已不可用,已退回 lobby(WebSocket 連線保留) |
234
+ | `error` | `{ code, message }` | 發生錯誤 |
235
+
236
+ ---
237
+
238
+ ## Room — 聊天室操作
239
+
240
+ `client.join()` 回傳的 `Room` 實例,用於發送訊息與監聽聊天室事件。
241
+
242
+ ### 屬性
243
+
244
+ | 屬性 | 型別 | 說明 |
245
+ |------|------|------|
246
+ | `id` | `string` | 聊天室唯一識別碼 |
247
+ | `name` | `string` | 聊天室顯示名稱 |
248
+ | `messages` | `Message[]` | 目前已載入的訊息列表 |
249
+ | `hasMore` | `boolean` | 是否有更早的歷史訊息可載入 |
250
+ | `cooldownSeconds` | `number` | 發送訊息冷卻秒數(0 表示無限制) |
251
+ | `greetingMessage` | `string \| null` | 加入時的歡迎訊息 |
252
+ | `stickerPacks` | `StickerPack[]` | 此聊天室可用的貼圖包列表 |
253
+ | `isDisposed` | `boolean` | 是否已失效(換房間或斷線後舊的 Room 會被標記) |
254
+
255
+ ### 方法
256
+
257
+ | 方法 | 說明 |
258
+ |------|------|
259
+ | `send(content): Promise<void>` | 發送文字訊息(樂觀更新) |
260
+ | `sendSticker({ packId, stickerId }): Promise<void>` | 發送貼圖(樂觀更新) |
261
+
262
+ ### 事件
263
+
264
+ 使用 `room.on(event, handler)` 監聽。
265
+
266
+ | 事件 | Payload | 說明 |
267
+ |------|---------|------|
268
+ | `message` | `Message` | 收到新訊息(含樂觀更新與失敗訊息) |
269
+ | `presence` | `{ type, user, onlineCount }` | 使用者加入或離開 |
270
+ | `muted` | `{ until }` | 使用者被禁言 |
271
+ | `unmuted` | — | 禁言解除 |
272
+ | `room_updated` | `{ cooldownSeconds }` | 聊天室設定更新 |
273
+
274
+ ---
275
+
276
+ ## 錯誤碼
277
+
278
+ | 錯誤碼 | 說明 |
279
+ |--------|------|
280
+ | `TOKEN_INVALID` | Token 無效或已過期 |
281
+ | `FORBIDDEN` | 無操作權限 |
282
+ | `USER_MUTED` | 使用者已被禁言 |
283
+ | `USER_BANNED` | 使用者已被封禁 |
284
+ | `ROOM_NOT_FOUND` | 房間不存在 |
285
+ | `CHAT_MESSAGE_BLOCKED` | 訊息被過濾攔截 |
286
+ | `INTERNAL_SERVER_ERROR` | 伺服器內部錯誤 |
287
+ | `TOKEN_REFRESH_FAILED` | Token 刷新失敗 |