Skip to content

Commit dba46f0

Browse files
committed
fix(videoconf): add ios push deduplication state checks
Fixes stale duplicate notification handling on cold boot causing users to blindly navigate into ended video conference sessions infinitely. Closes #7015
1 parent 48c5fc2 commit dba46f0

3 files changed

Lines changed: 37 additions & 1 deletion

File tree

app/lib/notifications/index.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import EJSON from 'ejson';
22
import { Platform } from 'react-native';
3+
import AsyncStorage from '@react-native-async-storage/async-storage';
34

45
import { appInit } from '../../actions/app';
56
import { deepLinkingClickCallPush, deepLinkingOpen } from '../../actions/deepLinking';
@@ -16,14 +17,22 @@ interface IEjson {
1617
messageId: string;
1718
}
1819

19-
export const onNotification = (push: INotification): void => {
20+
export const onNotification = async (push: INotification): Promise<void> => {
2021
const identifier = String(push?.payload?.action?.identifier);
2122

2223
// Handle video conf notification actions (Accept/Decline buttons)
2324
if (identifier === 'ACCEPT_ACTION' || identifier === 'DECLINE_ACTION') {
2425
if (push?.payload?.ejson) {
2526
try {
2627
const notification = EJSON.parse(push.payload.ejson);
28+
const lastId = await AsyncStorage.getItem('lastProcessedVideoConfNotificationId');
29+
const currentId = push.identifier || push.payload?.notId;
30+
if (currentId && lastId === currentId) {
31+
return;
32+
}
33+
if (currentId) {
34+
await AsyncStorage.setItem('lastProcessedVideoConfNotificationId', currentId);
35+
}
2736
store.dispatch(
2837
deepLinkingClickCallPush({ ...notification, event: identifier === 'ACCEPT_ACTION' ? 'accept' : 'decline' })
2938
);
@@ -40,6 +49,14 @@ export const onNotification = (push: INotification): void => {
4049

4150
// Handle video conf notification tap (default action) - treat as accept
4251
if (notification?.notificationType === 'videoconf') {
52+
const lastId = await AsyncStorage.getItem('lastProcessedVideoConfNotificationId');
53+
const currentId = push.identifier || push.payload?.notId;
54+
if (currentId && lastId === currentId) {
55+
return;
56+
}
57+
if (currentId) {
58+
await AsyncStorage.setItem('lastProcessedVideoConfNotificationId', currentId);
59+
}
4360
store.dispatch(deepLinkingClickCallPush({ ...notification, event: 'accept' }));
4461
return;
4562
}

app/lib/notifications/videoConf/getInitialNotification.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as Notifications from 'expo-notifications';
22
import EJSON from 'ejson';
33
import { DeviceEventEmitter, Platform } from 'react-native';
4+
import AsyncStorage from '@react-native-async-storage/async-storage';
45

56
import { deepLinkingClickCallPush } from '../../../actions/deepLinking';
67
import { store } from '../../store/auxStore';
@@ -66,6 +67,14 @@ export const getInitialNotification = async (): Promise<boolean> => {
6667
if (payload.ejson) {
6768
const ejsonData = EJSON.parse(payload.ejson);
6869
if (ejsonData?.notificationType === 'videoconf') {
70+
const notificationId = notification.request.identifier;
71+
const lastId = await AsyncStorage.getItem('lastProcessedVideoConfNotificationId');
72+
if (notificationId && lastId === notificationId) {
73+
return false;
74+
}
75+
if (notificationId) {
76+
await AsyncStorage.setItem('lastProcessedVideoConfNotificationId', notificationId);
77+
}
6978
// Accept/Decline actions or default tap (treat as accept)
7079
let event = 'accept';
7180
if (actionIdentifier === 'DECLINE_ACTION') {

app/sagas/deepLinking.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { all, call, delay, put, select, take, takeLatest } from 'redux-saga/effects';
2+
import AsyncStorage from '@react-native-async-storage/async-storage';
23

34
import { shareSetParams } from '../actions/share';
45
import * as types from '../actions/actionsTypes';
@@ -245,11 +246,20 @@ const handleNavigateCallRoom = function* handleNavigateCallRoom({ params }) {
245246

246247
const handleClickCallPush = function* handleClickCallPush({ params }) {
247248
let { host } = params;
249+
const { notId } = params;
248250

249251
if (!host) {
250252
return;
251253
}
252254

255+
const lastId = yield call(AsyncStorage.getItem, 'lastProcessedVideoConfNotificationId');
256+
if (notId && lastId === notId) {
257+
return;
258+
}
259+
if (notId) {
260+
yield call(AsyncStorage.setItem, 'lastProcessedVideoConfNotificationId', notId);
261+
}
262+
253263
if (host.slice(-1) === '/') {
254264
host = host.slice(0, host.length - 1);
255265
}

0 commit comments

Comments
 (0)