Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions app/containers/NewMediaCall/CreateCall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,26 @@ import { CustomIcon } from '../CustomIcon';
import { usePeerAutocompleteStore } from '../../lib/services/voip/usePeerAutocompleteStore';
import { mediaSessionInstance } from '../../lib/services/voip/MediaSessionInstance';
import { hideActionSheetRef } from '../ActionSheet';
import { showErrorAlert } from '../../lib/methods/helpers/info';
import sharedStyles from '../../views/Styles';

export const CreateCall = () => {
const { colors } = useTheme();

const selectedPeer = usePeerAutocompleteStore(state => state.selectedPeer);

const handleCall = () => {
const handleCall = async () => {
if (!selectedPeer) {
return;
}

mediaSessionInstance.startCall(selectedPeer.value, selectedPeer.type);
hideActionSheetRef();
try {
await mediaSessionInstance.startCall(selectedPeer.value, selectedPeer.type);
hideActionSheetRef();
} catch (e) {
const message = e instanceof Error && e.message ? e.message : I18n.t('VoIP_Call_Issue');
showErrorAlert(message, I18n.t('Oops'));
}
};

const isCallDisabled = !selectedPeer;
Expand Down
24 changes: 24 additions & 0 deletions app/lib/methods/enterpriseModules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { clearEnterpriseModules, setEnterpriseModules } from '../../actions/enterpriseModules';
import { initStore } from '../store/auxStore';
import { mockedStore } from '../../reducers/mockedStore';
import { isVoipModuleAvailable } from './enterpriseModules';

describe('isVoipModuleAvailable', () => {
beforeAll(() => {
initStore(mockedStore);
});

beforeEach(() => {
mockedStore.dispatch(clearEnterpriseModules());
});

it('returns false when teams-voip is absent', () => {
mockedStore.dispatch(setEnterpriseModules(['omnichannel-mobile-enterprise']));
expect(isVoipModuleAvailable()).toBe(false);
});

it('returns true when teams-voip is present', () => {
mockedStore.dispatch(setEnterpriseModules(['teams-voip']));
expect(isVoipModuleAvailable()).toBe(true);
});
});
4 changes: 2 additions & 2 deletions app/lib/methods/enterpriseModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@ export function isOmnichannelModuleAvailable() {
}

export function isVoipModuleAvailable() {
// const { enterpriseModules } = reduxStore.getState();
return true; // enterpriseModules.includes('teams-voip');
const { enterpriseModules } = reduxStore.getState();
return enterpriseModules.includes('teams-voip');
}
5 changes: 0 additions & 5 deletions app/lib/services/restApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1095,11 +1095,6 @@ export const registerPushToken = async (): Promise<void> => {
return;
}

// TODO: voice permission check and retry to avoid race condition
if (isIOS && (!token || !voipToken)) {
return;
}

let data: TRegisterPushTokenData = {
id: await getUniqueId(),
value: '',
Expand Down
4 changes: 2 additions & 2 deletions app/lib/services/voip/MediaSessionInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,10 @@ class MediaSessionInstance {
}
};

public startCall = (userId: string, actor: CallActorType) => {
public startCall = async (userId: string, actor: CallActorType): Promise<void> => {
requestPhoneStatePermission();
console.log('[VoIP] Starting call:', userId);
this.instance?.startCall(actor, userId);
await Promise.resolve(this.instance?.startCall(actor, userId));
};

public endCall = (callId: string) => {
Expand Down
56 changes: 44 additions & 12 deletions ios/Libraries/AppDelegate+Voip.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@ import PushKit

fileprivate let voipAppDelegateLogTag = "RocketChat.AppDelegate+Voip"

/// Shared CallKit reporting for VoIP PushKit payloads (`handle` and `localizedCallerName` may differ; often both are the caller display name).
fileprivate func reportVoipIncomingCallToCallKit(
callUUID: String,
handle: String,
localizedCallerName: String,
payload: [AnyHashable: Any],
onReportComplete: @escaping () -> Void
) {
RNCallKeep.reportNewIncomingCall(
callUUID,
handle: handle,
handleType: "generic",
hasVideo: false,
localizedCallerName: localizedCallerName,
supportsHolding: true,
supportsDTMF: true,
supportsGrouping: false,
supportsUngrouping: false,
fromPushKit: true,
payload: payload,
withCompletionHandler: onReportComplete
)
}

// MARK: - PKPushRegistryDelegate

extension AppDelegate: PKPushRegistryDelegate {
Expand All @@ -22,11 +46,26 @@ extension AppDelegate: PKPushRegistryDelegate {
public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
let payloadDict = payload.dictionaryPayload

/// PushKit requires reporting to CallKit before `completion()`. For expired or unparseable payloads,
/// report a short-lived incoming call and end it so the system is not left without a CallKit update.
let reportPlaceholderCallAndEnd: (_ callUUID: String, _ displayName: String) -> Void = { callUUID, displayName in
reportVoipIncomingCallToCallKit(
callUUID: callUUID,
handle: displayName,
localizedCallerName: displayName,
payload: payloadDict,
onReportComplete: {
RNCallKeep.endCall(withUUID: callUUID, reason: 1)
completion()
}
)
}

guard let voipPayload = VoipPayload.fromDictionary(payloadDict) else {
#if DEBUG
print("[\(voipAppDelegateLogTag)] Failed to parse incoming VoIP payload: \(payloadDict)")
#endif
completion()
reportPlaceholderCallAndEnd(UUID().uuidString, "Rocket.Chat")
return
}

Expand All @@ -36,25 +75,18 @@ extension AppDelegate: PKPushRegistryDelegate {
#if DEBUG
print("[\(voipAppDelegateLogTag)] Skipping expired or invalid VoIP payload for callId: \(callId): \(voipPayload)")
#endif
completion()
reportPlaceholderCallAndEnd(callId, caller)
return
}

VoipService.prepareIncomingCall(voipPayload, storeEventsForJs: true)

RNCallKeep.reportNewIncomingCall(
callId,
reportVoipIncomingCallToCallKit(
callUUID: callId,
handle: caller,
handleType: "generic",
hasVideo: false,
localizedCallerName: caller,
supportsHolding: true,
supportsDTMF: true,
supportsGrouping: false,
supportsUngrouping: false,
fromPushKit: true,
payload: payloadDict,
withCompletionHandler: {}
onReportComplete: {}
)
completion()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
Expand Down
Loading