cordova-plugin-reteno 2.2.0 → 2.2.1
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 +4 -0
- 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 +124 -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 +128 -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 +23 -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.1" 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.4" />
|
|
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);
|
|
@@ -1262,12 +1265,18 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
1262
1265
|
if (info == null || info.metaData == null) {
|
|
1263
1266
|
return readAccessKeyFromCordovaPreferences();
|
|
1264
1267
|
}
|
|
1265
|
-
String key = info.metaData.getString(
|
|
1268
|
+
String key = info.metaData.getString(RETENO_ACCESS_KEY_META);
|
|
1266
1269
|
String normalized = normalizeAccessKey(key);
|
|
1267
1270
|
if (normalized != null) {
|
|
1268
1271
|
return normalized;
|
|
1269
1272
|
}
|
|
1270
1273
|
|
|
1274
|
+
key = info.metaData.getString(LEGACY_SDK_ACCESS_KEY_META);
|
|
1275
|
+
normalized = normalizeAccessKey(key);
|
|
1276
|
+
if (normalized != null) {
|
|
1277
|
+
return normalized;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1271
1280
|
return readAccessKeyFromCordovaPreferences();
|
|
1272
1281
|
} catch (Exception ignored) {
|
|
1273
1282
|
return readAccessKeyFromCordovaPreferences();
|
|
@@ -1276,8 +1285,13 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
1276
1285
|
|
|
1277
1286
|
private String readAccessKeyFromCordovaPreferences() {
|
|
1278
1287
|
try {
|
|
1279
|
-
|
|
1280
|
-
|
|
1288
|
+
if (this.preferences == null) {
|
|
1289
|
+
return null;
|
|
1290
|
+
}
|
|
1291
|
+
String key = normalizeAccessKey(this.preferences.getString("RETENO_ACCESS_KEY", null));
|
|
1292
|
+
return key != null
|
|
1293
|
+
? key
|
|
1294
|
+
: normalizeAccessKey(this.preferences.getString("SDK_ACCESS_KEY", null));
|
|
1281
1295
|
} catch (Exception ignored) {
|
|
1282
1296
|
return null;
|
|
1283
1297
|
}
|
|
@@ -1288,7 +1302,7 @@ public class RetenoPlugin extends CordovaPlugin {
|
|
|
1288
1302
|
return null;
|
|
1289
1303
|
}
|
|
1290
1304
|
key = key.trim();
|
|
1291
|
-
if (key.length() == 0 || "$SDK_ACCESS_KEY".equals(key) || "MISSING".equals(key)) {
|
|
1305
|
+
if (key.length() == 0 || "$RETENO_ACCESS_KEY".equals(key) || "$SDK_ACCESS_KEY".equals(key) || "MISSING".equals(key)) {
|
|
1292
1306
|
return null;
|
|
1293
1307
|
}
|
|
1294
1308
|
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)
|