From 2eb6722ab17dd9c332f8e4ed1ed278987d38ac68 Mon Sep 17 00:00:00 2001 From: balzack Date: Fri, 24 Jan 2025 15:30:22 -0800 Subject: [PATCH] adding calling component --- .../mobile/src/calling/useCalling.hook.ts | 80 ++++++++++++++++++- app/client/mobile/src/session/Session.tsx | 2 +- app/sdk/src/api.ts | 4 +- app/sdk/src/contact.ts | 3 +- app/sdk/src/entities.ts | 7 +- app/sdk/src/link.ts | 43 +++++----- app/sdk/src/net/addContactRing.ts | 6 +- 7 files changed, 112 insertions(+), 33 deletions(-) diff --git a/app/client/mobile/src/calling/useCalling.hook.ts b/app/client/mobile/src/calling/useCalling.hook.ts index 2fc2919e..aac0a750 100644 --- a/app/client/mobile/src/calling/useCalling.hook.ts +++ b/app/client/mobile/src/calling/useCalling.hook.ts @@ -1,15 +1,31 @@ -import { useState, useContext, useEffect } from 'react' +import { useState, useContext, useEffect, useRef } from 'react' import { DisplayContext } from '../context/DisplayContext'; import { AppContext } from '../context/AppContext' import { ContextType } from '../context/ContextType' +import { Link } from 'databag-client-sdk'; + +import { + ScreenCapturePickerView, + RTCPeerConnection, + RTCIceCandidate, + RTCSessionDescription, + RTCView, + MediaStream, + MediaStreamTrack, + mediaDevices, + registerGlobals +} from 'react-native-webrtc'; export function useCalling() { const app = useContext(AppContext) as ContextType; const display = useContext(DisplayContext) as ContextType; + const call = useRef(null as { peerConnection: RTCPeerConnection, signalLink: Link } | null); const [state, setState] = useState({ strings: {}, ringing: [], + calls: [], cards: [], + calling: false, }) // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -17,6 +33,13 @@ export function useCalling() { setState((s) => ({ ...s, ...value })) } + useEffect(() => { + const calls = state.ringing + .map(ring => ({ callId: ring.callId, card: state.cards.find(card => ring.cardId === card.cardId) }) ) + .filter(ring => (ring.card && !ring.card.blocked)); + updateState({ calls }); + }, [state.ringing, state.cards]); + useEffect(() => { const { strings } = display.state; updateState({ strings }); @@ -43,8 +66,63 @@ export function useCalling() { const actions = { call: async (cardId: string) => { + if (call.current) { + throw new Error('active call in proegress'); + } + const contact = app.state.session.getContact(); const link = await contact.callCard(cardId); + const ice = link.getIce(); + + const peerConnection = new RTCPeerConnection({ iceServers: ice }); + peerConnection.addEventListener( 'connectionstatechange', event => { + console.log("CONNECTION STATE", event); + } ); + peerConnection.addEventListener( 'icecandidate', event => { + console.log("ICE CANDIDATE", event); + } ); + peerConnection.addEventListener( 'icecandidateerror', event => { + console.log("ICE ERROR"); + } ); + peerConnection.addEventListener( 'iceconnectionstatechange', event => { + console.log("ICE STATE CHANGE", event); + } ); + peerConnection.addEventListener( 'negotiationneeded', async (ev) => { + console.log("ICE NEGOTIATION NEEDEED"); + } ); + peerConnection.addEventListener( 'signalingstatechange', event => { + console.log("ICE SIGNALING", event); + } ); + peerConnection.addEventListener( 'track', event => { + console.log("TRACK EVENT"); + } ); + + link.setStatusListener(async (status: string) => { + if (status === 'connected') { + try { + const stream = await mediaDevices.getUserMedia({ + audio: true, + video: { + frameRate: 30, + facingMode: 'user' + } + }); + for (const track of stream.getTracks()) { + if (track.kind === 'audio') { + peerConnection.addTrack(track, stream); + } + if (track.kind === 'video') { + track.enabled = false; + } + } + } catch (err) { + console.log(err); + } + } + }); + link.setMessageListener(async (message: any) => { + // relay ice config + }); console.log(link); }, } diff --git a/app/client/mobile/src/session/Session.tsx b/app/client/mobile/src/session/Session.tsx index 16cf4a29..1d065b1d 100644 --- a/app/client/mobile/src/session/Session.tsx +++ b/app/client/mobile/src/session/Session.tsx @@ -397,7 +397,7 @@ function ContactsScreen({nav}) { overlayColor: 'rgba(8,8,8,.9)', }}> {({navigation}) => ( - + )} ); diff --git a/app/sdk/src/api.ts b/app/sdk/src/api.ts index c319664a..62fe940f 100644 --- a/app/sdk/src/api.ts +++ b/app/sdk/src/api.ts @@ -17,9 +17,9 @@ export interface Session { } export interface Link { - setStatusListener(ev: (status: string) => void): void; + setStatusListener(ev: (status: string) => Promise): void; clearStatusListener(): void; - setMessageListener(ev: (message: any) => void): void; + setMessageListener(ev: (message: any) => Promise): void; clearMessageListener(): void; getIce(): { urls: string; username: string; credential: string }[]; diff --git a/app/sdk/src/contact.ts b/app/sdk/src/contact.ts index c1c6873f..c7b997ae 100644 --- a/app/sdk/src/contact.ts +++ b/app/sdk/src/contact.ts @@ -854,8 +854,7 @@ export class ContactModule implements Contact { } const { profile, detail } = entry.item; const link = new LinkModule(this.log); -console.log("LINK CALL"); - await link.call(node, secure, token, cardId, profile.node, detail.token); + await link.call(node, secure, token, cardId, profile.node, profile.guid, detail.token); return link; } diff --git a/app/sdk/src/entities.ts b/app/sdk/src/entities.ts index c3f4ffb6..88aafdc5 100644 --- a/app/sdk/src/entities.ts +++ b/app/sdk/src/entities.ts @@ -255,14 +255,17 @@ export type Calling = { callerToken: string; calleeToken: string; keepAlive: number; - ice: { urls: string; username: string; credential: string }[]; + ice: { urls: string[]; username: string; credential: string }[]; } export type Ringing = { cardId: string; callId: string; calleeToken: string; - ice: { urls: string; username: string; credential: string }[]; + ice: { urls: string[]; username: string; credential: string }[]; + iceUrl: string; + iceUsername: string; + icePassword: string; }; export type Revision = { diff --git a/app/sdk/src/link.ts b/app/sdk/src/link.ts index ac38c6e8..b12f0506 100644 --- a/app/sdk/src/link.ts +++ b/app/sdk/src/link.ts @@ -36,6 +36,7 @@ export class LinkModule implements Link { this.status = 'idle'; this.error = false; this.closed = false; + this.connected = false; this.notifying = false; this.websocket = null; this.staleInterval = null; @@ -49,18 +50,15 @@ export class LinkModule implements Link { return this.ice; } - public async call(node: string, secure: boolean, token: string, cardId: string, contactNode: string, contactToken: string) { -console.log('add call'); + 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) }; -console.log('add ring', contactNode); const { id, keepAlive, calleeToken, callerToken, ice } = call; const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(contactNode); - const ring = { index: 0, callId: id, calleeToken, ice }; - await addContactRing(contactNode, !insecure, contactToken, ring); + const ring = { index: 0, callId: id, calleeToken, ice: JSON.parse(JSON.stringify(ice))}; + await addContactRing(contactNode, !insecure, contactGuid, contactToken, ring); -console.log('go'); this.aliveInterval = setInterval(async () => { try { await keepCall(node, secure, token, id); @@ -72,25 +70,25 @@ console.log('go'); this.ringInterval = setInterval(async () => { try { ring.index += 1; - await addContactRing(contactNode, !insecure, contactToken, ring); + await addContactRing(contactNode, !insecure, contactGuid, contactToken, ring); } catch (err) { this.log.error(err); } }, RING_INTERVAL); this.ice = ice; - connect(callerToken, node, secure); + this.connect(callerToken, node, secure); } 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); } - connect(access, server, !insecure); + this.connect(access, server, !insecure); } private connect(token: string, node: string, secure: boolean) { - this.websocket = this.setWebSocket(token, node, secure, ice); + this.websocket = this.setWebSocket(token, node, secure); this.staleInterval = setInterval(() => { if (this.websocket?.readyState == 1) { this.websocket.ping?.(); // not defined in browser @@ -124,7 +122,7 @@ console.log('go'); } } - public setStatusListener(listener: (status: string) => void) { + public setStatusListener(listener: (status: string) => Promise) { this.statusListener = listener; this.notifyStatus(this.status); } @@ -132,7 +130,7 @@ console.log('go'); this.statusListener = null; } - public setMessageListener(ev: (message: any) => void) { + public setMessageListener(listener: (message: any) => Promise) { this.messageListener = listener; } public clearMessageListener() { @@ -153,14 +151,14 @@ console.log('go'); while(this.messages.length > 0 && !this.error && !this.closed) { const data = this.messages.shift(); try { - const message = JSON.parse(daata); + const message = JSON.parse(data); if (message.status) { await this.notifyStatus(message.status); } else { await this.notifyMessage(message); } } catch (err) { - this.log('failed to process signal message'); + this.log.error('failed to process signal message'); this.notifyStatus('error'); } } @@ -169,18 +167,21 @@ console.log('go'); } private async notifyStatus(status: string) { - if (status === 'connected' && this.ringInterval) { - clearInterval(this.ringInterval); - this.ringInterval = null; - } - try { this.status = status; if (this.statusListener) { - await this.statusListner(status); + await this.statusListener(this.connected && status === 'connected' ? 'reconnected' : status); } } catch (err) { - this.log('status notification failed'); + this.log.error('status notification failed'); + } + + if (status === 'connected') { + this.connected = true; + if (this.ringInterval) { + clearInterval(this.ringInterval); + this.ringInterval = null; + } } } diff --git a/app/sdk/src/net/addContactRing.ts b/app/sdk/src/net/addContactRing.ts index 91d4b9b6..034c9408 100644 --- a/app/sdk/src/net/addContactRing.ts +++ b/app/sdk/src/net/addContactRing.ts @@ -1,10 +1,8 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { Ringing } from '../entities'; -export async function addContactRing(server: string, secure: boolean, token: string, ringing: Ringing) { - const endpoint = `http${secure ? 's' : '' }://${server}/talk/rings/?contact=${token}`; -console.log(endpoint); - +export async function addContactRing(server: string, secure: boolean, guid: string, token: string, ringing: Ringing) { + const endpoint = `http${secure ? 's' : '' }://${server}/talk/rings?contact=${guid}.${token}`; const { status } = await fetchWithTimeout(endpoint, { method: 'POST', body: JSON.stringify(ringing) }); checkResponse(status); }