cordova-plugin-reteno 2.1.0 → 2.2.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/docs/android.md +20 -3
- package/docs/api.md +30 -10
- package/docs/ios.md +4 -4
- package/package.json +1 -1
- package/plugin.xml +12 -2
- package/src/android/app/build.gradle +1 -1
- package/src/android/app/src/main/AndroidManifest.xml +6 -1
- package/src/android/app/src/main/java/com/reteno/plugin/RetenoNotificationGroupingRuleProvider.java +117 -0
- package/src/android/app/src/main/java/com/reteno/plugin/RetenoNotificationSummaryManager.java +262 -0
- package/src/android/app/src/main/java/com/reteno/plugin/RetenoPlugin.java +76 -2
- package/src/android/reteno.gradle +1 -1
- package/src/ios/RetenoPlugin.swift +1 -1
- package/types/index.ts +10 -2
- package/www/cordova-plugin-reteno.js +39 -1
package/docs/android.md
CHANGED
|
@@ -58,7 +58,7 @@ RetenoPlugin.init()
|
|
|
58
58
|
});
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
For Android SDK `2.9.
|
|
61
|
+
For Android SDK `2.9.6+`, you can also configure session duration and split session lifecycle flags during init:
|
|
62
62
|
|
|
63
63
|
```js
|
|
64
64
|
RetenoPlugin.init({
|
|
@@ -66,7 +66,7 @@ RetenoPlugin.init({
|
|
|
66
66
|
sessionDurationSeconds: 30 * 60,
|
|
67
67
|
lifecycleTrackingOptions: {
|
|
68
68
|
appLifecycleEnabled: true,
|
|
69
|
-
// ApplicationForgrounded/ApplicationBackgrounded events (disabled by default
|
|
69
|
+
// ApplicationForgrounded/ApplicationBackgrounded events (disabled by default)
|
|
70
70
|
foregroundLifecycleEnabled: false,
|
|
71
71
|
pushSubscriptionEnabled: true,
|
|
72
72
|
// SessionStart/SessionEnd can now be managed separately
|
|
@@ -84,7 +84,7 @@ If you need advanced Reteno configuration (custom `RetenoConfig`, custom device
|
|
|
84
84
|
|
|
85
85
|
## Push notification listeners
|
|
86
86
|
|
|
87
|
-
The plugin uses the listener-based API (`EventListener` / `Procedure`, Android SDK `2.9.
|
|
87
|
+
The plugin uses the listener-based API (`EventListener` / `Procedure`, Android SDK `2.9.6`) for all push events.
|
|
88
88
|
|
|
89
89
|
- `setOnRetenoPushDismissedListener(listener)` — called when a push notification is dismissed (swiped away).
|
|
90
90
|
- `setOnRetenoCustomPushReceivedListener(listener)` — called when a custom push notification is received.
|
|
@@ -108,6 +108,23 @@ RetenoPlugin.removeOnRetenoPushDismissedListener(onDismissed);
|
|
|
108
108
|
RetenoPlugin.removeOnRetenoCustomPushReceivedListener(onCustomReceived);
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
## Notification grouping
|
|
112
|
+
|
|
113
|
+
Android notifications can be assigned to a group using either a value from the push payload or a constant group ID. The configuration is stored natively and restored when the Android process starts, so it does not depend on the Cordova WebView being active.
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
// Group notifications by the `chatId` field in the push payload.
|
|
117
|
+
await RetenoPlugin.setNotificationGroupingRule({ payloadKey: 'chatId' });
|
|
118
|
+
|
|
119
|
+
// Alternatively, assign all Reteno notifications to one group.
|
|
120
|
+
await RetenoPlugin.setNotificationGroupingRule({ groupId: 'messages' });
|
|
121
|
+
|
|
122
|
+
// Disable grouping.
|
|
123
|
+
await RetenoPlugin.setNotificationGroupingRule(null);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This configures the group key on each notification. If you want Android to display a summary row for the group, the application must create its own notification with `setGroupSummary(true)` using the same group ID.
|
|
127
|
+
|
|
111
128
|
### Without Firebasex (Cordova)
|
|
112
129
|
|
|
113
130
|
If you **do not need** `cordova-plugin-firebasex` features, you can still use Reteno Push on Android.
|
package/docs/api.md
CHANGED
|
@@ -12,17 +12,17 @@ Notes:
|
|
|
12
12
|
|
|
13
13
|
| Method | Supported platform | Description |
|
|
14
14
|
| ---------------------------------------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
-
| [init](../www/cordova-plugin-reteno.js) | iOS, Android | Initializes Reteno SDK. Accepts optional `RetenoInitializeOptions` with `accessKey`, `pauseInAppMessages`, `pausePushInAppMessages`, `inAppMessagesPauseBehaviour` (iOS), `lifecycleTrackingOptions`, `sessionDurationSeconds` (Android 2.9.
|
|
15
|
+
| [init](../www/cordova-plugin-reteno.js) | iOS, Android | Initializes Reteno SDK. Accepts optional `RetenoInitializeOptions` with `accessKey`, `pauseInAppMessages`, `pausePushInAppMessages`, `inAppMessagesPauseBehaviour` (iOS), `lifecycleTrackingOptions`, `sessionDurationSeconds` (Android 2.9.6+, iOS 2.7.2+), `isAutomaticScreenReportingEnabled` (iOS; see note below) and `isDebugMode`. [Types](../types/index.ts) |
|
|
16
16
|
| [requestNotificationPermission](../www/cordova-plugin-reteno.js) | iOS, Android | Requests push permission (iOS) or `POST_NOTIFICATIONS` (Android 13+). Returns `0` or `1` on Android (`RequestNotificationPermissionResult`) in [types](../types/index.ts). |
|
|
17
17
|
| [setLifecycleTrackingOptions](../www/cordova-plugin-reteno.js) | iOS, Android | Configures automatic tracking for app lifecycle, push subscription, and session events. Android: applies immediately. iOS: supported only before initialization (stored and applied during `init(...)`). [Types](../types/index.ts) |
|
|
18
18
|
|
|
19
19
|
### User data
|
|
20
20
|
|
|
21
|
-
| Method | Supported platform | Description
|
|
22
|
-
| ---------------------------------------------------------------- | ------------------ |
|
|
23
|
-
| [setUserAttributes](../www/cordova-plugin-reteno.js) | iOS, Android | Supports `marketId`
|
|
24
|
-
| [setAnonymousUserAttributes](../www/cordova-plugin-reteno.js) | iOS, Android | Supports `marketId`
|
|
25
|
-
| [setMultiAccountUserAttributes](../www/cordova-plugin-reteno.js) | iOS, Android | Supports `marketId`
|
|
21
|
+
| Method | Supported platform | Description |
|
|
22
|
+
| ---------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| [setUserAttributes](../www/cordova-plugin-reteno.js) | iOS, Android | Supports `marketId` with the bundled Android 2.9.6+ and iOS 2.7.2+ SDKs. Pass `""` to clear. [Types](../types/index.ts) |
|
|
24
|
+
| [setAnonymousUserAttributes](../www/cordova-plugin-reteno.js) | iOS, Android | Supports `marketId` with the bundled Android 2.9.6+ and iOS 2.7.2+ SDKs. Pass `""` to clear. [Types](../types/index.ts) |
|
|
25
|
+
| [setMultiAccountUserAttributes](../www/cordova-plugin-reteno.js) | iOS, Android | Supports `marketId` with the bundled Android 2.9.6+ and iOS 2.7.2+ SDKs. Pass `""` to clear. [Types](../types/index.ts) |
|
|
26
26
|
|
|
27
27
|
### Event tracking
|
|
28
28
|
|
|
@@ -53,9 +53,12 @@ Notes:
|
|
|
53
53
|
| [setWillPresentNotificationOptions](../www/cordova-plugin-reteno.js) | iOS | Sets presentation options for foreground notifications. Optionally emits `reteno-push-received`. [Types](../types/index.ts) |
|
|
54
54
|
| [setDidReceiveNotificationResponseHandler](../www/cordova-plugin-reteno.js) | iOS | Enables a response handler for notification taps. Optionally emits `reteno-notification-clicked`. [Types](../types/index.ts) |
|
|
55
55
|
| [updateDefaultNotificationChannel](../www/cordova-plugin-reteno.js) | Android | Updates the default notification channel name and description for existing users. [Types](../types/index.ts) |
|
|
56
|
+
| [setNotificationGroupingRule](../www/cordova-plugin-reteno.js) | Android | Groups notifications by a push payload field or a constant group ID. The rule is persisted natively and restored before background push handling. Pass `null` to disable. [Types](../types/index.ts) |
|
|
56
57
|
|
|
57
58
|
### In-app messages
|
|
58
59
|
|
|
60
|
+
Bundled native SDKs support the `DOES_NOT_EQUAL` in-app rule condition: Android SDK 2.9.6+ and iOS SDK 2.7.2+.
|
|
61
|
+
|
|
59
62
|
| Method | Supported platform | Description |
|
|
60
63
|
| --------------------------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
61
64
|
| [pauseInAppMessages](../www/cordova-plugin-reteno.js) | iOS, Android | Pauses or resumes in-app messages at runtime. Pass `true` to pause, `false` to resume. |
|
|
@@ -99,7 +102,7 @@ RetenoPlugin.init()
|
|
|
99
102
|
// pausePushInAppMessages: pauses in-app messages triggered by push notifications.
|
|
100
103
|
// inAppMessagesPauseBehaviour: (iOS) defines handling while paused: 'SKIP_IN_APPS' or 'POSTPONE_IN_APPS'.
|
|
101
104
|
// lifecycleTrackingOptions: configures app lifecycle, push subscription, and session event tracking ('ALL', 'NONE', or an object).
|
|
102
|
-
// sessionDurationSeconds (Android 2.9.
|
|
105
|
+
// sessionDurationSeconds (Android 2.9.6+, iOS 2.7.2+): optional session reset duration.
|
|
103
106
|
// isAutomaticScreenReportingEnabled: enables automatic native screen view tracking on iOS.
|
|
104
107
|
// Keep this false in Cordova/Ionic WebView apps (including Android demos).
|
|
105
108
|
// Defaults to false. Note: in Cordova/Ionic apps the UI runs inside a single WebView,
|
|
@@ -116,13 +119,13 @@ RetenoPlugin.init({
|
|
|
116
119
|
inAppMessagesPauseBehaviour: 'SKIP_IN_APPS', // iOS
|
|
117
120
|
isAutomaticScreenReportingEnabled: false, // keep false for hybrid/WebView apps
|
|
118
121
|
isDebugMode: true, // enables debug mode
|
|
119
|
-
sessionDurationSeconds: 30 * 60, // Android 2.9.
|
|
122
|
+
sessionDurationSeconds: 30 * 60, // Android 2.9.6+, iOS 2.7.2+: 30 minutes
|
|
120
123
|
lifecycleTrackingOptions: {
|
|
121
124
|
appLifecycleEnabled: true,
|
|
122
125
|
foregroundLifecycleEnabled: false,
|
|
123
126
|
pushSubscriptionEnabled: true,
|
|
124
127
|
sessionStartEventsEnabled: true,
|
|
125
|
-
sessionEndEventsEnabled: false,
|
|
128
|
+
sessionEndEventsEnabled: false,
|
|
126
129
|
},
|
|
127
130
|
})
|
|
128
131
|
.then(() => console.log('init: OK'))
|
|
@@ -483,7 +486,7 @@ Subscribe to push received events while the app is running.
|
|
|
483
486
|
|
|
484
487
|
Notes:
|
|
485
488
|
|
|
486
|
-
- On Android this is based on the SDK 2.9.
|
|
489
|
+
- On Android this is based on the SDK 2.9.6 `EventListener` / `Procedure` API (`RetenoNotifications.getReceived()`).
|
|
487
490
|
- This listener is not a replacement for `getInitialNotification()` (cold start): if the app was launched by tapping a notification, use `getInitialNotification()`.
|
|
488
491
|
|
|
489
492
|
```js
|
|
@@ -565,6 +568,23 @@ RetenoPlugin.updateDefaultNotificationChannel({
|
|
|
565
568
|
.catch((err) => console.error('updateDefaultNotificationChannel: ERROR', err));
|
|
566
569
|
```
|
|
567
570
|
|
|
571
|
+
### setNotificationGroupingRule example
|
|
572
|
+
|
|
573
|
+
Configures Android notification grouping without a JavaScript callback, so the rule also works when the WebView is not running. Payload type: `NotificationGroupingRule` in [types](../types/index.ts).
|
|
574
|
+
|
|
575
|
+
```js
|
|
576
|
+
// Use the value of `chatId` from each push payload as its Android group key.
|
|
577
|
+
await RetenoPlugin.setNotificationGroupingRule({ payloadKey: 'chatId' });
|
|
578
|
+
|
|
579
|
+
// Or put every Reteno notification into one constant group.
|
|
580
|
+
await RetenoPlugin.setNotificationGroupingRule({ groupId: 'messages' });
|
|
581
|
+
|
|
582
|
+
// Disable grouping.
|
|
583
|
+
await RetenoPlugin.setNotificationGroupingRule(null);
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
The SDK assigns `NotificationCompat.Builder.setGroup(...)`; displaying a group-summary notification remains the application's responsibility.
|
|
587
|
+
|
|
568
588
|
### getAppInboxMessages example
|
|
569
589
|
|
|
570
590
|
Fetches paginated App Inbox messages. Payload type: `GetAppInboxMessagesPayload` in [types](../types/index.ts).
|
package/docs/ios.md
CHANGED
|
@@ -68,12 +68,12 @@ target 'App' do
|
|
|
68
68
|
|
|
69
69
|
target 'NotificationServiceExtension' do
|
|
70
70
|
inherit! :search_paths
|
|
71
|
-
pod 'Reteno', '2.7.
|
|
71
|
+
pod 'Reteno', '2.7.3'
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
target 'NotificationContentExtension' do
|
|
75
75
|
inherit! :search_paths
|
|
76
|
-
pod 'Reteno', '2.7.
|
|
76
|
+
pod 'Reteno', '2.7.3'
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
```
|
|
@@ -186,12 +186,12 @@ target 'App' do
|
|
|
186
186
|
|
|
187
187
|
target 'NotificationServiceExtension' do
|
|
188
188
|
inherit! :search_paths
|
|
189
|
-
pod 'Reteno', '2.7.
|
|
189
|
+
pod 'Reteno', '2.7.3'
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
target 'NotificationContentExtension' do
|
|
193
193
|
inherit! :search_paths
|
|
194
|
-
pod 'Reteno', '2.7.
|
|
194
|
+
pod 'Reteno', '2.7.3'
|
|
195
195
|
end
|
|
196
196
|
end
|
|
197
197
|
```
|
package/package.json
CHANGED
package/plugin.xml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<?xml version='1.0' encoding='utf-8'?>
|
|
2
|
-
<plugin id="cordova-plugin-reteno" version="2.
|
|
2
|
+
<plugin id="cordova-plugin-reteno" version="2.2.0" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
|
3
3
|
<name>cordova-plugin-reteno</name>
|
|
4
4
|
<engines>
|
|
5
5
|
<engine name="cordova-android" version=">=12.0.0" />
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
|
|
23
23
|
<config-file target="AndroidManifest.xml" parent="/manifest/application">
|
|
24
24
|
<meta-data android:name="com.reteno.SDK_ACCESS_KEY" android:value="$SDK_ACCESS_KEY" />
|
|
25
|
+
<provider
|
|
26
|
+
android:name="com.reteno.plugin.RetenoNotificationGroupingRuleProvider"
|
|
27
|
+
android:authorities="${applicationId}.reteno.notification-grouping-rule"
|
|
28
|
+
android:exported="false" />
|
|
25
29
|
</config-file>
|
|
26
30
|
|
|
27
31
|
<config-file target="config.xml" parent="/*">
|
|
@@ -45,6 +49,12 @@
|
|
|
45
49
|
<source-file
|
|
46
50
|
src="src/android/app/src/main/java/com/reteno/plugin/RetenoUtil.java"
|
|
47
51
|
target-dir="src/com/reteno/plugin" />
|
|
52
|
+
<source-file
|
|
53
|
+
src="src/android/app/src/main/java/com/reteno/plugin/RetenoNotificationGroupingRuleProvider.java"
|
|
54
|
+
target-dir="src/com/reteno/plugin" />
|
|
55
|
+
<source-file
|
|
56
|
+
src="src/android/app/src/main/java/com/reteno/plugin/RetenoNotificationSummaryManager.java"
|
|
57
|
+
target-dir="src/com/reteno/plugin" />
|
|
48
58
|
</platform>
|
|
49
59
|
|
|
50
60
|
<platform name="ios">
|
|
@@ -58,7 +68,7 @@
|
|
|
58
68
|
|
|
59
69
|
<podspec>
|
|
60
70
|
<pods>
|
|
61
|
-
<pod name="Reteno" spec="2.7.
|
|
71
|
+
<pod name="Reteno" spec="2.7.3" />
|
|
62
72
|
<pod name="FirebaseMessaging" />
|
|
63
73
|
</pods>
|
|
64
74
|
</podspec>
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
3
|
package="com.reteno.plugin">
|
|
4
4
|
|
|
5
|
-
<application
|
|
5
|
+
<application>
|
|
6
|
+
<provider
|
|
7
|
+
android:name="com.reteno.plugin.RetenoNotificationGroupingRuleProvider"
|
|
8
|
+
android:authorities="${applicationId}.reteno.notification-grouping-rule"
|
|
9
|
+
android:exported="false" />
|
|
10
|
+
</application>
|
|
6
11
|
|
|
7
12
|
</manifest>
|
package/src/android/app/src/main/java/com/reteno/plugin/RetenoNotificationGroupingRuleProvider.java
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
package com.reteno.plugin;
|
|
2
|
+
|
|
3
|
+
import android.content.ContentProvider;
|
|
4
|
+
import android.content.ContentValues;
|
|
5
|
+
import android.content.Context;
|
|
6
|
+
import android.content.SharedPreferences;
|
|
7
|
+
import android.database.Cursor;
|
|
8
|
+
import android.net.Uri;
|
|
9
|
+
import android.text.TextUtils;
|
|
10
|
+
|
|
11
|
+
import com.reteno.push.RetenoNotifications;
|
|
12
|
+
|
|
13
|
+
import java.util.Map;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Restores the declarative notification grouping rule before services handle a push.
|
|
17
|
+
* This keeps grouping available when the Cordova WebView has not started yet.
|
|
18
|
+
*/
|
|
19
|
+
public final class RetenoNotificationGroupingRuleProvider extends ContentProvider {
|
|
20
|
+
private static final String PREFERENCES = "com.reteno.plugin.notification-grouping-rule";
|
|
21
|
+
private static final String PAYLOAD_KEY = "payloadKey";
|
|
22
|
+
private static final String GROUP_ID = "groupId";
|
|
23
|
+
private static final String SHOW_SUMMARY = "showSummary";
|
|
24
|
+
|
|
25
|
+
@Override
|
|
26
|
+
public boolean onCreate() {
|
|
27
|
+
Context context = getContext();
|
|
28
|
+
if (context != null) {
|
|
29
|
+
restore(context);
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static void configure(Context context, String payloadKey, String groupId, boolean showSummary) {
|
|
35
|
+
SharedPreferences.Editor editor = preferences(context).edit().clear();
|
|
36
|
+
if (!TextUtils.isEmpty(payloadKey)) {
|
|
37
|
+
editor.putString(PAYLOAD_KEY, payloadKey);
|
|
38
|
+
} else if (!TextUtils.isEmpty(groupId)) {
|
|
39
|
+
editor.putString(GROUP_ID, groupId);
|
|
40
|
+
}
|
|
41
|
+
if (showSummary) {
|
|
42
|
+
editor.putBoolean(SHOW_SUMMARY, true);
|
|
43
|
+
}
|
|
44
|
+
// Persist before returning so a process restart cannot lose a just-configured rule.
|
|
45
|
+
editor.commit();
|
|
46
|
+
install(context, payloadKey, groupId, showSummary);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private static void restore(Context context) {
|
|
50
|
+
SharedPreferences preferences = preferences(context);
|
|
51
|
+
install(
|
|
52
|
+
context,
|
|
53
|
+
preferences.getString(PAYLOAD_KEY, null),
|
|
54
|
+
preferences.getString(GROUP_ID, null),
|
|
55
|
+
preferences.getBoolean(SHOW_SUMMARY, false)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Resolves the group a received push belongs to under the persisted rule. */
|
|
60
|
+
static String resolveGroup(Context context, Map<String, String> payload) {
|
|
61
|
+
SharedPreferences preferences = preferences(context);
|
|
62
|
+
String payloadKey = preferences.getString(PAYLOAD_KEY, null);
|
|
63
|
+
if (!TextUtils.isEmpty(payloadKey)) {
|
|
64
|
+
String value = payload.get(payloadKey);
|
|
65
|
+
return TextUtils.isEmpty(value) ? null : value;
|
|
66
|
+
}
|
|
67
|
+
String groupId = preferences.getString(GROUP_ID, null);
|
|
68
|
+
return TextUtils.isEmpty(groupId) ? null : groupId;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private static SharedPreferences preferences(Context context) {
|
|
72
|
+
return context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private static void install(Context context, String payloadKey, String groupId, boolean showSummary) {
|
|
76
|
+
if (!TextUtils.isEmpty(payloadKey)) {
|
|
77
|
+
RetenoNotifications.setGroupingRule((Map<String, String> payload) -> {
|
|
78
|
+
String value = payload.get(payloadKey);
|
|
79
|
+
return TextUtils.isEmpty(value) ? null : value;
|
|
80
|
+
});
|
|
81
|
+
} else if (!TextUtils.isEmpty(groupId)) {
|
|
82
|
+
RetenoNotifications.setGroupingRule((Map<String, String> payload) -> groupId);
|
|
83
|
+
} else {
|
|
84
|
+
RetenoNotifications.setGroupingRule(null);
|
|
85
|
+
}
|
|
86
|
+
RetenoNotificationSummaryManager.setEnabled(
|
|
87
|
+
context,
|
|
88
|
+
showSummary && (!TextUtils.isEmpty(payloadKey) || !TextUtils.isEmpty(groupId)),
|
|
89
|
+
!TextUtils.isEmpty(payloadKey) ? "payloadKey:" + payloadKey : "groupId:" + groupId
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Override
|
|
94
|
+
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@Override
|
|
99
|
+
public String getType(Uri uri) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Override
|
|
104
|
+
public Uri insert(Uri uri, ContentValues values) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@Override
|
|
109
|
+
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@Override
|
|
114
|
+
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
package com.reteno.plugin;
|
|
2
|
+
|
|
3
|
+
import android.Manifest;
|
|
4
|
+
import android.app.Notification;
|
|
5
|
+
import android.app.NotificationChannel;
|
|
6
|
+
import android.app.NotificationManager;
|
|
7
|
+
import android.content.Context;
|
|
8
|
+
import android.content.pm.PackageManager;
|
|
9
|
+
import android.os.Bundle;
|
|
10
|
+
import android.os.Handler;
|
|
11
|
+
import android.os.Looper;
|
|
12
|
+
import android.service.notification.StatusBarNotification;
|
|
13
|
+
|
|
14
|
+
import androidx.core.app.ActivityCompat;
|
|
15
|
+
import androidx.core.app.NotificationCompat;
|
|
16
|
+
import androidx.core.app.NotificationManagerCompat;
|
|
17
|
+
|
|
18
|
+
import com.reteno.core.util.Procedure;
|
|
19
|
+
import com.reteno.push.RetenoNotifications;
|
|
20
|
+
|
|
21
|
+
import java.util.HashMap;
|
|
22
|
+
import java.util.Map;
|
|
23
|
+
import java.util.Objects;
|
|
24
|
+
|
|
25
|
+
/** Maintains optional Android group-summary notifications for Reteno pushes. */
|
|
26
|
+
final class RetenoNotificationSummaryManager {
|
|
27
|
+
private static final String SUMMARY_CHANNEL_ID = "reteno_group_summary";
|
|
28
|
+
private static final String SUMMARY_TAG_PREFIX = "com.reteno.plugin.group-summary:";
|
|
29
|
+
private static final int SUMMARY_NOTIFICATION_ID = 1;
|
|
30
|
+
private static final int MAX_POST_ATTEMPTS = 25;
|
|
31
|
+
private static final int MAX_REMOVAL_ATTEMPTS = 10;
|
|
32
|
+
private static final long RETRY_DELAY_MS = 200L;
|
|
33
|
+
|
|
34
|
+
private static RetenoNotificationSummaryManager instance;
|
|
35
|
+
|
|
36
|
+
private final Context context;
|
|
37
|
+
private final Handler handler = new Handler(Looper.getMainLooper());
|
|
38
|
+
private final NotificationManagerCompat notificationManager;
|
|
39
|
+
|
|
40
|
+
private Procedure<Bundle> receivedListener;
|
|
41
|
+
private Procedure<Bundle> clickListener;
|
|
42
|
+
private Procedure<Bundle> closeListener;
|
|
43
|
+
private boolean enabled;
|
|
44
|
+
private String ruleIdentity;
|
|
45
|
+
|
|
46
|
+
private RetenoNotificationSummaryManager(Context context) {
|
|
47
|
+
this.context = context.getApplicationContext();
|
|
48
|
+
this.notificationManager = NotificationManagerCompat.from(this.context);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static synchronized void setEnabled(Context context, boolean enabled, String ruleIdentity) {
|
|
52
|
+
if (instance == null) {
|
|
53
|
+
instance = new RetenoNotificationSummaryManager(context);
|
|
54
|
+
}
|
|
55
|
+
if (enabled) {
|
|
56
|
+
instance.enable(ruleIdentity);
|
|
57
|
+
} else {
|
|
58
|
+
instance.disable();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private void enable(String ruleIdentity) {
|
|
63
|
+
if (enabled) {
|
|
64
|
+
if (Objects.equals(this.ruleIdentity, ruleIdentity)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// The grouping rule may have changed since we were last enabled (e.g. constant group
|
|
68
|
+
// "A" -> "B"). Group "A" will never receive another received/click/close event under
|
|
69
|
+
// the new rule, so its summary would otherwise be orphaned forever. Drop any pending
|
|
70
|
+
// retry first - it closed over the old group and would otherwise recreate a stale
|
|
71
|
+
// summary via reconcile() right after we cancel it below - then cancel every summary
|
|
72
|
+
// this manager owns; each will be rebuilt from scratch as traffic for the new rule
|
|
73
|
+
// comes in. cancelAllSummaries() only touches our own SUMMARY_TAG_PREFIX-tagged
|
|
74
|
+
// notifications, so it never disturbs a group some other feature of the host app uses.
|
|
75
|
+
handler.removeCallbacksAndMessages(null);
|
|
76
|
+
cancelAllSummaries();
|
|
77
|
+
this.ruleIdentity = ruleIdentity;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
enabled = true;
|
|
81
|
+
this.ruleIdentity = ruleIdentity;
|
|
82
|
+
createSummaryChannel();
|
|
83
|
+
|
|
84
|
+
receivedListener = this::onReceived;
|
|
85
|
+
clickListener = this::onRemoved;
|
|
86
|
+
closeListener = this::onRemoved;
|
|
87
|
+
RetenoNotifications.getReceived().addListener(receivedListener);
|
|
88
|
+
RetenoNotifications.getClick().addListener(clickListener);
|
|
89
|
+
RetenoNotifications.getClose().addListener(closeListener);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private void disable() {
|
|
93
|
+
enabled = false;
|
|
94
|
+
ruleIdentity = null;
|
|
95
|
+
handler.removeCallbacksAndMessages(null);
|
|
96
|
+
if (receivedListener != null) {
|
|
97
|
+
RetenoNotifications.getReceived().removeListener(receivedListener);
|
|
98
|
+
receivedListener = null;
|
|
99
|
+
}
|
|
100
|
+
if (clickListener != null) {
|
|
101
|
+
RetenoNotifications.getClick().removeListener(clickListener);
|
|
102
|
+
clickListener = null;
|
|
103
|
+
}
|
|
104
|
+
if (closeListener != null) {
|
|
105
|
+
RetenoNotifications.getClose().removeListener(closeListener);
|
|
106
|
+
closeListener = null;
|
|
107
|
+
}
|
|
108
|
+
cancelAllSummaries();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private void createSummaryChannel() {
|
|
112
|
+
NotificationChannel channel = new NotificationChannel(
|
|
113
|
+
SUMMARY_CHANNEL_ID,
|
|
114
|
+
"Grouped notifications summary",
|
|
115
|
+
NotificationManager.IMPORTANCE_DEFAULT
|
|
116
|
+
);
|
|
117
|
+
notificationManager.createNotificationChannel(channel);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private void onReceived(Bundle bundle) {
|
|
121
|
+
String group = resolveGroup(bundle);
|
|
122
|
+
if (group == null) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
awaitChildAndReconcile(group, notificationId(bundle), 0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private void awaitChildAndReconcile(String group, int notificationId, int attempt) {
|
|
129
|
+
if (!enabled) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (hasChild(group, notificationId) || attempt >= MAX_POST_ATTEMPTS) {
|
|
133
|
+
reconcile(group);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
handler.postDelayed(
|
|
137
|
+
() -> awaitChildAndReconcile(group, notificationId, attempt + 1),
|
|
138
|
+
RETRY_DELAY_MS
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private void onRemoved(Bundle bundle) {
|
|
143
|
+
if (!enabled) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
String group = resolveGroup(bundle);
|
|
147
|
+
if (group == null) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
awaitRemovalAndReconcile(group, notificationId(bundle), 0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private void awaitRemovalAndReconcile(String group, int notificationId, int attempt) {
|
|
154
|
+
if (!enabled) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (hasChild(group, notificationId) && attempt < MAX_REMOVAL_ATTEMPTS) {
|
|
158
|
+
handler.postDelayed(
|
|
159
|
+
() -> awaitRemovalAndReconcile(group, notificationId, attempt + 1),
|
|
160
|
+
RETRY_DELAY_MS
|
|
161
|
+
);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
reconcile(group);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private String resolveGroup(Bundle bundle) {
|
|
168
|
+
return RetenoNotificationGroupingRuleProvider.resolveGroup(context, toStringMap(bundle));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private Map<String, String> toStringMap(Bundle bundle) {
|
|
172
|
+
Map<String, String> payload = new HashMap<>();
|
|
173
|
+
if (bundle == null) {
|
|
174
|
+
return payload;
|
|
175
|
+
}
|
|
176
|
+
for (String key : bundle.keySet()) {
|
|
177
|
+
Object value = bundle.get(key);
|
|
178
|
+
payload.put(key, value == null ? null : value.toString());
|
|
179
|
+
}
|
|
180
|
+
return payload;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private int notificationId(Bundle bundle) {
|
|
184
|
+
String interactionId = bundle == null ? null : bundle.getString("es_interaction_id");
|
|
185
|
+
return interactionId == null ? 1 : interactionId.hashCode();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private boolean hasChild(String group, int notificationId) {
|
|
189
|
+
for (StatusBarNotification sbn : notificationManager.getActiveNotifications()) {
|
|
190
|
+
if (sbn.getId() == notificationId && isChildInGroup(sbn.getNotification(), group)) {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private void reconcile(String group) {
|
|
198
|
+
if (!enabled) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
int childCount = 0;
|
|
203
|
+
for (StatusBarNotification sbn : notificationManager.getActiveNotifications()) {
|
|
204
|
+
if (isChildInGroup(sbn.getNotification(), group)) {
|
|
205
|
+
childCount++;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
String tag = summaryTag(group);
|
|
210
|
+
if (childCount < 2) {
|
|
211
|
+
notificationManager.cancel(tag, SUMMARY_NOTIFICATION_ID);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)
|
|
215
|
+
!= PackageManager.PERMISSION_GRANTED) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
Notification summary = new NotificationCompat.Builder(context, SUMMARY_CHANNEL_ID)
|
|
220
|
+
.setContentTitle("New notifications")
|
|
221
|
+
.setContentText("You have " + childCount + " new notifications")
|
|
222
|
+
.setSmallIcon(resolveSmallIcon())
|
|
223
|
+
.setGroup(group)
|
|
224
|
+
.setGroupSummary(true)
|
|
225
|
+
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
|
226
|
+
.setOnlyAlertOnce(true)
|
|
227
|
+
.build();
|
|
228
|
+
|
|
229
|
+
notificationManager.notify(tag, SUMMARY_NOTIFICATION_ID, summary);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The launcher icon (context.getApplicationInfo().icon) is commonly an adaptive icon on modern
|
|
234
|
+
* apps, which is not a valid notification small icon and can make the system reject the
|
|
235
|
+
* notification. Reuse the plain vector drawable the Reteno Android SDK itself ships and falls
|
|
236
|
+
* back to (RetenoSdkPush's "reteno_default_push_icon"), which is guaranteed to be merged into
|
|
237
|
+
* any app that depends on the SDK, instead of ever touching the launcher icon.
|
|
238
|
+
*/
|
|
239
|
+
private int resolveSmallIcon() {
|
|
240
|
+
int icon = context.getResources().getIdentifier(
|
|
241
|
+
"reteno_default_push_icon", "drawable", context.getPackageName()
|
|
242
|
+
);
|
|
243
|
+
return icon != 0 ? icon : android.R.drawable.ic_dialog_info;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private boolean isChildInGroup(Notification notification, String group) {
|
|
247
|
+
return group.equals(notification.getGroup()) && !NotificationCompat.isGroupSummary(notification);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private String summaryTag(String group) {
|
|
251
|
+
return SUMMARY_TAG_PREFIX + group;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private void cancelAllSummaries() {
|
|
255
|
+
for (StatusBarNotification sbn : notificationManager.getActiveNotifications()) {
|
|
256
|
+
String tag = sbn.getTag();
|
|
257
|
+
if (tag != null && tag.startsWith(SUMMARY_TAG_PREFIX)) {
|
|
258
|
+
notificationManager.cancel(tag, sbn.getId());
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -353,6 +353,25 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
353
353
|
return true;
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
+
if ("setNotificationGroupingRule".equals(action)) {
|
|
357
|
+
Activity activity = cordova.getActivity();
|
|
358
|
+
if (activity == null) {
|
|
359
|
+
callbackContext.error("Reteno Android SDK Error: Activity is null.");
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
activity.runOnUiThread(new Runnable() {
|
|
363
|
+
@Override
|
|
364
|
+
public void run() {
|
|
365
|
+
try {
|
|
366
|
+
setNotificationGroupingRule(args, callbackContext);
|
|
367
|
+
} catch (Exception e) {
|
|
368
|
+
callbackContext.error("Reteno Android SDK Error: " + e.getLocalizedMessage());
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
|
|
356
375
|
if ("getAppInboxMessages".equals(action)) {
|
|
357
376
|
cordova.getThreadPool().execute(new Runnable() {
|
|
358
377
|
@Override
|
|
@@ -608,7 +627,7 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
608
627
|
}
|
|
609
628
|
|
|
610
629
|
/**
|
|
611
|
-
* Register notification event listeners using the SDK 2.9.
|
|
630
|
+
* Register notification event listeners using the SDK 2.9.6 EventListener API.
|
|
612
631
|
* Each listener is a direct {@link Procedure} implementation — no reflection needed.
|
|
613
632
|
*/
|
|
614
633
|
private void registerNotificationListeners() {
|
|
@@ -1181,7 +1200,7 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
1181
1200
|
? payload.optBoolean("sessionStartEventsEnabled", legacySessionEventsEnabled)
|
|
1182
1201
|
: legacySessionEventsEnabled;
|
|
1183
1202
|
|
|
1184
|
-
// SDK
|
|
1203
|
+
// SDK default for session end is disabled unless explicitly enabled.
|
|
1185
1204
|
boolean sessionEndEventsEnabled = payload.has("sessionEndEventsEnabled")
|
|
1186
1205
|
? payload.optBoolean("sessionEndEventsEnabled", false)
|
|
1187
1206
|
: (payload.has("sessionEventsEnabled") ? legacySessionEventsEnabled : false);
|
|
@@ -1320,6 +1339,61 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
1320
1339
|
}
|
|
1321
1340
|
}
|
|
1322
1341
|
|
|
1342
|
+
private void setNotificationGroupingRule(JSONArray args, CallbackContext callbackContext) {
|
|
1343
|
+
Object arg0 = (args == null || args.length() == 0) ? null : args.opt(0);
|
|
1344
|
+
if (arg0 instanceof JSONArray) {
|
|
1345
|
+
arg0 = ((JSONArray) arg0).opt(0);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (arg0 == null || arg0 == JSONObject.NULL) {
|
|
1349
|
+
RetenoNotificationGroupingRuleProvider.configure(
|
|
1350
|
+
cordova.getActivity().getApplicationContext(), null, null, false
|
|
1351
|
+
);
|
|
1352
|
+
callbackContext.success(1);
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
if (!(arg0 instanceof JSONObject)) {
|
|
1357
|
+
callbackContext.error("Invalid argument: expected null or an object with payloadKey or groupId");
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
JSONObject config = (JSONObject) arg0;
|
|
1362
|
+
Object rawPayloadKey = config.opt("payloadKey");
|
|
1363
|
+
Object rawGroupId = config.opt("groupId");
|
|
1364
|
+
Object rawShowSummary = config.opt("showSummary");
|
|
1365
|
+
if ((rawPayloadKey != null && rawPayloadKey != JSONObject.NULL && !(rawPayloadKey instanceof String))
|
|
1366
|
+
|| (rawGroupId != null && rawGroupId != JSONObject.NULL && !(rawGroupId instanceof String))) {
|
|
1367
|
+
callbackContext.error("Invalid argument: payloadKey and groupId must be strings");
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
if (rawShowSummary != null && rawShowSummary != JSONObject.NULL
|
|
1371
|
+
&& !(rawShowSummary instanceof Boolean)) {
|
|
1372
|
+
callbackContext.error("Invalid argument: showSummary must be a boolean");
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
String payloadKey = rawPayloadKey instanceof String ? (String) rawPayloadKey : null;
|
|
1377
|
+
String groupId = rawGroupId instanceof String ? (String) rawGroupId : null;
|
|
1378
|
+
payloadKey = payloadKey == null ? null : payloadKey.trim();
|
|
1379
|
+
groupId = groupId == null ? null : groupId.trim();
|
|
1380
|
+
|
|
1381
|
+
boolean hasPayloadKey = !TextUtils.isEmpty(payloadKey);
|
|
1382
|
+
boolean hasGroupId = !TextUtils.isEmpty(groupId);
|
|
1383
|
+
if (hasPayloadKey == hasGroupId) {
|
|
1384
|
+
callbackContext.error("Invalid argument: provide exactly one of payloadKey or groupId");
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
RetenoNotificationGroupingRuleProvider.configure(
|
|
1389
|
+
cordova.getActivity().getApplicationContext(),
|
|
1390
|
+
hasPayloadKey ? payloadKey : null,
|
|
1391
|
+
hasGroupId ? groupId : null,
|
|
1392
|
+
rawShowSummary instanceof Boolean && (Boolean) rawShowSummary
|
|
1393
|
+
);
|
|
1394
|
+
callbackContext.success(1);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1323
1397
|
private void getAppInboxMessages(JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
1324
1398
|
if (args == null || args.length() == 0) {
|
|
1325
1399
|
callbackContext.error("Missing argument: payload");
|
|
@@ -117,7 +117,7 @@ class RetenoPlugin: CDVPlugin {
|
|
|
117
117
|
isAutomaticScreenReportingEnabled: screenReportingEnabled,
|
|
118
118
|
isAutomaticAppLifecycleReportingEnabled: lifecycleAppEnabled,
|
|
119
119
|
isApplicationForegroundLifecycleReportingEnabled: lifecycleForegroundEnabled,
|
|
120
|
-
|
|
120
|
+
isAutomaticPushSubscriptionReportingEnabled: lifecyclePushEnabled,
|
|
121
121
|
sessionConfiguration: RetenoSessionConfiguration(
|
|
122
122
|
sessionDuration: sessionDurationSeconds,
|
|
123
123
|
isSessionStartReportingEnabled: lifecycleSessionStartEnabled,
|
package/types/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ export type UserAttributesAnonymous = {
|
|
|
23
23
|
* Market ID. Max 64 chars; latin letters, digits, '-' and '_' are allowed.
|
|
24
24
|
* Pass `""` (empty string) to clear the value on the server.
|
|
25
25
|
* Passing `null` or omitting the field has no effect (the existing value is kept).
|
|
26
|
-
* Available
|
|
26
|
+
* Available with the bundled Android 2.9.6+ and iOS 2.7.2+ SDKs.
|
|
27
27
|
*/
|
|
28
28
|
marketId?: string;
|
|
29
29
|
};
|
|
@@ -104,7 +104,7 @@ export type RetenoInitializeOptions = {
|
|
|
104
104
|
inAppMessagesPauseBehaviour?: InAppPauseBehaviour;
|
|
105
105
|
isAutomaticScreenReportingEnabled?: boolean;
|
|
106
106
|
lifecycleTrackingOptions?: LifecycleTrackingOptions;
|
|
107
|
-
/** Session reset duration in seconds (Android 2.9.
|
|
107
|
+
/** Session reset duration in seconds (supported by the bundled Android 2.9.6+ and iOS 2.7.2+ SDKs). */
|
|
108
108
|
sessionDurationSeconds?: number;
|
|
109
109
|
/** Enables Reteno debug mode for near real-time event monitoring. Use only with test devices. */
|
|
110
110
|
isDebugMode?: boolean;
|
|
@@ -154,6 +154,14 @@ export type NotificationChannelConfig = {
|
|
|
154
154
|
description: string;
|
|
155
155
|
};
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Android notification grouping rule. The rule is persisted natively so it
|
|
159
|
+
* remains available when a push is handled before the WebView starts.
|
|
160
|
+
*/
|
|
161
|
+
export type NotificationGroupingRule =
|
|
162
|
+
| { payloadKey: string; groupId?: never; showSummary?: boolean }
|
|
163
|
+
| { groupId: string; payloadKey?: never; showSummary?: boolean };
|
|
164
|
+
|
|
157
165
|
export type AppInboxStatus = 'OPENED' | 'UNOPENED';
|
|
158
166
|
|
|
159
167
|
export type AppInboxMessage = {
|
|
@@ -143,7 +143,7 @@ const RetenoPluginFunctions = {
|
|
|
143
143
|
inAppMessagesPauseBehaviour?: 'SKIP_IN_APPS' | 'POSTPONE_IN_APPS',
|
|
144
144
|
isAutomaticScreenReportingEnabled?: boolean,
|
|
145
145
|
isDebugMode?: boolean,
|
|
146
|
-
sessionDurationSeconds?: number, // Android 2.9.
|
|
146
|
+
sessionDurationSeconds?: number, // Android 2.9.6+, iOS 2.7.2+
|
|
147
147
|
lifecycleTrackingOptions?: {
|
|
148
148
|
appLifecycleEnabled?: boolean,
|
|
149
149
|
foregroundLifecycleEnabled?: boolean,
|
|
@@ -525,6 +525,44 @@ const RetenoPluginFunctions = {
|
|
|
525
525
|
return __callWithAutoInit('updateDefaultNotificationChannel', [config], success, error);
|
|
526
526
|
},
|
|
527
527
|
|
|
528
|
+
/*
|
|
529
|
+
config: NotificationGroupingRule | null
|
|
530
|
+
{ payloadKey: string } groups by a value from the push payload.
|
|
531
|
+
{ groupId: string } assigns every Reteno notification to a constant group.
|
|
532
|
+
Set showSummary: true to let the plugin maintain a group-summary notification.
|
|
533
|
+
Passing null disables grouping.
|
|
534
|
+
Android only.
|
|
535
|
+
*/
|
|
536
|
+
setNotificationGroupingRule: function (arg0, success, error) {
|
|
537
|
+
// Accept either `config` or legacy `[config]`.
|
|
538
|
+
const config = Array.isArray(arg0) ? arg0[0] : arg0;
|
|
539
|
+
if (config === null) {
|
|
540
|
+
return __callWithExec('setNotificationGroupingRule', [null], success, error);
|
|
541
|
+
}
|
|
542
|
+
if (!config || typeof config !== 'object') {
|
|
543
|
+
return Promise.reject(
|
|
544
|
+
new Error('Invalid argument: expected null or an object with payloadKey or groupId')
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const payloadKey = typeof config.payloadKey === 'string' ? config.payloadKey.trim() : '';
|
|
549
|
+
const groupId = typeof config.groupId === 'string' ? config.groupId.trim() : '';
|
|
550
|
+
if (config.showSummary !== undefined && typeof config.showSummary !== 'boolean') {
|
|
551
|
+
return Promise.reject(new Error('Invalid argument: showSummary must be a boolean'));
|
|
552
|
+
}
|
|
553
|
+
if (!!payloadKey === !!groupId) {
|
|
554
|
+
return Promise.reject(
|
|
555
|
+
new Error('Invalid argument: provide exactly one of payloadKey or groupId')
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const normalizedConfig = payloadKey ? { payloadKey } : { groupId };
|
|
560
|
+
if (config.showSummary === true) {
|
|
561
|
+
normalizedConfig.showSummary = true;
|
|
562
|
+
}
|
|
563
|
+
return __callWithExec('setNotificationGroupingRule', [normalizedConfig], success, error);
|
|
564
|
+
},
|
|
565
|
+
|
|
528
566
|
/*
|
|
529
567
|
payload: GetAppInboxMessagesPayload
|
|
530
568
|
{
|