This differential implements native iOS code in Objective-C that can fetch data (any data - not
necessarily notif payload) from blob service. Additionally it implements first usage of this code in
NotificationService where large notification payload is downloaded from blob service, AES decrypted and
persisted in flat file and redux (if app is in the background). Finally the keyserver code that upload large notification payload to the blob service is implemented.
Details
- Build iOS app.
- Background or kill the app.
- Send large notifications to the app. I used 16000 character notifications for testing.
- Ensure notification is displayed shortly after it was sent.
- Kill your local keyserver
- Open the app. Ensure that (entire!!!) message content is available in the thread.
Diff Detail
- Repository
- rCOMM Comm
- Lint
Lint Not Applicable - Unit
Tests Not Applicable
Event Timeline
native/ios/NotificationService/NSEBlobServiceClient.mm | ||
---|---|---|
5 ↗ | (On Diff #29228) | This time limit matches the one we introduced and justified for Android. Android has 20 seconds to process notif, iOS has 30. Theoretically we could increase it to 20 but maybe it is better to keep behaviour similar between platforms? |
13 ↗ | (On Diff #29228) | Why to make this class singleton? Additionally I have done some experimentation and established that NSE uses different threads to process subsequent notifications but it uses the same process for a prolonged period of time. Once I had sent first notification to iOS device I was seeing the same PID being logged to the console for each notifications for > 15 minutes. Therefore I think it is better to create this object once end just keep it then to create it for every invocation. |
15 ↗ | (On Diff #29228) | This way of implementing singleton is inspired by our CommSecureStoreIOSWrapper. |
24 ↗ | (On Diff #29228) | Why to pass mainQueue here? According to apple docs: https://developer.apple.com/documentation/foundation/nsurlsession/1411597-sessionwithconfiguration, passing nothing makes NSURLSession instance create its own queue and thread to process request completion handlers. We don't want to create new threads when we process notif so I pass mainQueue here since it is always associated with the main thread which is always present so we don't create new threads when processing notif. |
32 ↗ | (On Diff #29228) | Why synchronous? Unfortunately I don't have any Apple docs to support it - just my own experimentation. Basically I tried to make this call async in two ways:
So my conclusion is that NSE doesn't process new notification until we call completionHandler of previous notification no matter the thread it takes place. Therefore there is no good way to make this call async in NSE. |
native/ios/NotificationService/NotificationService.mm | ||
91 ↗ | (On Diff #29228) | I made some of NSE methods static since I want to call them in a block and avoid this issue: https://bytes.vokal.io/objc-block-capture-weakself/. Those methods were stateless anyway . |
native/ios/NotificationService/NSEBlobServiceClient.mm | ||
---|---|---|
5 ↗ | (On Diff #29228) | I'd leave 15 - Shouldn't notif processing be as quick as possible? |
13 ↗ | (On Diff #29228) | Yeah, singleton seems to be good option here |
native/ios/NotificationService/NotificationService.mm | ||
13 ↗ | (On Diff #29228) | Technically I'd prefer this constant to be defined in the AES Crypto module, however I'm not sure if ObjC <-> Swift won't complicate it too much - might be not worth it |
280 ↗ | (On Diff #29228) |
|
- Remove AES constant definition and use new AESCryptoModuleObjCCompat methods
- Make AESCryptoModuleObjCCompat object static and initialized only once.
Seems reasonable, but I don't know Objective-C that well
native/ios/NotificationService/NSEBlobServiceClient.mm | ||
---|---|---|
20 ↗ | (On Diff #29438) | Do we have a task for it? We probably can mention it in the code comment. |
native/ios/NotificationService/NotificationService.mm | ||
59–61 ↗ | (On Diff #29438) | How do we get the encryptionKey? |
native/ios/NotificationService/NSEBlobServiceClient.mm | ||
---|---|---|
20 ↗ | (On Diff #29438) | This TODO section is about authentication with COMM_ACCESS_TOKEN. I was advices not to land this feature until COMM_ACCESS_TOKEN staff is resolved and utilised in this stack. So I don't think we need a task for it since authentication is a part of this task. |
native/ios/NotificationService/NotificationService.mm | ||
59–61 ↗ | (On Diff #29438) | The encryptionKey is sent by the keyserver in olm-encrypted notification. It is delivered as base 64 string an converted to bytes there since AESCryptoModule requires bytes. |
native/ios/Comm/CommIOSNotifications/CommIOSNotificationsBlobClient.mm | ||
---|---|---|
87 ↗ | (On Diff #30792) | This signature got me confused. binaryAuthObject = [NSJSONSerialization dataWithJSONObject:jsonAuthObject options:0 error:&jsonError]; // From NSJSONSerialization get data, with given JSON object, options and error |
keyserver/src/push/send.js | ||
---|---|---|
1029–1030 ↗ | (On Diff #38139) | Why is that the case? |
native/ios/Comm/CommIOSServices/CommIOSBlobClient.mm | ||
5–10 ↗ | (On Diff #38139) | Would that mean that it is impossible to use the local blob service? |
12 ↗ | (On Diff #38139) | What unit do we use here? |
Fix two errors:
- Call signal semaphore after receiving response with error code.
- Check against entire range of successful response codes - not just 200
Some nits below, but I didn't review closely.
This code deals with a lot of parallelism stuff, so we should be very careful in reviewing it. Probably same goes for the comparable Android code.
keyserver/src/push/send.js | ||
---|---|---|
1032 ↗ | (On Diff #38193) | Capitalize Notifs |
native/ios/NotificationService/NotificationService.mm | ||
889–896 ↗ | (On Diff #38193) | Seems like very narrow columns here. Can we stick with 80 chars? Also typo in instancess |
keyserver/src/push/send.js | ||
---|---|---|
1029–1030 ↗ | (On Diff #38139) | The context is here: https://linear.app/comm/issue/ENG-3797/download-notification-payload-from-blob-service-if-its-size-exceeded#comment-4d35b32c. My understanding is that it was a business decision that was made when MacOS notifications were first introduced. The reason here was probably the fact that initially MacOS notifications were handled entirely inside a main desktop process where there is no access to redux so messageInfos were simply unnecessary. Technically it would be possible to transfer messageInfos from notification to renderer proces using ipc but it wasn't implemented. Making messageInfos useful in MacOS notifications is an option that we can consider but it is out of the scope of this differential. cc @michal |
native/ios/Comm/CommIOSServices/CommIOSBlobClient.mm | ||
5–10 ↗ | (On Diff #38139) | Using local blob service will be possible after replacing one of the blobServiceAddress above. Developer willing to use local blob service has to hardcode its address anyway in lib/facts/blob-service.js. |
12 ↗ | (On Diff #38139) | Its seconds. This constant is used a couple of lines below where setting request timeout which require timeout value in seconds: https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1408259-timeoutintervalforrequest |
native/ios/Comm/CommIOSServices/CommIOSBlobClient.mm | ||
---|---|---|
20–110 ↗ | (On Diff #38193) | Completely unsolicited thought, but does this code need to be in Objective C? At a high level this seems like a LOT of potentially error-prone code just to make an HTTP request? I'm not suggesting we do it, but code to make an HTTP request in Swift would be a lot simpler thanks to eg async/await |
keyserver/src/push/send.js | ||
---|---|---|
1093–1095 ↗ | (On Diff #38193) | It's probably safer to spread first |
native/ios/Comm/CommIOSServices/CommIOSBlobClient.mm | ||
75 ↗ | (On Diff #38193) | Is it a good practice to check it like this? |
95 ↗ | (On Diff #38193) | Is it possible for some exceptions to be thrown before this call? Can we execute it in some kind of finally block? |
native/ios/Comm/CommIOSServices/CommIOSBlobClient.mm | ||
---|---|---|
20–110 ↗ | (On Diff #38193) | I will create a follow up task to rewrite it in Swift. However I don't think it is actionable right now. The purpose of this project was to refresh most of the existing code. Rewriting in swift would require to add some headers with bindings do additional testing which could delay this project further. |
75 ↗ | (On Diff #38193) | I was unable to find any instance method of NSHTTPURLResponse to check if the request was successful or not. It is pretty low level API so I think we have to check maunally. |
native/ios/Comm/CommIOSServices/CommIOSBlobClient.mm | ||
---|---|---|
20–110 ↗ | (On Diff #38193) | Eh not sure we need a task, it was more just commentary rather than something to prioritize |