@stream-io/video-client 0.6.10 → 0.7.1

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/index.browser.es.js +60 -157
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +59 -164
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.d.ts +0 -1
  7. package/dist/index.es.js +60 -162
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/src/Call.d.ts +13 -1
  10. package/dist/src/coordinator/connection/client.d.ts +0 -11
  11. package/dist/src/coordinator/connection/signing.d.ts +1 -24
  12. package/dist/src/coordinator/connection/token_manager.d.ts +2 -3
  13. package/dist/src/coordinator/connection/types.d.ts +4 -4
  14. package/dist/src/gen/coordinator/index.d.ts +382 -1018
  15. package/dist/src/stats/utils.d.ts +11 -0
  16. package/dist/src/store/CallState.d.ts +2 -2
  17. package/index.ts +0 -1
  18. package/package.json +2 -3
  19. package/src/Call.ts +53 -0
  20. package/src/__tests__/StreamVideoClient.test.ts +8 -3
  21. package/src/coordinator/connection/client.ts +1 -40
  22. package/src/coordinator/connection/signing.ts +10 -89
  23. package/src/coordinator/connection/token_manager.ts +3 -17
  24. package/src/coordinator/connection/types.ts +4 -4
  25. package/src/gen/coordinator/index.ts +361 -983
  26. package/src/stats/utils.ts +23 -0
  27. package/src/store/CallState.ts +5 -4
  28. package/dist/src/StreamVideoServerClient.d.ts +0 -33
  29. package/src/StreamVideoServerClient.ts +0 -106
  30. package/src/__tests__/server-side/call-members.test.ts +0 -93
  31. package/src/__tests__/server-side/call-types.test.ts +0 -127
  32. package/src/__tests__/server-side/call.test.ts +0 -158
  33. package/src/__tests__/server-side/create-token.test.ts +0 -44
  34. package/src/__tests__/server-side/server-side-user.test.ts +0 -28
@@ -1,6 +1,17 @@
1
+ import { LocalClientDetailsType } from '../client-details';
1
2
  /**
2
3
  * Flatten the stats report into an array of stats objects.
3
4
  *
4
5
  * @param report the report to flatten.
5
6
  */
6
7
  export declare const flatten: (report: RTCStatsReport) => RTCStats[];
8
+ export declare const getSdkSignature: (clientDetails: LocalClientDetailsType) => {
9
+ os?: import("../gen/video/sfu/models/models").OS | undefined;
10
+ browser?: import("../gen/video/sfu/models/models").Browser | undefined;
11
+ device?: import("../gen/video/sfu/models/models").Device | undefined;
12
+ webRTCInfo?: {
13
+ version: string;
14
+ } | undefined;
15
+ sdkName: string;
16
+ sdkVersion: string;
17
+ };
@@ -2,7 +2,7 @@ import { Observable } from 'rxjs';
2
2
  import type { Patch } from './rxUtils';
3
3
  import { StreamVideoParticipant, StreamVideoParticipantPatch, StreamVideoParticipantPatches } from '../types';
4
4
  import { CallStatsReport } from '../stats';
5
- import { CallIngressResponse, CallResponse, CallSessionResponse, CallSettingsResponse, EgressResponse, MemberResponse, OwnCapability, ThumbnailResponse, UserResponse, VideoEvent } from '../gen/coordinator';
5
+ import { CallIngressResponse, CallResponse, CallSessionResponse, CallSettingsResponse, EgressResponse, MemberResponse, OwnCapability, ThumbnailResponse, UserResponse, WSEvent } from '../gen/coordinator';
6
6
  import { Pin } from '../gen/video/sfu/models/models';
7
7
  import { Comparator } from '../sorting';
8
8
  import { Logger } from '../coordinator/connection/types';
@@ -476,7 +476,7 @@ export declare class CallState {
476
476
  *
477
477
  * @param event the video event that our backend sent us.
478
478
  */
479
- updateFromEvent: (event: VideoEvent) => void;
479
+ updateFromEvent: (event: WSEvent) => void;
480
480
  /**
481
481
  * Updates the participant pinned state with server side pinning data.
482
482
  *
package/index.ts CHANGED
@@ -13,7 +13,6 @@ export * from './src/stats/types';
13
13
  export * from './src/Call';
14
14
  export * from './src/CallType';
15
15
  export * from './src/StreamVideoClient';
16
- export * from './src/StreamVideoServerClient';
17
16
  export * from './src/StreamSfuClient';
18
17
  export * from './src/devices';
19
18
  export * from './src/store';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "0.6.10",
3
+ "version": "0.7.1",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
@@ -35,7 +35,6 @@
35
35
  "axios": "^1.6.0",
36
36
  "base64-js": "^1.5.1",
37
37
  "isomorphic-ws": "^5.0.0",
38
- "jsonwebtoken": "^9.0.2",
39
38
  "rxjs": "~7.8.1",
40
39
  "sdp-transform": "^2.14.1",
41
40
  "ua-parser-js": "^1.0.36",
@@ -46,7 +45,7 @@
46
45
  "@openapitools/openapi-generator-cli": "^2.7.0",
47
46
  "@rollup/plugin-replace": "^5.0.5",
48
47
  "@rollup/plugin-typescript": "^11.1.6",
49
- "@types/jsonwebtoken": "^9.0.3",
48
+ "@stream-io/node-sdk": "^0.1.12",
50
49
  "@types/sdp-transform": "^2.4.7",
51
50
  "@types/ua-parser-js": "^0.7.37",
52
51
  "@vitest/coverage-v8": "^0.34.4",
package/src/Call.ts CHANGED
@@ -22,6 +22,8 @@ import {
22
22
  AcceptCallResponse,
23
23
  BlockUserRequest,
24
24
  BlockUserResponse,
25
+ CollectUserFeedbackRequest,
26
+ CollectUserFeedbackResponse,
25
27
  EndCallResponse,
26
28
  GetCallResponse,
27
29
  GetCallStatsResponse,
@@ -123,6 +125,7 @@ import {
123
125
  ScreenShareManager,
124
126
  SpeakerManager,
125
127
  } from './devices';
128
+ import { getSdkSignature } from './stats/utils';
126
129
 
127
130
  /**
128
131
  * An object representation of a `Call`.
@@ -1909,6 +1912,56 @@ export class Call {
1909
1912
  return this.streamClient.get<GetCallStatsResponse>(endpoint);
1910
1913
  };
1911
1914
 
1915
+ /**
1916
+ * Submit user feedback for the call
1917
+ *
1918
+ * @param rating Rating between 1 and 5 denoting the experience of the user in the call
1919
+ * @param reason The reason/description for the rating
1920
+ * @param custom Custom data
1921
+ * @returns
1922
+ */
1923
+ submitFeedback = async (
1924
+ rating: number,
1925
+ {
1926
+ reason,
1927
+ custom,
1928
+ }: {
1929
+ reason?: string;
1930
+ custom?: Record<string, any>;
1931
+ } = {},
1932
+ ) => {
1933
+ if (rating < 1 || rating > 5) {
1934
+ throw new Error('Rating must be between 1 and 5');
1935
+ }
1936
+ const userSessionId = this.sfuClient?.sessionId;
1937
+ const callSessionId = this.state.session?.id;
1938
+ if (!callSessionId || !userSessionId) {
1939
+ throw new Error(
1940
+ 'Feedback can be submitted only in the context of a call session',
1941
+ );
1942
+ }
1943
+
1944
+ const { sdkName, sdkVersion, ...platform } = getSdkSignature(
1945
+ getClientDetails(),
1946
+ );
1947
+
1948
+ const endpoint = `${this.streamClientBasePath}/feedback/${callSessionId}`;
1949
+ return this.streamClient.post<
1950
+ CollectUserFeedbackResponse,
1951
+ CollectUserFeedbackRequest
1952
+ >(endpoint, {
1953
+ rating,
1954
+ reason,
1955
+ user_session_id: userSessionId,
1956
+ sdk: sdkName,
1957
+ sdk_version: sdkVersion,
1958
+ custom: {
1959
+ ...custom,
1960
+ 'x-stream-platform-data': platform,
1961
+ },
1962
+ });
1963
+ };
1964
+
1912
1965
  /**
1913
1966
  * Sends a custom event to all call participants.
1914
1967
  *
@@ -2,18 +2,23 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  import { StreamVideoClient } from '../StreamVideoClient';
3
3
  import 'dotenv/config';
4
4
  import { generateUUIDv4 } from '../coordinator/connection/utils';
5
- import { StreamVideoServerClient } from '../StreamVideoServerClient';
6
5
  import { User } from '../coordinator/connection/types';
6
+ import { StreamClient } from '@stream-io/node-sdk';
7
7
 
8
8
  const apiKey = process.env.STREAM_API_KEY!;
9
9
  const secret = process.env.STREAM_SECRET!;
10
10
 
11
+ const serverClient = new StreamClient(apiKey, secret);
12
+
11
13
  const tokenProvider = (userId: string) => {
12
- const serverClient = new StreamVideoServerClient(apiKey, { secret });
13
14
  return async () => {
14
15
  return new Promise<string>((resolve) => {
15
16
  setTimeout(() => {
16
- const token = serverClient.createToken(userId);
17
+ const token = serverClient.createToken(
18
+ userId,
19
+ undefined,
20
+ Math.round(Date.now() / 1000 - 10),
21
+ );
17
22
  resolve(token);
18
23
  }, 100);
19
24
  });
@@ -7,7 +7,7 @@ import axios, {
7
7
  } from 'axios';
8
8
  import https from 'https';
9
9
  import { StableWSConnection } from './connection';
10
- import { DevToken, JWTUserToken } from './signing';
10
+ import { DevToken } from './signing';
11
11
  import { TokenManager } from './token_manager';
12
12
  import { WSConnectionFallback } from './connection_fallback';
13
13
  import { isErrorResponse, isWSFailure } from './errors';
@@ -416,7 +416,6 @@ export class StreamClient {
416
416
  {
417
417
  user: {
418
418
  ...user,
419
- role: 'guest',
420
419
  },
421
420
  },
422
421
  { publicEndpoint: true },
@@ -862,42 +861,4 @@ export class StreamClient {
862
861
  createAbortControllerForNextRequest = () => {
863
862
  return (this.nextRequestAbortController = new AbortController());
864
863
  };
865
-
866
- /**
867
- * createToken - Creates a token to authenticate this user. This function is used server side.
868
- * The resulting token should be passed to the client side when the users registers or logs in.
869
- *
870
- * @param {string} userID The UserWithId ID
871
- * @param {number} [exp] The expiration time for the token expressed in the number of seconds since the epoch
872
- * @param call_cids for anonymous tokens you have to provide the call cids the use can join
873
- *
874
- * @return {string} Returns a token
875
- */
876
- createToken = (
877
- userID: string,
878
- exp?: number,
879
- iat?: number,
880
- call_cids?: string[],
881
- ) => {
882
- if (this.secret == null) {
883
- throw Error(
884
- `tokens can only be created server-side using the API Secret`,
885
- );
886
- }
887
- const extra: { exp?: number; iat?: number; call_cids?: string[] } = {};
888
-
889
- if (exp) {
890
- extra.exp = exp;
891
- }
892
-
893
- if (iat) {
894
- extra.iat = iat;
895
- }
896
-
897
- if (call_cids) {
898
- extra.call_cids = call_cids;
899
- }
900
-
901
- return JWTUserToken(this.secret, userID, extra, {});
902
- };
903
864
  }
@@ -1,77 +1,4 @@
1
- import jwt, { Secret, SignOptions } from 'jsonwebtoken';
2
- import crypto from 'crypto';
3
- import { encodeBase64, decodeBase64 } from './base64';
4
- import { UR } from './types';
5
-
6
- /**
7
- * Creates the JWT token that can be used for a UserSession
8
- * @method JWTUserToken
9
- * @memberof signing
10
- * @private
11
- * @param {Secret} apiSecret - API Secret key
12
- * @param {string} userId - The user_id key in the JWT payload
13
- * @param {UR} [extraData] - Extra that should be part of the JWT token
14
- * @param {SignOptions} [jwtOptions] - Options that can be past to jwt.sign
15
- * @return {string} JWT Token
16
- */
17
- export function JWTUserToken(
18
- apiSecret: Secret,
19
- userId: string,
20
- extraData: UR = {},
21
- jwtOptions: SignOptions = {},
22
- ) {
23
- if (typeof userId !== 'string') {
24
- throw new TypeError('userId should be a string');
25
- }
26
-
27
- const payload: { user_id: string } & UR = {
28
- user_id: userId,
29
- ...extraData,
30
- };
31
-
32
- // make sure we return a clear error when jwt is shimmed (ie. browser build)
33
- if (jwt == null || jwt.sign == null) {
34
- throw Error(
35
- `Unable to find jwt crypto, if you are getting this error is probably because you are trying to generate tokens on browser or React Native (or other environment where crypto functions are not available). Please Note: token should only be generated server-side.`,
36
- );
37
- }
38
-
39
- const opts: SignOptions = Object.assign(
40
- { algorithm: 'HS256', noTimestamp: true },
41
- jwtOptions,
42
- );
43
-
44
- if (payload.iat) {
45
- opts.noTimestamp = false;
46
- }
47
- return jwt.sign(payload, apiSecret, opts);
48
- }
49
-
50
- export function JWTServerToken(
51
- apiSecret: Secret,
52
- jwtOptions: SignOptions = {},
53
- ) {
54
- const payload = {
55
- server: true,
56
- };
57
-
58
- const opts: SignOptions = Object.assign(
59
- { algorithm: 'HS256', noTimestamp: true },
60
- jwtOptions,
61
- );
62
- return jwt.sign(payload, apiSecret, opts);
63
- }
64
-
65
- export function UserFromToken(token: string) {
66
- const fragments = token.split('.');
67
- if (fragments.length !== 3) {
68
- return '';
69
- }
70
- const b64Payload = fragments[1];
71
- const payload = decodeBase64(b64Payload);
72
- const data = JSON.parse(payload);
73
- return data.user_id as string;
74
- }
1
+ import { decodeBase64, encodeBase64 } from './base64';
75
2
 
76
3
  /**
77
4
  *
@@ -86,19 +13,13 @@ export function DevToken(userId: string) {
86
13
  ].join('.');
87
14
  }
88
15
 
89
- /**
90
- *
91
- * @param {string} body the signed message
92
- * @param {string} secret the shared secret used to generate the signature (Stream API secret)
93
- * @param {string} signature the signature to validate
94
- * @return {boolean}
95
- */
96
- export function CheckSignature(
97
- body: string,
98
- secret: string,
99
- signature: string,
100
- ) {
101
- const key = Buffer.from(secret, 'ascii');
102
- const hash = crypto.createHmac('sha256', key).update(body).digest('hex');
103
- return hash === signature;
16
+ export function UserFromToken(token: string) {
17
+ const fragments = token.split('.');
18
+ if (fragments.length !== 3) {
19
+ return '';
20
+ }
21
+ const b64Payload = fragments[1];
22
+ const payload = decodeBase64(b64Payload);
23
+ const data = JSON.parse(payload);
24
+ return data.user_id as string;
104
25
  }
@@ -1,5 +1,4 @@
1
- import { Secret } from 'jsonwebtoken';
2
- import { JWTServerToken, JWTUserToken, UserFromToken } from './signing';
1
+ import { UserFromToken } from './signing';
3
2
  import { isFunction } from './utils';
4
3
  import type { TokenOrProvider, UserWithId } from './types';
5
4
 
@@ -11,7 +10,7 @@ import type { TokenOrProvider, UserWithId } from './types';
11
10
  export class TokenManager {
12
11
  loadTokenPromise: Promise<string> | null;
13
12
  type: 'static' | 'provider';
14
- secret?: Secret;
13
+ secret?: string;
15
14
  token?: string;
16
15
  tokenProvider?: TokenOrProvider;
17
16
  user?: UserWithId;
@@ -20,17 +19,13 @@ export class TokenManager {
20
19
  *
21
20
  * @param {Secret} secret
22
21
  */
23
- constructor(secret?: Secret) {
22
+ constructor(secret?: string) {
24
23
  this.loadTokenPromise = null;
25
24
  if (secret) {
26
25
  this.secret = secret;
27
26
  }
28
27
 
29
28
  this.type = 'static';
30
-
31
- if (this.secret) {
32
- this.token = JWTServerToken(this.secret);
33
- }
34
29
  }
35
30
 
36
31
  /**
@@ -59,11 +54,6 @@ export class TokenManager {
59
54
  this.type = 'static';
60
55
  }
61
56
 
62
- if (!tokenOrProvider && this.user && this.secret) {
63
- this.token = JWTUserToken(this.secret, user.id, {}, {});
64
- this.type = 'static';
65
- }
66
-
67
57
  await this.loadToken();
68
58
  };
69
59
 
@@ -155,10 +145,6 @@ export class TokenManager {
155
145
  return this.token;
156
146
  }
157
147
 
158
- if (this.secret) {
159
- return JWTServerToken(this.secret);
160
- }
161
-
162
148
  throw new Error(
163
149
  `Both secret and user tokens are not set. Either client.connectUser wasn't called or client.disconnect was called`,
164
150
  );
@@ -1,6 +1,6 @@
1
1
  import { AxiosRequestConfig, AxiosResponse } from 'axios';
2
2
  import { StableWSConnection } from './connection';
3
- import { ConnectedEvent, UserRequest, VideoEvent } from '../../gen/coordinator';
3
+ import { ConnectedEvent, UserRequest, WSEvent } from '../../gen/coordinator';
4
4
  import { AllSfuEvents } from '../../rtc';
5
5
 
6
6
  export type UR = Record<string, unknown>;
@@ -62,7 +62,7 @@ export type ConnectionRecoveredEvent = {
62
62
  };
63
63
 
64
64
  export type StreamVideoEvent = (
65
- | VideoEvent
65
+ | WSEvent
66
66
  | ConnectionChangedEvent
67
67
  | TransportChangedEvent
68
68
  | ConnectionRecoveredEvent
@@ -70,7 +70,7 @@ export type StreamVideoEvent = (
70
70
 
71
71
  // TODO: we should use WSCallEvent here but that needs fixing
72
72
  export type StreamCallEvent = Extract<StreamVideoEvent, { call_cid: string }>;
73
- export type EventTypes = 'all' | VideoEvent['type'];
73
+ export type EventTypes = 'all' | WSEvent['type'];
74
74
 
75
75
  export type AllClientEventTypes = 'all' | StreamVideoEvent['type'];
76
76
  export type AllClientEvents = {
@@ -81,7 +81,7 @@ export type ClientEventListener<E extends keyof AllClientEvents> = (
81
81
  ) => void;
82
82
 
83
83
  export type AllClientCallEvents = {
84
- [K in EventTypes]: Extract<VideoEvent, { type: K }>;
84
+ [K in EventTypes]: Extract<WSEvent, { type: K }>;
85
85
  };
86
86
 
87
87
  export type AllCallEvents = AllClientCallEvents & AllSfuEvents;