From f45016365c3f9d686d30e705f3a0df54d6441e3e Mon Sep 17 00:00:00 2001 From: Vitalik Date: Wed, 27 Mar 2024 23:17:44 +0200 Subject: [PATCH] FEATURE | Call ket --- index.js | 1 + ios/taskme2.xcodeproj/project.pbxproj | 11 +- .../xcshareddata/xcschemes/taskme2.xcscheme | 2 +- ios/taskme2/AppDelegate.mm | 45 +++---- ios/taskme2/Info.plist | 1 + src/App.tsx | 17 ++- src/api/calls/requests.ts | 4 + src/config/index.ts | 2 - .../calls/core/call-events-listener.ts | 44 ------- src/modules/calls/hooks/use-call-data.hook.ts | 113 +----------------- .../calls/hooks/use-call-streams.hook.ts | 1 - src/modules/calls/screens/call/index.tsx | 22 +--- .../calls/services/call-root.service.ts | 22 ++++ .../calls/services/call-utility.service.ts | 10 ++ src/modules/calls/services/call.service.ts | 68 +++++++++++ .../calls/services/callkeep.service.ts | 78 ++++++++++++ .../calls/services/calls-events.service.ts | 77 ++++++++++++ src/modules/calls/services/index.ts | 6 + .../calls/services/peer-connection.service.ts | 66 ++++++++++ ...call-swipable-row-card.smart-component.tsx | 6 +- .../calls/widgets/incoming-call.widget.tsx | 40 +------ .../screens/contact-detail.screen.tsx | 17 ++- .../hooks/use-app-socket-listener.hook.ts | 1 - src/modules/root/index.tsx | 1 - src/services/domain/actions-queue.service.ts | 1 - src/services/system/callkeep.service.ts | 27 ----- src/services/system/index.ts | 3 +- .../system/voip-notification.service.ts | 71 ++++------- src/shared/db/index.ts | 11 +- src/shared/events/index.ts | 2 +- .../chat-message-preview-text.helper.ts | 2 +- 31 files changed, 422 insertions(+), 350 deletions(-) delete mode 100644 src/modules/calls/core/call-events-listener.ts create mode 100644 src/modules/calls/services/call-root.service.ts create mode 100644 src/modules/calls/services/call-utility.service.ts create mode 100644 src/modules/calls/services/call.service.ts create mode 100644 src/modules/calls/services/callkeep.service.ts create mode 100644 src/modules/calls/services/calls-events.service.ts create mode 100644 src/modules/calls/services/index.ts create mode 100644 src/modules/calls/services/peer-connection.service.ts delete mode 100644 src/services/system/callkeep.service.ts diff --git a/index.js b/index.js index f84bbfc..a130f28 100644 --- a/index.js +++ b/index.js @@ -16,6 +16,7 @@ import '@/services/system/skeleton-data.service' const errorHandler = (e, isFatal) => { if (isFatal) { + console.log(e) Alert.alert( 'Unexpected error occurred', ` diff --git a/ios/taskme2.xcodeproj/project.pbxproj b/ios/taskme2.xcodeproj/project.pbxproj index 7926c7a..18beb9b 100644 --- a/ios/taskme2.xcodeproj/project.pbxproj +++ b/ios/taskme2.xcodeproj/project.pbxproj @@ -334,7 +334,7 @@ CreatedOnToolsVersion = 14.3.1; }; 13B07F861A680F5B00A75B9A = { - LastSwiftMigration = 1120; + LastSwiftMigration = 1520; }; }; }; @@ -734,7 +734,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = taskme2/taskme2Stage.Release.entitlements; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 22; DEVELOPMENT_TEAM = HQ3J3TDPR2; HEADER_SEARCH_PATHS = ( "$(inherited)", @@ -854,6 +854,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_OBJC_BRIDGING_HEADER = "taskme2-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -1013,7 +1014,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = taskme2/taskme2.entitlements; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 22; DEVELOPMENT_TEAM = HQ3J3TDPR2; ENABLE_BITCODE = NO; HEADER_SEARCH_PATHS = ( @@ -1134,6 +1135,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_OBJC_BRIDGING_HEADER = "taskme2-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1150,7 +1152,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = taskme2/taskme2.entitlements; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 22; DEVELOPMENT_TEAM = HQ3J3TDPR2; HEADER_SEARCH_PATHS = ( "$(inherited)", @@ -1270,6 +1272,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_OBJC_BRIDGING_HEADER = "taskme2-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; diff --git a/ios/taskme2.xcodeproj/xcshareddata/xcschemes/taskme2.xcscheme b/ios/taskme2.xcodeproj/xcshareddata/xcschemes/taskme2.xcscheme index 3125b9a..c407e7b 100644 --- a/ios/taskme2.xcodeproj/xcshareddata/xcschemes/taskme2.xcscheme +++ b/ios/taskme2.xcodeproj/xcshareddata/xcschemes/taskme2.xcscheme @@ -59,7 +59,7 @@ "]]; - token = [token stringByReplacingOccurrencesOfString:@" " withString:@""]; - - NSLog(@"VoIP token: %@", token); - - NSDictionary *params = @{@"token": token}; + NSLog(@"pushRegistry didUpdatePushCredentials"); + [RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:type]; +} - [[NSNotificationCenter defaultCenter] postNotificationName:@"VoipTokenReceived" object:nil userInfo:params]; - [RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type]; -} -- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type -{ - // --- The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to reregister the push type. Instead, use this method to notify your server not to send push notifications using the matching push token. +// --- Handle invalidation of push token +- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type { + NSLog(@"pushRegistry didInvalidatePushTokenForType"); } // --- Handle incoming pushes - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion { - // --- NOTE: apple forced us to invoke callkit ASAP when we receive voip push - // --- see: react-native-callkeep - - // --- Retrieve information from your voip push payload NSString *uuid = payload.dictionaryPayload[@"uuid"]; - NSString *callerName = [NSString stringWithFormat:@"%@ (Connecting...)", payload.dictionaryPayload[@"callerName"]]; + NSString *callerName = payload.dictionaryPayload[@"callerName"]; NSString *handle = payload.dictionaryPayload[@"handle"]; - // --- this is optional, only required if you want to call `completion()` on the js side - [RNVoipPushNotificationManager addCompletionHandler:uuid completionHandler:completion]; + NSLog(@"Payload uuid: %@", uuid); + + // --- this is optional, only required if you want to call `completion()` on the js side + // [RNVoipPushNotificationManager addCompletionHandler:uuid completionHandler:completion]; // --- Process the received push [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type]; - // --- You should make sure to report to callkit BEFORE execute `completion()` + [RNCallKeep reportNewIncomingCall: uuid handle: handle handleType: @"generic" hasVideo: NO localizedCallerName: callerName - supportsHolding: YES - supportsDTMF: YES - supportsGrouping: YES - supportsUngrouping: YES + supportsHolding: NO + supportsDTMF: NO + supportsGrouping: NO + supportsUngrouping: NO fromPushKit: YES payload: nil withCompletionHandler: completion]; - - // --- You don't need to call it if you stored `completion()` and will call it on the js side. completion(); } diff --git a/ios/taskme2/Info.plist b/ios/taskme2/Info.plist index c522729..47b11cb 100644 --- a/ios/taskme2/Info.plist +++ b/ios/taskme2/Info.plist @@ -79,6 +79,7 @@ UIBackgroundModes + fetch remote-notification voip diff --git a/src/App.tsx b/src/App.tsx index 2f479c7..8e6df69 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,6 @@ if (__DEV__) { } import { ThemeProvider } from './shared/themes' -// import Orientation from 'react-native-orientation-locker' import { AppState, LogBox, Platform } from 'react-native' import { appService } from './services/app.service' import 'react-native-gesture-handler' @@ -18,17 +17,17 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler' import { appEvents } from './shared' import Toast from 'react-native-toast-message' import { toastConfig } from './config/toast.config' -import './services/system/callkeep.service' import { VoipNotificationsService } from './services/system' +import { callKeepService } from './modules/calls/services/callkeep.service' +import './modules/calls/services/calls-events.service' -LogBox.ignoreLogs(['Warning: ...', 'Require cycle: ...']) // Ignore log notification by message -LogBox.ignoreAllLogs() //Ignore all log notifications +LogBox.ignoreLogs(['Warning: ...', 'Require cycle: ...']) +LogBox.ignoreAllLogs() const App: FC = () => { useEffect(() => { appService.initApp() - // Orientation.lockToPortrait() }, []) useEffect(() => { @@ -48,6 +47,14 @@ const App: FC = () => { } }, []) + useEffect(() => { + // console.log(callsEventsService) + callKeepService.register() + return () => { + callKeepService.unregister() + } + }, []) + useEffect(() => { if (Platform.OS === 'ios') { VoipNotificationsService.getInstance().initListeners() diff --git a/src/api/calls/requests.ts b/src/api/calls/requests.ts index 16a8cc7..8c39f72 100644 --- a/src/api/calls/requests.ts +++ b/src/api/calls/requests.ts @@ -33,3 +33,7 @@ export const getCallsListReq = (params: any) => { export const deleteCallHistoryReq = (callId: number) => { return api.delete(`calls/history/${callId}`, null, '') } + +export const getIncomeDataReq = (callId: string) => { + return api.post(`calls/income/${callId}`, {}, {}, '') +} diff --git a/src/config/index.ts b/src/config/index.ts index fe3b1ee..a83d807 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -9,8 +9,6 @@ export const dynamicConfig = { googlePlayUrl: 'https://play.google.com/store/apps/details?id=com.app.task_me', } -console.log('config', Config) - export const config = { ...dynamicConfig, fonts, diff --git a/src/modules/calls/core/call-events-listener.ts b/src/modules/calls/core/call-events-listener.ts deleted file mode 100644 index d9e8964..0000000 --- a/src/modules/calls/core/call-events-listener.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { socketEvents } from '@/shared' -import { CallMod, callDataStoreHelper, useCallDataStore } from '../hooks' -import { RTCSessionDescription } from 'react-native-webrtc' -// import inCallManager from 'react-native-incall-manager' - -class CallEventsListenter { - constructor() { - this.init() - } - - private init() { - this.initCallAnswered() - } - - private initCallAnswered() { - socketEvents.on('call/answered', data => { - console.log('ANSWERED') - callDataStoreHelper - .peerConnection() - .setRemoteDescription( - new RTCSessionDescription(data.rtcMessage), - ) - useCallDataStore.getState().changeMod(CallMod.Speaking) - - this.proccessIceCandidates() - // inCallManager.stopRingback() - }) - } - - private proccessIceCandidates() { - const existIcecandidates = useCallDataStore.getState().icecandidates - - if (existIcecandidates.length) { - existIcecandidates.map(candidate => - useCallDataStore - .getState() - .peerConnection.addIceCandidate(candidate), - ) - useCallDataStore.getState().cleanIcecandidates() - } - } -} - -export const callEventsListener = new CallEventsListenter() diff --git a/src/modules/calls/hooks/use-call-data.hook.ts b/src/modules/calls/hooks/use-call-data.hook.ts index 5b22f06..37e312c 100644 --- a/src/modules/calls/hooks/use-call-data.hook.ts +++ b/src/modules/calls/hooks/use-call-data.hook.ts @@ -1,19 +1,5 @@ -import { MediaStream, RTCPeerConnection } from 'react-native-webrtc' +import { RTCPeerConnection } from 'react-native-webrtc' import { create } from 'zustand' -import { iceServers } from '../configs' -import { - cancelCallReq, - finishCallReq, - iceCandidateReq, -} from '@/api/calls/requests' -import { AcceptCall, StartCall } from '../core' -import { useCallFromStore } from './use-call-from.hook' -import { NavigationService } from '@/services/system' -import { RouteKey } from '@/shared' -// import InCallManager from 'react-native-incall-manager' -import { StopCall } from '../core/stop-call' -import { callsStreamsCtr, initCallsMediaDevices } from './use-call-streams.hook' -import { Alert } from 'react-native' export enum CallMod { Outgoing, @@ -95,101 +81,4 @@ export const useCallDataStore = create()(set => ({ export const callDataStoreHelper = { peerConnection: () => useCallDataStore.getState().peerConnection, - proccesIncome: async (data: any) => { - const peerConnection = await createPeerConnection() - - useCallDataStore - .getState() - .incomeCall( - data.callerId, - data.rtcMessage, - data.callId, - peerConnection, - ) - - peerConnection.setRemoteDescription( - new RTCSessionDescription(data.rtcMessage), - ) - }, - processAccept: async () => { - new AcceptCall(useCallDataStore.getState).accept() - }, - processStart: async (targetUserId: number) => { - // InCallManager.start({ media: 'audio', ringback: '_BUNDLE_' }) - try { - const peerConnection = await createPeerConnection() - useCallDataStore.getState().startCall(targetUserId, peerConnection) - NavigationService.navigate(RouteKey.Call, {}) - new StartCall( - useCallDataStore.getState, - useCallFromStore.getState, - ).start() - } catch (e) { - console.log(e) - } - }, - stop: () => { - new StopCall(useCallDataStore.getState, callsStreamsCtr).stop() - }, - finishCall: async () => { - useCallDataStore.setState(useCallDataStore.getInitialState()) - NavigationService.goBack() - }, - cancel: async () => { - await cancelCallReq({ - callId: useCallDataStore.getState().callId, - }).catch(console.log) - new StopCall(useCallDataStore.getState, callsStreamsCtr).stop() - useCallDataStore.setState(useCallDataStore.getInitialState()) - NavigationService.goBack() - }, -} - -async function createPeerConnection() { - const peerConnection = new RTCPeerConnection({ - iceServers: iceServers, - }) - await initCallsMediaDevices(peerConnection) - - peerConnection.addEventListener('track', event => { - try { - const existremoteStream = callsStreamsCtr().remoteStream - const remoteMediaStream = existremoteStream || new MediaStream() - remoteMediaStream.addTrack(event.track) - callsStreamsCtr().setRemoteStream(remoteMediaStream) - } catch (e) { - console.log(e) - } - }) - - peerConnection.addEventListener('icecandidate', event => { - if (event.candidate !== null) { - iceCandidateReq({ - targetUserId: useCallDataStore.getState().targetUserId, - candidates: [event.candidate], - }) - } - }) - - peerConnection.addEventListener('iceconnectionstatechange', event => { - console.log(peerConnection.iceConnectionState) - useCallDataStore - .getState() - .setConnectedStatus(peerConnection.iceConnectionState) - - if (peerConnection.iceConnectionState === 'closed') { - useCallDataStore.getState().changeMod(CallMod.Finished) - } - - if ( - peerConnection.iceConnectionState === 'disconnected' || - peerConnection.iceConnectionState === 'failed' - ) { - new StopCall(useCallDataStore.getState, callsStreamsCtr).stop() - } - }) - - peerConnection.addEventListener('onicegatheringstatechange', event => {}) - - return peerConnection } diff --git a/src/modules/calls/hooks/use-call-streams.hook.ts b/src/modules/calls/hooks/use-call-streams.hook.ts index d40a784..1a0ba92 100644 --- a/src/modules/calls/hooks/use-call-streams.hook.ts +++ b/src/modules/calls/hooks/use-call-streams.hook.ts @@ -1,6 +1,5 @@ import { MediaStream, mediaDevices } from 'react-native-webrtc' import { create } from 'zustand' -import { callDataStoreHelper } from './use-call-data.hook' export interface ICallStreamsStore { localStream: MediaStream diff --git a/src/modules/calls/screens/call/index.tsx b/src/modules/calls/screens/call/index.tsx index 47c62cb..941ac80 100644 --- a/src/modules/calls/screens/call/index.tsx +++ b/src/modules/calls/screens/call/index.tsx @@ -1,22 +1,15 @@ -import React, { useEffect, useRef, useState } from 'react' +import React from 'react' import { CallingAtom, OutgoingcallAtom } from './atoms' import { Button } from '@/shared' import { CallMod, - callDataStoreHelper, useCallDataStore, useCallFromStore, useCallsStream, } from '../../hooks' import { CallBackground, CallBtn } from '../../components' -import { - Alert, - Dimensions, - Modal, - StatusBar, - StyleSheet, - View, -} from 'react-native' +import { Dimensions, Modal, StatusBar, StyleSheet, View } from 'react-native' +import { callService } from '../../services' export const CallScreen = () => { const mod = useCallDataStore(s => s.mod) @@ -25,7 +18,7 @@ export const CallScreen = () => { const connectedStatus = useCallDataStore(s => s.connectedStatus) const stopCall = () => { - callDataStoreHelper.stop() + callService.stop() } const templates = { @@ -55,7 +48,7 @@ export const CallScreen = () => { [CallMod.Outgoing]: ( ), @@ -66,10 +59,7 @@ export const CallScreen = () => { needOverlay={false} showAvatar={false}> -