@amplytools/react-native-amply-sdk 0.1.0 → 0.1.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 +33 -673
- package/android/build.gradle +1 -1
- package/android/src/main/java/tools/amply/sdk/reactnative/AmplyModule.kt +32 -1
- package/android/src/main/java/tools/amply/sdk/reactnative/core/AmplyClient.kt +5 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/core/DefaultAmplyClient.kt +49 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/AmplyInitializationOptions.kt +35 -1
- package/dist/src/index.d.ts +14 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +67 -0
- package/dist/src/nativeSpecs/NativeAmplyModule.d.ts +29 -0
- package/dist/src/nativeSpecs/NativeAmplyModule.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/DEVELOPMENT.md +177 -0
- package/docs/FEATURE_DEBUG.md +326 -0
- package/ios/Sources/AmplyReactNative/AmplyModule.mm +113 -1
- package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative-generated.mm +16 -2
- package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative.h +14 -0
- package/package.json +6 -7
- package/src/index.ts +78 -0
- package/src/nativeSpecs/NativeAmplyModule.ts +30 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Development Guide
|
|
2
|
+
|
|
3
|
+
This guide is for SDK developers contributing to the Amply React Native SDK.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/ # TypeScript source (JS side)
|
|
9
|
+
├── nativeSpecs/
|
|
10
|
+
│ └── NativeAmplyModule.ts # TurboModule spec (codegen source)
|
|
11
|
+
├── index.ts # Public API exports
|
|
12
|
+
├── nativeModule.ts # Native module loader with fallbacks
|
|
13
|
+
└── systemEvents.ts # Event emitter helpers
|
|
14
|
+
|
|
15
|
+
android/ # Android native code
|
|
16
|
+
├── src/main/
|
|
17
|
+
│ ├── java/tools/amply/sdk/reactnative/
|
|
18
|
+
│ │ ├── AmplyModule.kt # TurboModule implementation
|
|
19
|
+
│ │ ├── AmplyPackage.kt # Package for React Native
|
|
20
|
+
│ │ └── core/ # Amply SDK wrapper
|
|
21
|
+
│ └── jni/
|
|
22
|
+
│ └── CMakeLists.txt # C++ build config
|
|
23
|
+
└── src/newarch/ # Auto-generated codegen artifacts
|
|
24
|
+
|
|
25
|
+
ios/ # iOS native code
|
|
26
|
+
├── AmplyReactNative.podspec # CocoaPods spec
|
|
27
|
+
└── Sources/AmplyReactNative/
|
|
28
|
+
├── AmplyModule.mm # TurboModule implementation
|
|
29
|
+
└── AmplyReactNative/ # Codegen artifacts (committed)
|
|
30
|
+
├── AmplyReactNative.h
|
|
31
|
+
└── AmplyReactNative-generated.mm
|
|
32
|
+
|
|
33
|
+
example/bare/ # Bare RN example app
|
|
34
|
+
example/expo/ # Expo example app
|
|
35
|
+
|
|
36
|
+
plugin/ # Expo config plugin source
|
|
37
|
+
├── src/
|
|
38
|
+
│ ├── withAmply.ts # Main plugin logic
|
|
39
|
+
│ └── index.ts # Plugin export
|
|
40
|
+
└── build/ # Compiled plugin (generated)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Build Commands
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Build TypeScript distribution
|
|
47
|
+
yarn build
|
|
48
|
+
|
|
49
|
+
# Build Expo config plugin
|
|
50
|
+
yarn build:plugin
|
|
51
|
+
|
|
52
|
+
# Run tests
|
|
53
|
+
yarn test
|
|
54
|
+
|
|
55
|
+
# Lint and type check
|
|
56
|
+
yarn lint && yarn typecheck
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Codegen
|
|
60
|
+
|
|
61
|
+
This SDK uses React Native TurboModules. When you modify the TypeScript spec (`src/nativeSpecs/NativeAmplyModule.ts`), the native codegen artifacts must be updated.
|
|
62
|
+
|
|
63
|
+
**Android:** Codegen runs automatically during Gradle build. No manual steps needed.
|
|
64
|
+
|
|
65
|
+
**iOS:** The committed codegen files must be manually updated:
|
|
66
|
+
- `ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative.h`
|
|
67
|
+
- `ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative-generated.mm`
|
|
68
|
+
|
|
69
|
+
When adding new methods or config fields to the spec:
|
|
70
|
+
1. Add the method/field declaration to the header struct
|
|
71
|
+
2. Add the inline implementation in the header
|
|
72
|
+
3. Add the host function and method mapping in the generated .mm file
|
|
73
|
+
4. Run `expo prebuild --clean` in example app to verify
|
|
74
|
+
|
|
75
|
+
## Sample Apps
|
|
76
|
+
|
|
77
|
+
The SDK includes two example apps for testing during development.
|
|
78
|
+
|
|
79
|
+
### Expo Sample (`example/expo`)
|
|
80
|
+
|
|
81
|
+
Recommended for fast iteration with hot reload.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cd example/expo
|
|
85
|
+
yarn install
|
|
86
|
+
expo prebuild --clean
|
|
87
|
+
expo start # Terminal 1
|
|
88
|
+
expo run:android # Terminal 2 (Android)
|
|
89
|
+
expo run:ios # Terminal 2 (iOS)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Bare React Native Sample (`example/bare`)
|
|
93
|
+
|
|
94
|
+
For production-like testing and intent filter validation.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
cd example/bare
|
|
98
|
+
yarn install
|
|
99
|
+
yarn react-native run-android # Android
|
|
100
|
+
yarn react-native run-ios # iOS
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Local SDK Development
|
|
104
|
+
|
|
105
|
+
Sample apps use `link:` protocol for local SDK development:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"dependencies": {
|
|
110
|
+
"@amplytools/react-native-amply-sdk": "link:../.."
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This allows immediate testing of SDK changes without npm reinstall.
|
|
116
|
+
|
|
117
|
+
> **Note:** Regular app developers should install from npm: `yarn add @amplytools/react-native-amply-sdk`
|
|
118
|
+
|
|
119
|
+
## Development Workflow
|
|
120
|
+
|
|
121
|
+
### 1. Setup workspace
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
yarn install
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 2. Make changes
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# For TypeScript spec changes (new methods, config fields)
|
|
131
|
+
# 1. Edit the spec
|
|
132
|
+
nano src/nativeSpecs/NativeAmplyModule.ts
|
|
133
|
+
# 2. Update iOS codegen files manually (see Codegen section above)
|
|
134
|
+
# 3. Android codegen updates automatically during build
|
|
135
|
+
|
|
136
|
+
# For native implementation changes
|
|
137
|
+
nano android/src/main/java/tools/amply/sdk/reactnative/AmplyModule.kt
|
|
138
|
+
nano ios/Sources/AmplyReactNative/AmplyModule.mm
|
|
139
|
+
|
|
140
|
+
# For JS changes
|
|
141
|
+
nano src/index.ts
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 3. Test with sample apps
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Test with Expo (faster feedback)
|
|
148
|
+
cd example/expo
|
|
149
|
+
rm -rf android ios
|
|
150
|
+
expo prebuild --clean
|
|
151
|
+
expo start # Terminal 1
|
|
152
|
+
expo run:android # Terminal 2
|
|
153
|
+
|
|
154
|
+
# Validate with bare RN
|
|
155
|
+
cd ../bare
|
|
156
|
+
yarn react-native run-android
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Testing Deep Links
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# iOS Simulator
|
|
163
|
+
xcrun simctl openurl booted "amply://campaign/promo/123"
|
|
164
|
+
|
|
165
|
+
# Android
|
|
166
|
+
adb shell am start -a android.intent.action.VIEW \
|
|
167
|
+
-d "amply://campaign/promo/123" <package>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## PR Checklist
|
|
171
|
+
|
|
172
|
+
- [ ] Changes tested in both example apps
|
|
173
|
+
- [ ] `yarn lint && yarn typecheck && yarn test` passes
|
|
174
|
+
- [ ] iOS codegen files updated (if spec changed)
|
|
175
|
+
- [ ] Commit messages are clear
|
|
176
|
+
- [ ] No breaking changes (or documented)
|
|
177
|
+
- [ ] README updated if API changes
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# Debug Feature Implementation Summary
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document summarizes the implementation of the debug/logging feature for the Amply React Native SDK, as requested in `AMPLY_SDK_DEBUG_FEATURE_REQUEST.md`.
|
|
6
|
+
|
|
7
|
+
## Architecture Decision
|
|
8
|
+
|
|
9
|
+
**Chosen Approach**: Centralized logging in KMP SDK with event-based forwarding to React Native.
|
|
10
|
+
|
|
11
|
+
Instead of implementing logging separately in Android/iOS React Native wrappers, all logging originates from the KMP SDK and is forwarded to JS via the existing system events mechanism. This approach:
|
|
12
|
+
- Centralizes logging logic in one place
|
|
13
|
+
- Enables campaign evaluation logging (only possible in KMP SDK)
|
|
14
|
+
- Reduces code duplication across platforms
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Files Changed
|
|
19
|
+
|
|
20
|
+
### 1. KMP SDK (multiplatform-library-template)
|
|
21
|
+
|
|
22
|
+
#### New File: `library/src/commonMain/kotlin/tools/amply/sdk/logging/Logger.kt`
|
|
23
|
+
|
|
24
|
+
Created a complete logging infrastructure:
|
|
25
|
+
|
|
26
|
+
```kotlin
|
|
27
|
+
// Log levels
|
|
28
|
+
enum class LogLevel(val level: Int) {
|
|
29
|
+
NONE(0), ERROR(1), WARN(2), INFO(3), DEBUG(4)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Log categories for filtering
|
|
33
|
+
object LogCategory {
|
|
34
|
+
const val SDK = "sdk"
|
|
35
|
+
const val EVENT = "event"
|
|
36
|
+
const val CAMPAIGN = "campaign"
|
|
37
|
+
const val CONFIG = "config"
|
|
38
|
+
const val SESSION = "session"
|
|
39
|
+
const val DEEPLINK = "deeplink"
|
|
40
|
+
const val NETWORK = "network"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Log entry data class
|
|
44
|
+
data class LogEntry(
|
|
45
|
+
val level: LogLevel,
|
|
46
|
+
val category: String,
|
|
47
|
+
val message: String,
|
|
48
|
+
val timestamp: Long,
|
|
49
|
+
val details: Map<String, Any>? = null
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
// Listener interface for log events
|
|
53
|
+
interface LogListener {
|
|
54
|
+
fun onLog(entry: LogEntry)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Singleton logger
|
|
58
|
+
object Logger {
|
|
59
|
+
var currentLevel: LogLevel = LogLevel.NONE
|
|
60
|
+
private var listener: LogListener? = null
|
|
61
|
+
|
|
62
|
+
fun setLevel(level: LogLevel)
|
|
63
|
+
fun setListener(listener: LogListener?)
|
|
64
|
+
fun log(level: LogLevel, category: String, message: String, details: Map<String, Any>? = null)
|
|
65
|
+
|
|
66
|
+
// Convenience methods
|
|
67
|
+
fun debug(category: String, message: String, ...)
|
|
68
|
+
fun info(category: String, message: String, ...)
|
|
69
|
+
fun warn(category: String, message: String, ...)
|
|
70
|
+
fun error(category: String, message: String, ...)
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Modified: `library/src/commonMain/kotlin/tools/amply/sdk/campaigns/CampaignManager.kt`
|
|
75
|
+
|
|
76
|
+
Added campaign evaluation logging:
|
|
77
|
+
|
|
78
|
+
```kotlin
|
|
79
|
+
// In fetchCampaigns()
|
|
80
|
+
Logger.info(LogCategory.CONFIG, "Config fetch started")
|
|
81
|
+
Logger.info(LogCategory.CONFIG, "Config fetch completed: ${campaigns.size} campaigns loaded")
|
|
82
|
+
|
|
83
|
+
// In activate()
|
|
84
|
+
Logger.debug(LogCategory.CAMPAIGN, "Evaluating ${campaigns.size} campaigns for event: ${event.name}")
|
|
85
|
+
Logger.debug(LogCategory.CAMPAIGN, "Campaign \"${name}\": targeting rules not satisfied")
|
|
86
|
+
Logger.debug(LogCategory.CAMPAIGN, "Campaign \"${name}\": trigger MATCHED for event ${event.name}")
|
|
87
|
+
Logger.info(LogCategory.CAMPAIGN, "Campaign \"${name}\": MATCH - executing action")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Modified: `library/src/androidMain/kotlin/tools/amply/sdk/Amply.kt`
|
|
91
|
+
|
|
92
|
+
Added logging API methods:
|
|
93
|
+
|
|
94
|
+
```kotlin
|
|
95
|
+
fun setLogLevel(level: LogLevel)
|
|
96
|
+
fun setLogLevel(level: String?)
|
|
97
|
+
fun getLogLevel(): LogLevel
|
|
98
|
+
fun setLogListener(listener: LogListener?)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Modified: `library/src/iosMain/kotlin/tools/amply/sdk/Amply.kt`
|
|
102
|
+
|
|
103
|
+
Added same logging API methods as Android.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### 2. React Native SDK (react-native-sdk)
|
|
108
|
+
|
|
109
|
+
#### Modified: `src/nativeSpecs/NativeAmplyModule.ts`
|
|
110
|
+
|
|
111
|
+
Added TypeScript types and TurboModule methods:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// New type
|
|
115
|
+
export type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'debug';
|
|
116
|
+
|
|
117
|
+
// Extended AmplyInitializationConfig
|
|
118
|
+
export type AmplyInitializationConfig = {
|
|
119
|
+
// ... existing fields ...
|
|
120
|
+
debug?: boolean | null; // Shorthand for logLevel: 'debug'
|
|
121
|
+
logLevel?: LogLevel | null; // Takes precedence over debug
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// New methods in Spec
|
|
125
|
+
setLogLevel(level: string): void;
|
|
126
|
+
getLogLevel(): string;
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Modified: `src/index.ts`
|
|
130
|
+
|
|
131
|
+
Added JS-side debug log handling:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Debug log listener that outputs to Metro console
|
|
135
|
+
function ensureDebugLogListener(): void {
|
|
136
|
+
addSystemEventListenerInternal((event) => {
|
|
137
|
+
if (event.name === 'DebugLog') {
|
|
138
|
+
// Format and output to appropriate console method
|
|
139
|
+
const level = event.properties.level;
|
|
140
|
+
switch (level) {
|
|
141
|
+
case 'error': console.error(formattedLog); break;
|
|
142
|
+
case 'warn': console.warn(formattedLog); break;
|
|
143
|
+
case 'debug': console.debug(formattedLog); break;
|
|
144
|
+
default: console.log(formattedLog);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Exported functions
|
|
151
|
+
export function setLogLevel(level: LogLevel): void;
|
|
152
|
+
export function getLogLevel(): LogLevel;
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Modified: `android/src/main/java/tools/amply/sdk/reactnative/model/AmplyInitializationOptions.kt`
|
|
156
|
+
|
|
157
|
+
Added LogLevel enum and config parsing:
|
|
158
|
+
|
|
159
|
+
```kotlin
|
|
160
|
+
enum class LogLevel(val level: Int) {
|
|
161
|
+
NONE(0), ERROR(1), WARN(2), INFO(3), DEBUG(4);
|
|
162
|
+
|
|
163
|
+
companion object {
|
|
164
|
+
fun fromString(value: String?): LogLevel
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
data class AmplyInitializationOptions(
|
|
169
|
+
// ... existing fields ...
|
|
170
|
+
val debug: Boolean?,
|
|
171
|
+
val logLevel: LogLevel?,
|
|
172
|
+
) {
|
|
173
|
+
fun getEffectiveLogLevel(): LogLevel
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Modified: `android/src/main/java/tools/amply/sdk/reactnative/core/AmplyClient.kt`
|
|
178
|
+
|
|
179
|
+
Added interface methods:
|
|
180
|
+
|
|
181
|
+
```kotlin
|
|
182
|
+
val logEvents: SharedFlow<EventEnvelope>
|
|
183
|
+
fun setLogLevel(level: LogLevel)
|
|
184
|
+
fun getLogLevel(): LogLevel
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Modified: `android/src/main/java/tools/amply/sdk/reactnative/core/DefaultAmplyClient.kt`
|
|
188
|
+
|
|
189
|
+
- Set up LogListener during initialization to forward logs to JS
|
|
190
|
+
- Implemented setLogLevel/getLogLevel methods
|
|
191
|
+
- Added logEvents SharedFlow
|
|
192
|
+
|
|
193
|
+
#### Modified: `android/src/main/java/tools/amply/sdk/reactnative/AmplyModule.kt`
|
|
194
|
+
|
|
195
|
+
- Added log event collection job
|
|
196
|
+
- Implemented setLogLevel/getLogLevel TurboModule methods
|
|
197
|
+
- Forward log events to JS via emitOnSystemEvent
|
|
198
|
+
|
|
199
|
+
#### Modified: `ios/Sources/AmplyReactNative/AmplyModule.mm`
|
|
200
|
+
|
|
201
|
+
- Added AmplyLogLevel enum and helper functions
|
|
202
|
+
- Implemented ASDKLogListener protocol
|
|
203
|
+
- Added setLogLevel/getLogLevel methods
|
|
204
|
+
- Added registerLogListenerInternal for log forwarding
|
|
205
|
+
- Implemented ASDKSystemEventsListener for system events (fixes iOS gap)
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Usage
|
|
210
|
+
|
|
211
|
+
### Initialize with Debug Mode
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import Amply from '@anthropic/react-native-amply-sdk';
|
|
215
|
+
|
|
216
|
+
// Option 1: Simple debug flag
|
|
217
|
+
await Amply.initialize({
|
|
218
|
+
appId: 'my-app-id',
|
|
219
|
+
apiKeyPublic: 'my-public-key',
|
|
220
|
+
debug: true, // Enables all logging
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Option 2: Specific log level
|
|
224
|
+
await Amply.initialize({
|
|
225
|
+
appId: 'my-app-id',
|
|
226
|
+
apiKeyPublic: 'my-public-key',
|
|
227
|
+
logLevel: 'info', // Only info, warn, error
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Change Log Level at Runtime
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// Enable debug logging
|
|
235
|
+
Amply.setLogLevel('debug');
|
|
236
|
+
|
|
237
|
+
// Disable logging
|
|
238
|
+
Amply.setLogLevel('none');
|
|
239
|
+
|
|
240
|
+
// Check current level
|
|
241
|
+
const level = Amply.getLogLevel(); // 'debug'
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Console Output Example
|
|
245
|
+
|
|
246
|
+
When debug mode is enabled, developers see logs in Metro console:
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
[Amply INFO] [sdk] SDK initializing with appId=my-app-id
|
|
250
|
+
[Amply INFO] [config] Config fetch started
|
|
251
|
+
[Amply INFO] [config] Config fetch completed: 3 campaigns loaded
|
|
252
|
+
[Amply DEBUG] [campaign] Campaign loaded: "Welcome Offer" (camp_123)
|
|
253
|
+
[Amply DEBUG] [campaign] Evaluating 3 campaigns for event: ButtonTapped
|
|
254
|
+
[Amply DEBUG] [campaign] Campaign "rate_review": targeting rules not satisfied
|
|
255
|
+
[Amply DEBUG] [campaign] Campaign "welcome_offer": trigger MATCHED for event ButtonTapped
|
|
256
|
+
[Amply INFO] [campaign] Campaign "welcome_offer": MATCH - executing action
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Log Level Hierarchy
|
|
262
|
+
|
|
263
|
+
| Level | Value | Includes |
|
|
264
|
+
|-------|-------|----------|
|
|
265
|
+
| `none` | 0 | Nothing |
|
|
266
|
+
| `error` | 1 | Errors only |
|
|
267
|
+
| `warn` | 2 | Errors + Warnings |
|
|
268
|
+
| `info` | 3 | Errors + Warnings + Info (SDK lifecycle) |
|
|
269
|
+
| `debug` | 4 | Everything (including campaign evaluation) |
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Data Flow
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
KMP SDK (Kotlin)
|
|
277
|
+
│
|
|
278
|
+
├── Logger.log() called
|
|
279
|
+
│ │
|
|
280
|
+
│ ├── Check if level enabled
|
|
281
|
+
│ │ │
|
|
282
|
+
│ │ └── If enabled: Create LogEntry
|
|
283
|
+
│ │ │
|
|
284
|
+
│ │ ├── Print to native console (always)
|
|
285
|
+
│ │ │
|
|
286
|
+
│ │ └── Call listener.onLog(entry)
|
|
287
|
+
│ │
|
|
288
|
+
└── LogListener (set by RN wrapper)
|
|
289
|
+
│
|
|
290
|
+
└── Convert to EventEnvelope
|
|
291
|
+
│
|
|
292
|
+
├── Android: emit to logEvents SharedFlow
|
|
293
|
+
│ │
|
|
294
|
+
│ └── AmplyModule collects and emits to JS
|
|
295
|
+
│
|
|
296
|
+
└── iOS: call emitOnSystemEvent directly
|
|
297
|
+
│
|
|
298
|
+
└── JS receives via onSystemEvent
|
|
299
|
+
│
|
|
300
|
+
└── If event.name === 'DebugLog'
|
|
301
|
+
│
|
|
302
|
+
└── Output to console.log/warn/error/debug
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Known Issues / TODO
|
|
308
|
+
|
|
309
|
+
1. **KMP SDK Tests Failing**: Some session-related tests are failing after the changes. Need to investigate if this is related to Logger import or pre-existing issues.
|
|
310
|
+
|
|
311
|
+
2. **iOS Log Listener Protocol**: The `ASDKLogListener` protocol may not be exported from the KMP SDK XCFramework yet. Will need to verify after building the iOS framework.
|
|
312
|
+
|
|
313
|
+
3. **Codegen**: The TurboModule codegen needs to be run during native build to generate the setLogLevel/getLogLevel bindings.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Improvements Over Original Request
|
|
318
|
+
|
|
319
|
+
| Original Issue | Resolution |
|
|
320
|
+
|----------------|------------|
|
|
321
|
+
| Option A vs B undecided | Used Option A (System Events) - logs forwarded via existing channel |
|
|
322
|
+
| `debug` + `logLevel` conflict | `logLevel` takes precedence; `debug: true` is alias for `logLevel: 'debug'` |
|
|
323
|
+
| KMP SDK has no logging framework | Created `Logger` singleton with levels, categories, and listener interface |
|
|
324
|
+
| Campaign evaluation silent | Added detailed logging in `CampaignManager.activate()` |
|
|
325
|
+
| iOS system events gap | Fixed - iOS now properly implements `ASDKSystemEventsListener` |
|
|
326
|
+
| Centralized logging | All logs originate from KMP SDK, ensuring consistency |
|
|
@@ -11,9 +11,46 @@
|
|
|
11
11
|
|
|
12
12
|
using namespace facebook::react;
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Log level enum for SDK debug output.
|
|
16
|
+
*/
|
|
17
|
+
typedef NS_ENUM(NSInteger, AmplyLogLevel) {
|
|
18
|
+
AmplyLogLevelNone = 0,
|
|
19
|
+
AmplyLogLevelError = 1,
|
|
20
|
+
AmplyLogLevelWarn = 2,
|
|
21
|
+
AmplyLogLevelInfo = 3,
|
|
22
|
+
AmplyLogLevelDebug = 4
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
static AmplyLogLevel AmplyLogLevelFromString(NSString *level) {
|
|
26
|
+
if (!level) return AmplyLogLevelNone;
|
|
27
|
+
NSString *lowercased = [level lowercaseString];
|
|
28
|
+
if ([lowercased isEqualToString:@"none"]) return AmplyLogLevelNone;
|
|
29
|
+
if ([lowercased isEqualToString:@"error"]) return AmplyLogLevelError;
|
|
30
|
+
if ([lowercased isEqualToString:@"warn"]) return AmplyLogLevelWarn;
|
|
31
|
+
if ([lowercased isEqualToString:@"info"]) return AmplyLogLevelInfo;
|
|
32
|
+
if ([lowercased isEqualToString:@"debug"]) return AmplyLogLevelDebug;
|
|
33
|
+
return AmplyLogLevelNone;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static NSString* AmplyLogLevelToString(AmplyLogLevel level) {
|
|
37
|
+
switch (level) {
|
|
38
|
+
case AmplyLogLevelNone: return @"none";
|
|
39
|
+
case AmplyLogLevelError: return @"error";
|
|
40
|
+
case AmplyLogLevelWarn: return @"warn";
|
|
41
|
+
case AmplyLogLevelInfo: return @"info";
|
|
42
|
+
case AmplyLogLevelDebug: return @"debug";
|
|
43
|
+
}
|
|
44
|
+
return @"none";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Note: ASDKLogListener is not yet available in KMP SDK iOS XCFramework
|
|
48
|
+
// Logging support will be enabled when the KMP SDK iOS exports these APIs
|
|
49
|
+
@interface Amply : NativeAmplyModuleSpecBase <NativeAmplyModuleSpec, ASDKDeepLinkListener, ASDKSystemEventsListener>
|
|
15
50
|
@property (nonatomic, strong) ASDKAmply *amplyInstance;
|
|
16
51
|
@property (nonatomic, assign) BOOL deepLinkListenerRegistered;
|
|
52
|
+
@property (nonatomic, assign) BOOL systemEventsListenerRegistered;
|
|
53
|
+
@property (nonatomic, assign) AmplyLogLevel currentLogLevel;
|
|
17
54
|
@end
|
|
18
55
|
|
|
19
56
|
@implementation Amply
|
|
@@ -66,6 +103,31 @@ RCT_EXPORT_MODULE()
|
|
|
66
103
|
// Create Amply instance
|
|
67
104
|
self.amplyInstance = [[ASDKAmply alloc] initWithConfig:amplyConfig];
|
|
68
105
|
|
|
106
|
+
// Parse debug and logLevel options
|
|
107
|
+
std::optional<bool> debugOpt = config.debug();
|
|
108
|
+
BOOL debug = debugOpt.has_value() && debugOpt.value();
|
|
109
|
+
NSString *logLevelStr = config.logLevel();
|
|
110
|
+
|
|
111
|
+
// Resolve effective log level: logLevel takes precedence over debug
|
|
112
|
+
if (logLevelStr && logLevelStr.length > 0) {
|
|
113
|
+
self.currentLogLevel = AmplyLogLevelFromString(logLevelStr);
|
|
114
|
+
} else if (debug) {
|
|
115
|
+
self.currentLogLevel = AmplyLogLevelDebug;
|
|
116
|
+
} else {
|
|
117
|
+
self.currentLogLevel = AmplyLogLevelNone;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (self.currentLogLevel != AmplyLogLevelNone) {
|
|
121
|
+
RCTLogInfo(@"[AmplyReactNative] Debug logging enabled at level: %@", AmplyLogLevelToString(self.currentLogLevel));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// TODO: Enable when KMP SDK iOS exports setLogLevel and setLogListener
|
|
125
|
+
// [self.amplyInstance setLogLevelLevel:AmplyLogLevelToString(self.currentLogLevel)];
|
|
126
|
+
// [self registerLogListenerInternal];
|
|
127
|
+
|
|
128
|
+
// Register system events listener
|
|
129
|
+
[self registerSystemEventsListenerInternal];
|
|
130
|
+
|
|
69
131
|
RCTLogInfo(@"[AmplyReactNative] Initialized with appId=%@", appId);
|
|
70
132
|
|
|
71
133
|
if (resolve) {
|
|
@@ -324,6 +386,56 @@ RCT_EXPORT_MODULE()
|
|
|
324
386
|
RCTLogInfo(@"[AmplyReactNative] removeListeners called with count: %f", count);
|
|
325
387
|
}
|
|
326
388
|
|
|
389
|
+
- (void)setLogLevel:(NSString *)level
|
|
390
|
+
{
|
|
391
|
+
self.currentLogLevel = AmplyLogLevelFromString(level);
|
|
392
|
+
RCTLogInfo(@"[AmplyReactNative] Log level set to: %@", level);
|
|
393
|
+
// TODO: Enable when KMP SDK iOS exports setLogLevel
|
|
394
|
+
// if (self.amplyInstance) {
|
|
395
|
+
// [self.amplyInstance setLogLevelLevel:level];
|
|
396
|
+
// }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
- (NSString *)getLogLevel
|
|
400
|
+
{
|
|
401
|
+
return AmplyLogLevelToString(self.currentLogLevel);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
#pragma mark - Internal Listener Registration
|
|
405
|
+
|
|
406
|
+
- (void)registerSystemEventsListenerInternal
|
|
407
|
+
{
|
|
408
|
+
if (self.systemEventsListenerRegistered) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (!self.amplyInstance) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
[self.amplyInstance setSystemEventsListenerListener:self];
|
|
415
|
+
self.systemEventsListenerRegistered = YES;
|
|
416
|
+
RCTLogInfo(@"[AmplyReactNative] System events listener registered");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
#pragma mark - ASDKSystemEventsListener
|
|
420
|
+
|
|
421
|
+
- (void)onEventEvent:(id<ASDKEventInterface>)event
|
|
422
|
+
{
|
|
423
|
+
RCTLogInfo(@"[AmplyReactNative] System event received: %@", event.name);
|
|
424
|
+
|
|
425
|
+
NSDictionary *payload = @{
|
|
426
|
+
@"name": event.name ?: @"",
|
|
427
|
+
@"type": @"system",
|
|
428
|
+
@"timestamp": @(event.timestamp),
|
|
429
|
+
@"properties": event.properties ?: @{}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
[self emitOnSystemEvent:payload];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// TODO: Enable ASDKLogListener when KMP SDK iOS exports logging APIs
|
|
436
|
+
// #pragma mark - ASDKLogListener
|
|
437
|
+
// - (void)onLogEntry:(ASDKLogEntry *)entry { ... }
|
|
438
|
+
|
|
327
439
|
- (std::shared_ptr<TurboModule>)getTurboModule:(const ObjCTurboModule::InitParams &)params
|
|
328
440
|
{
|
|
329
441
|
return std::make_shared<NativeAmplyModuleSpecJSI>(params);
|
|
@@ -68,6 +68,14 @@ namespace facebook::react {
|
|
|
68
68
|
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "registerDeepLinkListener", @selector(registerDeepLinkListener), args, count);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
static facebook::jsi::Value __hostFunction_NativeAmplyModuleSpecJSI_setLogLevel(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
|
72
|
+
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "setLogLevel", @selector(setLogLevel:), args, count);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static facebook::jsi::Value __hostFunction_NativeAmplyModuleSpecJSI_getLogLevel(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
|
76
|
+
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, StringKind, "getLogLevel", @selector(getLogLevel), args, count);
|
|
77
|
+
}
|
|
78
|
+
|
|
71
79
|
static facebook::jsi::Value __hostFunction_NativeAmplyModuleSpecJSI_addListener(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
|
72
80
|
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "addListener", @selector(addListener:), args, count);
|
|
73
81
|
}
|
|
@@ -95,8 +103,14 @@ namespace facebook::react {
|
|
|
95
103
|
|
|
96
104
|
|
|
97
105
|
methodMap_["registerDeepLinkListener"] = MethodMetadata {0, __hostFunction_NativeAmplyModuleSpecJSI_registerDeepLinkListener};
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
methodMap_["setLogLevel"] = MethodMetadata {1, __hostFunction_NativeAmplyModuleSpecJSI_setLogLevel};
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
methodMap_["getLogLevel"] = MethodMetadata {0, __hostFunction_NativeAmplyModuleSpecJSI_getLogLevel};
|
|
112
|
+
|
|
113
|
+
|
|
100
114
|
methodMap_["addListener"] = MethodMetadata {1, __hostFunction_NativeAmplyModuleSpecJSI_addListener};
|
|
101
115
|
|
|
102
116
|
|
|
@@ -41,6 +41,8 @@ namespace JS {
|
|
|
41
41
|
NSString *endpoint() const;
|
|
42
42
|
id<NSObject> _Nullable datasetPrefetch() const;
|
|
43
43
|
NSString *defaultConfig() const;
|
|
44
|
+
std::optional<bool> debug() const;
|
|
45
|
+
NSString *logLevel() const;
|
|
44
46
|
|
|
45
47
|
AmplyInitializationConfig(NSDictionary *const v) : _v(v) {}
|
|
46
48
|
private:
|
|
@@ -84,6 +86,8 @@ namespace JS {
|
|
|
84
86
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
85
87
|
reject:(RCTPromiseRejectBlock)reject;
|
|
86
88
|
- (void)registerDeepLinkListener;
|
|
89
|
+
- (void)setLogLevel:(NSString *)level;
|
|
90
|
+
- (NSString *)getLogLevel;
|
|
87
91
|
- (void)addListener:(NSString *)eventName;
|
|
88
92
|
- (void)removeListeners:(double)count;
|
|
89
93
|
|
|
@@ -138,6 +142,16 @@ inline NSString *JS::NativeAmplyModule::AmplyInitializationConfig::defaultConfig
|
|
|
138
142
|
id const p = _v[@"defaultConfig"];
|
|
139
143
|
return RCTBridgingToOptionalString(p);
|
|
140
144
|
}
|
|
145
|
+
inline std::optional<bool> JS::NativeAmplyModule::AmplyInitializationConfig::debug() const
|
|
146
|
+
{
|
|
147
|
+
id const p = _v[@"debug"];
|
|
148
|
+
return RCTBridgingToOptionalBool(p);
|
|
149
|
+
}
|
|
150
|
+
inline NSString *JS::NativeAmplyModule::AmplyInitializationConfig::logLevel() const
|
|
151
|
+
{
|
|
152
|
+
id const p = _v[@"logLevel"];
|
|
153
|
+
return RCTBridgingToOptionalString(p);
|
|
154
|
+
}
|
|
141
155
|
inline NSString *JS::NativeAmplyModule::TrackEventPayload::name() const
|
|
142
156
|
{
|
|
143
157
|
id const p = _v[@"name"];
|