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.
- package/README.md +2 -4
- package/docs/Cordova/Action Buttons/README.md +62 -0
- package/docs/Cordova/AppInbox/README.md +55 -0
- package/docs/Cordova/AppLifeCycleEvents/README.md +80 -0
- package/docs/Cordova/Ecommerce/README.md +144 -0
- package/docs/Cordova/InAppMessages/README.md +72 -0
- package/docs/Cordova/Push notification/README.md +129 -0
- package/docs/Cordova/Recommendations/README.md +62 -0
- package/docs/Cordova/SetupGuide/Android.md +135 -0
- package/docs/Cordova/SetupGuide/IOS.md +325 -0
- package/docs/Cordova/SetupGuide/README.md +91 -0
- package/docs/Cordova/Tracking user behaviour/README.md +52 -0
- package/docs/Cordova/Tracking user information/README.md +112 -0
- package/docs/Ionic/Action Buttons/README.md +66 -0
- package/docs/Ionic/AppInbox/README.md +61 -0
- package/docs/Ionic/AppLifeCycleEvents/README.md +77 -0
- package/docs/Ionic/Ecommerce/README.md +139 -0
- package/docs/Ionic/InAppMessages/README.md +65 -0
- package/docs/Ionic/Push notification/README.md +133 -0
- package/docs/Ionic/Recommendations/README.md +46 -0
- package/docs/Ionic/SetupGuide/Android.md +173 -0
- package/docs/Ionic/SetupGuide/IOS.md +354 -0
- package/docs/Ionic/SetupGuide/README.md +167 -0
- package/docs/Ionic/Tracking user behaviour/README.md +31 -0
- package/docs/Ionic/Tracking user information/README.md +98 -0
- package/package.json +1 -1
- package/plugin.xml +20 -2
- package/scripts/ios-podfile-use-modular-headers.js +109 -0
- package/scripts/validate-install.js +37 -9
- package/src/android/app/build.gradle +1 -1
- package/src/android/app/src/main/java/com/reteno/plugin/RetenoPlugin.java +29 -9
- package/src/android/reteno.gradle +19 -9
- package/src/ios/RetenoPlugin.swift +37 -5
- package/src/ios/RetenoPluginBootstrap.m +117 -0
- package/types/index.ts +1 -1
- package/docs/android.md +0 -264
- package/docs/api.md +0 -734
- package/docs/ios.md +0 -495
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Tracking user behaviour
|
|
2
|
+
|
|
3
|
+
## Track custom events
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
await this.reteno.logEvent({
|
|
7
|
+
eventName: 'EVENT_NAME',
|
|
8
|
+
date: new Date().toISOString(),
|
|
9
|
+
parameters: [{ name: 'Additional parameter', value: 'Additional value' }],
|
|
10
|
+
});
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Force push cached data
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
await this.reteno.forcePushData();
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
On iOS this uses a technical `logEvent(..., forcePush: true)` call under the hood.
|
|
20
|
+
|
|
21
|
+
## Log screen view events
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
await this.reteno.logScreenView('HomeScreen');
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Lifecycle tracking options
|
|
28
|
+
|
|
29
|
+
See configuration and examples in:
|
|
30
|
+
|
|
31
|
+
- [AppLifeCycleEvents/README.md](../AppLifeCycleEvents/README.md)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Tracking user information
|
|
2
|
+
|
|
3
|
+
Use `AwesomeCordovaPluginReteno` service methods.
|
|
4
|
+
|
|
5
|
+
## Set user attributes (identified user)
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
await this.reteno.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
|
+
```
|
|
33
|
+
|
|
34
|
+
## Set anonymous user attributes
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
await this.reteno.setAnonymousUserAttributes({
|
|
38
|
+
firstName: 'John',
|
|
39
|
+
lastName: 'Doe',
|
|
40
|
+
languageCode: 'en',
|
|
41
|
+
timeZone: 'Europe/Kyiv',
|
|
42
|
+
marketId: 'market_1',
|
|
43
|
+
address: {
|
|
44
|
+
region: 'Kyivska',
|
|
45
|
+
town: 'Kyiv',
|
|
46
|
+
address: 'Khreshchatyk St, 1',
|
|
47
|
+
postcode: '01001',
|
|
48
|
+
},
|
|
49
|
+
fields: [{ key: 'utm_source', value: 'google' }],
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Anonymous attributes cannot include `phone` or `email`.
|
|
54
|
+
|
|
55
|
+
## Multi-account mode
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
await this.reteno.setMultiAccountUserAttributes({
|
|
59
|
+
externalUserId: 'user-123',
|
|
60
|
+
user: {
|
|
61
|
+
userAttributes: {
|
|
62
|
+
email: 'john.doe@example.com',
|
|
63
|
+
firstName: 'John',
|
|
64
|
+
lastName: 'Doe',
|
|
65
|
+
marketId: 'market_1',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Market ID
|
|
72
|
+
|
|
73
|
+
`marketId` requires `cordova-plugin-reteno@2.1.0`. The
|
|
74
|
+
`awesome-cordova-plugins-reteno@9.2.0` wrapper includes its TypeScript declaration. It is
|
|
75
|
+
supported by `setUserAttributes`, `setAnonymousUserAttributes`, and
|
|
76
|
+
`setMultiAccountUserAttributes`.
|
|
77
|
+
|
|
78
|
+
- The value can contain up to 64 Latin letters, digits, hyphens (`-`), and underscores (`_`).
|
|
79
|
+
- Pass an empty string to clear the existing value:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
await this.reteno.setUserAttributes({
|
|
83
|
+
externalUserId: 'USER_ID',
|
|
84
|
+
user: {
|
|
85
|
+
userAttributes: {
|
|
86
|
+
marketId: '',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- Omit `marketId` to keep the existing value unchanged.
|
|
93
|
+
- 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`.
|
|
94
|
+
|
|
95
|
+
## Notes
|
|
96
|
+
|
|
97
|
+
- `languageCode`: RFC 5646 (example: `de-AT`)
|
|
98
|
+
- `timeZone`: TZ database value (example: `Europe/Kyiv`)
|
package/package.json
CHANGED
package/plugin.xml
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
<?xml version='1.0' encoding='utf-8'?>
|
|
2
|
-
<plugin id="cordova-plugin-reteno" version="2.2.
|
|
2
|
+
<plugin id="cordova-plugin-reteno" version="2.2.2" 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" />
|
|
6
6
|
<engine name="cordova-ios" version=">=8.0.0" />
|
|
7
7
|
</engines>
|
|
8
|
+
<preference name="RETENO_ACCESS_KEY" default="MISSING" />
|
|
9
|
+
<!-- Deprecated compatibility alias. Use RETENO_ACCESS_KEY for new integrations. -->
|
|
8
10
|
<preference name="SDK_ACCESS_KEY" default="MISSING" />
|
|
9
11
|
<preference name="IOS_DEVICE_TOKEN_HANDLING_MODE" default="manual" />
|
|
10
12
|
<hook type="before_plugin_install" src="scripts/validate-install.js" />
|
|
11
13
|
<hook type="after_plugin_install" src="scripts/android-fcm-setup.js" />
|
|
12
14
|
<hook type="after_platform_add" src="scripts/android-fcm-setup.js" />
|
|
13
15
|
<hook type="after_prepare" src="scripts/android-fcm-setup.js" />
|
|
16
|
+
<hook type="before_plugin_install" src="scripts/ios-podfile-use-modular-headers.js" />
|
|
17
|
+
<hook type="before_prepare" src="scripts/ios-podfile-use-modular-headers.js" />
|
|
14
18
|
<js-module name="cordova-plugin-reteno" src="www/cordova-plugin-reteno.js">
|
|
15
19
|
<clobbers target="RetenoPlugin" />
|
|
16
20
|
</js-module>
|
|
@@ -21,6 +25,8 @@
|
|
|
21
25
|
</config-file>
|
|
22
26
|
|
|
23
27
|
<config-file target="AndroidManifest.xml" parent="/manifest/application">
|
|
28
|
+
<meta-data android:name="com.reteno.RETENO_ACCESS_KEY" android:value="$RETENO_ACCESS_KEY" />
|
|
29
|
+
<!-- Deprecated compatibility alias. -->
|
|
24
30
|
<meta-data android:name="com.reteno.SDK_ACCESS_KEY" android:value="$SDK_ACCESS_KEY" />
|
|
25
31
|
<provider
|
|
26
32
|
android:name="com.reteno.plugin.RetenoNotificationGroupingRuleProvider"
|
|
@@ -67,12 +73,24 @@
|
|
|
67
73
|
</config-file>
|
|
68
74
|
|
|
69
75
|
<podspec>
|
|
76
|
+
<!--
|
|
77
|
+
Empty <config/> is required even without <source> entries: cordova-ios's
|
|
78
|
+
Api.js#removePodSpecs does `Object.keys(obj.sources)` unconditionally (unlike
|
|
79
|
+
addPodSpecs, which guards it) and crashes with "Cannot convert undefined or
|
|
80
|
+
null to object" on `cordova plugin rm` when <config> is absent, since
|
|
81
|
+
PluginInfo#getPodSpecs() then returns `sources: undefined` instead of `{}`.
|
|
82
|
+
That crash aborts the uninstall before it finishes, which then leaves iOS
|
|
83
|
+
plugin bookkeeping (plugins/ios.json) stale so a later reinstall thinks the
|
|
84
|
+
plugin is "already installed" and skips re-copying its www/ bridge file.
|
|
85
|
+
-->
|
|
86
|
+
<config></config>
|
|
70
87
|
<pods>
|
|
71
|
-
<pod name="Reteno" spec="2.7.
|
|
88
|
+
<pod name="Reteno" spec="2.7.6" />
|
|
72
89
|
<pod name="FirebaseMessaging" />
|
|
73
90
|
</pods>
|
|
74
91
|
</podspec>
|
|
75
92
|
|
|
76
93
|
<source-file src="src/ios/RetenoPlugin.swift" />
|
|
94
|
+
<source-file src="src/ios/RetenoPluginBootstrap.m" />
|
|
77
95
|
</platform>
|
|
78
96
|
</plugin>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Cordova hook: add `use_modular_headers!` to platforms/ios/Podfile.
|
|
3
|
+
*
|
|
4
|
+
* cordova-ios auto-generates the Podfile and does not expose a config.xml/plugin.xml
|
|
5
|
+
* knob for arbitrary CocoaPods directives other than `use_frameworks!` and
|
|
6
|
+
* `inhibit_all_warnings!` (see cordova-ios/lib/Podfile.js declarationRegexpMap).
|
|
7
|
+
* Cordova links pods statically by default (no `use_frameworks!`), and recent
|
|
8
|
+
* Firebase pods (pulled in via `pod 'FirebaseMessaging'`) depend on GoogleUtilities,
|
|
9
|
+
* which does not define modules -- `pod install` refuses to proceed under static
|
|
10
|
+
* linking unless `use_modular_headers!` is set.
|
|
11
|
+
*
|
|
12
|
+
* The generated Podfile is fully rewritten on every `prepare`, but cordova-ios seeds
|
|
13
|
+
* its in-memory declarations by parsing whatever already sits between the
|
|
14
|
+
* `platform :ios, 'x.y'` and `target 'App' do` lines (any non-comment line there is
|
|
15
|
+
* treated as a raw declaration and re-emitted verbatim). So patching the file before
|
|
16
|
+
* cordova-ios's own prepare runs (before_prepare, ahead of `preparePlatforms`) is
|
|
17
|
+
* enough for our line to survive the regeneration.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
const DECLARATION = 'use_modular_headers!';
|
|
24
|
+
|
|
25
|
+
function exists(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
fs.accessSync(filePath, fs.constants.F_OK);
|
|
28
|
+
return true;
|
|
29
|
+
} catch (_) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function alreadyPresent(text) {
|
|
35
|
+
return new RegExp(`^\\s*${DECLARATION}\\s*$`, 'm').test(text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function insertIntoExisting(text) {
|
|
39
|
+
const platformLineRe = /(platform :ios,\s*'[^']+'\s*\n)/;
|
|
40
|
+
if (!platformLineRe.test(text)) return null;
|
|
41
|
+
|
|
42
|
+
return text.replace(platformLineRe, `$1${DECLARATION}\n`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function seedTemplate() {
|
|
46
|
+
return (
|
|
47
|
+
'# DO NOT MODIFY -- auto-generated by Apache Cordova\n' +
|
|
48
|
+
"platform :ios, '15.0'\n" +
|
|
49
|
+
`${DECLARATION}\n` +
|
|
50
|
+
"target 'App' do\n" +
|
|
51
|
+
"\tproject 'App.xcodeproj'\n" +
|
|
52
|
+
'end\n'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = function (context) {
|
|
57
|
+
const opts = (context && context.opts) || {};
|
|
58
|
+
// HooksRunner.fire() normalizes `opts.cordova.platforms` for every hook type
|
|
59
|
+
// (see cordova-lib/src/hooks/HooksRunner.js). `opts.platforms` is only ever set
|
|
60
|
+
// by the `prepare` command itself (before_prepare/after_prepare) -- for
|
|
61
|
+
// before_plugin_install/after_plugin_install (fired during `cordova plugin add`,
|
|
62
|
+
// including plugin restoration onto a freshly added platform) the platform list
|
|
63
|
+
// only exists at `opts.cordova.platforms`. Check both so this hook actually runs
|
|
64
|
+
// on every path that can trigger `pod install`.
|
|
65
|
+
const platformsList = (opts.cordova && opts.cordova.platforms) || opts.platforms || [];
|
|
66
|
+
if (!platformsList.includes('ios')) return;
|
|
67
|
+
|
|
68
|
+
const projectRoot =
|
|
69
|
+
opts.projectRoot ||
|
|
70
|
+
(opts.cordova && opts.cordova.project && opts.cordova.project.root) ||
|
|
71
|
+
null;
|
|
72
|
+
|
|
73
|
+
if (!projectRoot) return;
|
|
74
|
+
|
|
75
|
+
const iosPlatformDir = path.join(projectRoot, 'platforms', 'ios');
|
|
76
|
+
if (!exists(iosPlatformDir)) return;
|
|
77
|
+
|
|
78
|
+
const podfilePath = path.join(iosPlatformDir, 'Podfile');
|
|
79
|
+
|
|
80
|
+
let result;
|
|
81
|
+
let action;
|
|
82
|
+
|
|
83
|
+
if (exists(podfilePath)) {
|
|
84
|
+
const original = fs.readFileSync(podfilePath, 'utf8');
|
|
85
|
+
if (alreadyPresent(original)) {
|
|
86
|
+
action = 'already-present';
|
|
87
|
+
result = original;
|
|
88
|
+
} else {
|
|
89
|
+
const patched = insertIntoExisting(original);
|
|
90
|
+
if (!patched) {
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.log('cordova-plugin-reteno: iOS setup: Podfile format not recognized, skipping use_modular_headers! patch.');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
action = 'patched';
|
|
96
|
+
result = patched;
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
action = 'seeded';
|
|
100
|
+
result = seedTemplate();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (action !== 'already-present') {
|
|
104
|
+
fs.writeFileSync(podfilePath, result, 'utf8');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// eslint-disable-next-line no-console
|
|
108
|
+
console.log(`cordova-plugin-reteno: iOS setup: Podfile use_modular_headers!=${action}.`);
|
|
109
|
+
};
|
|
@@ -79,17 +79,45 @@ module.exports = function (context) {
|
|
|
79
79
|
(opts.options && opts.options.options && opts.options.options.cli_variables) ||
|
|
80
80
|
{};
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
function firstValidValue(candidates) {
|
|
83
|
+
for (const candidate of candidates) {
|
|
84
|
+
const value = candidate == null ? '' : String(candidate).trim();
|
|
85
|
+
if (
|
|
86
|
+
value.length > 0 &&
|
|
87
|
+
value !== 'MISSING' &&
|
|
88
|
+
value !== '$RETENO_ACCESS_KEY' &&
|
|
89
|
+
value !== '$SDK_ACCESS_KEY'
|
|
90
|
+
) {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
88
96
|
|
|
89
|
-
|
|
97
|
+
const retenoAccessKey = firstValidValue([
|
|
98
|
+
variables.RETENO_ACCESS_KEY,
|
|
99
|
+
readVarFromCmdLine('RETENO_ACCESS_KEY'),
|
|
100
|
+
readVarFromPackageJson('RETENO_ACCESS_KEY'),
|
|
101
|
+
process.env.RETENO_ACCESS_KEY,
|
|
102
|
+
]);
|
|
103
|
+
const legacyAccessKey = firstValidValue([
|
|
104
|
+
variables.SDK_ACCESS_KEY,
|
|
105
|
+
readVarFromCmdLine('SDK_ACCESS_KEY'),
|
|
106
|
+
readVarFromPackageJson('SDK_ACCESS_KEY'),
|
|
107
|
+
process.env.SDK_ACCESS_KEY,
|
|
108
|
+
]);
|
|
109
|
+
const accessKey = retenoAccessKey || legacyAccessKey;
|
|
110
|
+
|
|
111
|
+
if (!accessKey) {
|
|
90
112
|
throw new Error(
|
|
91
|
-
'cordova-plugin-reteno:
|
|
92
|
-
'cordova plugin add cordova-plugin-reteno --variable
|
|
113
|
+
'cordova-plugin-reteno: RETENO_ACCESS_KEY is required. Install with: ' +
|
|
114
|
+
'cordova plugin add cordova-plugin-reteno --variable RETENO_ACCESS_KEY=YOUR_KEY'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!retenoAccessKey && legacyAccessKey) {
|
|
119
|
+
console.warn(
|
|
120
|
+
'cordova-plugin-reteno: SDK_ACCESS_KEY is deprecated; use RETENO_ACCESS_KEY instead.'
|
|
93
121
|
);
|
|
94
122
|
}
|
|
95
123
|
};
|
|
@@ -68,7 +68,8 @@ import java.util.Map;
|
|
|
68
68
|
public class RetenoPlugin extends CordovaPlugin {
|
|
69
69
|
private static final int REQ_CODE_POST_NOTIFICATIONS = 10001;
|
|
70
70
|
private static final String PERMISSION_POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
|
|
71
|
-
private static final String
|
|
71
|
+
private static final String RETENO_ACCESS_KEY_META = "com.reteno.RETENO_ACCESS_KEY";
|
|
72
|
+
private static final String LEGACY_SDK_ACCESS_KEY_META = "com.reteno.SDK_ACCESS_KEY";
|
|
72
73
|
|
|
73
74
|
private static volatile RetenoPlugin activeInstance;
|
|
74
75
|
|
|
@@ -561,9 +562,9 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
561
562
|
}
|
|
562
563
|
if (TextUtils.isEmpty(accessKey)) {
|
|
563
564
|
callbackContext.error(
|
|
564
|
-
"Missing
|
|
565
|
-
"(e.g. --variable
|
|
566
|
-
"or set AndroidManifest meta-data 'com.reteno.
|
|
565
|
+
"Missing RETENO_ACCESS_KEY. Provide it when installing the Cordova plugin " +
|
|
566
|
+
"(e.g. --variable RETENO_ACCESS_KEY=YOUR_KEY), pass it to RetenoPlugin.init({accessKey: ...}), " +
|
|
567
|
+
"or set AndroidManifest meta-data 'com.reteno.RETENO_ACCESS_KEY'."
|
|
567
568
|
);
|
|
568
569
|
return;
|
|
569
570
|
}
|
|
@@ -779,7 +780,9 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
779
780
|
if (options == null) {
|
|
780
781
|
return null;
|
|
781
782
|
}
|
|
782
|
-
String[] keys = new String[] {
|
|
783
|
+
String[] keys = new String[] {
|
|
784
|
+
"accessKey", "access_key", "retenoAccessKey", "RETENO_ACCESS_KEY", "sdkAccessKey", "SDK_ACCESS_KEY"
|
|
785
|
+
};
|
|
783
786
|
for (String keyName : keys) {
|
|
784
787
|
String raw = options.optString(keyName, null);
|
|
785
788
|
String normalized = normalizeAccessKey(raw);
|
|
@@ -1246,6 +1249,12 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
1246
1249
|
if (extras == null || extras.isEmpty()) {
|
|
1247
1250
|
return;
|
|
1248
1251
|
}
|
|
1252
|
+
// Reteno uses the interaction id as the canonical identifier for push interactions.
|
|
1253
|
+
// App/deep-link SDK extras (for example Branch) must not be exposed as an initial
|
|
1254
|
+
// Reteno notification.
|
|
1255
|
+
if (!extras.containsKey("es_interaction_id")) {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1249
1258
|
|
|
1250
1259
|
initialNotification = RetenoUtil.bundleToJson(extras);
|
|
1251
1260
|
}
|
|
@@ -1262,12 +1271,18 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
1262
1271
|
if (info == null || info.metaData == null) {
|
|
1263
1272
|
return readAccessKeyFromCordovaPreferences();
|
|
1264
1273
|
}
|
|
1265
|
-
String key = info.metaData.getString(
|
|
1274
|
+
String key = info.metaData.getString(RETENO_ACCESS_KEY_META);
|
|
1266
1275
|
String normalized = normalizeAccessKey(key);
|
|
1267
1276
|
if (normalized != null) {
|
|
1268
1277
|
return normalized;
|
|
1269
1278
|
}
|
|
1270
1279
|
|
|
1280
|
+
key = info.metaData.getString(LEGACY_SDK_ACCESS_KEY_META);
|
|
1281
|
+
normalized = normalizeAccessKey(key);
|
|
1282
|
+
if (normalized != null) {
|
|
1283
|
+
return normalized;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1271
1286
|
return readAccessKeyFromCordovaPreferences();
|
|
1272
1287
|
} catch (Exception ignored) {
|
|
1273
1288
|
return readAccessKeyFromCordovaPreferences();
|
|
@@ -1276,8 +1291,13 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
1276
1291
|
|
|
1277
1292
|
private String readAccessKeyFromCordovaPreferences() {
|
|
1278
1293
|
try {
|
|
1279
|
-
|
|
1280
|
-
|
|
1294
|
+
if (this.preferences == null) {
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
String key = normalizeAccessKey(this.preferences.getString("RETENO_ACCESS_KEY", null));
|
|
1298
|
+
return key != null
|
|
1299
|
+
? key
|
|
1300
|
+
: normalizeAccessKey(this.preferences.getString("SDK_ACCESS_KEY", null));
|
|
1281
1301
|
} catch (Exception ignored) {
|
|
1282
1302
|
return null;
|
|
1283
1303
|
}
|
|
@@ -1288,7 +1308,7 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
1288
1308
|
return null;
|
|
1289
1309
|
}
|
|
1290
1310
|
key = key.trim();
|
|
1291
|
-
if (key.length() == 0 || "$SDK_ACCESS_KEY".equals(key) || "MISSING".equals(key)) {
|
|
1311
|
+
if (key.length() == 0 || "$RETENO_ACCESS_KEY".equals(key) || "$SDK_ACCESS_KEY".equals(key) || "MISSING".equals(key)) {
|
|
1292
1312
|
return null;
|
|
1293
1313
|
}
|
|
1294
1314
|
return key;
|
|
@@ -5,7 +5,7 @@ allprojects {
|
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
def retenoFcmVersion = '2.10.
|
|
8
|
+
def retenoFcmVersion = '2.10.2'
|
|
9
9
|
|
|
10
10
|
def firebaseMessagingVersion = '23.1.0'
|
|
11
11
|
|
|
@@ -16,7 +16,8 @@ dependencies {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
|
-
// Capacitor: auto-substitute
|
|
19
|
+
// Capacitor: auto-substitute RETENO_ACCESS_KEY (or its legacy alias) in the
|
|
20
|
+
// merged plugin manifest.
|
|
20
21
|
//
|
|
21
22
|
// TODO: Migrate to a dedicated Gradle task with declared inputs/outputs when
|
|
22
23
|
// Configuration Cache becomes the default in Capacitor. Running file I/O
|
|
@@ -24,8 +25,8 @@ dependencies {
|
|
|
24
25
|
// executes on every Gradle invocation (including `./gradlew tasks`).
|
|
25
26
|
//
|
|
26
27
|
// Capacitor does NOT resolve $VARIABLE placeholders from plugin.xml; it uses
|
|
27
|
-
// the <preference default="…"> value instead.
|
|
28
|
-
// is "MISSING", which makes the SDK fail at runtime.
|
|
28
|
+
// the <preference default="…"> value instead. For access-key preferences the
|
|
29
|
+
// default is "MISSING", which makes the SDK fail at runtime.
|
|
29
30
|
//
|
|
30
31
|
// This block reads the real value from capacitor.config.json (generated by
|
|
31
32
|
// Capacitor CLI from capacitor.config.ts) and patches the manifest source
|
|
@@ -49,7 +50,7 @@ try {
|
|
|
49
50
|
new File(project.projectDir, 'src/main/AndroidManifest.xml'), // when applied from the plugin module itself
|
|
50
51
|
]
|
|
51
52
|
for (def f : manifestCandidates) {
|
|
52
|
-
if (f.exists() && f.text.contains('com.reteno.SDK_ACCESS_KEY')) {
|
|
53
|
+
if (f.exists() && (f.text.contains('com.reteno.RETENO_ACCESS_KEY') || f.text.contains('com.reteno.SDK_ACCESS_KEY'))) {
|
|
53
54
|
capPluginManifest = f
|
|
54
55
|
break
|
|
55
56
|
}
|
|
@@ -71,16 +72,25 @@ try {
|
|
|
71
72
|
def slurper = new groovy.json.JsonSlurper()
|
|
72
73
|
def config = slurper.parse(capConfigJson)
|
|
73
74
|
def prefs = config?.cordova?.preferences
|
|
74
|
-
def
|
|
75
|
+
def normalizeAccessKey = { value ->
|
|
76
|
+
def key = value?.toString()?.trim()
|
|
77
|
+
return key != null && key.length() > 0 && key != 'MISSING' && key != '$RETENO_ACCESS_KEY' && key != '$SDK_ACCESS_KEY'
|
|
78
|
+
? key
|
|
79
|
+
: null
|
|
80
|
+
}
|
|
81
|
+
def retenoAccessKey = normalizeAccessKey(prefs?.RETENO_ACCESS_KEY)
|
|
82
|
+
def legacyAccessKey = normalizeAccessKey(prefs?.SDK_ACCESS_KEY)
|
|
83
|
+
def accessKey = retenoAccessKey ?: legacyAccessKey
|
|
84
|
+
def manifestKey = retenoAccessKey != null ? 'RETENO_ACCESS_KEY' : 'SDK_ACCESS_KEY'
|
|
75
85
|
|
|
76
|
-
if (accessKey != null
|
|
86
|
+
if (accessKey != null) {
|
|
77
87
|
def manifestText = capPluginManifest.text
|
|
78
|
-
def pattern = ~/(<meta-data\s+android:name="com\.reteno
|
|
88
|
+
def pattern = ~/(<meta-data\s+android:name="com\.reteno\.${manifestKey}"\s+android:value=")([^"]*?)(")/
|
|
79
89
|
def matcher = pattern.matcher(manifestText)
|
|
80
90
|
if (matcher.find() && matcher.group(2) != accessKey) {
|
|
81
91
|
def patched = matcher.replaceFirst('$1' + java.util.regex.Matcher.quoteReplacement(accessKey) + '$3')
|
|
82
92
|
capPluginManifest.text = patched
|
|
83
|
-
logger.lifecycle("cordova-plugin-reteno: Patched
|
|
93
|
+
logger.lifecycle("cordova-plugin-reteno: Patched ${manifestKey} in Capacitor plugin manifest.")
|
|
84
94
|
}
|
|
85
95
|
}
|
|
86
96
|
}
|
|
@@ -11,6 +11,7 @@ import FirebaseMessaging
|
|
|
11
11
|
@objc(RetenoPlugin)
|
|
12
12
|
class RetenoPlugin: CDVPlugin {
|
|
13
13
|
private static weak var activeInstance: RetenoPlugin?
|
|
14
|
+
private static var hasStartedDelayedInitialization = false
|
|
14
15
|
private var inboxCountCallbackId: String?
|
|
15
16
|
private var isManualTokenMode = false
|
|
16
17
|
#if canImport(FirebaseMessaging)
|
|
@@ -21,6 +22,10 @@ class RetenoPlugin: CDVPlugin {
|
|
|
21
22
|
|
|
22
23
|
override func pluginInitialize() {
|
|
23
24
|
RetenoPlugin.activeInstance = self
|
|
25
|
+
// The Objective-C bootstrap normally calls this from AppDelegate's
|
|
26
|
+
// didFinishLaunchingWithOptions. Keep this call as an idempotent fallback
|
|
27
|
+
// for unusual hosts that replace or bypass UIApplication's delegate setup.
|
|
28
|
+
RetenoPlugin.delayedStartIfNeeded()
|
|
24
29
|
#if canImport(FirebaseMessaging)
|
|
25
30
|
if let messaging = messagingIfAvailable() {
|
|
26
31
|
messaging.delegate = self
|
|
@@ -28,6 +33,15 @@ class RetenoPlugin: CDVPlugin {
|
|
|
28
33
|
#endif
|
|
29
34
|
}
|
|
30
35
|
|
|
36
|
+
/// Called by RetenoPluginBootstrap from didFinishLaunchingWithOptions.
|
|
37
|
+
/// The Objective-C entry point lets Cordova and Capacitor start notification
|
|
38
|
+
/// response buffering before either framework lazily creates the plugin.
|
|
39
|
+
@objc static func delayedStartIfNeeded() {
|
|
40
|
+
guard !hasStartedDelayedInitialization else { return }
|
|
41
|
+
hasStartedDelayedInitialization = true
|
|
42
|
+
Reteno.delayedStart()
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
override func onAppTerminate() {
|
|
32
46
|
if RetenoPlugin.activeInstance === self {
|
|
33
47
|
RetenoPlugin.activeInstance = nil
|
|
@@ -42,12 +56,14 @@ class RetenoPlugin: CDVPlugin {
|
|
|
42
56
|
func initialize(_ command: CDVInvokedUrlCommand) {
|
|
43
57
|
let options = (command.arguments.first as? [String: Any]) ?? [:]
|
|
44
58
|
|
|
45
|
-
let providedKey = (options["accessKey"] as? String)
|
|
46
|
-
let
|
|
47
|
-
let
|
|
59
|
+
let providedKey = normalizedAccessKey(options["accessKey"] as? String)
|
|
60
|
+
let retenoPreferenceKey = normalizedAccessKey(getPreference("reteno_access_key"))
|
|
61
|
+
let legacyPreferenceKey = normalizedAccessKey(getPreference("sdk_access_key"))
|
|
62
|
+
let preferenceKey = retenoPreferenceKey ?? legacyPreferenceKey
|
|
63
|
+
let accessKey = providedKey ?? preferenceKey
|
|
48
64
|
|
|
49
65
|
guard let apiKey = accessKey, !apiKey.isEmpty else {
|
|
50
|
-
sendError("Missing SDK access key. Provide options.accessKey or set preference
|
|
66
|
+
sendError("Missing SDK access key. Provide options.accessKey or set preference RETENO_ACCESS_KEY.", to: command)
|
|
51
67
|
return
|
|
52
68
|
}
|
|
53
69
|
|
|
@@ -131,7 +147,12 @@ class RetenoPlugin: CDVPlugin {
|
|
|
131
147
|
self.isManualTokenMode = (deviceTokenMode == .manual)
|
|
132
148
|
|
|
133
149
|
DispatchQueue.main.async {
|
|
134
|
-
|
|
150
|
+
// delayedSetup (paired with delayedStartIfNeeded() during native bootstrap)
|
|
151
|
+
// completes initialization and replays any push response buffered since
|
|
152
|
+
// launch -- e.g. a push-linked in-app opened while the SDK was still
|
|
153
|
+
// cold-starting. Requires delayedStart() to have run first, or this is a
|
|
154
|
+
// silent no-op (see Reteno.delayedSetup's IsDelayedInitialization guard).
|
|
155
|
+
Reteno.delayedSetup(apiKey: apiKey, deviceTokenHandlingMode: deviceTokenMode, configuration: configuration)
|
|
135
156
|
|
|
136
157
|
// When IOS_DEVICE_TOKEN_HANDLING_MODE is manual and FirebaseMessaging is available,
|
|
137
158
|
// configure Firebase if the developer hasn't done it yet, then hook FCM
|
|
@@ -1331,6 +1352,17 @@ class RetenoPlugin: CDVPlugin {
|
|
|
1331
1352
|
return nil
|
|
1332
1353
|
}
|
|
1333
1354
|
|
|
1355
|
+
private func normalizedAccessKey(_ key: String?) -> String? {
|
|
1356
|
+
guard let value = key?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
1357
|
+
!value.isEmpty,
|
|
1358
|
+
value != "MISSING",
|
|
1359
|
+
value != "$RETENO_ACCESS_KEY",
|
|
1360
|
+
value != "$SDK_ACCESS_KEY" else {
|
|
1361
|
+
return nil
|
|
1362
|
+
}
|
|
1363
|
+
return value
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1334
1366
|
private func sendError(_ message: String, to command: CDVInvokedUrlCommand) {
|
|
1335
1367
|
let result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: message)
|
|
1336
1368
|
commandDelegate.send(result, callbackId: command.callbackId)
|