1818
1919import androidx .annotation .Nullable ;
2020
21- import com .facebook .react .bridge .ReactApplicationContext ;
2221import com .google .gson .Gson ;
2322
2423import java .util .ArrayList ;
@@ -43,7 +42,6 @@ public class CustomPushNotification {
4342 private static final boolean ENABLE_VERBOSE_LOGS = BuildConfig .DEBUG ;
4443
4544 // Shared state
46- public static volatile ReactApplicationContext reactApplicationContext ;
4745 private static final Gson gson = new Gson ();
4846 private static final Map <String , List <Bundle >> notificationMessages = new ConcurrentHashMap <>();
4947
@@ -67,25 +65,10 @@ public CustomPushNotification(Context context, Bundle bundle) {
6765 createNotificationChannel ();
6866 }
6967
70- /**
71- * Sets the React application context when React Native initializes.
72- * Called from MainApplication when React context is ready.
73- */
74- public static void setReactContext (ReactApplicationContext context ) {
75- reactApplicationContext = context ;
76- }
77-
7868 public static void clearMessages (int notId ) {
7969 notificationMessages .remove (Integer .toString (notId ));
8070 }
8171
82- /**
83- * Check if React Native is initialized
84- */
85- private boolean isReactInitialized () {
86- return reactApplicationContext != null ;
87- }
88-
8972 public void onReceived () {
9073 String notId = mBundle .getString ("notId" );
9174
@@ -101,64 +84,18 @@ public void onReceived() {
10184 return ;
10285 }
10386
104- // Check if React is ready - needed for MMKV access (avatars, encryption, message-id-only)
105- if (!isReactInitialized ()) {
106- Log .w (TAG , "React not initialized yet, waiting before processing notification..." );
107-
108- // Wait for React to initialize with timeout
109- new Thread (() -> {
110- int attempts = 0 ;
111- int maxAttempts = 50 ; // 5 seconds total (50 * 100ms)
112-
113- while (!isReactInitialized () && attempts < maxAttempts ) {
114- try {
115- Thread .sleep (100 ); // Wait 100ms
116- attempts ++;
117-
118- if (attempts % 10 == 0 && ENABLE_VERBOSE_LOGS ) {
119- Log .d (TAG , "Still waiting for React initialization... (" + (attempts * 100 ) + "ms elapsed)" );
120- }
121- } catch (InterruptedException e ) {
122- Log .e (TAG , "Wait interrupted" , e );
123- Thread .currentThread ().interrupt ();
124- return ;
125- }
126- }
127-
128- if (isReactInitialized ()) {
129- Log .i (TAG , "React initialized after " + (attempts * 100 ) + "ms, proceeding with notification" );
130- try {
131- handleNotification ();
132- } catch (Exception e ) {
133- Log .e (TAG , "Failed to process notification after React initialization" , e );
134- }
135- } else {
136- Log .e (TAG , "Timeout waiting for React initialization after " + (maxAttempts * 100 ) + "ms, processing without MMKV" );
137- try {
138- handleNotification ();
139- } catch (Exception e ) {
140- Log .e (TAG , "Failed to process notification without React context" , e );
141- }
142- }
143- }).start ();
144-
145- return ; // Exit early, notification will be processed in the thread
146- }
147-
148- if (ENABLE_VERBOSE_LOGS ) {
149- Log .d (TAG , "React already initialized, proceeding with notification" );
150- }
151-
87+ // Process notification immediately - no need to wait for React Native
88+ // MMKV is initialized at app startup, so all notification types can work without React
15289 try {
15390 handleNotification ();
15491 } catch (Exception e ) {
155- Log .e (TAG , "Failed to process notification on main thread " , e );
92+ Log .e (TAG , "Failed to process notification" , e );
15693 }
15794 }
15895
15996 private void handleNotification () {
16097 Ejson receivedEjson = safeFromJson (mBundle .getString ("ejson" , "{}" ), Ejson .class );
161-
98+
16299 if (receivedEjson != null && receivedEjson .notificationType != null && receivedEjson .notificationType .equals ("message-id-only" )) {
163100 Log .d (TAG , "Detected message-id-only notification, will fetch full content from server" );
164101 loadNotificationAndProcess (receivedEjson );
@@ -210,7 +147,7 @@ private void processNotification() {
210147 // Handle E2E encrypted notifications
211148 if (isE2ENotification (loadedEjson )) {
212149 handleE2ENotification (mBundle , loadedEjson , notId );
213- return ; // E2E processor will handle showing the notification
150+ return ; // handleE2ENotification will decrypt and show the notification
214151 }
215152
216153 // Handle regular (non-E2E) notifications
@@ -225,54 +162,30 @@ private boolean isE2ENotification(Ejson ejson) {
225162 }
226163
227164 /**
228- * Handles E2E encrypted notifications by delegating to the async processor.
165+ * Handles E2E encrypted notifications by decrypting immediately using regular Android Context.
166+ * No longer waits for React Native initialization.
229167 */
230168 private void handleE2ENotification (Bundle bundle , Ejson ejson , String notId ) {
231- // Check if React context is immediately available
232- if ( reactApplicationContext != null ) {
233- // Fast path: decrypt immediately
234- String decrypted = Encryption . shared . decryptMessage ( ejson , reactApplicationContext );
235-
236- if ( decrypted != null ) {
237- bundle . putString ( "message" , decrypted );
169+ // Decrypt immediately using regular Android Context (mContext)
170+ // This works without React Native initialization
171+ String decrypted = Encryption . shared . decryptMessage ( ejson , mContext );
172+
173+ if ( decrypted != null ) {
174+ bundle . putString ( "message" , decrypted );
175+ synchronized ( this ) {
238176 mBundle = bundle ;
239- ejson = safeFromJson (bundle .getString ("ejson" , "{}" ), Ejson .class );
240- showNotification (bundle , ejson , notId );
241- } else {
242- Log .w (TAG , "E2E decryption failed for notification" );
243177 }
244- return ;
245- }
246-
247- // Slow path: wait for React context asynchronously
248- Log .i (TAG , "Waiting for React context to decrypt E2E notification" );
249-
250- E2ENotificationProcessor processor = new E2ENotificationProcessor (
251- // Context provider
252- () -> reactApplicationContext ,
253-
254- // Callback
255- new E2ENotificationProcessor .NotificationCallback () {
256- @ Override
257- public void onDecryptionComplete (Bundle decryptedBundle , Ejson decryptedEjson , String notificationId ) {
258- mBundle = decryptedBundle ;
259- Ejson finalEjson = safeFromJson (decryptedBundle .getString ("ejson" , "{}" ), Ejson .class );
260- showNotification (decryptedBundle , finalEjson , notificationId );
261- }
262-
263- @ Override
264- public void onDecryptionFailed (Bundle originalBundle , Ejson originalEjson , String notificationId ) {
265- Log .w (TAG , "E2E decryption failed for notification" );
266- }
267-
268- @ Override
269- public void onTimeout (Bundle originalBundle , Ejson originalEjson , String notificationId ) {
270- Log .w (TAG , "Timeout waiting for React context for E2E notification" );
271- }
178+ showNotification (bundle , ejson , notId );
179+ } else {
180+ Log .w (TAG , "E2E decryption failed for notification, showing fallback notification" );
181+ // Show fallback notification so user knows a message arrived
182+ // Use a placeholder message since we can't decrypt
183+ bundle .putString ("message" , "Encrypted message" );
184+ synchronized (this ) {
185+ mBundle = bundle ;
272186 }
273- );
274-
275- processor .processAsync (bundle , ejson , notId );
187+ showNotification (bundle , ejson , notId );
188+ }
276189 }
277190
278191 /**
@@ -289,13 +202,23 @@ private void showNotification(Bundle bundle, Ejson ejson, String notId) {
289202 boolean hasSender = ejson != null && ejson .sender != null ;
290203 String title = bundle .getString ("title" );
291204
205+ String displaySenderName = (ejson != null && ejson .senderName != null && !ejson .senderName .isEmpty ())
206+ ? ejson .senderName
207+ : (hasSender ? ejson .sender .username : title );
208+
292209 bundle .putLong ("time" , new Date ().getTime ());
293- bundle .putString ("username" , hasSender ? ejson . sender . username : title );
210+ bundle .putString ("username" , displaySenderName );
294211 bundle .putString ("senderId" , hasSender ? ejson .sender ._id : "1" );
295212
296213 String avatarUri = ejson != null ? ejson .getAvatarUri () : null ;
297214 bundle .putString ("avatarUri" , avatarUri );
298215
216+ // Ensure mBundle is updated with all modifications before building notification
217+ // This ensures buildNotification() sees the complete bundle with all fields (including ejson)
218+ synchronized (this ) {
219+ mBundle = bundle ;
220+ }
221+
299222 // Handle special notification types
300223 if (ejson != null && "videoconf" .equals (ejson .notificationType )) {
301224 handleVideoConfNotification (bundle , ejson );
@@ -372,19 +295,6 @@ private Notification.Builder buildNotification(int notificationId) {
372295
373296 // Determine the correct title based on notification type
374297 String notificationTitle = title ;
375- if (ejson != null && ejson .type != null ) {
376- if ("p" .equals (ejson .type ) || "c" .equals (ejson .type )) {
377- // For groups/channels, use room name if available, otherwise fall back to title
378- notificationTitle = (ejson .name != null && !ejson .name .isEmpty ()) ? ejson .name : title ;
379- } else if ("d" .equals (ejson .type )) {
380- // For direct messages, use title (sender name from server)
381- notificationTitle = title ;
382- } else if ("l" .equals (ejson .type )) {
383- // For omnichannel, use sender name if available, otherwise fall back to title
384- notificationTitle = (ejson .sender != null && ejson .sender .name != null && !ejson .sender .name .isEmpty ())
385- ? ejson .sender .name : title ;
386- }
387- }
388298
389299 if (ENABLE_VERBOSE_LOGS ) {
390300 Log .d (TAG , "[buildNotification] notId=" + notId );
@@ -546,19 +456,6 @@ private void notificationStyle(Notification.Builder notification, int notId, Bun
546456 // Determine the correct conversation title based on notification type
547457 Ejson bundleEjson = safeFromJson (bundle .getString ("ejson" , "{}" ), Ejson .class );
548458 String conversationTitle = title ;
549- if (bundleEjson != null && bundleEjson .type != null ) {
550- if ("p" .equals (bundleEjson .type ) || "c" .equals (bundleEjson .type )) {
551- // For groups/channels, use room name if available, otherwise fall back to title
552- conversationTitle = (bundleEjson .name != null && !bundleEjson .name .isEmpty ()) ? bundleEjson .name : title ;
553- } else if ("d" .equals (bundleEjson .type )) {
554- // For direct messages, use title (sender name from server)
555- conversationTitle = title ;
556- } else if ("l" .equals (bundleEjson .type )) {
557- // For omnichannel, use sender name if available, otherwise fall back to title
558- conversationTitle = (bundleEjson .sender != null && bundleEjson .sender .name != null && !bundleEjson .sender .name .isEmpty ())
559- ? bundleEjson .sender .name : title ;
560- }
561- }
562459 messageStyle .setConversationTitle (conversationTitle );
563460
564461 if (bundles != null ) {
@@ -570,15 +467,17 @@ private void notificationStyle(Notification.Builder notification, int notId, Bun
570467 Ejson ejson = safeFromJson (data .getString ("ejson" , "{}" ), Ejson .class );
571468 String m = extractMessage (message , ejson );
572469
470+ String displaySenderName = (ejson != null && ejson .senderName != null && !ejson .senderName .isEmpty ())
471+ ? ejson .senderName
472+ : (ejson != null && ejson .sender != null ? ejson .sender .username : title );
473+
573474 if (Build .VERSION .SDK_INT < Build .VERSION_CODES .P ) {
574- String senderName = ejson != null ? ejson .senderName : "Unknown" ;
575- messageStyle .addMessage (m , timestamp , senderName );
475+ messageStyle .addMessage (m , timestamp , displaySenderName );
576476 } else {
577477 Bitmap avatar = getAvatar (avatarUri );
578- String senderName = ejson != null ? ejson .senderName : "Unknown" ;
579478 Person .Builder senderBuilder = new Person .Builder ()
580479 .setKey (senderId )
581- .setName (senderName );
480+ .setName (displaySenderName );
582481
583482 if (avatar != null ) {
584483 senderBuilder .setIcon (Icon .createWithBitmap (avatar ));
0 commit comments