daily-examples/custom/shared/hooks/useSharedState.js

123 lines
4.4 KiB
JavaScript

import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useCallState } from '../contexts/CallProvider';
// @params initialValues - initial values of the shared state.
// @params broadcast - share the state with the other participants whenever the state changes.
export const useSharedState = ({ initialValues = {}, broadcast = true }) => {
const { callObject } = useCallState();
const stateRef = useRef(null);
const requestIntervalRef = useRef(null);
const [state, setState] = useState({
sharedState: initialValues,
setAt: undefined,
});
// handling the app-message event, to check if the state is being shared.
const handleAppMessage = useCallback(
event => {
// two types of events -
// 1. Request shared state (request-shared-state)
// 2. Set shared state (set-shared-state)
switch (event.data?.message?.type) {
// if we receive a request-shared-state message type, we check if the user has any previous state,
// if yes, we will send the shared-state to everyone in the call.
case 'request-shared-state':
if (!stateRef.current.setAt) return;
callObject.sendAppMessage(
{
message: {
type: 'set-shared-state',
value: stateRef.current,
},
},
'*',
);
break;
// if we receive a set-shared-state message type then, we check the state timestamp with the local one and
// we set the latest shared-state values into the local state.
case 'set-shared-state':
clearInterval(requestIntervalRef.current);
if (
stateRef.current.setAt &&
new Date(stateRef.current.setAt) > new Date(event.data.message.value.setAt)
)
return;
setState(event.data.message.value);
break;
}
},
[stateRef, callObject],
);
// whenever local user joins, we randomly pick a participant from the call and request him for the state.
const handleJoinedMeeting = useCallback(() => {
const randomDelay = 1000 + Math.ceil(1000 * Math.random());
requestIntervalRef.current = setInterval(() => {
const callObjectParticipants = callObject.participants();
const participants = Object.values(callObjectParticipants);
const localParticipant = callObjectParticipants.local;
if (participants.length > 1) {
const remoteParticipants = participants.filter((p) =>
!p.local && new Date(p.joined_at) < new Date(localParticipant.joined_at)
);
const randomPeer = remoteParticipants[Math.floor(Math.random() * remoteParticipants.length)];
callObject.sendAppMessage(
{
message: {
type: 'request-shared-state',
},
},
randomPeer.user_id,
);
} else clearInterval(requestIntervalRef.current);
}, randomDelay);
return () => clearInterval(requestIntervalRef.current);
}, [callObject]);
useEffect(() => {
if (!callObject) return;
callObject.on('app-message', handleAppMessage);
callObject.on('joined-meeting', handleJoinedMeeting);
return () => {
callObject.off('app-message', handleAppMessage);
callObject.off('joined-meeting', handleJoinedMeeting);
}
}, [callObject, handleAppMessage, handleJoinedMeeting]);
useEffect(() => {
stateRef.current = state;
}, [state]);
// setSharedState function takes in the state values :-
// 1. shares the state with everyone in the call.
// 2. set the state for the local user.
const setSharedState = useCallback(
values => {
setState((state) => {
const currentValues = typeof values === 'function' ? values(state.sharedState) : values;
const stateObj = { ...state, sharedState: currentValues, setAt: new Date() };
// if broadcast is true, we send the state to everyone in the call whenever the state changes.
if (broadcast) {
callObject.sendAppMessage(
{
message: {
type: 'set-shared-state',
value: stateObj,
},
},
'*',
);
}
return stateObj;
});
}, [broadcast, callObject],
);
// returns back the sharedState and the setSharedState function
return { sharedState: state.sharedState, setSharedState };
};