pushwoosh-react-native-plugin 7.0.0 → 7.0.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pushwoosh-react-native-plugin",
3
- "version": "7.0.0",
3
+ "version": "7.0.1",
4
4
  "description": "This plugin allows you to send and receive push notifications. Powered by Pushwoosh (www.pushwoosh.com).",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = "pushwoosh-react-native-plugin"
3
- s.version = "7.0.0"
3
+ s.version = "7.0.1"
4
4
  s.summary = "React Native Pushwoosh Push Notifications module"
5
5
  s.requires_arc = true
6
6
  s.author = 'Pushwoosh'
@@ -20,5 +20,5 @@ Pod::Spec.new do |s|
20
20
  end
21
21
 
22
22
  s.dependency 'PushwooshXCFramework', '7.2.6'
23
- s.dependency 'PushwooshInboxUIXCFramework'
23
+ s.dependency 'PushwooshInboxUIXCFramework', '7.0.42'
24
24
  end
@@ -9,6 +9,7 @@ import com.facebook.react.bridge.Callback;
9
9
  import com.facebook.react.bridge.ReactContext;
10
10
  import com.facebook.react.bridge.WritableMap;
11
11
  import com.facebook.react.modules.core.DeviceEventManagerModule;
12
+ import com.pushwoosh.internal.utils.PWLog;
12
13
 
13
14
  public class EventDispatcher {
14
15
 
@@ -37,7 +38,15 @@ public class EventDispatcher {
37
38
 
38
39
  List<Callback> list = subscribers.get(event);
39
40
  for (Callback subscriber : list) {
40
- subscriber.invoke(objects);
41
+ try {
42
+ subscriber.invoke(objects);
43
+ } catch (Exception e) {
44
+ // A callback bound to a runtime that is already gone throws, and React Native
45
+ // throws on a second invocation of a callback as well. The subscribers behind
46
+ // it, and the clear() below, have to happen anyway - otherwise the bad callback
47
+ // stays in the list and takes down every dispatch that follows.
48
+ PWLog.exception(e);
49
+ }
41
50
  }
42
51
 
43
52
  // A native module is supposed to invoke its callback only once!
@@ -61,17 +61,39 @@ public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEve
61
61
  private static final String PUSH_RECEIVED_EVENT = Pushwoosh.PUSH_RECEIVE_EVENT;
62
62
  private static final String PUSH_RECEIVED_JS_EVENT = "pushReceived";
63
63
 
64
- private static EventDispatcher mEventDispatcher = new EventDispatcher();
65
-
66
- private static String sReceivedPushData;
67
- private static boolean sReceivedPushCallbackRegistered = false;
68
-
69
- private static String sStartPushData;
70
- private static boolean sPushCallbackRegistered = false;
71
-
72
- private static boolean sInitialized = false;
64
+ // Per bundle. The callbacks belong to the JS runtime that subscribed them and "JS has called
65
+ // init()" is true of that runtime alone, so this state goes away with the module React Native
66
+ // tears down - a late teardown has nothing left to reach into the next module with.
67
+ private final EventDispatcher mEventDispatcher = new EventDispatcher();
68
+ private boolean mInitialized = false;
69
+ private boolean mPushCallbackRegistered = false;
70
+ private boolean mReceivedPushCallbackRegistered = false;
71
+
72
+ // Per process. A push handed over before JS could take it outlives the React instance it
73
+ // arrived at: the next init() replays it.
74
+ private static PendingPush sReceivedPush;
75
+ private static PendingPush sStartPush;
73
76
  private static final Object sStartPushLock = new Object();
74
77
 
78
+ // A push and what has already been done with it. Both paths to JS are replayed to a bundle
79
+ // that comes later - the device event by init(), the deprecated callback by onPushOpen() /
80
+ // onPushReceived() - and each of them only until the push has gone that way once. Without
81
+ // that, every new React instance was handed the last push of the process again: a pushOpened
82
+ // re-routing a deep link the app had already handled, and a launch push taking the place of
83
+ // the one-shot callback registration the new bundle was making.
84
+ private static final class PendingPush {
85
+ final String data;
86
+ boolean sentAsEvent;
87
+ boolean sentToCallback;
88
+
89
+ PendingPush(String data) {
90
+ this.data = data;
91
+ }
92
+ }
93
+
94
+ // The module JS is talking to, published on the first call from JS rather than in the
95
+ // constructor: TurboModuleManager.invalidate() constructs the module JS never required, and
96
+ // such a stillborn one must not take the live module's place.
75
97
  private static PushwooshPlugin INSTANCE = null;
76
98
 
77
99
  private InboxUiStyleManager inboxUiInboxUiStyleManager;
@@ -79,8 +101,6 @@ public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEve
79
101
  public PushwooshPlugin(ReactApplicationContext reactContext) {
80
102
  super(reactContext);
81
103
 
82
- INSTANCE = this;
83
-
84
104
  reactContext.addLifecycleEventListener(this);
85
105
 
86
106
  Context applicationContext = AndroidPlatformModule.getApplicationContext();
@@ -122,18 +142,21 @@ public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEve
122
142
  Pushwoosh.getInstance().setAppId(appId);
123
143
 
124
144
  synchronized (sStartPushLock) {
125
- if (sReceivedPushData != null) {
126
- sendEvent(PUSH_RECEIVED_JS_EVENT, ConversionUtil.stringToJSONObject(sReceivedPushData));
145
+ if (sReceivedPush != null && !sReceivedPush.sentAsEvent) {
146
+ sendEvent(PUSH_RECEIVED_JS_EVENT, ConversionUtil.stringToJSONObject(sReceivedPush.data));
147
+ sReceivedPush.sentAsEvent = true;
127
148
  }
128
149
 
129
- if (sStartPushData != null) {
130
- sendEvent(PUSH_OPEN_JS_EVENT, ConversionUtil.stringToJSONObject(sStartPushData));
150
+ if (sStartPush != null && !sStartPush.sentAsEvent) {
151
+ sendEvent(PUSH_OPEN_JS_EVENT, ConversionUtil.stringToJSONObject(sStartPush.data));
152
+ sStartPush.sentAsEvent = true;
131
153
  }
132
154
 
133
155
  // Under the same lock as the replay above: a push taking the lock between the replay
134
- // and this flag would find sInitialized false, skip its own send, and never be
135
- // replayed either.
136
- sInitialized = true;
156
+ // and this flag would find the gate closed, skip its own send, and never be replayed
157
+ // either.
158
+ mInitialized = true;
159
+ INSTANCE = this;
137
160
  }
138
161
 
139
162
  if (success != null) {
@@ -167,13 +190,16 @@ public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEve
167
190
  @ReactMethod
168
191
  public void onPushOpen(Callback callback) {
169
192
  synchronized (sStartPushLock) {
170
- if (!sPushCallbackRegistered && sStartPushData != null) {
171
- callback.invoke(ConversionUtil.toWritableMap(ConversionUtil.stringToJSONObject(sStartPushData)));
172
- sPushCallbackRegistered = true;
193
+ INSTANCE = this;
194
+
195
+ if (!mPushCallbackRegistered && sStartPush != null && !sStartPush.sentToCallback) {
196
+ callback.invoke(ConversionUtil.toWritableMap(ConversionUtil.stringToJSONObject(sStartPush.data)));
197
+ sStartPush.sentToCallback = true;
198
+ mPushCallbackRegistered = true;
173
199
  return;
174
200
  }
175
201
 
176
- sPushCallbackRegistered = true;
202
+ mPushCallbackRegistered = true;
177
203
  mEventDispatcher.subscribe(PUSH_OPEN_EVENT, callback);
178
204
  }
179
205
  }
@@ -181,13 +207,16 @@ public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEve
181
207
  @ReactMethod
182
208
  public void onPushReceived(Callback callback) {
183
209
  synchronized (sStartPushLock) {
184
- if (!sReceivedPushCallbackRegistered && sReceivedPushData != null) {
185
- callback.invoke(ConversionUtil.toWritableMap(ConversionUtil.stringToJSONObject(sReceivedPushData)));
186
- sReceivedPushCallbackRegistered = true;
210
+ INSTANCE = this;
211
+
212
+ if (!mReceivedPushCallbackRegistered && sReceivedPush != null && !sReceivedPush.sentToCallback) {
213
+ callback.invoke(ConversionUtil.toWritableMap(ConversionUtil.stringToJSONObject(sReceivedPush.data)));
214
+ sReceivedPush.sentToCallback = true;
215
+ mReceivedPushCallbackRegistered = true;
187
216
  return;
188
217
  }
189
218
 
190
- sReceivedPushCallbackRegistered = true;
219
+ mReceivedPushCallbackRegistered = true;
191
220
  mEventDispatcher.subscribe(PUSH_RECEIVED_EVENT, callback);
192
221
  }
193
222
  }
@@ -600,29 +629,32 @@ public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEve
600
629
  public void onHostDestroy() {
601
630
  PWLog.noise(TAG, "Host destroyed");
602
631
 
603
- // A push can be delivered on the FCM thread while the host is going down, and every other
604
- // accessor of these fields holds the lock.
605
- synchronized (sStartPushLock) {
606
- // Only for the module this host is taking down. A restarted host builds a new
607
- // PushwooshPlugin, and the order of the two events is not guaranteed, so a late
608
- // teardown must not reset what the new module has already set up.
609
- if (INSTANCE != this) {
610
- return;
611
- }
632
+ // Nothing is reset here: the host going down is the activity, not the JS runtime. React
633
+ // Native keeps the context and the JS bundle across activities, JS does not call init() or
634
+ // onPushOpen() a second time, and a push opened after the user left by Back has to reach
635
+ // that bundle. Everything JS set up is reset in invalidate(), where the runtime ends.
636
+ }
612
637
 
613
- sPushCallbackRegistered = false;
614
- sStartPushData = null;
638
+ // React Native tears the module down with its React instance (legacy ModuleHolder.destroy(),
639
+ // TurboModuleManager.invalidate()). Everything JS set up lives on this module and goes with it;
640
+ // only the module's place as the live one has to be given up here. The cached push stays - the
641
+ // next init() replays it.
642
+ @Override
643
+ public void invalidate() {
644
+ super.invalidate();
645
+ PWLog.noise(TAG, "Module invalidated");
615
646
 
616
- sReceivedPushCallbackRegistered = false;
617
- sReceivedPushData = null;
647
+ getReactApplicationContext().removeLifecycleEventListener(this);
618
648
 
619
- // These are what openPush()/messageReceived() consult before sending to JS. Left
620
- // standing, a push arriving after the host died was handed to a context with no JS
621
- // runtime, the broad catch there swallowed the failure, and the push was gone for
622
- // good - the cache that would have replayed it on the next init() had just been
623
- // cleared above. Cleared, the same push is cached and the next init() replays it.
624
- sInitialized = false;
625
- INSTANCE = null;
649
+ // A push can be delivered on the FCM thread while the runtime is going down, and every
650
+ // other accessor of the field holds the lock.
651
+ synchronized (sStartPushLock) {
652
+ // Only if this module is still the live one. The next module publishes itself on the
653
+ // first call from JS and the order of the two events is not guaranteed, so a late
654
+ // teardown must not unseat a module that has already taken over.
655
+ if (INSTANCE == this) {
656
+ INSTANCE = null;
657
+ }
626
658
  }
627
659
  }
628
660
 
@@ -635,12 +667,21 @@ public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEve
635
667
 
636
668
  try {
637
669
  synchronized (sStartPushLock) {
638
- sStartPushData = pushData;
639
- if (sPushCallbackRegistered) {
640
- mEventDispatcher.dispatchEvent(PUSH_OPEN_EVENT, ConversionUtil.toWritableMap(ConversionUtil.stringToJSONObject(pushData)));
670
+ PendingPush push = new PendingPush(pushData);
671
+ sStartPush = push;
672
+
673
+ PushwooshPlugin plugin = INSTANCE;
674
+ if (plugin == null || !plugin.hasLiveRuntime()) {
675
+ return;
676
+ }
677
+
678
+ if (plugin.mPushCallbackRegistered) {
679
+ plugin.mEventDispatcher.dispatchEvent(PUSH_OPEN_EVENT, ConversionUtil.toWritableMap(ConversionUtil.stringToJSONObject(pushData)));
680
+ push.sentToCallback = true;
641
681
  }
642
- if (sInitialized && INSTANCE != null) {
643
- INSTANCE.sendEvent(PUSH_OPEN_JS_EVENT, ConversionUtil.stringToJSONObject(pushData));
682
+ if (plugin.mInitialized) {
683
+ plugin.sendEvent(PUSH_OPEN_JS_EVENT, ConversionUtil.stringToJSONObject(pushData));
684
+ push.sentAsEvent = true;
644
685
  }
645
686
  }
646
687
  } catch (Exception e) {
@@ -653,17 +694,33 @@ public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEve
653
694
  mEventDispatcher.sendJSEvent(getReactApplicationContext(), event, ConversionUtil.toWritableMap(params));
654
695
  }
655
696
 
697
+ // The React instance behind the module is still running. One on its way down is not: emitting
698
+ // into it throws, the catch around the callers swallows the failure, and the push is gone.
699
+ // Held back, it stays cached for the next init(). Callers hold sStartPushLock.
700
+ private boolean hasLiveRuntime() {
701
+ return getReactApplicationContext().hasActiveReactInstance();
702
+ }
703
+
656
704
  static void messageReceived(String pushData) {
657
705
  PWLog.info(TAG, "Push received: " + pushData);
658
706
 
659
707
  try {
660
708
  synchronized (sStartPushLock) {
661
- sReceivedPushData = pushData;
662
- if (sReceivedPushCallbackRegistered) {
663
- mEventDispatcher.dispatchEvent(PUSH_RECEIVED_EVENT, ConversionUtil.toWritableMap(ConversionUtil.stringToJSONObject(pushData)));
709
+ PendingPush push = new PendingPush(pushData);
710
+ sReceivedPush = push;
711
+
712
+ PushwooshPlugin plugin = INSTANCE;
713
+ if (plugin == null || !plugin.hasLiveRuntime()) {
714
+ return;
715
+ }
716
+
717
+ if (plugin.mReceivedPushCallbackRegistered) {
718
+ plugin.mEventDispatcher.dispatchEvent(PUSH_RECEIVED_EVENT, ConversionUtil.toWritableMap(ConversionUtil.stringToJSONObject(pushData)));
719
+ push.sentToCallback = true;
664
720
  }
665
- if (sInitialized && INSTANCE != null) {
666
- INSTANCE.sendEvent(PUSH_RECEIVED_JS_EVENT, ConversionUtil.stringToJSONObject(pushData));
721
+ if (plugin.mInitialized) {
722
+ plugin.sendEvent(PUSH_RECEIVED_JS_EVENT, ConversionUtil.stringToJSONObject(pushData));
723
+ push.sentAsEvent = true;
667
724
  }
668
725
  }
669
726
  } catch (Exception e) {