From a9e5c3ff85a66ea5925471a3567dfd9d05da3f33 Mon Sep 17 00:00:00 2001 From: balzack Date: Tue, 28 Jan 2025 19:41:30 -0800 Subject: [PATCH] support accepting calls --- .../mobile/src/calling/Calling.styled.ts | 27 +++- app/client/mobile/src/calling/Calling.tsx | 147 +++++++++++------- .../mobile/src/calling/useCalling.hook.ts | 32 ++-- app/sdk/src/link.ts | 18 ++- app/sdk/src/ring.ts | 9 +- 5 files changed, 153 insertions(+), 80 deletions(-) diff --git a/app/client/mobile/src/calling/Calling.styled.ts b/app/client/mobile/src/calling/Calling.styled.ts index 069f6eba..29be92e9 100644 --- a/app/client/mobile/src/calling/Calling.styled.ts +++ b/app/client/mobile/src/calling/Calling.styled.ts @@ -16,7 +16,6 @@ export const styles = StyleSheet.create({ position: 'absolute', alignItems: 'center', justifyContent: 'center', - backgroundColor: 'rgb(64,64,64)', }, inactive: { display: 'none', @@ -25,7 +24,13 @@ export const styles = StyleSheet.create({ position: 'absolute', alignItems: 'center', justifyContent: 'center', - backgroundColor: 'rgb(64,64,64)', + }, + base: { + width: '100%', + height: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', }, container: { width: '100%', @@ -33,6 +38,11 @@ export const styles = StyleSheet.create({ display: 'flex', alignItems: 'center', justifyContent: 'center', + backgroundColor: 'rgb(64,64,64)', + }, + calls: { + borderRadius: 8, + overflow: 'hidden', }, call: { width: '100%', @@ -56,6 +66,11 @@ export const styles = StyleSheet.create({ closeIcon: { borderRadius: 8, }, + circleIcon: { + }, + flipIcon: { + transform: [{ rotate: '135deg' }], + }, name: { fontSize: 28, color: '#aaaaaa', @@ -87,4 +102,12 @@ export const styles = StyleSheet.create({ height: '100%', backgroundColor: 'rgb(64,64,64)', }, + card: { + width: '100%', + height: 48, + paddingTop: 8, + paddingBottom: 8, + paddingLeft: 16, + borderBottomWidth: 1, + } }); diff --git a/app/client/mobile/src/calling/Calling.tsx b/app/client/mobile/src/calling/Calling.tsx index 500adf09..23bc1072 100644 --- a/app/client/mobile/src/calling/Calling.tsx +++ b/app/client/mobile/src/calling/Calling.tsx @@ -10,6 +10,7 @@ import FastImage from 'react-native-fast-image' import LinearGradient from 'react-native-linear-gradient'; import { Colors } from '../constants/Colors'; import { RTCView } from 'react-native-webrtc'; +import { Card } from '../card/Card'; export function Calling({ callCard }: { callCard: string }) { const { state, actions } = useCalling(); @@ -80,6 +81,19 @@ export function Calling({ callCard }: { callCard: string }) { } } + const accept = async (callId, card) => { + if (!connecting) { + setConnecting(true); + try { + await actions.accept(callId, card); + } catch (err) { + console.log(err); + setAlert(true); + } + setConnecting(false); + } + } + const alertParams = { title: state.strings.operationFailed, prompt: state.strings.tryAgain, @@ -98,17 +112,40 @@ export function Calling({ callCard }: { callCard: string }) { } }, [callCard]); + const calls = state.calls.map((contact, index) => { + const { callId, card } = contact; + const { name, handle, node, imageUrl } = card; + const ignore = {}} /> + const decline = {}} /> + const accept = actions.accept(callId, card)} /> + return ( + + + + ) + }); + const overlap = (width + 128) > height; const frameWidth = width > height ? height : width - 16; const frameHeight = frameWidth; - const frameOffset = (height - frameHeight) / 8; + const frameOffset = (height - frameHeight) / 4; return ( - 0 || alert) ? styles.active : styles.inactive}> - - { connecting && !state.calling && ( + 0 || alert) ? styles.active : styles.inactive}> + { state.calls.length > 0 && !connecting && !state.calling && ( + + + + { calls } + + + )} + { connecting && !state.calling && ( + - )} - { state.calling && ( + + )} + { state.calling && ( + )} - )} - { state.calling && state.loaded && ( - - - { state.calling.name && ( - { state.calling.name } - )} - { !state.calling.name && ( - { `${state.calling.handle}/${state.calling.node}` } - )} - + + )} + { state.calling && state.loaded && ( + + + { state.calling.name && ( + { state.calling.name } + )} + { !state.calling.name && ( + { `${state.calling.handle}/${state.calling.node}` } + )} - )} - { state.calling && state.loaded && state.remote && ( - - )} - { state.calling && state.loaded && state.local && !state.remote && ( - - )} - { state.calling && state.loaded && state.local && state.remote && ( - - )} - { state.calling && state.loaded && ( - - - - - - + + )} + { state.calling && state.loaded && state.remote && ( + + )} + { state.calling && state.loaded && state.local && !state.remote && ( + + )} + { state.calling && state.loaded && state.local && state.remote && ( + + )} + { state.calling && state.loaded && ( + + + + + - )} - + + )} - + ); } diff --git a/app/client/mobile/src/calling/useCalling.hook.ts b/app/client/mobile/src/calling/useCalling.hook.ts index 527393ab..c1cf7a3e 100644 --- a/app/client/mobile/src/calling/useCalling.hook.ts +++ b/app/client/mobile/src/calling/useCalling.hook.ts @@ -112,7 +112,7 @@ export function useCalling() { const linkMessage = async (message: any) => { if (call.current) { - const { peer, link, candidates, policy } = call.current; + const { peer, link, policy } = call.current; try { if (message.description) { const offer = new RTCSessionDescription(message.description); @@ -123,11 +123,13 @@ export function useCalling() { link.sendMessage({ description }); } - call.current.candidates = []; - for (const candidate of candidates) { - await peer.addIceCandidate(candidate); - }; - candidates.length = 0; + if (call.current) { + const { candidates } = call.current; + call.current.candidates = []; + for (const candidate of candidates) { + await peer.addIceCandidate(candidate); + }; + } } else if (message.candidate) { const candidate = new RTCIceCandidate(message.candidate); if (peer.remoteDescription == null) { @@ -189,6 +191,9 @@ export function useCalling() { await linkMessage(data); } else if (type === 'remote_track') { await remoteStream.current.addTrack(data, remoteStream.current); + if (data.kind === 'video') { + updateState({ remote: remoteStream.current }); + } } else if (type === 'local_track') { await peerTrack(data); } @@ -219,10 +224,6 @@ export function useCalling() { }); peerConnection.addEventListener( 'track', event => { updatePeer('remote_track', event.track); - - if (event.track.kind === 'video') { - updateState({ remote: remoteStream.current }); - } }); return peerConnection; } @@ -263,13 +264,15 @@ export function useCalling() { remoteStream.current = null; updateState({ calling: null, audio: null, video: null, local: null, remote: null }); }, - accept: async (callId: string, call: Call) => { + accept: async (callId: string, card: Card) => { if (call.current) { throw new Error('active call in progress'); } - const { cardId, node } = call; + const { cardId, node } = card; const ring = app.state.session.getRing(); +console.log("ACCEPTING"); const link = await ring.accept(cardId, callId, node); +console.log("ACCEPTED"); const ice = link.getIce(); const peer = transmit(ice); const policy = 'impolite'; @@ -277,7 +280,8 @@ export function useCalling() { call.current = { policy, peer, link, candidates }; link.setStatusListener(linkStatus); link.setMessageListener((msg) => updatePeer('message', msg)); - updateState({ calling: call.card }); + updateState({ calling: card }); +console.log("DONE"); }, call: async (cardId: string) => { if (call.current) { @@ -295,7 +299,7 @@ export function useCalling() { const candidates = []; call.current = { policy, peer, link, candidates }; link.setStatusListener(linkStatus); - link.setMessageListener(linkMessage); + link.setMessageListener((msg) => updatePeer('message', msg)); updateState({ calling: card }); }, loaded: (e) => { diff --git a/app/sdk/src/link.ts b/app/sdk/src/link.ts index ea07d935..efff6b6a 100644 --- a/app/sdk/src/link.ts +++ b/app/sdk/src/link.ts @@ -53,7 +53,13 @@ export class LinkModule implements Link { public async call(node: string, secure: boolean, token: string, cardId: string, contactNode: string, contactGuid: string, contactToken: string) { const call = await addCall(node, secure, token, cardId); - this.cleanup = () => { removeCall(node, secure, token, call.id) }; + this.cleanup = async () => { + try { + await removeCall(node, secure, token, call.id) + } catch (err) { + this.log.error(err); + } + } const { id, keepAlive, calleeToken, callerToken, ice } = call; const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(contactNode); @@ -84,7 +90,13 @@ export class LinkModule implements Link { public async join(server: string, access: string, ice: { urls: string; username: string; credential: string }[]) { this.ice = ice; const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(server); - this.cleanup = () => { removeContactCall(server, !insecure, access); } + this.cleanup = async () => { + try { + await removeContactCall(server, !insecure, access); + } catch (err) { + this.log.error(err); + } + } this.connect(access, server, !insecure); } @@ -163,8 +175,6 @@ export class LinkModule implements Link { await this.notifyMessage(message); } } catch (err) { -console.log("HERE!"); -this.log.error(err, data); this.log.error('failed to process signal message'); this.notifyStatus('error'); } diff --git a/app/sdk/src/ring.ts b/app/sdk/src/ring.ts index 3de39ae2..a899f51a 100644 --- a/app/sdk/src/ring.ts +++ b/app/sdk/src/ring.ts @@ -19,7 +19,6 @@ export class RingModule implements Ring { this.log = log; this.emitter = new EventEmitter(); this.calls = new Map(); - this.ringing = []; this.expire = null; this.closed = false; } @@ -58,20 +57,20 @@ export class RingModule implements Ring { public async accept(cardId: string, callId: string, contactNode: string): Promise { const now = (new Date()).getTime(); const id = `${cardId}:${callId}`; - const entry = this.ringing.get(id); + const entry = this.calls.get(id); if (!entry || entry.expires < now || entry.status !== 'ringing') { throw new Error('invalid ringing entry'); } entry.status = 'accepted'; this.emitRinging(); const link = new LinkModule(this.log); - await link.join(contactNode, entry.call.calleeToken, ice); + await link.join(contactNode, entry.call.calleeToken, entry.call.ice); return link; } public async ignore(cardId: stirng, callId: string): Promise { const id = `${cardId}:${callId}`; - const entry = this.ringing.get(id); + const entry = this.calls.get(id); if (!entry || entry.expires < now || entry.status !== 'ringing') { throw new Error('invalid ringing entry'); } @@ -81,7 +80,7 @@ export class RingModule implements Ring { public async decline(cardId: string, callId: string, contactNode: string): Promise { const id = `${cardId}:${callId}`; - const entry = this.ringing.get(id); + const entry = this.calls.get(id); if (!entry || entry.expires < now || entry.status !== 'ringing') { throw new Error('invalid ringing entry'); }