Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions packages/client/src/Call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2238,13 +2238,10 @@ export class Call {

/**
* Allows you to grant or revoke a specific permission to a user in a call. The permissions are specific to the call experience and do not survive the call itself.
*
* When revoking a permission, this endpoint will also mute the relevant track from the user. This is similar to muting a user with the difference that the user will not be able to unmute afterwards.
*
* Supported permissions that can be granted or revoked: `send-audio`, `send-video` and `screenshare`.
*
* `call.permissions_updated` event is sent to all members of the call.
*
*/
updateUserPermissions = async (data: UpdateUserPermissionsRequest) => {
return this.streamClient.post<
Expand Down Expand Up @@ -2561,6 +2558,33 @@ export class Call {
return this.streamClient.get<GetCallReportResponse>(endpoint, params);
};

/**
* Loads the call report for the given session ID.
*/
getCallParticipantsStats = async (opts: {
sessionId?: string;
userId?: string;
userSessionId?: string;
kind?: 'timeline' | 'details';
}): Promise<any> => {
const {
sessionId = this.state.session?.id,
userId = this.currentUserId,
userSessionId = this.unifiedSessionId,
kind = 'details',
} = opts;
// FIXME OL: not yet part of the API
if (!sessionId) return;
const base = `${this.streamClient.baseURL}/call_stats/${this.type}/${this.id}/${sessionId}`;
const endpoint =
userId && userSessionId
? kind === 'details'
? `${base}/participant/${userId}/${userSessionId}/details`
: `${base}/participants/${userId}/${userSessionId}/timeline`
: `${base}/participants`;
return this.streamClient.get(endpoint);
};

/**
* Submit user feedback for the call
*
Expand Down
32 changes: 32 additions & 0 deletions sample-apps/react/react-dogfood/components/DevMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { getConnectionString } from '../lib/connectionString';

export const DevMenu = () => {
const call = useCall();
const { useLocalParticipant } = useCallStateHooks();
const localParticipant = useLocalParticipant();
return (
<ul className="rd__dev-menu">
<li className="rd__dev-menu__item">
Expand Down Expand Up @@ -97,6 +99,36 @@ export const DevMenu = () => {
Go to Inspector
</a>
)}
{call && (
<a
className="rd__link rd__link--faux-button rd__link--align-left"
href={`/stats/${call.cid}`}
rel="noreferrer"
target="_blank"
>
Go to Participant Stats
</a>
)}
{call && localParticipant && (
<a
className="rd__link rd__link--faux-button rd__link--align-left"
href={`/stats/${call.cid}?user_id=${call.currentUserId}&user_session_id=${call['unifiedSessionId'] || localParticipant.sessionId}&kind=details`}
rel="noreferrer"
target="_blank"
>
Go to {localParticipant?.name || 'User'} Stats (Details)
</a>
)}
{call && localParticipant && (
<a
className="rd__link rd__link--faux-button rd__link--align-left"
href={`/stats/${call.cid}?user_id=${call.currentUserId}&user_session_id=${call['unifiedSessionId'] || localParticipant.sessionId}&kind=timeline`}
rel="noreferrer"
target="_blank"
>
Go to {localParticipant?.name || 'User'} Stats (Timeline)
</a>
)}
</ul>
);
};
Expand Down
70 changes: 70 additions & 0 deletions sample-apps/react/react-dogfood/pages/stats/[cid].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import {
getServerSideCredentialsProps,
ServerSideCredentialsProps,
} from '../../lib/getServerSideCredentialsProps';
import { useAppEnvironment } from '../../context/AppEnvironmentContext';
import { getClient } from '../../helpers/client';

export default function Stats(props: ServerSideCredentialsProps) {
const { apiKey, userToken, user } = props;
const environment = useAppEnvironment();
const router = useRouter();
const cid = router.query['cid'] as string;
const [data, setData] = useState<any>({ message: 'Loading...' });

useEffect(() => {
const useLocalCoordinator =
router.query['use_local_coordinator'] === 'true';
const coordinatorUrl = useLocalCoordinator
? 'http://localhost:3030/video'
: (router.query['coordinator_url'] as string | undefined);
const callSessionId = router.query['call_session_id'] as string | undefined;
const _client = getClient(
{ apiKey, user, userToken, coordinatorUrl },
environment,
);
window.client = _client;

const [type, id] = cid.split(':');
const _call = _client.call(type, id, { reuseInstance: true });
(async () => {
try {
await _call.get();
const userId =
(router.query['user_id'] as string | undefined) || user.id;
const userSessionId = router.query['user_session_id'] as
| string
| undefined;
const kind =
(router.query['kind'] as 'details' | 'timeline') || 'details';
const stats = await _call.getCallParticipantsStats({
userId,
userSessionId,
sessionId: callSessionId,
kind,
});
console.log('Call participants stats:', stats);
setData(stats);
} catch (err) {
setData({ message: 'Failed to get call participants stats', err });
}
})();

return () => {
_client
.disconnectUser()
.catch((e) => console.error('Failed to disconnect user', e));

window.client = undefined;
};
}, [apiKey, user, userToken, environment, cid, router.query]);

return (
<pre style={{ height: '100%', overflow: 'scroll' }}>
{data && JSON.stringify(data, null, 2)}
</pre>
);
}
export const getServerSideProps = getServerSideCredentialsProps;
Loading