-
-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Store notifications in server side KV store #2129
Store notifications in server side KV store #2129
Conversation
…on-consistent-across-user-account
…on-consistent-across-user-account
…ficationStatus function
Passing run #2520 ↗︎
Details:
Review all test suite changes for PR #2129 ↗︎ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ready for the review!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work @Silver-IT!! Review ready!
@@ -121,3 +120,14 @@ export function isOlder (notification: Notification): boolean { | |||
export function maxAge (notification: Notification): number { | |||
return notification.read ? MAX_AGE_READ : MAX_AGE_UNREAD | |||
} | |||
|
|||
export function makeNotificationHash (notification: Object): string { | |||
const excludingKeys = ['read', 'hash', 'body'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Problem 1 - same hash for different notifications
Hmm, by excluding the body
key, is it possible that two different notifications might get hashed the same way? Looking at the templates.js
, it seems that it is possible.
But, I agree that the body
should be excluded because it could change (e.g. when someone changes their display name, and then logs back in on a new device).
So what's needed is an identifier that is both unique to the notification, and also consistent across devices (meaning it doesn't matter when the notification is sent or on what device).
When calling gi.notifications/emit
, we can pass in a variable called uniqueConsistentID
.
-
For notifications that are generated inside of contract sideEffects, we can use the
hash
parameter that is passed in to thesideEffect
. This guarantees that the same unique consistent identifier is used for these notifications. E.g. do:uniqueConsistentID: `MEMBER_ADDED.${hash}`
-
For notifications that are generated outside of sideEffects, e.g. the ones created by the periodic notifications system inside of
mainNotificationsMixin.js
, you will have to construct a unique consistent identifier yourself. One example is given below:emit ({ rootState, rootGetters }) { const period = rootGetters.groupSettings.distributionDate const groupID = /* ... code here to fetch corresponding groupID ... */ sbp('gi.notifications/emit', 'NEAR_DISTRIBUTION_END', { // WARNING: see "Problem 2" below for why createdDate can cause problems createdDate: new Date().toISOString(), groupID: rootState.currentGroupId, period, uniqueConsistentID: `NEAR_DISTRIBUTION_END.${groupID}.${period}` }) },
Problem 2 - different hash for the same notifications
As shown in the example above for NEAR_DISTRIBUTION_END
, some notifications include dynamic data in them that changes depending on when the notification is posted.
This means that if you log in on another device, the same notification will have a different hash. In this case, the offending variable is createdDate
.
So, both problems can be solved by ONLY using uniqueConsistentID
.
In fact, by requiring all notifications to have a uniqueConsistentID
, we can remove the makeNotificationHash
function entirely and only rely on the uniqueConsistentID
parameter to act as the key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by excluding the body key, is it possible that two different notifications might get hashed the same way?
Simply, the body
is the outcome of translation. 😁 So we should not include it.
For the uniqueConsistentID
, it's a good idea. And I don't think we don't need to add type
in the uniqueConsistentID
because the notification
object has its type`.
const notification = {
...template,
hash: '',
avatarUserID: template.avatarUserID || sbp('state/vuex/getters').ourIdentityContractId,
// Sets 'groupID' if this notification only pertains to a certain group.
...(template.scope === 'group' ? { groupID: data.groupID } : {}),
// Store integer timestamps rather than ISO strings here to make age comparisons easier.
timestamp: data.createdDate ? new Date(data.createdDate).getTime() : Date.now(),
type
}
notification.hash = makeNotificationHash(notification)
And for the notifications created inside the contract sideEffect, I think there are no problems with the current function makeNotificationHash
because it uses meta.createdDate
as createdDate
of notification itself.
But for the other notifications created outside of contract sideEffect, their createdDate
could differ from one device to another even though they are same notifications. So, I think we should consider only this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, I totally agree with your idea that we should use uniqueConsistentID
.
I thought you wanted to only add that field and still using makeNotificationHash
function. But if we add uniqueConsistentID
, we don't need makeNotificationHash
function anymore. 😁👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@taoeffect, while I was working on this, I noticed that we don't need uniqueConsistentID
and we can still have identifiable data to make hash using makeNotificationHash
. The only reason why we need uniqueConsistentID
was that we set createdDate
inside the notificationData
in the place outside of contract sideEffect.
sbp('gi.notifications/emit', 'NEAR_DISTRIBUTION_END', {
createdDate: new Date().toISOString(),
groupID: rootState.currentGroupId,
period: rootGetters.groupSettings.distributionDate
})
But we don't need to set the createdDate
here because we have a way to use the default value Date.now()
.
const notification = {
...template,
hash: makeNotificationHash({ ...data, type }),
avatarUserID: template.avatarUserID || sbp('state/vuex/getters').ourIdentityContractId,
// Sets 'groupID' if this notification only pertains to a certain group.
...(template.scope === 'group' ? { groupID: data.groupID } : {}),
// Store integer timestamps rather than ISO strings here to make age comparisons easier.
timestamp: data.createdDate ? new Date(data.createdDate).getTime() : Date.now(),
type
}
This means that we can easily using makeNotificationHash
by removing createdDate
in the place where we pass notificationData
.
And to be honest, I really prefer to use makeNotificationHash
because to make ID (uniqueConsistentID
) manually is something like tricky temporary solution when we don't have the better solution. We also create hash for GIMessages using the identifiable value.
hash = createCID(value)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing createdDate
from the hashed data only addresses Problem 2 in the comment I wrote above.
Problem 1 is still an issue. So I think we need to introduce uniqueConsistentID
, unless you have some other idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there is a problem (Problem 1), because notificationData contains createdDate, which is meta.createdDate, when the we create notifications in the contract sideEffect.
There is a problem. It is possible for notifications to contain the same data but be different notifications.
For example, PAYMENT_THANKYOU_SENT
has this:
PAYMENT_THANKYOU_SENT (data: { creatorID: string, fromMemberID: string, toMemberID: string }) {
return {
avatarUserID: data.fromMemberID,
body: L('{strong_}{name}{_strong} sent you a {strong_}thank you note{_strong} for your contribution.', {
name: `${CHATROOM_MEMBER_MENTION_SPECIAL_CHAR}${data.fromMemberID}`,
...LTags('strong')
}),
creatorID: data.fromMemberID,
icon: '',
level: 'info',
linkTo: `/payments?modal=ThankYouNoteModal&from=${data.fromMemberID}&to=${data.toMemberID}`,
scope: 'group'
}
},
This notification will be sent multiple times, and it will have the same exact hash each time it is sent between these two members.
EDIT: There may be many other such notifications as there is nothing preventing the developer from creating such notifications. OTOH, with uniqueConsistentID
the problem is explicitly considered and acknowledged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to focus on the data here, where we call the gi.notifications/emit
function.
sbp('gi.contracts/group/emitNotificationsAfterSyncing', [contractID, innerSigningContractID], [{
notificationName: 'PAYMENT_THANKYOU_SENT',
notificationData: {
createdDate: meta.createdDate,
groupID: contractID,
fromMemberID: innerSigningContractID,
toMemberID: data.toMemberID
}
}])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to generate the same hash every time:
sbp('gi.notifications/emit', 'NEW_DISTRIBUTION_PERIOD', {
groupID: rootState.currentGroupId,
creatorID: rootGetters.ourIdentityContractId,
memberType: rootGetters.ourGroupProfile.incomeDetailsType === 'pledgeAmount' ? 'pledger' : 'receiver'
})
This is going to continue to be a problem for the reasons I mentioned in the original post above.
Anyone creating these notifications is going to eventually accidentally create notifications that result in the same hash (Problem 1).
Instead of ignoring this problem and manually fixing it anyway when it comes up, I think it's much better to explicitly acknowledge the problem and require all notifications to define uniqueConsistentID
manually. You need to do it anyway with the data. By defining uniqueConsistentID
explicitly we inform all developers about this issue and force them to think about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really don't want to use uniqueConsistentID
you will have to manually perform the same checks on the data
that's passed in to gi.notifications/emit
.
Also, in this case please add a comment above the definition of gi.notifications/emit
explaining all of this.
My recommendation remains to prefer uniqueConsistentID
over makeNotificationHash
, simply because using uniqueConsistentID
forces the developer to think about this problem, whereas with makeNotificationHash
the developer is allowed to ignore it, but then passes on that burden to the reviewer who has to double-check their work and also not miss any mistakes the developer made in either running afoul of Problem 1 - same hash for different notifications or Problem 2 - different hash for the same notifications.
Either way, please document both problems 1 and 2 in a comment above gi.notifications/emit
. 🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ready for the review!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work @Silver-IT! Only a few remaining issues. Also the previous issue about the same hash has not been addressed.
… hash for NEW_DISTRIBUTION_PERIOD
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review ready!
@@ -99,7 +99,7 @@ async function startApp () { | |||
|
|||
// Used in 'chelonia/configure' hooks to emit an error notification. | |||
function errorNotification (activity: string, error: Error, message: GIMessage) { | |||
sbp('gi.notifications/emit', 'CHELONIA_ERROR', { activity, error, message }) | |||
sbp('gi.notifications/emit', 'CHELONIA_ERROR', { createdDate: new Date().toISOString(), activity, error, message }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, this notification now runs afoul of Problem 2 - different hash for the same notifications
because it uses new Date().toISOString()
.
Meaning: the same notification on different devices will have different hashes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was my intention to make all the errors independent because
- Same error could occur several times
- Even though it seems to be same, the reason could be different from one to another, from device to device
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also a request for comment above gi.notifications/emit
@@ -121,3 +120,14 @@ export function isOlder (notification: Notification): boolean { | |||
export function maxAge (notification: Notification): number { | |||
return notification.read ? MAX_AGE_READ : MAX_AGE_UNREAD | |||
} | |||
|
|||
export function makeNotificationHash (notification: Object): string { | |||
const excludingKeys = ['read', 'hash', 'body'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really don't want to use uniqueConsistentID
you will have to manually perform the same checks on the data
that's passed in to gi.notifications/emit
.
Also, in this case please add a comment above the definition of gi.notifications/emit
explaining all of this.
My recommendation remains to prefer uniqueConsistentID
over makeNotificationHash
, simply because using uniqueConsistentID
forces the developer to think about this problem, whereas with makeNotificationHash
the developer is allowed to ignore it, but then passes on that burden to the reviewer who has to double-check their work and also not miss any mistakes the developer made in either running afoul of Problem 1 - same hash for different notifications or Problem 2 - different hash for the same notifications.
Either way, please document both problems 1 and 2 in a comment above gi.notifications/emit
. 🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic work @Silver-IT!!! 🎉
Summary of Changes