Page MenuHomePhabricator

Upload/download large notification payload from the keyserver/Android NSE if its size exceeds FCM limits
ClosedPublic

Authored by marcin on Jul 19 2023, 8:29 AM.
Tags
None
Referenced Files
F3384428: D8566.id38174.diff
Thu, Nov 28, 8:59 PM
Unknown Object (File)
Thu, Nov 28, 7:11 PM
Unknown Object (File)
Thu, Nov 28, 6:02 PM
Unknown Object (File)
Thu, Nov 28, 5:11 PM
Unknown Object (File)
Tue, Nov 26, 1:43 PM
Unknown Object (File)
Tue, Nov 26, 1:18 PM
Unknown Object (File)
Tue, Nov 26, 12:52 PM
Unknown Object (File)
Tue, Nov 26, 9:27 AM
Subscribers

Details

Summary

This differential enables CommNotificationsHandler /keyserver to fetch/upload and AES-decrypt/AES-encrypt notification payload if it's size exceeds FCM limits.

Test Plan

Change NEXT_CODE_VERSION to 0 in lib/shared/version-utils.js. Build Android app (can be emulator).
Background the app. Send big notification (type 8000 * 'x' in python shell and copy the string). Ensure that the banner is displayed in Android client. Kill the keyserver and open the app. Keyserver is killed but the message is visible since we fetched it from blob service.

Diff Detail

Repository
rCOMM Comm
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotificationsBlobServiceClient.java
37 ↗(On Diff #28842)

My idea to handle blob service query failure is that the notification containing blob_hash and encryption key will contain some very short text (to ensure it is deliverable via FCM) that will be displayed instead if we can't fetch the actual blob at the moment.

An alternative to handle network/blob service downtime would be to turn this query into a WorkRequest with exponential backoff retry policy: https://developer.android.com/guide/background/persistent/getting-started/define-work#retries_backoff

native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java
110 ↗(On Diff #28842)

Why to make this call asynchronous?

On one hand FCM docs state that onMessageReceived is executed on separate thread - different than main thread: https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService. On the other thread, I had done some search through FCM code and found out it is always the same thread - subsequent onMessageReceived calls are executed sequentially on dedicated background thread. Therefore I concluded that we shouldn't block displaying small notifications immediately on some large notification being downloaded.

To avoid background execution abuse I set time limit on each blob request to the value mentioned in the docs: https://firebase.google.com/docs/cloud-messaging/android/receive#backgrounded

native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotificationsBlobServiceClient.java
37 ↗(On Diff #28842)

But I think using WorkManager and exponential backoff policy for this case a bit of over-engineering.

native/android/app/src/main/java/app/comm/android/notifications/CommAndroidNotificationsBlobServiceClient.java
23–26 ↗(On Diff #28842)
  • According to the docs, 20s is the maximum limit, but can be shorter depending on OS and other random stuff. Maybe we can decrease it to let's say 15s?
  • Other than that, can we extract this number as a private static final constant? Along with this comment
37 ↗(On Diff #28842)

My idea to handle blob service query failure is that the notification containing blob_hash and encryption key will contain some very short text (to ensure it is deliverable via FCM) that will be displayed instead if we can't fetch the actual blob at the moment.

This idea makes sense - I think we want notification to be displayed even if it's short instead of delaying its delivery until request succeeds (which may never happen).

As a user, I'd be more frustrated when received notif too late (or totally miss it) than if it was brief/vague but delivered quickly.

native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java
110 ↗(On Diff #28842)

Makes sense to me

364 ↗(On Diff #28842)

Do we have to create this object each time this function is called?

This revision is now accepted and ready to land.Jul 25 2023, 5:33 AM

Implement WIP of comm access token authentication on Android

marcin edited the test plan for this revision. (Show Details)
marcin retitled this revision from Connect to blob service from CommNotificationsHandler and download notification payload if its size exceeds FCM limit to Upload/download large notification payload from the keyserver/Android NSE if its size exceeds FCM limits.
marcin edited the test plan for this revision. (Show Details)

Renew the code.

All the comments are minor except the one about badgeOnly handling which seems confusing.

keyserver/src/push/send.js
1210–1216 ↗(On Diff #38175)

We're not supporting that code version anymore, so the condition from the original if becomes !badgeOnly || true which s equal to true. Why then we're still checking !badgeOnly? And why we're handling only badgeOnly: '0' case?

native/android/app/src/main/java/app/comm/android/commservices/CommAndroidBlobClient.java
25 ↗(On Diff #38175)

It is a good idea to describe the unit in the variable name.

34 ↗(On Diff #38175)

We don't have to specify all the exceptions when we're specifying their supertype (e.g. OutOfMemoryError is a subtype of Exception which means that it is enough to specify throws Exception). But do we really need to specify Exception?

57–63 ↗(On Diff #38175)

It would be more convenient if CommSecureStore.get was returning Optional<String>. Then we could simply call String userID = CommSecureStore.get("userID").orElse("")

native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java
291–301 ↗(On Diff #38175)

I guess that by logging out e we're giving the info about which type of exception was thrown, so we probably don't need to have a catch clause for each type of exception Log.w("COMM", "Failure when handling large notification.", e);.

This revision is now accepted and ready to land.Mar 22 2024, 9:05 AM
keyserver/src/push/send.js
1210–1216 ↗(On Diff #38175)

You are right @tomek . This if statement should be removed and badgeOnly always set to '0'. Real badge only notifs are handled in updateBadgeCount function in this file.

native/android/app/src/main/java/app/comm/android/commservices/CommAndroidBlobClient.java
34 ↗(On Diff #38175)

OkHttp library methods are throwing checked exception so throws clause is necessary.

57–63 ↗(On Diff #38175)

This is probably out of the scope of this differential.

Address Tomek suggestions

native/android/app/src/main/java/app/comm/android/commservices/CommAndroidBlobClient.java
34 ↗(On Diff #38175)

Are they declaring to throw Exception or a subtype? If only some subtypes, we can make this method declare throwing only the subtypes and not the Exception.

native/android/app/src/main/java/app/comm/android/commservices/CommAndroidBlobClient.java
34 ↗(On Diff #38175)

OkHttp methods that we use here [[ https://square.github.io/okhttp/#post-to-a-server | could throw IOException ]]. Out getAuthToken method could throw JSONException. Theoretically we can experience OutOfMemory error since we are downloading potentially large blob into memory. That said if we want to be more specific we would have to bring back the original code and remove the Exception.

keyserver/src/push/send.js
1210–1216 ↗(On Diff #38175)

We actually introduced a bug here. From my latest understanding updateBadgeCount handles user-initiated badge updates (marking thread manually as unread/read). The code above was supposed to handle the case of visual notification sent to the thread that user might have potentially muted. The badgeOnly here was based on thread subcription.

keyserver/src/push/send.js
1210–1216 ↗(On Diff #38175)

Will be fixed when we land: https://phab.comm.dev/D12539

keyserver/src/push/send.js
1210–1216 ↗(On Diff #38175)

Can you clarify what product impact this bug has? How does it affect users?

keyserver/src/push/send.js
1210–1216 ↗(On Diff #38175)

I spoke to @marcin about this in our 1:1 today. The bug is that when the keyserver should send a badge-only notif to an Android device, it accidentally sets badgeOnly to 0 and includes fields not relevant for badgeOnly notifs (the text of the notif). This results in a visual notif for the user

Since this is a user-facing issue, I've created an urgent task to track: ENG-8620