preparing call component

This commit is contained in:
Roland Osborne 2025-01-23 14:14:25 -08:00
parent 84e351b1f8
commit 42c4026679
11 changed files with 94 additions and 22 deletions

View File

@ -1,13 +1,19 @@
import React, { useState } from 'react';
import React, { useEffect } from 'react';
import { SafeAreaView, Modal, ScrollView, View } from 'react-native';
import { Surface, Icon, Divider, Button, IconButton, Text, TextInput} from 'react-native-paper';
import {styles} from './Calling.styled';
import {useCalling} from './useCalling.hook';
import {BlurView} from '@react-native-community/blur';
export function Calling() {
export function Calling({ callCard }: { callCard: string }) {
const { state, actions } = useCalling();
useEffect(() => {
if (callCard.cardId) {
actions.call(callCard.cardId);
}
}, [callCard]);
return (
<View style={(state.link || state.ringing.length > 0) ? styles.active : styles.inactive}>
<BlurView style={styles.blur} blurType="dark" blurAmount={2} reducedTransparencyFallbackColor="dark" />

View File

@ -16,8 +16,6 @@ export function useCalling() {
useEffect(() => {
if (app.state.session) {
const setRinging = (ringing: { cardId: string, callId: string }[]) => {
console.log(">>>> ", ringing);
updateState({ ringing });
}
const ring = app.state.session.getRing();
@ -29,6 +27,9 @@ console.log(">>>> ", ringing);
}, [app.state.session]);
const actions = {
call: (cardId: string) => {
console.log('calling: ', cardId);
},
}
return { state, actions }

View File

@ -33,19 +33,24 @@ export function Session() {
const scheme = useColorScheme();
const [tab, setTab] = useState('content');
const [textCard, setTextCard] = useState({ cardId: null} as {cardId: null|string});
const [callCard, setCallCard] = useState({ cardId: null} as {cardId: null|string});
const [dismissed, setDismissed] = useState(false);
const [disconnected, setDisconnected] = useState(false);
const [showDisconnected, setShowDisconnected] = useState(false);
const sessionNav = {strings: state.strings};
const showContent = {display: tab === 'content' ? 'flex' : 'none'};
const showContact = {display: tab === 'contacts' ? 'flex' : 'none'};
const showSettings = {display: tab === 'settings' ? 'flex' : 'none'};
const textContact = (cardId: null|string) => {
setTextCard({ cardId });
}
const callContact = (cardId: null|string) => {
setCallCard({ cardId });
}
const sessionNav = {strings: state.strings, callContact, callCard, textContact, textCard};
const showContent = {display: tab === 'content' ? 'flex' : 'none'};
const showContact = {display: tab === 'contacts' ? 'flex' : 'none'};
const showSettings = {display: tab === 'settings' ? 'flex' : 'none'};
const dismiss = () => {
setDismissed(true);
setTimeout(() => {
@ -89,7 +94,7 @@ export function Session() {
...styles.body,
...showContact,
}}>
<ContactTab textContact={textContact} scheme={scheme} />
<ContactTab textContact={textContact} callContact={callContact} scheme={scheme} />
</View>
<View
style={{
@ -190,7 +195,7 @@ export function Session() {
</Surface>
</View>
)}
<Calling />
<Calling callCard={callCard} />
</View>
);
}
@ -226,7 +231,7 @@ function ContentTab({scheme, textCard, contentTab}: {scheme: string, textCard: {
);
}
function ContactTab({scheme, textContact}: {scheme: string, textContact: (cardId: null|string)=>void}) {
function ContactTab({scheme, textContact, callContact}: {scheme: string, textContact: (cardId: string)=>void, callContact: (cardId: string)=>void}) {
const [contactParams, setContactParams] = useState({
guid: '',
} as ContactParams);
@ -244,7 +249,7 @@ function ContactTab({scheme, textContact}: {scheme: string, textContact: (cardId
setContactParams(params);
props.navigation.navigate('profile');
}}
callContact={(cardId: string)=>console.log("CALL: ", cardId)}
callContact={callContact}
textContact={textContact}
/>
)}
@ -364,7 +369,6 @@ function RegistryScreen({nav}) {
}
function ContactsScreen({nav}) {
const [textCard, setTextCard] = useState({ cardId: null} as {cardId: null|string});
const ContactsComponent = useCallback(
() => (
<Surface elevation={3}>
@ -373,8 +377,8 @@ function ContactsScreen({nav}) {
openContact={(params: ContactParams) => {
nav.openContact(params, nav.profile.openDrawer);
}}
callContact={(cardId: string)=>console.log('CALL: ', cardId)}
textContact={(cardId: null|string)=>setTextCard({ cardId })}
callContact={nav.callContact}
textContact={nav.textContact}
/>
</Surface>
),
@ -393,7 +397,7 @@ function ContactsScreen({nav}) {
overlayColor: 'rgba(8,8,8,.9)',
}}>
<ContactsDrawer.Screen name="settings">{({navigation}) => (
<SettingsScreen nav={{...nav, textCard, contacts: navigation}} />
<SettingsScreen nav={{...nav, textCard, callCard, contacts: navigation}} />
)}</ContactsDrawer.Screen>
</ContactsDrawer.Navigator>
);
@ -428,8 +432,6 @@ function SettingsScreen({nav}) {
}
function HomeScreen({nav}) {
const [textCard, setTextCard] = useState({ cardId: null} as {cardId: null|string});
return (
<View style={styles.frame}>
<View style={styles.left}>

View File

@ -249,6 +249,15 @@ export const defaultProfileEntity = {
node: '',
};
export type Calling = {
id: string;
cardId: string;
callerToken: string;
calleeToken: string;
keepAlive: number;
ice: { urls: string; username: string; credential: string }[];
}
export type Ringing = {
cardId: string;
callId: string;

View File

@ -21,6 +21,7 @@ export class LinkModule implements Link {
private secure: boolean;
private token: string;
private ice: { urls: string; username: string; credential: string }[];
private cleanup: null | (()=>void);
constructor(log: Logging) {
this.log = log;
@ -36,6 +37,7 @@ export class LinkModule implements Link {
this.aliveInterval = null;
this.ringInterval = null;
this.ice = [];
this.cleanup = null;
}
public getIce(): { urls: string; username: string; credential: string }[] {
@ -44,6 +46,7 @@ export class LinkModule implements Link {
public async call(node: string, secure: boolean, token: string, cardId: string, contactNode: string, contactToken: string) {
const call = await addCall(node, secure, token, cardId);
this.cleanup = () => { removeCall(node, secure, token, call.id };
const { id, keepAlive, calleeToken, callerToken, ice } = call;
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(contactNode);
@ -74,6 +77,7 @@ 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); }
connect(access, server, !insecure);
}
@ -103,6 +107,13 @@ export class LinkModule implements Link {
if (this.websocket) {
this.websocket.close();
}
if (this.cleanup) {
try {
this.cleanup();
} catch (err) {
this.log.error(err);
}
}
}
public setStatusListener(listener: (status: string) => void) {

View File

@ -0,0 +1,10 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
import { Calling } from '../entities';
export async function addCall(node: string, secure: boolean, token: string, cardId: string): Promise<Calling> {
const endpoint = `http${secure ? 's' : ''}://${node}/talk/calls?agent=${token}`;
const call = await fetchWithTimeout(endpoint, { method: 'POST', body: JSON.stringify(cardId) });
checkResponse(call.status);
return await call.json();
}

View File

@ -0,0 +1,9 @@
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}';
const { status } = await fetchWithTimeout(endpoint, { method: 'POST', body: JSON.stringify(ringing) });
checkResponse(status);
}

View File

@ -0,0 +1,8 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function keepCall(node: string, secure: boolean, token: string, callId: string): Promise<void> {
const endpoint = `http${secure ? 's' : ''}://${node}/talk/calls/${callId}?agent=${token}`;
const { status } = await fetchWithTimeout(endpoint, { method: 'PUT' });
checkResponse(status);
}

View File

@ -0,0 +1,8 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function removeCall(node: string, secure: boolean, token: string, callId: string): Promise<void> {
const endpoint = `http${secure ? 's' : ''}://${node}/talk/calls/${callId}?agent=${token}`;
const { status } = await fetchWithTimeout(endpoint, { method: 'DELETE' });
checkResponse(status);
}

View File

@ -0,0 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function removeContactCall(server: string, secure: boolean, token: string, callId: string): Promise<void> {
const endpoint = `http${secure ? 's' : ''}://${server}/talk/calls/${callId}?contact=${token}`;
const { status } = await fetchWithTimeout(endpoint, { method: 'DELETE' });
checkResponse(status);
}

View File

@ -84,14 +84,15 @@ export class RingModule implements Ring {
if (!entry || entry.expires < now || entry.status !== 'ringing') {
throw new Error('invalid ringing entry');
}
entry.status = 'declined';
this.emitRinging();
try {
await removeContactCall(contactNode, entry.call.calleeToken, entry.call.callId);
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(contactNode);
await removeContactCall(contactNode, !insecure, entry.call.calleeToken, entry.call.callId);
}
catch (err) {
console.log(err);
}
entry.status = 'declined';
this.emitRinging();
}
public close(): void {