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,34 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "CarrotQuest"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/ticketscloud/react-native-carrot-quest.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = [
|
|
17
|
+
"ios/**/*.{swift}",
|
|
18
|
+
"ios/**/*.{m,mm}",
|
|
19
|
+
"cpp/**/*.{hpp,cpp}",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
s.dependency 'React-jsi'
|
|
23
|
+
s.dependency 'React-callinvoker'
|
|
24
|
+
|
|
25
|
+
# Allows any 3.x. The public API used here was verified against 3.1.6 and
|
|
26
|
+
# 3.2.1; CarrotquestSDK is a closed-source vendor pod, so pin it yourself in
|
|
27
|
+
# your app's Podfile if you need a specific build.
|
|
28
|
+
s.dependency 'CarrotquestSDK', '~> 3.1'
|
|
29
|
+
|
|
30
|
+
load 'nitrogen/generated/ios/CarrotQuest+autolinking.rb'
|
|
31
|
+
add_nitrogen_files(s)
|
|
32
|
+
|
|
33
|
+
install_modules_dependencies(s)
|
|
34
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hamed
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
# react-native-carrot-quest
|
|
2
|
+
|
|
3
|
+
Unofficial React Native bindings for the [Carrot quest](https://www.carrotquest.io/) mobile SDKs, built with [Nitro Modules](https://nitro.margelo.com/).
|
|
4
|
+
|
|
5
|
+
> **Not affiliated with Carrot quest.** Carrot does not officially support React
|
|
6
|
+
> Native, so this package maintains its own compatibility coverage against the
|
|
7
|
+
> vendor's iOS and Android SDKs. Versions are pinned deliberately — see
|
|
8
|
+
> [SDK versions](#sdk-versions).
|
|
9
|
+
|
|
10
|
+
- Swift and Kotlin implementations, no Objective-C bridging layer
|
|
11
|
+
- Fully typed across the JS/native boundary — the spec is the single source of truth
|
|
12
|
+
- `setup()` resolves only when the SDK reports success, so auth can never race init
|
|
13
|
+
- Native init hooks so the SDK is ready before your JS bundle loads
|
|
14
|
+
- A `verify:native` check that fails if a pinned SDK stops exposing a call the bridge makes
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
| | Minimum |
|
|
19
|
+
|---|---|
|
|
20
|
+
| React Native | 0.75 (New Architecture) |
|
|
21
|
+
| iOS | 13.4 |
|
|
22
|
+
| Android | API 24 |
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install react-native-carrot-quest react-native-nitro-modules
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`react-native-nitro-modules` is a required peer dependency.
|
|
31
|
+
|
|
32
|
+
### iOS
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cd ios && pod install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Android
|
|
39
|
+
|
|
40
|
+
The Carrot Android SDK and two of its transitive dependencies live outside Maven
|
|
41
|
+
Central. Add both repositories to your `android/build.gradle`:
|
|
42
|
+
|
|
43
|
+
```gradle
|
|
44
|
+
allprojects {
|
|
45
|
+
repositories {
|
|
46
|
+
maven { url "https://raw.github.com/carrotquest/android-sdk/carrotquest" }
|
|
47
|
+
// SDK 3.x pulls com.github.Redman1037:TSnackBar and com.github.chrisbanes:PhotoView
|
|
48
|
+
maven { url "https://jitpack.io" }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
### Pick an API style
|
|
56
|
+
|
|
57
|
+
The default export exposes every method on one `CarrotQuest` object:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import CarrotQuest from 'react-native-carrot-quest';
|
|
61
|
+
|
|
62
|
+
CarrotQuest.setup('your-api-key', {
|
|
63
|
+
locale: 'ru',
|
|
64
|
+
theme: 'fromDevice',
|
|
65
|
+
}).catch((error) => {
|
|
66
|
+
console.warn('Carrot Quest setup failed', error);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
CarrotQuest.trackEvent('app_opened');
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Named exports remain available and call the same functions:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { setup, trackEvent } from 'react-native-carrot-quest';
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Carrot Quest is a process-wide native singleton, so a React Context provider is
|
|
79
|
+
not required for setup, auth, tracking, or opening chat. Add a provider (or your
|
|
80
|
+
existing state store) only when React components need shared **reactive** state,
|
|
81
|
+
such as an unread badge or chat-visibility state.
|
|
82
|
+
|
|
83
|
+
Call `setup()` once. The package owns readiness natively: if another method is
|
|
84
|
+
called while setup is still finishing, it waits for that same setup attempt.
|
|
85
|
+
Your application does not need a provider, a readiness promise, or repeated
|
|
86
|
+
`setup()` calls.
|
|
87
|
+
|
|
88
|
+
### Option A: initialise from `index.js`
|
|
89
|
+
|
|
90
|
+
Use this when Carrot Quest should start as soon as the JS bundle evaluates:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
import { AppRegistry } from 'react-native';
|
|
94
|
+
import CarrotQuest from 'react-native-carrot-quest';
|
|
95
|
+
import App from './App';
|
|
96
|
+
import { name as appName } from './app.json';
|
|
97
|
+
|
|
98
|
+
// Start setup early, but do not await it before registering the React app.
|
|
99
|
+
CarrotQuest.setup('your-api-key', {
|
|
100
|
+
locale: 'ru',
|
|
101
|
+
theme: 'fromDevice',
|
|
102
|
+
}).catch((error) => {
|
|
103
|
+
console.warn('Carrot Quest setup failed', error);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
AppRegistry.registerComponent(appName, () => App);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Option B: initialise from `App.tsx`
|
|
110
|
+
|
|
111
|
+
Use this when the first screen does not need Carrot Quest and you prefer to let
|
|
112
|
+
React commit its first render before setup begins:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { useEffect } from 'react';
|
|
116
|
+
import CarrotQuest from 'react-native-carrot-quest';
|
|
117
|
+
|
|
118
|
+
export default function App() {
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
CarrotQuest.setup('your-api-key', {
|
|
121
|
+
locale: 'ru',
|
|
122
|
+
theme: 'fromDevice',
|
|
123
|
+
}).catch((error) => {
|
|
124
|
+
console.warn('Carrot Quest setup failed', error);
|
|
125
|
+
});
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
return <Navigation />;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
After this one call, use `CarrotQuest.auth()`, `openChat()`, `trackEvent()` and
|
|
133
|
+
the rest of the API directly wherever they are needed. Do not hide the whole
|
|
134
|
+
app behind the setup promise unless support chat is required for the first
|
|
135
|
+
screen.
|
|
136
|
+
|
|
137
|
+
### Does setup affect startup time?
|
|
138
|
+
|
|
139
|
+
Potentially, yes—but the API style (default object, named imports, or provider)
|
|
140
|
+
is not what matters. The important choices are **when setup starts** and whether
|
|
141
|
+
your app waits for it before rendering:
|
|
142
|
+
|
|
143
|
+
| Placement | SDK starts | Startup tradeoff |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `index.js` | During JS bundle evaluation | Ready earlier; native CPU, disk, and network work may compete with startup. Registering the app without awaiting setup keeps it off the explicit render gate. |
|
|
146
|
+
| `App.tsx` `useEffect` | After the first React commit | Usually the lowest-risk choice for first-render latency; chat becomes ready slightly later. |
|
|
147
|
+
|
|
148
|
+
Importing the package only creates the Nitro hybrid-object binding. The
|
|
149
|
+
meaningful work begins when `setup()` initializes the vendor SDK. Because the
|
|
150
|
+
method returns a promise, calling it does not require blocking React, but gating
|
|
151
|
+
your root component on that promise will extend time-to-content by the setup
|
|
152
|
+
duration.
|
|
153
|
+
|
|
154
|
+
Measure the impact in a **release build** over multiple cold launches. Compare a
|
|
155
|
+
user-visible milestone such as navigation-ready or first content with and
|
|
156
|
+
without early setup; timing only the setup promise measures SDK readiness, not
|
|
157
|
+
its effect on the rest of startup.
|
|
158
|
+
|
|
159
|
+
### Setup guarantees
|
|
160
|
+
|
|
161
|
+
`setup()` returns a promise so the application can observe success or failure—an
|
|
162
|
+
invalid key or a dead network does not leave a silently half-initialised SDK.
|
|
163
|
+
You do not need to await that promise before calling another package method;
|
|
164
|
+
the native readiness queue preserves the call order.
|
|
165
|
+
|
|
166
|
+
Matching concurrent calls are single-flight. Mutable options contributed by a
|
|
167
|
+
call that joins during initialization are applied before every joined promise
|
|
168
|
+
resolves, including a call arriving while success is being finalized.
|
|
169
|
+
|
|
170
|
+
SDK operations called during initialization wait in the native setup queue.
|
|
171
|
+
Promise-returning operations reject with the setup error if initialization
|
|
172
|
+
fails; fire-and-forget operations are skipped and logged.
|
|
173
|
+
|
|
174
|
+
Calling `setup()` again with a **different** API key, locale, EU-server flag,
|
|
175
|
+
App Group or service mode rejects with a configuration-conflict error rather
|
|
176
|
+
than resolving and quietly keeping the first configuration:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
await setup(productionKey);
|
|
180
|
+
await setup(stagingKey); // rejects — production is still active
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The native SDKs cannot be re-pointed at another account at runtime. Restart the
|
|
184
|
+
process to change it.
|
|
185
|
+
|
|
186
|
+
### Authenticating
|
|
187
|
+
|
|
188
|
+
Carrot has **two distinct authentication flows** with different security
|
|
189
|
+
properties. They are not interchangeable.
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { auth, hashedAuth, logout } from 'react-native-carrot-quest';
|
|
193
|
+
|
|
194
|
+
// Recommended. `hash` is generated per user on your server.
|
|
195
|
+
const carrotId = await hashedAuth(userId, hashFromYourBackend);
|
|
196
|
+
|
|
197
|
+
// Simpler, less secure. `userAuthKey` is the single key from your Carrot
|
|
198
|
+
// settings, shipped inside the app and identical for every user.
|
|
199
|
+
const sameId = await auth(userId, USER_AUTH_KEY);
|
|
200
|
+
|
|
201
|
+
await logout();
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
| | `hashedAuth(userId, hash)` | `auth(userId, userAuthKey)` |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| Credential | Per-user hash | One shared key |
|
|
207
|
+
| Lives | On your server | In the app binary |
|
|
208
|
+
| Use when | You have a backend | You have no backend |
|
|
209
|
+
| Security | **Recommended** | Anyone who extracts the key can authenticate as any user id |
|
|
210
|
+
|
|
211
|
+
> Do not pass a per-user backend hash to `auth()` — it takes the static User
|
|
212
|
+
> Auth Key, and authentication will fail.
|
|
213
|
+
|
|
214
|
+
### Chat, properties, and tracking
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import {
|
|
218
|
+
closeChat,
|
|
219
|
+
getUnreadConversationsCount,
|
|
220
|
+
onChatVisibilityChanged,
|
|
221
|
+
openChat,
|
|
222
|
+
setProperties,
|
|
223
|
+
setUserProperties,
|
|
224
|
+
trackEvent,
|
|
225
|
+
trackScreen,
|
|
226
|
+
trackUtm,
|
|
227
|
+
} from 'react-native-carrot-quest';
|
|
228
|
+
|
|
229
|
+
await openChat();
|
|
230
|
+
await closeChat(); // iOS only
|
|
231
|
+
|
|
232
|
+
// Listeners return a subscription — remove it on unmount.
|
|
233
|
+
const subscription = onChatVisibilityChanged((visible) => setOpen(visible));
|
|
234
|
+
subscription.remove();
|
|
235
|
+
|
|
236
|
+
const unread = await getUnreadConversationsCount();
|
|
237
|
+
|
|
238
|
+
setUserProperties({ name: 'Ada', email: 'ada@example.com', custom: { plan: 'pro' } });
|
|
239
|
+
|
|
240
|
+
// Merge operations and the e-commerce namespace:
|
|
241
|
+
setProperties([
|
|
242
|
+
{ key: 'cartAmount', value: '42', kind: 'ecommerce' },
|
|
243
|
+
{ key: 'visits', value: '1', operation: 'add' },
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
trackEvent('checkout_completed', { total: 42, currency: 'EUR', firstOrder: true });
|
|
247
|
+
trackScreen('CheckoutScreen');
|
|
248
|
+
trackUtm('https://example.com/?utm_source=newsletter');
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Push notifications
|
|
252
|
+
|
|
253
|
+
Register the device token, then hand Carrot payloads to the SDK:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import {
|
|
257
|
+
handlePushClick,
|
|
258
|
+
handlePushNotification,
|
|
259
|
+
isCarrotPush,
|
|
260
|
+
setPushToken,
|
|
261
|
+
} from 'react-native-carrot-quest';
|
|
262
|
+
|
|
263
|
+
setPushToken(token); // APNs hex string on iOS, FCM token on Android
|
|
264
|
+
|
|
265
|
+
// In your message handler:
|
|
266
|
+
if (isCarrotPush(remoteMessage.data)) {
|
|
267
|
+
handlePushNotification(remoteMessage.data);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// iOS, when the user taps a notification:
|
|
271
|
+
handlePushClick(response.notification.request.content.userInfo, /* openLink */ false);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Payload arguments accept either a decoded object or a raw JSON string.
|
|
275
|
+
|
|
276
|
+
**iOS also needs, outside this package:**
|
|
277
|
+
|
|
278
|
+
1. A Notification Service Extension target using `CarrotNotificationServiceExtension`.
|
|
279
|
+
2. An App Group shared between the app and that extension, passed as
|
|
280
|
+
`setup(key, { appGroup: 'group.com.example' })`. Without it,
|
|
281
|
+
`wasPushShownEarlier` cannot work and rich pushes will not render.
|
|
282
|
+
|
|
283
|
+
**Android also needs** a `POST_NOTIFICATIONS` permission request on API 33+, and
|
|
284
|
+
optionally `notificationIconResourceName` in the setup options.
|
|
285
|
+
|
|
286
|
+
### Deep links
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import { onUrlOpen } from 'react-native-carrot-quest';
|
|
290
|
+
|
|
291
|
+
// iOS only — intercept links instead of letting the SDK open them.
|
|
292
|
+
useEffect(() => {
|
|
293
|
+
const subscription = onUrlOpen('all', (url) =>
|
|
294
|
+
navigation.navigate(routeFor(url))
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
return () => subscription.remove();
|
|
298
|
+
}, [navigation]);
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
On Android, links route through the activity named by
|
|
302
|
+
`parentActivityClassName`; the SDK exposes no interception hook.
|
|
303
|
+
|
|
304
|
+
> **Keep at least one `onUrlOpen` listener registered while you want links
|
|
305
|
+
> intercepted.** The SDK's url opener is a setter with no detach, so removing
|
|
306
|
+
> every listener leaves it installed with nobody to receive the url — links
|
|
307
|
+
> then go nowhere rather than reverting to the SDK's own handling. Removing
|
|
308
|
+
> subscriptions on unmount is still right; just re-register on mount.
|
|
309
|
+
|
|
310
|
+
## API
|
|
311
|
+
|
|
312
|
+
| Method | Returns | iOS | Android |
|
|
313
|
+
|---|---|:---:|:---:|
|
|
314
|
+
| `setup(apiKey, options?)` | `Promise<void>` | ✅ | ✅ |
|
|
315
|
+
| `isConfigured()` | `boolean` | ✅ | ✅ |
|
|
316
|
+
| `getSdkVersion()` | `string` | ✅ | ✅ |
|
|
317
|
+
| `auth(userId, userAuthKey)` | `Promise<string \| undefined>` | ✅ | ✅ |
|
|
318
|
+
| `hashedAuth(userId, hash)` | `Promise<string \| undefined>` | ✅ | ✅ |
|
|
319
|
+
| `logout()` | `Promise<void>` | ✅ | ✅ |
|
|
320
|
+
| `openChat()` | `Promise<void>` | ✅ | ✅ |
|
|
321
|
+
| `closeChat()` | `Promise<void>` | ✅ | — |
|
|
322
|
+
| `isChatOpen()` | `boolean` | ✅ | — |
|
|
323
|
+
| `onChatVisibilityChanged(cb)` | `Subscription` | ✅ | close only |
|
|
324
|
+
| `setTheme(theme)` | `void` | ✅ | ✅ |
|
|
325
|
+
| `getUnreadConversationsCount()` | `Promise<number>` | ✅ | ✅ |
|
|
326
|
+
| `getUnreadMessagesCount()` | `Promise<number \| undefined>` | ✅ | — |
|
|
327
|
+
| `onUnreadConversationsChanged(cb)` | `Subscription` | — | ✅ |
|
|
328
|
+
| `setUserProperties(props)` | `void` | ✅ | ✅ |
|
|
329
|
+
| `setProperties(list)` | `void` | ✅ | ✅ |
|
|
330
|
+
| `trackEvent(name, params?)` | `void` | ✅ | ✅ |
|
|
331
|
+
| `trackScreen(name)` | `void` | ✅ | ✅ |
|
|
332
|
+
| `trackUtm(url)` | `void` | ✅ | ✅ |
|
|
333
|
+
| `setPushToken(token)` | `void` | ✅ | ✅ |
|
|
334
|
+
| `deletePushToken()` | `void` | ✅ | — |
|
|
335
|
+
| `pushNotificationsUnsubscribe()` | `Promise<void>` | ✅ | ✅ |
|
|
336
|
+
| `pushCampaignsUnsubscribe()` | `Promise<void>` | ✅ | ✅ |
|
|
337
|
+
| `isCarrotPush(payload)` | `boolean` | ✅ | ✅ |
|
|
338
|
+
| `isAutoMessage(payload)` | `boolean` | best effort | ✅ |
|
|
339
|
+
| `handlePushNotification(payload)` | `void` | ✅ | ✅ |
|
|
340
|
+
| `handlePushClick(payload, openLink?)` | `void` | ✅ | — |
|
|
341
|
+
| `getPushLink(payload)` | `string \| undefined` | best effort | — |
|
|
342
|
+
| `wasPushShownEarlier(payload)` | `Promise<boolean>` | ✅ | — |
|
|
343
|
+
| `onUrlOpen(source, cb)` | `Subscription` | ✅ | — |
|
|
344
|
+
| `openBrowserLink(url)` / `openUniversalLink(url)` | `void` | ✅ | — |
|
|
345
|
+
| `getDiagnostics()` | `string \| undefined` | — | ✅ |
|
|
346
|
+
| `onLog(cb)` | `Subscription` | — | ✅ |
|
|
347
|
+
|
|
348
|
+
Methods that cannot meaningfully fail are synchronous and return `void`. Methods
|
|
349
|
+
that can fail return a `Promise` so you get the error. A `—` means the platform's
|
|
350
|
+
SDK has no equivalent; those calls are inert rather than throwing.
|
|
351
|
+
|
|
352
|
+
### Forwarding SDK logs
|
|
353
|
+
|
|
354
|
+
Android only. Useful for routing the SDK's own diagnostics into your crash
|
|
355
|
+
reporter:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
import { onLog } from 'react-native-carrot-quest';
|
|
359
|
+
|
|
360
|
+
const subscription = onLog((entry) => {
|
|
361
|
+
if (entry.level === 'error') {
|
|
362
|
+
Sentry.captureMessage(`[carrot/${entry.category}] ${entry.message}`);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Entries are filtered by `logLevel`, so keep that at `info` or lower in
|
|
368
|
+
production — `verbose` crosses the bridge for every network call. Values are
|
|
369
|
+
redacted unless `logIncludeSensitive` is on.
|
|
370
|
+
|
|
371
|
+
Removing the last `onLog` subscription detaches the native sink, so log traffic
|
|
372
|
+
stops crossing the bridge entirely; the next subscription re-attaches it.
|
|
373
|
+
|
|
374
|
+
### Not wrapped
|
|
375
|
+
|
|
376
|
+
**The iOS floating chat button** (`showButton(in:)`, `hideButton()`,
|
|
377
|
+
`setButton(icon:)`). `showButton` takes a `UIView`, so exposing it properly means
|
|
378
|
+
shipping a Nitro **view** component rather than a hybrid object. Open an issue if
|
|
379
|
+
you need it.
|
|
380
|
+
|
|
381
|
+
### `CarrotQuestSetupOptions`
|
|
382
|
+
|
|
383
|
+
| Option | Platform | Description |
|
|
384
|
+
|---|---|---|
|
|
385
|
+
| `locale` | both | Force a UI locale, e.g. `'ru'`. See [Locale forcing](#locale-forcing) |
|
|
386
|
+
| `theme` | both | `light`, `dark`, `fromDevice`, `fromWeb` |
|
|
387
|
+
| `useEuServer` | both | Route to Carrot's EU servers. See the caveat below |
|
|
388
|
+
| `appGroup` | iOS | App Group shared with your Notification Service Extension |
|
|
389
|
+
| `isServiceMode` | iOS | Run without UI, inside an extension process |
|
|
390
|
+
| `parentActivityClassName` | Android | Activity the chat returns to on close |
|
|
391
|
+
| `notificationIconResourceName` | Android | Drawable *name* for push notifications |
|
|
392
|
+
| `logLevel` | Android | `none`, `error`, `warn`, `info`, `debugging`, `verbose` |
|
|
393
|
+
| `logIncludeSensitive` | Android | Allow tokens/user ids into SDK logs |
|
|
394
|
+
|
|
395
|
+
> **`useEuServer` on Android is inferred, not documented.** The SDK is obfuscated;
|
|
396
|
+
> the flag maps to the four-argument `setup(context, key, boolean, callback)`
|
|
397
|
+
> overload, identified from the neighbouring `isUseEuApi()` accessor. Verify your
|
|
398
|
+
> traffic routing before relying on it. Leaving the option unset uses the
|
|
399
|
+
> three-argument overload, so default behaviour is unaffected.
|
|
400
|
+
|
|
401
|
+
### Locale forcing
|
|
402
|
+
|
|
403
|
+
The Carrot SDKs resolve their language from the system locale and expose no
|
|
404
|
+
public `setLanguage` API. Passing `locale` works around that, but it is a blunt
|
|
405
|
+
instrument:
|
|
406
|
+
|
|
407
|
+
- **iOS** writes `AppleLanguages` / `AppleLocale` in `UserDefaults`
|
|
408
|
+
- **Android** calls `Locale.setDefault`, which is **process-global** and affects
|
|
409
|
+
every locale-sensitive API in your app, not just Carrot
|
|
410
|
+
|
|
411
|
+
Only set it if you need to pin the chat to one language.
|
|
412
|
+
|
|
413
|
+
## SDK versions
|
|
414
|
+
|
|
415
|
+
| Platform | Pinned |
|
|
416
|
+
|---|---|
|
|
417
|
+
| iOS | `CarrotquestSDK ~> 3.1` (verified against 3.2.1) |
|
|
418
|
+
| Android | `io.carrotquest:android-sdk:3.1.0-commonRelease` |
|
|
419
|
+
|
|
420
|
+
Carrot publishes one Android artifact per region flavour — `commonRelease` or
|
|
421
|
+
`usRelease`. Override from your app's `android/build.gradle`:
|
|
422
|
+
|
|
423
|
+
```gradle
|
|
424
|
+
ext { carrotSdkVersion = "3.1.0-usRelease" }
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Android 2.x is not supported.** It exposes a callback-free `setup()` that
|
|
428
|
+
reports success before initialisation finishes, a `setDebug()` toggle that 3.x
|
|
429
|
+
replaced with `setLogLevel`, and JSON-string event params that 3.x replaced with
|
|
430
|
+
`EventParams`.
|
|
431
|
+
|
|
432
|
+
### Guarding against drift
|
|
433
|
+
|
|
434
|
+
`yarn test` fails if a version pin moves without the verified version in
|
|
435
|
+
`scripts/native-api.json` being updated to match. Maintainers can additionally
|
|
436
|
+
check every native symbol against the real SDK artifacts — see
|
|
437
|
+
[CONTRIBUTING.md](CONTRIBUTING.md#verifying-the-native-sdk-surface).
|
|
438
|
+
|
|
439
|
+
## Platform support
|
|
440
|
+
|
|
441
|
+
iOS and Android only. Importing on web resolves to a stub: predicates answer
|
|
442
|
+
`false`/`undefined` and everything else throws, so you can guard with
|
|
443
|
+
`Platform.OS` without breaking bundling.
|
|
444
|
+
|
|
445
|
+
## Contributing
|
|
446
|
+
|
|
447
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
448
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
449
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
450
|
+
|
|
451
|
+
## License
|
|
452
|
+
|
|
453
|
+
MIT
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
project(carrotquest)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set(PACKAGE_NAME carrotquest)
|
|
5
|
+
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
7
|
+
|
|
8
|
+
# Define C++ library and add all sources
|
|
9
|
+
add_library(${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp)
|
|
10
|
+
|
|
11
|
+
# Add Nitrogen specs :)
|
|
12
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/carrotquest+autolinking.cmake)
|
|
13
|
+
|
|
14
|
+
# Set up local includes
|
|
15
|
+
include_directories("src/main/cpp" "../cpp")
|
|
16
|
+
|
|
17
|
+
find_library(LOG_LIB log)
|
|
18
|
+
|
|
19
|
+
# Link all libraries together
|
|
20
|
+
target_link_libraries(
|
|
21
|
+
${PACKAGE_NAME}
|
|
22
|
+
${LOG_LIB}
|
|
23
|
+
android # <-- Android core
|
|
24
|
+
)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.CarrotQuest = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36,
|
|
6
|
+
// Carrot publishes one artifact per region flavour: `commonRelease` for the
|
|
7
|
+
// default servers, `usRelease` for the US ones. Override from your app's
|
|
8
|
+
// android/build.gradle:
|
|
9
|
+
// ext { carrotSdkVersion = "3.1.0-usRelease" }
|
|
10
|
+
//
|
|
11
|
+
// Must stay on the 3.x line — 2.x has a callback-free `setup`, a `setDebug`
|
|
12
|
+
// toggle and JSON-string event params, none of which this bridge uses.
|
|
13
|
+
carrotSdkVersion: "3.1.0-commonRelease"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
ext.getExtOrDefault = { prop ->
|
|
17
|
+
if (rootProject.ext.has(prop)) {
|
|
18
|
+
return rootProject.ext.get(prop)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return CarrotQuest[prop]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
repositories {
|
|
25
|
+
google()
|
|
26
|
+
mavenCentral()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
dependencies {
|
|
30
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
31
|
+
// noinspection DifferentKotlinGradleVersion
|
|
32
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def reactNativeArchitectures() {
|
|
37
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
38
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
apply plugin: "com.android.library"
|
|
42
|
+
apply plugin: "kotlin-android"
|
|
43
|
+
apply from: '../nitrogen/generated/android/carrotquest+autolinking.gradle'
|
|
44
|
+
|
|
45
|
+
apply plugin: "com.facebook.react"
|
|
46
|
+
|
|
47
|
+
android {
|
|
48
|
+
namespace "com.margelo.nitro.carrotquest"
|
|
49
|
+
|
|
50
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
51
|
+
|
|
52
|
+
defaultConfig {
|
|
53
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
54
|
+
|
|
55
|
+
externalNativeBuild {
|
|
56
|
+
cmake {
|
|
57
|
+
cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
|
|
58
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
|
59
|
+
abiFilters (*reactNativeArchitectures())
|
|
60
|
+
|
|
61
|
+
buildTypes {
|
|
62
|
+
debug {
|
|
63
|
+
cppFlags "-O1 -g"
|
|
64
|
+
}
|
|
65
|
+
release {
|
|
66
|
+
cppFlags "-O2"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
externalNativeBuild {
|
|
74
|
+
cmake {
|
|
75
|
+
path "CMakeLists.txt"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
packaging {
|
|
80
|
+
resources {
|
|
81
|
+
excludes += [
|
|
82
|
+
"META-INF",
|
|
83
|
+
"META-INF/**"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
jniLibs {
|
|
88
|
+
excludes += [
|
|
89
|
+
"**/libc++_shared.so",
|
|
90
|
+
"**/libfbjni.so",
|
|
91
|
+
"**/libjsi.so",
|
|
92
|
+
"**/libfolly_json.so",
|
|
93
|
+
"**/libfolly_runtime.so",
|
|
94
|
+
"**/libglog.so",
|
|
95
|
+
"**/libhermes.so",
|
|
96
|
+
"**/libhermes-executor-debug.so",
|
|
97
|
+
"**/libhermes_executor.so",
|
|
98
|
+
"**/libreactnative.so",
|
|
99
|
+
"**/libreactnativejni.so",
|
|
100
|
+
"**/libturbomodulejsijni.so",
|
|
101
|
+
"**/libreact_nativemodule_core.so",
|
|
102
|
+
"**/libjscexecutor.so"
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
buildFeatures {
|
|
108
|
+
prefab true
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
compileOptions {
|
|
112
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
113
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
repositories {
|
|
118
|
+
google()
|
|
119
|
+
mavenCentral()
|
|
120
|
+
// Carrot quest publishes its Android SDK from a GitHub-hosted Maven repo
|
|
121
|
+
// rather than Maven Central.
|
|
122
|
+
maven { url "https://raw.github.com/carrotquest/android-sdk/carrotquest" }
|
|
123
|
+
// Required for the SDK's own transitive GitHub dependencies — 3.1.0 pulls
|
|
124
|
+
// com.github.Redman1037:TSnackBar and com.github.chrisbanes:PhotoView.
|
|
125
|
+
maven { url "https://jitpack.io" }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
dependencies {
|
|
129
|
+
implementation "com.facebook.react:react-android"
|
|
130
|
+
implementation project(":react-native-nitro-modules")
|
|
131
|
+
|
|
132
|
+
// `api`, not `implementation`, so consumers can call CarrotQuestSetup from
|
|
133
|
+
// their own Application class. Pinned exactly — the vendor publishes a single
|
|
134
|
+
// artifact per release with no semver guarantees. Bump deliberately.
|
|
135
|
+
api "io.carrotquest:android-sdk:${getExtOrDefault('carrotSdkVersion')}"
|
|
136
|
+
}
|