Fix iOS Push notification crashes (#7237)

This commit is contained in:
Elias Nahum
2023-03-30 09:26:56 -03:00
committed by GitHub
parent 6472ce1247
commit 05469207d7
8 changed files with 42 additions and 34 deletions

View File

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import Emm from '@mattermost/react-native-emm';
import {Alert, DeviceEventEmitter, Linking, Platform} from 'react-native';
import {Alert, AppState, DeviceEventEmitter, Linking, Platform} from 'react-native';
import {Notifications} from 'react-native-notifications';
import {appEntry, pushNotificationEntry, upgradeEntry} from '@actions/remote/entry';
@@ -45,7 +45,8 @@ export const initialLaunch = async () => {
return launchAppFromNotification(convertToNotificationData(notification!), true);
}
return launchApp({launchType: Launch.Normal, coldStart: notification ? tapped : true});
const coldStart = notification ? (tapped || AppState.currentState === 'active') : true;
return launchApp({launchType: Launch.Normal, coldStart});
};
const launchAppFromDeepLink = async (deepLinkUrl: string, coldStart = false) => {

View File

@@ -25,7 +25,7 @@ import NativeNotifications from '@notifications';
import {getServerDisplayName} from '@queries/app/servers';
import {getCurrentChannelId} from '@queries/servers/system';
import {getIsCRTEnabled, getThreadById} from '@queries/servers/thread';
import {dismissOverlay, showOverlay} from '@screens/navigation';
import {showOverlay} from '@screens/navigation';
import EphemeralStore from '@store/ephemeral_store';
import NavigationStore from '@store/navigation_store';
import {isBetaApp} from '@utils/general';
@@ -139,6 +139,7 @@ class PushNotifications {
const condition3 = isInThreadScreen && !isSameThreadNotification;
if (condition1 || condition2 || condition3) {
// Dismiss the screen if it's already visible or else it blocks the navigation
DeviceEventEmitter.emit(Navigation.NAVIGATION_SHOW_OVERLAY);
const screen = Screens.IN_APP_NOTIFICATION;
@@ -148,9 +149,6 @@ class PushNotifications {
serverUrl,
};
// Dismiss the screen if it's already visible or else it blocks the navigation
await dismissOverlay(screen);
showOverlay(screen, passProps);
}
}

View File

@@ -30,6 +30,7 @@ type InAppNotificationProps = {
}
const AUTO_DISMISS_TIME_MILLIS = 5000;
const noInsets = {top: 0, bottom: 0, left: 0, right: 0};
const styles = StyleSheet.create({
container: {
@@ -69,11 +70,10 @@ const InAppNotification = ({componentId, serverName, serverUrl, notification}: I
const dismissTimerRef = useRef<NodeJS.Timeout | null>(null);
const initial = useSharedValue(-130);
const isTablet = useIsTablet();
let insets = {top: 0};
if (Platform.OS === 'ios') {
let insets = useSafeAreaInsets();
if (Platform.OS === 'android') {
// on Android we disable the safe area provider as it conflicts with the gesture system
// eslint-disable-next-line react-hooks/rules-of-hooks
insets = useSafeAreaInsets();
insets = noInsets;
}
const tapped = useRef<boolean>(false);

View File

@@ -15,7 +15,7 @@ extension Network {
// remove existing users in the database
users = [User]()
let storedUserIds = Database.default.queryUsers(byIds: userIds, forServerUrl: serverUrl)
if !(userIds.filter{ !storedUserIds.contains($0) }).isEmpty {
if !(userIds.subtracting(storedUserIds)).isEmpty {
group.enter()
DispatchQueue.global(qos: .default).async {
self.fetchUsers(byIds: Array(userIds), forServerUrl: serverUrl) {data, response, error in
@@ -29,7 +29,7 @@ extension Network {
}
let storedUsernames = Database.default.queryUsers(byUsernames: usernames, forServerUrl: serverUrl)
if !(usernames.filter{ !storedUsernames.contains($0) }).isEmpty {
if !(usernames.subtracting(storedUsernames)).isEmpty {
group.enter()
DispatchQueue.global(qos: .default).async {
self.fetchUsers(byUsernames: Array(usernames), forServerUrl: serverUrl) {data, response, error in

View File

@@ -6,16 +6,19 @@ extension PushNotification {
var updatedAt: Double = 0
func processResponse(data: Data?, response: URLResponse?, error: Error?) {
if let httpResponse = response as? HTTPURLResponse {
if (httpResponse.statusCode == 200 && error == nil) {
let statusCode = httpResponse.statusCode
let errorMessage = error?.localizedDescription ?? ""
if (statusCode == 200 && error == nil) {
ImageCache.default.insertImage(data, for: senderId, updatedAt: updatedAt, forServer: serverUrl)
completionHandler(data)
} else {
os_log(
OSLogType.default,
"Mattermost Notifications: Request for profile image failed with status %{public}@ and error %{public}@",
httpResponse.statusCode,
(error?.localizedDescription ?? "")
String(statusCode),
errorMessage
)
completionHandler(nil)
}
}
}

View File

@@ -73,16 +73,19 @@ public class PushNotification: NSObject {
let url = Network.default.buildApiUrl(ackNotification.serverUrl, endpoint)
Network.default.request(
url, withMethod: "POST", withBody: jsonData,
andHeaders: headers, forServerUrl: ackNotification.serverUrl) { data, response, error in
if (error != nil && ackNotification.isIdLoaded) {
let backoffInSeconds = self.fibonacciBackoffsInSeconds[self.retryIndex]
self.retryIndex += 1
andHeaders: headers, forServerUrl: ackNotification.serverUrl) {[weak self] data, response, error in
if error != nil && ackNotification.isIdLoaded,
let fibonacciBackoffsInSeconds = self?.fibonacciBackoffsInSeconds,
let retryIndex = self?.retryIndex,
fibonacciBackoffsInSeconds.count > retryIndex {
let backoffInSeconds = fibonacciBackoffsInSeconds[retryIndex]
self?.retryIndex += 1
DispatchQueue.main.asyncAfter(deadline: .now() + backoffInSeconds, execute: {[weak self] in
os_log(
OSLogType.default,
"Mattermost Notifications: receipt retrieval failed. Retry %{public}@",
String(describing: self?.retryIndex)
String(retryIndex)
)
self?.postNotificationReceiptWithRetry(ackNotification, completionHandler: completionHandler)
})
@@ -94,6 +97,7 @@ public class PushNotification: NSObject {
}
} catch {
os_log(OSLogType.default, "Mattermost Notifications: receipt failed %{public}@", error.localizedDescription)
completionHandler(nil)
}
}

View File

@@ -19,7 +19,7 @@ extension Database {
SELECT SUM(my.mentions_count) \
FROM MyChannel my \
INNER JOIN MyChannelSettings mys ON mys.id=my.id \
INNER JOIN Channel c ON c.id=my.id \
INNER JOIN Channel c INDEXED BY sqlite_autoindex_Channel_1 ON c.id=my.id \
WHERE c.delete_at = 0 AND mys.notify_props NOT LIKE '%"mark_unread":"mention"%'
"""
let mentions = try? db.prepare(stmtString).scalar() as? Double
@@ -28,11 +28,12 @@ extension Database {
public func getThreadMentions(_ db: Connection) -> Int {
let stmtString = """
SELECT SUM(unread_mentions) \
FROM Thread t
INNER JOIN Post p ON t.id=p.id \
INNER JOIN Channel c ON p.channel_id=c.id
WHERE c.delete_at = 0
SELECT SUM(t.unread_mentions) \
FROM Thread t \
INNER JOIN Post p INDEXED BY Post_channel_id ON t.id=p.id \
INNER JOIN Channel c ON p.channel_id=c.id \
INNER JOIN MyChannelSettings mys ON mys.id=c.id \
WHERE c.delete_at = 0 AND mys.notify_props NOT LIKE '%"mark_unread":"mention"%'
"""
let mentions = try? db.prepare(stmtString).scalar() as? Double
return Int(mentions ?? 0)

View File

@@ -27,7 +27,7 @@ class NotificationService: UNNotificationServiceExtension {
})
} else {
bestAttemptContent.badge = Gekidou.Database.default.getTotalMentions() as NSNumber
os_log(OSLogType.default, "Mattermost Notifications: app in the foreground, no data processed. Will call sendMessageIntent")
os_log(OSLogType.default, "Mattermost Notifications: app in use, no data processed. Will call sendMessageIntent")
self?.sendMessageIntent()
}
return
@@ -55,10 +55,8 @@ class NotificationService: UNNotificationServiceExtension {
if #available(iOSApplicationExtension 15.0, *) {
let overrideUsername = notification.userInfo["override_username"] as? String
let senderId = notification.userInfo["sender_id"] as? String
let sender = overrideUsername ?? senderId
guard let serverUrl = notification.userInfo["server_url"] as? String,
let sender = sender
guard let serverUrl = notification.userInfo["server_url"] as? String
else {
os_log(OSLogType.default, "Mattermost Notifications: No intent created. will call contentHandler to present notification")
self.contentHandler?(notification)
@@ -66,10 +64,13 @@ class NotificationService: UNNotificationServiceExtension {
}
let overrideIconUrl = notification.userInfo["override_icon_url"] as? String
os_log(OSLogType.default, "Mattermost Notifications: Fetching profile Image in server %{public}@ for sender %{public}@", serverUrl, sender)
PushNotification.default.fetchProfileImageSync(serverUrl, senderId: sender, overrideIconUrl: overrideIconUrl) {[weak self] data in
self?.sendMessageIntentCompletion(data)
os_log(OSLogType.default, "Mattermost Notifications: Fetching profile Image in server %{public}@ for sender %{public}@", serverUrl, senderId ?? overrideUsername ?? "no sender is set")
if senderId != nil || overrideIconUrl != nil {
PushNotification.default.fetchProfileImageSync(serverUrl, senderId: senderId ?? "", overrideIconUrl: overrideIconUrl) {[weak self] data in
self?.sendMessageIntentCompletion(data)
}
} else {
self.sendMessageIntentCompletion(nil)
}
}
}