damus
damus copied to clipboard
ios app level notifications
Need to know when updates and/mentions ready for review
In app notifications seem to work as of Feb 13. Should this ticket be considered closed?
App level would be Push Notifications? I cannot get it to work. Even tho the App does ask for notifications permissions. I believe what actually is needed to get push notifications working is the Nostr relay side needs to generate the push somehow. Not sure how that can be done.

I created issue #257 in nostr-protocol/nips. This is what I suspect is needed to make Push Notifications possible. Please add/critique, etc as needed to help make this a reality!
App level would be Push Notifications?
I think you are right. In my earlier comment I misread the title of the ticket (in app notifications vs. iOS notifications).
@githubbbie @nobu-maeda we now have local notifications in Damus. Does this close this issue for you? 🙏
Hi @alltheseas. Thanks for the headsup. Do you have a reference to what's implemented?
I am not sure what App Level vs Local notification exactly refers when this issue was initially created by @githubbbie. I am more thinking about Push Notifications up to the iOS level when say, a DM is received. If this is not the right issue for that I can create a new one. @githubbbie to clarify tho
Hi @alltheseas. Thanks for the headsup. Do you have a reference to what's implemented?
I am not sure what App Level vs Local notification exactly refers when this issue was initially created by @githubbbie. I am more thinking about Push Notifications up to the iOS level when say, a DM is received. If this is not the right issue for that I can create a new one. @githubbbie to clarify tho
When user receives them local vs push notifications appear the same.
Local notifications appear when you have the app open, or minimized recently. See your settings to check local notifications configuration - e.g. for DMs, likes, zaps etc..
We don't have push notifications yet.
@alltheseas this should be in our roadmap, high priority
@alltheseas this should be in our roadmap, high priority
Push notifications are now high priority
Signal
Example methodology from Will. Script running in strfry - when see event, create notification. Build on damus relay for real time processing. E.g. go script.
cc @danieldaquino
Sprint 7 commitment @danieldaquino second highest priority after https://github.com/damus-io/damus/issues/1668
At nostrasia Rabble discusses push notifications solution for his safety & moderation focused client https://youtu.be/gOIZMMvHSZw?t=19753
Nice! Thanks for sharing @alltheseas, I will definitely take a look once I start working on this ticket
On Wed, Nov 01, 2023 at 10:56:09AM -0700, Daniel D’Aquino wrote:
Nice! Thanks for sharing @alltheseas, I will definitely take a look once I start working on this ticket
This is a different approach, which requires pulling down notes from other relays. I'm not against using it but it would start failing for DMs once we require AUTH for pulling DMs.
Currently working on this ticket!
-received a reply -received a mention -received a DM -received a zap -received a reaction (e.g. 🤙) -someone re-posted your note -someone quoted your note -someone followed you -someone added you to a user list
I wrote some code already (still not functional yet), but building is underway:
- To send push notifications I am making a strfry plugin using Deno and this policy framework: https://gitlab.com/soapbox-pub/strfry-policies/-/tree/develop?ref_type=heads
- The strfry plugin will send requests to APNs who will deliver the notifications
- I am using sqlite to keep track of who got push notifications and on which Apple devices
- I will create a Deno server endpoint that receives device token IDs from the Damus app (Cryptographically signed and associated with the pubkey) and saves that info into the sqlite database for use with push notifications
@jb55, in terms of sharing the code while it is a draft, do have any preferences in terms of whether to make it public or keep it private for now?
On Fri, Nov 03, 2023 at 08:57:08PM -0700, Daniel D’Aquino wrote:
@jb55, in terms of sharing the code while it is a draft, do have any preferences in terms of whether to make it public or keep it private for now?
I can setup a repo on damus-io, what should it be called? strfry-push-notify ?
what should it be called? strfry-push-notify?
I like it! Sounds good!
Updates on this project (What is done and what is still pending): [X] Make a plugin that connects to strfry and processes events [X] Make mechanism to keep track of which pubkeys received notifications for which events [X] Make server endpoint to keep track of device tokens [X] Implement basic mechanism for sending push notification device tokens to the server [X] Implement mechanism for sending basic notifications to the (mock) APNS server [X] Implement basic mock APNS server (For easier testing without authentication) [ ] Implement authentication on the process of sending device tokens to the server (I am thinking to simply sign the device token using the user's keypair) [ ] Generate better notification messages [ ] Notify authors of events to which the new event is a reply of (And other more complex references where the pubkey is not directly referenced in the new event) [ ] Implement authentication with APNS server on push notifications (Not yet clear from Apple Docs how to obtain auth details) [ ] Set it up on a staging server and test (Once ready) [ ] Deploy to production server (Once ready)
I was able to start Damus connected to a local relay (setup with push notifications plugin), post a new event, and get correct push notification payloads to be printed to the console of a mock APNS server.
@jb55, created the new strfry-push-notify repo so that we have a place where we can share the code!
https://github.com/damus-io/strfry-push-notify/
I will continue working on it, testing, and pushing code directly (for now), as now I have all the test environment working smoothly
Updates on this project (What is done and what is still pending): [X] Make a plugin that connects to strfry and processes events [X] Make mechanism to keep track of which pubkeys received notifications for which events [X] Make server endpoint to keep track of device tokens [X] Implement basic mechanism for sending push notification device tokens to the server [X] Implement mechanism for sending basic notifications to the (mock) APNS server [X] Implement basic mock APNS server (For easier testing without authentication) [/] Implement authentication on the process of sending device tokens to the server (I am thinking to simply sign the device token using the user's keypair) [/] Generate better notification messages [/] Notify authors of events to which the new event is a reply of (And other more complex references where the pubkey is not directly referenced in the new event) [ ] Implement authentication with APNS server on push notifications (Not yet clear from Apple Docs how to obtain auth details) [ ] Set it up on a staging server and test (Once ready) [ ] Deploy to production server (Once ready)
(/ means in progress)
I am currently focusing on improving the data in the notifications (notify on thread replies, getting profile names from relay, etc), as well as improving the structure of the project to something more object-oriented.
I tried making the device token authentication/authorization mechanism work earlier today, but I decided to leave this part for later, as for some reason I cannot get the signatures to verify correctly on the Deno side. I think this part will be essential once we have auth on DMs, but it is probably not immediately urgent. (More on this on the next comment)
@jb55, for the authentication/authorization mechanism, I am trying to make an ECDSA signature on the device token + a timestamp (using the secp256k1 curve)
I made a draft implementation here:
- Relay-side: https://github.com/danieldaquino/strfry-push-notify/commit/feecf33d40d75d9948617b2d479d997f129ca6d7
- Client-side: https://github.com/danieldaquino/damus/commit/64acb318fdbd3be5ad0c381212398b8af52c8361 (Sorry the code for this is not very clean yet, I will clean it up before sending the official patches)
I already checked that the encoding and decoding of bytes is correct, they are both using ECDSA algorithm on the secp256k1 curve, the SHA256 hash matches on both sides, and I checked that I can successfully verify the signature on the client-side. However, for some reason the signature verification always fails on the server-side. I think there is some kind of cryptographic parameter that is different 🤔
Do you have any thoughts on what I might be doing wrong?
Updates on this: I am working on these 3 items:
- Implement authentication on the process of sending device tokens to the server
- Generate better notification messages
- Notify authors of events to which the new event is a reply of (And other more complex references where the pubkey is not directly referenced in the new event)
Here are some of the things discussed in today's standup:
- For authorization/authentication: Will suggested we can use Nostr libraries to sign a Nostr event instead of using raw cryptography libraries (e.g. we can use https://github.com/jb55/nostr-js)
- For the formatting of notification content: Formatting notification messages is a non-trivial process consisting of multiple steps including: Localization of text, replacement of pubkeys with profile names, decryption of DMs. Some of these could be done on the server or the client. On the meeting we came to the conclusion that it is preferable to do most or all of this on the client side.
- The notification DB: We are using sqlite at the moment, which is the simplest way for now. However, we need to watch out for:
- Performance: How well it will perform under heavier loads
- Access by multiple processes: Likely works out of the box, but we need to watch out that there will be no corruptions or crashes arising from two processes trying to access the same DB at the same time.
- Alternatives: If one of the above becomes a real issue under heavy loads, we should consider moving to PostgreSQL, Redis, or another DB that is made for handling heavier loads.
- The notification DB schema: Will suggested we have rows for each pubkey/event_id notification status (Instead of having pubkeys and their statuses in JSON). This will improve performance by removing the JSON parsing step.
- Separate notification relay: We discussed that it might be a good idea to run this push notification as a separate relay that does not store any information, to allow the user to get push notifications in a more privacy-preserving way (e.g. if they do not want their notes stored on the Damus relay)
- For the implementation of this privacy-preserving relay, we can just use strfry with this notification-sender policy, and have it blanket reject all incoming notes. A simple but effective and elegant hack.
@jb55, I spent some time today researching into how the notification service extension works and relates to the main app.
I remember you asked me yesterday whether the apps share the same file container. I tested listing the documents folder from the extension and it seems to use a completely separate file container by default, unfortunately.
Since we want to do as much as possible on the client-side, being able to share data between the app and the extension will essential for us to be able to:
- Unfurl pubkeys into the user's display name
- Suppress notifications from profiles whom the user has muted
The good news
The good news is that I found a way for us to share data between the main app and the extension! I essentially created an "app group" which involves both the Damus app and the extension, and that caused the creation a shared file container between them. I wrote a small proof-of-concept here where I was able to see data.mdb from the extension.
Not saying that we should necessarily move Ndb data to the shared container 😅 but at least we know we can share data between them, which will be essential.
The not-so-good news
The not-so-good news is that I also found that apparently iOS only gives a maximum of about 24MB of RAM memory to the extension, so even if we could get NostrDB or other existing code to compile and run under the extension, we have to be careful not to "OOM" our extension. Source
Different ways to move forward
I believe decryption of DMs is not much a concern, but since profile display names, user mute lists, etc are only available in NostrDB (AFAIK), it is non-trivial to access that information. There are at least two different approaches to it:
- Get NostrDB fully or partially working on the extension so that I can access profile and other relevant data. For that I will need to migrate
data.mdbandlock.mdbto the new shared container. - Create some new cache files on the shared container, where we write only the data that the extension needs (e.g. pubkeys and display names, the mute list, etc), and we use those files in the extension to do notification processing.
I am leaning on checking whether NostrDB generally uses less than 20MB of RAM, and if yes, trying approach (1) first (to take maximum advantage of existing code/logic and avoid duplication), and if that fails or becomes a giant rabbit-hole, go for (2).
@jb55, do you have any preference on which approach to try first (Or any other thoughts on this)?
Hey Daniel!
responses inline.
On Sat, Nov 11, 2023 at 03:17:21PM -0800, Daniel D’Aquino wrote:
@jb55, I spent some time today researching into how the notification service extension works and relates to the main app.
I remember you asked me yesterday whether the apps share the same file container. I tested listing the documents folder from the extension and it seems to use a completely separate file container by default, unfortunately.
Since we want to do as much as possible on the client-side, being able to share data between the app and the extension will essential for us to be able to:
- Unfurl pubkeys into the user's display name
- Suppress notifications from profiles whom the user has muted
The good news
The good news is that I found a way for us to share data between the main app and the extension! I essentially created an "app group" which involves both the Damus app and the extension, and that caused the creation a shared file container between them. I wrote a small proof-of-concept here where I was able to see
data.mdbfrom the extension.Not saying that we should necessarily move Ndb data to the shared container 😅 but at least we know we can share data between them, which will be essential.
Definitely will be essential if we want profile names in push notifications. I wonder if this could be a phase-2 PR though, since it's getting pretty hairy. I would be ok with a simpler initial PR that doesn't have rendered profile names.
The not-so-good news
The not-so-good news is that I also found that apparently iOS only gives a maximum of about 24MB of RAM memory to the extension, so even if we could get NostrDB or other existing code to compile and run under the extension, we have to be careful not to "OOM" our extension.
We rely on the extended virtual memory entitlement for nostrdb to work:
https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_kernel_extended-virtual-addressing
How much actual ram is used depends on how many active pages the extension loads into memory when accessing the database. It should probably be much less than 20MB. nostrdb doesn't do any allocations when reading from the database.
Also, if these are separate processes, I'm still concerned about concurrent access and if lmdb handles that properly. I'm not sure it does since we've had crashed when multiple open handles are created to the same database in the same process. It may be fine if there isn't any concurrent access which might be the case with the extension?
Here's what chatGPT has to say:
Q: "Can you concurrently access the same lmdb database from different processes ?"
A: "Yes, you can concurrently access the same LMDB (Lightning Memory-Mapped Database) database from different processes. LMDB is designed to support concurrent read access by multiple processes, making it a good choice for scenarios where multiple processes need to access a shared database.
LMDB uses a combination of memory-mapped files and locking mechanisms to ensure data consistency and allow for concurrent access. Each process that wants to access the database must open the LMDB environment, and LMDB handles the synchronization and locking internally to ensure that multiple processes can read from the database simultaneously without corrupting the data.
However, it's important to note that while LMDB supports concurrent read access, it is not designed for concurrent write access from multiple processes. If you have multiple processes that need to write to the database, you should carefully coordinate and synchronize their write operations to avoid data corruption or conflicts. LMDB's design makes it highly efficient for read-heavy workloads but requires caution when dealing with concurrent writes.
In summary, LMDB is suitable for concurrent read access from different processes, but you should be mindful of concurrent write operations and implement proper synchronization mechanisms if needed."
Different ways to move forward
I believe decryption of DMs is not much a concern, but since profile display names, user mute lists, etc are only available in NostrDB (AFAIK), it is non-trivial to access that information. There are at least two different approaches to it:
- Get NostrDB fully or partially working on the extension so that I can access profile and other relevant data. For that I will need to migrate
data.mdbandlock.mdbto the new shared container.
What about user settings and keychain? Ideally the extension would have access to the private key as well for decrypting DMs.
- Create some new cache files on the shared container, where we write only the data that the extension needs (e.g. pubkeys and display names, the mute list, etc), and we use those files in the extension to do notification processing.
I am leaning on checking whether NostrDB generally uses less than 20MB of RAM, and if yes, trying approach (1) first (to take maximum advantage of existing code/logic and avoid duplication), and if that fails or becomes a giant rabbit-hole, go for (2).
@jb55, do you have any preference on which approach to try first (Or any other thoughts on this)?
@jb55 to @danieldaquino How might we forward damus notes to notifications relay?
Str-fry has sync mechanism in background. Does it use negative entropy (neg entropy)?
Str-fry forwards notes, or replicate notes to other str-fry instances.
New relay does not store notes.