pushwoosh-react-native-plugin 7.0.1 → 7.0.3

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.1",
3
+ "version": "7.0.3",
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.1"
3
+ s.version = "7.0.3"
4
4
  s.summary = "React Native Pushwoosh Push Notifications module"
5
5
  s.requires_arc = true
6
6
  s.author = 'Pushwoosh'
@@ -19,6 +19,6 @@ Pod::Spec.new do |s|
19
19
  s.dependency "React-Core"
20
20
  end
21
21
 
22
- s.dependency 'PushwooshXCFramework', '7.2.6'
22
+ s.dependency 'PushwooshXCFramework', '7.2.8'
23
23
  s.dependency 'PushwooshInboxUIXCFramework', '7.0.42'
24
24
  end
@@ -144,7 +144,7 @@ android {
144
144
  }
145
145
 
146
146
  ext {
147
- pushwoosh = "6.13.0"
147
+ pushwoosh = "6.15.0"
148
148
  }
149
149
 
150
150
  dependencies {
@@ -31,8 +31,12 @@ public final class ConversionUtil {
31
31
  }
32
32
 
33
33
  public static JSONObject toJsonObject(ReadableMap readableMap) {
34
- ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
35
34
  JSONObject result = new JSONObject();
35
+ if (readableMap == null) {
36
+ PWLog.error(PushwooshPlugin.TAG, "Could not convert object: map is null.");
37
+ return result;
38
+ }
39
+ ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
36
40
  while (iterator.hasNextKey()) {
37
41
  String key = iterator.nextKey();
38
42
  ReadableType type = readableMap.getType(key);
@@ -215,6 +219,10 @@ public final class ConversionUtil {
215
219
 
216
220
  public static ArrayList<String> messageCodesArrayToArrayList(ReadableArray readableArray) {
217
221
  ArrayList<String> result = new ArrayList<>();
222
+ if (readableArray == null) {
223
+ PWLog.error(PushwooshPlugin.TAG, "Could not convert message codes: array is null.");
224
+ return result;
225
+ }
218
226
  for (int i = 0; i < readableArray.size(); i++) {
219
227
  ReadableType indexType = readableArray.getType(i);
220
228
  try {
@@ -7,8 +7,10 @@ import android.graphics.drawable.BitmapDrawable;
7
7
  import android.graphics.drawable.Drawable;
8
8
 
9
9
  import com.facebook.react.bridge.ReadableMap;
10
+ import com.facebook.react.bridge.ReadableType;
10
11
  import com.pushwoosh.inbox.ui.PushwooshInboxStyle;
11
12
  import com.pushwoosh.inbox.ui.model.customizing.formatter.InboxDateFormatter;
13
+ import com.pushwoosh.internal.utils.PWLog;
12
14
 
13
15
  import java.io.IOException;
14
16
  import java.net.URL;
@@ -53,22 +55,45 @@ class InboxUiStyleManager {
53
55
  }
54
56
 
55
57
  private void setDateFormat(ReadableMap mapStyle) {
56
- if (!mapStyle.hasKey(DATE_FORMAT_KEY)) {
57
- return;
58
- }
59
- String dateFormat = mapStyle.getString(DATE_FORMAT_KEY);
58
+ String dateFormat = getString(mapStyle, DATE_FORMAT_KEY);
60
59
  if (dateFormat != null && !dateFormat.isEmpty()) {
61
- ReactInboxDateFormatter inboxDateFormatter = new ReactInboxDateFormatter(dateFormat);
62
- PushwooshInboxStyle.INSTANCE.setDateFormatter(inboxDateFormatter);
60
+ try {
61
+ PushwooshInboxStyle.INSTANCE.setDateFormatter(new ReactInboxDateFormatter(dateFormat));
62
+ } catch (IllegalArgumentException e) {
63
+ PWLog.error(PushwooshPlugin.TAG, "Invalid inbox dateFormat pattern: " + dateFormat, e);
64
+ }
63
65
  }
64
66
  }
65
67
 
66
68
  private void setTexts(ReadableMap mapStyle) {
67
69
  PushwooshInboxStyle PWInboxStyle = PushwooshInboxStyle.INSTANCE;
68
- if (mapStyle.hasKey(LIST_ERROR_MESSAGE_KEY))
69
- PWInboxStyle.setListErrorMessage(mapStyle.getString(LIST_ERROR_MESSAGE_KEY));
70
- if (mapStyle.hasKey(LIST_EMPTY_MESSAGE_KEY))
71
- PWInboxStyle.setListEmptyText(mapStyle.getString(LIST_EMPTY_MESSAGE_KEY));
70
+ String listErrorMessage = getString(mapStyle, LIST_ERROR_MESSAGE_KEY);
71
+ if (listErrorMessage != null)
72
+ PWInboxStyle.setListErrorMessage(listErrorMessage);
73
+ String listEmptyMessage = getString(mapStyle, LIST_EMPTY_MESSAGE_KEY);
74
+ if (listEmptyMessage != null)
75
+ PWInboxStyle.setListEmptyText(listEmptyMessage);
76
+ }
77
+
78
+ // A production ReadableNativeMap throws out of presentInboxUI() on a typed read of a
79
+ // mismatched or null value, so the type is checked before the value is taken.
80
+ private boolean hasTypedValue(ReadableMap mapStyle, String key, ReadableType type, String label) {
81
+ if (!mapStyle.hasKey(key)) {
82
+ return false;
83
+ }
84
+ if (mapStyle.getType(key) != type) {
85
+ PWLog.error(PushwooshPlugin.TAG, "Inbox style " + key + " must be " + label + "; ignored.");
86
+ return false;
87
+ }
88
+ return true;
89
+ }
90
+
91
+ private String getString(ReadableMap mapStyle, String key) {
92
+ return hasTypedValue(mapStyle, key, ReadableType.String, "a string") ? mapStyle.getString(key) : null;
93
+ }
94
+
95
+ private Integer getColor(ReadableMap mapStyle, String key) {
96
+ return hasTypedValue(mapStyle, key, ReadableType.Number, "a number") ? mapStyle.getInt(key) : null;
72
97
  }
73
98
 
74
99
  private void setImages(ReadableMap mapStyle) {
@@ -87,11 +112,15 @@ class InboxUiStyleManager {
87
112
  }
88
113
 
89
114
  private Drawable getImage(ReadableMap mapStyle, String key) {
90
- if (!mapStyle.hasKey(key)) {
115
+ if (!hasTypedValue(mapStyle, key, ReadableType.Map, "an object with a uri")) {
116
+ return null;
117
+ }
118
+ ReadableMap imageMap = mapStyle.getMap(key);
119
+ if (!imageMap.hasKey(URI_KEY) || imageMap.getType(URI_KEY) != ReadableType.String) {
120
+ PWLog.error(PushwooshPlugin.TAG, "Inbox style " + key + "." + URI_KEY + " must be a string; ignored.");
91
121
  return null;
92
122
  }
93
- ReadableMap defaultImageIcon = mapStyle.getMap(key);
94
- String uri = defaultImageIcon.getString(URI_KEY);
123
+ String uri = imageMap.getString(URI_KEY);
95
124
  try {
96
125
  return getDrawable(uri);
97
126
  } catch (IOException e) {
@@ -113,36 +142,49 @@ class InboxUiStyleManager {
113
142
  private void setColors(ReadableMap mapStyle) {
114
143
  PushwooshInboxStyle PWInboxStyle = PushwooshInboxStyle.INSTANCE;
115
144
 
116
- if (mapStyle.hasKey(ACCENT_COLOR_KEY))
117
- PWInboxStyle.setAccentColor(mapStyle.getInt(ACCENT_COLOR_KEY));
118
- if (mapStyle.hasKey(HIGHLIGHT_COLOR_KEY))
119
- PWInboxStyle.setHighlightColor(mapStyle.getInt(HIGHLIGHT_COLOR_KEY));
120
- if (mapStyle.hasKey(BACKGROUND_COLOR_KEY))
121
- PWInboxStyle.setBackgroundColor(mapStyle.getInt(BACKGROUND_COLOR_KEY));
122
- if (mapStyle.hasKey(DIVIDER_COLOR_KEY))
123
- PWInboxStyle.setDividerColor(mapStyle.getInt(DIVIDER_COLOR_KEY));
124
-
125
- if (mapStyle.hasKey(DATE_COLOR_KEY))
126
- PWInboxStyle.setDateColor(mapStyle.getInt(DATE_COLOR_KEY));
127
- if (mapStyle.hasKey(READ_DATE_COLOR_KEY))
128
- PWInboxStyle.setReadDateColor(mapStyle.getInt(READ_DATE_COLOR_KEY));
129
-
130
- if (mapStyle.hasKey(TITLE_COLOR_KEY))
131
- PWInboxStyle.setTitleColor(mapStyle.getInt(TITLE_COLOR_KEY));
132
- if (mapStyle.hasKey(READ_TITLE_COLOR_KEY))
133
- PWInboxStyle.setReadTitleColor(mapStyle.getInt(READ_TITLE_COLOR_KEY));
134
-
135
- if (mapStyle.hasKey(DESCRIPTION_COLOR_KEY))
136
- PWInboxStyle.setDescriptionColor(mapStyle.getInt(DESCRIPTION_COLOR_KEY));
137
- if (mapStyle.hasKey(READ_DESCRIPTION_COLOR_KEY))
138
- PWInboxStyle.setReadDescriptionColor(mapStyle.getInt(READ_DESCRIPTION_COLOR_KEY));
139
-
140
- if (mapStyle.hasKey(BAR_BACKGROUND_COLOR))
141
- PWInboxStyle.setBarBackgroundColor(mapStyle.getInt(BAR_BACKGROUND_COLOR));
142
- if (mapStyle.hasKey(BAR_ACCENT_COLOR))
143
- PWInboxStyle.setBarAccentColor(mapStyle.getInt(BAR_ACCENT_COLOR));
144
- if (mapStyle.hasKey(BAR_TEXT_COLOR))
145
- PWInboxStyle.setBarTextColor(mapStyle.getInt(BAR_TEXT_COLOR));
145
+ Integer accentColor = getColor(mapStyle, ACCENT_COLOR_KEY);
146
+ if (accentColor != null)
147
+ PWInboxStyle.setAccentColor(accentColor);
148
+ Integer highlightColor = getColor(mapStyle, HIGHLIGHT_COLOR_KEY);
149
+ if (highlightColor != null)
150
+ PWInboxStyle.setHighlightColor(highlightColor);
151
+ Integer backgroundColor = getColor(mapStyle, BACKGROUND_COLOR_KEY);
152
+ if (backgroundColor != null)
153
+ PWInboxStyle.setBackgroundColor(backgroundColor);
154
+ Integer dividerColor = getColor(mapStyle, DIVIDER_COLOR_KEY);
155
+ if (dividerColor != null)
156
+ PWInboxStyle.setDividerColor(dividerColor);
157
+
158
+ Integer dateColor = getColor(mapStyle, DATE_COLOR_KEY);
159
+ if (dateColor != null)
160
+ PWInboxStyle.setDateColor(dateColor);
161
+ Integer readDateColor = getColor(mapStyle, READ_DATE_COLOR_KEY);
162
+ if (readDateColor != null)
163
+ PWInboxStyle.setReadDateColor(readDateColor);
164
+
165
+ Integer titleColor = getColor(mapStyle, TITLE_COLOR_KEY);
166
+ if (titleColor != null)
167
+ PWInboxStyle.setTitleColor(titleColor);
168
+ Integer readTitleColor = getColor(mapStyle, READ_TITLE_COLOR_KEY);
169
+ if (readTitleColor != null)
170
+ PWInboxStyle.setReadTitleColor(readTitleColor);
171
+
172
+ Integer descriptionColor = getColor(mapStyle, DESCRIPTION_COLOR_KEY);
173
+ if (descriptionColor != null)
174
+ PWInboxStyle.setDescriptionColor(descriptionColor);
175
+ Integer readDescriptionColor = getColor(mapStyle, READ_DESCRIPTION_COLOR_KEY);
176
+ if (readDescriptionColor != null)
177
+ PWInboxStyle.setReadDescriptionColor(readDescriptionColor);
178
+
179
+ Integer barBackgroundColor = getColor(mapStyle, BAR_BACKGROUND_COLOR);
180
+ if (barBackgroundColor != null)
181
+ PWInboxStyle.setBarBackgroundColor(barBackgroundColor);
182
+ Integer barAccentColor = getColor(mapStyle, BAR_ACCENT_COLOR);
183
+ if (barAccentColor != null)
184
+ PWInboxStyle.setBarAccentColor(barAccentColor);
185
+ Integer barTextColor = getColor(mapStyle, BAR_TEXT_COLOR);
186
+ if (barTextColor != null)
187
+ PWInboxStyle.setBarTextColor(barTextColor);
146
188
  }
147
189
 
148
190
  private class ReactInboxDateFormatter implements InboxDateFormatter {
@@ -130,7 +130,7 @@ public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEve
130
130
 
131
131
  @ReactMethod
132
132
  public void init(ReadableMap config, Callback success, Callback error) {
133
- String appId = config.hasKey("pw_appid") ? config.getString("pw_appid") : null;
133
+ String appId = config != null && config.hasKey("pw_appid") ? config.getString("pw_appid") : null;
134
134
 
135
135
  if (appId == null) {
136
136
  if (error != null) {
@@ -441,12 +441,11 @@ public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEve
441
441
  }
442
442
 
443
443
  Activity currentActivity = getCurrentActivity();
444
- Intent intent = new Intent(currentActivity, InboxActivity.class);
445
- if (currentActivity != null) {
446
- currentActivity.startActivity(intent);
447
- }else {
444
+ if (currentActivity == null) {
448
445
  PWLog.error(TAG, "current activity is null");
446
+ return;
449
447
  }
448
+ currentActivity.startActivity(new Intent(currentActivity, InboxActivity.class));
450
449
  }
451
450
 
452
451
  @ReactMethod
@@ -564,6 +563,10 @@ public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEve
564
563
 
565
564
  @ReactMethod
566
565
  public void setNotificationIconBackgroundColor(String color) {
566
+ if (color == null || color.isEmpty()) {
567
+ PWLog.error(TAG, "setNotificationIconBackgroundColor: color is required");
568
+ return;
569
+ }
567
570
  int intColor;
568
571
  try {
569
572
  intColor = Color.parseColor(color);
@@ -3,11 +3,18 @@ package com.pushwoosh.reactnativeplugin.internal;
3
3
  import com.pushwoosh.internal.PluginProvider;
4
4
 
5
5
  public class ReactNativePluginProvider implements PluginProvider {
6
+ private static final String PLUGIN_VERSION = "7.0.3";
7
+
6
8
  @Override
7
9
  public String getPluginType() {
8
10
  return "React Native";
9
11
  }
10
12
 
13
+ // No @Override: getPluginVersion() lands in PluginProvider only in the next Android SDK release.
14
+ public String getPluginVersion() {
15
+ return PLUGIN_VERSION;
16
+ }
17
+
11
18
  @Override
12
19
  public int richMediaStartDelay() {
13
20
  return DEFAULT_RICH_MEDIA_START_DELAY;
@@ -72,6 +72,17 @@ static NSDictionary * gStartPushData = nil;
72
72
  static BOOL gStartPushReplayed = NO; // Launch push already replayed into the JS events by init:
73
73
  static NSURL * gPushDeepLinkURL = nil; // Deep link URL for New Architecture support
74
74
  static NSString * gEarlyHandledHash = nil; // Last push hash the cold-start delegate accepted, to avoid double handling
75
+ static NSString * gPendingWebLaunchLink = nil; // http(s) link of the launch push, until the SDK hands it to RCTLinkingManager
76
+ static NSUInteger gPendingWebLaunchLinkGeneration = 0; // Bumped on every arm, so a stale delivery timeout leaves a newer link alone
77
+ static NSString * gLastArmedWebLaunchLink = nil; // Survives delivery, so re-arming the same push is recognised
78
+ static BOOL gInitialURLAnswered = NO; // JS called getInitialURL() since gLastArmedWebLaunchLink was first armed
79
+ static BOOL gPluginInitialized = NO; // init: ran, so JS is up and a tapped push is no longer a launch push
80
+ static NSMutableArray<void (^)(NSString *)> * gInitialURLWaiters = nil; // getInitialURL() calls held for the pending link
81
+ static NSUInteger gInitialURLHoldGeneration = 0; // Bumped on every settle, so a stale hold timeout leaves a newer hold alone
82
+ // The SDK waits 0.2 s after accepting the push, then fetches apple-app-site-association with a 5 s timeout.
83
+ static NSTimeInterval const kPWWebLaunchLinkDeliveryTimeout = 6.0;
84
+ // Covers the SDK's delay and a cached or quick verdict without stalling an app whose link never reaches RN.
85
+ static NSTimeInterval const kPWInitialURLHoldTimeout = 1.0;
75
86
  static NSString * const kRegistrationSuccesEvent = @"PWRegistrationSuccess";
76
87
  static NSString * const kRegistrationErrorEvent = @"PWRegistrationError";
77
88
  static NSString * const kPushReceivedEvent = @"PWPushReceived";
@@ -80,6 +91,25 @@ static NSString * const kPushOpenEvent = @"PWPushOpen";
80
91
  static NSString * const kPushOpenJSEvent = @"pushOpened";
81
92
  static NSString * const kPushReceivedJSEvent = @"pushReceived";
82
93
 
94
+ static NSString * const kPWFrameworkType = @"React Native";
95
+ static NSString * const kPWFrameworkVersion = @"7.0.3";
96
+
97
+ /// Reports the framework type and the plugin version to the native core, on the cores that take them.
98
+ /// Dispatched by selector name: setFrameworkType:version: lands in a core newer than the pinned one.
99
+ static void pwplugin_setFrameworkTelemetry(NSString *type, NSString *version) {
100
+ Class configuration = [Pushwoosh configure];
101
+ SEL selector = NSSelectorFromString(@"setFrameworkType:version:");
102
+
103
+ if (![configuration respondsToSelector:selector]) {
104
+ return;
105
+ }
106
+
107
+ #pragma clang diagnostic push
108
+ #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
109
+ [configuration performSelector:selector withObject:type withObject:version];
110
+ #pragma clang diagnostic pop
111
+ }
112
+
83
113
  /// Returns the push link only when it is a deep link this app can route itself, otherwise nil.
84
114
  ///
85
115
  /// http/https links are left to the native SDK: it checks the host's apple-app-site-association
@@ -101,6 +131,78 @@ static NSURL * pwplugin_deepLinkURLFromPushLink(id link) {
101
131
  return url;
102
132
  }
103
133
 
134
+ /// Returns the http(s) push link as `URL.absoluteString`, the form RCTLinkingManager posts, otherwise nil.
135
+ static NSString * pwplugin_webLinkFromPushLink(id link) {
136
+ if (![link isKindOfClass:[NSString class]] || [link length] == 0) {
137
+ return nil;
138
+ }
139
+
140
+ NSURL *url = [NSURL URLWithString:link];
141
+ NSString *scheme = url.scheme.lowercaseString;
142
+ if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]) {
143
+ return url.absoluteString;
144
+ }
145
+
146
+ return nil;
147
+ }
148
+
149
+ /// Answers every getInitialURL() call held for the pending link: with `url` when it arrived, otherwise
150
+ /// with what RCTLinkingManager itself reports. JS has read its initial URL either way.
151
+ static void pwplugin_settleInitialURLWaiters(NSString *url) {
152
+ NSArray<void (^)(NSString *)> *waiters = gInitialURLWaiters;
153
+ gInitialURLWaiters = nil;
154
+ gInitialURLHoldGeneration++;
155
+ gInitialURLAnswered = YES;
156
+ for (void (^waiter)(NSString *) in waiters) {
157
+ waiter(url);
158
+ }
159
+ }
160
+
161
+ /// Arms the launch push's web link for the SDK's delivery window; called by every launch-push site.
162
+ /// A push without a web link disarms it, so a later Safari open of the same URL is not caught.
163
+ static void pwplugin_rememberWebLaunchLink(NSDictionary *push) {
164
+ NSString *link = pwplugin_webLinkFromPushLink(push[@"l"]);
165
+ // The SDK accepts the same launch push at init() again, possibly after JS read getInitialURL().
166
+ if (!link || ![link isEqualToString:gLastArmedWebLaunchLink]) {
167
+ gInitialURLAnswered = NO;
168
+ }
169
+ gLastArmedWebLaunchLink = link;
170
+ gPendingWebLaunchLink = link;
171
+ NSUInteger generation = ++gPendingWebLaunchLinkGeneration;
172
+
173
+ if (!link) {
174
+ if (gInitialURLWaiters) {
175
+ pwplugin_settleInitialURLWaiters(nil);
176
+ }
177
+ return;
178
+ }
179
+
180
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kPWWebLaunchLinkDeliveryTimeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
181
+ if (generation == gPendingWebLaunchLinkGeneration) {
182
+ gPendingWebLaunchLink = nil;
183
+ if (gInitialURLWaiters) {
184
+ pwplugin_settleInitialURLWaiters(nil);
185
+ }
186
+ }
187
+ });
188
+ }
189
+
190
+ /// Catches the launch push's Universal Link on its way to RCTLinkingManager: resolves held getInitialURL()
191
+ /// calls, or parks it for the first one; a link after getInitialURL() answered is left to the `url` event.
192
+ static void pwplugin_handleOpenURLNotification(NSNotification *notification) {
193
+ id url = notification.userInfo[@"url"];
194
+ if (!gPendingWebLaunchLink || ![url isKindOfClass:[NSString class]] || ![url isEqualToString:gPendingWebLaunchLink]) {
195
+ return;
196
+ }
197
+
198
+ gPendingWebLaunchLink = nil;
199
+ if (gInitialURLWaiters) {
200
+ pwplugin_settleInitialURLWaiters(url);
201
+ } else if (!gInitialURLAnswered) {
202
+ gPushDeepLinkURL = [NSURL URLWithString:url];
203
+ }
204
+ }
205
+
104
206
  // Bridgeless hands a module an RCTBridgeProxy: an NSProxy that carries none of NSObject's KVC and
105
207
  // turns an unknown selector into a logged no-op. object_getClass asks without messaging it.
106
208
  static BOOL pwplugin_isRealBridge(id bridge) {
@@ -124,25 +226,50 @@ static BOOL pwplugin_isRealBridge(id bridge) {
124
226
  - (void)pwplugin_original_getInitialURL:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
125
227
  @end
126
228
 
229
+ static void pwplugin_originalGetInitialURL(id self, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject) {
230
+ SEL originalSelector = @selector(pwplugin_original_getInitialURL:reject:);
231
+ if ([self respondsToSelector:originalSelector]) {
232
+ void (*originalIMP)(id, SEL, RCTPromiseResolveBlock, RCTPromiseRejectBlock);
233
+ originalIMP = (void (*)(id, SEL, RCTPromiseResolveBlock, RCTPromiseRejectBlock))objc_msgSend;
234
+ originalIMP(self, originalSelector, resolve, reject);
235
+ } else {
236
+ resolve([NSNull null]);
237
+ }
238
+ }
239
+
127
240
  // Swizzled getInitialURL for RCTLinkingManager (New Architecture support)
128
241
  static void pwplugin_getInitialURL(id self, SEL _cmd, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject) {
129
242
  // If we have a saved deep link URL from push notification, return it
130
243
  if (gPushDeepLinkURL) {
131
244
  NSURL *url = gPushDeepLinkURL;
132
245
  gPushDeepLinkURL = nil; // Clear after use (one-time)
246
+ gInitialURLAnswered = YES;
133
247
  resolve(url.absoluteString);
134
248
  return;
135
249
  }
136
250
 
137
- // Otherwise call the original implementation
138
- SEL originalSelector = @selector(pwplugin_original_getInitialURL:reject:);
139
- if ([self respondsToSelector:originalSelector]) {
140
- void (*originalIMP)(id, SEL, RCTPromiseResolveBlock, RCTPromiseRejectBlock);
141
- originalIMP = (void (*)(id, SEL, RCTPromiseResolveBlock, RCTPromiseRejectBlock))objc_msgSend;
142
- originalIMP(self, originalSelector, resolve, reject);
143
- } else {
144
- resolve([NSNull null]);
251
+ if (gPendingWebLaunchLink && !gInitialURLAnswered) {
252
+ if (!gInitialURLWaiters) {
253
+ gInitialURLWaiters = [NSMutableArray array];
254
+ NSUInteger hold = gInitialURLHoldGeneration;
255
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kPWInitialURLHoldTimeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
256
+ if (hold == gInitialURLHoldGeneration) {
257
+ pwplugin_settleInitialURLWaiters(nil);
258
+ }
259
+ });
260
+ }
261
+ [gInitialURLWaiters addObject:[^(NSString *url) {
262
+ if (url) {
263
+ resolve(url);
264
+ } else {
265
+ pwplugin_originalGetInitialURL(self, resolve, reject);
266
+ }
267
+ } copy]];
268
+ return;
145
269
  }
270
+
271
+ gInitialURLAnswered = YES;
272
+ pwplugin_originalGetInitialURL(self, resolve, reject);
146
273
  }
147
274
 
148
275
  void pushwoosh_swizzle(Class cls, SEL fromChange, SEL toChange, IMP impl, const char * signature) {
@@ -210,7 +337,9 @@ RCT_EXPORT_METHOD(init:(NSDictionary*)config success:(RCTResponseSenderBlock)suc
210
337
  return;
211
338
  }
212
339
 
340
+ gPluginInitialized = YES;
213
341
  [PushNotificationManager initializeWithAppCode:appCode appName:nil];
342
+ pwplugin_setFrameworkTelemetry(kPWFrameworkType, kPWFrameworkVersion);
214
343
  [[PushNotificationManager pushManager] sendAppOpen];
215
344
  [PushNotificationManager pushManager].delegate = self;
216
345
 
@@ -1118,6 +1247,7 @@ RCT_EXPORT_METHOD(getRichMediaType:(RCTResponseSenderBlock)callback) {
1118
1247
  if (onStart) {
1119
1248
  gStartPushData = pushNotification;
1120
1249
  gStartPushReplayed = NO;
1250
+ pwplugin_rememberWebLaunchLink(pushNotification);
1121
1251
  // Save deep link URL for New Architecture (Linking.getInitialURL support)
1122
1252
  NSURL *deepLink = pwplugin_deepLinkURLFromPushLink(pushNotification[@"l"]);
1123
1253
  if (deepLink) {
@@ -1129,6 +1259,7 @@ RCT_EXPORT_METHOD(getRichMediaType:(RCTResponseSenderBlock)callback) {
1129
1259
  if (onStart) {
1130
1260
  gStartPushData = pushNotification;
1131
1261
  gStartPushReplayed = NO;
1262
+ pwplugin_rememberWebLaunchLink(pushNotification);
1132
1263
  // Save deep link URL for New Architecture (Linking.getInitialURL support)
1133
1264
  NSURL *deepLink = pwplugin_deepLinkURLFromPushLink(pushNotification[@"l"]);
1134
1265
  if (deepLink) {
@@ -1181,9 +1312,12 @@ static void pwplugin_acceptColdStartPushOnce(NSDictionary *userInfo) {
1181
1312
  - (void)userNotificationCenter:(UNUserNotificationCenter *)center
1182
1313
  didReceiveNotificationResponse:(UNNotificationResponse *)response
1183
1314
  withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)) {
1184
- if ([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
1315
+ BOOL dismissed = [response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier];
1316
+ // After init the plugin's own delegate or the app accepts the tap; a dismissed push opened nothing.
1317
+ if (!gPluginInitialized && !dismissed && [response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
1185
1318
  NSDictionary *userInfo = response.notification.request.content.userInfo;
1186
1319
  NSURL *appSchemeDeepLink = pwplugin_deepLinkURLFromPushLink(userInfo[@"l"]);
1320
+ pwplugin_rememberWebLaunchLink(userInfo);
1187
1321
 
1188
1322
  if (appSchemeDeepLink) {
1189
1323
  // Routing it through the SDK too would deliver the deep link twice.
@@ -1216,6 +1350,19 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
1216
1350
 
1217
1351
  static PWEarlyNotificationDelegate *gEarlyDelegate = nil;
1218
1352
 
1353
+ // Wraps the delegate the app set in didFinishLaunching. A scene-based app gets the launch push's
1354
+ // response before the main queue drains, so installing it any later loses the push.
1355
+ static void pwplugin_installEarlyNotificationDelegate(void) {
1356
+ if (gEarlyDelegate) {
1357
+ return;
1358
+ }
1359
+
1360
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
1361
+ gEarlyDelegate = [[PWEarlyNotificationDelegate alloc] init];
1362
+ gEarlyDelegate.originalDelegate = center.delegate;
1363
+ center.delegate = gEarlyDelegate;
1364
+ }
1365
+
1219
1366
  @implementation RCTLinkingManager (PushwooshDeepLink)
1220
1367
 
1221
1368
  + (void)load {
@@ -1230,25 +1377,25 @@ static PWEarlyNotificationDelegate *gEarlyDelegate = nil;
1230
1377
  "v@:@@"
1231
1378
  );
1232
1379
 
1233
- // Set up early notification delegate to capture push on cold start
1234
- if (@available(iOS 10.0, *)) {
1235
- dispatch_async(dispatch_get_main_queue(), ^{
1236
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
1237
- gEarlyDelegate = [[PWEarlyNotificationDelegate alloc] init];
1238
- gEarlyDelegate.originalDelegate = center.delegate;
1239
- center.delegate = gEarlyDelegate;
1240
- });
1241
- }
1380
+ // RN keeps the notification name file-local; the string is its contract with the app delegate.
1381
+ [[NSNotificationCenter defaultCenter] addObserverForName:@"RCTOpenURLNotification"
1382
+ object:nil
1383
+ queue:nil
1384
+ usingBlock:^(NSNotification *notification) {
1385
+ pwplugin_handleOpenURLNotification(notification);
1386
+ }];
1242
1387
 
1243
- // Also check launchOptions when app finishes launching
1244
1388
  [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification
1245
1389
  object:nil
1246
1390
  queue:nil
1247
1391
  usingBlock:^(NSNotification *notification) {
1392
+ pwplugin_installEarlyNotificationDelegate();
1393
+
1248
1394
  NSDictionary *launchOptions = notification.userInfo;
1249
1395
  NSDictionary *remoteNotification = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
1250
1396
  if (remoteNotification) {
1251
1397
  NSURL *deepLink = pwplugin_deepLinkURLFromPushLink(remoteNotification[@"l"]);
1398
+ pwplugin_rememberWebLaunchLink(remoteNotification);
1252
1399
  if (deepLink) {
1253
1400
  if (!gPushDeepLinkURL) {
1254
1401
  gPushDeepLinkURL = deepLink;