react-native-qpush 0.0.0-stage → 1.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 +439 -2
- package/android/build.gradle +77 -0
- package/android/gradle.properties +5 -0
- package/android/proguard-rules.pro +5 -0
- package/android/src/main/AndroidManifest.xml +29 -0
- package/android/src/main/java/com/qpush/reactnative/QPushDeviceInfo.kt +96 -0
- package/android/src/main/java/com/qpush/reactnative/QPushEventBus.kt +153 -0
- package/android/src/main/java/com/qpush/reactnative/QPushFcm.kt +108 -0
- package/android/src/main/java/com/qpush/reactnative/QPushHttp.kt +92 -0
- package/android/src/main/java/com/qpush/reactnative/QPushImages.kt +103 -0
- package/android/src/main/java/com/qpush/reactnative/QPushLog.kt +39 -0
- package/android/src/main/java/com/qpush/reactnative/QPushMessagingService.kt +77 -0
- package/android/src/main/java/com/qpush/reactnative/QPushNotificationOpenActivity.kt +121 -0
- package/android/src/main/java/com/qpush/reactnative/QPushNotificationRenderer.kt +256 -0
- package/android/src/main/java/com/qpush/reactnative/QPushPayload.kt +114 -0
- package/android/src/main/java/com/qpush/reactnative/QPushPushModule.kt +368 -0
- package/android/src/main/java/com/qpush/reactnative/QPushPushPackage.kt +15 -0
- package/android/src/main/java/com/qpush/reactnative/QPushStore.kt +108 -0
- package/android/src/main/res/values/qpush_strings.xml +6 -0
- package/app.plugin.js +1 -0
- package/ios/Extension/QPushNotificationServiceExtension.swift +261 -0
- package/ios/Extension/QPushPushExtension.podspec +34 -0
- package/ios/QPushPush.m +62 -0
- package/ios/QPushPush.swift +186 -0
- package/ios/QPushPushBridge.h +5 -0
- package/ios/QPushPushCore.swift +457 -0
- package/ios/QPushPushHooks.m +237 -0
- package/ios/QPushPushSupport.swift +249 -0
- package/ios_nse_template/Info.plist +38 -0
- package/ios_nse_template/NotificationService.entitlements +10 -0
- package/ios_nse_template/NotificationService.swift +44 -0
- package/lib/NativeQPushPush.d.ts +44 -0
- package/lib/NativeQPushPush.js +4 -0
- package/lib/QPush.d.ts +117 -0
- package/lib/QPush.js +206 -0
- package/lib/core/QPushCore.d.ts +148 -0
- package/lib/core/QPushCore.js +1387 -0
- package/lib/core/constants.d.ts +37 -0
- package/lib/core/constants.js +40 -0
- package/lib/core/events.d.ts +11 -0
- package/lib/core/events.js +40 -0
- package/lib/core/http.d.ts +41 -0
- package/lib/core/http.js +123 -0
- package/lib/core/logger.d.ts +18 -0
- package/lib/core/logger.js +43 -0
- package/lib/core/operationQueue.d.ts +117 -0
- package/lib/core/operationQueue.js +264 -0
- package/lib/core/operations.d.ts +80 -0
- package/lib/core/operations.js +88 -0
- package/lib/core/payload.d.ts +20 -0
- package/lib/core/payload.js +187 -0
- package/lib/core/platform.d.ts +72 -0
- package/lib/core/platform.js +7 -0
- package/lib/core/state.d.ts +51 -0
- package/lib/core/state.js +74 -0
- package/lib/core/storage.d.ts +34 -0
- package/lib/core/storage.js +68 -0
- package/lib/core/subscription.d.ts +67 -0
- package/lib/core/subscription.js +98 -0
- package/lib/core/tags.d.ts +39 -0
- package/lib/core/tags.js +123 -0
- package/lib/core/url.d.ts +17 -0
- package/lib/core/url.js +64 -0
- package/lib/core/uuid.d.ts +2 -0
- package/lib/core/uuid.js +18 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +15 -0
- package/lib/notification.d.ts +67 -0
- package/lib/notification.js +73 -0
- package/lib/reactNativePlatform.d.ts +3 -0
- package/lib/reactNativePlatform.js +164 -0
- package/lib/types.d.ts +157 -0
- package/lib/types.js +31 -0
- package/package.json +70 -4
- package/plugin/withQPush.js +330 -0
- package/react-native-qpush.podspec +30 -0
- package/react-native.config.js +18 -0
- package/src/NativeQPushPush.ts +57 -0
- package/src/QPush.ts +292 -0
- package/src/core/QPushCore.ts +1515 -0
- package/src/core/constants.ts +42 -0
- package/src/core/events.ts +34 -0
- package/src/core/http.ts +146 -0
- package/src/core/logger.ts +42 -0
- package/src/core/operationQueue.ts +324 -0
- package/src/core/operations.ts +166 -0
- package/src/core/payload.ts +175 -0
- package/src/core/platform.ts +84 -0
- package/src/core/state.ts +127 -0
- package/src/core/storage.ts +76 -0
- package/src/core/subscription.ts +133 -0
- package/src/core/tags.ts +116 -0
- package/src/core/url.ts +58 -0
- package/src/core/uuid.ts +13 -0
- package/src/index.ts +23 -0
- package/src/notification.ts +113 -0
- package/src/reactNativePlatform.ts +172 -0
- package/src/types.ts +174 -0
package/README.md
CHANGED
|
@@ -1,3 +1,440 @@
|
|
|
1
|
-
#
|
|
1
|
+
# react-native-qpush
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React Native SDK for **QPush**, the self-hosted, OneSignal-compatible push notification server.
|
|
4
|
+
|
|
5
|
+
The API mirrors the OneSignal React Native SDK v5 (`OneSignal.*` becomes `QPush.*`), so if you have used OneSignal you already know how to use it:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { QPush, LogLevel } from 'react-native-qpush';
|
|
9
|
+
|
|
10
|
+
QPush.Debug.setLogLevel(LogLevel.Verbose);
|
|
11
|
+
QPush.initialize('YOUR_APP_ID'); // qpush cloud: https://qpush.dev/api/v1
|
|
12
|
+
QPush.Notifications.requestPermission(true);
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- Android: FCM, with **no `google-services.json` required**. FCM credentials come from your QPush dashboard.
|
|
16
|
+
- iOS: APNs, with no AppDelegate changes, plus a Notification Service Extension for images, action buttons and confirmed delivery.
|
|
17
|
+
- Expo: config plugin for development builds and EAS.
|
|
18
|
+
- Works on the old architecture and on the New Architecture (bridgeless) through React Native's interop layer.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Contents
|
|
23
|
+
|
|
24
|
+
1. [Requirements](#requirements)
|
|
25
|
+
2. [Installation](#installation)
|
|
26
|
+
3. [Android setup](#android-setup)
|
|
27
|
+
4. [iOS setup (bare React Native)](#ios-setup-bare-react-native)
|
|
28
|
+
5. [Expo setup](#expo-setup)
|
|
29
|
+
6. [Initialization](#initialization)
|
|
30
|
+
7. [API reference](#api-reference)
|
|
31
|
+
8. [How it works](#how-it-works)
|
|
32
|
+
9. [Migrating from OneSignal](#migrating-from-onesignal)
|
|
33
|
+
10. [Troubleshooting](#troubleshooting)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Requirements
|
|
38
|
+
|
|
39
|
+
| | Minimum |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| React Native | 0.71 |
|
|
42
|
+
| Android | minSdk 21, compileSdk 34 recommended |
|
|
43
|
+
| iOS | 13.0, Xcode 15+ |
|
|
44
|
+
| Expo | SDK 50+ with a [development build](https://docs.expo.dev/develop/development-builds/introduction/). Expo Go is not supported. |
|
|
45
|
+
| QPush server | An app with Android (FCM) and/or iOS (APNs) credentials configured in the dashboard |
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
npm install react-native-qpush
|
|
51
|
+
# or
|
|
52
|
+
yarn add react-native-qpush
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Bare React Native: `cd ios && pod install`. Expo: see [Expo setup](#expo-setup).
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Android setup
|
|
60
|
+
|
|
61
|
+
There is nothing to configure. Autolinking registers the module, and the library's manifest declares:
|
|
62
|
+
|
|
63
|
+
- `POST_NOTIFICATIONS` (Android 13+ runtime permission), `INTERNET`, `ACCESS_NETWORK_STATE`, `VIBRATE`
|
|
64
|
+
- `QPushMessagingService`, a `FirebaseMessagingService` that renders QPush notifications even when the app is killed
|
|
65
|
+
- `QPushNotificationOpenActivity`, the invisible activity that handles notification taps (Android 12+ compliant)
|
|
66
|
+
|
|
67
|
+
**FCM without `google-services.json`.** On every cold start the SDK downloads `android_params.js` from your server and initializes a secondary `FirebaseApp` named `QPUSH_SDK_FCM_APP` using the FCM credentials in your QPush dashboard. OneSignal works the same way. If the dashboard has no FCM credentials, the SDK uses the app's default `FirebaseApp` when one exists (that is, when the app has `google-services.json`).
|
|
68
|
+
|
|
69
|
+
### Optional: notification icon
|
|
70
|
+
|
|
71
|
+
Android shows the small icon as a white silhouette. Add a monochrome drawable named **`ic_stat_qpush_default`** (for example in `android/app/src/main/res/drawable-*/ic_stat_qpush_default.png`). The SDK picks icons in this order:
|
|
72
|
+
|
|
73
|
+
1. the `small_icon` sent with the notification (a drawable name)
|
|
74
|
+
2. `ic_stat_qpush_default`
|
|
75
|
+
3. the app icon
|
|
76
|
+
|
|
77
|
+
### Optional: default accent color
|
|
78
|
+
|
|
79
|
+
```xml
|
|
80
|
+
<!-- android/app/src/main/AndroidManifest.xml, inside <application> -->
|
|
81
|
+
<meta-data android:name="com.qpush.NotificationAccentColor.DEFAULT" android:value="FF0070F3" />
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Optional: disable opening URLs on tap
|
|
85
|
+
|
|
86
|
+
```xml
|
|
87
|
+
<meta-data android:name="com.qpush.suppressLaunchURLs" android:value="true" />
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
You can also change it at runtime with `QPush.Notifications.setAutoOpenUrls(false)`.
|
|
91
|
+
|
|
92
|
+
### Optional: Firebase BoM version
|
|
93
|
+
|
|
94
|
+
The SDK uses Firebase BoM `33.16.0` by default, the newest line that still supports minSdk 21. If your app's minSdk is 23 or higher you can use a newer BoM:
|
|
95
|
+
|
|
96
|
+
```groovy
|
|
97
|
+
// android/build.gradle
|
|
98
|
+
ext {
|
|
99
|
+
qpushFirebaseBomVersion = "34.19.0"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If your app already uses Firebase, Gradle resolves the highest `firebase-messaging` version on its own.
|
|
104
|
+
|
|
105
|
+
### Notification channels
|
|
106
|
+
|
|
107
|
+
Notifications without `channel_id` go to the **`qpush_default`** channel, named "Notifications" with high importance. To localize the name, override the string resource `qpush_default_channel_name`. To use your own channels (for example with custom sounds), create them in your app and send their `channel_id`. If a channel does not exist on the device, the default channel is used.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## iOS setup (bare React Native)
|
|
112
|
+
|
|
113
|
+
### 1. Install the pod
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
cd ios && pod install
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The SDK swizzles the app delegate to capture the APNs token, the same technique OneSignal uses. It also installs itself as the `UNUserNotificationCenter` delegate and forwards everything that is not a QPush notification to any delegate that your app or other libraries set. You do not need to edit `AppDelegate`.
|
|
120
|
+
|
|
121
|
+
### 2. Add capabilities to the app target
|
|
122
|
+
|
|
123
|
+
In Xcode, select your app target, open **Signing & Capabilities**, and add:
|
|
124
|
+
|
|
125
|
+
1. **Push Notifications**
|
|
126
|
+
2. **Background Modes**, then check **Remote notifications**
|
|
127
|
+
3. **App Groups**, then add **`group.<your bundle id>.qpush`** (for example `group.com.acme.app.qpush`)
|
|
128
|
+
|
|
129
|
+
The App Group lets the Notification Service Extension read the app id, server URL and subscription id that the SDK saves when it initializes. To use a different group name, set it as `QPushAppGroup` (String) in the Info.plist of both the app and the extension.
|
|
130
|
+
|
|
131
|
+
### 3. Add the Notification Service Extension
|
|
132
|
+
|
|
133
|
+
The extension downloads notification images, registers action buttons and confirms delivery (`report_received`) even when the app is not running. Without it, notifications are still shown, but with no image and no buttons, and delivery is only reported while the app is open.
|
|
134
|
+
|
|
135
|
+
1. In Xcode choose **File > New > Target…** and pick **Notification Service Extension**.
|
|
136
|
+
2. Set the product name to **`QPushNotificationServiceExtension`** and the language to **Swift**. Click **Finish**, then **Don't Activate** when Xcode asks about the scheme.
|
|
137
|
+
3. Select the new target:
|
|
138
|
+
- Under **General > Minimum Deployments**, use the same iOS version as your app (13.0 or later).
|
|
139
|
+
- Under **Signing & Capabilities**, add **App Groups** with the same `group.<your bundle id>.qpush`.
|
|
140
|
+
- Make sure **Version** and **Build** match your app. App Store Connect rejects mismatches.
|
|
141
|
+
4. Replace the generated `QPushNotificationServiceExtension/NotificationService.swift` with the template from
|
|
142
|
+
[`node_modules/react-native-qpush/ios_nse_template/NotificationService.swift`](ios_nse_template/NotificationService.swift).
|
|
143
|
+
5. Add the helper pod to the extension target at the end of your `ios/Podfile`:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
target 'QPushNotificationServiceExtension' do
|
|
147
|
+
pod 'QPushPushExtension', :path => '../node_modules/react-native-qpush/ios/Extension'
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
6. Run `cd ios && pod install`.
|
|
152
|
+
|
|
153
|
+
If you would rather not use the pod, add `node_modules/react-native-qpush/ios/Extension/QPushNotificationServiceExtension.swift` to the extension target instead. The template compiles either way.
|
|
154
|
+
|
|
155
|
+
### iOS details
|
|
156
|
+
|
|
157
|
+
- The token is the hex-encoded APNs device token.
|
|
158
|
+
- `test_type` is detected automatically from `embedded.mobileprovision` (`aps-environment = development` gives `1`, ad-hoc gives `2`, App Store and TestFlight give `null`). Simulators report `1`. With the APNs environment set to `auto` in the dashboard, the server picks sandbox or production on its own.
|
|
159
|
+
- The badge is cleared when the app opens, and `badge_count: 0` is sent to the server.
|
|
160
|
+
- To disable opening URLs on tap at build time, set `QPush_suppress_launch_urls` (Boolean `YES`) in Info.plist.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Expo setup
|
|
165
|
+
|
|
166
|
+
Expo Go does not include native code, so you need a [development build](https://docs.expo.dev/develop/development-builds/introduction/).
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
npx expo install react-native-qpush expo-dev-client
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Add the plugin to `app.json` / `app.config.js`:
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"expo": {
|
|
177
|
+
"ios": { "bundleIdentifier": "com.acme.app" },
|
|
178
|
+
"android": { "package": "com.acme.app" },
|
|
179
|
+
"plugins": [
|
|
180
|
+
["react-native-qpush", { "mode": "development" }]
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Then:
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
npx expo prebuild
|
|
190
|
+
npx expo run:ios # or: eas build --profile development
|
|
191
|
+
npx expo run:android
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The plugin, modeled on `onesignal-expo-plugin`, does all of the [iOS setup](#ios-setup-bare-react-native) for you:
|
|
195
|
+
|
|
196
|
+
- adds the `aps-environment` entitlement and the App Group `group.<bundle id>.qpush`
|
|
197
|
+
- enables Background Modes / `remote-notification`
|
|
198
|
+
- creates the `QPushNotificationServiceExtension` target from the template, with the App Group, the matching version and build number, and the `QPushPushExtension` pod in the Podfile
|
|
199
|
+
- registers the extension in `extra.eas.build.experimental.ios.appExtensions` so EAS Build manages its provisioning profile
|
|
200
|
+
|
|
201
|
+
### Plugin options
|
|
202
|
+
|
|
203
|
+
| Option | Type | Default | Description |
|
|
204
|
+
| --- | --- | --- | --- |
|
|
205
|
+
| `mode` | `"development" \| "production"` | `"development"` | Value of the `aps-environment` entitlement. Use `"production"` for TestFlight and App Store builds. |
|
|
206
|
+
| `devTeam` | `string` | – | Apple Team ID applied to the extension target. |
|
|
207
|
+
| `iPhoneDeploymentTarget` | `string` | `"15.1"` | Deployment target of the extension. Match your app's. |
|
|
208
|
+
| `disableNSE` | `boolean` | `false` | Skip the Notification Service Extension. |
|
|
209
|
+
| `nseFilePath` | `string` | – | Path to your own `NotificationService.swift`, used instead of the template. |
|
|
210
|
+
| `appGroup` | `string` | `group.<bundle id>.qpush` | Custom App Group name. |
|
|
211
|
+
| `suppressLaunchUrls` | `boolean` | `false` | Do not open the notification URL on tap (iOS Info.plist switch). |
|
|
212
|
+
| `smallIcon` | `string` | – | Path to a PNG copied as the Android `ic_stat_qpush_default` drawable. |
|
|
213
|
+
| `accentColor` | `string` | – | Android default accent color (`#RRGGBB` or `AARRGGBB`). |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Initialization
|
|
218
|
+
|
|
219
|
+
Call `initialize` once, as early as possible. Calling it in your entry file or at module scope of `App.tsx` makes sure clicks that cold-start the app are delivered. You can call any other method right after it, because calls are queued until the SDK has loaded its state.
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { QPush, LogLevel } from 'react-native-qpush';
|
|
223
|
+
|
|
224
|
+
QPush.Debug.setLogLevel(LogLevel.Warn);
|
|
225
|
+
QPush.initialize('YOUR_APP_ID', {
|
|
226
|
+
serverUrl: 'https://qpush.dev/api/v1', // qpush cloud (the default); change only if you self-host
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Ask for permission. The prompt shows on iOS and Android 13+; earlier
|
|
230
|
+
// Android versions grant it automatically.
|
|
231
|
+
QPush.Notifications.requestPermission(true);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
What `initialize` does:
|
|
235
|
+
|
|
236
|
+
1. Saves the config (also for native code and the iOS extension) and fetches `android_params.js` / `ios_params.js`.
|
|
237
|
+
2. Gets the push token: FCM through the dashboard credentials, or APNs.
|
|
238
|
+
3. Creates or refreshes the user and this device's subscription.
|
|
239
|
+
4. Starts session tracking: a new session on cold start or after 30 seconds or more in the background, plus foreground time.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## API reference
|
|
244
|
+
|
|
245
|
+
### `QPush`
|
|
246
|
+
|
|
247
|
+
| Method | Description |
|
|
248
|
+
| --- | --- |
|
|
249
|
+
| `initialize(appId: string, options?: { serverUrl?: string; logLevel?: LogLevel }): void` | Initializes the SDK. `serverUrl` defaults to the qpush cloud (`https://qpush.dev/api/v1`). |
|
|
250
|
+
| `login(externalId: string): void` | Identifies the user with your user id (`external_id`). If the device's user is anonymous, it becomes the identified user and keeps its tags. If another user already has that `external_id`, the device moves to that user and its tags are loaded. |
|
|
251
|
+
| `logout(): void` | Creates a new anonymous user that owns this device's subscription. Does nothing when no user is logged in. |
|
|
252
|
+
| `setConsentRequired(required: boolean): void` | When `true`, nothing is sent to the server until `setConsentGiven(true)`. Call it **before** `initialize`. |
|
|
253
|
+
| `setConsentGiven(given: boolean): void` | Grants or revokes consent. Both settings persist across launches. |
|
|
254
|
+
|
|
255
|
+
### `QPush.Debug`
|
|
256
|
+
|
|
257
|
+
| Method | Description |
|
|
258
|
+
| --- | --- |
|
|
259
|
+
| `setLogLevel(level: LogLevel): void` | `None`, `Fatal`, `Error`, `Warn` (default), `Info`, `Debug`, `Verbose`. Applies to JS and native logs. |
|
|
260
|
+
|
|
261
|
+
### `QPush.Notifications`
|
|
262
|
+
|
|
263
|
+
| Method | Description |
|
|
264
|
+
| --- | --- |
|
|
265
|
+
| `requestPermission(fallbackToSettings: boolean): Promise<boolean>` | Shows the OS prompt. When the prompt can no longer be shown (permission permanently denied) and `fallbackToSettings` is `true`, it opens the app's notification settings and resolves when the user comes back. |
|
|
266
|
+
| `getPermissionAsync(): Promise<boolean>` | Whether the app can currently show notifications. |
|
|
267
|
+
| `permissionNative(): Promise<NotificationPermission>` | `NotDetermined`, `Denied`, `Authorized`, `Provisional` or `Ephemeral`. |
|
|
268
|
+
| `canRequestPermission(): Promise<boolean>` | Whether `requestPermission` would show the OS prompt. |
|
|
269
|
+
| `registerForProvisionalAuthorization(): Promise<boolean>` | iOS 12+ quiet notifications without a prompt. On Android it reports the current permission. |
|
|
270
|
+
| `addEventListener(event, listener)` / `removeEventListener(event, listener)` | See [events](#notification-events). |
|
|
271
|
+
| `clearAll(): void` | Removes all QPush notifications from the notification center (and clears the badge on iOS). |
|
|
272
|
+
| `removeNotification(notificationId: string): void` | Removes one notification by its QPush id. |
|
|
273
|
+
| `removeGroupedNotifications(groupKey: string): void` | Removes an Android group or an iOS `thread-id`. |
|
|
274
|
+
| `setAutoOpenUrls(enabled: boolean): void` | Turns opening of `url` on tap on or off. Persisted natively, so it also applies to cold starts. Default is enabled. |
|
|
275
|
+
|
|
276
|
+
#### Notification events
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { QPush, NotificationClickEvent, NotificationWillDisplayEvent } from 'react-native-qpush';
|
|
280
|
+
|
|
281
|
+
// Tap on the notification or on an action button.
|
|
282
|
+
// Clicks that happen before any listener is added (cold start) are queued and
|
|
283
|
+
// delivered as soon as you add the first listener.
|
|
284
|
+
QPush.Notifications.addEventListener('click', (event: NotificationClickEvent) => {
|
|
285
|
+
const { notification, result } = event;
|
|
286
|
+
console.log(notification.notificationId, notification.additionalData);
|
|
287
|
+
console.log(result.actionId); // id of the tapped button, if any
|
|
288
|
+
console.log(result.url); // button url wins over the notification url
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Notification received while the app is in the foreground.
|
|
292
|
+
QPush.Notifications.addEventListener('foregroundWillDisplay', (event: NotificationWillDisplayEvent) => {
|
|
293
|
+
event.preventDefault(); // must be called synchronously
|
|
294
|
+
const notification = event.getNotification();
|
|
295
|
+
if (shouldShow(notification)) notification.display(); // show it later, within 25 s
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Permission granted or revoked (also detected when returning from system settings).
|
|
299
|
+
QPush.Notifications.addEventListener('permissionChange', (granted: boolean) => {});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
If no listener calls `preventDefault()`, the notification is shown. If the JS runtime does not answer within about one second, it is also shown.
|
|
303
|
+
|
|
304
|
+
`QPushNotification` fields: `notificationId`, `title`, `subtitle`, `body`, `launchURL`, `additionalData`, `bigPicture`, `largeIcon`, `smallIcon`, `accentColor`, `sound`, `channelId`, `priority`, `groupKey`, `collapseId`, `visibility`, `badge`, `category`, `interruptionLevel`, `relevanceScore`, `contentAvailable`, `actionButtons` (`{ id, text, icon?, url? }[]`), `rawPayload`, plus the method `display()`.
|
|
305
|
+
|
|
306
|
+
### `QPush.User`
|
|
307
|
+
|
|
308
|
+
| Method | Description |
|
|
309
|
+
| --- | --- |
|
|
310
|
+
| `addTag(key: string, value: string \| number \| boolean): void` | Adds or updates a tag. Values are stored as strings. |
|
|
311
|
+
| `addTags(tags: Record<string, string \| number \| boolean>): void` | Same for several tags. Only values that changed are sent. |
|
|
312
|
+
| `removeTag(key: string): void` / `removeTags(keys: string[]): void` | Removes tags. |
|
|
313
|
+
| `getTags(): Promise<Record<string, string>>` | Local copy of the tags, refreshed from the server on every app start and on login. |
|
|
314
|
+
| `addAlias(label: string, id: string): void` / `addAliases(record)` | Adds custom aliases (for example `crm_id`). Use `QPush.login` for `external_id`. |
|
|
315
|
+
| `removeAlias(label: string): void` / `removeAliases(labels)` | Removes aliases. |
|
|
316
|
+
| `setLanguage(language: string): void` | ISO 639-1 code. Defaults to the device language. |
|
|
317
|
+
| `getQPushId(): Promise<string \| null>` | The QPush user id (`qpush_id`). `getOnesignalId()` is an alias. |
|
|
318
|
+
| `getExternalId(): Promise<string \| null>` | The logged-in `external_id`. |
|
|
319
|
+
| `addEventListener('change', listener)` / `removeEventListener` | `{ current: { qpushId, externalId } }` |
|
|
320
|
+
|
|
321
|
+
### `QPush.User.pushSubscription`
|
|
322
|
+
|
|
323
|
+
| Method | Description |
|
|
324
|
+
| --- | --- |
|
|
325
|
+
| `getIdAsync(): Promise<string \| null>` | This device's subscription id. |
|
|
326
|
+
| `getTokenAsync(): Promise<string \| null>` | FCM token (Android) or hex APNs token (iOS). |
|
|
327
|
+
| `getOptedInAsync(): Promise<boolean>` | `true` when there is a token, the permission is granted, and you have not called `optOut()`. |
|
|
328
|
+
| `optIn(): void` | Re-enables push for this device. It prompts for permission if the user was never asked. |
|
|
329
|
+
| `optOut(): void` | Stops push to this device without touching the OS permission. |
|
|
330
|
+
| `addEventListener('change', listener)` / `removeEventListener` | `{ previous: { id, token, optedIn }, current: { id, token, optedIn } }` |
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## How it works
|
|
335
|
+
|
|
336
|
+
- **A single TypeScript state machine** handles the user, subscription, sessions and every HTTP call. Native code only does what has to be native: the push token, permission, rendering, and handling taps and receipts.
|
|
337
|
+
- **Persisted operation queue.** Every server change (create user, tags, aliases, login, subscription updates, session counters) is written to a queue saved in SharedPreferences or UserDefaults through the native module (AsyncStorage is not used). Operations run in order and are merged when possible (for example several `addTag` calls become one `PATCH`). `429` and `5xx` responses are retried with exponential backoff, up to 5 attempts. Network failures are retried until they succeed, so changes made offline reach the server later, even after the app restarts.
|
|
338
|
+
- **Users are referenced locally.** An operation queued before `login`/`logout` always applies to the user that existed when you called it.
|
|
339
|
+
- **Receipts and opens are reported natively** (`PUT /notifications/{id}/report_received` and `PUT /notifications/{id}`). They are sent from the Android messaging service and notification activity, and from the iOS extension and notification delegate, so they work even when the JS runtime is not running.
|
|
340
|
+
|
|
341
|
+
All endpoints and payloads follow the [QPush SDK protocol](https://github.com/qpush/qpush/blob/main/docs/sdk-protocol.md).
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Migrating from OneSignal
|
|
346
|
+
|
|
347
|
+
| OneSignal | QPush |
|
|
348
|
+
| --- | --- |
|
|
349
|
+
| `OneSignal.initialize(appId)` | `QPush.initialize(appId, { serverUrl })` |
|
|
350
|
+
| `OneSignal.Notifications.*` | `QPush.Notifications.*` (same events: `click`, `foregroundWillDisplay`, `permissionChange`) |
|
|
351
|
+
| `OneSignal.User.*` | `QPush.User.*` |
|
|
352
|
+
| `OneSignal.User.getOnesignalId()` | `QPush.User.getQPushId()` (or `getOnesignalId()`) |
|
|
353
|
+
| `OneSignal.login` / `logout` | `QPush.login` / `logout` |
|
|
354
|
+
| `onesignal-expo-plugin` | `react-native-qpush` plugin (same idea, `mode` option) |
|
|
355
|
+
| `OneSignalNotificationServiceExtension` | `QPushNotificationServiceExtension` |
|
|
356
|
+
| App Group `group.<bundle>.onesignal` | `group.<bundle>.qpush` |
|
|
357
|
+
|
|
358
|
+
Remove `react-native-onesignal` before installing this package. Both SDKs register a `FirebaseMessagingService` on Android and a notification delegate on iOS.
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Troubleshooting
|
|
363
|
+
|
|
364
|
+
Turn on verbose logs first:
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
QPush.Debug.setLogLevel(LogLevel.Verbose);
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Then watch `adb logcat -s QPush` on Android or the Xcode console (filter `[QPush]`) on iOS.
|
|
371
|
+
|
|
372
|
+
**"The native module 'QPushPush' is not available"**
|
|
373
|
+
Rebuild the app after installing (`pod install` on iOS). Expo Go is not supported, so use a development build.
|
|
374
|
+
|
|
375
|
+
**Android: no token / `notification_types = -3`**
|
|
376
|
+
- Add Android (FCM) credentials to the app in the QPush dashboard (project id, app id, API key, sender id), or add `google-services.json` to use the default FirebaseApp.
|
|
377
|
+
- The device or emulator needs Google Play services. Use an emulator image "with Google Play".
|
|
378
|
+
- If the dashboard credentials were added after the first launch, restart the app or bring it to the foreground. The SDK retries registration.
|
|
379
|
+
|
|
380
|
+
**Android: notifications do not appear**
|
|
381
|
+
- Android 13+: call `QPush.Notifications.requestPermission(true)`.
|
|
382
|
+
- Check that the channel you send in `channel_id` exists, or leave it empty to use `qpush_default`.
|
|
383
|
+
- Another library with its own `FirebaseMessagingService`, such as `@react-native-firebase/messaging`, can take QPush's messages, because only one service receives them. Forward them from your service:
|
|
384
|
+
|
|
385
|
+
```kotlin
|
|
386
|
+
override fun onMessageReceived(message: RemoteMessage) {
|
|
387
|
+
if (com.qpush.reactnative.QPushMessagingService.handleMessage(this, message)) return
|
|
388
|
+
// ...your handling
|
|
389
|
+
}
|
|
390
|
+
override fun onNewToken(token: String) {
|
|
391
|
+
com.qpush.reactnative.QPushMessagingService.handleNewToken(this, token)
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**iOS: no token / `notification_types = -10`**
|
|
396
|
+
- Enable the **Push Notifications** capability and make sure the provisioning profile includes it.
|
|
397
|
+
- Simulators only get tokens on iOS 16+ on Apple silicon Macs. Use a real device otherwise.
|
|
398
|
+
- `-10` means APNs registration failed. Check the Xcode logs for the reason.
|
|
399
|
+
|
|
400
|
+
**iOS: push works in development but not from TestFlight (or the reverse)**
|
|
401
|
+
Set the APNs environment of the app to `auto` in the dashboard. The SDK sends `test_type = 1` for development-signed builds, so the server uses the sandbox gateway for them.
|
|
402
|
+
|
|
403
|
+
**iOS: no image, no buttons, or delivery not confirmed**
|
|
404
|
+
These depend on the Notification Service Extension. Check that:
|
|
405
|
+
- the extension target exists and is embedded in the app (Build Phases > Embed Foundation Extensions / Copy Files)
|
|
406
|
+
- its deployment target is not higher than the device's iOS version
|
|
407
|
+
- both targets have the **same App Group**. Open the app once after installing so it writes its config to the group.
|
|
408
|
+
- the server sends `mutable-content: 1` (QPush always does)
|
|
409
|
+
|
|
410
|
+
**iOS: foreground notifications not shown, or taps not reported, with another notification library**
|
|
411
|
+
QPush keeps itself as the `UNUserNotificationCenter` delegate and forwards non-QPush notifications to the delegate set by the other library. If a library overrides delegate methods through swizzling, initialize QPush first and check the order in which the libraries are linked.
|
|
412
|
+
|
|
413
|
+
**Clicks that open the app are missing**
|
|
414
|
+
Add the `click` listener early, at module scope or in your root component. Clicks are queued natively until JS is ready, and in JS until the first listener is added.
|
|
415
|
+
|
|
416
|
+
**Nothing is sent**
|
|
417
|
+
- `setConsentRequired(true)` without `setConsentGiven(true)` blocks all network traffic.
|
|
418
|
+
- If `android_params.js` / `ios_params.js` returns `404`, the app id or `serverUrl` is wrong, and the SDK stops syncing until the next launch. Look for "was not found" in the logs.
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Developing against a plain-HTTP server
|
|
423
|
+
|
|
424
|
+
Production servers should use HTTPS. While developing against a QPush server on
|
|
425
|
+
your LAN (for example `http://192.168.1.10:8080`):
|
|
426
|
+
|
|
427
|
+
- **Android:** delivery receipts and click reports are sent by native code,
|
|
428
|
+
which Android blocks over cleartext HTTP. Allow it in debug builds only:
|
|
429
|
+
`<application android:usesCleartextTraffic="true"/>` in
|
|
430
|
+
`android/app/src/debug/AndroidManifest.xml` (React Native debug builds already allow it).
|
|
431
|
+
- **iOS:** IP addresses are exempt from App Transport Security. For `.local`
|
|
432
|
+
hostnames (`http://my-mac.local:8080`) add `NSAppTransportSecurity →
|
|
433
|
+
NSAllowsLocalNetworking = YES` to the app's and the Notification Service
|
|
434
|
+
Extension's `Info.plist`. The Expo plugin adds it automatically when `mode` is `"development"`, and the shipped NSE template includes it.
|
|
435
|
+
|
|
436
|
+
The SDK logs a warning at startup when `serverUrl` uses plain HTTP.
|
|
437
|
+
|
|
438
|
+
## License
|
|
439
|
+
|
|
440
|
+
MIT
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.qpushSafeExtGet = { name, fallback ->
|
|
3
|
+
rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["QPushPush_" + name] ?: fallback)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.2.2"
|
|
13
|
+
// noinspection DifferentKotlinGradleVersion
|
|
14
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${qpushSafeExtGet('kotlinVersion', '1.9.24')}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: "com.android.library"
|
|
19
|
+
apply plugin: "kotlin-android"
|
|
20
|
+
|
|
21
|
+
def safeExtGetInt(name, fallback) {
|
|
22
|
+
def value = rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["QPushPush_" + name]
|
|
23
|
+
return value != null ? value.toString().toInteger() : fallback
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def appMinSdk = safeExtGetInt("minSdkVersion", 21)
|
|
27
|
+
|
|
28
|
+
// Firebase BoM 33.x supports minSdk 21. Apps can pick a newer BoM (34.x needs
|
|
29
|
+
// minSdk 23+) with `ext.qpushFirebaseBomVersion = "34.x.y"` in the root
|
|
30
|
+
// build.gradle. If the app already uses Firebase, Gradle resolves the highest
|
|
31
|
+
// firebase-messaging version anyway.
|
|
32
|
+
def firebaseBomVersion = rootProject.ext.has("qpushFirebaseBomVersion") ?
|
|
33
|
+
rootProject.ext.get("qpushFirebaseBomVersion") :
|
|
34
|
+
(project.properties["QPushPush_firebaseBomVersion"] ?: "33.16.0")
|
|
35
|
+
|
|
36
|
+
android {
|
|
37
|
+
if (project.android.hasProperty("namespace")) {
|
|
38
|
+
namespace "com.qpush.reactnative"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
compileSdkVersion safeExtGetInt("compileSdkVersion", 34)
|
|
42
|
+
|
|
43
|
+
defaultConfig {
|
|
44
|
+
minSdkVersion appMinSdk
|
|
45
|
+
targetSdkVersion safeExtGetInt("targetSdkVersion", 34)
|
|
46
|
+
consumerProguardFiles "proguard-rules.pro"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
buildTypes {
|
|
50
|
+
release {
|
|
51
|
+
minifyEnabled false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
compileOptions {
|
|
56
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
57
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
repositories {
|
|
62
|
+
mavenCentral()
|
|
63
|
+
google()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
def kotlinVersion = qpushSafeExtGet("kotlinVersion", "1.9.24")
|
|
67
|
+
|
|
68
|
+
dependencies {
|
|
69
|
+
// Version provided by the React Native Gradle plugin of the app.
|
|
70
|
+
implementation "com.facebook.react:react-android"
|
|
71
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}"
|
|
72
|
+
|
|
73
|
+
implementation platform("com.google.firebase:firebase-bom:${firebaseBomVersion}")
|
|
74
|
+
implementation "com.google.firebase:firebase-messaging"
|
|
75
|
+
|
|
76
|
+
implementation "androidx.core:core:1.9.0"
|
|
77
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Keep the classes referenced from the manifest and by React Native reflection.
|
|
2
|
+
-keep class com.qpush.reactnative.QPushMessagingService { *; }
|
|
3
|
+
-keep class com.qpush.reactnative.QPushNotificationOpenActivity { *; }
|
|
4
|
+
-keep class com.qpush.reactnative.QPushPushModule { *; }
|
|
5
|
+
-keep class com.qpush.reactnative.QPushPushPackage { *; }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
|
|
3
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
4
|
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
5
|
+
<uses-permission android:name="android.permission.VIBRATE" />
|
|
6
|
+
<!-- Android 13+ runtime notification permission. -->
|
|
7
|
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
8
|
+
|
|
9
|
+
<application>
|
|
10
|
+
<!-- Receives QPush data messages (data["qpush"]) and renders them. -->
|
|
11
|
+
<service
|
|
12
|
+
android:name="com.qpush.reactnative.QPushMessagingService"
|
|
13
|
+
android:exported="false">
|
|
14
|
+
<intent-filter>
|
|
15
|
+
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
|
16
|
+
</intent-filter>
|
|
17
|
+
</service>
|
|
18
|
+
|
|
19
|
+
<!-- Transparent trampoline started directly by the notification PendingIntent
|
|
20
|
+
(Android 12+ compliant: no service/receiver trampolines). -->
|
|
21
|
+
<activity
|
|
22
|
+
android:name="com.qpush.reactnative.QPushNotificationOpenActivity"
|
|
23
|
+
android:excludeFromRecents="true"
|
|
24
|
+
android:exported="false"
|
|
25
|
+
android:noHistory="true"
|
|
26
|
+
android:taskAffinity=""
|
|
27
|
+
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
|
28
|
+
</application>
|
|
29
|
+
</manifest>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
package com.qpush.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.net.ConnectivityManager
|
|
5
|
+
import android.net.NetworkCapabilities
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.telephony.TelephonyManager
|
|
8
|
+
import com.facebook.react.bridge.Arguments
|
|
9
|
+
import com.facebook.react.bridge.WritableMap
|
|
10
|
+
import java.io.File
|
|
11
|
+
import java.util.Locale
|
|
12
|
+
import java.util.TimeZone
|
|
13
|
+
|
|
14
|
+
/** Device fields sent with the subscription (see SubscriptionPayload in the protocol). */
|
|
15
|
+
internal object QPushDeviceInfo {
|
|
16
|
+
fun collect(context: Context): WritableMap {
|
|
17
|
+
val map = Arguments.createMap()
|
|
18
|
+
val locale = Locale.getDefault()
|
|
19
|
+
map.putString("platform", "android")
|
|
20
|
+
map.putString("deviceModel", Build.MODEL)
|
|
21
|
+
map.putString("deviceOs", Build.VERSION.RELEASE)
|
|
22
|
+
appVersion(context)?.let { map.putString("appVersion", it) }
|
|
23
|
+
map.putString("timezone", TimeZone.getDefault().id)
|
|
24
|
+
val tag = locale.toLanguageTag()
|
|
25
|
+
map.putString("locale", tag)
|
|
26
|
+
// toLanguageTag() maps legacy codes (iw -> he, in -> id).
|
|
27
|
+
map.putString("language", tag.substringBefore('-').lowercase(Locale.ROOT))
|
|
28
|
+
if (locale.country.isNotEmpty()) map.putString("country", locale.country)
|
|
29
|
+
carrier(context)?.let { map.putString("carrier", it) }
|
|
30
|
+
map.putBoolean("rooted", isRooted())
|
|
31
|
+
netType(context)?.let { map.putInt("netType", it) }
|
|
32
|
+
return map
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Suppress("DEPRECATION")
|
|
36
|
+
private fun appVersion(context: Context): String? = try {
|
|
37
|
+
context.packageManager.getPackageInfo(context.packageName, 0).versionName
|
|
38
|
+
} catch (e: Exception) {
|
|
39
|
+
null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private fun carrier(context: Context): String? = try {
|
|
43
|
+
val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager
|
|
44
|
+
telephony?.networkOperatorName?.takeIf { it.isNotBlank() }
|
|
45
|
+
} catch (e: Exception) {
|
|
46
|
+
null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 0 wifi/ethernet, 1 cellular, null unknown. Needs ACCESS_NETWORK_STATE. */
|
|
50
|
+
@Suppress("DEPRECATION")
|
|
51
|
+
private fun netType(context: Context): Int? = try {
|
|
52
|
+
val connectivity = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
|
53
|
+
if (connectivity == null) {
|
|
54
|
+
null
|
|
55
|
+
} else if (Build.VERSION.SDK_INT >= 23) {
|
|
56
|
+
val capabilities = connectivity.getNetworkCapabilities(connectivity.activeNetwork)
|
|
57
|
+
when {
|
|
58
|
+
capabilities == null -> null
|
|
59
|
+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
|
|
60
|
+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> 0
|
|
61
|
+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> 1
|
|
62
|
+
else -> null
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
when (connectivity.activeNetworkInfo?.type) {
|
|
66
|
+
ConnectivityManager.TYPE_WIFI, ConnectivityManager.TYPE_ETHERNET -> 0
|
|
67
|
+
ConnectivityManager.TYPE_MOBILE -> 1
|
|
68
|
+
else -> null
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (e: Exception) {
|
|
72
|
+
null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private fun isRooted(): Boolean {
|
|
76
|
+
if (Build.TAGS?.contains("test-keys") == true) return true
|
|
77
|
+
val paths = arrayOf(
|
|
78
|
+
"/system/app/Superuser.apk",
|
|
79
|
+
"/sbin/su",
|
|
80
|
+
"/system/bin/su",
|
|
81
|
+
"/system/xbin/su",
|
|
82
|
+
"/data/local/xbin/su",
|
|
83
|
+
"/data/local/bin/su",
|
|
84
|
+
"/system/sd/xbin/su",
|
|
85
|
+
"/system/bin/failsafe/su",
|
|
86
|
+
"/data/local/su",
|
|
87
|
+
)
|
|
88
|
+
return paths.any { path ->
|
|
89
|
+
try {
|
|
90
|
+
File(path).exists()
|
|
91
|
+
} catch (e: Exception) {
|
|
92
|
+
false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|