@gravito/radiance 1.0.2 → 1.0.4
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/README.zh-TW.md +238 -3
- package/dist/index.js +29 -9
- package/dist/index.js.map +9 -9
- package/package.json +8 -6
package/README.zh-TW.md
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
# @gravito/radiance
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
輕量級、高效能的 Gravito 廣播系統,支援多種驅動(Pusher、Ably、Redis、WebSocket)。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**狀態**: v0.1.0 - 核心功能完整,支援多種廣播驅動。
|
|
6
|
+
|
|
7
|
+
## ✨ 功能特色
|
|
8
|
+
|
|
9
|
+
- **零運行時開銷**:純類型封裝,直接委派給驅動程式
|
|
10
|
+
- **多驅動支援**:Pusher、Ably、Redis、WebSocket
|
|
11
|
+
- **模組化設計**:僅需安裝您需要的驅動程式
|
|
12
|
+
- **事件整合**:事件可實現 `ShouldBroadcast` 介面自動廣播
|
|
13
|
+
- **通道授權**:完整支援私有(Private)與存在(Presence)通道
|
|
14
|
+
- **AI 友善**:強型別、清晰的 JSDoc 與可預測的 API
|
|
15
|
+
|
|
16
|
+
## 📦 安裝
|
|
6
17
|
|
|
7
18
|
```bash
|
|
8
19
|
bun add @gravito/radiance
|
|
9
20
|
```
|
|
10
21
|
|
|
11
|
-
## 快速開始
|
|
22
|
+
## 🚀 快速開始
|
|
23
|
+
|
|
24
|
+
### 1. 配置 OrbitRadiance
|
|
12
25
|
|
|
13
26
|
```typescript
|
|
14
27
|
import { PlanetCore } from '@gravito/core'
|
|
@@ -24,7 +37,229 @@ const core = await PlanetCore.boot({
|
|
|
24
37
|
secret: 'your-secret',
|
|
25
38
|
cluster: 'mt1',
|
|
26
39
|
},
|
|
40
|
+
// 錯誤處理選項 (v0.1.1+)
|
|
41
|
+
throwOnError: true,
|
|
42
|
+
|
|
43
|
+
// 通道授權回調
|
|
44
|
+
authorizeChannel: async (channel, socketId, userId) => {
|
|
45
|
+
// 在此實作您的授權邏輯
|
|
46
|
+
return true
|
|
47
|
+
},
|
|
27
48
|
}),
|
|
28
49
|
],
|
|
29
50
|
})
|
|
30
51
|
```
|
|
52
|
+
|
|
53
|
+
### 2. 建立可廣播事件
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { Event, ShouldBroadcast } from '@gravito/core'
|
|
57
|
+
import { PrivateChannel } from '@gravito/radiance'
|
|
58
|
+
|
|
59
|
+
class OrderShipped extends Event implements ShouldBroadcast {
|
|
60
|
+
constructor(public order: Order) {
|
|
61
|
+
super()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 定義廣播通道
|
|
65
|
+
broadcastOn(): PrivateChannel {
|
|
66
|
+
return new PrivateChannel(`user.${this.order.userId}`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 定義廣播資料 (可選)
|
|
70
|
+
broadcastWith(): Record<string, unknown> {
|
|
71
|
+
return {
|
|
72
|
+
order_id: this.order.id,
|
|
73
|
+
status: 'shipped',
|
|
74
|
+
tracking_number: this.order.trackingNumber,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 定義事件名稱 (可選)
|
|
79
|
+
broadcastAs(): string {
|
|
80
|
+
return 'OrderShipped'
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3. 發送事件 (自動廣播)
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
await core.events.dispatch(new OrderShipped(order))
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 4. 手動廣播
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const broadcast = c.get('broadcast') as BroadcastManager
|
|
95
|
+
|
|
96
|
+
await broadcast.broadcast(
|
|
97
|
+
event,
|
|
98
|
+
{ name: 'user.123', type: 'private' },
|
|
99
|
+
{ message: 'Hello' },
|
|
100
|
+
'CustomEvent'
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 🔌 驅動程式配置
|
|
105
|
+
|
|
106
|
+
### Pusher
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
OrbitRadiance.configure({
|
|
110
|
+
driver: 'pusher',
|
|
111
|
+
config: {
|
|
112
|
+
appId: 'your-app-id',
|
|
113
|
+
key: 'your-key',
|
|
114
|
+
secret: 'your-secret',
|
|
115
|
+
cluster: 'mt1',
|
|
116
|
+
useTLS: true,
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Ably
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
OrbitRadiance.configure({
|
|
125
|
+
driver: 'ably',
|
|
126
|
+
config: {
|
|
127
|
+
apiKey: 'your-api-key',
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Redis
|
|
133
|
+
|
|
134
|
+
適用於內部微服務通訊。
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { RedisDriver } from '@gravito/radiance'
|
|
138
|
+
|
|
139
|
+
// 若 Core Container 已有 'redis' 實例,會自動注入
|
|
140
|
+
OrbitRadiance.configure({
|
|
141
|
+
driver: 'redis',
|
|
142
|
+
config: {
|
|
143
|
+
keyPrefix: 'gravito:broadcast:',
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### WebSocket
|
|
149
|
+
|
|
150
|
+
適用於單節點開發或自定義 WebSocket 伺服器。
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
OrbitRadiance.configure({
|
|
154
|
+
driver: 'websocket',
|
|
155
|
+
config: {
|
|
156
|
+
// 提供獲取所有連線的方法
|
|
157
|
+
getConnections: () => {
|
|
158
|
+
return Array.from(websocketConnections.values())
|
|
159
|
+
},
|
|
160
|
+
// 可選:自訂 Logger
|
|
161
|
+
logger: core.logger,
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 📡 通道類型
|
|
167
|
+
|
|
168
|
+
### 公開通道 (Public)
|
|
169
|
+
|
|
170
|
+
任何人都可以訂閱。
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { PublicChannel } from '@gravito/radiance'
|
|
174
|
+
|
|
175
|
+
class PublicEvent extends Event implements ShouldBroadcast {
|
|
176
|
+
broadcastOn(): PublicChannel {
|
|
177
|
+
return new PublicChannel('public-channel')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 私有通道 (Private)
|
|
183
|
+
|
|
184
|
+
需要授權才能訂閱。名稱以 `private-` 開頭。
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { PrivateChannel } from '@gravito/radiance'
|
|
188
|
+
|
|
189
|
+
class PrivateEvent extends Event implements ShouldBroadcast {
|
|
190
|
+
broadcastOn(): PrivateChannel {
|
|
191
|
+
return new PrivateChannel(`user.${this.userId}`)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 存在通道 (Presence)
|
|
197
|
+
|
|
198
|
+
需要授權,並包含使用者資訊。名稱以 `presence-` 開頭。
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { PresenceChannel } from '@gravito/radiance'
|
|
202
|
+
|
|
203
|
+
class PresenceEvent extends Event implements ShouldBroadcast {
|
|
204
|
+
broadcastOn(): PresenceChannel {
|
|
205
|
+
return new PresenceChannel('presence-room')
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## 🔐 通道授權
|
|
211
|
+
|
|
212
|
+
私有與存在通道需要授權回調。
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
OrbitRadiance.configure({
|
|
216
|
+
driver: 'pusher',
|
|
217
|
+
config: { /* ... */ },
|
|
218
|
+
authorizeChannel: async (channel, socketId, userId) => {
|
|
219
|
+
if (channel.startsWith('private-user.')) {
|
|
220
|
+
const channelUserId = channel.replace('private-user.', '')
|
|
221
|
+
// 驗證當前使用者 ID 是否匹配
|
|
222
|
+
return userId?.toString() === channelUserId
|
|
223
|
+
}
|
|
224
|
+
return false
|
|
225
|
+
},
|
|
226
|
+
})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## 📚 API 參考
|
|
230
|
+
|
|
231
|
+
### BroadcastManager
|
|
232
|
+
|
|
233
|
+
#### 方法
|
|
234
|
+
|
|
235
|
+
- `broadcast(event, channel, data, eventName): Promise<void>` - 廣播事件
|
|
236
|
+
- `authorizeChannel(channel, socketId, userId): Promise<{ auth, channel_data? } | null>` - 授權通道存取
|
|
237
|
+
- `setDriver(driver: BroadcastDriver): void` - 設定廣播驅動
|
|
238
|
+
- `setAuthCallback(callback): void` - 設定授權回調
|
|
239
|
+
- `setThrowOnError(throwOnError: boolean): void` - 設定是否在錯誤時拋出異常
|
|
240
|
+
|
|
241
|
+
### ShouldBroadcast 介面
|
|
242
|
+
|
|
243
|
+
實作此介面的事件會被自動廣播:
|
|
244
|
+
|
|
245
|
+
- `broadcastOn(): string | Channel` - 廣播通道(必須)
|
|
246
|
+
- `broadcastWith?(): Record<string, unknown>` - 廣播資料(可選)
|
|
247
|
+
- `broadcastAs?(): string` - 事件名稱(可選)
|
|
248
|
+
|
|
249
|
+
## ❓ 疑難排解
|
|
250
|
+
|
|
251
|
+
### 廣播失敗但不拋出錯誤?
|
|
252
|
+
|
|
253
|
+
檢查 `throwOnError` 設定。預設為 `true`,若設為 `false` 則只會記錄錯誤日誌。
|
|
254
|
+
|
|
255
|
+
### WebSocket 驅動沒有送出訊息?
|
|
256
|
+
|
|
257
|
+
確認 `getConnections()` 回傳了正確的連線陣列,且連線狀態為 `OPEN`。WebSocket 驅動會自動忽略非開啟狀態的連線。
|
|
258
|
+
|
|
259
|
+
### Redis 驅動報錯 "Redis client not set"?
|
|
260
|
+
|
|
261
|
+
Redis 驅動需要一個 Redis 客戶端實例。請確保在 `OrbitRadiance.configure` 之前或通過依賴注入提供了 `redis` 實例。
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
MIT © Carl Lee
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,13 @@ class BroadcastManager {
|
|
|
3
3
|
core;
|
|
4
4
|
driver = null;
|
|
5
5
|
authCallback;
|
|
6
|
+
throwOnError = true;
|
|
6
7
|
constructor(core) {
|
|
7
8
|
this.core = core;
|
|
8
9
|
}
|
|
10
|
+
setThrowOnError(throwOnError) {
|
|
11
|
+
this.throwOnError = throwOnError;
|
|
12
|
+
}
|
|
9
13
|
setDriver(driver) {
|
|
10
14
|
this.driver = driver;
|
|
11
15
|
}
|
|
@@ -21,7 +25,9 @@ class BroadcastManager {
|
|
|
21
25
|
await this.driver.broadcast(channel, eventName, data);
|
|
22
26
|
} catch (error) {
|
|
23
27
|
this.core.logger.error(`[BroadcastManager] Failed to broadcast event ${eventName}:`, error);
|
|
24
|
-
|
|
28
|
+
if (this.throwOnError) {
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
25
31
|
}
|
|
26
32
|
}
|
|
27
33
|
async authorizeChannel(channel, socketId, userId) {
|
|
@@ -167,8 +173,10 @@ ${queryString.toString()}`;
|
|
|
167
173
|
const hashArray = Array.from(new Uint8Array(signature));
|
|
168
174
|
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
169
175
|
}
|
|
170
|
-
md5(
|
|
171
|
-
|
|
176
|
+
md5(str) {
|
|
177
|
+
const hasher = new Bun.CryptoHasher("md5");
|
|
178
|
+
hasher.update(str);
|
|
179
|
+
return hasher.digest("hex");
|
|
172
180
|
}
|
|
173
181
|
}
|
|
174
182
|
// src/drivers/RedisDriver.ts
|
|
@@ -217,7 +225,11 @@ class WebSocketDriver {
|
|
|
217
225
|
try {
|
|
218
226
|
connection.send(message);
|
|
219
227
|
} catch (error) {
|
|
220
|
-
|
|
228
|
+
this.config.logger?.warn("WebSocket broadcast failed", {
|
|
229
|
+
channel: channel.name,
|
|
230
|
+
event,
|
|
231
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
232
|
+
});
|
|
221
233
|
}
|
|
222
234
|
}
|
|
223
235
|
}
|
|
@@ -234,6 +246,9 @@ class OrbitRadiance {
|
|
|
234
246
|
}
|
|
235
247
|
async install(core) {
|
|
236
248
|
const manager = new BroadcastManager(core);
|
|
249
|
+
if (this.options.throwOnError !== undefined) {
|
|
250
|
+
manager.setThrowOnError(this.options.throwOnError);
|
|
251
|
+
}
|
|
237
252
|
let driver;
|
|
238
253
|
switch (this.options.driver) {
|
|
239
254
|
case "pusher":
|
|
@@ -244,15 +259,20 @@ class OrbitRadiance {
|
|
|
244
259
|
break;
|
|
245
260
|
case "redis": {
|
|
246
261
|
driver = new RedisDriver(this.options.config);
|
|
247
|
-
const redisClient = core.
|
|
262
|
+
const redisClient = core.container.make("redis");
|
|
248
263
|
if (redisClient) {
|
|
249
264
|
driver.setRedisClient(redisClient);
|
|
250
265
|
}
|
|
251
266
|
break;
|
|
252
267
|
}
|
|
253
|
-
case "websocket":
|
|
254
|
-
|
|
268
|
+
case "websocket": {
|
|
269
|
+
const wsConfig = this.options.config;
|
|
270
|
+
if (!wsConfig.logger) {
|
|
271
|
+
wsConfig.logger = core.logger;
|
|
272
|
+
}
|
|
273
|
+
driver = new WebSocketDriver(wsConfig);
|
|
255
274
|
break;
|
|
275
|
+
}
|
|
256
276
|
default:
|
|
257
277
|
throw new Error(`Unsupported broadcast driver: ${this.options.driver}`);
|
|
258
278
|
}
|
|
@@ -260,7 +280,7 @@ class OrbitRadiance {
|
|
|
260
280
|
if (this.options.authorizeChannel) {
|
|
261
281
|
manager.setAuthCallback(this.options.authorizeChannel);
|
|
262
282
|
}
|
|
263
|
-
core.
|
|
283
|
+
core.container.instance("broadcast", manager);
|
|
264
284
|
if (core.events) {
|
|
265
285
|
core.events.setBroadcastManager({
|
|
266
286
|
broadcast: async (event, channel, data, eventName) => {
|
|
@@ -283,4 +303,4 @@ export {
|
|
|
283
303
|
AblyDriver
|
|
284
304
|
};
|
|
285
305
|
|
|
286
|
-
//# debugId=
|
|
306
|
+
//# debugId=3151C12BCA217F2764756E2164756E21
|
package/dist/index.js.map
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/BroadcastManager.ts", "../src/channels/Channel.ts", "../src/drivers/AblyDriver.ts", "../src/drivers/PusherDriver.ts", "../src/drivers/RedisDriver.ts", "../src/drivers/WebSocketDriver.ts", "../src/OrbitRadiance.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { PlanetCore } from '@gravito/core'\nimport type { BroadcastDriver } from './drivers/BroadcastDriver'\n\n/**\n * Channel authorization callback.\n */\nexport type ChannelAuthorizationCallback = (\n channel: string,\n socketId: string,\n userId?: string | number\n) => Promise<boolean>\n\n/**\n *
|
|
6
|
-
"/**\n * Base channel interface.\n */\nexport interface Channel {\n /**\n *
|
|
7
|
-
"import type { BroadcastDriver } from './BroadcastDriver'\n\n/**\n * Ably driver
|
|
8
|
-
"import type { BroadcastDriver } from './BroadcastDriver'\n\n/**\n * Pusher driver
|
|
9
|
-
"import type { BroadcastDriver } from './BroadcastDriver'\n\n/**\n * Redis driver
|
|
10
|
-
"import type { BroadcastDriver } from './BroadcastDriver'\n\n/**\n * WebSocket connection
|
|
11
|
-
"import type { GravitoOrbit, PlanetCore } from '@gravito/core'\nimport { BroadcastManager } from './BroadcastManager'\nimport type { AblyDriverConfig } from './drivers/AblyDriver'\nimport { AblyDriver } from './drivers/AblyDriver'\nimport type { BroadcastDriver } from './drivers/BroadcastDriver'\nimport type { PusherDriverConfig } from './drivers/PusherDriver'\nimport { PusherDriver } from './drivers/PusherDriver'\nimport type { RedisDriverConfig } from './drivers/RedisDriver'\nimport { RedisDriver } from './drivers/RedisDriver'\nimport type { WebSocketDriverConfig } from './drivers/WebSocketDriver'\nimport { WebSocketDriver } from './drivers/WebSocketDriver'\n\n/**\n *
|
|
5
|
+
"import type { PlanetCore } from '@gravito/core'\nimport type { BroadcastDriver } from './drivers/BroadcastDriver'\n\n/**\n * Channel authorization callback.\n *\n * Used to verify if a user has permission to join a specific broadcast channel.\n *\n * @param channel - The name of the channel to authorize\n * @param socketId - The unique identifier for the client connection\n * @param userId - The identifier of the user requesting access\n * @returns True if the user is authorized to join the channel\n */\nexport type ChannelAuthorizationCallback = (\n channel: string,\n socketId: string,\n userId?: string | number\n) => Promise<boolean>\n\n/**\n * Central manager for real-time event broadcasting.\n *\n * Orchestrates the delivery of events to various broadcast drivers and handles\n * channel authorization logic. It acts as a bridge between Gravito's event system\n * and external real-time services.\n *\n * @example\n * ```typescript\n * const manager = new BroadcastManager(core);\n * manager.setDriver(new PusherDriver(config));\n * await manager.broadcast(null, { name: 'orders', type: 'public' }, { id: 1 }, 'OrderCreated');\n * ```\n */\nexport class BroadcastManager {\n private driver: BroadcastDriver | null = null\n private authCallback?: ChannelAuthorizationCallback\n private throwOnError = true\n\n constructor(private core: PlanetCore) {}\n\n /**\n * Configure error handling behavior for broadcast operations.\n *\n * Determines whether failures in the underlying broadcast driver should\n * propagate as exceptions or be silently logged.\n *\n * @param throwOnError - Enable or disable exception propagation on failure\n *\n * @example\n * ```typescript\n * manager.setThrowOnError(false); // Log errors instead of throwing\n * ```\n */\n setThrowOnError(throwOnError: boolean): void {\n this.throwOnError = throwOnError\n }\n\n /**\n * Assign the active broadcast delivery driver.\n *\n * Sets the driver responsible for the actual transmission of event data\n * to the real-time service (e.g., Pusher, Ably, Redis).\n *\n * @param driver - The broadcast driver implementation to use\n *\n * @example\n * ```typescript\n * manager.setDriver(new RedisDriver(config));\n * ```\n */\n setDriver(driver: BroadcastDriver): void {\n this.driver = driver\n }\n\n /**\n * Register a custom authorization logic for private channels.\n *\n * Provides a hook to implement business-specific permission checks\n * before allowing a client to subscribe to restricted channels.\n *\n * @param callback - The authorization logic implementation\n *\n * @example\n * ```typescript\n * manager.setAuthCallback(async (channel, socketId, userId) => {\n * return userId === 'admin';\n * });\n * ```\n */\n setAuthCallback(callback: ChannelAuthorizationCallback): void {\n this.authCallback = callback\n }\n\n /**\n * Transmit an event payload to a specific channel.\n *\n * Forwards the event data to the configured driver. If no driver is set,\n * the broadcast is skipped with a warning.\n *\n * @param _event - The original event instance (reserved for future use)\n * @param channel - Target channel metadata including name and visibility type\n * @param data - The serializable payload to be transmitted\n * @param eventName - The name of the event as it will appear on the client\n * @throws {Error} If the driver fails and throwOnError is enabled\n *\n * @example\n * ```typescript\n * await manager.broadcast(\n * null,\n * { name: 'private-user.1', type: 'private' },\n * { message: 'Hello' },\n * 'UserMessage'\n * );\n * ```\n */\n async broadcast(\n _event: unknown,\n channel: { name: string; type: string },\n data: Record<string, unknown>,\n eventName: string\n ): Promise<void> {\n if (!this.driver) {\n this.core.logger.warn('[BroadcastManager] No broadcast driver set, skipping broadcast')\n return\n }\n\n try {\n await this.driver.broadcast(channel, eventName, data)\n } catch (error) {\n this.core.logger.error(`[BroadcastManager] Failed to broadcast event ${eventName}:`, error)\n if (this.throwOnError) {\n throw error\n }\n }\n }\n\n /**\n * Validate client access to a specific channel.\n *\n * Executes the registered authorization callback and, if available,\n * delegates to the driver's native authorization mechanism.\n *\n * @param channel - The name of the channel to authorize\n * @param socketId - The unique identifier for the client connection\n * @param userId - The identifier of the user requesting access\n * @returns The authorization payload or null if access is denied\n *\n * @example\n * ```typescript\n * const auth = await manager.authorizeChannel('private-user.1', '123.456', 'user_1');\n * ```\n */\n async authorizeChannel(\n channel: string,\n socketId: string,\n userId?: string | number\n ): Promise<{ auth: string; channel_data?: string } | null> {\n // Check authorization callback first.\n if (this.authCallback) {\n const authorized = await this.authCallback(channel, socketId, userId)\n if (!authorized) {\n return null\n }\n }\n\n // If the driver supports authorization, use it.\n if (this.driver?.authorizeChannel) {\n return await this.driver.authorizeChannel(channel, socketId, userId)\n }\n\n // Default deny for private/presence channels.\n if (channel.startsWith('private-') || channel.startsWith('presence-')) {\n return null\n }\n\n // Public channels do not require authorization.\n return { auth: '' }\n }\n}\n",
|
|
6
|
+
"/**\n * Base channel interface representing a broadcast destination.\n *\n * Channels segregate broadcast traffic. Different channel types imply different\n * access control rules and behaviors.\n */\nexport interface Channel {\n /**\n * The unique identifier for the channel.\n *\n * @remarks\n * For private/presence channels, this typically includes a prefix like 'private-' or 'presence-'.\n */\n name: string\n\n /**\n * The security level and behavior type of the channel.\n */\n type: 'public' | 'private' | 'presence'\n}\n\n/**\n * A public channel open to any subscriber.\n *\n * Public channels require no authorization. Any client can subscribe and listen\n * to events broadcast on these channels.\n *\n * @example\n * ```typescript\n * const channel = new PublicChannel('orders');\n * ```\n */\nexport class PublicChannel implements Channel {\n type = 'public' as const\n\n /**\n * Creates a new PublicChannel instance.\n *\n * @param name - The name of the channel (without prefixes).\n */\n constructor(public name: string) {}\n}\n\n/**\n * A private channel requiring authorization.\n *\n * Private channels are secured and require the client to provide an authentication\n * signature (usually via an auth endpoint) before subscription is allowed.\n * Suitable for sensitive user data.\n *\n * @example\n * ```typescript\n * const channel = new PrivateChannel('user.123');\n * // Driver may prefix this as 'private-user.123'\n * ```\n */\nexport class PrivateChannel implements Channel {\n type = 'private' as const\n\n /**\n * Creates a new PrivateChannel instance.\n *\n * @param name - The name of the channel.\n */\n constructor(public name: string) {}\n}\n\n/**\n * A presence channel that tracks online users.\n *\n * Presence channels extend private channels by adding the ability to know *who*\n * is subscribed. They are commonly used for chat rooms, \"user is typing\" indicators,\n * and online user lists.\n *\n * @example\n * ```typescript\n * const channel = new PresenceChannel('chat.room.1');\n * // Driver may prefix this as 'presence-chat.room.1'\n * ```\n */\nexport class PresenceChannel implements Channel {\n type = 'presence' as const\n\n /**\n * Creates a new PresenceChannel instance.\n *\n * @param name - The name of the channel.\n */\n constructor(public name: string) {}\n}\n",
|
|
7
|
+
"import type { BroadcastDriver } from './BroadcastDriver'\n\n/**\n * Configuration options for the Ably broadcast driver.\n */\nexport interface AblyDriverConfig {\n /**\n * The Ably API key.\n *\n * Format: \"appId.keyId:secret\"\n */\n apiKey: string\n}\n\n/**\n * Ably broadcast driver implementation.\n *\n * Uses the Ably REST API to publish messages to channels.\n * Suitable for high-reliability global messaging without managing infrastructure.\n *\n * @remarks\n * This driver uses standard HTTP fetch for publishing and does not require the full Ably SDK.\n * Presence authorization is implemented using a basic compatible format.\n *\n * @example\n * ```typescript\n * const driver = new AblyDriver({\n * apiKey: 'APP_ID.KEY_ID:SECRET'\n * });\n * ```\n */\nexport class AblyDriver implements BroadcastDriver {\n private baseUrl = 'https://rest.ably.io'\n\n /**\n * Creates a new AblyDriver instance.\n *\n * @param config - The Ably connection configuration.\n */\n constructor(private config: AblyDriverConfig) {}\n\n /**\n * Broadcast an event to an Ably channel.\n *\n * @param channel - The target channel metadata.\n * @param event - The name of the event.\n * @param data - The event payload.\n * @throws {Error} If the Ably API request fails.\n *\n * @example\n * ```typescript\n * await driver.broadcast({ name: 'orders', type: 'public' }, 'OrderCreated', { id: 123 });\n * ```\n */\n async broadcast(\n channel: { name: string; type: string },\n event: string,\n data: Record<string, unknown>\n ): Promise<void> {\n const path = `/channels/${channel.name}/messages`\n const auth = btoa(this.config.apiKey)\n\n const response = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${auth}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n name: event,\n data,\n }),\n })\n\n if (!response.ok) {\n const error = await response.text()\n throw new Error(`Failed to broadcast via Ably: ${error}`)\n }\n }\n\n /**\n * Authorize a client for a private or presence channel.\n *\n * @param channel - The channel name.\n * @param _socketId - The socket ID (unused by Ably REST auth in this simple mode).\n * @param userId - The user ID (used for presence).\n * @returns The authorization object.\n *\n * @example\n * ```typescript\n * const auth = await driver.authorizeChannel('presence-chat', 'socket-1', 'user-1');\n * ```\n */\n async authorizeChannel(\n channel: string,\n _socketId: string,\n userId?: string | number\n ): Promise<{ auth: string; channel_data?: string }> {\n // Ably uses a different authorization mechanism.\n // This is only a basic implementation.\n return {\n auth: this.config.apiKey,\n ...(channel.startsWith('presence-') && userId\n ? {\n channel_data: JSON.stringify({\n clientId: userId.toString(),\n }),\n }\n : {}),\n }\n }\n}\n",
|
|
8
|
+
"import type { BroadcastDriver } from './BroadcastDriver'\n\n/**\n * Configuration for the Pusher broadcast driver.\n */\nexport interface PusherDriverConfig {\n /**\n * The application ID provided by Pusher.\n */\n appId: string\n\n /**\n * The public application key.\n */\n key: string\n\n /**\n * The secret key for signing requests.\n */\n secret: string\n\n /**\n * The Pusher cluster to connect to (e.g., 'mt1', 'eu').\n * @defaultValue 'mt1'\n */\n cluster?: string\n\n /**\n * Whether to force TLS (HTTPS) for API requests.\n * @defaultValue true\n */\n useTLS?: boolean\n}\n\n/**\n * Pusher broadcast driver implementation.\n *\n * Interacts with the Pusher HTTP API to trigger events on channels.\n * It handles request signing, authentication generation for private channels,\n * and HTTP communication using the native Fetch API.\n *\n * @remarks\n * This driver avoids heavy dependencies by implementing the necessary crypto\n * signatures (HMAC-SHA256 and MD5) using standard Web Crypto or Bun APIs.\n *\n * @example\n * ```typescript\n * const driver = new PusherDriver({\n * appId: '123456',\n * key: 'my-app-key',\n * secret: 'my-app-secret',\n * cluster: 'us2'\n * });\n * ```\n */\nexport class PusherDriver implements BroadcastDriver {\n private baseUrl: string\n\n /**\n * Creates a new PusherDriver instance.\n *\n * @param config - The Pusher connection configuration.\n */\n constructor(private config: PusherDriverConfig) {\n const cluster = this.config.cluster || 'mt1'\n this.baseUrl = `https://api-${cluster}.pusher.com`\n }\n\n /**\n * Broadcast an event to a channel via the Pusher API.\n *\n * @param channel - The target channel metadata.\n * @param event - The name of the event.\n * @param data - The event payload.\n * @throws {Error} If the Pusher API returns a non-200 response.\n *\n * @example\n * ```typescript\n * await driver.broadcast({ name: 'news', type: 'public' }, 'BreakingNews', { title: 'Hello' });\n * ```\n */\n async broadcast(\n channel: { name: string; type: string },\n event: string,\n data: Record<string, unknown>\n ): Promise<void> {\n const path = `/apps/${this.config.appId}/events`\n const body = {\n name: event,\n channel: channel.name,\n data: JSON.stringify(data),\n }\n\n const timestamp = Math.floor(Date.now() / 1000)\n const queryString = new URLSearchParams({\n auth_key: this.config.key,\n auth_timestamp: timestamp.toString(),\n auth_version: '1.0',\n body_md5: this.md5(JSON.stringify(body)),\n })\n\n const authString = `POST\\n${path}\\n${queryString.toString()}`\n const authSignature = await this.hmacSHA256(authString, this.config.secret)\n\n queryString.append('auth_signature', authSignature)\n\n const response = await fetch(`${this.baseUrl}${path}?${queryString.toString()}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const error = await response.text()\n throw new Error(`Failed to broadcast via Pusher: ${error}`)\n }\n }\n\n /**\n * Generate an authorization signature for private or presence channels.\n *\n * @param channel - The name of the channel.\n * @param socketId - The client's socket ID.\n * @param userId - The user ID (required for presence channels).\n * @returns The auth object expected by Pusher client libraries.\n *\n * @example\n * ```typescript\n * const auth = await driver.authorizeChannel('private-user.1', '123.456');\n * ```\n */\n async authorizeChannel(\n channel: string,\n socketId: string,\n userId?: string | number\n ): Promise<{ auth: string; channel_data?: string }> {\n const stringToSign = `${socketId}:${channel}`\n const signature = await this.hmacSHA256(stringToSign, this.config.secret)\n\n if (channel.startsWith('presence-')) {\n const channelData = JSON.stringify({\n user_id: userId?.toString(),\n user_info: {},\n })\n return {\n auth: `${this.config.key}:${signature}`,\n channel_data: channelData,\n }\n }\n\n return {\n auth: `${this.config.key}:${signature}`,\n }\n }\n\n private async hmacSHA256(message: string, secret: string): Promise<string> {\n // Uses the Web Crypto API.\n const encoder = new TextEncoder()\n const keyData = encoder.encode(secret)\n const messageData = encoder.encode(message)\n\n const key = await crypto.subtle.importKey(\n 'raw',\n keyData,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n\n const signature = await crypto.subtle.sign('HMAC', key, messageData)\n const hashArray = Array.from(new Uint8Array(signature))\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')\n }\n\n private md5(str: string): string {\n const hasher = new Bun.CryptoHasher('md5')\n hasher.update(str)\n return hasher.digest('hex')\n }\n}\n",
|
|
9
|
+
"import type { BroadcastDriver } from './BroadcastDriver'\n\n/**\n * Configuration options for the Redis broadcast driver.\n */\nexport interface RedisDriverConfig {\n /**\n * The Redis connection URL.\n * @example 'redis://localhost:6379'\n */\n url?: string\n\n /**\n * The Redis host address.\n */\n host?: string\n\n /**\n * The Redis port number.\n */\n port?: number\n\n /**\n * The Redis password for authentication.\n */\n password?: string\n\n /**\n * The Redis database index.\n */\n db?: number\n\n /**\n * The prefix to use for Pub/Sub channels.\n * @defaultValue 'gravito:broadcast:'\n */\n keyPrefix?: string\n}\n\n/**\n * Redis broadcast driver implementation.\n *\n * Leverages Redis Pub/Sub to broadcast messages. This is ideal for internal\n * microservices or when managing your own WebSocket server fleet that subscribes to Redis.\n *\n * @remarks\n * This driver is an adapter and requires an external Redis client instance to be injected.\n * It is compatible with any Redis client that exposes a `publish` method.\n *\n * @example\n * ```typescript\n * const driver = new RedisDriver({ keyPrefix: 'app:' });\n * driver.setRedisClient(redis);\n * ```\n */\nexport class RedisDriver implements BroadcastDriver {\n private redis: {\n publish(channel: string, message: string): Promise<number>\n } | null = null\n\n /**\n * Creates a new RedisDriver instance.\n *\n * @param config - The Redis configuration options.\n */\n constructor(private config: RedisDriverConfig) {\n // The Redis client should be injected from the outside.\n // This class only provides the adapter interface.\n }\n\n /**\n * Inject the Redis client instance.\n *\n * @param client - A compatible Redis client (must have a publish method).\n */\n setRedisClient(client: { publish(channel: string, message: string): Promise<number> }): void {\n this.redis = client\n }\n\n /**\n * Broadcast an event to a Redis channel.\n *\n * @param channel - The target channel metadata.\n * @param event - The name of the event.\n * @param data - The event payload.\n * @throws {Error} If the Redis client has not been set.\n *\n * @example\n * ```typescript\n * await driver.broadcast({ name: 'system', type: 'public' }, 'Alert', { msg: 'Down' });\n * ```\n */\n async broadcast(\n channel: { name: string; type: string },\n event: string,\n data: Record<string, unknown>\n ): Promise<void> {\n if (!this.redis) {\n throw new Error(\n 'Redis client not set. Please install a Redis client and call setRedisClient()'\n )\n }\n\n const prefix = this.config.keyPrefix || 'gravito:broadcast:'\n const channelName = `${prefix}${channel.name}`\n const message = JSON.stringify({\n event,\n data,\n channel: channel.name,\n type: channel.type,\n })\n\n await this.redis.publish(channelName, message)\n }\n}\n",
|
|
10
|
+
"import type { Logger } from '@gravito/core'\nimport type { BroadcastDriver } from './BroadcastDriver'\n\n/**\n * Interface representing a WebSocket connection.\n *\n * Abstracts the native WebSocket or similar connection object to allow\n * different underlying implementations (e.g., standard WebSocket, uWebSockets.js).\n */\nexport interface WebSocketConnection {\n /**\n * Send a message to the client.\n *\n * @param data - The stringified message payload.\n */\n send(data: string): void\n\n /**\n * Close the connection.\n */\n close(): void\n\n /**\n * The current state of the connection (1 = OPEN).\n */\n readyState: number\n}\n\n/**\n * Configuration for the WebSocket broadcast driver.\n */\nexport interface WebSocketDriverConfig {\n /**\n * Retrieve all active WebSocket connections.\n *\n * This function is called on every broadcast to get the list of recipients.\n */\n getConnections(): WebSocketConnection[]\n\n /**\n * Optional strategy to filter connections based on channel subscriptions.\n *\n * @param channel - The name of the channel to filter by.\n */\n filterConnectionsByChannel?(channel: string): WebSocketConnection[]\n\n /**\n * Logger instance for reporting broadcast failures.\n */\n logger?: Logger\n}\n\n/**\n * Native WebSocket broadcast driver.\n *\n * Broadcasts events directly to connected WebSocket clients.\n * Ideal for single-node deployments or simple setups where you want to\n * avoid external dependencies like Pusher or Redis.\n *\n * @remarks\n * This driver iterates over all (or filtered) connections and sends the message.\n * It is not scalable across multiple nodes without an additional sync mechanism (like Redis).\n *\n * @example\n * ```typescript\n * const driver = new WebSocketDriver({\n * getConnections: () => Array.from(wss.clients),\n * logger: console\n * });\n * ```\n */\nexport class WebSocketDriver implements BroadcastDriver {\n /**\n * Creates a new WebSocketDriver instance.\n *\n * @param config - Driver configuration.\n */\n constructor(private config: WebSocketDriverConfig) {}\n\n /**\n * Broadcast an event to WebSocket clients.\n *\n * @param channel - The target channel metadata.\n * @param event - The name of the event.\n * @param data - The event payload.\n *\n * @example\n * ```typescript\n * await driver.broadcast({ name: 'chat', type: 'public' }, 'NewMessage', { text: 'Hi' });\n * ```\n */\n async broadcast(\n channel: { name: string; type: string },\n event: string,\n data: Record<string, unknown>\n ): Promise<void> {\n const message = JSON.stringify({\n channel: channel.name,\n event,\n data,\n })\n\n let connections = this.config.getConnections()\n\n // If channel filtering is supported, use it.\n if (this.config.filterConnectionsByChannel) {\n connections = this.config.filterConnectionsByChannel(channel.name)\n }\n\n // Send to all connections.\n for (const connection of connections) {\n if (connection.readyState === 1) {\n // WebSocket.OPEN\n try {\n connection.send(message)\n } catch (error) {\n this.config.logger?.warn('WebSocket broadcast failed', {\n channel: channel.name,\n event,\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n }\n }\n }\n}\n",
|
|
11
|
+
"import type { GravitoOrbit, PlanetCore } from '@gravito/core'\nimport { BroadcastManager } from './BroadcastManager'\nimport type { AblyDriverConfig } from './drivers/AblyDriver'\nimport { AblyDriver } from './drivers/AblyDriver'\nimport type { BroadcastDriver } from './drivers/BroadcastDriver'\nimport type { PusherDriverConfig } from './drivers/PusherDriver'\nimport { PusherDriver } from './drivers/PusherDriver'\nimport type { RedisDriverConfig } from './drivers/RedisDriver'\nimport { RedisDriver } from './drivers/RedisDriver'\nimport type { WebSocketDriverConfig } from './drivers/WebSocketDriver'\nimport { WebSocketDriver } from './drivers/WebSocketDriver'\n\n/**\n * Configuration options for the Radiance broadcasting orbit.\n *\n * Defines the delivery provider, its specific credentials, and optional\n * hooks for security and error handling.\n *\n * @public\n */\nexport interface OrbitRadianceOptions {\n /**\n * The underlying delivery service provider.\n *\n * - pusher: Industry standard WebSocket service\n * - ably: High-reliability global edge network\n * - redis: Pub/Sub for internal microservices\n * - websocket: Direct server-to-client communication\n */\n driver: 'pusher' | 'ably' | 'redis' | 'websocket'\n\n /**\n * Provider-specific connection and authentication settings.\n */\n config: PusherDriverConfig | AblyDriverConfig | RedisDriverConfig | WebSocketDriverConfig\n\n /**\n * Hook for implementing custom channel access control.\n *\n * @param channel - The name of the channel being accessed\n * @param socketId - The unique client connection identifier\n * @param userId - The authenticated user's identifier\n * @returns True if the subscription request should be granted\n */\n authorizeChannel?: (\n channel: string,\n socketId: string,\n userId?: string | number\n ) => Promise<boolean>\n\n /**\n * Control whether broadcast failures should interrupt the execution flow.\n * @defaultValue true\n */\n throwOnError?: boolean\n}\n\n/**\n * OrbitRadiance provides real-time event broadcasting capabilities.\n *\n * It abstracts various delivery providers (Pusher, Ably, etc.) and integrates\n * seamlessly with Gravito's Event system. When installed, it enables automatic\n * broadcasting of events that implement the `ShouldBroadcast` interface.\n *\n * @example\n * ```typescript\n * const radiance = OrbitRadiance.configure({\n * driver: 'pusher',\n * config: { appId: '...', key: '...', secret: '...' }\n * });\n * core.addOrbit(radiance);\n * ```\n * @public\n */\nexport class OrbitRadiance implements GravitoOrbit {\n private options: OrbitRadianceOptions\n\n constructor(options: OrbitRadianceOptions) {\n this.options = options\n }\n\n /**\n * Create a new OrbitRadiance instance with the specified configuration.\n *\n * This static factory method is the preferred way to initialize the orbit\n * before adding it to the PlanetCore.\n *\n * @param options - The configuration settings for the broadcaster\n * @returns A configured OrbitRadiance instance\n *\n * @example\n * ```typescript\n * const orbit = OrbitRadiance.configure({\n * driver: 'redis',\n * config: { url: 'redis://localhost:6379' }\n * });\n * ```\n */\n static configure(options: OrbitRadianceOptions): OrbitRadiance {\n return new OrbitRadiance(options)\n }\n\n /**\n * Initialize and register the broadcasting system into the core.\n *\n * Sets up the BroadcastManager, initializes the selected driver, and\n * hooks into the EventManager to enable automatic event broadcasting.\n *\n * @param core - The PlanetCore instance where the orbit is being installed\n * @throws {Error} If an unsupported driver is specified in options\n */\n async install(core: PlanetCore): Promise<void> {\n const manager = new BroadcastManager(core)\n\n if (this.options.throwOnError !== undefined) {\n manager.setThrowOnError(this.options.throwOnError)\n }\n\n // Create and set driver.\n let driver: BroadcastDriver\n\n switch (this.options.driver) {\n case 'pusher':\n driver = new PusherDriver(this.options.config as PusherDriverConfig)\n break\n case 'ably':\n driver = new AblyDriver(this.options.config as AblyDriverConfig)\n break\n case 'redis': {\n driver = new RedisDriver(this.options.config as RedisDriverConfig)\n // If a Redis client is provided via core container, set it.\n const redisClient = core.container.make('redis') as\n | { publish(channel: string, message: string): Promise<number> }\n | undefined\n if (redisClient) {\n ;(driver as RedisDriver).setRedisClient(redisClient)\n }\n break\n }\n case 'websocket': {\n const wsConfig = this.options.config as WebSocketDriverConfig\n if (!wsConfig.logger) {\n wsConfig.logger = core.logger\n }\n driver = new WebSocketDriver(wsConfig)\n break\n }\n default:\n throw new Error(`Unsupported broadcast driver: ${this.options.driver}`)\n }\n\n manager.setDriver(driver)\n\n // Set auth callback.\n if (this.options.authorizeChannel) {\n manager.setAuthCallback(this.options.authorizeChannel)\n }\n\n // Register into core container\n core.container.instance('broadcast', manager)\n\n // Integrate with EventManager.\n if (core.events) {\n core.events.setBroadcastManager({\n broadcast: async (event: any, channel: any, data: any, eventName: any) => {\n await manager.broadcast(event, channel, data, eventName)\n },\n })\n }\n\n core.logger.info(`[OrbitRadiance] Installed with ${this.options.driver} driver`)\n }\n}\n\n// Module augmentation for GravitoVariables\ndeclare module '@gravito/core' {\n interface GravitoVariables {\n /** Broadcaster manager for real-time events */\n broadcast?: BroadcastManager\n }\n}\n"
|
|
12
12
|
],
|
|
13
|
-
"mappings": ";
|
|
14
|
-
"debugId": "
|
|
13
|
+
"mappings": "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",
|
|
14
|
+
"debugId": "3151C12BCA217F2764756E2164756E21",
|
|
15
15
|
"names": []
|
|
16
16
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravito/radiance",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -23,10 +23,12 @@
|
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "bun run build.ts",
|
|
26
|
-
"test": "bun test",
|
|
26
|
+
"test": "bun test --timeout=10000",
|
|
27
27
|
"typecheck": "bun tsc -p tsconfig.json --noEmit --skipLibCheck",
|
|
28
|
-
"test:coverage": "bun test --coverage --coverage-
|
|
29
|
-
"test:ci": "bun test --coverage --coverage-
|
|
28
|
+
"test:coverage": "bun test --timeout=10000 --coverage --coverage-reporter=lcov --coverage-dir coverage && bun run --bun scripts/check-coverage.ts",
|
|
29
|
+
"test:ci": "bun test --timeout=10000 --coverage --coverage-reporter=lcov --coverage-dir coverage && bun run --bun scripts/check-coverage.ts",
|
|
30
|
+
"test:unit": "bun test tests/ --timeout=10000",
|
|
31
|
+
"test:integration": "test $(find tests -name '*.integration.test.ts' 2>/dev/null | wc -l) -gt 0 && find tests -name '*.integration.test.ts' -print0 | xargs -0 bun test --timeout=10000 || echo 'No integration tests found'"
|
|
30
32
|
},
|
|
31
33
|
"keywords": [
|
|
32
34
|
"gravito",
|
|
@@ -41,7 +43,7 @@
|
|
|
41
43
|
"author": "Carl Lee <carllee0520@gmail.com>",
|
|
42
44
|
"license": "MIT",
|
|
43
45
|
"dependencies": {
|
|
44
|
-
"@gravito/core": "
|
|
46
|
+
"@gravito/core": "^1.6.1"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"bun-types": "latest",
|
|
@@ -53,4 +55,4 @@
|
|
|
53
55
|
"url": "git+https://github.com/gravito-framework/gravito.git",
|
|
54
56
|
"directory": "packages/radiance"
|
|
55
57
|
}
|
|
56
|
-
}
|
|
58
|
+
}
|