Skip to content

Commit

Permalink
feature: iOS push compat
Browse files Browse the repository at this point in the history
  • Loading branch information
lukashaertel committed Sep 6, 2024
1 parent f030b92 commit c1ee810
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 117 deletions.
3 changes: 0 additions & 3 deletions eas.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
"build": {
"development": {
"developmentClient": true,
"android": {
"gradleCommand": ":app:assembleRelease --parallel --no-daemon"
},
"distribution": "internal"
},
"preview": {
Expand Down
49 changes: 0 additions & 49 deletions src/hooks/notifications/types/NotificationTrigger.tsx

This file was deleted.

70 changes: 33 additions & 37 deletions src/hooks/notifications/useNotificationReceivedManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,11 @@ import { addNotificationReceivedListener, Notification, removeNotificationSubscr
import moment from "moment";
import { useEffect } from "react";

import { Platform } from "react-native";
import { useSynchronizer } from "../../components/sync/SynchronizationProvider";
import { conId } from "../../configuration";
import { NotificationChannels } from "../../init/NotificationChannel";
import { captureNotificationException } from "../../sentryHelpers";
import { useAppDispatch } from "../../store";
import { logFCMMessage } from "../../store/background/slice";
import { FirebaseNotificationTrigger, isTrigger, isTriggerWithData, isTriggerWithNotification } from "./types/NotificationTrigger";

const scheduleNotificationFromTrigger = (source: FirebaseNotificationTrigger, channelId: NotificationChannels = "default") =>
scheduleNotificationAsync({
identifier: source.remoteMessage.messageId ?? undefined,
content: {
title: source.remoteMessage.notification.title ?? undefined,
body: source.remoteMessage.notification.body ?? undefined,
data: source.remoteMessage.data,
},
trigger: {
channelId,
},
});

/**
* Manages the foreground part of notification handling, as well as handling sync requests
Expand All @@ -39,51 +24,62 @@ export const useNotificationReceivedManager = () => {
// Setup notification received handler.
useEffect(() => {
const receive = addNotificationReceivedListener(({ request: { content, trigger, identifier } }: Notification) => {
// Prevent reentrant error when scheduling a notification locally from a remote message.
if (!isTrigger(trigger)) {
console.log("Skipping empty message from remote");
return;
}
if (trigger?.type !== "push") return;

// Track immediately when the message came in.
const dateReceived = moment().toISOString();

// Always log receiving of the message.
console.log(`Received at ${dateReceived}:`, trigger);

// Always dispatch a state update tracking the message.
dispatch(logFCMMessage({ dateReceived, content, trigger, identifier }));
const data = (Platform.OS === "ios" ? trigger.payload : trigger.remoteMessage?.data) ?? {};
const event = data.Event;
// const cid = data.CID;
const title: string = Platform.OS === "ios" ? (trigger.payload as any).aps?.alert?.title : trigger.remoteMessage?.notification?.title;
const body: string = Platform.OS === "ios" ? (trigger.payload as any).aps?.alert?.body : trigger.remoteMessage?.notification?.body;

// Check if data trigger. Otherwise, not actionable.
if (isTriggerWithData(trigger)) {
// Get CID and event type.
const cid = trigger.remoteMessage.data.CID;
const event = trigger.remoteMessage.data.Event;
console.log("Parsed as push notification:", { event, title, body, data });

// Skip if not for this convention.
if (cid !== conId) return;
// // Check ID match.
// if (cid !== conId) return;

// Handle for sync, announcement, and notification.
if (event === "Sync") {
switch (event) {
case "Sync": {
// Is sync, do synchronization silently.
synchronize().catch(captureException);

// Log sync.
console.log("Synchronized for remote Sync request");
} else if (event === "Announcement" && isTriggerWithNotification(trigger)) {
break;
}

case "Announcement": {
// Schedule it.
scheduleNotificationFromTrigger(trigger, "announcements").then(
scheduleNotificationAsync({
content: { title, body, data },
trigger: null, // { channelId: "announcements" },
}).then(
() => console.log("Announcement scheduled"),
(e) => captureNotificationException("Unable to schedule announcement", e),
);
} else if (event === "Notification" && isTriggerWithNotification(trigger)) {
break;
}

case "Notification": {
// Schedule it.
scheduleNotificationFromTrigger(trigger, "private_messages").then(
scheduleNotificationAsync({
content: { title, body, data },
trigger: null, // { channelId: "private_messages" },
}).then(
() => console.log("Personal message scheduled"),
(e) => captureNotificationException("Unable to schedule personal message", e),
(e) => captureNotificationException("Unable to schedule announcement", e),
);
break;
}
}

// Always dispatch a state update tracking the message.
dispatch(logFCMMessage({ dateReceived, content, trigger, identifier }));
});

// Return removal of subscription.
Expand Down
12 changes: 6 additions & 6 deletions src/hooks/notifications/useNotificationRespondedManager.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { useLastNotificationResponse } from "expo-notifications";
import moment from "moment";
import { useEffect } from "react";

import { conId } from "../../configuration";
import { useAppNavigation } from "../nav/useAppNavigation";

/**
* Manages the foreground part notification response handling.
* This handles interacting with scheduled notifications from the received manager.
* @constructor
*/
export const useNotificationRespondedManager = () => {
Expand All @@ -15,6 +13,8 @@ export const useNotificationRespondedManager = () => {

// Setup handler for notification response.
useEffect(() => {
if (!response) return;

// Track when the response was observed.
const dateResponded = moment().toISOString();

Expand All @@ -23,12 +23,12 @@ export const useNotificationRespondedManager = () => {

// Get the data object. Resolve CID, type, and related ID.
const data = response?.notification?.request?.content?.data;
const cid = data?.CID;
// const cid = data?.CID;
const event = data?.Event;
const relatedId = data?.RelatedId;

// Check ID match.
if (cid !== conId) return;
// // Check ID match.
// if (cid !== conId) return;

// Event is for an announcement.
if (event === "Announcement") {
Expand Down
20 changes: 11 additions & 9 deletions src/init/BackgroundSyncGenerator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { registerTaskAsync } from "expo-notifications";
import { defineTask, TaskManagerTaskBody } from "expo-task-manager";
import { Platform } from "react-native";

import { conId } from "../configuration";
import { requestSyncFromBackground } from "../hooks/sync/useBackgroundSyncManager";
import { captureNotificationException } from "../sentryHelpers";

Expand All @@ -15,22 +14,25 @@ import { captureNotificationException } from "../sentryHelpers";
const BG_NOTIFICATIONS_NAME = "background_notifications";

// Define task for notification handling.
defineTask(BG_NOTIFICATIONS_NAME, ({ data, error, executionInfo }: TaskManagerTaskBody<{ notification?: any }>) => {
defineTask(BG_NOTIFICATIONS_NAME, (body: TaskManagerTaskBody<any>) => {
// Skip method if error was given to be handled.
if (error) {
captureEvent(error);
if (body.error) {
captureEvent(body.error);
return;
}

// Parse from proper source.
const data: Record<string, string> = (Platform.OS === "ios" ? body.data : body.data?.notification?.data) ?? {};

// Log that a notification was received.
console.log("Received data in background", data, "App state", executionInfo.appState);
console.log("Received data in background", data, "App state", body.executionInfo.appState);

// Get event data.
const cid = data?.notification?.data?.CID;
const event = data?.notification?.data?.Event;
// const cid = data.CID;
const event = data.Event;

// Skip if not for this convention.
if (cid !== conId) return;
// // Skip if not for this convention.
// if (cid !== conId) return;

// Handle for Sync events only.
if (event === "Sync") {
Expand Down
6 changes: 3 additions & 3 deletions src/init/NotificationHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import { captureNotificationException } from "../sentryHelpers";

// Set general notification handling strategy.
setNotificationHandler({
handleNotification: ({ request: { content } }) => {
handleNotification: async ({ request: { content } }) => {
// Mark handling notification.
console.log("Handling notification", content);

// Show if it's a notification trigger.
return Promise.resolve({
return {
shouldShowAlert: typeof content?.title === "string" || typeof content?.body === "string",
shouldPlaySound: false,
shouldSetBadge: false,
});
};
},
handleSuccess: (id) => {
// Log success.
Expand Down
15 changes: 5 additions & 10 deletions src/routes/pm/PmItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,16 @@ export const PmItem = () => {
}, [message, markRead]);

// If no message currently displayable, check if fetching. If not fetching
if (!message) {
if (ready) {
return null;
} else {
useEffect(() => {
if (ready && !message) {
navigation.pop();
return null;
}
}
}, [message, ready, navigation]);

return (
<ScrollView style={StyleSheet.absoluteFill} stickyHeaderIndices={[0]} stickyHeaderHiddenOnScroll>
<Header>{message.Subject}</Header>
<Floater contentStyle={appStyles.trailer}>
<MarkdownContent>{message.Message}</MarkdownContent>
</Floater>
<Header>{!message ? "Viewing message" : message.Subject}</Header>
<Floater contentStyle={appStyles.trailer}>{!message ? null : <MarkdownContent>{message.Message}</MarkdownContent>}</Floater>
</ScrollView>
);
};

0 comments on commit c1ee810

Please sign in to comment.