pushwoosh-react-native-plugin 6.1.61 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1
- package/index.d.ts +4 -0
- package/index.js +5 -3
- package/package.json +12 -1
- package/pushwoosh-react-native-plugin.podspec +10 -6
- package/src/android/build.gradle +115 -3
- package/src/android/proguard-rules.pro +16 -0
- package/src/android/src/main/java/com/pushwoosh/reactnativeplugin/ConversionUtil.java +3 -4
- package/src/android/src/main/java/com/pushwoosh/reactnativeplugin/PushwooshPackage.java +32 -19
- package/src/android/src/main/java/com/pushwoosh/reactnativeplugin/PushwooshPlugin.java +71 -32
- package/src/android/src/newarch/java/com/pushwoosh/reactnativeplugin/PushwooshPluginSpec.java +10 -0
- package/src/android/src/oldarch/java/com/pushwoosh/reactnativeplugin/PushwooshPluginSpec.java +11 -0
- package/src/ios/PushwooshPlugin/Pushwoosh.h +6 -0
- package/src/ios/PushwooshPlugin/{Pushwoosh.m → Pushwoosh.mm} +120 -53
- package/src/package.json +11 -0
- package/src/specs/NativePushwoosh.ts +73 -0
package/README.md
CHANGED
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
|
|
26
26
|
- [Integration Guide](https://docs.pushwoosh.com/platform-docs/pushwoosh-sdk/cross-platform-frameworks/react-native/integrating-react-native-plugin) — step-by-step setup
|
|
27
27
|
- [API Reference](docs/README.md) — full API documentation
|
|
28
|
-
- [Sample Project](https://github.com/Pushwoosh/pushwoosh-react-native-plugin/tree/master/example/demoapp) — ready-to-run demo app
|
|
28
|
+
- [Sample Project](https://github.com/Pushwoosh/pushwoosh-react-native-plugin/tree/master/example/demoapp) — ready-to-run demo app on the current React Native (New Architecture)
|
|
29
|
+
- [Legacy Sample Project](https://github.com/Pushwoosh/pushwoosh-react-native-plugin/tree/master/example/demoapp-legacy) — the same demo on React Native 0.74 with the legacy architecture
|
|
29
30
|
|
|
30
31
|
## Features
|
|
31
32
|
|
|
@@ -76,6 +77,29 @@ The wizard will automatically add all required configuration.
|
|
|
76
77
|
|
|
77
78
|
The Pushwoosh plugin already includes the `firebase-messaging` dependency, so you do not need to add it manually.
|
|
78
79
|
|
|
80
|
+
### React Native versions and the New Architecture
|
|
81
|
+
|
|
82
|
+
The plugin is a Turbo Native Module and also works on the legacy bridge. One package covers both:
|
|
83
|
+
|
|
84
|
+
| React Native | Architecture | How the plugin runs |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| 0.74 – 0.81, `newArchEnabled=false` (default) | Legacy bridge | Bridge module |
|
|
87
|
+
| 0.74 – 0.81, `newArchEnabled=true` / `RCT_NEW_ARCH_ENABLED=1` | New Architecture | Codegen TurboModule |
|
|
88
|
+
| 0.82 and later | New Architecture only (bridgeless) | Codegen TurboModule |
|
|
89
|
+
|
|
90
|
+
No interop layer is involved on the New Architecture. Events keep their API on both architectures:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
import { DeviceEventEmitter } from 'react-native';
|
|
94
|
+
|
|
95
|
+
DeviceEventEmitter.addListener('pushOpened', (push) => { /* ... */ });
|
|
96
|
+
DeviceEventEmitter.addListener('pushReceived', (push) => { /* ... */ });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`presentInboxUI()` presents a native screen (a view controller on iOS, an Activity on Android) outside the React view tree, so it does not depend on Fabric. React Native below 0.74 is supported by plugin versions 6.1.x.
|
|
100
|
+
|
|
101
|
+
Both setups ship as samples with identical screens: `example/demoapp` (React Native 0.87, New Architecture) and `example/demoapp-legacy` (React Native 0.74, legacy architecture).
|
|
102
|
+
|
|
79
103
|
## AI-Assisted Integration
|
|
80
104
|
|
|
81
105
|
Integrate the Pushwoosh React Native plugin using AI coding assistants (Claude Code, Cursor, GitHub Copilot, etc.).
|
package/index.d.ts
CHANGED
|
@@ -87,6 +87,10 @@ declare module 'pushwoosh-react-native-plugin' {
|
|
|
87
87
|
isCommunicationEnabled(success: (isEnabled: boolean)=> void): void;
|
|
88
88
|
setCommunicationEnabled(enabled: boolean, success?: () => void, fail?: (error: Error) => void): void;
|
|
89
89
|
|
|
90
|
+
//Rich Media presentation style
|
|
91
|
+
setRichMediaType(type: number): void;
|
|
92
|
+
getRichMediaType(callback: (type: number) => void): void;
|
|
93
|
+
RichMediaStyle: { MODAL: number; LEGACY: number };
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
const Pushwoosh: Pushwoosh;
|
package/index.js
CHANGED
|
@@ -201,9 +201,11 @@
|
|
|
201
201
|
* @module pushwoosh-react-native-plugin
|
|
202
202
|
*/
|
|
203
203
|
|
|
204
|
-
import
|
|
204
|
+
import NativePushwoosh from './src/specs/NativePushwoosh';
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
// Resolved through TurboModuleRegistry: the codegen TurboModule on the New Architecture, the
|
|
207
|
+
// bridge module on the legacy architecture.
|
|
208
|
+
const PushwooshModule = NativePushwoosh;
|
|
207
209
|
|
|
208
210
|
//Constant: RichMediaStyle
|
|
209
211
|
//Rich Media presentation style constants.
|
|
@@ -348,7 +350,7 @@ class PushNotification {
|
|
|
348
350
|
if (!fail) {
|
|
349
351
|
fail = function(error) {};
|
|
350
352
|
}
|
|
351
|
-
PushwooshModule.
|
|
353
|
+
PushwooshModule.registerForPushNotifications(success, fail);
|
|
352
354
|
}
|
|
353
355
|
|
|
354
356
|
//Function: unregister
|
package/package.json
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pushwoosh-react-native-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
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",
|
|
7
|
+
"codegenConfig": {
|
|
8
|
+
"name": "RNPushwooshSpec",
|
|
9
|
+
"type": "modules",
|
|
10
|
+
"jsSrcsDir": "src/specs",
|
|
11
|
+
"android": {
|
|
12
|
+
"javaPackageName": "com.pushwoosh.reactnativeplugin"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
7
15
|
"repository": {
|
|
8
16
|
"type": "git",
|
|
9
17
|
"url": "git+https://github.com/Pushwoosh/pushwoosh-react-native-plugin.git"
|
|
@@ -17,6 +25,9 @@
|
|
|
17
25
|
"bugs": {
|
|
18
26
|
"url": "https://github.com/Pushwoosh/pushwoosh-react-native-plugin/issues"
|
|
19
27
|
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react-native": ">=0.74.0"
|
|
30
|
+
},
|
|
20
31
|
"rnpm": {
|
|
21
32
|
"android": {
|
|
22
33
|
"sourceDir": "./src/android"
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
Pod::Spec.new do |s|
|
|
2
2
|
s.name = "pushwoosh-react-native-plugin"
|
|
3
|
-
s.version = "
|
|
3
|
+
s.version = "7.0.0"
|
|
4
4
|
s.summary = "React Native Pushwoosh Push Notifications module"
|
|
5
5
|
s.requires_arc = true
|
|
6
6
|
s.author = 'Pushwoosh'
|
|
7
7
|
s.license = 'MIT'
|
|
8
8
|
s.homepage = 'n/a'
|
|
9
9
|
s.source = { :git => "https://github.com/Pushwoosh/pushwoosh-react-native-plugin.git" }
|
|
10
|
-
s.source_files = 'src/ios/PushwooshPlugin/Pushwoosh.{h,
|
|
10
|
+
s.source_files = 'src/ios/PushwooshPlugin/Pushwoosh.{h,mm}', 'src/ios/PushwooshPlugin/PWEventDispatcher.{h,m}'
|
|
11
11
|
s.platform = :ios, "13.0"
|
|
12
|
-
s.xcconfig = {
|
|
13
|
-
"HEADER_SEARCH_PATHS" => "${PODS_ROOT}/Headers/Public/React"
|
|
14
|
-
}
|
|
15
12
|
s.static_framework = true
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
# React Native 0.71+ adds React-Core and, when the New Architecture is on, the codegen and
|
|
15
|
+
# TurboModule dependencies together with the RCT_NEW_ARCH_ENABLED define.
|
|
16
|
+
if respond_to?(:install_modules_dependencies, true)
|
|
17
|
+
install_modules_dependencies(s)
|
|
18
|
+
else
|
|
19
|
+
s.dependency "React-Core"
|
|
20
|
+
end
|
|
21
|
+
|
|
18
22
|
s.dependency 'PushwooshXCFramework', '7.2.6'
|
|
19
23
|
s.dependency 'PushwooshInboxUIXCFramework'
|
|
20
24
|
end
|
package/src/android/build.gradle
CHANGED
|
@@ -20,28 +20,135 @@ allprojects {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// [major, minor] of the react-native the app resolves, or null when it cannot be found.
|
|
24
|
+
// Every node_modules above the android project is a candidate: the template keeps react-native in
|
|
25
|
+
// the one next door, a monorepo hoists it any number of levels up. The included build of the React
|
|
26
|
+
// Native Gradle Plugin names the node_modules the app really resolves, though only a 0.74-style
|
|
27
|
+
// settings.gradle exposes it - from 0.82 the include sits in pluginManagement and stays private.
|
|
28
|
+
// `node --print` answers the way Metro does and comes last: it needs node on the Gradle PATH,
|
|
29
|
+
// which an IDE launched from the Finder does not have.
|
|
30
|
+
def reactNativeVersion() {
|
|
31
|
+
def candidates = []
|
|
32
|
+
for (def dir = rootProject.projectDir; dir != null; dir = dir.parentFile) {
|
|
33
|
+
candidates += new File(dir, "node_modules/react-native/package.json")
|
|
34
|
+
}
|
|
35
|
+
project.gradle.includedBuilds.each { included ->
|
|
36
|
+
candidates += new File(included.projectDir, "../../react-native/package.json")
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
def resolved = ["node", "--print", "require.resolve('react-native/package.json')"]
|
|
40
|
+
.execute(null, rootProject.projectDir).text.trim()
|
|
41
|
+
if (!resolved.isEmpty()) {
|
|
42
|
+
candidates += new File(resolved)
|
|
43
|
+
}
|
|
44
|
+
} catch (Exception ignored) {
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (candidate in candidates) {
|
|
48
|
+
if (!candidate.isFile()) {
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
def version = new groovy.json.JsonSlurper().parseText(candidate.text).version
|
|
52
|
+
def match = version =~ /^(\d+)\.(\d+)\./
|
|
53
|
+
if (match) {
|
|
54
|
+
return [match[0][1].toInteger(), match[0][2].toInteger()]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// What the app asked for, or null when it did not ask. The React Native Gradle Plugin ORs the two
|
|
61
|
+
// spellings, so either one set to true turns the New Architecture on and a plain
|
|
62
|
+
// newArchEnabled=false does not veto a react-scoped true.
|
|
63
|
+
def newArchProperty() {
|
|
64
|
+
def names = ["newArchEnabled", "react.newArchEnabled"]
|
|
65
|
+
if (names.any { project.hasProperty(it) && project.property(it).toString().toBoolean() }) {
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
if (names.any { project.hasProperty(it) }) {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The architecture is the app's choice; this answers it the way the React Native Gradle Plugin
|
|
75
|
+
// does. Below 0.82 the app opts in with newArchEnabled; from 0.82 React Native is bridgeless only
|
|
76
|
+
// and ignores the flag. Guessing wrong is silent and lands on the customer: src/oldarch compiled
|
|
77
|
+
// into a bridgeless app leaves TurboModuleRegistry.get() answering null and every plugin call
|
|
78
|
+
// throwing, with a green build. So an undecidable case fails the build instead.
|
|
79
|
+
def resolveNewArchitectureEnabled() {
|
|
80
|
+
def version = reactNativeVersion()
|
|
81
|
+
if (version != null && (version[0] > 0 || version[1] >= 82)) {
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
def asked = newArchProperty()
|
|
86
|
+
if (asked != null) {
|
|
87
|
+
return asked
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (version == null) {
|
|
91
|
+
throw new GradleException(
|
|
92
|
+
"pushwoosh-react-native-plugin: cannot tell which React Native architecture to build. " +
|
|
93
|
+
"No react-native/package.json was found from ${rootProject.projectDir} and neither " +
|
|
94
|
+
"newArchEnabled nor react.newArchEnabled is set. Set newArchEnabled in " +
|
|
95
|
+
"android/gradle.properties to the architecture the app runs.")
|
|
96
|
+
}
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Once per configuration: the lookup walks the file system and may spawn node.
|
|
101
|
+
if (!project.hasProperty("pushwooshNewArchEnabled")) {
|
|
102
|
+
project.ext.pushwooshNewArchEnabled = resolveNewArchitectureEnabled()
|
|
103
|
+
}
|
|
104
|
+
def isNewArchitectureEnabled = project.ext.pushwooshNewArchEnabled
|
|
105
|
+
|
|
23
106
|
apply plugin: 'com.android.library'
|
|
107
|
+
if (isNewArchitectureEnabled) {
|
|
108
|
+
// Generates NativePushwooshSpec from the spec in ../specs, configured by ../package.json
|
|
109
|
+
apply plugin: 'com.facebook.react'
|
|
110
|
+
}
|
|
24
111
|
|
|
25
112
|
android {
|
|
26
113
|
compileSdkVersion 34
|
|
27
114
|
defaultConfig {
|
|
28
115
|
minSdkVersion 21
|
|
29
116
|
targetSdkVersion 34
|
|
117
|
+
// What a consuming app must keep to run minified. The library itself is not minified, so
|
|
118
|
+
// proguardFiles here would configure nothing; consumerProguardFiles is what travels.
|
|
119
|
+
consumerProguardFiles 'proguard-rules.pro'
|
|
30
120
|
}
|
|
31
121
|
buildTypes {
|
|
32
122
|
release {
|
|
33
123
|
minifyEnabled false
|
|
34
|
-
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
sourceSets {
|
|
127
|
+
main {
|
|
128
|
+
if (isNewArchitectureEnabled) {
|
|
129
|
+
java.srcDirs += ["src/newarch/java"]
|
|
130
|
+
} else {
|
|
131
|
+
java.srcDirs += ["src/oldarch/java"]
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// src/test runs under both architectures; the per-architecture suites assert what the
|
|
135
|
+
// switch above must have produced
|
|
136
|
+
test {
|
|
137
|
+
if (isNewArchitectureEnabled) {
|
|
138
|
+
java.srcDirs += ["src/testNewarch/java"]
|
|
139
|
+
} else {
|
|
140
|
+
java.srcDirs += ["src/testOldarch/java"]
|
|
141
|
+
}
|
|
35
142
|
}
|
|
36
143
|
}
|
|
37
144
|
}
|
|
38
145
|
|
|
39
146
|
ext {
|
|
40
|
-
pushwoosh = "6.
|
|
147
|
+
pushwoosh = "6.13.0"
|
|
41
148
|
}
|
|
42
149
|
|
|
43
150
|
dependencies {
|
|
44
|
-
implementation
|
|
151
|
+
implementation "com.facebook.react:react-android"
|
|
45
152
|
implementation "com.pushwoosh:pushwoosh:${pushwoosh}"
|
|
46
153
|
implementation "com.pushwoosh:pushwoosh-firebase:${pushwoosh}"
|
|
47
154
|
implementation "com.pushwoosh:pushwoosh-huawei:${pushwoosh}"
|
|
@@ -54,5 +161,10 @@ dependencies {
|
|
|
54
161
|
implementation "androidx.activity:activity:1.8.0"
|
|
55
162
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22"
|
|
56
163
|
implementation "com.google.firebase:firebase-messaging:23.1.0"
|
|
164
|
+
|
|
165
|
+
// Test-only, run with `make android-test`.
|
|
166
|
+
testImplementation "junit:junit:4.13.2"
|
|
167
|
+
testImplementation "org.robolectric:robolectric:4.14.1"
|
|
168
|
+
testImplementation "org.mockito:mockito-core:5.14.2"
|
|
57
169
|
}
|
|
58
170
|
|
|
@@ -15,3 +15,19 @@
|
|
|
15
15
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
16
16
|
# public *;
|
|
17
17
|
#}
|
|
18
|
+
|
|
19
|
+
# Shipped to the consuming app through consumerProguardFiles.
|
|
20
|
+
#
|
|
21
|
+
# The Pushwoosh SDK stores its requests with WorkManager, which keeps them in a Room database.
|
|
22
|
+
# room-runtime ships `-keep class * extends androidx.room.RoomDatabase`, which holds the generated
|
|
23
|
+
# class but not the constructor Room instantiates it with, and the R8 full mode AGP 8 runs by
|
|
24
|
+
# default drops that constructor. A minified Release app then dies on startup in androidx.startup
|
|
25
|
+
# with "Failed to create an instance of class androidx.work.impl.WorkDatabase".
|
|
26
|
+
-keep class * extends androidx.room.RoomDatabase { <init>(); }
|
|
27
|
+
|
|
28
|
+
# The same shape one layer up: WorkManager instantiates the work request's input merger by name,
|
|
29
|
+
# and work-runtime ships `-keep class * extends androidx.work.InputMerger`, which holds the class
|
|
30
|
+
# but not that constructor. Under R8 full mode every worker the SDK enqueues then dies before it
|
|
31
|
+
# runs with "Could not create Input Merger androidx.work.OverwritingInputMerger", so registering
|
|
32
|
+
# for pushes reports neither a token nor an error - the app just never hears back.
|
|
33
|
+
-keep class * extends androidx.work.InputMerger { <init>(); }
|
|
@@ -6,14 +6,13 @@ import android.util.Log;
|
|
|
6
6
|
import java.util.ArrayList;
|
|
7
7
|
import java.util.Iterator;
|
|
8
8
|
|
|
9
|
+
import com.facebook.react.bridge.Arguments;
|
|
9
10
|
import com.facebook.react.bridge.ReadableArray;
|
|
10
11
|
import com.facebook.react.bridge.ReadableMap;
|
|
11
12
|
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
|
12
13
|
import com.facebook.react.bridge.ReadableType;
|
|
13
14
|
import com.facebook.react.bridge.WritableArray;
|
|
14
15
|
import com.facebook.react.bridge.WritableMap;
|
|
15
|
-
import com.facebook.react.bridge.WritableNativeArray;
|
|
16
|
-
import com.facebook.react.bridge.WritableNativeMap;
|
|
17
16
|
import com.pushwoosh.inbox.data.InboxMessage;
|
|
18
17
|
import com.pushwoosh.internal.utils.JsonUtils;
|
|
19
18
|
import com.pushwoosh.internal.utils.PWLog;
|
|
@@ -115,7 +114,7 @@ public final class ConversionUtil {
|
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
public static WritableMap toWritableMap(JSONObject jsonObject) {
|
|
118
|
-
|
|
117
|
+
WritableMap result = Arguments.createMap();
|
|
119
118
|
|
|
120
119
|
Iterator<String> keys = jsonObject.keys();
|
|
121
120
|
while (keys.hasNext()) {
|
|
@@ -152,7 +151,7 @@ public final class ConversionUtil {
|
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
public static WritableArray toWritableArray(JSONArray array) {
|
|
155
|
-
|
|
154
|
+
WritableArray result = Arguments.createArray();
|
|
156
155
|
|
|
157
156
|
for (int i = 0; i < array.length(); i++) {
|
|
158
157
|
try {
|
|
@@ -1,33 +1,46 @@
|
|
|
1
1
|
package com.pushwoosh.reactnativeplugin;
|
|
2
2
|
|
|
3
|
+
import androidx.annotation.Nullable;
|
|
4
|
+
|
|
5
|
+
import com.facebook.react.BaseReactPackage;
|
|
3
6
|
import com.facebook.react.ReactPackage;
|
|
4
|
-
import com.facebook.react.bridge.JavaScriptModule;
|
|
5
7
|
import com.facebook.react.bridge.NativeModule;
|
|
6
8
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
9
|
+
import com.facebook.react.module.model.ReactModuleInfo;
|
|
10
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider;
|
|
11
|
+
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
|
|
7
12
|
|
|
8
|
-
import java.util.
|
|
9
|
-
import java.util.
|
|
10
|
-
import java.util.List;
|
|
13
|
+
import java.util.HashMap;
|
|
14
|
+
import java.util.Map;
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return Collections.emptyList();
|
|
16
|
-
}
|
|
16
|
+
// BaseReactPackage already implements ReactPackage; it is repeated because the autolinking scan
|
|
17
|
+
// of the React Native 0.74 CLI only recognises "implements ReactPackage" or "extends TurboReactPackage"
|
|
18
|
+
public class PushwooshPackage extends BaseReactPackage implements ReactPackage {
|
|
17
19
|
|
|
20
|
+
@Nullable
|
|
18
21
|
@Override
|
|
19
|
-
public
|
|
20
|
-
|
|
22
|
+
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
|
|
23
|
+
if (PushwooshPlugin.MODULE_NAME.equals(name)) {
|
|
24
|
+
return new PushwooshPlugin(reactContext);
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
@Override
|
|
24
|
-
public
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
public ReactModuleInfoProvider getReactModuleInfoProvider() {
|
|
31
|
+
return () -> {
|
|
32
|
+
// On the New Architecture PushwooshPluginSpec extends the codegen spec, which is a TurboModule
|
|
33
|
+
boolean isTurboModule = TurboModule.class.isAssignableFrom(PushwooshPlugin.class);
|
|
34
|
+
Map<String, ReactModuleInfo> modules = new HashMap<>();
|
|
35
|
+
modules.put(PushwooshPlugin.MODULE_NAME, new ReactModuleInfo(
|
|
36
|
+
PushwooshPlugin.MODULE_NAME,
|
|
37
|
+
PushwooshPlugin.class.getName(),
|
|
38
|
+
false, // canOverrideExistingModule
|
|
39
|
+
false, // needsEagerInit: the launch push is cached until JS calls init
|
|
40
|
+
false, // isCxxModule
|
|
41
|
+
isTurboModule
|
|
42
|
+
));
|
|
43
|
+
return modules;
|
|
44
|
+
};
|
|
31
45
|
}
|
|
32
46
|
}
|
|
33
|
-
|
|
@@ -8,14 +8,14 @@ import android.graphics.Color;
|
|
|
8
8
|
import androidx.annotation.NonNull;
|
|
9
9
|
import androidx.annotation.Nullable;
|
|
10
10
|
|
|
11
|
+
import com.facebook.react.bridge.Arguments;
|
|
11
12
|
import com.facebook.react.bridge.Callback;
|
|
12
13
|
import com.facebook.react.bridge.LifecycleEventListener;
|
|
13
14
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
14
|
-
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
15
15
|
import com.facebook.react.bridge.ReactMethod;
|
|
16
16
|
import com.facebook.react.bridge.ReadableArray;
|
|
17
17
|
import com.facebook.react.bridge.ReadableMap;
|
|
18
|
-
import com.facebook.react.bridge.
|
|
18
|
+
import com.facebook.react.bridge.WritableArray;
|
|
19
19
|
import com.pushwoosh.Pushwoosh;
|
|
20
20
|
import com.pushwoosh.RegisterForPushNotificationsResultData;
|
|
21
21
|
import com.pushwoosh.badge.PushwooshBadge;
|
|
@@ -50,9 +50,11 @@ import java.util.Collection;
|
|
|
50
50
|
import java.util.HashMap;
|
|
51
51
|
import java.util.Map;
|
|
52
52
|
|
|
53
|
-
public class PushwooshPlugin extends
|
|
53
|
+
public class PushwooshPlugin extends PushwooshPluginSpec implements LifecycleEventListener {
|
|
54
54
|
|
|
55
55
|
static final String TAG = "ReactNativePlugin";
|
|
56
|
+
// Same as the iOS class name: React Native resolves a TurboModule by class name first
|
|
57
|
+
public static final String MODULE_NAME = "PushwooshPlugin";
|
|
56
58
|
private static final String PUSH_OPEN_EVENT = "PwPushOpened";
|
|
57
59
|
private static final String PUSH_OPEN_JS_EVENT = "pushOpened";
|
|
58
60
|
|
|
@@ -91,7 +93,7 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
91
93
|
|
|
92
94
|
@Override
|
|
93
95
|
public String getName() {
|
|
94
|
-
return
|
|
96
|
+
return MODULE_NAME;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
@ReactMethod
|
|
@@ -127,9 +129,12 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
127
129
|
if (sStartPushData != null) {
|
|
128
130
|
sendEvent(PUSH_OPEN_JS_EVENT, ConversionUtil.stringToJSONObject(sStartPushData));
|
|
129
131
|
}
|
|
130
|
-
}
|
|
131
132
|
|
|
132
|
-
|
|
133
|
+
// Under the same lock as the replay above: a push taking the lock between the replay
|
|
134
|
+
// and this flag would find sInitialized false, skip its own send, and never be
|
|
135
|
+
// replayed either.
|
|
136
|
+
sInitialized = true;
|
|
137
|
+
}
|
|
133
138
|
|
|
134
139
|
if (success != null) {
|
|
135
140
|
success.invoke();
|
|
@@ -137,7 +142,7 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
@ReactMethod
|
|
140
|
-
public void
|
|
145
|
+
public void registerForPushNotifications(final Callback success, final Callback error) {
|
|
141
146
|
Pushwoosh.getInstance().registerForPushNotifications(new RegisterForPushNotificationCallback(success, error));
|
|
142
147
|
}
|
|
143
148
|
|
|
@@ -193,7 +198,7 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
193
198
|
}
|
|
194
199
|
|
|
195
200
|
@ReactMethod
|
|
196
|
-
public void removeListeners(
|
|
201
|
+
public void removeListeners(double count) {
|
|
197
202
|
// Required for NativeEventEmitter. No need to do anything here.
|
|
198
203
|
}
|
|
199
204
|
|
|
@@ -313,22 +318,22 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
313
318
|
|
|
314
319
|
JSONObject params = ConversionUtil.toJsonObject(data);
|
|
315
320
|
|
|
321
|
+
// optString answers "" for a missing key, never null: without a message there is nothing
|
|
322
|
+
// to show, and empty user data has no business reaching the payload.
|
|
316
323
|
String message = params.optString("msg");
|
|
317
|
-
if (message
|
|
324
|
+
if (message.isEmpty()){
|
|
325
|
+
PWLog.error(TAG, "createLocalNotification: msg is required");
|
|
318
326
|
return;
|
|
319
327
|
}
|
|
320
328
|
int seconds = params.optInt("seconds");
|
|
321
329
|
Bundle extras = new Bundle();
|
|
322
330
|
String userData = params.optString("userData");
|
|
323
|
-
if (userData
|
|
331
|
+
if (!userData.isEmpty()){
|
|
324
332
|
extras.putString("u", userData);
|
|
325
333
|
}
|
|
326
334
|
|
|
327
|
-
LocalNotification
|
|
328
|
-
|
|
329
|
-
builder.setExtras(extras);
|
|
330
|
-
}
|
|
331
|
-
LocalNotification notification = builder
|
|
335
|
+
LocalNotification notification = new LocalNotification.Builder()
|
|
336
|
+
.setExtras(extras)
|
|
332
337
|
.setMessage(message)
|
|
333
338
|
.setDelay(seconds)
|
|
334
339
|
.build();
|
|
@@ -351,8 +356,8 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
351
356
|
}
|
|
352
357
|
|
|
353
358
|
@ReactMethod
|
|
354
|
-
public void setApplicationIconBadgeNumber(
|
|
355
|
-
PushwooshBadge.setBadgeNumber(badgeNumber);
|
|
359
|
+
public void setApplicationIconBadgeNumber(double badgeNumber) {
|
|
360
|
+
PushwooshBadge.setBadgeNumber((int) badgeNumber);
|
|
356
361
|
}
|
|
357
362
|
|
|
358
363
|
@ReactMethod
|
|
@@ -361,8 +366,8 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
361
366
|
}
|
|
362
367
|
|
|
363
368
|
@ReactMethod
|
|
364
|
-
public void addToApplicationIconBadgeNumber(
|
|
365
|
-
PushwooshBadge.addBadgeNumber(badgeNumber);
|
|
369
|
+
public void addToApplicationIconBadgeNumber(double badgeNumber) {
|
|
370
|
+
PushwooshBadge.addBadgeNumber((int) badgeNumber);
|
|
366
371
|
}
|
|
367
372
|
|
|
368
373
|
@ReactMethod
|
|
@@ -381,18 +386,22 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
381
386
|
}
|
|
382
387
|
|
|
383
388
|
@ReactMethod
|
|
384
|
-
public void setColorLED(
|
|
385
|
-
|
|
389
|
+
public void setColorLED(double color) {
|
|
390
|
+
// The spec types a colour as a JS number, so it arrives as a double, and an opaque ARGB
|
|
391
|
+
// value does not fit a signed int: 0xFFFF0000 is 4294901760. A direct double -> int
|
|
392
|
+
// narrowing clamps that to Integer.MAX_VALUE, while going through long keeps the bits the
|
|
393
|
+
// SDK expects (-65536).
|
|
394
|
+
PushwooshNotificationSettings.setColorLED((int) (long) color);
|
|
386
395
|
}
|
|
387
396
|
|
|
388
397
|
@ReactMethod
|
|
389
|
-
public void setSoundType(
|
|
390
|
-
PushwooshNotificationSettings.setSoundNotificationType(SoundType.fromInt(type));
|
|
398
|
+
public void setSoundType(double type) {
|
|
399
|
+
PushwooshNotificationSettings.setSoundNotificationType(SoundType.fromInt((int) type));
|
|
391
400
|
}
|
|
392
401
|
|
|
393
402
|
@ReactMethod
|
|
394
|
-
public void setVibrateType(
|
|
395
|
-
PushwooshNotificationSettings.setVibrateNotificationType(VibrateType.fromInt(type));
|
|
403
|
+
public void setVibrateType(double type) {
|
|
404
|
+
PushwooshNotificationSettings.setVibrateNotificationType(VibrateType.fromInt((int) type));
|
|
396
405
|
}
|
|
397
406
|
|
|
398
407
|
|
|
@@ -455,7 +464,7 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
455
464
|
try {
|
|
456
465
|
if (result.isSuccess() && result.getData() != null) {
|
|
457
466
|
ArrayList<InboxMessage> messagesList = new ArrayList<>(result.getData());
|
|
458
|
-
|
|
467
|
+
WritableArray writableArray = Arguments.createArray();
|
|
459
468
|
for (InboxMessage message : messagesList) {
|
|
460
469
|
writableArray.pushMap(ConversionUtil.inboxMessageToWritableMap(message));
|
|
461
470
|
}
|
|
@@ -551,8 +560,8 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
551
560
|
}
|
|
552
561
|
|
|
553
562
|
@ReactMethod
|
|
554
|
-
public void setRichMediaType(
|
|
555
|
-
RichMediaType richMediaType = type == 0 ? RichMediaType.MODAL : RichMediaType.DEFAULT;
|
|
563
|
+
public void setRichMediaType(double type) {
|
|
564
|
+
RichMediaType richMediaType = (int) type == 0 ? RichMediaType.MODAL : RichMediaType.DEFAULT;
|
|
556
565
|
RichMediaManager.setRichMediaType(richMediaType);
|
|
557
566
|
}
|
|
558
567
|
|
|
@@ -562,6 +571,17 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
562
571
|
callback.invoke(type.ordinal());
|
|
563
572
|
}
|
|
564
573
|
|
|
574
|
+
// iOS-only setting. Android shows a notification for a push that arrives in the foreground,
|
|
575
|
+
// which is what the iOS default reports; the methods exist so the spec is fully implemented.
|
|
576
|
+
@ReactMethod
|
|
577
|
+
public void setShowPushnotificationAlert(boolean showPushnotificationAlert) {
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
@ReactMethod
|
|
581
|
+
public void getShowPushnotificationAlert(Callback callback) {
|
|
582
|
+
callback.invoke(true);
|
|
583
|
+
}
|
|
584
|
+
|
|
565
585
|
///
|
|
566
586
|
/// LifecycleEventListener callbacks
|
|
567
587
|
///
|
|
@@ -580,11 +600,30 @@ public class PushwooshPlugin extends ReactContextBaseJavaModule implements Lifec
|
|
|
580
600
|
public void onHostDestroy() {
|
|
581
601
|
PWLog.noise(TAG, "Host destroyed");
|
|
582
602
|
|
|
583
|
-
|
|
584
|
-
|
|
603
|
+
// A push can be delivered on the FCM thread while the host is going down, and every other
|
|
604
|
+
// accessor of these fields holds the lock.
|
|
605
|
+
synchronized (sStartPushLock) {
|
|
606
|
+
// Only for the module this host is taking down. A restarted host builds a new
|
|
607
|
+
// PushwooshPlugin, and the order of the two events is not guaranteed, so a late
|
|
608
|
+
// teardown must not reset what the new module has already set up.
|
|
609
|
+
if (INSTANCE != this) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
585
612
|
|
|
586
|
-
|
|
587
|
-
|
|
613
|
+
sPushCallbackRegistered = false;
|
|
614
|
+
sStartPushData = null;
|
|
615
|
+
|
|
616
|
+
sReceivedPushCallbackRegistered = false;
|
|
617
|
+
sReceivedPushData = null;
|
|
618
|
+
|
|
619
|
+
// These are what openPush()/messageReceived() consult before sending to JS. Left
|
|
620
|
+
// standing, a push arriving after the host died was handed to a context with no JS
|
|
621
|
+
// runtime, the broad catch there swallowed the failure, and the push was gone for
|
|
622
|
+
// good - the cache that would have replayed it on the next init() had just been
|
|
623
|
+
// cleared above. Cleared, the same push is cached and the next init() replays it.
|
|
624
|
+
sInitialized = false;
|
|
625
|
+
INSTANCE = null;
|
|
626
|
+
}
|
|
588
627
|
}
|
|
589
628
|
|
|
590
629
|
///
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
package com.pushwoosh.reactnativeplugin;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
4
|
+
|
|
5
|
+
// New Architecture: NativePushwooshSpec is generated by codegen from src/specs/NativePushwoosh.ts.
|
|
6
|
+
abstract class PushwooshPluginSpec extends NativePushwooshSpec {
|
|
7
|
+
PushwooshPluginSpec(ReactApplicationContext reactContext) {
|
|
8
|
+
super(reactContext);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package com.pushwoosh.reactnativeplugin;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
4
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
5
|
+
|
|
6
|
+
// Legacy architecture: a plain bridge module whose methods are exported by @ReactMethod.
|
|
7
|
+
abstract class PushwooshPluginSpec extends ReactContextBaseJavaModule {
|
|
8
|
+
PushwooshPluginSpec(ReactApplicationContext reactContext) {
|
|
9
|
+
super(reactContext);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
#import "PushwooshInboxUI.h"
|
|
25
25
|
#endif
|
|
26
26
|
|
|
27
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
28
|
+
#import <RNPushwooshSpec/RNPushwooshSpec.h>
|
|
29
|
+
|
|
30
|
+
@interface PushwooshPlugin: RCTEventEmitter<NativePushwooshSpec, PushNotificationDelegate>
|
|
31
|
+
#else
|
|
27
32
|
@interface PushwooshPlugin: RCTEventEmitter<RCTBridgeModule, PushNotificationDelegate>
|
|
33
|
+
#endif
|
|
28
34
|
|
|
29
35
|
@end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//
|
|
2
|
-
// Pushwoosh.
|
|
2
|
+
// Pushwoosh.mm
|
|
3
3
|
// Pushwoosh React Native Plugin
|
|
4
4
|
// (c) Pushwoosh 2016
|
|
5
5
|
//
|
|
@@ -69,6 +69,7 @@ static PWEmailsCompletion emailsCompletionHandler(NSUInteger addressCount, RCTRe
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
static NSDictionary * gStartPushData = nil;
|
|
72
|
+
static BOOL gStartPushReplayed = NO; // Launch push already replayed into the JS events by init:
|
|
72
73
|
static NSURL * gPushDeepLinkURL = nil; // Deep link URL for New Architecture support
|
|
73
74
|
static NSString * gEarlyHandledHash = nil; // Last push hash the cold-start delegate accepted, to avoid double handling
|
|
74
75
|
static NSString * const kRegistrationSuccesEvent = @"PWRegistrationSuccess";
|
|
@@ -144,17 +145,17 @@ static void pwplugin_getInitialURL(id self, SEL _cmd, RCTPromiseResolveBlock res
|
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
void pushwoosh_swizzle(Class
|
|
148
|
+
void pushwoosh_swizzle(Class cls, SEL fromChange, SEL toChange, IMP impl, const char * signature) {
|
|
148
149
|
Method method = nil;
|
|
149
|
-
method = class_getInstanceMethod(
|
|
150
|
+
method = class_getInstanceMethod(cls, fromChange);
|
|
150
151
|
|
|
151
152
|
if (method) {
|
|
152
153
|
//method exists add a new method and swap with original
|
|
153
|
-
class_addMethod(
|
|
154
|
-
method_exchangeImplementations(class_getInstanceMethod(
|
|
154
|
+
class_addMethod(cls, toChange, impl, signature);
|
|
155
|
+
method_exchangeImplementations(class_getInstanceMethod(cls, fromChange), class_getInstanceMethod(cls, toChange));
|
|
155
156
|
} else {
|
|
156
157
|
//just add as orignal method
|
|
157
|
-
class_addMethod(
|
|
158
|
+
class_addMethod(cls, fromChange, impl, signature);
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
|
|
@@ -173,7 +174,15 @@ void pushwoosh_swizzle(Class class, SEL fromChange, SEL toChange, IMP impl, cons
|
|
|
173
174
|
|
|
174
175
|
#pragma mark - Pushwoosh RCTBridgeModule
|
|
175
176
|
|
|
176
|
-
|
|
177
|
+
// No custom name: React Native looks a TurboModule up by NSClassFromString(name) before it
|
|
178
|
+
// consults the RCT_EXPORT_MODULE registry, and "Pushwoosh" is the native SDK's own class.
|
|
179
|
+
RCT_EXPORT_MODULE();
|
|
180
|
+
|
|
181
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
182
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
183
|
+
return std::make_shared<facebook::react::NativePushwooshSpecJSI>(params);
|
|
184
|
+
}
|
|
185
|
+
#endif
|
|
177
186
|
|
|
178
187
|
- (dispatch_queue_t)methodQueue {
|
|
179
188
|
return dispatch_get_main_queue();
|
|
@@ -250,32 +259,43 @@ RCT_EXPORT_METHOD(init:(NSDictionary*)config success:(RCTResponseSenderBlock)suc
|
|
|
250
259
|
success(@[]);
|
|
251
260
|
}
|
|
252
261
|
|
|
253
|
-
|
|
254
|
-
|
|
262
|
+
// Once per launch push. Neither source clears itself here - gStartPushData is consumed by the
|
|
263
|
+
// legacy onPushOpen:, launchNotification belongs to the SDK - so a second init() (a consent
|
|
264
|
+
// flow initialising after the user agreed, an app switching application code, a Fast Refresh
|
|
265
|
+
// reload) used to replay the same push as pushReceived and pushOpened again and to re-arm a
|
|
266
|
+
// deep link that Linking.getInitialURL() had already consumed, sending the user back to the
|
|
267
|
+
// screen the push opened the first time.
|
|
268
|
+
if (!gStartPushReplayed) {
|
|
269
|
+
gStartPushReplayed = YES;
|
|
255
270
|
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
gPushDeepLinkURL = deepLink;
|
|
259
|
-
}
|
|
271
|
+
if (gStartPushData) {
|
|
272
|
+
NSURL *deepLink = pwplugin_deepLinkURLFromPushLink(gStartPushData[@"l"]);
|
|
260
273
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
274
|
+
if (deepLink) {
|
|
275
|
+
if (!gPushDeepLinkURL) {
|
|
276
|
+
gPushDeepLinkURL = deepLink;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Writing the link into launchOptions is what makes RCTLinking report it, but only
|
|
280
|
+
// a real bridge accepts the write. Bridgeless reads it from the swizzled
|
|
281
|
+
// getInitialURL.
|
|
282
|
+
if (pwplugin_isRealBridge(self.bridge)) {
|
|
283
|
+
NSMutableDictionary *launchOptions = self.bridge.launchOptions.mutableCopy;
|
|
284
|
+
launchOptions[UIApplicationLaunchOptionsURLKey] = deepLink;
|
|
285
|
+
[self.bridge setValue:launchOptions forKey:@"launchOptions"];
|
|
286
|
+
}
|
|
267
287
|
}
|
|
288
|
+
|
|
289
|
+
[self sendJSEvent:kPushReceivedJSEvent withArgs:gStartPushData];
|
|
290
|
+
[self sendJSEvent:kPushOpenJSEvent withArgs:gStartPushData];
|
|
291
|
+
} else if([PushNotificationManager pushManager].launchNotification) {
|
|
292
|
+
[self sendJSEvent:kPushReceivedJSEvent withArgs:[PushNotificationManager pushManager].launchNotification];
|
|
293
|
+
[self sendJSEvent:kPushOpenJSEvent withArgs:[PushNotificationManager pushManager].launchNotification];
|
|
268
294
|
}
|
|
269
|
-
|
|
270
|
-
[self sendJSEvent:kPushReceivedJSEvent withArgs:gStartPushData];
|
|
271
|
-
[self sendJSEvent:kPushOpenJSEvent withArgs:gStartPushData];
|
|
272
|
-
} else if([PushNotificationManager pushManager].launchNotification) {
|
|
273
|
-
[self sendJSEvent:kPushReceivedJSEvent withArgs:[PushNotificationManager pushManager].launchNotification];
|
|
274
|
-
[self sendJSEvent:kPushOpenJSEvent withArgs:[PushNotificationManager pushManager].launchNotification];
|
|
275
295
|
}
|
|
276
296
|
}
|
|
277
297
|
|
|
278
|
-
RCT_EXPORT_METHOD(
|
|
298
|
+
RCT_EXPORT_METHOD(registerForPushNotifications:(RCTResponseSenderBlock)success error:(RCTResponseSenderBlock)error) {
|
|
279
299
|
[[PWEventDispatcher sharedDispatcher] subscribe:success toEvent:kRegistrationSuccesEvent];
|
|
280
300
|
[[PWEventDispatcher sharedDispatcher] subscribe:error toEvent:kRegistrationErrorEvent];
|
|
281
301
|
|
|
@@ -410,13 +430,13 @@ RCT_EXPORT_METHOD(setUserId:(NSString*)userId success:(RCTResponseSenderBlock)su
|
|
|
410
430
|
}];
|
|
411
431
|
}
|
|
412
432
|
|
|
413
|
-
RCT_EXPORT_METHOD(postEvent:(NSString*)event
|
|
433
|
+
RCT_EXPORT_METHOD(postEvent:(NSString*)event attributes:(NSDictionary*)attributes) {
|
|
414
434
|
[[PWInAppManager sharedManager] postEvent:event withAttributes:attributes];
|
|
415
435
|
}
|
|
416
436
|
|
|
417
|
-
RCT_EXPORT_METHOD(setApplicationIconBadgeNumber:(
|
|
437
|
+
RCT_EXPORT_METHOD(setApplicationIconBadgeNumber:(double)badgeNumber) {
|
|
418
438
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
419
|
-
[UIApplication sharedApplication].applicationIconBadgeNumber =
|
|
439
|
+
[UIApplication sharedApplication].applicationIconBadgeNumber = (NSInteger)badgeNumber;
|
|
420
440
|
});
|
|
421
441
|
}
|
|
422
442
|
|
|
@@ -432,9 +452,9 @@ RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback
|
|
|
432
452
|
}
|
|
433
453
|
}
|
|
434
454
|
|
|
435
|
-
RCT_EXPORT_METHOD(addToApplicationIconBadgeNumber:(
|
|
455
|
+
RCT_EXPORT_METHOD(addToApplicationIconBadgeNumber:(double)badgeNumber) {
|
|
436
456
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
437
|
-
[UIApplication sharedApplication].applicationIconBadgeNumber +=
|
|
457
|
+
[UIApplication sharedApplication].applicationIconBadgeNumber += (NSInteger)badgeNumber;
|
|
438
458
|
});
|
|
439
459
|
}
|
|
440
460
|
|
|
@@ -448,7 +468,7 @@ RCT_EXPORT_METHOD(presentInboxUI:(NSDictionary *)styleDictionary) {
|
|
|
448
468
|
inboxViewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Close", @"Close") style:UIBarButtonItemStylePlain target:self action:@selector(closeInbox)];
|
|
449
469
|
[[PushwooshPlugin findRootViewController] presentViewController:[[UINavigationController alloc] initWithRootViewController:inboxViewController] animated:YES completion:nil];
|
|
450
470
|
|
|
451
|
-
__weak
|
|
471
|
+
__weak __typeof__(self) wself = self;
|
|
452
472
|
inboxViewController.onMessageClickBlock = ^(NSObject<PWInboxMessageProtocol> *message) {
|
|
453
473
|
if (message.type == PWInboxMessageTypeDeeplink) {
|
|
454
474
|
[wself closeInbox];
|
|
@@ -484,15 +504,19 @@ RCT_EXPORT_METHOD(messagesCount:(RCTResponseSenderBlock)callback) {
|
|
|
484
504
|
|
|
485
505
|
RCT_EXPORT_METHOD(loadMessages:(RCTResponseSenderBlock)success fail:(RCTResponseSenderBlock)fail) {
|
|
486
506
|
[PWInbox loadMessagesWithCompletion:^(NSArray<NSObject<PWInboxMessageProtocol> *> *messages, NSError *error) {
|
|
507
|
+
if (error) {
|
|
508
|
+
if (fail) {
|
|
509
|
+
fail(@[ objectOrNull([error localizedDescription]) ]);
|
|
510
|
+
}
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
487
514
|
if (success) {
|
|
488
|
-
NSMutableArray*
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
success( @[ array ]);
|
|
494
|
-
} else if (error != nil && fail != nil) {
|
|
495
|
-
fail(@[ error ]);
|
|
515
|
+
NSMutableArray *array = [[NSMutableArray alloc] init];
|
|
516
|
+
for (NSObject<PWInboxMessageProtocol> *message in messages) {
|
|
517
|
+
[array addObject:[self inboxMessageToDictionary:message]];
|
|
518
|
+
}
|
|
519
|
+
success(@[ array ]);
|
|
496
520
|
}
|
|
497
521
|
}];
|
|
498
522
|
}
|
|
@@ -584,25 +608,50 @@ API_AVAILABLE(ios(10.0)) {
|
|
|
584
608
|
NSString *currentHash = content.userInfo[@"p"];
|
|
585
609
|
BOOL isNeedToImplementInPlugin = [[PushwooshPlugin getPluginImplementationInfoPlistKey] boolValue];
|
|
586
610
|
|
|
611
|
+
// 0 when this notification is not ours to present: someone else's, a repeat of the same
|
|
612
|
+
// message, or a Pushwoosh push the app asked us not to show an alert for.
|
|
613
|
+
UNNotificationPresentationOptions options = 0;
|
|
614
|
+
|
|
587
615
|
if (isPushwooshMessage && isNeedToImplementInPlugin && ![lastPushwooshHash isEqualToString:currentHash]) {
|
|
588
616
|
lastPushwooshHash = currentHash;
|
|
589
617
|
|
|
590
618
|
if ([PushNotificationManager pushManager].showPushnotificationAlert) {
|
|
591
|
-
|
|
619
|
+
options = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionAlert;
|
|
592
620
|
} else {
|
|
593
621
|
[[PushNotificationManager pushManager] handlePushReceived:content.userInfo];
|
|
594
622
|
}
|
|
595
623
|
|
|
596
624
|
[self sendJSEvent:kPushReceivedJSEvent withArgs:content.userInfo];
|
|
597
625
|
}
|
|
598
|
-
|
|
626
|
+
|
|
627
|
+
// UNUserNotificationCenter waits for exactly one answer: a second call is an error it logs,
|
|
628
|
+
// and no call at all leaves the notification unpresented. Both used to be reachable here -
|
|
629
|
+
// our own alert answered and the app's delegate answered again with the same handler, while a
|
|
630
|
+
// notification that is neither ours nor claimed by an app delegate was never answered at all.
|
|
631
|
+
// So whoever answers first decides and the rest is swallowed.
|
|
632
|
+
__block BOOL answered = NO;
|
|
633
|
+
void (^answerOnce)(UNNotificationPresentationOptions) = ^(UNNotificationPresentationOptions presentationOptions) {
|
|
634
|
+
if (answered) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
answered = YES;
|
|
638
|
+
completionHandler(presentationOptions);
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// The SDK's alert goes up straight away, as before.
|
|
642
|
+
if (options != 0) {
|
|
643
|
+
answerOnce(options);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// The app's own delegate still gets the callback either way. A Pushwoosh message is handed
|
|
647
|
+
// over with a delay, so the SDK presents its own alert first.
|
|
599
648
|
if (_originalNotificationCenterDelegate != nil &&
|
|
600
649
|
_originalNotificationCenterDelegateResponds.willPresentNotification) {
|
|
601
650
|
|
|
602
651
|
dispatch_block_t presentationBlock = ^{
|
|
603
652
|
[self->_originalNotificationCenterDelegate userNotificationCenter:center
|
|
604
653
|
willPresentNotification:notification
|
|
605
|
-
withCompletionHandler:
|
|
654
|
+
withCompletionHandler:answerOnce];
|
|
606
655
|
};
|
|
607
656
|
|
|
608
657
|
if (isPushwooshMessage) {
|
|
@@ -610,14 +659,10 @@ API_AVAILABLE(ios(10.0)) {
|
|
|
610
659
|
} else {
|
|
611
660
|
presentationBlock();
|
|
612
661
|
}
|
|
662
|
+
return;
|
|
613
663
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
_originalNotificationCenterDelegateResponds.willPresentNotification) {
|
|
617
|
-
[_originalNotificationCenterDelegate userNotificationCenter:center
|
|
618
|
-
willPresentNotification:notification
|
|
619
|
-
withCompletionHandler:completionHandler];
|
|
620
|
-
}
|
|
664
|
+
|
|
665
|
+
answerOnce(options);
|
|
621
666
|
}
|
|
622
667
|
|
|
623
668
|
- (BOOL)isContentAvailablePush:(NSDictionary *)userInfo {
|
|
@@ -737,7 +782,8 @@ API_AVAILABLE(ios(10.0)) {
|
|
|
737
782
|
|
|
738
783
|
- (NSString*)dateToString:(NSDate*)date {
|
|
739
784
|
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
|
740
|
-
[formatter
|
|
785
|
+
[formatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
|
|
786
|
+
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZ"];
|
|
741
787
|
return [formatter stringFromDate:date];
|
|
742
788
|
}
|
|
743
789
|
|
|
@@ -979,6 +1025,25 @@ RCT_EXPORT_METHOD(enableHuaweiPushNotifications) {
|
|
|
979
1025
|
// available in Android only
|
|
980
1026
|
}
|
|
981
1027
|
|
|
1028
|
+
#pragma mark - Android-only settings
|
|
1029
|
+
|
|
1030
|
+
// Declared so the TurboModule spec is fully implemented on iOS: a call from JS is a no-op here
|
|
1031
|
+
// instead of a TypeError. The settings exist only in the Android SDK.
|
|
1032
|
+
|
|
1033
|
+
RCT_EXPORT_METHOD(setMultiNotificationMode:(BOOL)on) {}
|
|
1034
|
+
|
|
1035
|
+
RCT_EXPORT_METHOD(setLightScreenOnNotification:(BOOL)on) {}
|
|
1036
|
+
|
|
1037
|
+
RCT_EXPORT_METHOD(setEnableLED:(BOOL)on) {}
|
|
1038
|
+
|
|
1039
|
+
RCT_EXPORT_METHOD(setColorLED:(double)color) {}
|
|
1040
|
+
|
|
1041
|
+
RCT_EXPORT_METHOD(setSoundType:(double)type) {}
|
|
1042
|
+
|
|
1043
|
+
RCT_EXPORT_METHOD(setVibrateType:(double)type) {}
|
|
1044
|
+
|
|
1045
|
+
RCT_EXPORT_METHOD(setNotificationIconBackgroundColor:(NSString *)color) {}
|
|
1046
|
+
|
|
982
1047
|
RCT_EXPORT_METHOD(registerSMSNumber:(NSString *)phoneNumber) {
|
|
983
1048
|
[[Pushwoosh sharedInstance] registerSmsNumber:phoneNumber];
|
|
984
1049
|
}
|
|
@@ -987,8 +1052,8 @@ RCT_EXPORT_METHOD(registerWhatsappNumber:(NSString *)phoneNumber) {
|
|
|
987
1052
|
[[Pushwoosh sharedInstance] registerWhatsappNumber:phoneNumber];
|
|
988
1053
|
}
|
|
989
1054
|
|
|
990
|
-
RCT_EXPORT_METHOD(setRichMediaType:(
|
|
991
|
-
PWRichMediaPresentationStyle style =
|
|
1055
|
+
RCT_EXPORT_METHOD(setRichMediaType:(double)type) {
|
|
1056
|
+
PWRichMediaPresentationStyle style = (NSInteger)type == 0
|
|
992
1057
|
? PWRichMediaPresentationStyleModal
|
|
993
1058
|
: PWRichMediaPresentationStyleLegacy;
|
|
994
1059
|
[Pushwoosh.media setRichMediaPresentationStyle:style];
|
|
@@ -1052,6 +1117,7 @@ RCT_EXPORT_METHOD(getRichMediaType:(RCTResponseSenderBlock)callback) {
|
|
|
1052
1117
|
- (void)onPushReceived:(PushNotificationManager *)manager withNotification:(NSDictionary *)pushNotification onStart:(BOOL)onStart {
|
|
1053
1118
|
if (onStart) {
|
|
1054
1119
|
gStartPushData = pushNotification;
|
|
1120
|
+
gStartPushReplayed = NO;
|
|
1055
1121
|
// Save deep link URL for New Architecture (Linking.getInitialURL support)
|
|
1056
1122
|
NSURL *deepLink = pwplugin_deepLinkURLFromPushLink(pushNotification[@"l"]);
|
|
1057
1123
|
if (deepLink) {
|
|
@@ -1062,6 +1128,7 @@ RCT_EXPORT_METHOD(getRichMediaType:(RCTResponseSenderBlock)callback) {
|
|
|
1062
1128
|
- (void)onPushAccepted:(PushNotificationManager *)manager withNotification:(NSDictionary *)pushNotification onStart:(BOOL)onStart {
|
|
1063
1129
|
if (onStart) {
|
|
1064
1130
|
gStartPushData = pushNotification;
|
|
1131
|
+
gStartPushReplayed = NO;
|
|
1065
1132
|
// Save deep link URL for New Architecture (Linking.getInitialURL support)
|
|
1066
1133
|
NSURL *deepLink = pwplugin_deepLinkURLFromPushLink(pushNotification[@"l"]);
|
|
1067
1134
|
if (deepLink) {
|
package/src/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type {TurboModule} from 'react-native';
|
|
2
|
+
import {TurboModuleRegistry} from 'react-native';
|
|
3
|
+
|
|
4
|
+
// Codegen input. Parameter names become the Objective-C selector labels (init:success:error:)
|
|
5
|
+
// and the Java parameter names, so they must match the native implementations.
|
|
6
|
+
export interface Spec extends TurboModule {
|
|
7
|
+
setReverseProxy(url: string, headers: Object | null): void;
|
|
8
|
+
init(config: Object, success: (result: Object) => void, error: (message: string) => void): void;
|
|
9
|
+
// `register` is a C++ keyword, so codegen cannot emit a method of that name
|
|
10
|
+
registerForPushNotifications(success: (token: string) => void, error: (message: string) => void): void;
|
|
11
|
+
unregister(success: (token: string) => void, error: (message: string) => void): void;
|
|
12
|
+
onPushOpen(callback: (push: Object) => void): void;
|
|
13
|
+
addListener(eventName: string): void;
|
|
14
|
+
removeListeners(count: number): void;
|
|
15
|
+
setEmails(emails: Array<string>, success: () => void, error: (message: string) => void): void;
|
|
16
|
+
setUserEmails(
|
|
17
|
+
userId: string,
|
|
18
|
+
emails: Array<string>,
|
|
19
|
+
success: () => void,
|
|
20
|
+
error: (message: string) => void,
|
|
21
|
+
): void;
|
|
22
|
+
registerSMSNumber(phoneNumber: string): void;
|
|
23
|
+
registerWhatsappNumber(phoneNumber: string): void;
|
|
24
|
+
setTags(tags: Object, success: () => void, error: (message: string) => void): void;
|
|
25
|
+
getTags(success: (tags: Object) => void, error: (message: string) => void): void;
|
|
26
|
+
setShowPushnotificationAlert(showPushnotificationAlert: boolean): void;
|
|
27
|
+
getShowPushnotificationAlert(callback: (showPushnotificationAlert: boolean) => void): void;
|
|
28
|
+
getPushToken(callback: (token: string) => void): void;
|
|
29
|
+
getHwid(callback: (hwid: string) => void): void;
|
|
30
|
+
getUserId(callback: (userId: string) => void): void;
|
|
31
|
+
setUserId(userId: string, success: () => void, error: (message: string) => void): void;
|
|
32
|
+
postEvent(event: string, attributes: Object): void;
|
|
33
|
+
setApplicationIconBadgeNumber(badgeNumber: number): void;
|
|
34
|
+
getApplicationIconBadgeNumber(callback: (badgeNumber: number) => void): void;
|
|
35
|
+
addToApplicationIconBadgeNumber(badgeNumber: number): void;
|
|
36
|
+
setMultiNotificationMode(on: boolean): void;
|
|
37
|
+
setLightScreenOnNotification(on: boolean): void;
|
|
38
|
+
setEnableLED(on: boolean): void;
|
|
39
|
+
setColorLED(color: number): void;
|
|
40
|
+
setSoundType(type: number): void;
|
|
41
|
+
setVibrateType(type: number): void;
|
|
42
|
+
presentInboxUI(style: Object | null): void;
|
|
43
|
+
messagesWithNoActionPerformedCount(callback: (count: number) => void): void;
|
|
44
|
+
unreadMessagesCount(callback: (count: number) => void): void;
|
|
45
|
+
messagesCount(callback: (count: number) => void): void;
|
|
46
|
+
loadMessages(success: (messages: Array<Object>) => void, fail: (message: string) => void): void;
|
|
47
|
+
readMessage(code: string): void;
|
|
48
|
+
readMessages(codes: Array<string>): void;
|
|
49
|
+
deleteMessage(code: string): void;
|
|
50
|
+
deleteMessages(codes: Array<string>): void;
|
|
51
|
+
performAction(code: string): void;
|
|
52
|
+
isCommunicationEnabled(callback: (enabled: boolean) => void): void;
|
|
53
|
+
setCommunicationEnabled(
|
|
54
|
+
enabled: boolean,
|
|
55
|
+
success: () => void,
|
|
56
|
+
error: (message: string) => void,
|
|
57
|
+
): void;
|
|
58
|
+
setNotificationIconBackgroundColor(color: string): void;
|
|
59
|
+
setLanguage(language: string): void;
|
|
60
|
+
enableHuaweiPushNotifications(): void;
|
|
61
|
+
setRichMediaType(type: number): void;
|
|
62
|
+
getRichMediaType(callback: (type: number) => void): void;
|
|
63
|
+
createLocalNotification(params: Object): void;
|
|
64
|
+
clearLocalNotification(): void;
|
|
65
|
+
clearNotificationCenter(): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// `get`, not `getEnforcing`: importing the package on a build without the native module (Jest,
|
|
69
|
+
// an unlinked app) must not throw; a method call does, as it always did.
|
|
70
|
+
//
|
|
71
|
+
// The module is named after its Objective-C class. React Native resolves a TurboModule by
|
|
72
|
+
// NSClassFromString(name) first, and "Pushwoosh" is the native SDK's own class.
|
|
73
|
+
export default TurboModuleRegistry.get<Spec>('PushwooshPlugin');
|