@plusscommunities/pluss-core-aws 2.0.22 → 2.0.23-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const moment = require("moment");
|
|
2
|
+
const { log } = require("..");
|
|
3
|
+
const getRef = require("../../db/common/getRef");
|
|
4
|
+
const updateRef = require("../../db/common/updateRef");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param {String} rateLimitId - Id of rates to check
|
|
9
|
+
* @param {Number} timeframe - Timeframe to check for number of calls given in ms
|
|
10
|
+
* @param {Numer} limit - Number of calls allowed in the given timeframe
|
|
11
|
+
* @param {Boolean} noSave - true if this call should not be recorded. Used for when checking multiple timeframes such as X calls in a minute and X calls in a day.
|
|
12
|
+
* @returns {Boolean} true if this call is fine. false if the call should be restricted due to rate limits.
|
|
13
|
+
*/
|
|
14
|
+
module.exports = async (rateLimitId, timeframe, limit, noSave) => {
|
|
15
|
+
const logId = log("checkRateLimit", "Input", {
|
|
16
|
+
rateLimitId,
|
|
17
|
+
timeframe,
|
|
18
|
+
limit,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// get rate limit item
|
|
22
|
+
const item = await getRef("ratelimit", "Id", rateLimitId);
|
|
23
|
+
const now = moment().valueOf();
|
|
24
|
+
const minTime = now - timeframe;
|
|
25
|
+
|
|
26
|
+
const history = item && item.History ? item.History : [];
|
|
27
|
+
|
|
28
|
+
if (history) {
|
|
29
|
+
// check history for entries to determine whether limit is surpassed
|
|
30
|
+
const entriesInTimeLimit = history.filter((e) => e > minTime);
|
|
31
|
+
log(
|
|
32
|
+
"checkRateLimit",
|
|
33
|
+
"entriesInTimeLimit",
|
|
34
|
+
entriesInTimeLimit.length,
|
|
35
|
+
logId
|
|
36
|
+
);
|
|
37
|
+
if (entriesInTimeLimit.length >= limit) {
|
|
38
|
+
// Limit surpassed
|
|
39
|
+
log("checkRateLimit", "Return", false, logId);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// check whether to save the current entry - used for when checking multiple timeframes such as X calls in a minute and X calls in a day
|
|
45
|
+
if (!noSave) {
|
|
46
|
+
history.push(now);
|
|
47
|
+
updateRef("ratelimit", {
|
|
48
|
+
Id: rateLimitId,
|
|
49
|
+
LastSent: now,
|
|
50
|
+
History: history,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Limit is fine
|
|
54
|
+
log("checkRateLimit", "Return", true, logId);
|
|
55
|
+
return true;
|
|
56
|
+
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const { Expo } = require("expo-server-sdk");
|
|
2
2
|
const _ = require("lodash");
|
|
3
3
|
const moment = require("moment");
|
|
4
|
+
const crypto = require("crypto");
|
|
4
5
|
const getUser = require("../db/users/getUser");
|
|
5
6
|
const getNotificationSetting = require("../db/notifications/getNotificationSetting");
|
|
7
|
+
const checkRateLimit = require("../helper/rates/checkRateLimit");
|
|
8
|
+
const { getMultiRowId } = require("../helper");
|
|
6
9
|
const { log } = require("../helper");
|
|
7
10
|
|
|
8
11
|
const SendNotification = async (tokens, message, type, key, params, config) => {
|
|
@@ -118,6 +121,49 @@ module.exports = async (
|
|
|
118
121
|
value,
|
|
119
122
|
config,
|
|
120
123
|
});
|
|
124
|
+
|
|
125
|
+
// Notification deduplication to prevent duplicate push notifications
|
|
126
|
+
// Use a 5-minute deduplication window to prevent the same notification
|
|
127
|
+
// from being sent multiple times for the same recipients and entity
|
|
128
|
+
|
|
129
|
+
// Use MD5 hash of sorted recipients for consistent, size-limited identifier
|
|
130
|
+
const recipientHash = crypto
|
|
131
|
+
.createHash("md5")
|
|
132
|
+
.update(receiverKeys.sort().join(","))
|
|
133
|
+
.digest("hex")
|
|
134
|
+
.substring(0, 8); // 8 hex chars = 4.3 billion combinations
|
|
135
|
+
|
|
136
|
+
const notificationKey = getMultiRowId([
|
|
137
|
+
"notification",
|
|
138
|
+
recipientHash, // Fixed 8-character recipient identifier
|
|
139
|
+
type,
|
|
140
|
+
key, // Use entity ID instead of message for proper uniqueness
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
log("sendNotifications - core", "notificationKey", notificationKey, logId);
|
|
144
|
+
const canSend = await checkRateLimit(
|
|
145
|
+
notificationKey,
|
|
146
|
+
300000, // 5 minutes in milliseconds
|
|
147
|
+
1, // Allow only 1 notification in the timeframe
|
|
148
|
+
false // Save this check to prevent duplicates
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (!canSend) {
|
|
152
|
+
log(
|
|
153
|
+
"sendNotifications",
|
|
154
|
+
"duplicateSkipped",
|
|
155
|
+
{
|
|
156
|
+
notificationKey,
|
|
157
|
+
recipientHash,
|
|
158
|
+
type,
|
|
159
|
+
entityKey: key,
|
|
160
|
+
recipientCount: receiverKeys.length,
|
|
161
|
+
},
|
|
162
|
+
logId
|
|
163
|
+
);
|
|
164
|
+
return; // Skip sending duplicate notification
|
|
165
|
+
}
|
|
166
|
+
|
|
121
167
|
const tokens = [];
|
|
122
168
|
const promises = [];
|
|
123
169
|
receiverKeys.forEach((uid) => {
|
package/package.json
CHANGED