streemo-video-call-sdk 0.1.2 → 0.2.3
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.md +46 -111
- package/dist/client.d.ts +35 -10
- package/dist/client.js +275 -4
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +475 -15
- package/dist/index.js +2377 -7
- package/dist/index.js.map +1 -1
- package/dist/sdkConfig-C9Fey7R2.d.ts +42 -0
- package/dist/styles.css +198 -0
- package/package.json +4 -3
- package/dist/ChatPanel.d.ts +0 -19
- package/dist/ChatPanel.d.ts.map +0 -1
- package/dist/ChatPanel.js +0 -78
- package/dist/ChatPanel.js.map +0 -1
- package/dist/VideoCallWidget.d.ts +0 -17
- package/dist/VideoCallWidget.d.ts.map +0 -1
- package/dist/VideoCallWidget.js +0 -20
- package/dist/VideoCallWidget.js.map +0 -1
- package/dist/VideoTile.d.ts +0 -15
- package/dist/VideoTile.d.ts.map +0 -1
- package/dist/VideoTile.js +0 -76
- package/dist/VideoTile.js.map +0 -1
- package/dist/auth.d.ts +0 -10
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -11
- package/dist/auth.js.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/sdkConfig.d.ts +0 -9
- package/dist/sdkConfig.d.ts.map +0 -1
- package/dist/sdkConfig.js +0 -17
- package/dist/sdkConfig.js.map +0 -1
- package/dist/sdkErrors.d.ts +0 -8
- package/dist/sdkErrors.d.ts.map +0 -1
- package/dist/sdkErrors.js +0 -32
- package/dist/sdkErrors.js.map +0 -1
- package/dist/signalingClient.d.ts +0 -33
- package/dist/signalingClient.d.ts.map +0 -1
- package/dist/signalingClient.js +0 -95
- package/dist/signalingClient.js.map +0 -1
- package/dist/tokenProvider.d.ts +0 -16
- package/dist/tokenProvider.d.ts.map +0 -1
- package/dist/tokenProvider.js +0 -89
- package/dist/tokenProvider.js.map +0 -1
- package/dist/useWebRTCCall.d.ts +0 -60
- package/dist/useWebRTCCall.d.ts.map +0 -1
- package/dist/useWebRTCCall.js +0 -1055
- package/dist/useWebRTCCall.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,138 +1,73 @@
|
|
|
1
|
-
#
|
|
1
|
+
# streemo-video-call-sdk
|
|
2
2
|
|
|
3
|
-
React
|
|
3
|
+
Production-ready React UI kit and headless hooks for Streemo realtime chat and video calls.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install
|
|
8
|
+
npm install streemo-video-call-sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Quick start (under 5 minutes)
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
import {
|
|
15
|
+
StreemoClient,
|
|
16
|
+
StreemoProvider,
|
|
17
|
+
StreemoTheme,
|
|
18
|
+
Chat,
|
|
19
|
+
ChannelList,
|
|
20
|
+
Channel,
|
|
21
|
+
MessageList,
|
|
22
|
+
MessageInput,
|
|
23
|
+
TypingIndicator,
|
|
24
|
+
} from 'streemo-video-call-sdk'
|
|
25
|
+
import 'streemo-video-call-sdk/styles.css'
|
|
26
|
+
|
|
27
|
+
const client = new StreemoClient({
|
|
28
|
+
apiKey: 'YOUR_APP_ID',
|
|
29
|
+
userToken: 'USER_TOKEN',
|
|
30
|
+
user: { id: 'u-1', name: 'Alice' },
|
|
31
|
+
baseUrl: 'https://api.streemo.ru',
|
|
23
32
|
})
|
|
24
33
|
|
|
25
|
-
export function
|
|
26
|
-
const [enabled, setEnabled] = useState(false)
|
|
27
|
-
|
|
34
|
+
export function App() {
|
|
28
35
|
return (
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
<StreemoProvider client={client}>
|
|
37
|
+
<StreemoTheme theme={{ primary: '#635bff', radius: '10px' }}>
|
|
38
|
+
<Chat>
|
|
39
|
+
<ChannelList />
|
|
40
|
+
<Channel>
|
|
41
|
+
<MessageList />
|
|
42
|
+
<TypingIndicator />
|
|
43
|
+
<MessageInput />
|
|
44
|
+
</Channel>
|
|
45
|
+
</Chat>
|
|
46
|
+
</StreemoTheme>
|
|
47
|
+
</StreemoProvider>
|
|
38
48
|
)
|
|
39
49
|
}
|
|
40
50
|
```
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- `POST /v1/rooms/:roomId/join`
|
|
45
|
-
- `POST /v1/rooms/:roomId/token`
|
|
46
|
-
|
|
47
|
-
Then it opens WS connection to `/v1/ws` and starts signaling.
|
|
48
|
-
|
|
49
|
-
If `apiBaseUrl` is not provided, SDK uses `https://api.streemo.ru` by default.
|
|
50
|
-
If `auth` is not passed into hook/widget/client, SDK uses values from `initVideoSDK(...)`.
|
|
51
|
-
|
|
52
|
-
## React usage (custom tokenProvider)
|
|
52
|
+
## Call UI
|
|
53
53
|
|
|
54
54
|
```tsx
|
|
55
|
-
import {
|
|
56
|
-
|
|
57
|
-
const call = useWebRTCCall({
|
|
58
|
-
roomId,
|
|
59
|
-
userId,
|
|
60
|
-
enabled: true,
|
|
61
|
-
wsBaseUrl: 'wss://your-domain.com',
|
|
62
|
-
tokenProvider: async ({ roomId }) => {
|
|
63
|
-
// custom flow if your backend differs
|
|
64
|
-
const response = await fetch(`/api/my-room-token?roomId=${roomId}`)
|
|
65
|
-
const data = await response.json()
|
|
66
|
-
return data.token
|
|
67
|
-
},
|
|
68
|
-
})
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Non-React usage (signaling-only entrypoint)
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
import { VideoSignalingClient } from '@streemo-video-call-sdk/client'
|
|
55
|
+
import { CallRoom, ParticipantGrid, CallControls } from 'streemo-video-call-sdk'
|
|
75
56
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
},
|
|
85
|
-
onMessage: (msg) => console.log('signal', msg),
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
await client.connect()
|
|
89
|
-
client.send({ type: 'join', payload: { joinedAt: new Date().toISOString() } })
|
|
57
|
+
export function SupportCall() {
|
|
58
|
+
return (
|
|
59
|
+
<CallRoom roomId="support-call">
|
|
60
|
+
<ParticipantGrid />
|
|
61
|
+
<CallControls />
|
|
62
|
+
</CallRoom>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
90
65
|
```
|
|
91
66
|
|
|
92
|
-
##
|
|
93
|
-
|
|
94
|
-
The SDK expects your server to support:
|
|
95
|
-
|
|
96
|
-
- `GET /v1/ws?roomId=<id>&token=<jwt>` WebSocket endpoint
|
|
97
|
-
- `POST /v1/rooms/:roomId/join` endpoint
|
|
98
|
-
- `POST /v1/rooms/:roomId/token` endpoint returning `{ "token": "..." }`
|
|
99
|
-
- Signaling message types:
|
|
100
|
-
- `welcome`
|
|
101
|
-
- `join` / `participant_joined`
|
|
102
|
-
- `participant_left`
|
|
103
|
-
- `offer`
|
|
104
|
-
- `answer`
|
|
105
|
-
- `ice_candidate`
|
|
106
|
-
|
|
107
|
-
By default, SDK sends auth headers:
|
|
108
|
-
|
|
109
|
-
- `X-Client-Id: <clientId>`
|
|
110
|
-
- `X-App-Id: <appId>`
|
|
111
|
-
- `X-Guest-Id: <userId>`
|
|
112
|
-
|
|
113
|
-
`Authorization` header is optional. If `auth.authToken` is provided, SDK also sends `Authorization: Bearer <authToken>`.
|
|
114
|
-
You can override header names in `auth` config (`authHeader`, `clientIdHeader`, `appIdHeader`).
|
|
115
|
-
|
|
116
|
-
## Auth error mapping for UI
|
|
117
|
-
|
|
118
|
-
SDK normalizes server auth errors into typed codes:
|
|
119
|
-
|
|
120
|
-
- `invalid_app`
|
|
121
|
-
- `invalid_client`
|
|
122
|
-
- `expired_token`
|
|
123
|
-
- `unauthorized`
|
|
124
|
-
- `forbidden`
|
|
125
|
-
|
|
126
|
-
Helpers:
|
|
127
|
-
|
|
128
|
-
- `mapSDKErrorToUIMessage(error)` from `@streemo-video-call-sdk`
|
|
129
|
-
- `mapSDKErrorToUIMessage(error)` from `@streemo-video-call-sdk/client`
|
|
130
|
-
|
|
131
|
-
## Publish to npm
|
|
67
|
+
## Build and publish
|
|
132
68
|
|
|
133
69
|
```bash
|
|
134
70
|
npm run clean
|
|
135
|
-
npm install
|
|
136
71
|
npm run build
|
|
137
72
|
npm publish --access public
|
|
138
73
|
```
|
package/dist/client.d.ts
CHANGED
|
@@ -1,10 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { T as TokenProvider, S as ServerAuthConfig } from './sdkConfig-C9Fey7R2.js';
|
|
2
|
+
export { a as SDKError, b as SDKErrorCode, c as ServerTokenProviderOptions, d as TokenProviderContext, V as VideoSDKConfig, e as createServerTokenProvider, f as defaultApiBaseUrl, g as defaultWsBaseUrlFromApi, h as getVideoSDKConfig, i as initVideoSDK, m as mapSDKErrorToUIMessage } from './sdkConfig-C9Fey7R2.js';
|
|
3
|
+
|
|
4
|
+
type SignalMessage = {
|
|
5
|
+
type: string;
|
|
6
|
+
roomId: string;
|
|
7
|
+
userId: string;
|
|
8
|
+
targetUserId?: string;
|
|
9
|
+
payload?: unknown;
|
|
10
|
+
};
|
|
11
|
+
type SignalingClientConfig = {
|
|
12
|
+
roomId: string;
|
|
13
|
+
userId: string;
|
|
14
|
+
roomToken?: string;
|
|
15
|
+
wsBaseUrl?: string;
|
|
16
|
+
tokenProvider?: TokenProvider;
|
|
17
|
+
apiBaseUrl?: string;
|
|
18
|
+
auth?: ServerAuthConfig;
|
|
19
|
+
onOpen?: () => void;
|
|
20
|
+
onClose?: (event: CloseEvent) => void;
|
|
21
|
+
onError?: (event: Event) => void;
|
|
22
|
+
onMessage?: (message: SignalMessage) => void;
|
|
23
|
+
};
|
|
24
|
+
declare class VideoSignalingClient {
|
|
25
|
+
private readonly config;
|
|
26
|
+
private ws;
|
|
27
|
+
private connected;
|
|
28
|
+
constructor(config: SignalingClientConfig);
|
|
29
|
+
isConnected(): boolean;
|
|
30
|
+
connect(): Promise<void>;
|
|
31
|
+
send(message: Omit<SignalMessage, 'roomId' | 'userId'>): void;
|
|
32
|
+
disconnect(): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { ServerAuthConfig, type SignalMessage, type SignalingClientConfig, TokenProvider, VideoSignalingClient };
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,276 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// src/auth.ts
|
|
2
|
+
function buildAuthHeaders(auth) {
|
|
3
|
+
const headers = {
|
|
4
|
+
[auth.appIdHeader ?? "X-App-Id"]: auth.appId,
|
|
5
|
+
[auth.authHeader ?? "Authorization"]: `Bearer ${auth.authToken}`
|
|
6
|
+
};
|
|
7
|
+
if (auth.clientId) {
|
|
8
|
+
headers[auth.clientIdHeader ?? "X-Client-Id"] = auth.clientId;
|
|
9
|
+
}
|
|
10
|
+
return headers;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/sdkErrors.ts
|
|
14
|
+
var SDKError = class extends Error {
|
|
15
|
+
code;
|
|
16
|
+
status;
|
|
17
|
+
constructor(code, message, status) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.status = status;
|
|
21
|
+
this.name = "SDKError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function mapSDKErrorToUIMessage(err) {
|
|
25
|
+
if (!(err instanceof SDKError)) {
|
|
26
|
+
return "Unexpected error while connecting to call server.";
|
|
27
|
+
}
|
|
28
|
+
switch (err.code) {
|
|
29
|
+
case "invalid_app":
|
|
30
|
+
return "Application is not allowed to use this video service.";
|
|
31
|
+
case "invalid_client":
|
|
32
|
+
return "Client is not recognized by video service.";
|
|
33
|
+
case "expired_token":
|
|
34
|
+
return "Authorization token is expired. Please sign in again.";
|
|
35
|
+
case "unauthorized":
|
|
36
|
+
return "Authorization failed. Please check your credentials.";
|
|
37
|
+
case "forbidden":
|
|
38
|
+
return "Access is forbidden for this application/client.";
|
|
39
|
+
case "rate_limited":
|
|
40
|
+
return "Organization rate limit exceeded. Please retry later.";
|
|
41
|
+
case "network_error":
|
|
42
|
+
return "Network error while connecting to video service.";
|
|
43
|
+
default:
|
|
44
|
+
return err.message || "Unknown video service error.";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/tokenProvider.ts
|
|
49
|
+
function mapServerError(status, serverMessage) {
|
|
50
|
+
const message = serverMessage.toLowerCase();
|
|
51
|
+
if (status === 403 && message.includes("app id")) {
|
|
52
|
+
return new SDKError("invalid_app", "invalid_app", status);
|
|
53
|
+
}
|
|
54
|
+
if (status === 403 && message.includes("auth token")) {
|
|
55
|
+
return new SDKError("unauthorized", "invalid_auth_token", status);
|
|
56
|
+
}
|
|
57
|
+
if (status === 403 && message.includes("client id")) {
|
|
58
|
+
return new SDKError("invalid_client", "invalid_client", status);
|
|
59
|
+
}
|
|
60
|
+
if (status === 401 && message.includes("expired token")) {
|
|
61
|
+
return new SDKError("expired_token", "expired_token", status);
|
|
62
|
+
}
|
|
63
|
+
if (status === 401) {
|
|
64
|
+
return new SDKError("unauthorized", "unauthorized", status);
|
|
65
|
+
}
|
|
66
|
+
if (status === 403) {
|
|
67
|
+
return new SDKError("forbidden", "forbidden", status);
|
|
68
|
+
}
|
|
69
|
+
if (status === 429) {
|
|
70
|
+
return new SDKError("rate_limited", "rate_limited", status);
|
|
71
|
+
}
|
|
72
|
+
return new SDKError("unknown_error", serverMessage || "unknown_error", status);
|
|
73
|
+
}
|
|
74
|
+
async function fetchJson(input, init) {
|
|
75
|
+
let response;
|
|
76
|
+
try {
|
|
77
|
+
response = await fetch(input, init);
|
|
78
|
+
} catch {
|
|
79
|
+
throw new SDKError("network_error", "network_error");
|
|
80
|
+
}
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
let serverMessage = response.statusText;
|
|
83
|
+
try {
|
|
84
|
+
const payload = await response.json();
|
|
85
|
+
serverMessage = payload.error || payload.message || serverMessage;
|
|
86
|
+
} catch {
|
|
87
|
+
const text = await response.text();
|
|
88
|
+
if (text) serverMessage = text;
|
|
89
|
+
}
|
|
90
|
+
throw mapServerError(response.status, serverMessage);
|
|
91
|
+
}
|
|
92
|
+
return response.json();
|
|
93
|
+
}
|
|
94
|
+
function defaultWsBaseUrlFromApi(apiBaseUrl) {
|
|
95
|
+
const normalized = apiBaseUrl.replace(/\/+$/, "");
|
|
96
|
+
if (normalized.startsWith("https://")) {
|
|
97
|
+
return normalized.replace(/^https:\/\//, "wss://");
|
|
98
|
+
}
|
|
99
|
+
if (normalized.startsWith("http://")) {
|
|
100
|
+
return normalized.replace(/^http:\/\//, "ws://");
|
|
101
|
+
}
|
|
102
|
+
return normalized;
|
|
103
|
+
}
|
|
104
|
+
function defaultApiBaseUrl() {
|
|
105
|
+
if (typeof window !== "undefined" && window.location?.origin) {
|
|
106
|
+
return window.location.origin;
|
|
107
|
+
}
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
function createServerTokenProvider(options) {
|
|
111
|
+
const externalUserRegisterPath = options.externalUserRegisterPath ?? "/v1/external-users/register";
|
|
112
|
+
const joinPathBuilder = options.joinPathBuilder ?? ((roomId) => `/v1/rooms/${roomId}/join`);
|
|
113
|
+
const tokenPathBuilder = options.tokenPathBuilder ?? ((roomId) => `/v1/rooms/${roomId}/token`);
|
|
114
|
+
const externalUserMap = /* @__PURE__ */ new Map();
|
|
115
|
+
return async ({ roomId, userId }) => {
|
|
116
|
+
const base = (options.apiBaseUrl || defaultApiBaseUrl()).replace(/\/+$/, "");
|
|
117
|
+
if (!base) {
|
|
118
|
+
throw new SDKError("unknown_error", "api_base_url_required");
|
|
119
|
+
}
|
|
120
|
+
const headers = {
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
...buildAuthHeaders(options.auth)
|
|
123
|
+
};
|
|
124
|
+
let internalUserID = externalUserMap.get(userId);
|
|
125
|
+
if (!internalUserID) {
|
|
126
|
+
const registration = await fetchJson(`${base}${externalUserRegisterPath}`, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers,
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
externalId: userId
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
if (!registration.userId) {
|
|
134
|
+
throw new SDKError("unknown_error", "external_user_registration_failed");
|
|
135
|
+
}
|
|
136
|
+
internalUserID = registration.userId;
|
|
137
|
+
externalUserMap.set(userId, internalUserID);
|
|
138
|
+
}
|
|
139
|
+
const roomHeaders = {
|
|
140
|
+
...headers,
|
|
141
|
+
"X-Guest-Id": internalUserID
|
|
142
|
+
};
|
|
143
|
+
await fetchJson(`${base}${joinPathBuilder(roomId)}`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: roomHeaders,
|
|
146
|
+
body: "{}"
|
|
147
|
+
});
|
|
148
|
+
const tokenResponse = await fetchJson(`${base}${tokenPathBuilder(roomId)}`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: roomHeaders
|
|
151
|
+
});
|
|
152
|
+
if (!tokenResponse.token) {
|
|
153
|
+
throw new Error("Room token is empty");
|
|
154
|
+
}
|
|
155
|
+
return tokenResponse.token;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/sdkConfig.ts
|
|
160
|
+
var DEFAULT_API_BASE_URL = "https://api.streemo.ru";
|
|
161
|
+
var DEFAULT_WS_BASE_URL = "wss://api.streemo.ru";
|
|
162
|
+
var globalConfig = {
|
|
163
|
+
apiBaseUrl: DEFAULT_API_BASE_URL,
|
|
164
|
+
wsBaseUrl: DEFAULT_WS_BASE_URL
|
|
165
|
+
};
|
|
166
|
+
function initVideoSDK(config) {
|
|
167
|
+
globalConfig = {
|
|
168
|
+
...globalConfig,
|
|
169
|
+
...config,
|
|
170
|
+
auth: config.auth ? { ...globalConfig.auth ?? {}, ...config.auth } : globalConfig.auth
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function getVideoSDKConfig() {
|
|
174
|
+
return globalConfig;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/signalingClient.ts
|
|
178
|
+
function resolveTokenProvider(config) {
|
|
179
|
+
const sdkConfig = getVideoSDKConfig();
|
|
180
|
+
const apiBaseUrl = config.apiBaseUrl ?? sdkConfig.apiBaseUrl;
|
|
181
|
+
const auth = config.auth ?? sdkConfig.auth;
|
|
182
|
+
if (config.tokenProvider) return config.tokenProvider;
|
|
183
|
+
if (auth) {
|
|
184
|
+
return createServerTokenProvider({
|
|
185
|
+
apiBaseUrl: apiBaseUrl ?? defaultApiBaseUrl(),
|
|
186
|
+
auth
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
async function resolveRoomToken(config) {
|
|
192
|
+
if (config.roomToken) return config.roomToken;
|
|
193
|
+
const provider = resolveTokenProvider(config);
|
|
194
|
+
if (!provider) {
|
|
195
|
+
throw new Error("roomToken or tokenProvider/apiBaseUrl+auth is required");
|
|
196
|
+
}
|
|
197
|
+
const ctx = {
|
|
198
|
+
roomId: config.roomId,
|
|
199
|
+
userId: config.userId
|
|
200
|
+
};
|
|
201
|
+
return provider(ctx);
|
|
202
|
+
}
|
|
203
|
+
function resolveWsBaseUrl(config) {
|
|
204
|
+
const sdkConfig = getVideoSDKConfig();
|
|
205
|
+
if (config.wsBaseUrl) return config.wsBaseUrl.replace(/\/+$/, "");
|
|
206
|
+
if (sdkConfig.wsBaseUrl) return sdkConfig.wsBaseUrl.replace(/\/+$/, "");
|
|
207
|
+
const apiBaseUrl = config.apiBaseUrl ?? sdkConfig.apiBaseUrl ?? defaultApiBaseUrl();
|
|
208
|
+
if (apiBaseUrl) return defaultWsBaseUrlFromApi(apiBaseUrl);
|
|
209
|
+
throw new Error("wsBaseUrl or apiBaseUrl is required");
|
|
210
|
+
}
|
|
211
|
+
var VideoSignalingClient = class {
|
|
212
|
+
config;
|
|
213
|
+
ws = null;
|
|
214
|
+
connected = false;
|
|
215
|
+
constructor(config) {
|
|
216
|
+
this.config = config;
|
|
217
|
+
}
|
|
218
|
+
isConnected() {
|
|
219
|
+
return this.connected;
|
|
220
|
+
}
|
|
221
|
+
async connect() {
|
|
222
|
+
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const roomToken = await resolveRoomToken(this.config);
|
|
226
|
+
const wsBase = resolveWsBaseUrl(this.config);
|
|
227
|
+
const wsURL = `${wsBase}/v1/ws?roomId=${encodeURIComponent(this.config.roomId)}&token=${encodeURIComponent(roomToken)}`;
|
|
228
|
+
await new Promise((resolve, reject) => {
|
|
229
|
+
const ws = new WebSocket(wsURL);
|
|
230
|
+
this.ws = ws;
|
|
231
|
+
ws.onopen = () => {
|
|
232
|
+
this.connected = true;
|
|
233
|
+
this.config.onOpen?.();
|
|
234
|
+
resolve();
|
|
235
|
+
};
|
|
236
|
+
ws.onclose = (event) => {
|
|
237
|
+
this.connected = false;
|
|
238
|
+
this.config.onClose?.(event);
|
|
239
|
+
};
|
|
240
|
+
ws.onerror = (event) => {
|
|
241
|
+
this.config.onError?.(event);
|
|
242
|
+
reject(new Error("WebSocket connection error"));
|
|
243
|
+
};
|
|
244
|
+
ws.onmessage = (event) => {
|
|
245
|
+
const message = JSON.parse(event.data);
|
|
246
|
+
this.config.onMessage?.(message);
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
send(message) {
|
|
251
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
252
|
+
this.ws.send(
|
|
253
|
+
JSON.stringify({
|
|
254
|
+
roomId: this.config.roomId,
|
|
255
|
+
userId: this.config.userId,
|
|
256
|
+
...message
|
|
257
|
+
})
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
disconnect() {
|
|
261
|
+
this.ws?.close();
|
|
262
|
+
this.ws = null;
|
|
263
|
+
this.connected = false;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
export {
|
|
267
|
+
SDKError,
|
|
268
|
+
VideoSignalingClient,
|
|
269
|
+
createServerTokenProvider,
|
|
270
|
+
defaultApiBaseUrl,
|
|
271
|
+
defaultWsBaseUrlFromApi,
|
|
272
|
+
getVideoSDKConfig,
|
|
273
|
+
initVideoSDK,
|
|
274
|
+
mapSDKErrorToUIMessage
|
|
275
|
+
};
|
|
5
276
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAExD,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAGvG,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAE9D,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"sources":["../src/auth.ts","../src/sdkErrors.ts","../src/tokenProvider.ts","../src/sdkConfig.ts","../src/signalingClient.ts"],"sourcesContent":["export type ServerAuthConfig = {\n authToken: string\n clientId?: string\n appId: string\n authHeader?: string\n clientIdHeader?: string\n appIdHeader?: string\n}\n\nexport function buildAuthHeaders(auth: ServerAuthConfig): Record<string, string> {\n const headers: Record<string, string> = {\n [auth.appIdHeader ?? 'X-App-Id']: auth.appId,\n [auth.authHeader ?? 'Authorization']: `Bearer ${auth.authToken}`,\n }\n if (auth.clientId) {\n headers[auth.clientIdHeader ?? 'X-Client-Id'] = auth.clientId\n }\n return headers\n}\n","export type SDKErrorCode =\n | 'invalid_app'\n | 'invalid_client'\n | 'expired_token'\n | 'unauthorized'\n | 'forbidden'\n | 'rate_limited'\n | 'network_error'\n | 'unknown_error'\n\nexport class SDKError extends Error {\n code: SDKErrorCode\n status?: number\n\n constructor(code: SDKErrorCode, message: string, status?: number) {\n super(message)\n this.code = code\n this.status = status\n this.name = 'SDKError'\n }\n}\n\nexport function mapSDKErrorToUIMessage(err: unknown): string {\n if (!(err instanceof SDKError)) {\n return 'Unexpected error while connecting to call server.'\n }\n\n switch (err.code) {\n case 'invalid_app':\n return 'Application is not allowed to use this video service.'\n case 'invalid_client':\n return 'Client is not recognized by video service.'\n case 'expired_token':\n return 'Authorization token is expired. Please sign in again.'\n case 'unauthorized':\n return 'Authorization failed. Please check your credentials.'\n case 'forbidden':\n return 'Access is forbidden for this application/client.'\n case 'rate_limited':\n return 'Organization rate limit exceeded. Please retry later.'\n case 'network_error':\n return 'Network error while connecting to video service.'\n default:\n return err.message || 'Unknown video service error.'\n }\n}\n","import { buildAuthHeaders, type ServerAuthConfig } from './auth'\nimport { SDKError } from './sdkErrors'\n\nexport type TokenProviderContext = {\n roomId: string\n userId: string\n}\n\nexport type TokenProvider = (ctx: TokenProviderContext) => Promise<string>\n\nexport type ServerTokenProviderOptions = {\n apiBaseUrl?: string\n auth: ServerAuthConfig\n externalUserRegisterPath?: string\n joinPathBuilder?: (roomId: string) => string\n tokenPathBuilder?: (roomId: string) => string\n}\n\ntype ServerErrorPayload = {\n error?: string\n message?: string\n}\n\nfunction mapServerError(status: number, serverMessage: string): SDKError {\n const message = serverMessage.toLowerCase()\n if (status === 403 && message.includes('app id')) {\n return new SDKError('invalid_app', 'invalid_app', status)\n }\n if (status === 403 && message.includes('auth token')) {\n return new SDKError('unauthorized', 'invalid_auth_token', status)\n }\n if (status === 403 && message.includes('client id')) {\n return new SDKError('invalid_client', 'invalid_client', status)\n }\n if (status === 401 && message.includes('expired token')) {\n return new SDKError('expired_token', 'expired_token', status)\n }\n if (status === 401) {\n return new SDKError('unauthorized', 'unauthorized', status)\n }\n if (status === 403) {\n return new SDKError('forbidden', 'forbidden', status)\n }\n if (status === 429) {\n return new SDKError('rate_limited', 'rate_limited', status)\n }\n return new SDKError('unknown_error', serverMessage || 'unknown_error', status)\n}\n\nasync function fetchJson<T>(input: RequestInfo | URL, init: RequestInit): Promise<T> {\n let response: Response\n try {\n response = await fetch(input, init)\n } catch {\n throw new SDKError('network_error', 'network_error')\n }\n if (!response.ok) {\n let serverMessage = response.statusText\n try {\n const payload = (await response.json()) as ServerErrorPayload\n serverMessage = payload.error || payload.message || serverMessage\n } catch {\n const text = await response.text()\n if (text) serverMessage = text\n }\n throw mapServerError(response.status, serverMessage)\n }\n return response.json() as Promise<T>\n}\n\nexport function defaultWsBaseUrlFromApi(apiBaseUrl: string): string {\n const normalized = apiBaseUrl.replace(/\\/+$/, '')\n if (normalized.startsWith('https://')) {\n return normalized.replace(/^https:\\/\\//, 'wss://')\n }\n if (normalized.startsWith('http://')) {\n return normalized.replace(/^http:\\/\\//, 'ws://')\n }\n return normalized\n}\n\nexport function defaultApiBaseUrl(): string {\n if (typeof window !== 'undefined' && window.location?.origin) {\n return window.location.origin\n }\n return ''\n}\n\nexport function createServerTokenProvider(options: ServerTokenProviderOptions): TokenProvider {\n const externalUserRegisterPath = options.externalUserRegisterPath ?? '/v1/external-users/register'\n const joinPathBuilder = options.joinPathBuilder ?? ((roomId: string) => `/v1/rooms/${roomId}/join`)\n const tokenPathBuilder = options.tokenPathBuilder ?? ((roomId: string) => `/v1/rooms/${roomId}/token`)\n const externalUserMap = new Map<string, string>()\n\n return async ({ roomId, userId }) => {\n const base = (options.apiBaseUrl || defaultApiBaseUrl()).replace(/\\/+$/, '')\n if (!base) {\n throw new SDKError('unknown_error', 'api_base_url_required')\n }\n const headers = {\n 'Content-Type': 'application/json',\n ...buildAuthHeaders(options.auth),\n }\n\n let internalUserID = externalUserMap.get(userId)\n if (!internalUserID) {\n const registration = await fetchJson<{ userId: string }>(`${base}${externalUserRegisterPath}`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n externalId: userId,\n }),\n })\n if (!registration.userId) {\n throw new SDKError('unknown_error', 'external_user_registration_failed')\n }\n internalUserID = registration.userId\n externalUserMap.set(userId, internalUserID)\n }\n const roomHeaders = {\n ...headers,\n 'X-Guest-Id': internalUserID,\n }\n\n await fetchJson<{ ok?: boolean }>(`${base}${joinPathBuilder(roomId)}`, {\n method: 'POST',\n headers: roomHeaders,\n body: '{}',\n })\n\n const tokenResponse = await fetchJson<{ token: string }>(`${base}${tokenPathBuilder(roomId)}`, {\n method: 'POST',\n headers: roomHeaders,\n })\n\n if (!tokenResponse.token) {\n throw new Error('Room token is empty')\n }\n return tokenResponse.token\n }\n}\n","import type { ServerAuthConfig } from './auth'\n\nexport type VideoSDKConfig = {\n apiBaseUrl?: string\n wsBaseUrl?: string\n auth?: ServerAuthConfig\n}\n\nconst DEFAULT_API_BASE_URL = 'https://api.streemo.ru'\nconst DEFAULT_WS_BASE_URL = 'wss://api.streemo.ru'\n\nlet globalConfig: VideoSDKConfig = {\n apiBaseUrl: DEFAULT_API_BASE_URL,\n wsBaseUrl: DEFAULT_WS_BASE_URL,\n}\n\nexport function initVideoSDK(config: VideoSDKConfig): void {\n globalConfig = {\n ...globalConfig,\n ...config,\n auth: config.auth ? { ...(globalConfig.auth ?? {}), ...config.auth } : globalConfig.auth,\n }\n}\n\nexport function getVideoSDKConfig(): VideoSDKConfig {\n return globalConfig\n}\n\n","import {\n createServerTokenProvider,\n defaultApiBaseUrl,\n defaultWsBaseUrlFromApi,\n type TokenProvider,\n type TokenProviderContext,\n} from './tokenProvider'\nimport type { ServerAuthConfig } from './auth'\nimport { getVideoSDKConfig } from './sdkConfig'\n\nexport type SignalMessage = {\n type: string\n roomId: string\n userId: string\n targetUserId?: string\n payload?: unknown\n}\n\nexport type SignalingClientConfig = {\n roomId: string\n userId: string\n roomToken?: string\n wsBaseUrl?: string\n tokenProvider?: TokenProvider\n apiBaseUrl?: string\n auth?: ServerAuthConfig\n onOpen?: () => void\n onClose?: (event: CloseEvent) => void\n onError?: (event: Event) => void\n onMessage?: (message: SignalMessage) => void\n}\n\nfunction resolveTokenProvider(config: SignalingClientConfig): TokenProvider | null {\n const sdkConfig = getVideoSDKConfig()\n const apiBaseUrl = config.apiBaseUrl ?? sdkConfig.apiBaseUrl\n const auth = config.auth ?? sdkConfig.auth\n if (config.tokenProvider) return config.tokenProvider\n if (auth) {\n return createServerTokenProvider({\n apiBaseUrl: apiBaseUrl ?? defaultApiBaseUrl(),\n auth,\n })\n }\n return null\n}\n\nasync function resolveRoomToken(config: SignalingClientConfig): Promise<string> {\n if (config.roomToken) return config.roomToken\n const provider = resolveTokenProvider(config)\n if (!provider) {\n throw new Error('roomToken or tokenProvider/apiBaseUrl+auth is required')\n }\n const ctx: TokenProviderContext = {\n roomId: config.roomId,\n userId: config.userId,\n }\n return provider(ctx)\n}\n\nfunction resolveWsBaseUrl(config: SignalingClientConfig): string {\n const sdkConfig = getVideoSDKConfig()\n if (config.wsBaseUrl) return config.wsBaseUrl.replace(/\\/+$/, '')\n if (sdkConfig.wsBaseUrl) return sdkConfig.wsBaseUrl.replace(/\\/+$/, '')\n const apiBaseUrl = config.apiBaseUrl ?? sdkConfig.apiBaseUrl ?? defaultApiBaseUrl()\n if (apiBaseUrl) return defaultWsBaseUrlFromApi(apiBaseUrl)\n throw new Error('wsBaseUrl or apiBaseUrl is required')\n}\n\nexport class VideoSignalingClient {\n private readonly config: SignalingClientConfig\n private ws: WebSocket | null = null\n private connected = false\n\n constructor(config: SignalingClientConfig) {\n this.config = config\n }\n\n isConnected(): boolean {\n return this.connected\n }\n\n async connect(): Promise<void> {\n if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {\n return\n }\n\n const roomToken = await resolveRoomToken(this.config)\n const wsBase = resolveWsBaseUrl(this.config)\n const wsURL = `${wsBase}/v1/ws?roomId=${encodeURIComponent(this.config.roomId)}&token=${encodeURIComponent(roomToken)}`\n\n await new Promise<void>((resolve, reject) => {\n const ws = new WebSocket(wsURL)\n this.ws = ws\n\n ws.onopen = () => {\n this.connected = true\n this.config.onOpen?.()\n resolve()\n }\n ws.onclose = (event) => {\n this.connected = false\n this.config.onClose?.(event)\n }\n ws.onerror = (event) => {\n this.config.onError?.(event)\n reject(new Error('WebSocket connection error'))\n }\n ws.onmessage = (event) => {\n const message = JSON.parse(event.data) as SignalMessage\n this.config.onMessage?.(message)\n }\n })\n }\n\n send(message: Omit<SignalMessage, 'roomId' | 'userId'>): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return\n this.ws.send(\n JSON.stringify({\n roomId: this.config.roomId,\n userId: this.config.userId,\n ...message,\n }),\n )\n }\n\n disconnect(): void {\n this.ws?.close()\n this.ws = null\n this.connected = false\n }\n}\n"],"mappings":"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","names":[]}
|