react-native-insider 8.1.0 → 9.0.0

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.
Files changed (48) hide show
  1. package/README.md +89 -660
  2. package/RNInsider.podspec +33 -12
  3. package/android/build.gradle +16 -2
  4. package/android/src/main/java/com/useinsider/react/RNInsiderAppFrameViewManager.java +253 -0
  5. package/android/src/main/java/com/useinsider/react/RNInsiderModule.java +238 -174
  6. package/android/src/main/java/com/useinsider/react/RNInsiderPackage.java +5 -1
  7. package/android/src/main/java/com/useinsider/react/RNUtils.java +111 -0
  8. package/babel.config.js +4 -1
  9. package/index.d.ts +196 -161
  10. package/index.js +60 -11
  11. package/ios/RNInsider/RNInsider.m +245 -341
  12. package/ios/RNInsider/RNInsiderAppFrameView.h +33 -0
  13. package/ios/RNInsider/RNInsiderAppFrameView.mm +315 -0
  14. package/ios/RNInsider/RNNotificationHandler.h +4 -1
  15. package/ios/RNInsider/RNNotificationHandler.m +16 -18
  16. package/ios/RNInsider/RNUtils.h +2 -0
  17. package/ios/RNInsider/RNUtils.m +79 -0
  18. package/package.json +43 -12
  19. package/src/EventHandler.js +17 -0
  20. package/src/InsiderAppFrame.d.ts +45 -0
  21. package/src/InsiderAppFrame.js +265 -0
  22. package/src/InsiderAppFrameLogic.d.ts +35 -0
  23. package/src/InsiderAppFrameLogic.js +192 -0
  24. package/src/InsiderAppFrameStatus.d.ts +22 -0
  25. package/src/InsiderAppFrameStatus.js +35 -0
  26. package/src/InsiderAppFramesError.d.ts +25 -0
  27. package/src/InsiderAppFramesError.js +77 -0
  28. package/src/InsiderEvent.js +1 -0
  29. package/src/InsiderEvents.d.ts +93 -0
  30. package/src/InsiderEvents.js +124 -0
  31. package/src/InsiderIdentifier.js +1 -1
  32. package/src/InsiderUser.d.ts +3 -2
  33. package/src/InsiderUser.js +27 -0
  34. package/src/Util.js +2 -1
  35. package/src/specs/InsiderAppFrameNativeComponent.ts +54 -0
  36. package/.orbit-output/config/MASTER.md +0 -129
  37. package/.orbit-output/config/architecture/API_DOCS.md +0 -342
  38. package/.orbit-output/config/architecture/ARCHITECTURE.md +0 -209
  39. package/.orbit-output/config/architecture/CODE-CATALOG.md +0 -172
  40. package/.orbit-output/config/architecture/project.yaml +0 -138
  41. package/.orbit-output/config/automation/AUTOMATION-SETUP.md +0 -50
  42. package/.orbit-output/config/enforcement/ENFORCEMENT-RULES.md +0 -41
  43. package/.orbit-output/config/enforcement/validate.sh +0 -49
  44. package/.orbit-output/skills/custom/academy-doc-generator.md +0 -598
  45. package/android/.settings/org.eclipse.buildship.core.prefs +0 -13
  46. package/android/src/test/java/com/useinsider/react/RNInsiderModuleTest.java +0 -169
  47. package/docs/app-cards/app-cards_new.md +0 -569
  48. package/ios/RNInsiderTests/RNInsiderTriggerPushProcessTests.m +0 -91
@@ -7,13 +7,16 @@ import android.os.Looper;
7
7
  import android.content.Intent;
8
8
  import android.net.Uri;
9
9
 
10
+ import com.facebook.react.bridge.Arguments;
10
11
  import com.facebook.react.bridge.Callback;
11
12
  import com.facebook.react.bridge.ReactApplicationContext;
13
+ import com.facebook.react.bridge.ReactContext;
12
14
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
13
15
  import com.facebook.react.bridge.ReactMethod;
14
16
  import com.facebook.react.bridge.ReadableArray;
15
17
  import com.facebook.react.bridge.ReadableMap;
16
18
  import com.facebook.react.bridge.WritableMap;
19
+ import com.facebook.react.bridge.WritableNativeMap;
17
20
  import com.facebook.react.modules.core.DeviceEventManagerModule;
18
21
  import com.google.firebase.messaging.RemoteMessage;
19
22
  import com.useinsider.insider.Insider;
@@ -31,10 +34,12 @@ import com.useinsider.insider.RecommendationEngine;
31
34
  import com.useinsider.insiderhybrid.InsiderHybrid;
32
35
  import com.useinsider.insiderhybrid.constants.InsiderHybridMethods;
33
36
  import com.useinsider.insider.InsiderIDListener;
37
+ import com.useinsider.insider.InsiderEventListener;
34
38
 
35
39
  import org.json.JSONArray;
36
40
  import org.json.JSONObject;
37
41
 
42
+ import java.lang.reflect.Method;
38
43
  import java.util.Date;
39
44
  import java.util.HashMap;
40
45
  import java.util.Map;
@@ -48,6 +53,11 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
48
53
 
49
54
  private InsiderIDListener insiderIDListener;
50
55
 
56
+ // Written on the NativeModules thread, read again from the posted main-looper Runnable
57
+ // whose identity check decides whether to register. volatile makes that read's visibility
58
+ // explicit rather than relying on the happens-before edge Handler.post happens to give us.
59
+ private volatile InsiderEventListener insiderEventListener;
60
+
51
61
  public RNInsiderModule(ReactApplicationContext reactContext) {
52
62
  super(reactContext);
53
63
  this.reactContext = reactContext;
@@ -58,40 +68,69 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
58
68
  return "RNInsider";
59
69
  }
60
70
 
61
- @ReactMethod
62
- public void setName(String name) {
71
+ private static final Method emitDeviceEventMethod = resolveEmitDeviceEvent();
72
+
73
+ private static Method resolveEmitDeviceEvent() {
63
74
  try {
64
- Insider.Instance.getCurrentUser().setName(name);
65
- } catch (Exception e) {
66
- Insider.Instance.putException(e);
75
+ return ReactContext.class.getMethod("emitDeviceEvent", String.class, Object.class);
76
+ } catch (NoSuchMethodException e) {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ private void emitDeviceEventCompat(String eventName, Object payload) {
82
+ ReactApplicationContext context = getReactApplicationContext();
83
+
84
+ if (emitDeviceEventMethod != null) {
85
+ try {
86
+ emitDeviceEventMethod.invoke(context, eventName, payload);
87
+ return;
88
+ } catch (Exception e) {
89
+ Insider.Instance.putException(e);
90
+ return;
91
+ }
92
+ }
93
+
94
+ DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
95
+ context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
96
+
97
+ if (emitter != null) {
98
+ emitter.emit(eventName, payload);
67
99
  }
68
100
  }
69
101
 
70
102
  @ReactMethod
71
- public void init(final String partnerName, final String sdkVersion) {
103
+ public void setName(String name) {
104
+ Insider.Instance.getCurrentUser().setName(name);
105
+ }
106
+
107
+ @ReactMethod
108
+ public void init(final String partnerName, final String sdkVersion, final String appIdentifier) {
72
109
  try {
73
110
  if (Insider.Instance.isSDKInitialized()) {
74
111
  return;
75
112
  }
113
+ final String app = resolveAppIdentifier(appIdentifier);
76
114
  new Handler(Looper.getMainLooper()).post(new Runnable() {
77
115
  @Override
78
116
  public void run() {
79
117
  Insider.Instance.registerInsiderCallback(new InsiderCallback() {
80
- DeviceEventManagerModule.RCTDeviceEventEmitter emitter = getReactApplicationContext()
81
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
82
-
83
118
  @Override
84
119
  public void doAction(JSONObject jsonObject, InsiderCallbackType insiderCallbackType) {
85
120
  try {
86
121
  WritableMap callbackData = RNUtils.convertJSONObjectToMap(jsonObject);
87
- emitter.emit(insiderCallbackType.name(), callbackData);
122
+ emitDeviceEventCompat(insiderCallbackType.name(), callbackData);
88
123
  } catch (Exception e) {
89
124
  Insider.Instance.putException(e);
90
125
  }
91
126
  }
92
127
  });
93
128
 
94
- Insider.Instance.init(((Application) reactContext.getApplicationContext()), partnerName);
129
+ if (app != null) {
130
+ Insider.Instance.init(((Application) reactContext.getApplicationContext()), partnerName, app);
131
+ } else {
132
+ Insider.Instance.init(((Application) reactContext.getApplicationContext()), partnerName);
133
+ }
95
134
  Insider.Instance.setSDKType("react-native");
96
135
  Insider.Instance.setHybridSDKVersion(sdkVersion);
97
136
 
@@ -109,22 +148,18 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
109
148
  }
110
149
  }
111
150
 
151
+ private String resolveAppIdentifier(String app) {
152
+ return app != null && !app.isEmpty() ? app : null;
153
+ }
154
+
112
155
  @ReactMethod
113
156
  public void reinitWithPartnerName(String partnerName) {
114
- try {
115
- Insider.Instance.reinitWithPartnerName(partnerName);
116
- } catch (Exception e) {
117
- Insider.Instance.putException(e);
118
- }
157
+ Insider.Instance.reinitWithPartnerName(partnerName);
119
158
  }
120
159
 
121
160
  @ReactMethod
122
161
  public void setCustomEndpoint(String endpoint) {
123
- try {
124
- Insider.Instance.setCustomEndpoint(endpoint);
125
- } catch (Exception e) {
126
- Insider.Instance.putException(e);
127
- }
162
+ Insider.Instance.setCustomEndpoint(endpoint);
128
163
  }
129
164
 
130
165
  @ReactMethod
@@ -138,11 +173,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
138
173
 
139
174
  @ReactMethod
140
175
  public void setGender(int gender) {
141
- try {
142
- InsiderHybrid.setGender(gender);
143
- } catch (Exception e) {
144
- Insider.Instance.putException(e);
145
- }
176
+ InsiderHybrid.setGender(gender);
146
177
  }
147
178
 
148
179
  @ReactMethod
@@ -159,116 +190,73 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
159
190
 
160
191
  @ReactMethod
161
192
  public void setSurname(String surname) {
162
- try {
163
- Insider.Instance.getCurrentUser().setSurname(surname);
164
- } catch (Exception e) {
165
- Insider.Instance.putException(e);
166
- }
193
+ Insider.Instance.getCurrentUser().setSurname(surname);
167
194
  }
168
195
 
169
196
  @ReactMethod
170
197
  public void setAge(int age) {
171
- try {
172
- Insider.Instance.getCurrentUser().setAge(age);
173
- } catch (Exception e) {
174
- Insider.Instance.putException(e);
175
- }
198
+ Insider.Instance.getCurrentUser().setAge(age);
176
199
  }
177
200
 
178
201
  @ReactMethod
179
202
  public void setLanguage(String language) {
180
- try {
181
- Insider.Instance.getCurrentUser().setLanguage(language);
182
- } catch (Exception e) {
183
- Insider.Instance.putException(e);
184
- }
203
+ Insider.Instance.getCurrentUser().setLanguage(language);
185
204
  }
186
205
 
187
206
  @ReactMethod
188
207
  public void setLocale(String locale) {
189
- try {
190
- Insider.Instance.getCurrentUser().setLocale(locale);
191
- } catch (Exception e) {
192
- Insider.Instance.putException(e);
193
- }
208
+ Insider.Instance.getCurrentUser().setLocale(locale);
194
209
  }
195
210
 
196
211
  @ReactMethod
197
212
  public void setFacebookID(String facebookID) {
198
- try {
199
- Insider.Instance.getCurrentUser().setFacebookID(facebookID);
200
- } catch (Exception e) {
201
- Insider.Instance.putException(e);
202
- }
213
+ Insider.Instance.getCurrentUser().setFacebookID(facebookID);
203
214
  }
204
215
 
205
216
  @ReactMethod
206
217
  public void setTwitterID(String twitterID) {
207
- try {
208
- Insider.Instance.getCurrentUser().setTwitterID(twitterID);
209
- } catch (Exception e) {
210
- Insider.Instance.putException(e);
211
- }
218
+ Insider.Instance.getCurrentUser().setTwitterID(twitterID);
212
219
  }
213
220
 
214
221
  @ReactMethod
215
222
  public void setEmailOptin(boolean emailOptin) {
216
- try {
217
- Insider.Instance.getCurrentUser().setEmailOptin(emailOptin);
218
- } catch (Exception e) {
219
- Insider.Instance.putException(e);
220
- }
223
+ Insider.Instance.getCurrentUser().setEmailOptin(emailOptin);
221
224
  }
222
225
 
223
226
  @ReactMethod
224
227
  public void setEmail(String email) {
225
- try {
226
- Insider.Instance.getCurrentUser().setEmail(email);
227
- } catch (Exception e) {
228
- Insider.Instance.putException(e);
229
- }
228
+ Insider.Instance.getCurrentUser().setEmail(email);
230
229
  }
231
230
 
232
231
  @ReactMethod
233
232
  public void setSMSOptin(boolean smsOptin) {
234
- try {
235
- Insider.Instance.getCurrentUser().setSMSOptin(smsOptin);
236
- } catch (Exception e) {
237
- Insider.Instance.putException(e);
238
- }
233
+ Insider.Instance.getCurrentUser().setSMSOptin(smsOptin);
239
234
  }
240
235
 
241
236
  @ReactMethod
242
237
  public void setPhoneNumber(String phoneNumber) {
243
- try {
244
- Insider.Instance.getCurrentUser().setPhoneNumber(phoneNumber);
245
- } catch (Exception e) {
246
- Insider.Instance.putException(e);
247
- }
238
+ Insider.Instance.getCurrentUser().setPhoneNumber(phoneNumber);
248
239
  }
249
240
 
250
241
  @ReactMethod
251
242
  public void setPushOptin(boolean pushOptin) {
252
- try {
253
- Insider.Instance.getCurrentUser().setPushOptin(pushOptin);
254
- } catch (Exception e) {
255
- Insider.Instance.putException(e);
256
- }
243
+ Insider.Instance.getCurrentUser().setPushOptin(pushOptin);
257
244
  }
258
245
 
259
246
  @ReactMethod
260
247
  public void setLocationOptin(boolean locationOptin) {
261
- try {
262
- Insider.Instance.getCurrentUser().setLocationOptin(locationOptin);
263
- } catch (Exception e) {
264
- Insider.Instance.putException(e);
265
- }
248
+ Insider.Instance.getCurrentUser().setLocationOptin(locationOptin);
266
249
  }
267
250
 
268
251
  @ReactMethod
269
252
  public void setWhatsappOptin(boolean whatsappOptin) {
253
+ Insider.Instance.getCurrentUser().setWhatsappOptin(whatsappOptin);
254
+ }
255
+
256
+ @ReactMethod
257
+ public void setPromotionalOptin(boolean promotionalOptin) {
270
258
  try {
271
- Insider.Instance.getCurrentUser().setWhatsappOptin(whatsappOptin);
259
+ Insider.Instance.getCurrentUser().setPromotionalOptin(promotionalOptin);
272
260
  } catch (Exception e) {
273
261
  Insider.Instance.putException(e);
274
262
  }
@@ -276,13 +264,9 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
276
264
 
277
265
  @ReactMethod
278
266
  public void login(ReadableMap identifiers) {
279
- try {
280
- InsiderIdentifiers setIdentifiers = setIdentifiers(identifiers);
267
+ InsiderIdentifiers setIdentifiers = setIdentifiers(identifiers);
281
268
 
282
- Insider.Instance.getCurrentUser().login(setIdentifiers);
283
- } catch (Exception e) {
284
- Insider.Instance.putException(e);
285
- }
269
+ Insider.Instance.getCurrentUser().login(setIdentifiers);
286
270
  }
287
271
 
288
272
  @ReactMethod
@@ -302,6 +286,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
302
286
  });
303
287
  } catch (Exception e) {
304
288
  Insider.Instance.putException(e);
289
+ RNInsiderIDResult.invoke((String) null);
305
290
  }
306
291
  }
307
292
 
@@ -335,11 +320,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
335
320
 
336
321
  @ReactMethod
337
322
  public void logout() {
338
- try {
339
- Insider.Instance.getCurrentUser().logout();
340
- } catch (Exception e) {
341
- Insider.Instance.putException(e);
342
- }
323
+ Insider.Instance.getCurrentUser().logout();
343
324
  }
344
325
 
345
326
  @ReactMethod
@@ -363,6 +344,9 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
363
344
  }
364
345
  } catch (Exception e) {
365
346
  Insider.Instance.putException(e);
347
+ if (insiderIDResult != null) {
348
+ insiderIDResult.invoke((String) null);
349
+ }
366
350
  }
367
351
  }
368
352
 
@@ -381,38 +365,22 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
381
365
 
382
366
  @ReactMethod
383
367
  public void setCustomAttributeWithString(String key, String value) {
384
- try {
385
- Insider.Instance.getCurrentUser().setCustomAttributeWithString(key, value);
386
- } catch (Exception e) {
387
- Insider.Instance.putException(e);
388
- }
368
+ Insider.Instance.getCurrentUser().setCustomAttributeWithString(key, value);
389
369
  }
390
370
 
391
371
  @ReactMethod
392
372
  public void setCustomAttributeWithInt(String key, int value) {
393
- try {
394
- Insider.Instance.getCurrentUser().setCustomAttributeWithInt(key, value);
395
- } catch (Exception e) {
396
- Insider.Instance.putException(e);
397
- }
373
+ Insider.Instance.getCurrentUser().setCustomAttributeWithInt(key, value);
398
374
  }
399
375
 
400
376
  @ReactMethod
401
377
  public void setCustomAttributeWithDouble(String key, double value) {
402
- try {
403
- Insider.Instance.getCurrentUser().setCustomAttributeWithDouble(key, value);
404
- } catch (Exception e) {
405
- Insider.Instance.putException(e);
406
- }
378
+ Insider.Instance.getCurrentUser().setCustomAttributeWithDouble(key, value);
407
379
  }
408
380
 
409
381
  @ReactMethod
410
382
  public void setCustomAttributeWithBoolean(String key, boolean value) {
411
- try {
412
- Insider.Instance.getCurrentUser().setCustomAttributeWithBoolean(key, value);
413
- } catch (Exception e) {
414
- Insider.Instance.putException(e);
415
- }
383
+ Insider.Instance.getCurrentUser().setCustomAttributeWithBoolean(key, value);
416
384
  }
417
385
 
418
386
  @ReactMethod
@@ -438,11 +406,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
438
406
 
439
407
  @ReactMethod
440
408
  public void unsetCustomAttribute(String key) {
441
- try {
442
- Insider.Instance.getCurrentUser().unsetCustomAttribute(key);
443
- } catch (Exception e) {
444
- Insider.Instance.putException(e);
445
- }
409
+ Insider.Instance.getCurrentUser().unsetCustomAttribute(key);
446
410
  }
447
411
 
448
412
  @ReactMethod
@@ -450,22 +414,14 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
450
414
  new Handler(Looper.getMainLooper()).post(new Runnable() {
451
415
  @Override
452
416
  public void run() {
453
- try {
454
- Insider.Instance.setGDPRConsent(gdprConsent);
455
- } catch (Exception e) {
456
- Insider.Instance.putException(e);
457
- }
417
+ Insider.Instance.setGDPRConsent(gdprConsent);
458
418
  }
459
419
  });
460
420
  }
461
421
 
462
422
  @ReactMethod
463
423
  public void setMobileAppAccess(final boolean mobileAppAccess) {
464
- try {
465
- Insider.Instance.setMobileAppAccess(mobileAppAccess);
466
- } catch (Exception e) {
467
- Insider.Instance.putException(e);
468
- }
424
+ Insider.Instance.setMobileAppAccess(mobileAppAccess);
469
425
  }
470
426
 
471
427
  @ReactMethod
@@ -580,11 +536,11 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
580
536
 
581
537
  @ReactMethod
582
538
  public void getMessageCenterData(int limit, String startDate, String endDate, final Callback messageCenterCallback) {
583
- // React Native's bridge does not support long type directly, so we use String to pass epoch time.
584
- long startDateEpoch = Long.parseLong(startDate);
585
- long endDateEpoch = Long.parseLong(endDate);
586
-
587
539
  try {
540
+ // React Native's bridge does not support long type directly, so we use String to pass epoch time.
541
+ long startDateEpoch = Long.parseLong(startDate);
542
+ long endDateEpoch = Long.parseLong(endDate);
543
+
588
544
  InsiderHybrid.getMessageCenterData(limit, startDateEpoch, endDateEpoch, new MessageCenterData() {
589
545
  @Override
590
546
  public void loadMessageCenterData(JSONArray messageCenterData) {
@@ -597,15 +553,16 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
597
553
  });
598
554
  } catch (Exception e) {
599
555
  Insider.Instance.putException(e);
556
+ messageCenterCallback.invoke("[]");
600
557
  }
601
558
  }
602
559
 
603
560
  @ReactMethod
604
561
  public void getMessageCenterDataWithIdentifiers(int limit, String startDate, String endDate, ReadableMap identifiers, final Callback messageCenterCallback) {
605
- long startDateEpoch = Long.parseLong(startDate);
606
- long endDateEpoch = Long.parseLong(endDate);
607
-
608
562
  try {
563
+ long startDateEpoch = Long.parseLong(startDate);
564
+ long endDateEpoch = Long.parseLong(endDate);
565
+
609
566
  Insider.Instance.getMessageCenterData(
610
567
  limit,
611
568
  new Date(startDateEpoch),
@@ -623,6 +580,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
623
580
  });
624
581
  } catch (Exception e) {
625
582
  Insider.Instance.putException(e);
583
+ messageCenterCallback.invoke("[]");
626
584
  }
627
585
  }
628
586
 
@@ -643,6 +601,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
643
601
  });
644
602
  } catch (Exception e) {
645
603
  Insider.Instance.putException(e);
604
+ smartRecommendation.invoke(Arguments.createMap());
646
605
  }
647
606
  }
648
607
 
@@ -664,6 +623,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
664
623
  });
665
624
  } catch (Exception e) {
666
625
  Insider.Instance.putException(e);
626
+ callback.invoke(Arguments.createMap());
667
627
  }
668
628
  }
669
629
 
@@ -685,6 +645,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
685
645
  });
686
646
  } catch (Exception e) {
687
647
  Insider.Instance.putException(e);
648
+ callback.invoke(Arguments.createMap());
688
649
  }
689
650
  }
690
651
 
@@ -758,6 +719,15 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
758
719
  }
759
720
  }
760
721
 
722
+ @ReactMethod
723
+ public void markContentOptimizerAsSeen(String variableName) {
724
+ try {
725
+ Insider.Instance.markContentOptimizerAsSeen(variableName);
726
+ } catch (Exception e) {
727
+ Insider.Instance.putException(e);
728
+ }
729
+ }
730
+
761
731
 
762
732
  @ReactMethod
763
733
  public void visitHomePage(ReadableArray customParameters) {
@@ -863,11 +833,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
863
833
  new Handler(Looper.getMainLooper()).post(new Runnable() {
864
834
  @Override
865
835
  public void run() {
866
- try {
867
- Insider.Instance.startTrackingGeofence();
868
- } catch (Exception e) {
869
- Insider.Instance.putException(e);
870
- }
836
+ Insider.Instance.startTrackingGeofence();
871
837
  }
872
838
  });
873
839
  }
@@ -944,12 +910,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
944
910
 
945
911
  @ReactMethod
946
912
  public void putErrorLog(String exception) {
947
- try {
948
- Exception exceptionObject = new Exception(exception);
949
- Insider.Instance.putException(exceptionObject);
950
- } catch (Exception ignored) {
951
-
952
- }
913
+ Insider.Instance.putException(new Exception(exception));
953
914
  }
954
915
 
955
916
  @ReactMethod
@@ -966,11 +927,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
966
927
  new Handler(Looper.getMainLooper()).post(new Runnable() {
967
928
  @Override
968
929
  public void run() {
969
- try {
970
- Insider.Instance.enableLocationCollection(consentStatus);
971
- } catch (Exception e) {
972
- Insider.Instance.putException(e);
973
- }
930
+ Insider.Instance.enableLocationCollection(consentStatus);
974
931
  }
975
932
  });
976
933
  }
@@ -980,11 +937,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
980
937
  new Handler(Looper.getMainLooper()).post(new Runnable() {
981
938
  @Override
982
939
  public void run() {
983
- try {
984
- Insider.Instance.enableIpCollection(consentStatus);
985
- } catch (Exception e) {
986
- Insider.Instance.putException(e);
987
- }
940
+ Insider.Instance.enableIpCollection(consentStatus);
988
941
  }
989
942
  });
990
943
  }
@@ -994,11 +947,7 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
994
947
  new Handler(Looper.getMainLooper()).post(new Runnable() {
995
948
  @Override
996
949
  public void run() {
997
- try {
998
- Insider.Instance.enableCarrierCollection(consentStatus);
999
- } catch (Exception e) {
1000
- Insider.Instance.putException(e);
1001
- }
950
+ Insider.Instance.enableCarrierCollection(consentStatus);
1002
951
  }
1003
952
  });
1004
953
  }
@@ -1068,12 +1017,9 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
1068
1017
  @Override
1069
1018
  public void run() {
1070
1019
  insiderIDListener = new InsiderIDListener() {
1071
- DeviceEventManagerModule.RCTDeviceEventEmitter emitter = getReactApplicationContext()
1072
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
1073
-
1074
1020
  @Override
1075
1021
  public void onUpdated(String insiderID) {
1076
- emitter.emit("INSIDER_ID_LISTENER", insiderID);
1022
+ emitDeviceEventCompat("INSIDER_ID_LISTENER", insiderID);
1077
1023
  }
1078
1024
  };
1079
1025
 
@@ -1087,24 +1033,142 @@ public class RNInsiderModule extends ReactContextBaseJavaModule {
1087
1033
  }
1088
1034
  }
1089
1035
 
1036
+ /**
1037
+ * Registers a single {@link InsiderEventListener} that forwards every recorded event to
1038
+ * JS as {@code INSIDER_EVENT_RECORDED}. The JS layer fans out to its own subscribers, so
1039
+ * this bridge only ever needs one native observer — the null-guard makes repeat calls
1040
+ * idempotent.
1041
+ *
1042
+ * <p>Note the SDK delivers on its own background thread; only the REGISTRATION is hopped
1043
+ * onto the main looper (matching {@link #registerInsiderIDListener()}). The
1044
+ * {@code parameters} map handed to us is unmodifiable, so we only read from it.
1045
+ *
1046
+ * <p>{@code Insider.Instance.events()} returns a no-op instance before SDK init, so it is
1047
+ * resolved fresh here at registration time rather than cached in a field.
1048
+ */
1090
1049
  @ReactMethod
1091
- public void disableInAppMessages() {
1050
+ public void registerInsiderEventListener() {
1092
1051
  try {
1093
- Insider.Instance.disableInAppMessages();
1052
+ // The guard and the field assignment BOTH happen synchronously on the calling
1053
+ // thread. Deferring the assignment into the posted Runnable would let the guard
1054
+ // race unregisterInsiderEventListener(), which reads the field synchronously:
1055
+ // a subscribe-then-immediate-unsubscribe would see null, no-op, and then leave
1056
+ // the Runnable to register an observer JS believes is gone; a subscribe burst
1057
+ // would see null twice and addObserver() twice, double-delivering every event.
1058
+ // Only the SDK call itself is hopped onto the main looper.
1059
+ if (insiderEventListener != null) return;
1060
+
1061
+ final InsiderEventListener listener = new InsiderEventListener() {
1062
+ @Override
1063
+ public void onEventRecorded(String name, Map<String, Object> parameters, long timestamp) {
1064
+ try {
1065
+ WritableMap payload = new WritableNativeMap();
1066
+ payload.putString("name", name);
1067
+ payload.putMap("parameters", RNUtils.convertObjectMapToWritableMap(parameters));
1068
+ // Passed through in WHOLE SECONDS, matching the native SDK.
1069
+ // JS must do `new Date(timestamp * 1000)`.
1070
+ payload.putDouble("timestamp", (double) timestamp);
1071
+
1072
+ // THREADING (matches the note in iOS RNInsider.m — do not
1073
+ // re-litigate): this runs on the SDK's listener thread and
1074
+ // needs no manual hop. RCTDeviceEventEmitter.emit is a
1075
+ // JS-module proxy call; the bridge enqueues it onto the JS
1076
+ // thread itself, in order, so emitting from here is safe.
1077
+ //
1078
+ // The emitter is resolved per emit rather than cached: a dev
1079
+ // reload or Activity recreation replaces the catalyst instance,
1080
+ // and a cached proxy would then target a dead one and silently
1081
+ // stop delivering. emitDeviceEventCompat also covers bridgeless,
1082
+ // where getJSModule may return null.
1083
+ emitDeviceEventCompat("INSIDER_EVENT_RECORDED", payload);
1084
+ } catch (Exception e) {
1085
+ Insider.Instance.putException(e);
1086
+ }
1087
+ }
1088
+ };
1089
+
1090
+ insiderEventListener = listener;
1091
+
1092
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
1093
+ @Override
1094
+ public void run() {
1095
+ try {
1096
+ // An unregister may have landed while this was queued. Only register
1097
+ // if this listener is still the current one, so we never revive an
1098
+ // observer the JS side has already dropped.
1099
+ if (insiderEventListener != listener) return;
1100
+
1101
+ Insider.Instance.events().addObserver(listener);
1102
+ } catch (Exception e) {
1103
+ if (insiderEventListener == listener) insiderEventListener = null;
1104
+ Insider.Instance.putException(e);
1105
+ }
1106
+ }
1107
+ });
1094
1108
  } catch (Exception e) {
1095
1109
  Insider.Instance.putException(e);
1096
1110
  }
1097
1111
  }
1098
1112
 
1113
+ /**
1114
+ * Removes the observer registered by {@link #registerInsiderEventListener()}. Android
1115
+ * holds listeners STRONGLY, so this teardown is required to avoid leaking the listener
1116
+ * for the lifetime of the process.
1117
+ *
1118
+ * <p>The field is cleared synchronously so a register that is still queued sees the
1119
+ * identity change and cancels itself. Both methods post to the same looper, so the SDK
1120
+ * add/remove calls also stay in submission order.
1121
+ */
1099
1122
  @ReactMethod
1100
- public void enableInAppMessages() {
1123
+ public void unregisterInsiderEventListener() {
1101
1124
  try {
1102
- Insider.Instance.enableInAppMessages();
1125
+ if (insiderEventListener != null) {
1126
+ final InsiderEventListener listenerToRemove = insiderEventListener;
1127
+ insiderEventListener = null;
1128
+
1129
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
1130
+ @Override
1131
+ public void run() {
1132
+ try {
1133
+ Insider.Instance.events().removeObserver(listenerToRemove);
1134
+ } catch (Exception e) {
1135
+ Insider.Instance.putException(e);
1136
+ }
1137
+ }
1138
+ });
1139
+ }
1103
1140
  } catch (Exception e) {
1104
1141
  Insider.Instance.putException(e);
1105
1142
  }
1106
1143
  }
1107
1144
 
1145
+ /**
1146
+ * Tears the event observer down when the bridge itself goes away — a dev reload or an
1147
+ * Activity recreation destroys the catalyst instance without JS ever getting to call
1148
+ * {@link #unregisterInsiderEventListener()}.
1149
+ *
1150
+ * <p>This matters because Android holds observers STRONGLY: without this the listener
1151
+ * survives the bridge, keeps the dead {@code ReactApplicationContext} alive through it,
1152
+ * and every subsequent reload stacks another observer onto the SDK. The delegation keeps
1153
+ * the teardown sequence (synchronous field clear, then the main-looper removeObserver) in
1154
+ * one place rather than duplicating it here.
1155
+ */
1156
+ @Override
1157
+ public void invalidate() {
1158
+ unregisterInsiderEventListener();
1159
+ super.invalidate();
1160
+ }
1161
+
1162
+ @ReactMethod
1163
+ public void disableInAppMessages() {
1164
+ Insider.Instance.disableInAppMessages();
1165
+ }
1166
+
1167
+ @ReactMethod
1168
+ public void enableInAppMessages() {
1169
+ Insider.Instance.enableInAppMessages();
1170
+ }
1171
+
1108
1172
  @ReactMethod
1109
1173
  public void getAppCardsCampaigns(final Callback callback) {
1110
1174
  try {