Skip to content

Commit 388b825

Browse files
committed
feat: add create srt credentials method to StreamCall
1 parent 5bfb32f commit 388b825

File tree

4 files changed

+102
-19
lines changed

4 files changed

+102
-19
lines changed

src/ApiClient.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { v4 as uuidv4 } from 'uuid';
2-
import { ApiConfig, RequestMetadata, StreamError } from './types';
2+
import {
3+
ApiConfig,
4+
RequestMetadata,
5+
StreamError,
6+
UserTokenPayload,
7+
} from './types';
8+
import { JWTUserToken } from './utils/create-token';
39
import { APIError } from './gen/models';
410
import { getRateLimitFromResponseHeader } from './utils/rate-limit';
511

@@ -147,4 +153,50 @@ export class ApiClient {
147153

148154
return newParams.join('&');
149155
};
156+
157+
/**
158+
*
159+
* @param payload
160+
* - user_id - the id of the user the token is for
161+
* - validity_in_seconds - how many seconds is the token valid for (starting from issued at), by default it's 1 hour, dicarded if exp is provided
162+
* - exp - when the token expires, unix timestamp in seconds
163+
* - iat - issued at date of the token, unix timestamp in seconds, by default it's now
164+
*/
165+
generateUserToken = (
166+
payload: {
167+
user_id: string;
168+
validity_in_seconds?: number;
169+
exp?: number;
170+
iat?: number;
171+
} & Record<string, unknown>,
172+
) => {
173+
if (!this.apiConfig.secret) {
174+
throw new Error('API secret is not set');
175+
}
176+
177+
const defaultIat = Math.floor((Date.now() - 1000) / 1000);
178+
payload.iat = payload.iat ?? defaultIat;
179+
const validityInSeconds = payload.validity_in_seconds ?? 60 * 60;
180+
payload.exp = payload.exp ?? payload.iat + validityInSeconds;
181+
182+
return JWTUserToken(this.apiConfig.secret, payload as UserTokenPayload);
183+
};
184+
185+
createToken = (
186+
userID: string,
187+
exp = Math.round(Date.now() / 1000) + 60 * 60,
188+
iat = Math.floor((Date.now() - 1000) / 1000),
189+
) => {
190+
if (!this.apiConfig.secret) {
191+
throw new Error('API secret is not set');
192+
}
193+
194+
const payload: UserTokenPayload = {
195+
user_id: userID,
196+
exp,
197+
iat,
198+
};
199+
200+
return JWTUserToken(this.apiConfig.secret, payload);
201+
};
150202
}

src/StreamCall.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
import { GetOrCreateCallRequest, QueryCallMembersRequest } from './gen/models';
1+
import {
2+
CallResponse,
3+
GetOrCreateCallRequest,
4+
QueryCallMembersRequest,
5+
} from './gen/models';
26
import { CallApi } from './gen/video/CallApi';
37
import { OmitTypeId } from './types';
48

59
export class StreamCall extends CallApi {
10+
data?: CallResponse;
11+
612
get cid() {
713
return `${this.type}:${this.id}`;
814
}
@@ -16,4 +22,40 @@ export class StreamCall extends CallApi {
1622
...(request ?? {}),
1723
});
1824
};
25+
26+
getOrCreate = async (request?: GetOrCreateCallRequest) => {
27+
const response = await super.getOrCreate(request);
28+
this.data = response.call;
29+
return response;
30+
};
31+
32+
get = async () => {
33+
const response = await super.get();
34+
this.data = response.call;
35+
return response;
36+
};
37+
38+
createSRTCredetials = (
39+
userID: string,
40+
): {
41+
address: string;
42+
} => {
43+
if (!this.data) {
44+
throw new Error(
45+
'Object is not initialized, call get() or getOrCreate() first',
46+
);
47+
}
48+
49+
const token = this.videoApi.apiClient.createToken(userID, undefined);
50+
const segments = token.split('.');
51+
if (segments.length !== 3) {
52+
throw new Error('Invalid token format');
53+
}
54+
55+
return {
56+
address: this.data.ingress.srt.address
57+
.replace('{passphrase}', segments[2])
58+
.replace('{token}', token),
59+
};
60+
};
1961
}

src/StreamClient.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export class StreamClient extends CommonApi {
5454
baseUrl: chatBaseUrl,
5555
timeout,
5656
agent: config?.agent as RequestInit['dispatcher'],
57+
secret,
5758
});
5859

5960
const videoApiClient = new ApiClient({
@@ -62,6 +63,7 @@ export class StreamClient extends CommonApi {
6263
baseUrl: videoBaseUrl,
6364
timeout,
6465
agent: config?.agent as RequestInit['dispatcher'],
66+
secret,
6567
});
6668

6769
const feedsApiClient = new ApiClient({
@@ -70,6 +72,7 @@ export class StreamClient extends CommonApi {
7072
baseUrl: feedsBaseUrl,
7173
timeout,
7274
agent: config?.agent as RequestInit['dispatcher'],
75+
secret,
7376
});
7477

7578
super(chatApiClient);
@@ -136,14 +139,7 @@ export class StreamClient extends CommonApi {
136139
exp?: number;
137140
iat?: number;
138141
} & Record<string, unknown>,
139-
) => {
140-
const defaultIat = Math.floor((Date.now() - 1000) / 1000);
141-
payload.iat = payload.iat ?? defaultIat;
142-
const validityInSeconds = payload.validity_in_seconds ?? 60 * 60;
143-
payload.exp = payload.exp ?? payload.iat + validityInSeconds;
144-
145-
return JWTUserToken(this.secret, payload as UserTokenPayload);
146-
};
142+
) => this.apiClient.generateUserToken(payload);
147143

148144
/**
149145
*
@@ -179,15 +175,7 @@ export class StreamClient extends CommonApi {
179175
userID: string,
180176
exp = Math.round(Date.now() / 1000) + 60 * 60,
181177
iat = Math.floor((Date.now() - 1000) / 1000),
182-
) => {
183-
const payload: UserTokenPayload = {
184-
user_id: userID,
185-
exp,
186-
iat,
187-
};
188-
189-
return JWTUserToken(this.secret, payload);
190-
};
178+
) => this.apiClient.createToken(userID, exp, iat);
191179

192180
/**
193181
*

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface ApiConfig {
77
/** The timeout for requests in milliseconds. The default is 3000. */
88
timeout: number;
99
agent?: RequestInit['dispatcher'];
10+
secret?: string;
1011
}
1112

1213
export interface RequestMetadata {

0 commit comments

Comments
 (0)