mirror of
https://github.com/vernu/textbee.git
synced 2026-05-19 05:46:23 -04:00
fix: handle duplicate received sms issue
This commit is contained in:
@@ -6,6 +6,7 @@ public class SMSDTO {
|
||||
private String sender;
|
||||
private String message = "";
|
||||
private long receivedAtInMillis;
|
||||
private String fingerprint;
|
||||
|
||||
private String smsId;
|
||||
private String smsBatchId;
|
||||
@@ -107,4 +108,12 @@ public class SMSDTO {
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
public void setFingerprint(String fingerprint) {
|
||||
this.fingerprint = fingerprint;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,18 @@ import com.vernu.sms.dtos.SMSDTO;
|
||||
import com.vernu.sms.helpers.SharedPreferenceHelper;
|
||||
import com.vernu.sms.workers.SMSReceivedWorker;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
public class SMSBroadcastReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "SMSBroadcastReceiver";
|
||||
// In-memory cache to prevent rapid duplicate processing (5 seconds TTL)
|
||||
private static final ConcurrentHashMap<String, Long> processedFingerprints = new ConcurrentHashMap<>();
|
||||
private static final long CACHE_TTL_MS = 5000; // 5 seconds
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@@ -60,6 +67,29 @@ public class SMSBroadcastReceiver extends BroadcastReceiver {
|
||||
// receivedSMSDTO.setMessage(receivedSMS.getMessage());
|
||||
// receivedSMSDTO.setReceivedAt(receivedSMS.getReceivedAt());
|
||||
|
||||
// Generate fingerprint for deduplication
|
||||
String fingerprint = generateFingerprint(
|
||||
receivedSMSDTO.getSender(),
|
||||
receivedSMSDTO.getMessage(),
|
||||
receivedSMSDTO.getReceivedAtInMillis()
|
||||
);
|
||||
receivedSMSDTO.setFingerprint(fingerprint);
|
||||
|
||||
// Check in-memory cache to prevent rapid duplicate processing
|
||||
long currentTime = System.currentTimeMillis();
|
||||
Long lastProcessedTime = processedFingerprints.get(fingerprint);
|
||||
|
||||
if (lastProcessedTime != null && (currentTime - lastProcessedTime) < CACHE_TTL_MS) {
|
||||
Log.d(TAG, "Duplicate SMS detected in cache, skipping: " + fingerprint);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update cache
|
||||
processedFingerprints.put(fingerprint, currentTime);
|
||||
|
||||
// Clean up old cache entries periodically
|
||||
cleanupCache(currentTime);
|
||||
|
||||
SMSReceivedWorker.enqueueWork(context, deviceId, apiKey, receivedSMSDTO);
|
||||
}
|
||||
|
||||
@@ -69,4 +99,50 @@ public class SMSBroadcastReceiver extends BroadcastReceiver {
|
||||
// appDatabase.localReceivedSMSDao().insertAll(localReceivedSMS);
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* Generate a unique fingerprint for an SMS message based on sender, message content, and timestamp
|
||||
*/
|
||||
private String generateFingerprint(String sender, String message, long timestamp) {
|
||||
try {
|
||||
String data = (sender != null ? sender : "") + "|" +
|
||||
(message != null ? message : "") + "|" +
|
||||
timestamp;
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] hashBytes = md.digest(data.getBytes("UTF-8"));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : hashBytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error generating fingerprint: " + e.getMessage());
|
||||
// Fallback to simple string concatenation if MD5 fails
|
||||
return (sender != null ? sender : "") + "_" +
|
||||
(message != null ? message : "") + "_" +
|
||||
timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old cache entries to prevent memory leaks
|
||||
*/
|
||||
private void cleanupCache(long currentTime) {
|
||||
// Only cleanup occasionally (every 100 entries processed)
|
||||
if (processedFingerprints.size() > 100) {
|
||||
Set<String> keysToRemove = new HashSet<>();
|
||||
for (String key : processedFingerprints.keySet()) {
|
||||
Long timestamp = processedFingerprints.get(key);
|
||||
if (timestamp != null && (currentTime - timestamp) > CACHE_TTL_MS) {
|
||||
keysToRemove.add(key);
|
||||
}
|
||||
}
|
||||
for (String key : keysToRemove) {
|
||||
processedFingerprints.remove(key);
|
||||
}
|
||||
Log.d(TAG, "Cleaned up " + keysToRemove.size() + " expired cache entries");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,14 @@ public class SMSStatusReceiver extends BroadcastReceiver {
|
||||
smsDTO.setErrorMessage(errorMessage);
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
|
||||
break;
|
||||
case SmsManager.RESULT_NETWORK_ERROR:
|
||||
errorMessage = "Network error";
|
||||
smsDTO.setStatus("FAILED");
|
||||
smsDTO.setFailedAtInMillis(timestamp);
|
||||
smsDTO.setErrorCode(String.valueOf(resultCode));
|
||||
smsDTO.setErrorMessage(errorMessage);
|
||||
Log.e(TAG, "SMS failed to send - ID: " + smsDTO.getSmsId() + ", Error code: " + resultCode + ", Error: " + errorMessage);
|
||||
break;
|
||||
default:
|
||||
errorMessage = "Unknown error";
|
||||
smsDTO.setStatus("FAILED");
|
||||
|
||||
@@ -94,13 +94,22 @@ public class SMSReceivedWorker extends Worker {
|
||||
.addTag("sms_received")
|
||||
.build();
|
||||
|
||||
String uniqueWorkName = "sms_received_" + System.currentTimeMillis();
|
||||
// Use fingerprint for unique work name if available, otherwise fallback to timestamp
|
||||
String uniqueWorkName;
|
||||
if (smsDTO.getFingerprint() != null && !smsDTO.getFingerprint().isEmpty()) {
|
||||
uniqueWorkName = "sms_received_" + smsDTO.getFingerprint();
|
||||
} else {
|
||||
// Fallback to timestamp if fingerprint is not available
|
||||
uniqueWorkName = "sms_received_" + System.currentTimeMillis();
|
||||
Log.w(TAG, "Fingerprint not available, using timestamp for work name");
|
||||
}
|
||||
|
||||
WorkManager.getInstance(context)
|
||||
.beginUniqueWork(uniqueWorkName,
|
||||
androidx.work.ExistingWorkPolicy.APPEND_OR_REPLACE,
|
||||
androidx.work.ExistingWorkPolicy.KEEP,
|
||||
workRequest)
|
||||
.enqueue();
|
||||
|
||||
Log.d(TAG, "Work enqueued for received SMS from: " + smsDTO.getSender());
|
||||
Log.d(TAG, "Work enqueued for received SMS from: " + smsDTO.getSender() + " with fingerprint: " + uniqueWorkName);
|
||||
}
|
||||
}
|
||||
@@ -545,6 +545,29 @@ recipient,
|
||||
? new Date(dto.receivedAtInMillis)
|
||||
: dto.receivedAt
|
||||
|
||||
// Deduplication: Check for existing SMS with same device, sender, message, and receivedAt (within ±5 seconds tolerance)
|
||||
const toleranceMs = 5000 // 5 seconds
|
||||
const toleranceStart = new Date(receivedAt.getTime() - toleranceMs)
|
||||
const toleranceEnd = new Date(receivedAt.getTime() + toleranceMs)
|
||||
|
||||
const existingSMS = await this.smsModel.findOne({
|
||||
device: device._id,
|
||||
type: SMSType.RECEIVED,
|
||||
sender: dto.sender,
|
||||
message: dto.message,
|
||||
receivedAt: {
|
||||
$gte: toleranceStart,
|
||||
$lte: toleranceEnd,
|
||||
},
|
||||
})
|
||||
|
||||
if (existingSMS) {
|
||||
console.log(
|
||||
`Duplicate SMS detected for device ${deviceId}, sender ${dto.sender}, returning existing record: ${existingSMS._id}`,
|
||||
)
|
||||
return existingSMS
|
||||
}
|
||||
|
||||
const sms = await this.smsModel.create({
|
||||
device: device._id,
|
||||
message: dto.message,
|
||||
|
||||
Reference in New Issue
Block a user