cordova-plugin-reteno 2.2.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +2 -4
  2. package/docs/Cordova/Action Buttons/README.md +62 -0
  3. package/docs/Cordova/AppInbox/README.md +55 -0
  4. package/docs/Cordova/AppLifeCycleEvents/README.md +80 -0
  5. package/docs/Cordova/Ecommerce/README.md +144 -0
  6. package/docs/Cordova/InAppMessages/README.md +72 -0
  7. package/docs/Cordova/Push notification/README.md +129 -0
  8. package/docs/Cordova/Recommendations/README.md +62 -0
  9. package/docs/Cordova/SetupGuide/Android.md +135 -0
  10. package/docs/Cordova/SetupGuide/IOS.md +325 -0
  11. package/docs/Cordova/SetupGuide/README.md +91 -0
  12. package/docs/Cordova/Tracking user behaviour/README.md +52 -0
  13. package/docs/Cordova/Tracking user information/README.md +112 -0
  14. package/docs/Ionic/Action Buttons/README.md +66 -0
  15. package/docs/Ionic/AppInbox/README.md +61 -0
  16. package/docs/Ionic/AppLifeCycleEvents/README.md +77 -0
  17. package/docs/Ionic/Ecommerce/README.md +139 -0
  18. package/docs/Ionic/InAppMessages/README.md +65 -0
  19. package/docs/Ionic/Push notification/README.md +133 -0
  20. package/docs/Ionic/Recommendations/README.md +46 -0
  21. package/docs/Ionic/SetupGuide/Android.md +173 -0
  22. package/docs/Ionic/SetupGuide/IOS.md +354 -0
  23. package/docs/Ionic/SetupGuide/README.md +167 -0
  24. package/docs/Ionic/Tracking user behaviour/README.md +31 -0
  25. package/docs/Ionic/Tracking user information/README.md +98 -0
  26. package/package.json +1 -1
  27. package/plugin.xml +20 -2
  28. package/scripts/ios-podfile-use-modular-headers.js +109 -0
  29. package/scripts/validate-install.js +37 -9
  30. package/src/android/app/build.gradle +1 -1
  31. package/src/android/app/src/main/java/com/reteno/plugin/RetenoPlugin.java +29 -9
  32. package/src/android/reteno.gradle +19 -9
  33. package/src/ios/RetenoPlugin.swift +37 -5
  34. package/src/ios/RetenoPluginBootstrap.m +117 -0
  35. package/types/index.ts +1 -1
  36. package/docs/android.md +0 -264
  37. package/docs/api.md +0 -734
  38. package/docs/ios.md +0 -495
@@ -0,0 +1,112 @@
1
+ # Tracking user information
2
+
3
+ ## Set user attributes (identified user)
4
+
5
+ Use `setUserAttributes` with required `externalUserId`:
6
+
7
+ ```js
8
+ RetenoPlugin.setUserAttributes({
9
+ externalUserId: 'USER_ID',
10
+ user: {
11
+ userAttributes: {
12
+ email: 'john.doe@example.com',
13
+ phone: '+1234567890',
14
+ firstName: 'John',
15
+ lastName: 'Doe',
16
+ languageCode: 'en',
17
+ timeZone: 'Europe/Kyiv',
18
+ marketId: 'market_1',
19
+ address: {
20
+ region: 'Kyivska',
21
+ town: 'Kyiv',
22
+ address: 'Khreshchatyk St, 1',
23
+ postcode: '01001',
24
+ },
25
+ fields: [{ key: 'plan', value: 'premium' }],
26
+ },
27
+ subscriptionKeys: ['news', 'promotions'],
28
+ groupNamesInclude: ['beta-testers'],
29
+ groupNamesExclude: ['unsubscribed'],
30
+ },
31
+ })
32
+ .then(() => console.log('setUserAttributes: OK'))
33
+ .catch((err) => console.error('setUserAttributes: ERROR', err));
34
+ ```
35
+
36
+ ## Set anonymous user attributes
37
+
38
+ Use `setAnonymousUserAttributes` before user identification:
39
+
40
+ ```js
41
+ RetenoPlugin.setAnonymousUserAttributes({
42
+ firstName: 'John',
43
+ lastName: 'Doe',
44
+ languageCode: 'en',
45
+ timeZone: 'Europe/Kyiv',
46
+ marketId: 'market_1',
47
+ address: {
48
+ region: 'Kyivska',
49
+ town: 'Kyiv',
50
+ address: 'Khreshchatyk St, 1',
51
+ postcode: '01001',
52
+ },
53
+ fields: [{ key: 'utm_source', value: 'google' }],
54
+ })
55
+ .then(() => console.log('setAnonymousUserAttributes: OK'))
56
+ .catch((err) => console.error('setAnonymousUserAttributes: ERROR', err));
57
+ ```
58
+
59
+ Note: anonymous attributes cannot include `phone` or `email`.
60
+
61
+ ## Multi-account mode
62
+
63
+ Use `setMultiAccountUserAttributes` when one device should receive push for multiple accounts:
64
+
65
+ ```js
66
+ RetenoPlugin.setMultiAccountUserAttributes({
67
+ externalUserId: 'user-123',
68
+ user: {
69
+ userAttributes: {
70
+ email: 'john.doe@example.com',
71
+ phone: '+1234567890',
72
+ firstName: 'John',
73
+ lastName: 'Doe',
74
+ languageCode: 'en',
75
+ timeZone: 'Europe/Kyiv',
76
+ marketId: 'market_1',
77
+ },
78
+ },
79
+ })
80
+ .then(() => console.log('setMultiAccountUserAttributes: OK'))
81
+ .catch((err) => console.error('setMultiAccountUserAttributes: ERROR', err));
82
+ ```
83
+
84
+ If you want default one-token-per-device behavior, switch back to `setUserAttributes`.
85
+
86
+ ## Market ID
87
+
88
+ `marketId` is supported by `setUserAttributes`, `setAnonymousUserAttributes`, and
89
+ `setMultiAccountUserAttributes` starting with `cordova-plugin-reteno@2.1.0`.
90
+
91
+ - The value can contain up to 64 Latin letters, digits, hyphens (`-`), and underscores (`_`).
92
+ - Pass an empty string to clear the existing value:
93
+
94
+ ```js
95
+ RetenoPlugin.setUserAttributes({
96
+ externalUserId: 'USER_ID',
97
+ user: {
98
+ userAttributes: {
99
+ marketId: '',
100
+ },
101
+ },
102
+ });
103
+ ```
104
+
105
+ - Omit `marketId` to keep the existing value unchanged.
106
+ - The feature requires Reteno Android SDK 2.9.5+ or Reteno iOS SDK 2.7.1+. These versions are included in `cordova-plugin-reteno@2.1.0`.
107
+
108
+ ## Notes
109
+
110
+ - `languageCode` should follow [RFC 5646](https://www.rfc-editor.org/rfc/rfc5646.html) (`de-AT`, `en`, etc).
111
+ - `timeZone` should be from [TZ database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (`Europe/Kyiv`, etc).
112
+ - API payload types are available in plugin [types](https://github.com/reteno-com/reteno-cordova/blob/main/types/index.ts).
@@ -0,0 +1,66 @@
1
+ # Action Buttons
2
+
3
+ ## Configuring Action Buttons
4
+
5
+ When creating mobile push notifications, you can add action buttons.
6
+
7
+ - iOS supports up to 4 buttons.
8
+ - Android supports up to 3 buttons.
9
+
10
+ <p align="center">
11
+ <img src="https://raw.githubusercontent.com/reteno-com/reteno-mobile-documentation/main/Ionic/Action%20Buttons/Resources/action_button_setup.png" width="100%"/>
12
+ </p>
13
+
14
+ Use unique `Action ID` values for different buttons.
15
+
16
+ | Parameter | Notes |
17
+ | --- | --- |
18
+ | Action ID | Unique button action identifier |
19
+ | Text | Text shown on the button |
20
+ | iOS icon path | Available on iOS 15+. Name of image from app bundle |
21
+ | Launch URL | URL/deeplink to open |
22
+ | Custom data | Additional JSON payload |
23
+
24
+ ## Handle action button click
25
+
26
+ ### iOS
27
+
28
+ Enable handler:
29
+
30
+ ```ts
31
+ await this.reteno.setNotificationActionHandler({ emitEvent: true });
32
+ // or equivalently:
33
+ await this.reteno.setNotificationActionHandler({ enabled: true, emitEvent: true });
34
+ ```
35
+
36
+ Any other value (`false`, `null`, `true`, `{ enabled: true }` without `emitEvent`) clears the handler.
37
+
38
+ Subscribe:
39
+
40
+ The plugin delivers the event through `document.addEventListener('reteno-push-button-clicked', ...)`, so the listener receives a DOM `CustomEvent` whose `detail` field matches `RetenoPushButtonClickedPayload`:
41
+
42
+ ```ts
43
+ const onPushButtonClicked = (event: any) => {
44
+ const detail = event?.detail ?? event;
45
+ console.log('reteno-push-button-clicked:', detail);
46
+ // detail.actionId — unique button action identifier
47
+ // detail.link — URL/deeplink associated with the button (may be null)
48
+ // detail.customData — additional JSON from the button (object, or raw string on Android)
49
+ // detail.userInfo — original notification payload
50
+ };
51
+
52
+ this.reteno.setOnRetenoPushButtonClickedListener(onPushButtonClicked);
53
+
54
+ // later
55
+ this.reteno.removeOnRetenoPushButtonClickedListener(onPushButtonClicked);
56
+ ```
57
+
58
+ Disable:
59
+
60
+ ```ts
61
+ await this.reteno.setNotificationActionHandler(false);
62
+ ```
63
+
64
+ ### Android
65
+
66
+ `setNotificationActionHandler(...)` is no-op on Android. Action button clicks are detected automatically.
@@ -0,0 +1,61 @@
1
+ # App Inbox
2
+
3
+ ## Get App Inbox messages
4
+
5
+ ```ts
6
+ const result = await this.reteno.getAppInboxMessages({
7
+ page: 1,
8
+ pageSize: 20,
9
+ status: 'UNOPENED', // optional: 'OPENED' | 'UNOPENED'
10
+ });
11
+
12
+ console.log(result.messages, result.totalPages);
13
+ ```
14
+
15
+ Returns `AppInboxMessages`: `{ messages: AppInboxMessage[], totalPages: number }`. Each `AppInboxMessage` contains:
16
+
17
+ ```ts
18
+ type AppInboxMessage = {
19
+ id: string;
20
+ title: string;
21
+ createdDate: string;
22
+ isNewMessage: boolean;
23
+ content?: string | null;
24
+ imageUrl?: string | null;
25
+ linkUrl?: string | null;
26
+ category?: string | null;
27
+ status?: 'OPENED' | 'UNOPENED' | null;
28
+ customData?: Record<string, string> | null;
29
+ };
30
+ ```
31
+
32
+ ## Get unread messages count
33
+
34
+ ```ts
35
+ const count = await this.reteno.getAppInboxMessagesCount();
36
+ console.log('Unread messages:', count);
37
+ ```
38
+
39
+ ## Subscribe to count changes
40
+
41
+ ```ts
42
+ this.reteno.subscribeOnMessagesCountChanged().subscribe({
43
+ next: (count) => console.log('App Inbox count changed:', count),
44
+ error: (err) => console.error('subscribeOnMessagesCountChanged:', err),
45
+ });
46
+
47
+ // later
48
+ await this.reteno.unsubscribeMessagesCountChanged();
49
+ ```
50
+
51
+ ## Mark message as opened
52
+
53
+ ```ts
54
+ await this.reteno.markAsOpened('message-id');
55
+ ```
56
+
57
+ ## Mark all messages as opened
58
+
59
+ ```ts
60
+ await this.reteno.markAllMessagesAsOpened();
61
+ ```
@@ -0,0 +1,77 @@
1
+ # App Lifecycle Events
2
+
3
+ Reteno can automatically track app lifecycle, foreground lifecycle, push subscription, and session events.
4
+
5
+ ## Tracked app lifecycle events
6
+
7
+ When `appLifecycleEnabled` is enabled:
8
+
9
+ - `ApplicationInstalled`
10
+ - `ApplicationUpdated`
11
+ - `ApplicationOpened`
12
+ - `ApplicationBackgrounded`
13
+
14
+ ## Tracked foreground lifecycle events
15
+
16
+ When `foregroundLifecycleEnabled` is enabled:
17
+
18
+ - `ApplicationOpened`
19
+ - `ApplicationBackgrounded`
20
+
21
+ ## Tracked push subscription events
22
+
23
+ When `pushSubscriptionEnabled` is enabled:
24
+
25
+ - `PushNotificationsSubscribed`
26
+ - `PushNotificationsUnsubscribed`
27
+
28
+ ## Tracked session events
29
+
30
+ When `sessionStartEventsEnabled` / `sessionEndEventsEnabled` are enabled:
31
+
32
+ - `SessionStarted`
33
+ - `SessionEnded`
34
+
35
+ ## Configure lifecycle tracking
36
+
37
+ During initialization:
38
+
39
+ ```ts
40
+ await this.reteno.init({
41
+ lifecycleTrackingOptions: {
42
+ appLifecycleEnabled: true,
43
+ foregroundLifecycleEnabled: false,
44
+ pushSubscriptionEnabled: true,
45
+ sessionStartEventsEnabled: true,
46
+ sessionEndEventsEnabled: false,
47
+ },
48
+ });
49
+ ```
50
+
51
+ Shortcuts:
52
+
53
+ ```ts
54
+ await this.reteno.init({ lifecycleTrackingOptions: 'ALL' });
55
+ await this.reteno.init({ lifecycleTrackingOptions: 'NONE' });
56
+ ```
57
+
58
+ Runtime update:
59
+
60
+ ```ts
61
+ await this.reteno.setLifecycleTrackingOptions({
62
+ sessionStartEventsEnabled: true,
63
+ sessionEndEventsEnabled: true,
64
+ });
65
+ ```
66
+
67
+ iOS note: runtime method `setLifecycleTrackingOptions(...)` works only before `init()`.
68
+ Use `lifecycleTrackingOptions` in `init(...)` as the main configuration path for iOS.
69
+ Legacy note: `sessionEventsEnabled` is still supported as an alias that toggles both session start and end events.
70
+
71
+ ## Screen tracking in Ionic/Cordova
72
+
73
+ Use manual tracking:
74
+
75
+ ```ts
76
+ await this.reteno.logScreenView('HomeScreen');
77
+ ```
@@ -0,0 +1,139 @@
1
+ # Ecommerce Activity Tracking
2
+
3
+ Use `logEcommerceEvent(...)`.
4
+
5
+ Supported `eventType` values:
6
+
7
+ - `productViewed`
8
+ - `productCategoryViewed`
9
+ - `productAddedToWishlist`
10
+ - `cartUpdated`
11
+ - `orderCreated`
12
+ - `orderUpdated`
13
+ - `orderDelivered`
14
+ - `orderCancelled`
15
+ - `searchRequest`
16
+
17
+ `currencyCode` should use ISO 4217 format (for example: `USD`, `EUR`, `GBP`).
18
+
19
+ ## Product viewed
20
+
21
+ ```ts
22
+ await this.reteno.logEcommerceEvent({
23
+ eventType: 'productViewed',
24
+ product: {
25
+ productId: 'SKU-123',
26
+ price: 29.99,
27
+ isInStock: true,
28
+ attributes: [{ name: 'color', value: ['red', 'blue'] }],
29
+ },
30
+ currencyCode: 'USD',
31
+ });
32
+ ```
33
+
34
+ ## Product category viewed
35
+
36
+ ```ts
37
+ await this.reteno.logEcommerceEvent({
38
+ eventType: 'productCategoryViewed',
39
+ category: {
40
+ productCategoryId: 'CATEGORY-01',
41
+ attributes: [{ name: 'gender', value: ['women'] }],
42
+ },
43
+ });
44
+ ```
45
+
46
+ ## Product added to wishlist
47
+
48
+ ```ts
49
+ await this.reteno.logEcommerceEvent({
50
+ eventType: 'productAddedToWishlist',
51
+ product: {
52
+ productId: 'SKU-123',
53
+ price: 29.99,
54
+ isInStock: true,
55
+ attributes: [{ name: 'color', value: ['red'] }],
56
+ },
57
+ currencyCode: 'USD',
58
+ });
59
+ ```
60
+
61
+ ## Cart updated
62
+
63
+ ```ts
64
+ await this.reteno.logEcommerceEvent({
65
+ eventType: 'cartUpdated',
66
+ cartId: 'cart-456',
67
+ products: [
68
+ {
69
+ productId: 'SKU-123',
70
+ quantity: 2,
71
+ price: 29.99,
72
+ name: 'T-Shirt',
73
+ category: 'Apparel',
74
+ },
75
+ ],
76
+ currencyCode: 'USD',
77
+ });
78
+ ```
79
+
80
+ ## Order created
81
+
82
+ `status` accepts `'INITIALIZED' | 'IN_PROGRESS' | 'DELIVERED' | 'CANCELLED'` (plus any custom string).
83
+
84
+ ```ts
85
+ await this.reteno.logEcommerceEvent({
86
+ eventType: 'orderCreated',
87
+ order: {
88
+ externalOrderId: 'ORD-789',
89
+ totalCost: 59.98,
90
+ status: 'INITIALIZED',
91
+ date: new Date().toISOString(),
92
+ items: [
93
+ {
94
+ externalItemId: 'SKU-123',
95
+ name: 'T-Shirt',
96
+ category: 'Apparel',
97
+ quantity: 2,
98
+ cost: 29.99,
99
+ url: 'https://example.com/products/SKU-123',
100
+ },
101
+ ],
102
+ },
103
+ currencyCode: 'USD',
104
+ });
105
+ ```
106
+
107
+ ## Order updated
108
+
109
+ ```ts
110
+ await this.reteno.logEcommerceEvent({
111
+ eventType: 'orderUpdated',
112
+ order: {
113
+ externalOrderId: 'ORD-789',
114
+ totalCost: 59.98,
115
+ status: 'IN_PROGRESS',
116
+ date: new Date().toISOString(),
117
+ },
118
+ currencyCode: 'USD',
119
+ });
120
+ ```
121
+
122
+ ## Order delivered / cancelled
123
+
124
+ For these event types use `externalOrderId` (not full `order` object):
125
+
126
+ ```ts
127
+ await this.reteno.logEcommerceEvent({ eventType: 'orderDelivered', externalOrderId: 'ORD-789' });
128
+ await this.reteno.logEcommerceEvent({ eventType: 'orderCancelled', externalOrderId: 'ORD-789' });
129
+ ```
130
+
131
+ ## Search request
132
+
133
+ ```ts
134
+ await this.reteno.logEcommerceEvent({
135
+ eventType: 'searchRequest',
136
+ search: 'red t-shirt',
137
+ isFound: true,
138
+ });
139
+ ```
@@ -0,0 +1,65 @@
1
+ # In-app messages
2
+
3
+ ## Pause and resume in-app messages
4
+
5
+ ```ts
6
+ await this.reteno.pauseInAppMessages(true); // pause
7
+ await this.reteno.pauseInAppMessages(false); // resume
8
+ ```
9
+
10
+ ## Configure pause behaviour
11
+
12
+ ```ts
13
+ await this.reteno.setInAppMessagesPauseBehaviour('SKIP_IN_APPS');
14
+ await this.reteno.setInAppMessagesPauseBehaviour('POSTPONE_IN_APPS');
15
+ ```
16
+
17
+ On iOS you can also preconfigure the pause behaviour during `init()` via `inAppMessagesPauseBehaviour` — see the options table in [Setup overview](../SetupGuide/README.md#options-reference).
18
+
19
+ ## Subscribe to in-app lifecycle events
20
+
21
+ ```ts
22
+ const onInAppStatus = (event: any) => {
23
+ const detail = event?.detail ?? event;
24
+ console.log('In-app status:', detail?.event, detail?.data);
25
+ };
26
+
27
+ await this.reteno.setOnInAppLifecycleCallback(onInAppStatus);
28
+
29
+ // unsubscribe
30
+ await this.reteno.setOnInAppLifecycleCallback(null);
31
+ ```
32
+
33
+ ### Platform-specific payload (`detail.data`)
34
+
35
+ | Event | Android | iOS |
36
+ |---|---|---|
37
+ | `beforeDisplay` | `{ id }` | `{}` |
38
+ | `onDisplay` | `{ id }` | `{}` |
39
+ | `beforeClose` | `{ id, closeAction }` | `{ closeAction, action }` |
40
+ | `afterClose` | `{ id, closeAction }` | `{ closeAction, action }` |
41
+ | `onError` | `{ id, errorMessage }` | `{ errorMessage }` |
42
+
43
+ In short: Android payload usually includes in-app `id`, while iOS payload for display events is empty (`{}`) and does not include message id.
44
+
45
+ `closeAction` values:
46
+ - **Android**: string from native SDK (e.g. via `InAppCloseData.getCloseAction()`)
47
+ - **iOS**: `"closeButtonClicked"`, `"buttonClicked"`, `"openUrlClicked"`, or `"unknown"`
48
+
49
+ `action` (iOS only): `{ isCloseButtonClicked, isButtonClicked, isOpenUrlClicked }`
50
+
51
+ ## Receive custom data from in-app (Android)
52
+
53
+ ```ts
54
+ const onCustomData = (event: any) => {
55
+ const detail = event?.detail ?? event;
56
+ console.log('In-app custom data:', detail);
57
+ };
58
+
59
+ this.reteno.setOnInAppMessageCustomDataReceivedListener(onCustomData);
60
+
61
+ // later
62
+ this.reteno.removeOnInAppMessageCustomDataReceivedListener(onCustomData);
63
+ ```
64
+
65
+ Important: when this listener is registered, automatic opening of in-app button links is disabled. Handle navigation yourself (for example, using `detail.url` / `payload.url`).
@@ -0,0 +1,133 @@
1
+ # Push notification
2
+
3
+ ## Request notification permission
4
+
5
+ ```ts
6
+ const granted = await this.reteno.requestNotificationPermission();
7
+ console.log('Permission result:', granted);
8
+ ```
9
+
10
+ ## Get initial notification
11
+
12
+ On Android, `getInitialNotification()` returns only a Reteno push payload
13
+ identified by `es_interaction_id`. Intent extras from Branch or other deeplink
14
+ SDKs are ignored. If the app was not opened from a Reteno push, the method
15
+ returns an empty object.
16
+
17
+ ```ts
18
+ const initial = await this.reteno.getInitialNotification();
19
+ console.log('Initial push payload:', initial);
20
+ ```
21
+
22
+ ## Listen for received push
23
+
24
+ ```ts
25
+ const onPushReceived = (event: any) => {
26
+ console.log('reteno-push-received', event);
27
+ };
28
+
29
+ this.reteno.setOnRetenoPushReceivedListener(onPushReceived);
30
+
31
+ // later
32
+ this.reteno.removeOnRetenoPushReceivedListener(onPushReceived);
33
+ ```
34
+
35
+ ## Listen for notification click
36
+
37
+ ```ts
38
+ const onClicked = (event: any) => {
39
+ console.log('reteno-notification-clicked', event);
40
+ };
41
+
42
+ this.reteno.setOnRetenoNotificationClickedListener(onClicked);
43
+
44
+ // later
45
+ this.reteno.removeOnRetenoNotificationClickedListener(onClicked);
46
+ ```
47
+
48
+ ## Android-only listeners
49
+
50
+ ```ts
51
+ const onDismissed = (payload: any) => console.log('Push dismissed', payload);
52
+ const onCustomReceived = (payload: any) => console.log('Custom push received', payload);
53
+
54
+ this.reteno.setOnRetenoPushDismissedListener(onDismissed);
55
+ this.reteno.setOnRetenoCustomPushReceivedListener(onCustomReceived);
56
+
57
+ // later
58
+ this.reteno.removeOnRetenoPushDismissedListener(onDismissed);
59
+ this.reteno.removeOnRetenoCustomPushReceivedListener(onCustomReceived);
60
+ ```
61
+
62
+ ## iOS foreground/tap handlers (optional)
63
+
64
+ These methods are iOS-only. On Android they are not implemented and calls return an error (`Promise` reject / `error` callback).
65
+
66
+ ```ts
67
+ await this.reteno.setWillPresentNotificationOptions({
68
+ options: ['badge', 'sound', 'banner'],
69
+ emitEvent: true,
70
+ });
71
+
72
+ await this.reteno.setDidReceiveNotificationResponseHandler({
73
+ enabled: true,
74
+ emitEvent: true,
75
+ });
76
+ ```
77
+
78
+ `options` accepts any combination of `NotificationPresentationOption` values: `'badge' | 'sound' | 'alert' | 'banner' | 'list'`.
79
+
80
+ Shorthand forms accepted by `setWillPresentNotificationOptions`:
81
+
82
+ ```ts
83
+ // Pass only the options array
84
+ await this.reteno.setWillPresentNotificationOptions(['badge', 'sound', 'banner']);
85
+
86
+ // Clear previously set options
87
+ await this.reteno.setWillPresentNotificationOptions(null);
88
+ ```
89
+
90
+ `setDidReceiveNotificationResponseHandler` also accepts `boolean | null` to disable or clear the handler:
91
+
92
+ ```ts
93
+ await this.reteno.setDidReceiveNotificationResponseHandler(false);
94
+ ```
95
+
96
+ If `emitEvent: true`, plugin emits JS events:
97
+
98
+ - `reteno-push-received`
99
+ - `reteno-notification-clicked`
100
+
101
+ ## Pass token manually
102
+
103
+ ```ts
104
+ await this.reteno.setDeviceToken(token);
105
+ ```
106
+
107
+ Use this when token callbacks are handled outside `cordova-plugin-reteno` (for example, by another plugin/SDK that owns APNs/FCM token delivery).
108
+
109
+ ## Android default channel update
110
+
111
+ ```ts
112
+ await this.reteno.updateDefaultNotificationChannel({
113
+ name: 'New Channel Name',
114
+ description: 'New Channel Description',
115
+ });
116
+ ```
117
+
118
+ ## Android notification grouping
119
+
120
+ Groups Android notifications by a value from the push payload or by a constant group ID. The rule is persisted natively and restored when the Android process starts, so it also applies to a push handled before the WebView is active.
121
+
122
+ ```ts
123
+ // Group notifications by the `chatId` field in the push payload.
124
+ await this.reteno.setNotificationGroupingRule({ payloadKey: 'chatId', showSummary: true });
125
+
126
+ // Or assign every Reteno notification to one constant group.
127
+ await this.reteno.setNotificationGroupingRule({ groupId: 'messages', showSummary: true });
128
+
129
+ // Disable grouping.
130
+ await this.reteno.setNotificationGroupingRule(null);
131
+ ```
132
+
133
+ `showSummary` is optional and defaults to `false`. When enabled, the plugin creates and maintains an Android group-summary notification for the group ("You have N new notifications") once at least two notifications share it, and removes the summary once fewer than two remain. Without `showSummary`, grouping only sets the group key on each notification — no collapsed summary row appears unless the app posts its own summary notification.
@@ -0,0 +1,46 @@
1
+ # Recommendations
2
+
3
+ ## Get recommendations
4
+
5
+ ```ts
6
+ const result = await this.reteno.getRecommendations({
7
+ recomVariantId: 'variant-id',
8
+ productIds: ['product-1', 'product-2'],
9
+ fields: ['name', 'price', 'image', 'link'],
10
+ });
11
+
12
+ console.log(result.recoms);
13
+ ```
14
+
15
+ Category-based:
16
+
17
+ ```ts
18
+ await this.reteno.getRecommendations({
19
+ recomVariantId: 'variant-id',
20
+ categoryId: 'category-1',
21
+ fields: ['name', 'price'],
22
+ });
23
+ ```
24
+
25
+ With filters (accepts a single filter object or an array):
26
+
27
+ ```ts
28
+ await this.reteno.getRecommendations({
29
+ recomVariantId: 'variant-id',
30
+ productIds: ['product-1'],
31
+ fields: ['name', 'price'],
32
+ filters: [{ name: 'category', values: ['shoes', 'boots'] }],
33
+ });
34
+ ```
35
+
36
+ ## Log recommendation events
37
+
38
+ ```ts
39
+ await this.reteno.logRecommendations({
40
+ recomVariantId: 'variant-id',
41
+ recomEvents: [
42
+ { recomEventType: 'IMPRESSIONS', occurred: new Date().toISOString(), productId: 'product-1' },
43
+ { recomEventType: 'CLICKS', occurred: new Date().toISOString(), productId: 'product-2' },
44
+ ],
45
+ });
46
+ ```