streemo-video-call-sdk 0.2.6 → 0.2.7
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/dist/client.d.ts +2 -2
- package/dist/client.js +161 -0
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +18 -120
- package/dist/index.js +82 -16
- package/dist/index.js.map +1 -1
- package/dist/sdkConfig-DTNZ7Mms.d.ts +165 -0
- package/package.json +2 -2
- package/dist/sdkConfig-C9Fey7R2.d.ts +0 -42
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { T as TokenProvider, S as ServerAuthConfig } from './sdkConfig-
|
|
2
|
-
export { a as SDKError, b as SDKErrorCode, c as ServerTokenProviderOptions, d as TokenProviderContext, V as VideoSDKConfig,
|
|
1
|
+
import { T as TokenProvider, S as ServerAuthConfig } from './sdkConfig-DTNZ7Mms.js';
|
|
2
|
+
export { a as SDKError, b as SDKErrorCode, c as ServerTokenProviderOptions, d as StreemoClient, e as StreemoClientOptions, f as TokenProviderContext, V as VideoSDKConfig, g as createServerTokenProvider, h as createStreemoClient, i as defaultApiBaseUrl, j as defaultWsBaseUrlFromApi, k as getVideoSDKConfig, l as initVideoSDK, m as mapSDKErrorToUIMessage } from './sdkConfig-DTNZ7Mms.js';
|
|
3
3
|
|
|
4
4
|
type SignalMessage = {
|
|
5
5
|
type: string;
|
package/dist/client.js
CHANGED
|
@@ -263,10 +263,171 @@ var VideoSignalingClient = class {
|
|
|
263
263
|
this.connected = false;
|
|
264
264
|
}
|
|
265
265
|
};
|
|
266
|
+
|
|
267
|
+
// src/client/StreemoClient.ts
|
|
268
|
+
var StreemoClient = class {
|
|
269
|
+
apiKey;
|
|
270
|
+
userToken;
|
|
271
|
+
user;
|
|
272
|
+
baseUrl;
|
|
273
|
+
wsUrl;
|
|
274
|
+
reconnect;
|
|
275
|
+
ws = null;
|
|
276
|
+
manuallyClosed = false;
|
|
277
|
+
reconnectAttempt = 0;
|
|
278
|
+
listeners = {};
|
|
279
|
+
constructor(options) {
|
|
280
|
+
this.apiKey = options.apiKey;
|
|
281
|
+
this.userToken = options.userToken;
|
|
282
|
+
this.user = options.user;
|
|
283
|
+
this.baseUrl = (options.baseUrl ?? "https://api.streemo.ru").replace(/\/+$/, "");
|
|
284
|
+
const defaultWs = this.baseUrl.replace(/^http/, "ws");
|
|
285
|
+
this.wsUrl = (options.wsUrl ?? defaultWs).replace(/\/+$/, "");
|
|
286
|
+
this.reconnect = options.reconnect ?? true;
|
|
287
|
+
}
|
|
288
|
+
on(event, listener) {
|
|
289
|
+
if (!this.listeners[event]) this.listeners[event] = /* @__PURE__ */ new Set();
|
|
290
|
+
this.listeners[event]?.add(listener);
|
|
291
|
+
return () => this.off(event, listener);
|
|
292
|
+
}
|
|
293
|
+
off(event, listener) {
|
|
294
|
+
this.listeners[event]?.delete(listener);
|
|
295
|
+
}
|
|
296
|
+
emit(event, payload) {
|
|
297
|
+
this.listeners[event]?.forEach((listener) => {
|
|
298
|
+
listener(payload);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
async connect() {
|
|
302
|
+
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) return;
|
|
303
|
+
this.manuallyClosed = false;
|
|
304
|
+
await this.openSocket();
|
|
305
|
+
}
|
|
306
|
+
disconnect() {
|
|
307
|
+
this.manuallyClosed = true;
|
|
308
|
+
this.ws?.close();
|
|
309
|
+
this.ws = null;
|
|
310
|
+
this.emit("connected", { connected: false });
|
|
311
|
+
}
|
|
312
|
+
async openSocket() {
|
|
313
|
+
await new Promise((resolve, reject) => {
|
|
314
|
+
const url = `${this.wsUrl}/v1/ws?token=${encodeURIComponent(this.userToken)}&userId=${encodeURIComponent(this.user.id)}`;
|
|
315
|
+
const ws = new WebSocket(url);
|
|
316
|
+
this.ws = ws;
|
|
317
|
+
ws.onopen = () => {
|
|
318
|
+
this.reconnectAttempt = 0;
|
|
319
|
+
this.emit("connected", { connected: true });
|
|
320
|
+
resolve();
|
|
321
|
+
};
|
|
322
|
+
ws.onmessage = (event) => {
|
|
323
|
+
const parsed = JSON.parse(event.data);
|
|
324
|
+
switch (parsed.type) {
|
|
325
|
+
case "message_new":
|
|
326
|
+
case "message_updated":
|
|
327
|
+
this.emit(parsed.type, parsed);
|
|
328
|
+
break;
|
|
329
|
+
case "typing":
|
|
330
|
+
this.emit("typing", parsed);
|
|
331
|
+
break;
|
|
332
|
+
case "presence":
|
|
333
|
+
this.emit("presence", parsed);
|
|
334
|
+
break;
|
|
335
|
+
case "reaction_new":
|
|
336
|
+
this.emit("reaction_new", parsed);
|
|
337
|
+
break;
|
|
338
|
+
default:
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
ws.onerror = () => {
|
|
343
|
+
reject(new Error("WebSocket connection failed"));
|
|
344
|
+
};
|
|
345
|
+
ws.onclose = () => {
|
|
346
|
+
this.emit("connected", { connected: false });
|
|
347
|
+
if (!this.manuallyClosed && this.reconnect) {
|
|
348
|
+
const backoffMs = Math.min(1e3 * 2 ** this.reconnectAttempt, 15e3);
|
|
349
|
+
this.reconnectAttempt += 1;
|
|
350
|
+
setTimeout(() => {
|
|
351
|
+
void this.openSocket();
|
|
352
|
+
}, backoffMs);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
buildUrl(path, query) {
|
|
358
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
359
|
+
if (query) {
|
|
360
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
361
|
+
if (value !== void 0) url.searchParams.set(key, String(value));
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return url.toString();
|
|
365
|
+
}
|
|
366
|
+
async request(path, init, query) {
|
|
367
|
+
const response = await fetch(this.buildUrl(path, query), {
|
|
368
|
+
...init,
|
|
369
|
+
headers: {
|
|
370
|
+
Authorization: `Bearer ${this.userToken}`,
|
|
371
|
+
"X-App-Id": this.apiKey,
|
|
372
|
+
"Content-Type": "application/json",
|
|
373
|
+
...init?.headers ?? {}
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
const data = await response.json().catch(() => ({}));
|
|
377
|
+
if (!response.ok) {
|
|
378
|
+
throw new Error(data.error || `HTTP ${response.status}`);
|
|
379
|
+
}
|
|
380
|
+
return data;
|
|
381
|
+
}
|
|
382
|
+
listChannels() {
|
|
383
|
+
return this.request("/v1/channels");
|
|
384
|
+
}
|
|
385
|
+
listMessages(channelId, cursor, limit = 30) {
|
|
386
|
+
return this.request(
|
|
387
|
+
`/v1/channels/${encodeURIComponent(channelId)}/messages`,
|
|
388
|
+
void 0,
|
|
389
|
+
{ cursor, limit }
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
sendMessage(input) {
|
|
393
|
+
return this.request(`/v1/channels/${encodeURIComponent(input.channelId)}/messages`, {
|
|
394
|
+
method: "POST",
|
|
395
|
+
body: JSON.stringify(input)
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
updateMessage(channelId, messageId, text) {
|
|
399
|
+
return this.request(
|
|
400
|
+
`/v1/channels/${encodeURIComponent(channelId)}/messages/${encodeURIComponent(messageId)}`,
|
|
401
|
+
{
|
|
402
|
+
method: "PATCH",
|
|
403
|
+
body: JSON.stringify({ text })
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
deleteMessage(channelId, messageId) {
|
|
408
|
+
return this.request(
|
|
409
|
+
`/v1/channels/${encodeURIComponent(channelId)}/messages/${encodeURIComponent(messageId)}`,
|
|
410
|
+
{ method: "DELETE" }
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
sendTyping(channelId, isTyping) {
|
|
414
|
+
return this.request(`/v1/channels/${encodeURIComponent(channelId)}/typing`, {
|
|
415
|
+
method: "POST",
|
|
416
|
+
body: JSON.stringify({ isTyping })
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// src/client/createStreemoClient.ts
|
|
422
|
+
function createStreemoClient(options) {
|
|
423
|
+
return new StreemoClient(options);
|
|
424
|
+
}
|
|
266
425
|
export {
|
|
267
426
|
SDKError,
|
|
427
|
+
StreemoClient,
|
|
268
428
|
VideoSignalingClient,
|
|
269
429
|
createServerTokenProvider,
|
|
430
|
+
createStreemoClient,
|
|
270
431
|
defaultApiBaseUrl,
|
|
271
432
|
defaultWsBaseUrlFromApi,
|
|
272
433
|
getVideoSDKConfig,
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/auth.ts","../src/sdkErrors.ts","../src/tokenProvider.ts","../src/sdkConfig.ts","../src/signalingClient.ts","../src/client/StreemoClient.ts","../src/client/createStreemoClient.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","import type {\r\n PresenceState,\r\n StreemoAttachment,\r\n StreemoChannel,\r\n StreemoMessage,\r\n StreemoReaction,\r\n StreemoUser,\r\n TypingState,\r\n} from '../types/models'\r\n\r\ntype Listener<T> = (event: T) => void\r\n\r\ntype RealtimeEventMap = {\r\n connected: { connected: boolean }\r\n message_new: StreemoMessage\r\n message_updated: StreemoMessage\r\n typing: TypingState\r\n presence: PresenceState\r\n reaction_new: { channelId: string; messageId: string; reaction: StreemoReaction }\r\n}\r\n\r\ntype QueryParams = Record<string, string | number | undefined>\r\n\r\n/** Internal storage uses a generic listener to avoid DTS inference issues with mapped types */\r\ntype AnyListener = Listener<RealtimeEventMap[keyof RealtimeEventMap]>\r\n\r\nexport type StreemoClientOptions = {\r\n apiKey: string\r\n userToken: string\r\n user: StreemoUser\r\n baseUrl?: string\r\n wsUrl?: string\r\n reconnect?: boolean\r\n}\r\n\r\nexport class StreemoClient {\r\n public readonly apiKey: string\r\n public readonly userToken: string\r\n public readonly user: StreemoUser\r\n public readonly baseUrl: string\r\n public readonly wsUrl: string\r\n private readonly reconnect: boolean\r\n\r\n private ws: WebSocket | null = null\r\n private manuallyClosed = false\r\n private reconnectAttempt = 0\r\n private listeners: Partial<Record<keyof RealtimeEventMap, Set<AnyListener>>> = {}\r\n\r\n constructor(options: StreemoClientOptions) {\r\n this.apiKey = options.apiKey\r\n this.userToken = options.userToken\r\n this.user = options.user\r\n this.baseUrl = (options.baseUrl ?? 'https://api.streemo.ru').replace(/\\/+$/, '')\r\n const defaultWs = this.baseUrl.replace(/^http/, 'ws')\r\n this.wsUrl = (options.wsUrl ?? defaultWs).replace(/\\/+$/, '')\r\n this.reconnect = options.reconnect ?? true\r\n }\r\n\r\n on<K extends keyof RealtimeEventMap>(event: K, listener: Listener<RealtimeEventMap[K]>): () => void {\r\n if (!this.listeners[event]) this.listeners[event] = new Set()\r\n this.listeners[event]?.add(listener as Listener<RealtimeEventMap[keyof RealtimeEventMap]>)\r\n return () => this.off(event, listener)\r\n }\r\n\r\n off<K extends keyof RealtimeEventMap>(event: K, listener: Listener<RealtimeEventMap[K]>): void {\r\n this.listeners[event]?.delete(listener as Listener<RealtimeEventMap[keyof RealtimeEventMap]>)\r\n }\r\n\r\n private emit<K extends keyof RealtimeEventMap>(event: K, payload: RealtimeEventMap[K]): void {\r\n this.listeners[event]?.forEach((listener) => {\r\n listener(payload)\r\n })\r\n }\r\n\r\n async connect(): Promise<void> {\r\n if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) return\r\n this.manuallyClosed = false\r\n await this.openSocket()\r\n }\r\n\r\n disconnect(): void {\r\n this.manuallyClosed = true\r\n this.ws?.close()\r\n this.ws = null\r\n this.emit('connected', { connected: false })\r\n }\r\n\r\n private async openSocket(): Promise<void> {\r\n await new Promise<void>((resolve, reject) => {\r\n const url = `${this.wsUrl}/v1/ws?token=${encodeURIComponent(this.userToken)}&userId=${encodeURIComponent(this.user.id)}`\r\n const ws = new WebSocket(url)\r\n this.ws = ws\r\n\r\n ws.onopen = () => {\r\n this.reconnectAttempt = 0\r\n this.emit('connected', { connected: true })\r\n resolve()\r\n }\r\n\r\n ws.onmessage = (event) => {\r\n const parsed = JSON.parse(event.data) as { type: keyof RealtimeEventMap } & Record<string, unknown>\r\n switch (parsed.type) {\r\n case 'message_new':\r\n case 'message_updated':\r\n this.emit(parsed.type, parsed as unknown as StreemoMessage)\r\n break\r\n case 'typing':\r\n this.emit('typing', parsed as unknown as TypingState)\r\n break\r\n case 'presence':\r\n this.emit('presence', parsed as unknown as PresenceState)\r\n break\r\n case 'reaction_new':\r\n this.emit('reaction_new', parsed as unknown as { channelId: string; messageId: string; reaction: StreemoReaction })\r\n break\r\n default:\r\n break\r\n }\r\n }\r\n\r\n ws.onerror = () => {\r\n reject(new Error('WebSocket connection failed'))\r\n }\r\n\r\n ws.onclose = () => {\r\n this.emit('connected', { connected: false })\r\n if (!this.manuallyClosed && this.reconnect) {\r\n const backoffMs = Math.min(1000 * 2 ** this.reconnectAttempt, 15000)\r\n this.reconnectAttempt += 1\r\n setTimeout(() => {\r\n void this.openSocket()\r\n }, backoffMs)\r\n }\r\n }\r\n })\r\n }\r\n\r\n private buildUrl(path: string, query?: QueryParams): string {\r\n const url = new URL(`${this.baseUrl}${path}`)\r\n if (query) {\r\n Object.entries(query).forEach(([key, value]) => {\r\n if (value !== undefined) url.searchParams.set(key, String(value))\r\n })\r\n }\r\n return url.toString()\r\n }\r\n\r\n private async request<T>(path: string, init?: RequestInit, query?: QueryParams): Promise<T> {\r\n const response = await fetch(this.buildUrl(path, query), {\r\n ...init,\r\n headers: {\r\n Authorization: `Bearer ${this.userToken}`,\r\n 'X-App-Id': this.apiKey,\r\n 'Content-Type': 'application/json',\r\n ...(init?.headers ?? {}),\r\n },\r\n })\r\n const data = await response.json().catch(() => ({}))\r\n if (!response.ok) {\r\n throw new Error((data as { error?: string }).error || `HTTP ${response.status}`)\r\n }\r\n return data as T\r\n }\r\n\r\n listChannels(): Promise<{ items: StreemoChannel[] }> {\r\n return this.request<{ items: StreemoChannel[] }>('/v1/channels')\r\n }\r\n\r\n listMessages(channelId: string, cursor?: string, limit = 30): Promise<{ items: StreemoMessage[]; nextCursor?: string; hasMore?: boolean }> {\r\n return this.request<{ items: StreemoMessage[]; nextCursor?: string; hasMore?: boolean }>(\r\n `/v1/channels/${encodeURIComponent(channelId)}/messages`,\r\n undefined,\r\n { cursor, limit },\r\n )\r\n }\r\n\r\n sendMessage(input: {\r\n channelId: string\r\n text: string\r\n parentId?: string\r\n attachments?: StreemoAttachment[]\r\n }): Promise<StreemoMessage> {\r\n return this.request<StreemoMessage>(`/v1/channels/${encodeURIComponent(input.channelId)}/messages`, {\r\n method: 'POST',\r\n body: JSON.stringify(input),\r\n })\r\n }\r\n\r\n updateMessage(channelId: string, messageId: string, text: string): Promise<StreemoMessage> {\r\n return this.request<StreemoMessage>(\r\n `/v1/channels/${encodeURIComponent(channelId)}/messages/${encodeURIComponent(messageId)}`,\r\n {\r\n method: 'PATCH',\r\n body: JSON.stringify({ text }),\r\n },\r\n )\r\n }\r\n\r\n deleteMessage(channelId: string, messageId: string): Promise<{ ok: true }> {\r\n return this.request<{ ok: true }>(\r\n `/v1/channels/${encodeURIComponent(channelId)}/messages/${encodeURIComponent(messageId)}`,\r\n { method: 'DELETE' },\r\n )\r\n }\r\n\r\n sendTyping(channelId: string, isTyping: boolean): Promise<{ ok: true }> {\r\n return this.request<{ ok: true }>(`/v1/channels/${encodeURIComponent(channelId)}/typing`, {\r\n method: 'POST',\r\n body: JSON.stringify({ isTyping }),\r\n })\r\n }\r\n}\r\n","import { StreemoClient, type StreemoClientOptions } from './StreemoClient'\n\n/**\n * Stable factory for app-level client creation.\n * Keeps construction centralized and allows future non-breaking extensions.\n */\nexport function createStreemoClient(options: StreemoClientOptions): StreemoClient {\n return new StreemoClient(options)\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,126 +1,9 @@
|
|
|
1
|
+
import { d as StreemoClient, n as StreemoChannel, o as TypingState, p as StreemoMessage, q as StreemoAttachment, P as PresenceState, T as TokenProvider, S as ServerAuthConfig } from './sdkConfig-DTNZ7Mms.js';
|
|
2
|
+
export { r as Participant, a as SDKError, b as SDKErrorCode, c as ServerTokenProviderOptions, e as StreemoClientOptions, s as StreemoReaction, t as StreemoUser, f as TokenProviderContext, V as VideoSDKConfig, g as createServerTokenProvider, h as createStreemoClient, i as defaultApiBaseUrl, j as defaultWsBaseUrlFromApi, k as getVideoSDKConfig, l as initVideoSDK, m as mapSDKErrorToUIMessage } from './sdkConfig-DTNZ7Mms.js';
|
|
1
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
4
|
import * as react from 'react';
|
|
3
5
|
import { PropsWithChildren, Component, ReactNode, ErrorInfo } from 'react';
|
|
4
6
|
export { StreemoThemeProvider, Theme, ThemeMode, ThemeTokens, createTheme, useTheme } from 'streemo-ui-kit-web/theme';
|
|
5
|
-
import { T as TokenProvider, S as ServerAuthConfig } from './sdkConfig-C9Fey7R2.js';
|
|
6
|
-
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';
|
|
7
|
-
|
|
8
|
-
type StreemoUser = {
|
|
9
|
-
id: string;
|
|
10
|
-
name: string;
|
|
11
|
-
image?: string;
|
|
12
|
-
};
|
|
13
|
-
type StreemoAttachment = {
|
|
14
|
-
id: string;
|
|
15
|
-
type: 'image' | 'video' | 'file';
|
|
16
|
-
url: string;
|
|
17
|
-
name?: string;
|
|
18
|
-
};
|
|
19
|
-
type StreemoReaction = {
|
|
20
|
-
type: string;
|
|
21
|
-
userId: string;
|
|
22
|
-
};
|
|
23
|
-
type StreemoMessage = {
|
|
24
|
-
id: string;
|
|
25
|
-
channelId: string;
|
|
26
|
-
text: string;
|
|
27
|
-
userId: string;
|
|
28
|
-
createdAt: string;
|
|
29
|
-
updatedAt?: string;
|
|
30
|
-
parentId?: string;
|
|
31
|
-
attachments?: StreemoAttachment[];
|
|
32
|
-
reactions?: StreemoReaction[];
|
|
33
|
-
deliveryStatus?: 'sending' | 'sent' | 'failed';
|
|
34
|
-
};
|
|
35
|
-
type StreemoChannel = {
|
|
36
|
-
id: string;
|
|
37
|
-
name: string;
|
|
38
|
-
image?: string;
|
|
39
|
-
lastMessageAt?: string;
|
|
40
|
-
unreadCount?: number;
|
|
41
|
-
};
|
|
42
|
-
type TypingState = {
|
|
43
|
-
channelId: string;
|
|
44
|
-
userId: string;
|
|
45
|
-
isTyping: boolean;
|
|
46
|
-
updatedAt: string;
|
|
47
|
-
};
|
|
48
|
-
type PresenceState = {
|
|
49
|
-
userId: string;
|
|
50
|
-
status: 'online' | 'offline' | 'away';
|
|
51
|
-
lastSeenAt?: string;
|
|
52
|
-
};
|
|
53
|
-
type Participant = {
|
|
54
|
-
userId: string;
|
|
55
|
-
userName: string;
|
|
56
|
-
stream?: MediaStream;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
type Listener<T> = (event: T) => void;
|
|
60
|
-
type RealtimeEventMap = {
|
|
61
|
-
connected: {
|
|
62
|
-
connected: boolean;
|
|
63
|
-
};
|
|
64
|
-
message_new: StreemoMessage;
|
|
65
|
-
message_updated: StreemoMessage;
|
|
66
|
-
typing: TypingState;
|
|
67
|
-
presence: PresenceState;
|
|
68
|
-
reaction_new: {
|
|
69
|
-
channelId: string;
|
|
70
|
-
messageId: string;
|
|
71
|
-
reaction: StreemoReaction;
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
|
-
type StreemoClientOptions = {
|
|
75
|
-
apiKey: string;
|
|
76
|
-
userToken: string;
|
|
77
|
-
user: StreemoUser;
|
|
78
|
-
baseUrl?: string;
|
|
79
|
-
wsUrl?: string;
|
|
80
|
-
reconnect?: boolean;
|
|
81
|
-
};
|
|
82
|
-
declare class StreemoClient {
|
|
83
|
-
readonly apiKey: string;
|
|
84
|
-
readonly userToken: string;
|
|
85
|
-
readonly user: StreemoUser;
|
|
86
|
-
readonly baseUrl: string;
|
|
87
|
-
readonly wsUrl: string;
|
|
88
|
-
private readonly reconnect;
|
|
89
|
-
private ws;
|
|
90
|
-
private manuallyClosed;
|
|
91
|
-
private reconnectAttempt;
|
|
92
|
-
private listeners;
|
|
93
|
-
constructor(options: StreemoClientOptions);
|
|
94
|
-
on<K extends keyof RealtimeEventMap>(event: K, listener: Listener<RealtimeEventMap[K]>): () => void;
|
|
95
|
-
off<K extends keyof RealtimeEventMap>(event: K, listener: Listener<RealtimeEventMap[K]>): void;
|
|
96
|
-
private emit;
|
|
97
|
-
connect(): Promise<void>;
|
|
98
|
-
disconnect(): void;
|
|
99
|
-
private openSocket;
|
|
100
|
-
private buildUrl;
|
|
101
|
-
private request;
|
|
102
|
-
listChannels(): Promise<{
|
|
103
|
-
items: StreemoChannel[];
|
|
104
|
-
}>;
|
|
105
|
-
listMessages(channelId: string, cursor?: string, limit?: number): Promise<{
|
|
106
|
-
items: StreemoMessage[];
|
|
107
|
-
nextCursor?: string;
|
|
108
|
-
hasMore?: boolean;
|
|
109
|
-
}>;
|
|
110
|
-
sendMessage(input: {
|
|
111
|
-
channelId: string;
|
|
112
|
-
text: string;
|
|
113
|
-
parentId?: string;
|
|
114
|
-
attachments?: StreemoAttachment[];
|
|
115
|
-
}): Promise<StreemoMessage>;
|
|
116
|
-
updateMessage(channelId: string, messageId: string, text: string): Promise<StreemoMessage>;
|
|
117
|
-
deleteMessage(channelId: string, messageId: string): Promise<{
|
|
118
|
-
ok: true;
|
|
119
|
-
}>;
|
|
120
|
-
sendTyping(channelId: string, isTyping: boolean): Promise<{
|
|
121
|
-
ok: true;
|
|
122
|
-
}>;
|
|
123
|
-
}
|
|
124
7
|
|
|
125
8
|
type StreemoContextValue = {
|
|
126
9
|
client: StreemoClient;
|
|
@@ -197,6 +80,7 @@ declare function useTyping(channelId: string): {
|
|
|
197
80
|
declare function usePresence(): {
|
|
198
81
|
presenceMap: Record<string, PresenceState>;
|
|
199
82
|
getUserPresence: (userId: string) => PresenceState;
|
|
83
|
+
findUserPresence: (userId: string) => PresenceState;
|
|
200
84
|
};
|
|
201
85
|
|
|
202
86
|
type UseCallParams = {
|
|
@@ -247,6 +131,20 @@ declare function useParticipants(params: UseCallParams): {
|
|
|
247
131
|
leaveCall: () => void;
|
|
248
132
|
};
|
|
249
133
|
|
|
134
|
+
type StreemoEntitlements = {
|
|
135
|
+
operations: {
|
|
136
|
+
createRoom: boolean;
|
|
137
|
+
chatWrite: boolean;
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
type UseEntitlementsResult = {
|
|
141
|
+
entitlements: StreemoEntitlements | null;
|
|
142
|
+
loading: boolean;
|
|
143
|
+
error: string | null;
|
|
144
|
+
refresh: () => Promise<StreemoEntitlements | null>;
|
|
145
|
+
};
|
|
146
|
+
declare function useEntitlements(enabled?: boolean): UseEntitlementsResult;
|
|
147
|
+
|
|
250
148
|
declare function Chat({ children }: PropsWithChildren): react_jsx_runtime.JSX.Element;
|
|
251
149
|
|
|
252
150
|
declare function ChannelList(): react_jsx_runtime.JSX.Element;
|
|
@@ -477,4 +375,4 @@ type VideoCallWidgetProps = {
|
|
|
477
375
|
};
|
|
478
376
|
declare function VideoCallWidget({ roomId, userId, userName, roomToken, wsBaseUrl, tokenProvider, apiBaseUrl, auth, enabled, localLabelSuffix, showChat, }: VideoCallWidgetProps): react_jsx_runtime.JSX.Element;
|
|
479
377
|
|
|
480
|
-
export { AttachmentPreview, Avatar, CallControls, CallRoom, CameraToggle, Channel, ChannelList, ChannelPreview, Chat, type ChatMessage, ChatPanel, type ChatPanelProps, ErrorBoundary, JoinCallButton, LeaveCallButton, LoadingSpinner, Message, MessageInput, MessageList, MuteButton,
|
|
378
|
+
export { AttachmentPreview, Avatar, CallControls, CallRoom, CameraToggle, Channel, ChannelList, ChannelPreview, Chat, type ChatMessage, ChatPanel, type ChatPanelProps, ErrorBoundary, JoinCallButton, LeaveCallButton, LoadingSpinner, Message, MessageInput, MessageList, MuteButton, ParticipantGrid, ParticipantTile, PresenceBadge, PresenceState, ReactionPicker, type RemoteTrackState, ScreenShareButton, ServerAuthConfig, StreemoAttachment, StreemoChannel, StreemoClient, type StreemoEntitlements, StreemoMessage, StreemoProvider, StreemoTheme, type StreemoThemeTokens, Thread, TokenProvider, TypingIndicator, type TypingParticipant, TypingState, type UseWebRTCCallParams, UserStatus, VideoCallWidget, type VideoCallWidgetProps, VideoTile, useCall, useCallRoomContext, useChannel, useChat, useEntitlements, useMessages, useParticipants, usePresence, useStreemoContext, useTyping, useWebRTCCall };
|
package/dist/index.js
CHANGED
|
@@ -152,6 +152,11 @@ var StreemoClient = class {
|
|
|
152
152
|
}
|
|
153
153
|
};
|
|
154
154
|
|
|
155
|
+
// src/client/createStreemoClient.ts
|
|
156
|
+
function createStreemoClient(options) {
|
|
157
|
+
return new StreemoClient(options);
|
|
158
|
+
}
|
|
159
|
+
|
|
155
160
|
// src/provider/StreemoProvider.tsx
|
|
156
161
|
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
157
162
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -448,7 +453,8 @@ function usePresence() {
|
|
|
448
453
|
return useMemo5(
|
|
449
454
|
() => ({
|
|
450
455
|
presenceMap: presence,
|
|
451
|
-
getUserPresence: (userId) => presence[userId]
|
|
456
|
+
getUserPresence: (userId) => presence[userId] ?? { userId, status: "offline", lastSeenAt: void 0 },
|
|
457
|
+
findUserPresence: (userId) => presence[userId]
|
|
452
458
|
}),
|
|
453
459
|
[presence]
|
|
454
460
|
);
|
|
@@ -1815,6 +1821,64 @@ function useParticipants(params) {
|
|
|
1815
1821
|
);
|
|
1816
1822
|
}
|
|
1817
1823
|
|
|
1824
|
+
// src/hooks/useEntitlements.ts
|
|
1825
|
+
import { useCallback as useCallback3, useEffect as useEffect7, useState as useState8 } from "react";
|
|
1826
|
+
var EMPTY_ENTITLEMENTS = {
|
|
1827
|
+
operations: {
|
|
1828
|
+
createRoom: false,
|
|
1829
|
+
chatWrite: false
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
function useEntitlements(enabled = true) {
|
|
1833
|
+
const { client } = useStreemoContext();
|
|
1834
|
+
const [entitlements, setEntitlements] = useState8(null);
|
|
1835
|
+
const [loading, setLoading] = useState8(false);
|
|
1836
|
+
const [error, setError] = useState8(null);
|
|
1837
|
+
const refresh = useCallback3(async () => {
|
|
1838
|
+
if (!enabled) {
|
|
1839
|
+
setEntitlements(null);
|
|
1840
|
+
setError(null);
|
|
1841
|
+
return null;
|
|
1842
|
+
}
|
|
1843
|
+
setLoading(true);
|
|
1844
|
+
setError(null);
|
|
1845
|
+
try {
|
|
1846
|
+
const sdkConfig = getVideoSDKConfig();
|
|
1847
|
+
const response = await fetch(`${client.baseUrl}/v1/me/entitlements`, {
|
|
1848
|
+
headers: {
|
|
1849
|
+
Authorization: `Bearer ${client.userToken}`,
|
|
1850
|
+
"X-App-Id": client.apiKey,
|
|
1851
|
+
...sdkConfig.auth?.authToken ? { "X-Org-Auth-Token": sdkConfig.auth.authToken } : {},
|
|
1852
|
+
"Content-Type": "application/json"
|
|
1853
|
+
}
|
|
1854
|
+
});
|
|
1855
|
+
const payload = await response.json().catch(() => ({}));
|
|
1856
|
+
if (!response.ok) {
|
|
1857
|
+
throw new Error(payload.error ?? `HTTP ${response.status}`);
|
|
1858
|
+
}
|
|
1859
|
+
const normalized = {
|
|
1860
|
+
operations: {
|
|
1861
|
+
createRoom: Boolean(payload.operations?.createRoom),
|
|
1862
|
+
chatWrite: Boolean(payload.operations?.chatWrite)
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1865
|
+
setEntitlements(normalized);
|
|
1866
|
+
return normalized;
|
|
1867
|
+
} catch (requestError) {
|
|
1868
|
+
const nextError = requestError instanceof Error ? requestError.message : "Failed to fetch entitlements";
|
|
1869
|
+
setError(nextError);
|
|
1870
|
+
setEntitlements(EMPTY_ENTITLEMENTS);
|
|
1871
|
+
return EMPTY_ENTITLEMENTS;
|
|
1872
|
+
} finally {
|
|
1873
|
+
setLoading(false);
|
|
1874
|
+
}
|
|
1875
|
+
}, [client.apiKey, client.baseUrl, client.userToken, enabled]);
|
|
1876
|
+
useEffect7(() => {
|
|
1877
|
+
void refresh();
|
|
1878
|
+
}, [refresh]);
|
|
1879
|
+
return { entitlements, loading, error, refresh };
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1818
1882
|
// src/components/chat/Chat.tsx
|
|
1819
1883
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1820
1884
|
function Chat({ children }) {
|
|
@@ -1868,7 +1932,7 @@ function Channel({ channelId, children }) {
|
|
|
1868
1932
|
}
|
|
1869
1933
|
|
|
1870
1934
|
// src/components/chat/MessageList.tsx
|
|
1871
|
-
import { useMemo as useMemo9, useState as
|
|
1935
|
+
import { useMemo as useMemo9, useState as useState9 } from "react";
|
|
1872
1936
|
|
|
1873
1937
|
// src/components/chat/AttachmentPreview.tsx
|
|
1874
1938
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
@@ -1921,7 +1985,7 @@ function Thread({ parent, messages, onClose }) {
|
|
|
1921
1985
|
import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1922
1986
|
function MessageList({ channelId }) {
|
|
1923
1987
|
const { channelId: resolvedChannelId, messages, loading, hasMore, loadingMore, loadMore } = useChannel(channelId);
|
|
1924
|
-
const [threadParent, setThreadParent] =
|
|
1988
|
+
const [threadParent, setThreadParent] = useState9(null);
|
|
1925
1989
|
const threadMessages = useMemo9(
|
|
1926
1990
|
() => messages.filter((message) => message.parentId === threadParent?.id),
|
|
1927
1991
|
[messages, threadParent?.id]
|
|
@@ -1936,12 +2000,12 @@ function MessageList({ channelId }) {
|
|
|
1936
2000
|
}
|
|
1937
2001
|
|
|
1938
2002
|
// src/components/chat/MessageInput.tsx
|
|
1939
|
-
import { useState as
|
|
2003
|
+
import { useState as useState10 } from "react";
|
|
1940
2004
|
import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1941
2005
|
function MessageInput({ channelId, parentId, placeholder = "Write a message..." }) {
|
|
1942
2006
|
const { sendMessage, sendTyping } = useChannel(channelId);
|
|
1943
|
-
const [value, setValue] =
|
|
1944
|
-
const [error, setError] =
|
|
2007
|
+
const [value, setValue] = useState10("");
|
|
2008
|
+
const [error, setError] = useState10(null);
|
|
1945
2009
|
const submit = async () => {
|
|
1946
2010
|
const text = value.trim();
|
|
1947
2011
|
if (!text) return;
|
|
@@ -2002,12 +2066,12 @@ function useCallRoomContext() {
|
|
|
2002
2066
|
}
|
|
2003
2067
|
|
|
2004
2068
|
// src/VideoTile.tsx
|
|
2005
|
-
import { useEffect as
|
|
2069
|
+
import { useEffect as useEffect8, useMemo as useMemo10, useRef as useRef3, useState as useState11 } from "react";
|
|
2006
2070
|
import { jsx as jsx16, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2007
2071
|
function VideoTile({ stream, muted = false, mirrored = false, label, subtitle, i18n }) {
|
|
2008
2072
|
const ref = useRef3(null);
|
|
2009
|
-
const [hasVideoTrack, setHasVideoTrack] =
|
|
2010
|
-
const [needsInteraction, setNeedsInteraction] =
|
|
2073
|
+
const [hasVideoTrack, setHasVideoTrack] = useState11(false);
|
|
2074
|
+
const [needsInteraction, setNeedsInteraction] = useState11(false);
|
|
2011
2075
|
const texts = {
|
|
2012
2076
|
resumePlayback: i18n?.resumePlayback ?? "Enable video/audio",
|
|
2013
2077
|
audioOnlyMuted: i18n?.audioOnlyMuted ?? "Microphone only",
|
|
@@ -2019,13 +2083,13 @@ function VideoTile({ stream, muted = false, mirrored = false, label, subtitle, i
|
|
|
2019
2083
|
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
2020
2084
|
return `${parts[0][0] ?? ""}${parts[1][0] ?? ""}`.toUpperCase();
|
|
2021
2085
|
}, [label]);
|
|
2022
|
-
|
|
2086
|
+
useEffect8(() => {
|
|
2023
2087
|
if (!ref.current) return;
|
|
2024
2088
|
ref.current.srcObject = stream;
|
|
2025
2089
|
ref.current.muted = muted;
|
|
2026
2090
|
void ref.current.play().then(() => setNeedsInteraction(false)).catch(() => setNeedsInteraction(true));
|
|
2027
2091
|
}, [stream, muted]);
|
|
2028
|
-
|
|
2092
|
+
useEffect8(() => {
|
|
2029
2093
|
if (!stream) {
|
|
2030
2094
|
setHasVideoTrack(false);
|
|
2031
2095
|
return;
|
|
@@ -2197,7 +2261,7 @@ var ErrorBoundary = class extends Component {
|
|
|
2197
2261
|
};
|
|
2198
2262
|
|
|
2199
2263
|
// src/ChatPanel.tsx
|
|
2200
|
-
import { useEffect as
|
|
2264
|
+
import { useEffect as useEffect9, useMemo as useMemo11, useRef as useRef4, useState as useState12 } from "react";
|
|
2201
2265
|
import { jsx as jsx29, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2202
2266
|
function ChatPanel({
|
|
2203
2267
|
messages,
|
|
@@ -2215,9 +2279,9 @@ function ChatPanel({
|
|
|
2215
2279
|
onFocusChange,
|
|
2216
2280
|
placeholder = "Write a message..."
|
|
2217
2281
|
}) {
|
|
2218
|
-
const [draft, setDraft] =
|
|
2219
|
-
const [editingId, setEditingId] =
|
|
2220
|
-
const [editingText, setEditingText] =
|
|
2282
|
+
const [draft, setDraft] = useState12("");
|
|
2283
|
+
const [editingId, setEditingId] = useState12(null);
|
|
2284
|
+
const [editingText, setEditingText] = useState12("");
|
|
2221
2285
|
const listRef = useRef4(null);
|
|
2222
2286
|
const typingTimeoutRef = useRef4(null);
|
|
2223
2287
|
const typingLine = useMemo11(() => {
|
|
@@ -2227,7 +2291,7 @@ function ChatPanel({
|
|
|
2227
2291
|
if (names.length === 2) return `${names[0]} and ${names[1]} are typing...`;
|
|
2228
2292
|
return `${names[0]} and ${names.length - 1} others are typing...`;
|
|
2229
2293
|
}, [typingParticipants]);
|
|
2230
|
-
|
|
2294
|
+
useEffect9(() => {
|
|
2231
2295
|
if (!listRef.current) return;
|
|
2232
2296
|
const el = listRef.current;
|
|
2233
2297
|
const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 60;
|
|
@@ -2464,6 +2528,7 @@ export {
|
|
|
2464
2528
|
VideoCallWidget,
|
|
2465
2529
|
VideoTile,
|
|
2466
2530
|
createServerTokenProvider,
|
|
2531
|
+
createStreemoClient,
|
|
2467
2532
|
createTheme2 as createTheme,
|
|
2468
2533
|
defaultApiBaseUrl,
|
|
2469
2534
|
defaultWsBaseUrlFromApi,
|
|
@@ -2474,6 +2539,7 @@ export {
|
|
|
2474
2539
|
useCallRoomContext,
|
|
2475
2540
|
useChannel,
|
|
2476
2541
|
useChat,
|
|
2542
|
+
useEntitlements,
|
|
2477
2543
|
useMessages,
|
|
2478
2544
|
useParticipants,
|
|
2479
2545
|
usePresence,
|