Skip to content

Commit 733c21f

Browse files
committed
Enhance CustomPushNotification and Ejson with improved logging and error handling
- Added verbose logging for notification processing and avatar URI generation in CustomPushNotification.java. - Implemented checks for React initialization before processing notifications. - Updated Ejson.java to ensure avatar URI generation handles null or empty values gracefully. - Enhanced LoadNotification.java to log encryption fields in notifications. - Improved overall error management and notification handling logic.
1 parent ebf1e4d commit 733c21f

File tree

3 files changed

+206
-18
lines changed

3 files changed

+206
-18
lines changed

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

Lines changed: 162 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
import chat.rocket.reactnative.R;
4141

4242
public class CustomPushNotification extends PushNotification {
43+
private static final String TAG = "RocketChat.CustomPush";
44+
private static final boolean ENABLE_VERBOSE_LOGS = BuildConfig.DEBUG;
45+
4346
public static ReactApplicationContext reactApplicationContext;
4447
final NotificationManager notificationManager;
4548

@@ -65,29 +68,111 @@ public static void clearMessages(int notId) {
6568

6669
@Override
6770
public void onReceived() throws InvalidNotificationException {
71+
// Check if React is ready - needed for MMKV access (avatars, encryption, message-id-only)
72+
if (!mAppLifecycleFacade.isReactInitialized()) {
73+
android.util.Log.w(TAG, "React not initialized yet, waiting before processing notification...");
74+
75+
// Wait for React to initialize with timeout
76+
new Thread(() -> {
77+
int attempts = 0;
78+
int maxAttempts = 50; // 5 seconds total (50 * 100ms)
79+
80+
while (!mAppLifecycleFacade.isReactInitialized() && attempts < maxAttempts) {
81+
try {
82+
Thread.sleep(100); // Wait 100ms
83+
attempts++;
84+
85+
if (attempts % 10 == 0 && ENABLE_VERBOSE_LOGS) {
86+
android.util.Log.d(TAG, "Still waiting for React initialization... (" + (attempts * 100) + "ms elapsed)");
87+
}
88+
} catch (InterruptedException e) {
89+
android.util.Log.e(TAG, "Wait interrupted", e);
90+
Thread.currentThread().interrupt();
91+
return;
92+
}
93+
}
94+
95+
if (mAppLifecycleFacade.isReactInitialized()) {
96+
android.util.Log.i(TAG, "React initialized after " + (attempts * 100) + "ms, proceeding with notification");
97+
handleNotification();
98+
} else {
99+
android.util.Log.e(TAG, "Timeout waiting for React initialization after " + (maxAttempts * 100) + "ms, processing without MMKV");
100+
handleNotification();
101+
}
102+
}).start();
103+
104+
return; // Exit early, notification will be processed in the thread
105+
}
106+
107+
if (ENABLE_VERBOSE_LOGS) {
108+
android.util.Log.d(TAG, "React already initialized, proceeding with notification");
109+
}
110+
handleNotification();
111+
}
112+
113+
private void handleNotification() {
68114
Bundle received = mNotificationProps.asBundle();
69115
Ejson receivedEjson = safeFromJson(received.getString("ejson", "{}"), Ejson.class);
70116

71117
if (receivedEjson != null && receivedEjson.notificationType != null && receivedEjson.notificationType.equals("message-id-only")) {
72-
android.util.Log.d("RocketChat.CustomPush", "Detected message-id-only notification, will fetch full content from server");
73-
notificationLoad(receivedEjson, new Callback() {
74-
@Override
75-
public void call(@Nullable Bundle bundle) {
76-
if (bundle != null) {
77-
android.util.Log.d("RocketChat.CustomPush", "Successfully loaded notification content from server, updating notification props");
78-
mNotificationProps = createProps(bundle);
79-
} else {
80-
android.util.Log.w("RocketChat.CustomPush", "Failed to load notification content from server, will display placeholder notification");
81-
}
82-
}
83-
});
118+
android.util.Log.d(TAG, "Detected message-id-only notification, will fetch full content from server");
119+
loadNotificationAndProcess(receivedEjson);
120+
return; // Exit early, notification will be processed in callback
84121
}
85122

123+
// For non-message-id-only notifications, process immediately
124+
processNotification();
125+
}
126+
127+
private void loadNotificationAndProcess(Ejson ejson) {
128+
notificationLoad(ejson, new Callback() {
129+
@Override
130+
public void call(@Nullable Bundle bundle) {
131+
if (bundle != null) {
132+
android.util.Log.d(TAG, "Successfully loaded notification content from server, updating notification props");
133+
134+
if (ENABLE_VERBOSE_LOGS) {
135+
// BEFORE createProps
136+
android.util.Log.d(TAG, "[BEFORE createProps] bundle.notificationLoaded=" + bundle.getBoolean("notificationLoaded", false));
137+
android.util.Log.d(TAG, "[BEFORE createProps] bundle.title=" + (bundle.getString("title") != null ? "[present]" : "[null]"));
138+
android.util.Log.d(TAG, "[BEFORE createProps] bundle.message length=" + (bundle.getString("message") != null ? bundle.getString("message").length() : 0));
139+
android.util.Log.d(TAG, "[BEFORE createProps] bundle has ejson=" + (bundle.getString("ejson") != null));
140+
}
141+
142+
mNotificationProps = createProps(bundle);
143+
144+
if (ENABLE_VERBOSE_LOGS) {
145+
// AFTER createProps - verify it worked
146+
Bundle verifyBundle = mNotificationProps.asBundle();
147+
android.util.Log.d(TAG, "[AFTER createProps] mNotificationProps.notificationLoaded=" + verifyBundle.getBoolean("notificationLoaded", false));
148+
android.util.Log.d(TAG, "[AFTER createProps] mNotificationProps.title=" + (verifyBundle.getString("title") != null ? "[present]" : "[null]"));
149+
android.util.Log.d(TAG, "[AFTER createProps] mNotificationProps.message length=" + (verifyBundle.getString("message") != null ? verifyBundle.getString("message").length() : 0));
150+
android.util.Log.d(TAG, "[AFTER createProps] mNotificationProps has ejson=" + (verifyBundle.getString("ejson") != null));
151+
}
152+
} else {
153+
android.util.Log.w(TAG, "Failed to load notification content from server, will display placeholder notification");
154+
}
155+
156+
processNotification();
157+
}
158+
});
159+
}
160+
161+
private void processNotification() {
86162
// We should re-read these values since that can be changed by notificationLoad
87163
Bundle bundle = mNotificationProps.asBundle();
88164
Ejson loadedEjson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class);
89165
String notId = bundle.getString("notId", "1");
90166

167+
if (ENABLE_VERBOSE_LOGS) {
168+
android.util.Log.d(TAG, "[onReceived processing] notId=" + notId);
169+
android.util.Log.d(TAG, "[onReceived processing] bundle.notificationLoaded=" + bundle.getBoolean("notificationLoaded", false));
170+
android.util.Log.d(TAG, "[onReceived processing] bundle.title=" + (bundle.getString("title") != null ? "[present]" : "[null]"));
171+
android.util.Log.d(TAG, "[onReceived processing] bundle.message length=" + (bundle.getString("message") != null ? bundle.getString("message").length() : 0));
172+
android.util.Log.d(TAG, "[onReceived processing] loadedEjson.notificationType=" + (loadedEjson != null ? loadedEjson.notificationType : "null"));
173+
android.util.Log.d(TAG, "[onReceived processing] loadedEjson.sender=" + (loadedEjson != null && loadedEjson.sender != null ? loadedEjson.sender.username : "null"));
174+
}
175+
91176
if (notificationMessages.get(notId) == null) {
92177
notificationMessages.put(notId, new ArrayList<Bundle>());
93178
}
@@ -107,12 +192,23 @@ public void call(@Nullable Bundle bundle) {
107192
bundle.putLong("time", new Date().getTime());
108193
bundle.putString("username", hasSender ? loadedEjson.sender.username : title);
109194
bundle.putString("senderId", hasSender ? loadedEjson.sender._id : "1");
110-
bundle.putString("avatarUri", loadedEjson != null ? loadedEjson.getAvatarUri() : null);
195+
196+
String avatarUri = loadedEjson != null ? loadedEjson.getAvatarUri() : null;
197+
if (ENABLE_VERBOSE_LOGS) {
198+
android.util.Log.d(TAG, "[processNotification] avatarUri=" + (avatarUri != null ? "[present]" : "[null]"));
199+
}
200+
bundle.putString("avatarUri", avatarUri);
111201

112202
if (loadedEjson != null && loadedEjson.notificationType instanceof String && loadedEjson.notificationType.equals("videoconf")) {
113203
notifyReceivedToJS();
114204
} else {
205+
if (ENABLE_VERBOSE_LOGS) {
206+
android.util.Log.d(TAG, "[Before add to notificationMessages] notId=" + notId + ", bundle.message length=" + (bundle.getString("message") != null ? bundle.getString("message").length() : 0) + ", bundle.notificationLoaded=" + bundle.getBoolean("notificationLoaded", false));
207+
}
115208
notificationMessages.get(notId).add(bundle);
209+
if (ENABLE_VERBOSE_LOGS) {
210+
android.util.Log.d(TAG, "[After add] notificationMessages[" + notId + "].size=" + notificationMessages.get(notId).size());
211+
}
116212
postNotification(Integer.parseInt(notId));
117213
notifyReceivedToJS();
118214
}
@@ -137,6 +233,16 @@ protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
137233
Boolean notificationLoaded = bundle.getBoolean("notificationLoaded", false);
138234
Ejson ejson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class);
139235

236+
if (ENABLE_VERBOSE_LOGS) {
237+
android.util.Log.d(TAG, "[getNotificationBuilder] notId=" + notId);
238+
android.util.Log.d(TAG, "[getNotificationBuilder] notificationLoaded=" + notificationLoaded);
239+
android.util.Log.d(TAG, "[getNotificationBuilder] title=" + (title != null ? "[present]" : "[null]"));
240+
android.util.Log.d(TAG, "[getNotificationBuilder] message length=" + (message != null ? message.length() : 0));
241+
android.util.Log.d(TAG, "[getNotificationBuilder] ejson=" + (ejson != null ? "present" : "null"));
242+
android.util.Log.d(TAG, "[getNotificationBuilder] ejson.notificationType=" + (ejson != null ? ejson.notificationType : "null"));
243+
android.util.Log.d(TAG, "[getNotificationBuilder] ejson.sender=" + (ejson != null && ejson.sender != null ? ejson.sender.username : "null"));
244+
}
245+
140246
notification
141247
.setContentTitle(title)
142248
.setContentText(message)
@@ -153,12 +259,13 @@ protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
153259

154260
// if notificationType is null (RC < 3.5) or notificationType is different of message-id-only or notification was loaded successfully
155261
if (ejson == null || ejson.notificationType == null || !ejson.notificationType.equals("message-id-only") || notificationLoaded) {
262+
android.util.Log.i(TAG, "[getNotificationBuilder] ✅ Rendering FULL notification style (ejson=" + (ejson != null) + ", notificationType=" + (ejson != null ? ejson.notificationType : "null") + ", notificationLoaded=" + notificationLoaded + ")");
156263
notificationStyle(notification, notificationId, bundle);
157264
notificationReply(notification, notificationId, bundle);
158265

159266
// message couldn't be loaded from server (Fallback notification)
160267
} else {
161-
android.util.Log.w("RocketChat.CustomPush", "Displaying fallback notification for message-id-only (content failed to load from server)");
268+
android.util.Log.w(TAG, "[getNotificationBuilder] ⚠️ Rendering FALLBACK notification (ejson=" + (ejson != null) + ", notificationType=" + (ejson != null ? ejson.notificationType : "null") + ", notificationLoaded=" + notificationLoaded + ")");
162269
Gson gson = new Gson();
163270
// iterate over the current notification ids to dismiss fallback notifications from same server
164271
for (Map.Entry<String, List<Bundle>> bundleList : notificationMessages.entrySet()) {
@@ -173,7 +280,9 @@ protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
173280
String id = not.getString("notId");
174281
// cancel this notification
175282
notificationManager.cancel(Integer.parseInt(id));
176-
android.util.Log.d("RocketChat.CustomPush", "Cancelled previous fallback notification from same server");
283+
if (ENABLE_VERBOSE_LOGS) {
284+
android.util.Log.d(TAG, "Cancelled previous fallback notification from same server");
285+
}
177286
}
178287
}
179288
}
@@ -190,14 +299,42 @@ private void notifyReceivedToJS() {
190299
}
191300

192301
private Bitmap getAvatar(String uri) {
302+
if (uri == null || uri.isEmpty()) {
303+
if (ENABLE_VERBOSE_LOGS) {
304+
android.util.Log.w(TAG, "getAvatar called with null/empty URI");
305+
}
306+
return largeIcon();
307+
}
308+
309+
// Sanitize URL for logging (remove query params with tokens)
310+
String sanitizedUri = uri;
311+
int queryStart = uri.indexOf("?");
312+
if (queryStart != -1) {
313+
sanitizedUri = uri.substring(0, queryStart) + "?[auth_params]";
314+
}
315+
316+
if (ENABLE_VERBOSE_LOGS) {
317+
android.util.Log.d(TAG, "Fetching avatar from: " + sanitizedUri);
318+
}
319+
193320
try {
194-
return Glide.with(mContext)
321+
Bitmap avatar = Glide.with(mContext)
195322
.asBitmap()
196323
.apply(RequestOptions.bitmapTransform(new RoundedCorners(10)))
197324
.load(uri)
198325
.submit(100, 100)
199326
.get();
327+
328+
if (avatar != null) {
329+
if (ENABLE_VERBOSE_LOGS) {
330+
android.util.Log.d(TAG, "Successfully loaded avatar");
331+
}
332+
} else {
333+
android.util.Log.w(TAG, "Avatar loaded but is null");
334+
}
335+
return avatar != null ? avatar : largeIcon();
200336
} catch (final ExecutionException | InterruptedException e) {
337+
android.util.Log.e(TAG, "Failed to fetch avatar: " + e.getMessage(), e);
201338
return largeIcon();
202339
}
203340
}
@@ -267,6 +404,15 @@ private void notificationColor(Notification.Builder notification) {
267404
private void notificationStyle(Notification.Builder notification, int notId, Bundle bundle) {
268405
List<Bundle> bundles = notificationMessages.get(Integer.toString(notId));
269406

407+
if (ENABLE_VERBOSE_LOGS) {
408+
android.util.Log.d(TAG, "[notificationStyle] notId=" + notId + ", bundles=" + (bundles != null ? bundles.size() : "null"));
409+
if (bundles != null && bundles.size() > 0) {
410+
Bundle firstBundle = bundles.get(0);
411+
android.util.Log.d(TAG, "[notificationStyle] first bundle.message length=" + (firstBundle.getString("message") != null ? firstBundle.getString("message").length() : 0));
412+
android.util.Log.d(TAG, "[notificationStyle] first bundle.notificationLoaded=" + firstBundle.getBoolean("notificationLoaded", false));
413+
}
414+
}
415+
270416
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
271417
Notification.InboxStyle messageStyle = new Notification.InboxStyle();
272418
if (bundles != null) {

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,32 @@ private void ensureMMKVInitialized() {
107107
}
108108

109109
public String getAvatarUri() {
110-
if (type == null) {
110+
if (sender == null || sender.username == null || sender.username.isEmpty()) {
111+
Log.w(TAG, "Cannot generate avatar URI: sender or username is null");
111112
return null;
112113
}
113-
return serverURL() + "/avatar/" + this.sender.username + "?rc_token=" + token() + "&rc_uid=" + userId();
114+
115+
String server = serverURL();
116+
if (server == null || server.isEmpty()) {
117+
Log.w(TAG, "Cannot generate avatar URI: serverURL is null");
118+
return null;
119+
}
120+
121+
String userToken = token();
122+
String uid = userId();
123+
124+
if (userToken.isEmpty() || uid.isEmpty()) {
125+
Log.w(TAG, "Cannot generate avatar URI: missing auth credentials (token=" + !userToken.isEmpty() + ", uid=" + !uid.isEmpty() + ")");
126+
return null;
127+
}
128+
129+
String uri = server + "/avatar/" + sender.username + "?format=png&size=100&rc_token=" + userToken + "&rc_uid=" + uid;
130+
131+
if (BuildConfig.DEBUG) {
132+
Log.d(TAG, "Generated avatar URI for user: " + sender.username);
133+
}
134+
135+
return uri;
114136
}
115137

116138
public String token() {

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,23 @@ class Payload {
3737
String notificationType;
3838
String name;
3939
String messageType;
40+
String senderName;
41+
String msg;
42+
String tmid;
43+
Content content;
4044

4145
class Sender {
4246
String _id;
4347
String username;
4448
String name;
4549
}
50+
51+
class Content {
52+
String algorithm;
53+
String ciphertext;
54+
String kid;
55+
String iv;
56+
}
4657
}
4758
}
4859
}
@@ -174,6 +185,15 @@ private void runRequest(OkHttpClient client, Request request, Callback callback,
174185
Log.e(TAG, "Invalid response structure: missing required fields");
175186
throw new IllegalStateException("Invalid response structure");
176187
}
188+
189+
// Log encryption fields if present
190+
if (json.data.notification.payload != null) {
191+
boolean hasEncryption = json.data.notification.payload.msg != null || json.data.notification.payload.content != null;
192+
if (hasEncryption) {
193+
Log.d(TAG, "Notification contains encrypted content: msg=" + (json.data.notification.payload.msg != null) +
194+
", content=" + (json.data.notification.payload.content != null));
195+
}
196+
}
177197

178198
Bundle bundle = new Bundle();
179199
bundle.putString("notId", json.data.notification.notId);

0 commit comments

Comments
 (0)