adding calling component

This commit is contained in:
balzack 2025-01-24 15:30:22 -08:00
parent 37fd22b58b
commit 2eb6722ab1
7 changed files with 112 additions and 33 deletions

View File

@ -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);
},
}

View File

@ -397,7 +397,7 @@ function ContactsScreen({nav}) {
overlayColor: 'rgba(8,8,8,.9)',
}}>
<ContactsDrawer.Screen name="settings">{({navigation}) => (
<SettingsScreen nav={{...nav, textCard, callCard, contacts: navigation}} />
<SettingsScreen nav={{...nav, contacts: navigation}} />
)}</ContactsDrawer.Screen>
</ContactsDrawer.Navigator>
);

View File

@ -17,9 +17,9 @@ export interface Session {
}
export interface Link {
setStatusListener(ev: (status: string) => void): void;
setStatusListener(ev: (status: string) => Promise<void>): void;
clearStatusListener(): void;
setMessageListener(ev: (message: any) => void): void;
setMessageListener(ev: (message: any) => Promise<void>): void;
clearMessageListener(): void;
getIce(): { urls: string; username: string; credential: string }[];

View File

@ -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;
}

View File

@ -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 = {

View File

@ -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<void>) {
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<void>) {
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;
}
}
}

View File

@ -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);
}