Skip to content

Commit a6ddbb3

Browse files
committed
Fix android push avatars
1 parent dbf4205 commit a6ddbb3

File tree

3 files changed

+92
-35
lines changed

3 files changed

+92
-35
lines changed

android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,6 @@ private void showNotification(Bundle bundle, Ejson ejson, String notId) {
300300
bundle.putString("senderId", hasSender ? ejson.sender._id : "1");
301301

302302
String avatarUri = ejson != null ? ejson.getAvatarUri() : null;
303-
if (ENABLE_VERBOSE_LOGS) {
304-
Log.d(TAG, "[showNotification] avatarUri=" + (avatarUri != null ? "[present]" : "[null]"));
305-
}
306303
bundle.putString("avatarUri", avatarUri);
307304

308305
// Handle special notification types
@@ -473,21 +470,9 @@ private void cancelPreviousFallbackNotifications(Ejson ejson) {
473470

474471
private Bitmap getAvatar(String uri) {
475472
if (uri == null || uri.isEmpty()) {
476-
if (ENABLE_VERBOSE_LOGS) {
477-
Log.w(TAG, "getAvatar called with null/empty URI");
478-
}
479473
return largeIcon();
480474
}
481475

482-
if (ENABLE_VERBOSE_LOGS) {
483-
String sanitizedUri = uri;
484-
int queryStart = uri.indexOf("?");
485-
if (queryStart != -1) {
486-
sanitizedUri = uri.substring(0, queryStart) + "?[auth_params]";
487-
}
488-
Log.d(TAG, "Fetching avatar from: " + sanitizedUri);
489-
}
490-
491476
try {
492477
// Use a 3-second timeout to avoid blocking the FCM service for too long
493478
// FCM has a 10-second limit, so we need to fail fast and use fallback icon
@@ -500,7 +485,7 @@ private Bitmap getAvatar(String uri) {
500485

501486
return avatar != null ? avatar : largeIcon();
502487
} catch (final ExecutionException | InterruptedException | TimeoutException e) {
503-
Log.e(TAG, "Failed to fetch avatar: " + e.getMessage(), e);
488+
Log.e(TAG, "Failed to fetch avatar", e);
504489
return largeIcon();
505490
}
506491
}
@@ -523,7 +508,10 @@ private void notificationIcons(Notification.Builder notification, Bundle bundle)
523508
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
524509
String avatarUri = ejson != null ? ejson.getAvatarUri() : null;
525510
if (avatarUri != null) {
526-
notification.setLargeIcon(getAvatar(avatarUri));
511+
Bitmap avatar = getAvatar(avatarUri);
512+
if (avatar != null) {
513+
notification.setLargeIcon(avatar);
514+
}
527515
}
528516
}
529517
}

android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public String getAvatarUri() {
7171
String uid = userId();
7272

7373
if (userToken.isEmpty() || uid.isEmpty()) {
74-
Log.w(TAG, "Cannot generate avatar URI: missing auth credentials (token=" + !userToken.isEmpty() + ", uid=" + !uid.isEmpty() + ")");
74+
Log.w(TAG, "Cannot generate avatar URI: missing auth credentials");
7575
return null;
7676
}
7777

@@ -86,9 +86,6 @@ public String getAvatarUri() {
8686
}
8787
try {
8888
avatarPath = "/avatar/" + URLEncoder.encode(sender.username, "UTF-8");
89-
if (BuildConfig.DEBUG) {
90-
Log.d(TAG, "Generated avatar URI for user: " + sender.username);
91-
}
9289
} catch (UnsupportedEncodingException e) {
9390
Log.e(TAG, "Failed to encode username", e);
9491
return null;
@@ -101,9 +98,6 @@ public String getAvatarUri() {
10198
}
10299
try {
103100
avatarPath = "/avatar/room/" + URLEncoder.encode(rid, "UTF-8");
104-
if (BuildConfig.DEBUG) {
105-
Log.d(TAG, "Generated avatar URI for room: " + rid);
106-
}
107101
} catch (UnsupportedEncodingException e) {
108102
Log.e(TAG, "Failed to encode rid", e);
109103
return null;

android/app/src/main/java/chat/rocket/reactnative/notification/Encryption.java

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -188,14 +188,23 @@ public Room readRoom(final Ejson ejson, Context context) {
188188
}
189189

190190
cursor.moveToFirst();
191-
String e2eKey = cursor.getString(cursor.getColumnIndex("e2e_key"));
192-
Boolean encrypted = cursor.getInt(cursor.getColumnIndex("encrypted")) > 0;
191+
int e2eKeyColumnIndex = cursor.getColumnIndex("e2e_key");
192+
int encryptedColumnIndex = cursor.getColumnIndex("encrypted");
193+
194+
if (e2eKeyColumnIndex == -1) {
195+
Log.e(TAG, "e2e_key column not found in subscriptions table");
196+
cursor.close();
197+
return null;
198+
}
199+
200+
String e2eKey = cursor.getString(e2eKeyColumnIndex);
201+
Boolean encrypted = encryptedColumnIndex != -1 && cursor.getInt(encryptedColumnIndex) > 0;
193202
cursor.close();
194203

195204
return new Room(e2eKey, encrypted);
196205

197206
} catch (Exception e) {
198-
Log.e("[ENCRYPTION]", "Error reading room", e);
207+
Log.e(TAG, "Error reading room", e);
199208
return null;
200209

201210
} finally {
@@ -236,7 +245,31 @@ public String readUserKey(final Ejson ejson) throws Exception {
236245
return null;
237246
}
238247

239-
PrivateKey privKey = gson.fromJson(privateKey, PrivateKey.class);
248+
PrivateKey privKey;
249+
try {
250+
// First, try parsing as direct JSON object
251+
privKey = gson.fromJson(privateKey, PrivateKey.class);
252+
} catch (com.google.gson.JsonSyntaxException e) {
253+
// If that fails, it might be a JSON-encoded string (double-encoded)
254+
// Try parsing as a string first, then parse that string as JSON
255+
try {
256+
String decoded = gson.fromJson(privateKey, String.class);
257+
privKey = gson.fromJson(decoded, PrivateKey.class);
258+
} catch (Exception e2) {
259+
Log.e(TAG, "Failed to parse private key", e2);
260+
throw new Exception("Failed to parse private key: " + e2.getMessage(), e2);
261+
}
262+
}
263+
264+
if (privKey == null) {
265+
return null;
266+
}
267+
268+
// Validate that required fields are present
269+
if (privKey.n == null || privKey.e == null || privKey.d == null) {
270+
Log.e(TAG, "PrivateKey missing required fields (n, e, or d)");
271+
return null;
272+
}
240273

241274
WritableMap jwk = Arguments.createMap();
242275
jwk.putString("n", privKey.n);
@@ -252,9 +285,19 @@ public String readUserKey(final Ejson ejson) throws Exception {
252285
}
253286

254287
public RoomKeyResult decryptRoomKey(final String e2eKey, final Ejson ejson) throws Exception {
288+
if (e2eKey == null || e2eKey.isEmpty()) {
289+
return null;
290+
}
291+
255292
// Parse using prefixed base64
256-
PrefixedData parsed = decodePrefixedBase64(e2eKey);
257-
keyId = parsed.prefix;
293+
PrefixedData parsed;
294+
try {
295+
parsed = decodePrefixedBase64(e2eKey);
296+
keyId = parsed.prefix;
297+
} catch (Exception e) {
298+
Log.e(TAG, "Failed to decode prefixed base64", e);
299+
throw e;
300+
}
258301

259302
// Decrypt the session key
260303
String userKey = readUserKey(ejson);
@@ -263,22 +306,54 @@ public RoomKeyResult decryptRoomKey(final String e2eKey, final Ejson ejson) thro
263306
}
264307

265308
String base64EncryptedData = Base64.encodeToString(parsed.data, Base64.NO_WRAP);
266-
String decrypted = RSACrypto.INSTANCE.decrypt(base64EncryptedData, userKey);
309+
String decrypted;
310+
try {
311+
decrypted = RSACrypto.INSTANCE.decrypt(base64EncryptedData, userKey);
312+
if (decrypted == null) {
313+
return null;
314+
}
315+
} catch (Exception e) {
316+
Log.e(TAG, "RSA decryption failed", e);
317+
throw e;
318+
}
267319

268320
// Parse sessionKey to determine v1 vs v2 from "alg" field
269-
JsonObject sessionKey = gson.fromJson(decrypted, JsonObject.class);
321+
JsonObject sessionKey;
322+
try {
323+
sessionKey = gson.fromJson(decrypted, JsonObject.class);
324+
if (sessionKey == null) {
325+
return null;
326+
}
327+
} catch (com.google.gson.JsonSyntaxException e) {
328+
Log.e(TAG, "Failed to parse decrypted session key as JSON", e);
329+
throw new Exception("Failed to parse decrypted session key as JSON: " + e.getMessage(), e);
330+
}
331+
332+
if (!sessionKey.has("k")) {
333+
return null;
334+
}
335+
270336
String k = sessionKey.get("k").getAsString();
271-
byte[] decoded = Base64.decode(k, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE);
337+
byte[] decoded;
338+
try {
339+
decoded = Base64.decode(k, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE);
340+
} catch (Exception e) {
341+
Log.e(TAG, "Failed to decode 'k' from base64", e);
342+
throw e;
343+
}
344+
272345
String decryptedKey = CryptoUtils.INSTANCE.bytesToHex(decoded);
273346

274347
// Determine format from "alg" field
348+
String algorithm;
275349
if (sessionKey.has("alg") && "A256GCM".equals(sessionKey.get("alg").getAsString())) {
276350
algorithm = "rc.v2.aes-sha2";
277-
return new RoomKeyResult(decryptedKey, "rc.v2.aes-sha2");
278351
} else {
279352
algorithm = "rc.v1.aes-sha2";
280-
return new RoomKeyResult(decryptedKey, "rc.v1.aes-sha2");
281353
}
354+
this.algorithm = algorithm;
355+
356+
return new RoomKeyResult(decryptedKey, algorithm);
282357
}
283358

284359
private String decryptContent(Ejson.Content content, String e2eKey) throws Exception {

0 commit comments

Comments
 (0)