reactnative-plugin-appice 1.7.43 → 1.7.44

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.
@@ -0,0 +1,60 @@
1
+ // ===============================
2
+ // TransactionalCampaign
3
+ // ===============================
4
+ const Campaign = require('./campaign');
5
+
6
+ class TransactionalCampaign extends Campaign {
7
+ constructor() {
8
+ super();
9
+ this._data.id = null;
10
+ this._data.p = null;
11
+ this._data.name = null;
12
+ this._data.custom = null;
13
+ this._data.delay = 0;
14
+ this._data.users = [];
15
+ this._data.emails = [];
16
+ this._data.msisdns = [];
17
+ this._data.tokens = [];
18
+ this._data.segment = "";
19
+ this._data.payload = [];
20
+ }
21
+ set setCampId(v) {
22
+ this._data.id = v;
23
+ }
24
+ set setPlatform(v) {
25
+ this._data.p = v;
26
+ }
27
+ set setName(v) {
28
+ this._data.name = v;
29
+ }
30
+ set setCustom(v) {
31
+ this._data.custom = v;
32
+ }
33
+ set setDelay(v) {
34
+ this._data.delay = v;
35
+ }
36
+ set setUsers(v) {
37
+ this._data.users = v;
38
+ }
39
+ set setEmails(v) {
40
+ this._data.emails = v;
41
+ }
42
+ set setMsisdns(v) {
43
+ this._data.msisdns = v;
44
+ }
45
+ set setTokens(v) {
46
+ this._data.tokens = v;
47
+ }
48
+ set setSegment(v) {
49
+ this._data.segment = v;
50
+ }
51
+ addPayload(payloadObj) {
52
+ this._data.payload.push(payloadObj.toJSON());
53
+ }
54
+ toRequestPayload() {
55
+ return JSON.stringify(this._data);
56
+ }
57
+ }
58
+
59
+ export default TransactionalCampaign;
60
+ module.exports = TransactionalCampaign;
@@ -97,7 +97,7 @@ dependencies {
97
97
  annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.3.1"
98
98
 
99
99
  //appice
100
- implementation "appice.io.android:sdk:2.6.38"
100
+ implementation "appice.io.android:sdk:2.6.39"
101
101
 
102
102
  //glide
103
103
  implementation "com.github.bumptech.glide:glide:4.11.0"
@@ -1,5 +1,8 @@
1
1
  package com.appice.react;
2
2
 
3
+ import static com.appice.react.AppIceReactPluginModule.isDeviceReadyFlag;
4
+ import static com.appice.react.AppIceReactPluginModule.pendingEventQueue;
5
+
3
6
  import android.content.Context;
4
7
  import android.content.Intent;
5
8
  import android.os.Bundle;
@@ -8,7 +11,7 @@ import androidx.annotation.NonNull;
8
11
  import androidx.annotation.Nullable;
9
12
 
10
13
  import com.facebook.react.bridge.Arguments;
11
- import com.facebook.react.bridge.ReactContext;
14
+ import com.facebook.react.bridge.ReactApplicationContext;
12
15
  import com.facebook.react.bridge.ReadableArray;
13
16
  import com.facebook.react.bridge.ReadableMap;
14
17
  import com.facebook.react.bridge.ReadableMapKeySetIterator;
@@ -17,18 +20,18 @@ import com.facebook.react.bridge.WritableArray;
17
20
  import com.facebook.react.bridge.WritableMap;
18
21
  import com.facebook.react.modules.core.DeviceEventManagerModule;
19
22
 
20
- import java.util.ArrayDeque;
21
23
  import java.util.ArrayList;
22
- import java.util.Deque;
24
+ import java.util.Arrays;
23
25
  import java.util.HashMap;
24
26
  import java.util.Iterator;
27
+ import java.util.List;
25
28
  import java.util.Map;
26
- import java.util.concurrent.atomic.AtomicBoolean;
27
29
 
28
30
  import org.json.JSONArray;
29
31
  import org.json.JSONObject;
30
32
 
31
33
  import semusi.activitysdk.ContextSdk;
34
+ import semusi.context.counthandler.CampaignActionListener;
32
35
  import semusi.context.ui.appInbox.AppICEInboxMessage;
33
36
 
34
37
  import semusi.context.utility.Utility;
@@ -39,8 +42,6 @@ import semusi.ruleengine.pushmanager.NotificationClickHandler;
39
42
 
40
43
  public class AppICEUtils {
41
44
  private static final String TAG = "AppICEUtils";
42
- static final Deque<Event> EVENT_QUEUE = new ArrayDeque<>();
43
- static final AtomicBoolean IS_FLUSHING = new AtomicBoolean(false);
44
45
 
45
46
  static class Event {
46
47
  final String name;
@@ -52,86 +53,29 @@ public class AppICEUtils {
52
53
  }
53
54
  }
54
55
 
55
- static void sendEventHelper(String eventName, @Nullable WritableMap params, ReactContext context) {
56
- if (context == null || !context.hasActiveCatalystInstance()) {
57
- synchronized (EVENT_QUEUE) {
58
- WritableMap queued = null;
59
- if (params != null) {
60
- try {
61
- queued = cloneReadableMap(params);
62
- } catch (Exception ex) {
63
- printLog(TAG, "sendEvent: failed to clone params for queue " + eventName + " - " + ex.getMessage());
64
- queued = null;
65
- }
66
- }
67
- EVENT_QUEUE.add(new Event(eventName, queued));
68
- }
69
- return;
70
- }
71
56
 
72
- WritableMap emitCopy = null;
73
- if (params != null) {
74
- try {
75
- emitCopy = cloneReadableMap(params);
76
- } catch (Exception ex) {
77
- emitCopy = null;
78
- }
79
- }
80
-
81
- try {
82
- context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
83
- .emit(eventName, emitCopy);
84
- printLog(TAG, "Successfully emitted event: " + eventName);
85
- } catch (Exception e) {
86
- printLog(TAG, "emit failed for " + eventName + " - " + e.getMessage());
87
- synchronized (EVENT_QUEUE) {
88
- WritableMap queued = null;
89
- if (params != null) {
90
- try {
91
- queued = cloneReadableMap(params);
92
- } catch (Exception ex) {
93
- queued = null;
94
- }
95
- }
96
- EVENT_QUEUE.add(new Event(eventName, queued));
97
- }
98
- }
99
- }
100
-
101
- static void flushEventQueue(ReactContext reactContext) {
102
- if (reactContext == null || !reactContext.hasActiveCatalystInstance()) {
57
+ // removed old logic that became overcomplicated with AI. later on when I reworked think
58
+ //of using different approach which flushevent when flag got updated
59
+ protected static void flushEventQueue(ReactApplicationContext ctx) {
60
+ if (!isDeviceReadyFlag || pendingEventQueue.isEmpty()) {
61
+ printLog("ActionPushDebug flushEventQueue", "Nothing to flush. flag=" + isDeviceReadyFlag + " queueSize=" + pendingEventQueue.size());
103
62
  return;
104
63
  }
105
64
 
106
- if (!IS_FLUSHING.compareAndSet(false, true)) {
107
- return;
108
- }
65
+ printLog("ActionPushDebug flushEventQueue", "Flushing " + pendingEventQueue.size() + " events");
109
66
 
110
- try {
111
- synchronized (EVENT_QUEUE) {
112
- int initialSize = EVENT_QUEUE.size();
113
- while (!EVENT_QUEUE.isEmpty()) {
114
- Event event = EVENT_QUEUE.poll();
115
- try {
116
- WritableMap toEmit = null;
117
- if (event.params != null) {
118
- try {
119
- toEmit = cloneReadableMap(event.params);
120
- } catch (Exception ex) {
121
- toEmit = event.params;
122
- }
123
- }
124
- reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
125
- .emit(event.name, toEmit);
126
- printLog(TAG, "Successfully flushed event: " + event.name + ". Remaining in queue: " + EVENT_QUEUE.size());
127
- } catch (Exception e) {
128
- EVENT_QUEUE.addFirst(event);
129
- break;
130
- }
131
- }
67
+ List<Map<String, Object>> toFlush = new ArrayList<>(pendingEventQueue);
68
+ pendingEventQueue.clear();
69
+
70
+ for (Map<String, Object> event : toFlush) {
71
+ try {
72
+ String eventName = (String) event.get("eventName");
73
+ WritableMap params = (WritableMap) event.get("param");
74
+ ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
75
+ printLog("ActionPushDebug flushEventQueue", "Flushed: " + eventName);
76
+ } catch (Exception e) {
77
+ printLog("ActionPushDebug flushEventQueue", "Flush failed: " + e.getMessage());
132
78
  }
133
- } finally {
134
- IS_FLUSHING.set(false);
135
79
  }
136
80
  }
137
81
 
@@ -295,7 +239,6 @@ public class AppICEUtils {
295
239
  }
296
240
 
297
241
 
298
-
299
242
  public static WritableMap toWritableMap(Map<String, Object> map) {
300
243
  WritableMap writableMap = Arguments.createMap();
301
244
  Iterator iterator = map.entrySet().iterator();
@@ -452,7 +395,7 @@ public class AppICEUtils {
452
395
  }
453
396
  }
454
397
  return jsonObject;
455
- }catch (Throwable t){
398
+ } catch (Throwable t) {
456
399
  return null;
457
400
  }
458
401
  }
@@ -487,7 +430,7 @@ public class AppICEUtils {
487
430
  }
488
431
  }
489
432
  return jsonArray;
490
- }catch (Throwable t){
433
+ } catch (Throwable t) {
491
434
  return null;
492
435
  }
493
436
  }
@@ -496,9 +439,10 @@ public class AppICEUtils {
496
439
  System.out.println(TAG + ", " + message);
497
440
  }
498
441
 
499
- public static boolean handleAppICEPayload(@NonNull Intent intent, Context context){
442
+
443
+ public static boolean handleAppICEPayload(@NonNull Intent intent, Context context) {
500
444
  try {
501
- if (intent.getExtras() != null){
445
+ if (intent.getExtras() != null) {
502
446
  Bundle bundle = intent.getExtras();
503
447
  boolean isAppICEPush = bundle.getBoolean("ai_push", false);
504
448
  if (isAppICEPush) {
@@ -507,24 +451,24 @@ public class AppICEUtils {
507
451
  intent.removeExtra("ai_push");
508
452
  }
509
453
  }
510
- }catch (Throwable t){
511
- printLog("PUSH", " exception in click "+t);
454
+ } catch (Throwable t) {
455
+ printLog("PUSH", " exception in click " + t);
512
456
  }
513
457
  return false;
514
458
  }
515
459
 
516
460
  /**
517
461
  * Handles app link actions triggered from a notification click.
518
- *
462
+ * <p>
519
463
  * Flow:
520
464
  * 1. Extracts NotificationClickHandler from the incoming Intent.
521
465
  * 2. Retrieves the payload associated with the notification.
522
466
  * 3. Validates the payload before processing.
523
467
  * 4. If valid:
524
- * - Triggers deep link handling via ContextSdk.handleOpenLinkUrl().
525
- * - Logs the click event via ContextSdk.pushNotificationClicked().
526
- * - Clears the intent to prevent duplicate processing.
527
- *
468
+ * - Triggers deep link handling via ContextSdk.handleOpenLinkUrl().
469
+ * - Logs the click event via ContextSdk.pushNotificationClicked().
470
+ * - Clears the intent to prevent duplicate processing.
471
+ * <p>
528
472
  * Edge Cases Handled:
529
473
  * - Null intent
530
474
  * - Missing or invalid NotificationClickHandler
@@ -536,23 +480,66 @@ public class AppICEUtils {
536
480
  */
537
481
  public static void handleAppLink(Intent intent, Context context) {
538
482
  try {
539
- if (intent != null){
483
+ if (intent != null) {
540
484
  NotificationClickHandler clickHandler = NotificationClickHandler.fromIntent(intent);
541
485
  if (clickHandler != null) {
542
486
  String payload = clickHandler.getPayload();
543
- Utility.loginfo("handleAppLink payload "+payload);
487
+ printLog("handleAppLink","handleAppLink payload " + payload);
544
488
  if (Utility.isValidObject(payload)) {
545
489
  ContextSdk.handleOpenLinkUrl(payload, context);
546
490
  ContextSdk.pushNotificationClicked(payload, context);
547
491
  clickHandler.deleteIntent(intent);
548
492
  } else
549
- Utility.loginfo("payload is not valid");
493
+ printLog("handleAppLink","payload is not valid");
550
494
  }
551
- }else
552
- Utility.loginfo("intent is null ");
495
+ } else
496
+ printLog("handleAppLink","intent is null ");
553
497
  } catch (Throwable e) {
554
- Utility.loginfo("exception in null ");
498
+ printLog("handleAppLink","exception in null ");
555
499
  }
556
500
  }
557
501
 
502
+ // this function will handle both normal push and action button push.
503
+ // through listner user will identify.
504
+ public static void handleClickOnPush(Intent intent, Context context) {
505
+ try {
506
+ if (intent != null) {
507
+ if (intent.getExtras() != null) {
508
+ Bundle bundle = intent.getExtras();
509
+ boolean isAppICEPush = bundle.getBoolean("ai_push", false);
510
+ if (isAppICEPush) {
511
+ CampaignCampsReceiver campsReceiver = new CampaignCampsReceiver();
512
+ campsReceiver.sendCallback(bundle, context);
513
+ intent.removeExtra("ai_push");
514
+ }else {
515
+ NotificationClickHandler.handleClickCallback(intent, context, new CampaignActionListener() {
516
+ @Override
517
+ public void onActionButtonClicked(Campaign campaign) {
518
+ WritableMap params = Arguments.createMap();
519
+ JSONObject campaignJson = campaign.toJson();
520
+
521
+ if (campaignJson != null && campaignJson.length() > 0) {
522
+ String campaignStr = campaignJson.toString();
523
+ params.putString(EnumConstants.APPICE_PUSH_NOTIFICATION_ACTION_BUTTON_CLICKED.getValue(), campaignStr);
524
+
525
+ printLog("ActionPushDebug onActionButtonClicked", "action button clicked | campaign: " + campaignStr);
526
+ printLog("ActionPushDebug onActionButtonClicked", "isDeviceReadyFlag: " + isDeviceReadyFlag);
527
+ printLog("ActionPushDebug onActionButtonClicked", "queue size before send: " + pendingEventQueue.size());
528
+
529
+ AppIceReactPluginModule.sendEvent(EnumConstants.APPICE_PUSH_NOTIFICATION_ACTION_BUTTON_CLICKED.getValue(), params);
530
+
531
+ printLog("ActionPushDebug onActionButtonClicked", "queue size after send: " + pendingEventQueue.size());
532
+ } else {
533
+ printLog("ActionPushDebug onActionButtonClicked", "campaignJson is null or empty — event NOT sent");
534
+ }
535
+ }
536
+ });
537
+ }
538
+ }
539
+ } else
540
+ printLog("ActionPushDebug", " | intent is null ");
541
+ } catch (Throwable e) {
542
+ printLog("ActionPushDebug"," | exception in null ");
543
+ }
544
+ }
558
545
  }
@@ -36,6 +36,7 @@ import semusi.model.campaign.TransactionalCampaign;
36
36
  import semusi.model.user.User;
37
37
 
38
38
  import java.util.*;
39
+ import java.util.concurrent.ConcurrentHashMap;
39
40
 
40
41
 
41
42
  import androidx.annotation.NonNull;
@@ -45,6 +46,7 @@ import com.facebook.react.bridge.ReadableArray;
45
46
  import com.facebook.react.bridge.WritableArray;
46
47
  import com.facebook.react.bridge.WritableMap;
47
48
  import com.facebook.react.module.annotations.ReactModule;
49
+ import com.facebook.react.modules.core.DeviceEventManagerModule;
48
50
 
49
51
  import org.json.JSONObject;
50
52
 
@@ -52,6 +54,9 @@ import org.json.JSONObject;
52
54
  public class AppIceReactPluginModule extends ReactContextBaseJavaModule {
53
55
  private static ReactApplicationContext context = null;
54
56
  public static final String NAME = "AppIceReactPlugin";
57
+ protected static volatile boolean isDeviceReadyFlag;
58
+ protected static final List<Map<String, Object>> pendingEventQueue =
59
+ Collections.synchronizedList(new ArrayList<>());
55
60
 
56
61
  public AppIceReactPluginModule(ReactApplicationContext reactContext) {
57
62
  super(reactContext);
@@ -99,7 +104,8 @@ public class AppIceReactPluginModule extends ReactContextBaseJavaModule {
99
104
  , context);
100
105
 
101
106
  Api.startContext(context, config);
102
-
107
+ String token= ContextSdk.getFCMToken(context);
108
+ printLog("AppICE","| firebase token = "+token);
103
109
  } catch (Exception e) {
104
110
  printLog(NAME, "Exception in startContext : " + e);
105
111
  }
@@ -639,17 +645,30 @@ public class AppIceReactPluginModule extends ReactContextBaseJavaModule {
639
645
  Context context = getReactApplicationContext();
640
646
  callback.invoke(ContextSdk.getInternalId(context));
641
647
  }
642
- @ReactMethod
643
- public void isDeviceReady(Boolean flag){
644
- if (flag){
645
- ReactApplicationContext ctx = getReactApplicationContext();
646
- flushEventQueue(ctx);
648
+
649
+ public static void sendEvent(String eventName, @Nullable WritableMap params) {
650
+ if (isDeviceReadyFlag) {
651
+ try {
652
+ context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
653
+ printLog("ActionPushDebug sendEvent", "Successfully emitted event: " + eventName);
654
+ } catch (Exception e) {
655
+ printLog("ActionPushDebug sendEvent", "emit failed for " + eventName + " - " + e.getMessage());
656
+ }
657
+ } else {
658
+ Map<String, Object> pending = new HashMap<>();
659
+ pending.put("eventName", eventName);
660
+ pending.put("param", params);
661
+ pendingEventQueue.add(pending);
662
+ printLog("ActionPushDebug sendEvent", "Queued event: " + eventName + " Queue size: " + pendingEventQueue.size());
647
663
  }
648
664
  }
649
665
 
650
- //=====================================
651
- public static void sendEvent(String eventName, @Nullable WritableMap params) {
652
- AppICEUtils.sendEventHelper(eventName, params, context);
666
+ @ReactMethod
667
+ public void isDeviceReady(Boolean flag) {
668
+ ReactApplicationContext ctx = getReactApplicationContext();
669
+ isDeviceReadyFlag = flag;
670
+ printLog("ActionPushDebug", "isDeviceReadyFlag " + isDeviceReadyFlag);
671
+ flushEventQueue(ctx);
653
672
  }
654
673
 
655
674
  private static AppICEInboxMessage getMediaPreProcessing(ReadableMap inboxMessage,Context context) {
@@ -886,6 +905,35 @@ public class AppIceReactPluginModule extends ReactContextBaseJavaModule {
886
905
  }
887
906
  }
888
907
 
908
+ //======================================================
909
+ // INAPP
910
+ //======================================================
911
+
912
+ @ReactMethod
913
+ public void resumeInApp(){
914
+ Context context = getReactApplicationContext();
915
+ ContextSdk.resumeInApp(context);
916
+ }
917
+
918
+ @ReactMethod
919
+ public void suspendInApp(){
920
+ Context context = getReactApplicationContext();
921
+ ContextSdk.suspendInApp(context);
922
+ }
923
+
924
+ @ReactMethod
925
+ public void discardInApp(){
926
+ Context context = getReactApplicationContext();
927
+ ContextSdk.discardInApp(context);
928
+ }
929
+
930
+ @ReactMethod
931
+ public void dismissInApp(){
932
+ ContextSdk.dismissInApp();
933
+ }
934
+
935
+ //================================================================
936
+
889
937
  @Nullable
890
938
  @Override
891
939
  public Map<String, Object> getConstants() {
@@ -902,6 +950,7 @@ public class AppIceReactPluginModule extends ReactContextBaseJavaModule {
902
950
  constants.put(EnumConstants.APPICE_USER_AGE.getConstants(), EnumConstants.APPICE_USER_AGE.getValue());
903
951
  constants.put(EnumConstants.APPICE_USER_GENDER.getConstants(), EnumConstants.APPICE_USER_GENDER.getValue());
904
952
  constants.put(EnumConstants.APPICE_PUSH_NOTIFICATION_CLICKED.getConstants(), EnumConstants.APPICE_PUSH_NOTIFICATION_CLICKED.getValue());
953
+ constants.put(EnumConstants.APPICE_PUSH_NOTIFICATION_ACTION_BUTTON_CLICKED.getConstants(), EnumConstants.APPICE_PUSH_NOTIFICATION_ACTION_BUTTON_CLICKED.getValue());
905
954
  constants.put(EnumConstants.APPICE_INAPP_NOTIFICATION_CLICKED.getConstants(), EnumConstants.APPICE_INAPP_NOTIFICATION_CLICKED.getValue());
906
955
  constants.put(EnumConstants.FIRST_SEEN.getConstants(), EnumConstants.FIRST_SEEN.getValue());
907
956
  constants.put(EnumConstants.LAST_SEEN.getConstants(), EnumConstants.LAST_SEEN.getValue());
@@ -924,6 +973,10 @@ public class AppIceReactPluginModule extends ReactContextBaseJavaModule {
924
973
  constants.put(EnumConstants.ACTION_URL.getConstants(), EnumConstants.ACTION_URL.getValue());
925
974
  constants.put(EnumConstants.ACTION_TYPE.getConstants(), EnumConstants.ACTION_TYPE.getValue());
926
975
  constants.put(EnumConstants.CUSTOMDATA.getConstants(), EnumConstants.CUSTOMDATA.getValue());
976
+ constants.put(EnumConstants.ACTION_TYPE_CUSTOM.getConstants(), EnumConstants.ACTION_TYPE_CUSTOM.getValue());
977
+ constants.put(EnumConstants.ACTION_TYPE_DEEPLINK.getConstants(), EnumConstants.ACTION_TYPE_DEEPLINK.getValue());
978
+ constants.put(EnumConstants.ACTION_TYPE_REPLY.getConstants(), EnumConstants.ACTION_TYPE_REPLY.getValue());
979
+ constants.put(EnumConstants.ACTION_TYPE_REMIND_ME_LATER.getConstants(), EnumConstants.ACTION_TYPE_REMIND_ME_LATER.getValue());
927
980
 
928
981
  return constants;
929
982
  }
@@ -31,6 +31,7 @@ public enum EnumConstants {
31
31
  //PUSH
32
32
  //============================
33
33
  APPICE_PUSH_NOTIFICATION_CLICKED("AppICEPushNotificationClicked"),
34
+ APPICE_PUSH_NOTIFICATION_ACTION_BUTTON_CLICKED("AppICEPushNotificationActionButtonClicked"),
34
35
 
35
36
  //===========================
36
37
  //IN-APP
@@ -88,7 +89,16 @@ public enum EnumConstants {
88
89
  CAMP_ID("CAMP_ID"),
89
90
  ACTION_URL("ACTION_URL"),
90
91
  ACTION_TYPE("ACTION_TYPE"),
91
- CUSTOMDATA("CUSTOMDATA");
92
+ CUSTOMDATA("CUSTOMDATA"),
93
+
94
+
95
+ //=======================
96
+ // ACTION TYPES
97
+ //=======================
98
+ ACTION_TYPE_CUSTOM("ACTION_TYPE_CUSTOM"),
99
+ ACTION_TYPE_DEEPLINK("ACTION_TYPE_DEEPLINK"),
100
+ ACTION_TYPE_REPLY("ACTION_TYPE_REPLY"),
101
+ ACTION_TYPE_REMIND_ME_LATER("ACTION_TYPE_REMIND_ME_LATER");
92
102
 
93
103
  private String constants;
94
104
 
@@ -148,6 +158,10 @@ public enum EnumConstants {
148
158
  return StringConstants.APPICE_PUSH_NOTIFICATION_CLICKED;
149
159
  }
150
160
 
161
+ case APPICE_PUSH_NOTIFICATION_ACTION_BUTTON_CLICKED:{
162
+ return StringConstants.APPICE_PUSH_NOTIFICATION_ACTION_BUTTON_CLICKED;
163
+ }
164
+
151
165
  case APPICE_INAPP_NOTIFICATION_CLICKED:{
152
166
  return StringConstants.APPICE_INAPP_NOTIFICATION_CLICKED;
153
167
  }
@@ -272,6 +286,22 @@ public enum EnumConstants {
272
286
  case CUSTOMDATA:{
273
287
  return CUSTOM_DATA;
274
288
  }
289
+
290
+ case ACTION_TYPE_CUSTOM:{
291
+ return CampaignConstants.CUSTOM;
292
+ }
293
+
294
+ case ACTION_TYPE_DEEPLINK:{
295
+ return CampaignConstants.DEEP_LINK;
296
+ }
297
+
298
+ case ACTION_TYPE_REPLY:{
299
+ return CampaignConstants.REPLY;
300
+ }
301
+ case ACTION_TYPE_REMIND_ME_LATER:{
302
+ return CampaignConstants.REMIND_LATER;
303
+ }
304
+
275
305
  default:
276
306
  return "";
277
307
  }
@@ -8,6 +8,7 @@ public interface StringConstants{
8
8
  String MEDIA_THUMBNAIL = "mediaThumbnail";
9
9
  String MEDIA_DATA = "mediaData";
10
10
  String APPICE_PUSH_NOTIFICATION_CLICKED = "AppICEPushNotificationClicked";
11
+ String APPICE_PUSH_NOTIFICATION_ACTION_BUTTON_CLICKED = "AppICEPushNotificationActionButtonClicked";
11
12
  String APPICE_INAPP_NOTIFICATION_CLICKED = "AppICEInAppClicked";
12
13
  String INBOX_MESSAGE = "message";
13
14
  String INBOX_TITLE = "title";
package/campaign.js CHANGED
@@ -1,6 +1,10 @@
1
1
  const { Linking, Platform } = require('react-native');
2
2
 
3
3
  async function handleClickOnPush(campaign) {
4
+ if (campaign && campaign._data) {
5
+ campaign = campaign._data;
6
+ }
7
+
4
8
  const url = campaign.actionUrl || campaign.acu || campaign.eurl;
5
9
  const actionType = campaign.actionType || campaign.act || campaign.et;
6
10
 
@@ -14,6 +18,7 @@ async function handleClickOnPush(campaign) {
14
18
  if (httpUrls) {
15
19
  try {
16
20
  if (Platform.OS === 'ios') {
21
+ campaign = normalizeCampaignPayloadForPlatform(campaign);
17
22
  return campaign;
18
23
  // iOS handle redirection on native side for landing page.
19
24
  }
@@ -31,6 +36,28 @@ async function handleClickOnPush(campaign) {
31
36
  }
32
37
  }
33
38
 
39
+
40
+ const PAYLOAD_KEYS = Object.freeze({
41
+ DATA: 'data',
42
+ MESSAGE: 'message',
43
+ });
44
+
45
+ // iOS push click payload often nests campaign fields under data.message.
46
+ // Android payload already comes in campaign shape.
47
+ function normalizeCampaignPayloadForPlatform(data) {
48
+ if (!data || typeof data !== 'object') return data;
49
+ if (Platform.OS !== 'ios') return data;
50
+
51
+ const dataNode = data[PAYLOAD_KEYS.DATA];
52
+ if (!dataNode || typeof dataNode !== 'object') return data;
53
+
54
+ const messageNode = dataNode[PAYLOAD_KEYS.MESSAGE];
55
+ if (messageNode && typeof messageNode === 'object') {
56
+ return messageNode;
57
+ }
58
+ return data;
59
+ }
60
+
34
61
  // ===============================
35
62
  // Campaign (merged backward-compatible)
36
63
  // ===============================
@@ -46,6 +73,9 @@ class Campaign {
46
73
  // if parse fails, keep eventData as-is (object expected)
47
74
  data = eventData || {};
48
75
  }
76
+
77
+ // Keep app-side listener thin by normalizing payload shape in campaign layer.
78
+ data = normalizeCampaignPayloadForPlatform(data);
49
79
  const isLegacy = data && (data.camp_id || data.eurl || data.et || data.abt || data.act || data.acc);
50
80
 
51
81
  if (isLegacy) {
@@ -200,6 +230,19 @@ class Campaign {
200
230
  return this._data.customData ? this._data.customData[key] || '' : '';
201
231
  }
202
232
 
233
+ //Action button
234
+ getSelectedActionTitle() {
235
+ return this._data.selectedActionTitle;
236
+ }
237
+
238
+ getSelectedActionType() {
239
+ return this._data.selectedActionType;
240
+ }
241
+
242
+ getSelectedActionUrl() {
243
+ return this._data.selectedActionUrl;
244
+ }
245
+
203
246
  // Convert to JSON
204
247
  toJSON() {
205
248
  return this._data;
package/index.js CHANGED
@@ -25,11 +25,12 @@ export const ACTION_URL = AppIceReactPlugin.ACTION_URL;
25
25
  export const ACTION_TYPE = AppIceReactPlugin.ACTION_TYPE;
26
26
  export const CUSTOMDATA = AppIceReactPlugin.CUSTOMDATA;
27
27
 
28
+
28
29
  const CampaignType = {
29
30
  NATIVE: 'NATIVE',
30
31
  INAPP: 'INAPP',
31
32
  PUSH: 'PUSH',
32
- };
33
+ };
33
34
 
34
35
 
35
36
  var AppICEReact = {
@@ -40,6 +41,7 @@ var AppICEReact = {
40
41
  // PUSH
41
42
  //========================
42
43
  AppICEPushNotificationClicked: AppIceReactPlugin.AppICEPushNotificationClicked,
44
+ AppICEPushNotificationActionButtonClicked: AppIceReactPlugin.AppICEPushNotificationActionButtonClicked,
43
45
 
44
46
  //==========================
45
47
  // IN-APP
@@ -101,7 +103,14 @@ var AppICEReact = {
101
103
  AVERAGE_TRANSACTION_AMOUNT: AppIceReactPlugin.AVERAGE_TRANSACTION_AMOUNT,
102
104
  FREQUENCY_OF_TRANSACTION: AppIceReactPlugin.FREQUENCY_OF_TRANSACTION,
103
105
  TYPE_OF_TRANSACTION: AppIceReactPlugin.TYPE_OF_TRANSACTION,
104
-
106
+
107
+ //====================
108
+ // ACTION BUTTON TYPES
109
+ //====================
110
+ ACTION_TYPE_CUSTOM: AppIceReactPlugin.ACTION_TYPE_CUSTOM,
111
+ ACTION_TYPE_DEEPLINK: AppIceReactPlugin.ACTION_TYPE_DEEPLINK,
112
+ ACTION_TYPE_REPLY: AppIceReactPlugin.ACTION_TYPE_REPLY,
113
+ ACTION_TYPE_REMIND_ME_LATER: AppIceReactPlugin.ACTION_TYPE_REMIND_ME_LATER,
105
114
 
106
115
  //======================
107
116
  // LISTENER
@@ -168,10 +177,10 @@ var AppICEReact = {
168
177
  AppState.addEventListener('change', (nextAppState) => {
169
178
  if (nextAppState === 'active') {
170
179
  AppIceReactPlugin.isDeviceReady(true);
171
- // AppIceReactPlugin.registerLifeCycle();
180
+ //AppIceReactPlugin.resumeInApp();
172
181
  } else if (nextAppState === 'background') {
173
- AppIceReactPlugin.isDeviceReady(false);
174
- // AppIceReactPlugin.registerLifeCycle();
182
+ AppIceReactPlugin.isDeviceReady(false);
183
+ //AppIceReactPlugin.discardInApp();
175
184
  }
176
185
  });
177
186
  },
@@ -199,7 +208,7 @@ var AppICEReact = {
199
208
  if (flush == undefined || flush == null) {
200
209
  await AppIceReactPlugin.tagEvent(eventName, props);
201
210
  } else {
202
- const lastEventData = await getEventData(LAST_EVENT_KEY)
211
+ const lastEventData = await getEventData(LAST_EVENT_KEY)
203
212
 
204
213
 
205
214
  if (lastEventData != undefined && lastEventData !== eventName) {
@@ -450,9 +459,9 @@ var AppICEReact = {
450
459
  callWithCallback('getUserForIds', [userIds], callback);
451
460
  },
452
461
 
453
- /*===================
454
- GetCampaign
455
- =====================*/
462
+ /*===================
463
+ GetCampaign
464
+ =====================*/
456
465
 
457
466
  /**
458
467
  * getCampaigns
@@ -462,69 +471,69 @@ var AppICEReact = {
462
471
 
463
472
  getCampaigns: function (campType, callback) {
464
473
  AppIceReactPlugin.getCampaigns(campType, (campaignArray) => {
465
- const campaigns = campaignArray.map(
466
- (campaignData) =>
467
- new Campaign(
468
- campaignData[CAMP_ID],
469
- campaignData[ACTION_URL],
470
- campaignData[ACTION_TYPE],
471
- campaignData[CUSTOMDATA]
472
- )
473
- );
474
- callback(campaigns);
474
+ const campaigns = campaignArray.map(
475
+ (campaignData) =>
476
+ new Campaign(
477
+ campaignData[CAMP_ID],
478
+ campaignData[ACTION_URL],
479
+ campaignData[ACTION_TYPE],
480
+ campaignData[CUSTOMDATA]
481
+ )
482
+ );
483
+ callback(campaigns);
475
484
  });
476
- },
477
-
478
-
485
+ },
486
+
487
+
479
488
 
480
489
  /**
481
490
  * getCampaignById
482
491
  * @param campId - string
483
492
  * @callback -Return a single Campaign object
484
493
  */
485
-
486
- getCampaignById: function (campId, callback) {
487
- AppIceReactPlugin.getCampaignById(campId, (campaignData) => {
488
- const getCampaign = new Campaign(
489
- campaignData[CAMP_ID],
490
- campaignData[ACTION_URL],
491
- campaignData[ACTION_TYPE],
492
- campaignData[CUSTOMDATA]
493
- );
494
- callback(getCampaign);
495
- });
496
- },
497
-
498
- //===============================================
499
- // Topic Push
494
+
495
+ getCampaignById: function (campId, callback) {
496
+ AppIceReactPlugin.getCampaignById(campId, (campaignData) => {
497
+ const getCampaign = new Campaign(
498
+ campaignData[CAMP_ID],
499
+ campaignData[ACTION_URL],
500
+ campaignData[ACTION_TYPE],
501
+ campaignData[CUSTOMDATA]
502
+ );
503
+ callback(getCampaign);
504
+ });
505
+ },
506
+
507
+ //===============================================
508
+ // Topic Push
500
509
  //===============================================
501
510
 
502
511
  isDeviceRegistered: function (callback) {
503
- callWithCallback('isDeviceRegistered', null, callback);
504
- },
512
+ callWithCallback('isDeviceRegistered', null, callback);
513
+ },
505
514
 
506
515
  setAppLanguage: function (language) {
507
-
508
- AppIceReactPlugin.setAppLanguage(language);
516
+
517
+ AppIceReactPlugin.setAppLanguage(language);
509
518
 
510
519
  },
511
520
 
512
521
  getAppLanguage: function (callback) {
513
- callWithCallback('getAppLanguage', null, callback);
514
- },
522
+ callWithCallback('getAppLanguage', null, callback);
523
+ },
515
524
 
516
525
  setDeviceLanguage: function (language) {
517
526
 
518
- AppIceReactPlugin.setDeviceLanguage(language);
519
-
527
+ AppIceReactPlugin.setDeviceLanguage(language);
528
+
520
529
  },
521
530
 
522
- getDeviceLanguage: function (callback) {
523
- callWithCallback('getDeviceLanguage', null, callback);
524
- },
531
+ getDeviceLanguage: function (callback) {
532
+ callWithCallback('getDeviceLanguage', null, callback);
533
+ },
525
534
 
526
- subscribeAppLanguage: function (language) {
527
- if (Platform.OS === 'android') {
535
+ subscribeAppLanguage: function (language) {
536
+ if (Platform.OS === 'android') {
528
537
  AppIceReactPlugin.subscribeAppLanguage(language);
529
538
  }
530
539
  },
@@ -535,24 +544,44 @@ var AppICEReact = {
535
544
  }
536
545
  },
537
546
 
538
- // -------- Custom Topics --------
539
- setCustomTopic: function (topic) {
540
- AppIceReactPlugin.setCustomTopic(topic);
541
- },
547
+ // -------- Custom Topics --------
548
+ setCustomTopic: function (topic) {
549
+ AppIceReactPlugin.setCustomTopic(topic);
550
+ },
551
+
552
+ removeCustomTopic: function (topic) {
553
+ AppIceReactPlugin.removeCustomTopic(topic);
554
+ },
555
+
556
+ scheduleTransactionalCampaign: function (payloadJson) {
557
+ AppIceReactPlugin.scheduleTransactionalCampaign(payloadJson);
542
558
 
543
- removeCustomTopic: function (topic) {
544
- AppIceReactPlugin.removeCustomTopic(topic);
545
- },
559
+ },
560
+
561
+ /*===================
562
+ INAPP
563
+ =====================*/
546
564
 
547
- scheduleTransactionalCampaign: function (payloadJson) {
548
- AppIceReactPlugin.scheduleTransactionalCampaign(payloadJson);
565
+ resumeInApp: function () {
566
+ AppIceReactPlugin.resumeInApp();
567
+ },
549
568
 
550
- },
569
+ suspendInApp: function () {
570
+ AppIceReactPlugin.suspendInApp();
571
+ },
551
572
 
552
- //campaign enum
553
- CampaignType,
573
+ discardInApp: function () {
574
+ AppIceReactPlugin.discardInApp();
575
+ },
576
+
577
+ dismissInApp: function (campType, callback) {
578
+ AppIceReactPlugin.dismissInApp();
579
+ },
580
+
581
+ //campaign enum
582
+ CampaignType,
554
583
  };
555
-
584
+
556
585
 
557
586
 
558
587
  //================================
@@ -585,8 +614,8 @@ const handleStoredEvents = async (eventName, props) => {
585
614
  events.push({ eventName, props });
586
615
 
587
616
  // Save the updated events list
588
- // appStorage.set(EVENT_KEY + eventName, JSON.stringify(events));
589
- // appStorage.set(LAST_EVENT_KEY, eventName);
617
+ // appStorage.set(EVENT_KEY + eventName, JSON.stringify(events));
618
+ // appStorage.set(LAST_EVENT_KEY, eventName);
590
619
 
591
620
  // async Storage
592
621
  await AsyncStorage.setItem(EVENT_KEY + eventName, JSON.stringify(events))
@@ -36,7 +36,7 @@ RCT_EXPORT_MODULE();
36
36
 
37
37
  - (NSArray<NSString *> *)supportedEvents
38
38
  {
39
- return @[kAppICEPushNotificationClicked, kAppICEInAppClicked];
39
+ return @[kAppICEPushNotificationClicked, kAppICEInAppClicked, kAppICEPushNotificationActionButtonClicked];
40
40
  }
41
41
 
42
42
 
@@ -51,7 +51,11 @@ RCT_EXPORT_MODULE();
51
51
  name:kAppICEInAppClicked
52
52
  object:nil];
53
53
 
54
-
54
+ // Observer for the handlePush event
55
+ [[NSNotificationCenter defaultCenter] addObserver:self
56
+ selector:@selector(didReceiveHandlePush:)
57
+ name:kAppICEPushNotificationActionButtonClicked
58
+ object:nil];
55
59
 
56
60
  }
57
61
 
@@ -82,5 +86,10 @@ RCT_EXPORT_MODULE();
82
86
  [self sendEventWithName:kAppICEInAppClicked body:eventDict];
83
87
  }
84
88
 
89
+ - (void)didReceiveHandlePush:(NSNotification *)event {
90
+ // Forward the handlePush event (which contains campaign data) to JS
91
+ [self sendEventWithName:kAppICEPushNotificationActionButtonClicked body:event.userInfo];
92
+ }
93
+
85
94
  @end
86
95
 
@@ -2,6 +2,7 @@
2
2
  #import <UserNotifications/UserNotifications.h>
3
3
  static NSString *const kAppICEPushNotificationClicked = @"AppICEPushNotificationClicked";
4
4
  static NSString *const kAppICEInAppClicked = @"AppICEInAppClicked";
5
+ static NSString *const kAppICEPushNotificationActionButtonClicked = @"AppICEPushNotificationActionButtonClicked";
5
6
 
6
7
  static NSString *const kn = @"APPICE_USER_NAME";
7
8
  static NSString *const kem = @"APPICE_USER_EMAIL";
@@ -35,6 +36,11 @@ static NSString *const ACTION_URL = @"ACTION_URL";
35
36
  static NSString *const ACTION_TYPE = @"ACTION_TYPE";
36
37
  static NSString *const CUSTOMDATA = @"CUSTOMDATA";
37
38
 
39
+ static NSString *const ACTION_TYPE_CUSTOM = @"ACTION_TYPE_CUSTOM";
40
+ static NSString *const ACTION_TYPE_DEEPLINK = @"ACTION_TYPE_DEEPLINK";
41
+ static NSString *const ACTION_TYPE_REPLY = @"ACTION_TYPE_REPLY";
42
+ static NSString *const ACTION_TYPE_REMIND_ME_LATER = @"ACTION_TYPE_REMIND_ME_LATER";
43
+
38
44
  // Custom data
39
45
  static NSString *const DEMOGRAPHIC_INFO = @"DEMOGRAPHIC_INFO";
40
46
  static NSString *TOP_N_PRODUCTS_VIEWED;
@@ -59,7 +65,7 @@ static NSString *TYPE_OF_TRANSACTION;
59
65
 
60
66
  @interface AppIceReactPlugin : NSObject <RCTBridgeModule,UNUserNotificationCenterDelegate>
61
67
  +(void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken;
62
- + (void)pushNotificationClicked:(NSDictionary *)userInfo;
68
+ + (void)pushNotificationClicked:(id)userInfo;
63
69
  + (void)pushNotificationReceived:(NSDictionary *)userInfo;
64
70
  @end
65
71
 
@@ -7,10 +7,13 @@
7
7
  #import <UserNotifications/UserNotifications.h>
8
8
  #import "Campaign.h"
9
9
  #import "TransactionalCampaign.h"
10
+ #import "AppICEConstantsPrivate.h"
10
11
  @implementation AppIceReactPlugin
11
12
 
12
13
  static BOOL isAppInitialized = NO;
13
14
  static NSDictionary *pendingDeeplink = nil;
15
+ static UNNotificationResponse *pendingDeeplinkResponse = nil;
16
+ static NSString *pendingSelectedAction = nil;
14
17
 
15
18
  RCT_EXPORT_MODULE()
16
19
  + (BOOL)requiresMainQueueSetup {
@@ -36,30 +39,31 @@ RCT_EXPORT_MODULE()
36
39
  return @{
37
40
  kAppICEPushNotificationClicked : kAppICEPushNotificationClicked,
38
41
  kAppICEInAppClicked : kAppICEInAppClicked,
39
- kn : @"n",
40
- kem : @"e",
41
- kph : @"p",
42
- ka : @"a",
43
- kg : @"g",
44
- ked : @"edt",
45
- ket : @"emt",
46
- km : @"m",
47
- kis_emp : @"em",
48
- kdob: @"d",
42
+ kAppICEPushNotificationActionButtonClicked : kAppICEPushNotificationActionButtonClicked,
43
+ kn : NAME,
44
+ kem : EMAIL,
45
+ kph : PHONE,
46
+ ka : AGE,
47
+ kg : GENDER,
48
+ ked : EDUCATIONTYPE,
49
+ ket : EMPLOYMENTTYPE,
50
+ km : ISMARRIED,
51
+ kis_emp : ISEMPLOYED,
52
+ kdob: D,
49
53
 
50
- INBOX_MESSAGE : @"message",
51
- INBOX_TITLE : @"title",
52
- INBOX_MESSAGE_STATUS : @"messageStatus",
53
- INBOX_CAMPAIGN_TYPE : @"campType",
54
- INBOX_MESSAGE_LANGUAGE : @"messageLanguage",
55
- INBOX_MESSAGE_ID : @"id",
56
- INBOX_CAMPAIGN_ID : @"campId",
57
- INBOX_MESSAGE_CATEGORY : @"category",
58
- INBOX_MESSAGE_ICON : @"icon",
59
- INBOX_CUSTOM_DATA : @"cdata",
60
- INBOX_MESSAGE_EXPANDED_IMAGE :@"eni",
54
+ INBOX_MESSAGE : MESSAGE,
55
+ INBOX_TITLE : TITLE,
56
+ INBOX_MESSAGE_STATUS : MESSAGESTATUS,
57
+ INBOX_CAMPAIGN_TYPE : CAMPTYPE,
58
+ INBOX_MESSAGE_LANGUAGE : MESSAGELANGUAGE,
59
+ INBOX_MESSAGE_ID : CAMPAIGN_ID,
60
+ INBOX_CAMPAIGN_ID : CAMPAIGNID,
61
+ INBOX_MESSAGE_CATEGORY : CATEGORY,
62
+ INBOX_MESSAGE_ICON : ICON,
63
+ INBOX_CUSTOM_DATA : CDATA,
64
+ INBOX_MESSAGE_EXPANDED_IMAGE :ENI,
61
65
 
62
- DEMOGRAPHIC_INFO :@"customProperties",
66
+ DEMOGRAPHIC_INFO :DEMOGRAPHIC_INFO,
63
67
  @"TOP_N_PRODUCTS_VIEWED" : TOP_N_PRODUCTS_VIEWED,
64
68
  @"N_COMPLAINTS_RAISED" : N_COMPLAINTS_RAISED,
65
69
  @"PREF_LOGIN_DEVICE" : PREF_LOGIN_DEVICE,
@@ -77,10 +81,15 @@ RCT_EXPORT_MODULE()
77
81
  @"AVERAGE_TRANSACTION_AMOUNT" : AVERAGE_TRANSACTION_AMOUNT,
78
82
  @"FREQUENCY_OF_TRANSACTION": FREQUENCY_OF_TRANSACTION,
79
83
  @"TYPE_OF_TRANSACTION" : TYPE_OF_TRANSACTION,
80
- CAMP_ID : @"campId",
81
- ACTION_URL : @"actionUrl",
82
- ACTION_TYPE: @"actionType",
83
- CUSTOMDATA : @"customData"
84
+ CAMP_ID : CAMPAIGNID,
85
+ ACTION_URL : ACTIONURL,
86
+ ACTION_TYPE: ACTIONTYPE,
87
+ CUSTOMDATA : CUSTOMDATA,
88
+
89
+ ACTION_TYPE_CUSTOM: CUS,
90
+ ACTION_TYPE_DEEPLINK: DL,
91
+ ACTION_TYPE_REPLY: RLY,
92
+ ACTION_TYPE_REMIND_ME_LATER: RML
84
93
  };
85
94
  }
86
95
  - (dispatch_queue_t)methodQueue {
@@ -248,26 +257,13 @@ RCT_EXPORT_METHOD(setUser:(NSDictionary*)userProfile)
248
257
  * when Called in killed state isAppInitialized will be false and deeplink is stored and will be handled after the App is initialized.
249
258
  ===========================*/
250
259
 
251
- + (void)pushNotificationClicked:(NSDictionary *)userInfo {
252
- @try {
253
-
254
- if (!isAppInitialized) {
255
- RCTLog(@"Deeplink Push : not isAppInitialized" );
256
- // App is not initialized, store the deeplink for later processing
257
- [self storePendingDeeplink:userInfo];
258
- } else {
259
- // App is initialized, handle deeplinks in the click handler
260
- RCTLog(@"Deeplink Push : pushNotificationClicked deeplink handled" );
261
- [self storePendingDeeplink:userInfo];
262
- [self processPendingDeeplink];
260
+ + (void)pushNotificationClicked:(id)response {
261
+ @try {
262
+ [self storePendingDeeplink:response];
263
+ [self processPendingDeeplink]; // processPendingDeeplink itself enforces init rules
264
+ } @catch (NSException *exception) {
265
+ RCTLog(@"Deeplink Push : pushNotificationClicked exception %@", exception);
263
266
  }
264
-
265
- NSDictionary *event = @{ kAppICEPushNotificationClicked : userInfo};
266
- [[NSNotificationCenter defaultCenter] postNotificationName:kAppICEPushNotificationClicked object:nil userInfo:event];
267
- } @catch (NSException *exception) {
268
- RCTLog(@"Deeplink Push : pushNotificationClicked exception %@",exception);
269
- }
270
-
271
267
  }
272
268
 
273
269
  RCT_EXPORT_METHOD(inAppClicked:(NSDictionary*)userInfo)
@@ -542,34 +538,129 @@ RCT_EXPORT_METHOD(getMediaThumbnail:(NSDictionary *)inboxMessageDict mediaData:(
542
538
  * @param userInfo - NSDictionary
543
539
  * Store the pending deeplink info in the static variable for later processing
544
540
  -----------------------------------------------------------------------------*/
545
- + (void)storePendingDeeplink:(NSDictionary *)userInfo {
541
+ + (void)storePendingDeeplink:(id)response {
546
542
  @try {
547
- // Store the pending deeplink info in the static variable for later processing
548
- pendingDeeplink = userInfo;
549
- RCTLog(@"Deeplink Push : storePendingDeeplink called" );
543
+ // Store the pending deeplink info in the static variable for later processing
544
+ // Old flow: NSDictionary userInfo
545
+ if ([response isKindOfClass:[NSDictionary class]]) {
546
+ // Old flow
547
+ pendingDeeplink = (NSDictionary *)response;
548
+ pendingDeeplinkResponse = nil;
549
+ pendingSelectedAction = nil;
550
+ RCTLog(@"Deeplink Push : storePendingDeeplink old flow pendingDeeplink: %@", pendingDeeplink);
551
+ return;
552
+ }
553
+ if ([response isKindOfClass:[UNNotificationResponse class]]) {
554
+ // New flow
555
+ pendingDeeplinkResponse = (UNNotificationResponse *)response;
556
+ pendingDeeplink = pendingDeeplinkResponse.notification.request.content.userInfo;
557
+ pendingSelectedAction = [self resolvedActFromResponse:pendingDeeplinkResponse];
558
+ RCTLog(@"Deeplink Push : storePendingDeeplink selectedAct: %@ actionIdentifier: %@",
559
+ pendingSelectedAction, pendingDeeplinkResponse.actionIdentifier);
560
+ return;
561
+ }
562
+ pendingDeeplink = nil;
563
+ pendingDeeplinkResponse = nil;
564
+ pendingSelectedAction = nil;
550
565
  } @catch (NSException *exception) {
551
566
  RCTLog(@"Deeplink Push : storePendingDeeplink exception %@",exception);
552
567
  }
553
568
  }
554
569
 
570
+ + (NSString *)resolvedActFromResponse:(UNNotificationResponse *)response {
571
+ // Maps iOS actionIdentifier -> payload action "act" value for routing.
572
+ if (![response isKindOfClass:[UNNotificationResponse class]]) return @"";
573
+
574
+ NSString *identifier = response.actionIdentifier ?: @"";
575
+ if ([identifier isEqualToString:UNNotificationDefaultActionIdentifier]) {
576
+ return DEFAULT;
577
+ }
578
+ NSDictionary *userInfo = response.notification.request.content.userInfo;
579
+ NSDictionary *message = userInfo[DATA][MESSAGE];
580
+ NSArray *actions = [message[ACTIONS] isKindOfClass:[NSArray class]] ? message[ACTIONS] : @[];
581
+ for (id a in actions) {
582
+ if (![a isKindOfClass:[NSDictionary class]]) continue;
583
+ NSDictionary *action = (NSDictionary *)a;
584
+ NSString *abt = [action[ABT] isKindOfClass:[NSString class]] ? action[ABT] : @"";
585
+ NSString *act = [action[ACT] isKindOfClass:[NSString class]] ? action[ACT] : @"";
586
+ if ([abt caseInsensitiveCompare:identifier] == NSOrderedSame) {
587
+ return act.lowercaseString;
588
+ }
589
+ }
590
+ // fallback: if identifier itself equals act
591
+ NSString *idLower = identifier.lowercaseString;
592
+ for (id a in actions) {
593
+ if (![a isKindOfClass:[NSDictionary class]]) continue;
594
+ NSString *act = [a[ACT] isKindOfClass:[NSString class]] ? [a[ACT] lowercaseString] : @"";
595
+ if ([act isEqualToString:idLower]) return act;
596
+ }
597
+ return @""; // unknown action
598
+ }
599
+
555
600
  /*----------------------------------------------------------------------------
556
601
  processPendingDeeplink
557
602
  * Process the pending deeplink if it exists and the app is initialized
603
+ * Flow A: Legacy wrapper input (NSDictionary userInfo).
604
+ Behavior is unchanged: process only after React/SDK initialization.
605
+ * Flow B: New wrapper input (UNNotificationResponse).
606
+ Routing is based on resolved payload action type.
607
+ 1. Normal push click (default action) -> preserve old behavior.
608
+ 2. RML/RLY/DIS action buttons -> allow kill/background processing path.
609
+ 3. Other action buttons -> process only after initialization.
558
610
  * Clear the pending deeplink after processing
559
611
  -----------------------------------------------------------------------------*/
560
612
  + (void)processPendingDeeplink {
561
613
  @try {
562
-
563
- RCTLog(@"Deeplink Push : processPendingDeeplink called isAppInitialized: %d , pendingDeeplink: %@",isAppInitialized,pendingDeeplink);
564
- if (isAppInitialized && pendingDeeplink) {
565
- RCTLog(@"Deeplink Push : handled in stored data");
566
- [[appICE sharedInstance] handleClickOnPush:pendingDeeplink OpenDeepLink:YES];
567
- pendingDeeplink = nil;
568
- }
614
+ BOOL didProcess = NO;
615
+ //Flow A
616
+ if (pendingDeeplink && !pendingDeeplinkResponse) {
617
+ if (isAppInitialized) {
618
+ [[appICE sharedInstance] handleClickOnPush:pendingDeeplink OpenDeepLink:YES];
619
+ NSDictionary *event = @{ kAppICEPushNotificationClicked : pendingDeeplink };
620
+ [[NSNotificationCenter defaultCenter] postNotificationName:kAppICEPushNotificationClicked
621
+ object:nil
622
+ userInfo:event];
623
+ didProcess = YES;
624
+ }
625
+ }
626
+ // Flow B:
627
+ if (!didProcess && pendingDeeplinkResponse) {
628
+ NSString *act = (pendingSelectedAction ?: @"").lowercaseString;
629
+ // B1) Normal push click (default action) -> preserve old behavior.
630
+ if ([act isEqualToString:DEFAULT]) {
631
+ if (isAppInitialized && pendingDeeplink) {
632
+ [[appICE sharedInstance] handleClickOnPush:pendingDeeplink OpenDeepLink:YES];
633
+ NSDictionary *event = @{ kAppICEPushNotificationClicked : pendingDeeplink };
634
+ [[NSNotificationCenter defaultCenter] postNotificationName:kAppICEPushNotificationClicked
635
+ object:nil
636
+ userInfo:event];
637
+ didProcess = YES;
638
+ }
639
+ }
640
+ // B2) RML/RLY/DIS action buttons -> allow kill/background processing path.
641
+ else if ([act isEqualToString:RML] ||
642
+ [act isEqualToString:RLY] ||
643
+ [act isEqualToString:DIS]) {
644
+ [self handlePush:pendingDeeplinkResponse openDeepLink:YES];
645
+ didProcess = YES;
646
+ }
647
+ // B3) Other action buttons -> process only after initialization.
648
+ else {
649
+ if (isAppInitialized) {
650
+ [self handlePush:pendingDeeplinkResponse openDeepLink:YES];
651
+ didProcess = YES;
652
+ }
653
+ }
654
+ }
655
+ if (didProcess) {
656
+ pendingDeeplink = nil;
657
+ pendingDeeplinkResponse = nil;
658
+ pendingSelectedAction = nil;
659
+ RCTLog(@"Deeplink Push : pending cleared");
660
+ }
569
661
  } @catch (NSException *exception) {
570
662
  RCTLog(@"Deeplink Push : processPendingDeeplink exception %@",exception);
571
663
  } @finally {
572
- pendingDeeplink = nil;
573
664
  RCTLog(@"Deeplink Push : finally processPendingDeeplink pendingDeeplink set as nil");
574
665
  }
575
666
 
@@ -628,7 +719,7 @@ RCT_EXPORT_METHOD(getUserForIds:(NSArray*)userIds callback:(RCTResponseSenderBlo
628
719
  NSDictionary *formattedMessage = [getUserData toDictionary];
629
720
  [formattedGetUser addObject:formattedMessage];
630
721
  }
631
- NSLog(@"[getUserDetails]: Formatted getUser Messages: %@", formattedGetUser);
722
+ RCTLog(@"[getUserDetails]: Formatted getUser Messages: %@", formattedGetUser);
632
723
  [self returnResult:formattedGetUser withCallback:callback andError:nil];
633
724
 
634
725
  }
@@ -647,7 +738,7 @@ RCT_EXPORT_METHOD(getCampaigns:(NSString *)type callback:(RCTResponseSenderBlock
647
738
 
648
739
  for (Campaign *campaign in campaigns) {
649
740
  NSDictionary *formatedCampaign = campaign.toDictionary;
650
- NSLog(@"Campaign: Formatted getCampaigns: %@", formatedCampaign);
741
+ RCTLog(@"Campaign: Formatted getCampaigns: %@", formatedCampaign);
651
742
  [formattedCampaigns addObject:formatedCampaign];
652
743
  }
653
744
  [self returnResult:formattedCampaigns withCallback:callback andError:nil];
@@ -721,7 +812,54 @@ RCT_EXPORT_METHOD(scheduleTransactionalCampaign:(NSString *)payloadJson)
721
812
  [[appICE sharedInstance] scheduleCampaign:tCampaign];
722
813
  }
723
814
  } @catch (NSException *exception) {
724
-
815
+
816
+ }
817
+ }
818
+
819
+ #pragma mark - In-app display state
820
+
821
+ // React Native API to resume in-app rendering after it was paused/suspended.
822
+ RCT_EXPORT_METHOD(resumeInApp)
823
+ {
824
+ [[appICE sharedInstance] resumeInApp];
825
+ }
826
+
827
+ // React Native API to temporarily suspend in-app rendering.
828
+ RCT_EXPORT_METHOD(suspendInApp)
829
+ {
830
+ [[appICE sharedInstance] suspendInApp];
831
+ }
832
+
833
+ // React Native API to discard queued or active in-app state.
834
+ RCT_EXPORT_METHOD(discardInApp)
835
+ {
836
+ [[appICE sharedInstance] discardInApp];
837
+ }
838
+
839
+ // React Native API to dismiss the currently visible in-app.
840
+ RCT_EXPORT_METHOD(dismissInApp)
841
+ {
842
+ [[appICE sharedInstance] dismissInApp];
843
+ }
844
+
845
+ // Handles push click responses from app delegate paths, including kill-state/cold-start handoff.
846
+ // Emits `kAppICEPushNotificationActionButtonClicked` with campaign payload for JS listeners.
847
+ +(void)handlePush:(id)response openDeepLink:(BOOL)openDeepLink {
848
+ @try {
849
+ RCTLog(@"handlePushFromAppDelegate: Entered are");
850
+ [[appICE sharedInstance] pushNotificationClicked:response openDeepLink:openDeepLink];
851
+ Campaign *campaign = [[appICE sharedInstance] handleClickCallback:response];
852
+
853
+ NSDictionary *campaignDict = campaign ? [campaign toDictionary] : @{};
854
+ RCTLog(@"handlePushFromAppDelegate: Campaign data: %@", campaignDict);
855
+
856
+ // Post an event with the campaign data so JS can process it.
857
+ NSDictionary *event = @{ kAppICEPushNotificationActionButtonClicked : campaignDict };
858
+ [[NSNotificationCenter defaultCenter] postNotificationName:kAppICEPushNotificationActionButtonClicked
859
+ object:nil
860
+ userInfo:event];
861
+ } @catch (NSException *exception) {
862
+ RCTLog(@"handlePushFromAppDelegate exception: %@", exception);
725
863
  }
726
864
  }
727
865
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reactnative-plugin-appice",
3
- "version": "1.7.43",
3
+ "version": "1.7.44",
4
4
  "description": "appICE React Native SDK",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -9,6 +9,7 @@
9
9
  "android",
10
10
  "index.js",
11
11
  "campaign.js",
12
+ "TransactionalCampaign.js",
12
13
  "ios",
13
14
  "reactnative-plugin-appice.podspec"
14
15
  ],
@@ -23,7 +23,7 @@ Pod::Spec.new do |s|
23
23
  s.requires_arc = true
24
24
 
25
25
  s.dependency "React"
26
- s.dependency "AppICE-IOS-SDK", "1.8.20"
26
+ s.dependency "AppICE-IOS-SDK", "1.8.22"
27
27
  # ...
28
28
  # s.dependency "..."
29
29
  end