mirror of
https://github.com/balzack/databag.git
synced 2025-05-04 23:45:21 +00:00
ring context cleanup
This commit is contained in:
parent
f56b6ae00b
commit
45b20948dc
@ -1,13 +1,14 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import classes from './Calling.module.css'
|
import classes from './Calling.module.css'
|
||||||
import { useCalling, type Ring } from './useCalling.hook';
|
import { type Card } from 'databag-client-sdk'
|
||||||
import { Card } from '../card/Card';
|
import { useCalling } from './useCalling.hook';
|
||||||
|
import { Card as Contact } from '../card/Card';
|
||||||
import { Loader, Image, Text, ActionIcon } from '@mantine/core'
|
import { Loader, Image, Text, ActionIcon } from '@mantine/core'
|
||||||
import { IconEyeX, IconPhone, IconPhoneOff, IconMicrophone, IconMicrophoneOff, IconVideo, IconVideoOff } from '@tabler/icons-react'
|
import { IconEyeX, IconPhone, IconPhoneOff, IconMicrophone, IconMicrophoneOff, IconVideo, IconVideoOff } from '@tabler/icons-react'
|
||||||
import { modals } from '@mantine/modals'
|
import { modals } from '@mantine/modals'
|
||||||
import { Colors } from '../constants/Colors'
|
import { Colors } from '../constants/Colors'
|
||||||
|
|
||||||
export function Calling({ callCard }: { callCard: { cardId: null|string }}) {
|
export function Calling({ callCard }: { callCard: { card: null|Card }}) {
|
||||||
const [connecting, setConnecting] = useState(false);
|
const [connecting, setConnecting] = useState(false);
|
||||||
const [ending, setEnding] = useState(false);
|
const [ending, setEnding] = useState(false);
|
||||||
const [applyingVideo, setApplyingVideo] = useState(false);
|
const [applyingVideo, setApplyingVideo] = useState(false);
|
||||||
@ -80,11 +81,11 @@ export function Calling({ callCard }: { callCard: { cardId: null|string }}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const call = async (cardId: string) => {
|
const call = async (card: Card) => {
|
||||||
if (!connecting) {
|
if (!connecting) {
|
||||||
setConnecting(true);
|
setConnecting(true);
|
||||||
try {
|
try {
|
||||||
await actions.call(cardId);
|
await actions.call(card);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
showError();
|
showError();
|
||||||
@ -93,7 +94,7 @@ export function Calling({ callCard }: { callCard: { cardId: null|string }}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const accept = async (ring: Ring) => {
|
const accept = async (ring: { callId: string, card: Card }) => {
|
||||||
if (!accepting) {
|
if (!accepting) {
|
||||||
setAccepting(ring.callId);
|
setAccepting(ring.callId);
|
||||||
try {
|
try {
|
||||||
@ -106,7 +107,7 @@ export function Calling({ callCard }: { callCard: { cardId: null|string }}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ignore = async (ring: Ring) => {
|
const ignore = async (ring: { callId: string, card: Card }) => {
|
||||||
if (!ignoring) {
|
if (!ignoring) {
|
||||||
setIgnoring(ring.callId);
|
setIgnoring(ring.callId);
|
||||||
try {
|
try {
|
||||||
@ -119,7 +120,7 @@ export function Calling({ callCard }: { callCard: { cardId: null|string }}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const decline = async (ring: Ring) => {
|
const decline = async (ring: { callId: string, card: Card }) => {
|
||||||
if (!declining) {
|
if (!declining) {
|
||||||
setDeclining(ring.callId);
|
setDeclining(ring.callId);
|
||||||
try {
|
try {
|
||||||
@ -133,8 +134,8 @@ export function Calling({ callCard }: { callCard: { cardId: null|string }}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (callCard?.cardId) {
|
if (callCard?.card) {
|
||||||
call(callCard.cardId);
|
call(callCard.card);
|
||||||
}
|
}
|
||||||
}, [callCard]);
|
}, [callCard]);
|
||||||
|
|
||||||
@ -162,7 +163,7 @@ export function Calling({ callCard }: { callCard: { cardId: null|string }}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={index} className={classes.caller}>
|
<div key={index} className={classes.caller}>
|
||||||
<Card className={classes.card} placeholder={''} imageUrl={imageUrl} name={name} node={node} handle={handle} actions={[ignoreButton, declineButton, acceptButton]} />
|
<Contact className={classes.card} placeholder={''} imageUrl={imageUrl} name={name} node={node} handle={handle} actions={[ignoreButton, declineButton, acceptButton]} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -1,33 +1,17 @@
|
|||||||
import { useState, useContext, useEffect, useRef } from 'react'
|
import { useState, useContext, useEffect, useRef } from 'react'
|
||||||
import { DisplayContext } from '../context/DisplayContext';
|
import { RingContext } from '../context/RingContext'
|
||||||
import { AppContext } from '../context/AppContext'
|
import { DisplayContext } from '../context/DisplayContext'
|
||||||
import { ContextType } from '../context/ContextType'
|
import { ContextType } from '../context/ContextType'
|
||||||
import { Link, type Card } from 'databag-client-sdk';
|
import { Card } from 'databag-client-sdk';
|
||||||
|
|
||||||
export type Ring = {
|
|
||||||
callId: string,
|
|
||||||
card: Card,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCalling() {
|
export function useCalling() {
|
||||||
const app = useContext(AppContext) as ContextType;
|
const ring = useContext(RingContext) as ContextType;
|
||||||
const display = useContext(DisplayContext) as ContextType;
|
const display = useContext(DisplayContext) as ContextType;
|
||||||
const call = useRef(null as { policy: string, peer: RTCPeerConnection, link: Link, candidates: RTCIceCandidate[] } | null);
|
|
||||||
const localStream = useRef(null as null|MediaStream);
|
|
||||||
const localAudio = useRef(null as null|MediaStreamTrack);
|
|
||||||
const localVideo = useRef(null as null|MediaStreamTrack);
|
|
||||||
const remoteStream = useRef(null as null|MediaStream);
|
|
||||||
const updatingPeer = useRef(false);
|
|
||||||
const peerUpdate = useRef([] as {type: string, data?: any}[]);
|
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
strings: display.state.strings,
|
strings: display.state.strings,
|
||||||
ringing: [] as { cardId: string, callId: string }[],
|
calls: [] as { callId: string, card: Card }[],
|
||||||
calls: [] as Ring[],
|
|
||||||
cards: [] as Card[],
|
|
||||||
calling: null as null | Card,
|
calling: null as null | Card,
|
||||||
failed: false,
|
|
||||||
loaded: false,
|
|
||||||
localStream: null as null|MediaStream,
|
localStream: null as null|MediaStream,
|
||||||
remoteStream: null as null|MediaStream,
|
remoteStream: null as null|MediaStream,
|
||||||
localVideo: false,
|
localVideo: false,
|
||||||
@ -35,6 +19,7 @@ export function useCalling() {
|
|||||||
audioEnabled: false,
|
audioEnabled: false,
|
||||||
videoEnabled: false,
|
videoEnabled: false,
|
||||||
connected: false,
|
connected: false,
|
||||||
|
failed: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -43,337 +28,10 @@ export function useCalling() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const calls = state.ringing
|
const { calls, calling, localStream, remoteStream, localVideo, remoteVideo, audioEnabled, videoEnabled, connected, failed } = ring.state;
|
||||||
.map(ring => ({ callId: ring.callId, card: state.cards.find(card => ring.cardId === card.cardId) }) )
|
updateState({ calls, calling, localStream, remoteStream, localVideo, remoteVideo, audioEnabled, videoEnabled, connected, failed });
|
||||||
.filter(ring => (ring.card && !ring.card.blocked));
|
}, [ring.state]);
|
||||||
updateState({ calls });
|
|
||||||
}, [state.ringing, state.cards]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const actions = ring.actions;
|
||||||
const { strings } = display.state;
|
return { state, actions };
|
||||||
updateState({ strings });
|
|
||||||
}, [display.state]);
|
|
||||||
|
|
||||||
const getAudioStream = async (audioId: null|string) => {
|
|
||||||
try {
|
|
||||||
if (audioId) {
|
|
||||||
return await navigator.mediaDevices.getUserMedia({ video: false, audio: { deviceId: audioId } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
return await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const getVideoStream = async (videoId: null|string) => {
|
|
||||||
try {
|
|
||||||
if (videoId) {
|
|
||||||
return await navigator.mediaDevices.getUserMedia({ video: { deviceId: videoId }, audio: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
return await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
const linkStatus = async (status: string) => {
|
|
||||||
if (call.current) {
|
|
||||||
const { policy, peer, link } = call.current;
|
|
||||||
if (status === 'connected') {
|
|
||||||
localVideo.current = null;
|
|
||||||
localStream.current = null;
|
|
||||||
remoteStream.current = new MediaStream();
|
|
||||||
updateState({ localStream: localStream.current, remoteStream: remoteStream.current,
|
|
||||||
audioEnabled: false, videoEnabled: false, localVideo: false, remoteVideo: false, connected: true });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const audioStream = await getAudioStream(null);
|
|
||||||
const audioTrack = audioStream.getTracks().find((track: MediaStreamTrack) => track.kind === 'audio');
|
|
||||||
if (audioTrack) {
|
|
||||||
localAudio.current = audioTrack;
|
|
||||||
}
|
|
||||||
if (localAudio.current) {
|
|
||||||
localAudio.current.enabled = true;
|
|
||||||
await updatePeer('local_track', { track: audioTrack, stream: audioStream });
|
|
||||||
updateState({ audioEnabled: true });
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
} else if (status === 'closed') {
|
|
||||||
updatePeer('close');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const linkMessage = async (message: any) => {
|
|
||||||
if (call.current) {
|
|
||||||
const { peer, link, policy, candidates } = call.current;
|
|
||||||
try {
|
|
||||||
if (message.description) {
|
|
||||||
const offer = new RTCSessionDescription(message.description);
|
|
||||||
await peer.setRemoteDescription(offer);
|
|
||||||
if (message.description.type === 'offer') {
|
|
||||||
const description = await peer.createAnswer();
|
|
||||||
await peer.setLocalDescription(description);
|
|
||||||
link.sendMessage({ description });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (call.current) {
|
|
||||||
for (const candidate of candidates) {
|
|
||||||
await peer.addIceCandidate(candidate);
|
|
||||||
};
|
|
||||||
call.current.candidates = [];
|
|
||||||
}
|
|
||||||
} else if (message.candidate) {
|
|
||||||
const candidate = new RTCIceCandidate(message.candidate);
|
|
||||||
if (peer.remoteDescription == null) {
|
|
||||||
candidates.push(candidate);
|
|
||||||
} else {
|
|
||||||
await peer.addIceCandidate(candidate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
updateState({ failed: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const peerCandidate = async (candidate: RTCIceCandidate) => {
|
|
||||||
if (call.current && candidate) {
|
|
||||||
const { link } = call.current;
|
|
||||||
await link.sendMessage({ candidate });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const peerNegotiate = async () => {
|
|
||||||
if (call.current) {
|
|
||||||
try {
|
|
||||||
const { peer, link } = call.current;
|
|
||||||
const description = await peer.createOffer();
|
|
||||||
await peer.setLocalDescription(description);
|
|
||||||
await link.sendMessage({ description });
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const peerTrack = async (track: MediaStreamTrack, stream: MediaStream) => {
|
|
||||||
if (call.current && localStream.current) {
|
|
||||||
try {
|
|
||||||
const { peer } = call.current;
|
|
||||||
peer.addTrack(track, stream);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatePeer = async (type: string, data?: any) => {
|
|
||||||
peerUpdate.current.push({ type, data });
|
|
||||||
|
|
||||||
if (!updatingPeer.current) {
|
|
||||||
updatingPeer.current = true;
|
|
||||||
while (peerUpdate.current.length > 0) {
|
|
||||||
const { type, data } = peerUpdate.current.shift() || { type: '' };
|
|
||||||
if (type === 'negotiate') {
|
|
||||||
await peerNegotiate();
|
|
||||||
} else if (type === 'candidate') {
|
|
||||||
await peerCandidate(data);
|
|
||||||
} else if (type === 'message') {
|
|
||||||
await linkMessage(data);
|
|
||||||
} else if (type === 'remote_track') {
|
|
||||||
if (remoteStream.current) {
|
|
||||||
remoteStream.current.addTrack(data);
|
|
||||||
if (data.kind === 'video') {
|
|
||||||
updateState({ remoteVideo: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (type === 'local_track') {
|
|
||||||
await peerTrack(data.track, data.stream);
|
|
||||||
} else if (type === 'close' && call.current) {
|
|
||||||
peerUpdate.current = [];
|
|
||||||
const { peer, link } = call.current;
|
|
||||||
call.current = null;
|
|
||||||
try {
|
|
||||||
peer.close();
|
|
||||||
link.close();
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
if (localVideo.current) {
|
|
||||||
localVideo.current.stop();
|
|
||||||
localVideo.current = null;
|
|
||||||
}
|
|
||||||
if (localAudio.current) {
|
|
||||||
localAudio.current.stop();
|
|
||||||
localAudio.current = null;
|
|
||||||
}
|
|
||||||
localStream.current = null;
|
|
||||||
remoteStream.current = null,
|
|
||||||
updateState({ calling: null, failed: false, localStream: null, remoteStream: null, localVideo: false, remoteVideo: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updatingPeer.current = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const transmit = (ice: { urls: string; username: string; credential: string }[]) => {
|
|
||||||
const peerConnection = new RTCPeerConnection({ iceServers: ice });
|
|
||||||
peerConnection.addEventListener( 'connectionstatechange', event => {
|
|
||||||
console.log("CONNECTION STATE", event);
|
|
||||||
});
|
|
||||||
peerConnection.addEventListener( 'icecandidate', event => {
|
|
||||||
updatePeer('candidate', event.candidate);
|
|
||||||
});
|
|
||||||
peerConnection.addEventListener( 'icecandidateerror', event => {
|
|
||||||
console.log("ICE ERROR");
|
|
||||||
});
|
|
||||||
peerConnection.addEventListener( 'iceconnectionstatechange', event => {
|
|
||||||
console.log("ICE STATE CHANGE", event);
|
|
||||||
});
|
|
||||||
peerConnection.addEventListener( 'negotiationneeded', event => {
|
|
||||||
updatePeer('negotiate');
|
|
||||||
});
|
|
||||||
peerConnection.addEventListener( 'signalingstatechange', event => {
|
|
||||||
console.log("ICE SIGNALING", event);
|
|
||||||
});
|
|
||||||
peerConnection.addEventListener( 'track', event => {
|
|
||||||
updatePeer('remote_track', event.track);
|
|
||||||
});
|
|
||||||
return peerConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (app.state.session) {
|
|
||||||
const setRinging = (ringing: { cardId: string, callId: string }[]) => {
|
|
||||||
updateState({ ringing });
|
|
||||||
}
|
|
||||||
const setContacts = (cards: Card[]) => {
|
|
||||||
updateState({ cards });
|
|
||||||
}
|
|
||||||
const ring = app.state.session.getRing();
|
|
||||||
ring.addRingingListener(setRinging);
|
|
||||||
const contact = app.state.session.getContact();
|
|
||||||
contact.addCardListener(setContacts);
|
|
||||||
return () => {
|
|
||||||
ring.removeRingingListener(setRinging);
|
|
||||||
contact.removeCardListener(setContacts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [app.state.session]);
|
|
||||||
|
|
||||||
const actions = {
|
|
||||||
ignore: async (callId: string, card: Card) => {
|
|
||||||
const ring = app.state.session.getRing();
|
|
||||||
await ring.ignore(card.cardId, callId);
|
|
||||||
},
|
|
||||||
decline: async (callId: string, card: Card) => {
|
|
||||||
const ring = app.state.session.getRing();
|
|
||||||
await ring.decline(card.cardId, callId);
|
|
||||||
},
|
|
||||||
end: async () => {
|
|
||||||
if (!call.current) {
|
|
||||||
throw new Error('no active call');
|
|
||||||
}
|
|
||||||
await updatePeer('close');
|
|
||||||
},
|
|
||||||
accept: async (callId: string, card: Card) => {
|
|
||||||
if (call.current) {
|
|
||||||
throw new Error('active call in progress');
|
|
||||||
}
|
|
||||||
const { cardId, node } = card;
|
|
||||||
const ring = app.state.session.getRing();
|
|
||||||
const link = await ring.accept(cardId, callId, node);
|
|
||||||
const ice = link.getIce();
|
|
||||||
const peer = transmit(ice);
|
|
||||||
const policy = 'impolite';
|
|
||||||
const candidates = [] as RTCIceCandidate[];
|
|
||||||
call.current = { policy, peer, link, candidates };
|
|
||||||
link.setStatusListener(linkStatus);
|
|
||||||
link.setMessageListener((msg: any) => updatePeer('message', msg));
|
|
||||||
updateState({ calling: card, connected: false });
|
|
||||||
},
|
|
||||||
call: async (cardId: string) => {
|
|
||||||
if (call.current) {
|
|
||||||
throw new Error('active call in proegress');
|
|
||||||
}
|
|
||||||
const card = state.cards.find(contact => contact.cardId === cardId);
|
|
||||||
if (!card) {
|
|
||||||
throw new Error('calling contact not found');
|
|
||||||
}
|
|
||||||
const contact = app.state.session.getContact();
|
|
||||||
const link = await contact.callCard(cardId);
|
|
||||||
const ice = link.getIce();
|
|
||||||
const peer = transmit(ice);
|
|
||||||
const policy = 'polite';
|
|
||||||
const candidates = [] as RTCIceCandidate[];
|
|
||||||
call.current = { policy, peer, link, candidates };
|
|
||||||
link.setStatusListener(linkStatus);
|
|
||||||
link.setMessageListener((msg: any) => updatePeer('message', msg));
|
|
||||||
updateState({ calling: card, connected: false });
|
|
||||||
},
|
|
||||||
enableAudio: async () => {
|
|
||||||
if (!call.current) {
|
|
||||||
throw new Error('cannot unmute audio');
|
|
||||||
}
|
|
||||||
if (!localAudio.current) {
|
|
||||||
const audioStream = await getAudioStream(null);
|
|
||||||
const audioTrack = audioStream.getTracks().find((track: MediaStreamTrack) => track.kind === 'audio');
|
|
||||||
if (!audioTrack) {
|
|
||||||
throw new Error('no available audio track');
|
|
||||||
}
|
|
||||||
localAudio.current = audioTrack;
|
|
||||||
localStream.current = audioStream;
|
|
||||||
updatePeer('local_track', { track: audioTrack, stream: audioStream });
|
|
||||||
updateState({ localAudio: true, localStream: audioStream, audioEnabled: true });
|
|
||||||
} else {
|
|
||||||
localAudio.current.enabled = true;
|
|
||||||
updateState({ audioEnabled: true });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
disableAudio: async () => {
|
|
||||||
if (!call.current) {
|
|
||||||
throw new Error('cannot mute audio');
|
|
||||||
}
|
|
||||||
if (localAudio.current) {
|
|
||||||
localAudio.current.enabled = false;
|
|
||||||
updateState({ audioEnabled: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enableVideo: async () => {
|
|
||||||
if (!call.current) {
|
|
||||||
throw new Error('cannot start video');
|
|
||||||
}
|
|
||||||
if (!localVideo.current) {
|
|
||||||
const videoStream = await getVideoStream(null);
|
|
||||||
const videoTrack = videoStream.getTracks().find((track: MediaStreamTrack) => track.kind === 'video');
|
|
||||||
if (videoTrack) {
|
|
||||||
localVideo.current = videoTrack;
|
|
||||||
localStream.current = videoStream;
|
|
||||||
updatePeer('local_track', { track: videoTrack, stream: videoStream });
|
|
||||||
updateState({ localVideo: true, localStream: videoStream });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (localVideo.current) {
|
|
||||||
localVideo.current.enabled = true;
|
|
||||||
updateState({ videoEnabled: true });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
disableVideo: async () => {
|
|
||||||
if (!call.current) {
|
|
||||||
throw new Error('cannot stop video');
|
|
||||||
}
|
|
||||||
if (localVideo.current) {
|
|
||||||
localVideo.current.enabled = false;
|
|
||||||
updateState({ videoEnabled: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return { state, actions }
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@ import { useContacts } from './useContacts.hook'
|
|||||||
import { Text, ActionIcon, TextInput, Button } from '@mantine/core'
|
import { Text, ActionIcon, TextInput, Button } from '@mantine/core'
|
||||||
import { IconUserCheck, IconCancel, IconRefresh, IconSearch, IconUserPlus, IconSortAscending, IconSortDescending, IconMessage2, IconPhone } from '@tabler/icons-react'
|
import { IconUserCheck, IconCancel, IconRefresh, IconSearch, IconUserPlus, IconSortAscending, IconSortDescending, IconMessage2, IconPhone } from '@tabler/icons-react'
|
||||||
import classes from './Contacts.module.css'
|
import classes from './Contacts.module.css'
|
||||||
import { Card } from '../card/Card'
|
import { type Card } from 'databag-client-sdk';
|
||||||
|
import { Card as Contact } from '../card/Card'
|
||||||
import { ProfileParams } from '../profile/Profile'
|
import { ProfileParams } from '../profile/Profile'
|
||||||
import { Colors } from '../constants/Colors'
|
import { Colors } from '../constants/Colors'
|
||||||
import { modals } from '@mantine/modals'
|
import { modals } from '@mantine/modals'
|
||||||
@ -37,7 +38,7 @@ function Action({ icon, color, strings, select }: { icon: ReactNode; color: stri
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Contacts({ openRegistry, openContact, textContact, callContact }: { openRegistry: ()=>void; openContact: (params: ProfileParams)=>void, textContact: (cardId: string)=>void, callContact: (cardId: string)=>void }) {
|
export function Contacts({ openRegistry, openContact, textContact, callContact }: { openRegistry: ()=>void; openContact: (params: ProfileParams)=>void, textContact: (cardId: string)=>void, callContact: (card: Card)=>void }) {
|
||||||
const { state, actions } = useContacts()
|
const { state, actions } = useContacts()
|
||||||
|
|
||||||
const cards = state.filtered.map((card, idx) => {
|
const cards = state.filtered.map((card, idx) => {
|
||||||
@ -47,7 +48,7 @@ export function Contacts({ openRegistry, openContact, textContact, callContact }
|
|||||||
const phone = <IconPhone size={24} />
|
const phone = <IconPhone size={24} />
|
||||||
const text = <IconMessage2 size={24} />
|
const text = <IconMessage2 size={24} />
|
||||||
return [
|
return [
|
||||||
<Action key="phone" icon={phone} color={Colors.connected} select={async () => callContact(card.cardId)} strings={state.strings} />,
|
<Action key="phone" icon={phone} color={Colors.connected} select={async () => callContact(card)} strings={state.strings} />,
|
||||||
<Action key="text" icon={text} color={Colors.connected} select={async () => textContact(card.cardId)} strings={state.strings} />,
|
<Action key="text" icon={text} color={Colors.connected} select={async () => textContact(card.cardId)} strings={state.strings} />,
|
||||||
]
|
]
|
||||||
} else if (status === 'offsync') {
|
} else if (status === 'offsync') {
|
||||||
@ -75,7 +76,7 @@ export function Contacts({ openRegistry, openContact, textContact, callContact }
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card key={idx} className={classes.card} imageUrl={card.imageUrl} name={card.name} handle={card.handle} node={card.node} placeholder={state.strings.name} select={select} actions={options} />
|
<Contact key={idx} className={classes.card} imageUrl={card.imageUrl} name={card.name} handle={card.handle} node={card.node} placeholder={state.strings.name} select={select} actions={options} />
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -18,11 +18,11 @@ export function useRingContext() {
|
|||||||
const peerUpdate = useRef([] as {type: string, data?: any}[]);
|
const peerUpdate = useRef([] as {type: string, data?: any}[]);
|
||||||
const connecting = useRef(false);
|
const connecting = useRef(false);
|
||||||
const closing = useRef(false);
|
const closing = useRef(false);
|
||||||
|
const [ringing, setRinging] = useState([] as { cardId: string, callId: string }[]);
|
||||||
|
const [cards, setCards] = useState([] as Card[]);
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
ringing: [] as { cardId: string, callId: string }[],
|
|
||||||
calls: [] as { callId: string, cardId: string}[],
|
calls: [] as { callId: string, cardId: string}[],
|
||||||
cards: [] as Card[],
|
|
||||||
calling: null as null | Card,
|
calling: null as null | Card,
|
||||||
localStream: null as null|MediaStream,
|
localStream: null as null|MediaStream,
|
||||||
remoteStream: null as null|MediaStream,
|
remoteStream: null as null|MediaStream,
|
||||||
@ -40,11 +40,10 @@ export function useRingContext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const calls = state.ringing
|
const calls = ringing.map(ring => ({ callId: ring.callId, card: cards.find(card => ring.cardId === card.cardId) }))
|
||||||
.map(ring => ({ callId: ring.callId, card: state.cards.find(card => ring.cardId === card.cardId) }) )
|
|
||||||
.filter(ring => (ring.card && !ring.card.blocked));
|
.filter(ring => (ring.card && !ring.card.blocked));
|
||||||
updateState({ calls });
|
updateState({ calls });
|
||||||
}, [state.ringing, state.cards]);
|
}, [ringing, cards]);
|
||||||
|
|
||||||
const getAudioStream = async (audioId: null|string) => {
|
const getAudioStream = async (audioId: null|string) => {
|
||||||
try {
|
try {
|
||||||
@ -74,10 +73,10 @@ export function useRingContext() {
|
|||||||
if (call.current) {
|
if (call.current) {
|
||||||
const { peer, link } = call.current;
|
const { peer, link } = call.current;
|
||||||
if (status === 'connected') {
|
if (status === 'connected') {
|
||||||
await updatePeer('open');
|
updateState({ connected: true });
|
||||||
await actions.enableAudio();
|
await actions.enableAudio();
|
||||||
} else if (status === 'closed') {
|
} else if (status === 'closed') {
|
||||||
await updatePeer('close');
|
await cleanup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,11 +140,6 @@ export function useRingContext() {
|
|||||||
updateState({ localVideo: true, localStream: localStream.current })
|
updateState({ localVideo: true, localStream: localStream.current })
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'open':
|
|
||||||
updateState({ connected: true });
|
|
||||||
case 'close':
|
|
||||||
await cleanup();
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
console.log('unknown event');
|
console.log('unknown event');
|
||||||
break;
|
break;
|
||||||
@ -229,18 +223,18 @@ export function useRingContext() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (app.state.session) {
|
if (app.state.session) {
|
||||||
const setRinging = (ringing: { cardId: string, callId: string }[]) => {
|
const setRing = (ringing: { cardId: string, callId: string }[]) => {
|
||||||
updateState({ ringing });
|
setRinging(ringing);
|
||||||
}
|
}
|
||||||
const setContacts = (cards: Card[]) => {
|
const setContacts = (cards: Card[]) => {
|
||||||
updateState({ cards });
|
setCards(cards);
|
||||||
}
|
}
|
||||||
const ring = app.state.session.getRing();
|
const ring = app.state.session.getRing();
|
||||||
ring.addRingingListener(setRinging);
|
ring.addRingingListener(setRinging);
|
||||||
const contact = app.state.session.getContact();
|
const contact = app.state.session.getContact();
|
||||||
contact.addCardListener(setContacts);
|
contact.addCardListener(setContacts);
|
||||||
return () => {
|
return () => {
|
||||||
ring.removeRingingListener(setRinging);
|
ring.removeRingingListener(setRing);
|
||||||
contact.removeCardListener(setContacts);
|
contact.removeCardListener(setContacts);
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
@ -275,18 +269,14 @@ export function useRingContext() {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
call: async (cardId: string) => {
|
call: async (card: Card) => {
|
||||||
if (connecting.current || closing.current || call.current) {
|
if (connecting.current || closing.current || call.current) {
|
||||||
throw new Error('not ready make calls');
|
throw new Error('not ready make calls');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
connecting.current = true;
|
connecting.current = true;
|
||||||
const card = state.cards.find(contact => contact.cardId === cardId);
|
|
||||||
if (!card) {
|
|
||||||
throw new Error('calling contact not found');
|
|
||||||
}
|
|
||||||
const contact = app.state.session.getContact();
|
const contact = app.state.session.getContact();
|
||||||
const link = await contact.callCard(cardId);
|
const link = await contact.callCard(card.cardId);
|
||||||
await setup(link, card);
|
await setup(link, card);
|
||||||
connecting.current = false;
|
connecting.current = false;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -14,7 +14,7 @@ import { Profile, ProfileParams } from '../profile/Profile'
|
|||||||
import { Details } from '../details/Details';
|
import { Details } from '../details/Details';
|
||||||
import { Content } from '../content/Content'
|
import { Content } from '../content/Content'
|
||||||
import { Conversation } from '../conversation/Conversation'
|
import { Conversation } from '../conversation/Conversation'
|
||||||
import { Focus } from 'databag-client-sdk'
|
import { Focus, Card } from 'databag-client-sdk'
|
||||||
import { useDisclosure } from '@mantine/hooks'
|
import { useDisclosure } from '@mantine/hooks'
|
||||||
import { IconAlertCircle } from '@tabler/icons-react'
|
import { IconAlertCircle } from '@tabler/icons-react'
|
||||||
import { Calling } from '../calling/Calling';
|
import { Calling } from '../calling/Calling';
|
||||||
@ -29,7 +29,7 @@ export function Session() {
|
|||||||
const [details, { open: openDetails, close: closeDetails }] = useDisclosure(false)
|
const [details, { open: openDetails, close: closeDetails }] = useDisclosure(false)
|
||||||
const [profile, { open: openProfile, close: closeProfile }] = useDisclosure(false)
|
const [profile, { open: openProfile, close: closeProfile }] = useDisclosure(false)
|
||||||
const [textCard, setTextCard] = useState({ cardId: null} as {cardId: null|string});
|
const [textCard, setTextCard] = useState({ cardId: null} as {cardId: null|string});
|
||||||
const [callCard, setCallCard] = useState({ cardId: null} as {cardId: null|string});
|
const [callCard, setCallCard] = useState({ card: null} as {card: null|Card});
|
||||||
|
|
||||||
const textContact = (cardId: string) => {
|
const textContact = (cardId: string) => {
|
||||||
console.log("MESSAGE: ", cardId);
|
console.log("MESSAGE: ", cardId);
|
||||||
@ -38,8 +38,8 @@ export function Session() {
|
|||||||
setTab('content');
|
setTab('content');
|
||||||
}
|
}
|
||||||
|
|
||||||
const callContact = (cardId: string) => {
|
const callContact = (card: Card) => {
|
||||||
setCallCard({ cardId });
|
setCallCard({ card });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user