react-native-carrot-quest 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CarrotQuest.podspec +34 -0
- package/LICENSE +20 -0
- package/README.md +457 -0
- package/android/CMakeLists.txt +24 -0
- package/android/build.gradle +136 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +11 -0
- package/android/src/main/java/com/margelo/nitro/carrotquest/CarrotQuest.kt +585 -0
- package/android/src/main/java/com/margelo/nitro/carrotquest/CarrotQuestPackage.kt +22 -0
- package/android/src/main/java/com/margelo/nitro/carrotquest/CarrotQuestSetup.kt +472 -0
- package/ios/CarrotQuest.swift +588 -0
- package/ios/CarrotQuestSetup.swift +333 -0
- package/lib/module/CarrotQuest.nitro.js +4 -0
- package/lib/module/CarrotQuest.nitro.js.map +1 -0
- package/lib/module/carrot-quest.js +136 -0
- package/lib/module/carrot-quest.js.map +1 -0
- package/lib/module/carrot-quest.native.js +155 -0
- package/lib/module/carrot-quest.native.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/subscriptions.js +76 -0
- package/lib/module/subscriptions.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/CarrotQuest.nitro.d.ts +394 -0
- package/lib/typescript/src/CarrotQuest.nitro.d.ts.map +1 -0
- package/lib/typescript/src/carrot-quest.d.ts +37 -0
- package/lib/typescript/src/carrot-quest.d.ts.map +1 -0
- package/lib/typescript/src/carrot-quest.native.d.ts +37 -0
- package/lib/typescript/src/carrot-quest.native.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/subscriptions.d.ts +32 -0
- package/lib/typescript/src/subscriptions.d.ts.map +1 -0
- package/nitro.json +23 -0
- package/nitrogen/generated/android/c++/JCarrotQuestEventValue.cpp +30 -0
- package/nitrogen/generated/android/c++/JCarrotQuestEventValue.hpp +83 -0
- package/nitrogen/generated/android/c++/JCarrotQuestLogCategory.hpp +76 -0
- package/nitrogen/generated/android/c++/JCarrotQuestLogEntry.hpp +100 -0
- package/nitrogen/generated/android/c++/JCarrotQuestLogLevel.hpp +70 -0
- package/nitrogen/generated/android/c++/JCarrotQuestProperty.hpp +74 -0
- package/nitrogen/generated/android/c++/JCarrotQuestPropertyKind.hpp +61 -0
- package/nitrogen/generated/android/c++/JCarrotQuestPropertyOperation.hpp +73 -0
- package/nitrogen/generated/android/c++/JCarrotQuestSetupOptions.hpp +94 -0
- package/nitrogen/generated/android/c++/JCarrotQuestTheme.hpp +64 -0
- package/nitrogen/generated/android/c++/JCarrotQuestUrlSource.hpp +64 -0
- package/nitrogen/generated/android/c++/JCarrotQuestUserProperties.hpp +84 -0
- package/nitrogen/generated/android/c++/JFunc_void_CarrotQuestLogEntry.hpp +84 -0
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_double.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/nitrogen/generated/android/c++/JHybridCarrotQuestSpec.cpp +388 -0
- package/nitrogen/generated/android/c++/JHybridCarrotQuestSpec.hpp +96 -0
- package/nitrogen/generated/android/carrotquest+autolinking.cmake +82 -0
- package/nitrogen/generated/android/carrotquest+autolinking.gradle +27 -0
- package/nitrogen/generated/android/carrotquestOnLoad.cpp +62 -0
- package/nitrogen/generated/android/carrotquestOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestEventValue.kt +75 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestLogCategory.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestLogEntry.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestLogLevel.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestProperty.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestPropertyKind.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestPropertyOperation.kt +28 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestSetupOptions.kt +91 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestTheme.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestUrlSource.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestUserProperties.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_CarrotQuestLogEntry.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_bool.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_double.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_std__string.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/HybridCarrotQuestSpec.kt +209 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/carrotquestOnLoad.kt +35 -0
- package/nitrogen/generated/ios/CarrotQuest+autolinking.rb +62 -0
- package/nitrogen/generated/ios/CarrotQuest-Swift-Cxx-Bridge.cpp +97 -0
- package/nitrogen/generated/ios/CarrotQuest-Swift-Cxx-Bridge.hpp +637 -0
- package/nitrogen/generated/ios/CarrotQuest-Swift-Cxx-Umbrella.hpp +80 -0
- package/nitrogen/generated/ios/CarrotQuestAutolinking.mm +33 -0
- package/nitrogen/generated/ios/CarrotQuestAutolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridCarrotQuestSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridCarrotQuestSpecSwift.hpp +344 -0
- package/nitrogen/generated/ios/swift/CarrotQuestEventValue.swift +32 -0
- package/nitrogen/generated/ios/swift/CarrotQuestLogCategory.swift +64 -0
- package/nitrogen/generated/ios/swift/CarrotQuestLogEntry.swift +86 -0
- package/nitrogen/generated/ios/swift/CarrotQuestLogLevel.swift +56 -0
- package/nitrogen/generated/ios/swift/CarrotQuestProperty.swift +56 -0
- package/nitrogen/generated/ios/swift/CarrotQuestPropertyKind.swift +44 -0
- package/nitrogen/generated/ios/swift/CarrotQuestPropertyOperation.swift +60 -0
- package/nitrogen/generated/ios/swift/CarrotQuestSetupOptions.swift +172 -0
- package/nitrogen/generated/ios/swift/CarrotQuestTheme.swift +48 -0
- package/nitrogen/generated/ios/swift/CarrotQuestUrlSource.swift +48 -0
- package/nitrogen/generated/ios/swift/CarrotQuestUserProperties.swift +110 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CarrotQuestLogEntry.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_double_.swift +53 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +53 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridCarrotQuestSpec.swift +88 -0
- package/nitrogen/generated/ios/swift/HybridCarrotQuestSpec_cxx.swift +674 -0
- package/nitrogen/generated/shared/c++/CarrotQuestLogCategory.hpp +100 -0
- package/nitrogen/generated/shared/c++/CarrotQuestLogEntry.hpp +114 -0
- package/nitrogen/generated/shared/c++/CarrotQuestLogLevel.hpp +92 -0
- package/nitrogen/generated/shared/c++/CarrotQuestProperty.hpp +101 -0
- package/nitrogen/generated/shared/c++/CarrotQuestPropertyKind.hpp +80 -0
- package/nitrogen/generated/shared/c++/CarrotQuestPropertyOperation.hpp +96 -0
- package/nitrogen/generated/shared/c++/CarrotQuestSetupOptions.hpp +121 -0
- package/nitrogen/generated/shared/c++/CarrotQuestTheme.hpp +84 -0
- package/nitrogen/generated/shared/c++/CarrotQuestUrlSource.hpp +84 -0
- package/nitrogen/generated/shared/c++/CarrotQuestUserProperties.hpp +97 -0
- package/nitrogen/generated/shared/c++/HybridCarrotQuestSpec.cpp +55 -0
- package/nitrogen/generated/shared/c++/HybridCarrotQuestSpec.hpp +118 -0
- package/package.json +140 -0
- package/src/CarrotQuest.nitro.ts +493 -0
- package/src/carrot-quest.native.tsx +241 -0
- package/src/carrot-quest.tsx +207 -0
- package/src/index.tsx +57 -0
- package/src/subscriptions.ts +83 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
package com.margelo.nitro.carrotquest
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.res.Configuration
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import io.carrotquest_sdk.android.Callback
|
|
7
|
+
import io.carrotquest_sdk.android.Carrot
|
|
8
|
+
import io.carrotquest_sdk.android.core.logging.SdkLogLevel
|
|
9
|
+
import io.carrotquest_sdk.android.core.main.ThemeSdk
|
|
10
|
+
import java.util.Locale
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Native entry point for initialising the Carrot quest SDK at app launch.
|
|
14
|
+
*
|
|
15
|
+
* Call this from `Application.onCreate` when you want the SDK ready before the
|
|
16
|
+
* JS bundle finishes loading. Apps that do not care about launch timing can
|
|
17
|
+
* skip it and call `setup()` from JS instead.
|
|
18
|
+
*
|
|
19
|
+
* ```kotlin
|
|
20
|
+
* // MainApplication.kt
|
|
21
|
+
* CarrotQuestSetup.configure(this, apiKey = "your-key", locale = "ru") { error ->
|
|
22
|
+
* if (error != null) Log.e("App", "Carrot setup failed", error)
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* Initialisation is single-flight: concurrent calls that agree on the API key
|
|
27
|
+
* and the account-critical options join the in-flight attempt instead of
|
|
28
|
+
* starting a second one, and every caller is notified when it settles. Calls
|
|
29
|
+
* that *disagree* fail with [CarrotQuestConfigurationConflictException] rather
|
|
30
|
+
* than silently keeping the first configuration.
|
|
31
|
+
*
|
|
32
|
+
* `Carrot.setup` performs I/O, so call this off the main thread; this object
|
|
33
|
+
* does not spawn its own thread, leaving the scheduler choice to the caller.
|
|
34
|
+
*/
|
|
35
|
+
object CarrotQuestSetup {
|
|
36
|
+
private const val TAG = "CarrotQuest"
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The options that decide *which account and server* the SDK talks to.
|
|
40
|
+
*
|
|
41
|
+
* Everything else (theme, parent activity, notification icon, log level) is
|
|
42
|
+
* applied after initialisation and can be changed freely, so it is not part
|
|
43
|
+
* of the conflict check.
|
|
44
|
+
*/
|
|
45
|
+
private data class Identity(
|
|
46
|
+
val apiKey: String,
|
|
47
|
+
val locale: String?,
|
|
48
|
+
val useEuServer: Boolean?,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
private sealed interface State {
|
|
52
|
+
object Idle : State
|
|
53
|
+
data class Initializing(val identity: Identity) : State
|
|
54
|
+
data class Configured(val identity: Identity) : State
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private val lock = Any()
|
|
58
|
+
private var state: State = State.Idle
|
|
59
|
+
private var pending = mutableListOf<(Throwable?) -> Unit>()
|
|
60
|
+
/** SDK operations called after setup started but before it finished. */
|
|
61
|
+
private var readinessWaiters = mutableListOf<(Throwable?) -> Unit>()
|
|
62
|
+
|
|
63
|
+
/** Application context the SDK was initialised with. Used to re-init after logout. */
|
|
64
|
+
@Volatile
|
|
65
|
+
private var appContext: Context? = null
|
|
66
|
+
|
|
67
|
+
@Volatile
|
|
68
|
+
private var lastOptions: Options? = null
|
|
69
|
+
|
|
70
|
+
private data class Options(
|
|
71
|
+
val identity: Identity,
|
|
72
|
+
val theme: ThemeSdk?,
|
|
73
|
+
val parentActivityClassName: String?,
|
|
74
|
+
val notificationIconResourceName: String?,
|
|
75
|
+
val logLevel: SdkLogLevel?,
|
|
76
|
+
val logIncludeSensitive: Boolean?,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
val apiKey: String?
|
|
80
|
+
get() = synchronized(lock) { (state as? State.Configured)?.identity?.apiKey }
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Whether the SDK finished initialising successfully.
|
|
84
|
+
*
|
|
85
|
+
* Prefers the SDK's own [Carrot.isInit] over our local bookkeeping so a
|
|
86
|
+
* teardown inside the SDK is not reported as still-configured.
|
|
87
|
+
*/
|
|
88
|
+
val isConfigured: Boolean
|
|
89
|
+
get() = apiKey != null && runCatching { Carrot.isInit() }
|
|
90
|
+
// Fail closed: if the SDK cannot report its own state, callers must not
|
|
91
|
+
// be told it is ready.
|
|
92
|
+
.onFailure { Log.w(TAG, "Carrot.isInit() threw; reporting not-configured", it) }
|
|
93
|
+
.getOrDefault(false)
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Run an SDK operation once the one application-level setup call settles.
|
|
97
|
+
*
|
|
98
|
+
* Operations arriving while setup is in flight are queued natively. Calls
|
|
99
|
+
* made before setup starts fail immediately, and a failed setup rejects every
|
|
100
|
+
* queued operation with the same error.
|
|
101
|
+
*/
|
|
102
|
+
internal fun whenConfigured(completion: (Throwable?) -> Unit) {
|
|
103
|
+
val immediateError = synchronized(lock) {
|
|
104
|
+
when (state) {
|
|
105
|
+
is State.Configured -> null
|
|
106
|
+
is State.Initializing -> {
|
|
107
|
+
readinessWaiters.add(completion)
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
State.Idle -> CarrotQuestNotConfiguredException()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
completion(immediateError)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Queue a fire-and-forget operation without leaking readiness into JS. */
|
|
118
|
+
internal fun runWhenConfigured(name: String, operation: () -> Unit) {
|
|
119
|
+
whenConfigured ready@ { error ->
|
|
120
|
+
if (error != null) {
|
|
121
|
+
Log.e(TAG, "$name skipped because setup did not complete", error)
|
|
122
|
+
return@ready
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
runCatching(operation)
|
|
126
|
+
.onFailure { Log.e(TAG, "$name failed", it) }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Initialise the SDK.
|
|
132
|
+
*
|
|
133
|
+
* @param context Any context; the application context is retained.
|
|
134
|
+
* @param apiKey Your Carrot quest API key.
|
|
135
|
+
* @param locale Optional locale override, e.g. `"ru"`. The SDK resolves its
|
|
136
|
+
* language from the system locale and exposes no `setLanguage` API, so this
|
|
137
|
+
* calls [Locale.setDefault], which is **process-global** and affects the
|
|
138
|
+
* whole app.
|
|
139
|
+
* @param completion Invoked once, when initialisation settles. Receives
|
|
140
|
+
* [CarrotQuestConfigurationConflictException] if the SDK is already
|
|
141
|
+
* running under a different API key, locale or server.
|
|
142
|
+
*/
|
|
143
|
+
@JvmStatic
|
|
144
|
+
@JvmOverloads
|
|
145
|
+
fun configure(
|
|
146
|
+
context: Context,
|
|
147
|
+
apiKey: String,
|
|
148
|
+
locale: String? = null,
|
|
149
|
+
theme: ThemeSdk? = null,
|
|
150
|
+
useEuServer: Boolean? = null,
|
|
151
|
+
parentActivityClassName: String? = null,
|
|
152
|
+
notificationIconResourceName: String? = null,
|
|
153
|
+
logLevel: SdkLogLevel? = null,
|
|
154
|
+
logIncludeSensitive: Boolean? = null,
|
|
155
|
+
completion: ((Throwable?) -> Unit)? = null,
|
|
156
|
+
) {
|
|
157
|
+
val identity = Identity(apiKey, locale, useEuServer)
|
|
158
|
+
|
|
159
|
+
val options = Options(
|
|
160
|
+
identity = identity,
|
|
161
|
+
theme = theme,
|
|
162
|
+
parentActivityClassName = parentActivityClassName,
|
|
163
|
+
notificationIconResourceName = notificationIconResourceName,
|
|
164
|
+
logLevel = logLevel,
|
|
165
|
+
logIncludeSensitive = logIncludeSensitive,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
synchronized(lock) {
|
|
169
|
+
when (val current = state) {
|
|
170
|
+
is State.Configured -> {
|
|
171
|
+
val conflict = conflictOrNull(current.identity, identity)
|
|
172
|
+
|
|
173
|
+
if (conflict == null) {
|
|
174
|
+
// Identity matches, so this is a re-configure rather than a second
|
|
175
|
+
// init. Apply the mutable options instead of dropping them — a
|
|
176
|
+
// native launch-time setup often has no theme, and the later JS
|
|
177
|
+
// call is where it arrives.
|
|
178
|
+
applyMutableOptions(context.applicationContext, options)
|
|
179
|
+
lastOptions = mergeMutableOptions(options)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
completion?.invoke(conflict)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
is State.Initializing -> {
|
|
187
|
+
val conflict = conflictOrNull(current.identity, identity)
|
|
188
|
+
|
|
189
|
+
if (conflict != null) {
|
|
190
|
+
completion?.invoke(conflict)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Join the in-flight attempt rather than initialising a second time,
|
|
195
|
+
// but carry this caller's options into it so they are not lost.
|
|
196
|
+
lastOptions = mergeMutableOptions(options)
|
|
197
|
+
completion?.let { pending.add(it) }
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
State.Idle -> {
|
|
202
|
+
state = State.Initializing(identity)
|
|
203
|
+
// Publish under the lock. Assigning after releasing it would race a
|
|
204
|
+
// joining caller's merge and clobber their options.
|
|
205
|
+
appContext = context.applicationContext
|
|
206
|
+
lastOptions = options
|
|
207
|
+
completion?.let { pending.add(it) }
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
val applicationContext = context.applicationContext
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
start(applicationContext, options) { error ->
|
|
216
|
+
if (error == null) {
|
|
217
|
+
settleSuccess(applicationContext, identity, options)
|
|
218
|
+
} else {
|
|
219
|
+
settleFailure(error)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} catch (error: Throwable) {
|
|
223
|
+
settleFailure(error)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Fold a later call's mutable options over the recorded ones.
|
|
229
|
+
*
|
|
230
|
+
* Only options the caller actually supplied win, so a second `setup` that
|
|
231
|
+
* omits a field does not clear what an earlier call set. Used to keep
|
|
232
|
+
* [lastOptions] accurate for re-initialisation after logout.
|
|
233
|
+
*/
|
|
234
|
+
private fun mergeMutableOptions(options: Options): Options {
|
|
235
|
+
val previous = lastOptions ?: return options
|
|
236
|
+
|
|
237
|
+
return previous.copy(
|
|
238
|
+
identity = options.identity,
|
|
239
|
+
theme = options.theme ?: previous.theme,
|
|
240
|
+
parentActivityClassName =
|
|
241
|
+
options.parentActivityClassName ?: previous.parentActivityClassName,
|
|
242
|
+
notificationIconResourceName =
|
|
243
|
+
options.notificationIconResourceName ?: previous.notificationIconResourceName,
|
|
244
|
+
logLevel = options.logLevel ?: previous.logLevel,
|
|
245
|
+
logIncludeSensitive = options.logIncludeSensitive ?: previous.logIncludeSensitive,
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Apply the options that do not require a fresh initialisation.
|
|
251
|
+
*
|
|
252
|
+
* Safe to call on an already-running SDK; each setter is independent.
|
|
253
|
+
*/
|
|
254
|
+
private fun applyMutableOptions(context: Context, options: Options) {
|
|
255
|
+
runCatching {
|
|
256
|
+
options.logLevel?.let { Carrot.setLogLevel(it) }
|
|
257
|
+
options.logIncludeSensitive?.let { Carrot.setLogIncludeSensitive(it) }
|
|
258
|
+
options.theme?.let { Carrot.setTheme(it) }
|
|
259
|
+
options.parentActivityClassName?.let { Carrot.setParentActivityClassName(it) }
|
|
260
|
+
options.notificationIconResourceName?.let { name ->
|
|
261
|
+
resolveDrawable(context, name)?.let { Carrot.setNotificationIcon(it) }
|
|
262
|
+
}
|
|
263
|
+
}.onFailure { Log.w(TAG, "Re-applying setup options failed", it) }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Refuse to re-point a live SDK at a different account or server.
|
|
268
|
+
*
|
|
269
|
+
* Silently keeping the first configuration would let a second `setup` call
|
|
270
|
+
* resolve successfully while data continued flowing to the wrong Carrot
|
|
271
|
+
* account.
|
|
272
|
+
*/
|
|
273
|
+
private fun conflictOrNull(
|
|
274
|
+
existing: Identity,
|
|
275
|
+
requested: Identity,
|
|
276
|
+
): CarrotQuestConfigurationConflictException? {
|
|
277
|
+
if (existing == requested) return null
|
|
278
|
+
|
|
279
|
+
val differences = buildList {
|
|
280
|
+
if (existing.apiKey != requested.apiKey) add("API key")
|
|
281
|
+
if (existing.locale != requested.locale) add("locale")
|
|
282
|
+
if (existing.useEuServer != requested.useEuServer) add("EU server flag")
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return CarrotQuestConfigurationConflictException(differences.joinToString(", "))
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private fun start(
|
|
289
|
+
context: Context,
|
|
290
|
+
options: Options,
|
|
291
|
+
onSettled: (Throwable?) -> Unit,
|
|
292
|
+
) {
|
|
293
|
+
val identity = options.identity
|
|
294
|
+
|
|
295
|
+
val setupContext = identity.locale
|
|
296
|
+
?.let { applyLocale(context, it) }
|
|
297
|
+
?: context
|
|
298
|
+
|
|
299
|
+
// Logging must be configured before setup so initialisation itself is
|
|
300
|
+
// covered by the requested level.
|
|
301
|
+
options.logLevel?.let { Carrot.setLogLevel(it) }
|
|
302
|
+
options.logIncludeSensitive?.let { Carrot.setLogIncludeSensitive(it) }
|
|
303
|
+
|
|
304
|
+
val callback = object : Callback<Boolean> {
|
|
305
|
+
override fun onResponse(result: Boolean?) {
|
|
306
|
+
// The SDK signals success with `true`; anything else is a failed
|
|
307
|
+
// initialisation reported through the success channel.
|
|
308
|
+
if (result != true) {
|
|
309
|
+
onSettled(CarrotQuestSetupRejectedException())
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Finalisation owns applying the latest merged options and publishing
|
|
314
|
+
// Configured as one serialised operation. Keeping that work together is
|
|
315
|
+
// what closes the late-join window.
|
|
316
|
+
onSettled(null)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
override fun onFailure(t: Throwable) {
|
|
320
|
+
Log.e(TAG, "setup failed", t)
|
|
321
|
+
onSettled(t)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// SDK 3.x removed the callback-free `setup(context, apiKey)` overload, so
|
|
326
|
+
// there is no path here that reports success before the SDK is ready.
|
|
327
|
+
// The four-argument overload is only used when the caller explicitly opts
|
|
328
|
+
// into EU routing — see CarrotQuestSetupOptions.useEuServer.
|
|
329
|
+
val useEuServer = identity.useEuServer
|
|
330
|
+
|
|
331
|
+
if (useEuServer != null) {
|
|
332
|
+
Carrot.setup(setupContext, identity.apiKey, useEuServer, callback)
|
|
333
|
+
} else {
|
|
334
|
+
Carrot.setup(setupContext, identity.apiKey, callback)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Apply the final merged options and publish Configured atomically.
|
|
340
|
+
*
|
|
341
|
+
* A matching configure call either acquires [lock] first and contributes its
|
|
342
|
+
* mutable options to [lastOptions], or acquires it after this method and sees
|
|
343
|
+
* [State.Configured], where it applies those options itself. There is no gap
|
|
344
|
+
* in which a caller can join, resolve, and have its options skipped.
|
|
345
|
+
*/
|
|
346
|
+
private fun settleSuccess(
|
|
347
|
+
context: Context,
|
|
348
|
+
identity: Identity,
|
|
349
|
+
fallbackOptions: Options,
|
|
350
|
+
) {
|
|
351
|
+
val waiting: List<(Throwable?) -> Unit>
|
|
352
|
+
val ready: List<(Throwable?) -> Unit>
|
|
353
|
+
|
|
354
|
+
synchronized(lock) {
|
|
355
|
+
applyMutableOptions(context, lastOptions ?: fallbackOptions)
|
|
356
|
+
state = State.Configured(identity)
|
|
357
|
+
waiting = pending.toList()
|
|
358
|
+
pending = mutableListOf()
|
|
359
|
+
ready = readinessWaiters.toList()
|
|
360
|
+
readinessWaiters = mutableListOf()
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
waiting.forEach { it(null) }
|
|
364
|
+
ready.forEach { it(null) }
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** Return to Idle after a failed attempt and reject every joined caller. */
|
|
368
|
+
private fun settleFailure(error: Throwable) {
|
|
369
|
+
val waiting: List<(Throwable?) -> Unit>
|
|
370
|
+
val ready: List<(Throwable?) -> Unit>
|
|
371
|
+
|
|
372
|
+
synchronized(lock) {
|
|
373
|
+
state = State.Idle
|
|
374
|
+
waiting = pending.toList()
|
|
375
|
+
pending = mutableListOf()
|
|
376
|
+
ready = readinessWaiters.toList()
|
|
377
|
+
readinessWaiters = mutableListOf()
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
waiting.forEach { it(error) }
|
|
381
|
+
ready.forEach { it(error) }
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Re-initialise after [Carrot.deInit]. The Android SDK tears down its whole
|
|
386
|
+
* client on logout, so it has to be set up again before the next auth.
|
|
387
|
+
*
|
|
388
|
+
* @param completion invoked once re-initialisation settles.
|
|
389
|
+
*/
|
|
390
|
+
internal fun reinitialiseAfterLogout(completion: (Throwable?) -> Unit) {
|
|
391
|
+
val context = appContext
|
|
392
|
+
val options = lastOptions
|
|
393
|
+
val identity = synchronized(lock) { (state as? State.Configured)?.identity }
|
|
394
|
+
|
|
395
|
+
if (context == null || options == null || identity == null) {
|
|
396
|
+
completion(null)
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
synchronized(lock) {
|
|
401
|
+
state = State.Initializing(identity)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
start(context, options) { error ->
|
|
406
|
+
if (error == null) {
|
|
407
|
+
settleSuccess(context, identity, options)
|
|
408
|
+
} else {
|
|
409
|
+
settleFailure(error)
|
|
410
|
+
}
|
|
411
|
+
completion(error)
|
|
412
|
+
}
|
|
413
|
+
} catch (error: Throwable) {
|
|
414
|
+
settleFailure(error)
|
|
415
|
+
completion(error)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private fun resolveDrawable(context: Context, name: String): Int? {
|
|
420
|
+
val id = context.resources.getIdentifier(name, "drawable", context.packageName)
|
|
421
|
+
|
|
422
|
+
if (id == 0) {
|
|
423
|
+
Log.w(TAG, "Notification icon drawable '$name' not found; keeping the SDK default.")
|
|
424
|
+
return null
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return id
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Override the process locale so the SDK picks up the right language.
|
|
432
|
+
*
|
|
433
|
+
* Must run before `Carrot.setup`. [Locale.setDefault] is process-global, so
|
|
434
|
+
* this affects every locale-sensitive API in the app, not just Carrot.
|
|
435
|
+
*/
|
|
436
|
+
private fun applyLocale(context: Context, locale: String): Context {
|
|
437
|
+
val target = Locale(locale)
|
|
438
|
+
Locale.setDefault(target)
|
|
439
|
+
|
|
440
|
+
val config = Configuration(context.resources.configuration)
|
|
441
|
+
config.setLocale(target)
|
|
442
|
+
|
|
443
|
+
return context.createConfigurationContext(config)
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
internal class CarrotQuestNotConfiguredException : IllegalStateException(
|
|
448
|
+
"Carrot quest is not configured. Await setup() first, or call CarrotQuestSetup.configure() from Application.onCreate."
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
internal class CarrotQuestNoActivityException : IllegalStateException(
|
|
452
|
+
"Carrot quest could not open the chat: no foreground activity."
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
internal class CarrotQuestInvalidPayloadException : IllegalArgumentException(
|
|
456
|
+
"Carrot quest could not decode the notification payload as a JSON object."
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
internal class CarrotQuestSetupRejectedException : IllegalStateException(
|
|
460
|
+
"Carrot quest setup did not succeed. Check the API key and network connection."
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
internal class CarrotQuestLogoutRejectedException : IllegalStateException(
|
|
464
|
+
"Carrot quest logout did not succeed; the previous user may still be authenticated."
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
internal class CarrotQuestConfigurationConflictException(differences: String) :
|
|
468
|
+
IllegalStateException(
|
|
469
|
+
"Carrot quest is already initialised with a different $differences. " +
|
|
470
|
+
"The SDK cannot be re-pointed at another account or server at runtime; " +
|
|
471
|
+
"restart the process to change it."
|
|
472
|
+
)
|