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