@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plusscommunities/pluss-core-aws",
3
- "version": "2.0.22",
3
+ "version": "2.0.23-beta.0",
4
4
  "description": "Core extension package for Pluss Communities platform",
5
5
  "scripts": {
6
6
  "betapatch": "npm version prepatch --preid=beta",