@kindly-ai/react-native 1.0.13 → 1.0.15
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 +54 -1
- package/android/src/main/java/com/kindlyai/reactnative/KindlyReactNativeModule.kt +73 -1
- package/ios/Frameworks/Kindly.xcframework/_CodeSignature/CodeDirectory +0 -0
- package/ios/Frameworks/Kindly.xcframework/_CodeSignature/CodeResources +52 -172
- package/ios/Frameworks/Kindly.xcframework/_CodeSignature/CodeSignature +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Assets.car +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/ConfirmationWindow.nib +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Headers/Kindly-Swift.h +72 -7
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Info.plist +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Kindly +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios.abi.json +598 -432
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios.private.swiftinterface +4 -4
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios.swiftinterface +3 -4
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/module.modulemap +0 -1
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/_CodeSignature/CodeResources +17 -17
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Assets.car +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/ConfirmationWindow.nib +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Headers/Kindly-Swift.h +146 -16
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Info.plist +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Kindly +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios-simulator.abi.json +598 -432
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +4 -4
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios-simulator.swiftinterface +3 -4
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/x86_64-apple-ios-simulator.abi.json +598 -432
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +4 -4
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +3 -4
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/module.modulemap +0 -1
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/_CodeSignature/CodeResources +25 -25
- package/ios/KindlyReactNative.mm +24 -6
- package/ios/KindlyReactNativeImpl.swift +91 -3
- package/lib/module/NativeKindlyReactNative.js.map +1 -1
- package/lib/module/index.js +67 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeKindlyReactNative.d.ts +6 -1
- package/lib/typescript/src/NativeKindlyReactNative.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +75 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeKindlyReactNative.ts +8 -1
- package/src/index.tsx +113 -1
- package/ios/Frameworks/Kindly.xcframework/_CodeSignature/CodeRequirements-1 +0 -0
package/README.md
CHANGED
|
@@ -37,6 +37,33 @@ KindlySDK.start({
|
|
|
37
37
|
},
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
+
// Identity providers that issue more than a single JWT - a companion access
|
|
41
|
+
// token, an issuer, or an opaque token whose expiry only you know - use
|
|
42
|
+
// authCredentialsCallback instead. It wins when both are given.
|
|
43
|
+
KindlySDK.start({
|
|
44
|
+
botKey: 'YOUR_BOT_KEY',
|
|
45
|
+
market: 'general',
|
|
46
|
+
authCredentialsCallback: async (chatId) => {
|
|
47
|
+
const tokens = await yourAuthBackend.getTokens({ chatId });
|
|
48
|
+
return {
|
|
49
|
+
idToken: tokens.idToken, // optional
|
|
50
|
+
accessToken: tokens.accessToken, // optional
|
|
51
|
+
issuer: 'https://idp.example.com/', // optional
|
|
52
|
+
expiresAt: tokens.expiresAtUnixSeconds, // optional, Unix seconds
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Only an access token? Return only that. It becomes the bearer.
|
|
58
|
+
KindlySDK.start({
|
|
59
|
+
botKey: 'YOUR_BOT_KEY',
|
|
60
|
+
market: 'general',
|
|
61
|
+
authCredentialsCallback: async () => ({
|
|
62
|
+
accessToken: await yourAuthBackend.getAccessToken(),
|
|
63
|
+
issuer: 'https://idp.example.com/',
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
|
|
40
67
|
// 2. Open the chat UI when the user wants it
|
|
41
68
|
<Button title="Open chat" onPress={() => KindlySDK.launchChat()} />
|
|
42
69
|
|
|
@@ -59,7 +86,7 @@ KindlySDK.setDelegate({
|
|
|
59
86
|
|
|
60
87
|
| Method | Purpose |
|
|
61
88
|
|---|---|
|
|
62
|
-
| `start({botKey, market, languageCode?, authTokenCallback?})` | Initialize the SDK. Must be called before any other method. `market` is **required** (Markets feature); the SDK loads that market's settings with no fallback. |
|
|
89
|
+
| `start({botKey, market, languageCode?, authTokenCallback?, authCredentialsCallback?})` | Initialize the SDK. Must be called before any other method. `market` is **required** (Markets feature); the SDK loads that market's settings with no fallback. Use `authCredentialsCallback` for identity providers that issue more than a single JWT. |
|
|
63
90
|
| `displayChat({languageCode?, triggerDialogueId?})` | Show the chat UI. |
|
|
64
91
|
| `launchChat({triggerDialogueId?})` | Connect (if needed) and show chat. Recommended on Android. |
|
|
65
92
|
| `closeChat()` | Dismiss the UI without ending the session. |
|
|
@@ -67,6 +94,32 @@ KindlySDK.setDelegate({
|
|
|
67
94
|
| `kill()` | Tear the SDK down completely; call `start()` again before reuse. |
|
|
68
95
|
| `setLanguage(code)` | Update the language for the current session. |
|
|
69
96
|
|
|
97
|
+
### Authentication
|
|
98
|
+
|
|
99
|
+
| Method | Purpose |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `authenticate()` | Re-authenticate the live chat with fresh credentials. For a sign-in mid-session; expiry is handled by the SDK on its own. |
|
|
102
|
+
| `deauthenticate()` | Sign out of the chat without ending it. History is kept, the chat continues anonymously. |
|
|
103
|
+
|
|
104
|
+
Every field of `KindlyAuthCredentials` is optional, but set at least one of `idToken` /
|
|
105
|
+
`accessToken`. Whichever you supply becomes the `Authorization: Bearer`, id token first. A provider
|
|
106
|
+
that issues only an access token is a normal, supported case, not a workaround. Credentials with
|
|
107
|
+
neither token cannot authenticate and the chat stays anonymous.
|
|
108
|
+
|
|
109
|
+
Send `accessToken` alongside `idToken` only when the two really are a pair from the same provider.
|
|
110
|
+
The backend verifies the companion against the id token's `at_hash` and rejects a mismatch, so
|
|
111
|
+
passing the same value in both fields fails an auth that would otherwise have succeeded. The SDK
|
|
112
|
+
drops a companion that duplicates the id token rather than sending it.
|
|
113
|
+
|
|
114
|
+
The SDK refreshes credentials 30 seconds before they expire, using whichever is earliest of the
|
|
115
|
+
backend's reported expiry, your `expiresAt`, and the `exp` claim of the bearer. If none is known the
|
|
116
|
+
credentials are treated as non-expiring - a trap for opaque tokens, so set `expiresAt` there. A
|
|
117
|
+
token the SDK cannot parse means the expiry is unknown, never that the token is expired.
|
|
118
|
+
|
|
119
|
+
`deauthenticate()` is safe to call when the chat is already anonymous. The local credentials are
|
|
120
|
+
cleared even if the backend call fails, so a sign-out is never left half done, but the promise still
|
|
121
|
+
rejects so you know the backend did not hear about it.
|
|
122
|
+
|
|
70
123
|
### Messaging
|
|
71
124
|
|
|
72
125
|
| Method | Purpose |
|
|
@@ -22,6 +22,8 @@ import kotlinx.coroutines.runBlocking
|
|
|
22
22
|
import kotlinx.coroutines.withTimeoutOrNull
|
|
23
23
|
import no.kindly.chatsdk.chat.AuthTokenCallback
|
|
24
24
|
import no.kindly.chatsdk.chat.ChatKindlySDK
|
|
25
|
+
import no.kindly.chatsdk.chat.KindlyAuthCredentials
|
|
26
|
+
import no.kindly.chatsdk.chat.KindlyAuthCredentialsProvider
|
|
25
27
|
import no.kindly.chatsdk.chat.common.CallbackHandover
|
|
26
28
|
import no.kindly.chatsdk.chat.common.KindlySDKInteraction
|
|
27
29
|
import no.kindly.chatsdk.chat.domain.MessageChat
|
|
@@ -59,6 +61,7 @@ class KindlyReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
59
61
|
|
|
60
62
|
// Pending request maps for the sync round-trips.
|
|
61
63
|
private val authPending = ConcurrentHashMap<String, CompletableDeferred<String?>>()
|
|
64
|
+
private val credentialsPending = ConcurrentHashMap<String, CompletableDeferred<ReadableMap?>>()
|
|
62
65
|
private val shouldHandlePending = ConcurrentHashMap<String, CompletableDeferred<Boolean>>()
|
|
63
66
|
|
|
64
67
|
// Delegate bridge — set on ChatKindlySDK.`interface` when at least one
|
|
@@ -86,13 +89,24 @@ class KindlyReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
86
89
|
// Best-effort — clear any pending deferreds so the JS side doesn't hang.
|
|
87
90
|
authPending.values.forEach { it.complete(null) }
|
|
88
91
|
authPending.clear()
|
|
92
|
+
// A null credentials map yields a credential with no token, which the
|
|
93
|
+
// provider below rejects, so the chat falls back to anonymous instead of
|
|
94
|
+
// sitting on a deferred nobody will ever complete.
|
|
95
|
+
credentialsPending.values.forEach { it.complete(null) }
|
|
96
|
+
credentialsPending.clear()
|
|
89
97
|
shouldHandlePending.values.forEach { it.complete(true) }
|
|
90
98
|
shouldHandlePending.clear()
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
// MARK: - Lifecycle
|
|
94
102
|
|
|
95
|
-
override fun start(
|
|
103
|
+
override fun start(
|
|
104
|
+
botKey: String,
|
|
105
|
+
languageCode: String?,
|
|
106
|
+
market: String,
|
|
107
|
+
hasAuthCallback: Boolean,
|
|
108
|
+
hasAuthCredentialsCallback: Boolean,
|
|
109
|
+
) {
|
|
96
110
|
val app = context.applicationContext as? Application
|
|
97
111
|
if (app == null) return
|
|
98
112
|
|
|
@@ -120,15 +134,69 @@ class KindlyReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
120
134
|
null
|
|
121
135
|
}
|
|
122
136
|
|
|
137
|
+
// The credentials provider wins when both are wired, matching the native SDKs.
|
|
138
|
+
val credentialsProvider: KindlyAuthCredentialsProvider? = if (hasAuthCredentialsCallback) {
|
|
139
|
+
KindlyAuthCredentialsProvider { request ->
|
|
140
|
+
val requestId = UUID.randomUUID().toString()
|
|
141
|
+
val pending = CompletableDeferred<ReadableMap?>()
|
|
142
|
+
credentialsPending[requestId] = pending
|
|
143
|
+
|
|
144
|
+
emitEvent("KindlyGetAuthCredentials", Arguments.createMap().apply {
|
|
145
|
+
putString("requestId", requestId)
|
|
146
|
+
putString("chatId", request.chatId)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
val map = pending.await()
|
|
150
|
+
fun str(key: String): String? =
|
|
151
|
+
map?.takeIf { it.hasKey(key) && !it.isNull(key) }?.getString(key)
|
|
152
|
+
|
|
153
|
+
// Both tokens are optional and either one alone is a valid credential:
|
|
154
|
+
// the SDK picks the bearer itself via `bearerToken`. The only thing that
|
|
155
|
+
// disqualifies a credential is carrying neither, which is what
|
|
156
|
+
// `hasToken` answers. Never gate on `idToken`.
|
|
157
|
+
val credentials = KindlyAuthCredentials(
|
|
158
|
+
idToken = str("idToken"),
|
|
159
|
+
accessToken = str("accessToken"),
|
|
160
|
+
issuer = str("issuer"),
|
|
161
|
+
// JS numbers cross the bridge as Double.
|
|
162
|
+
expiresAt = map?.takeIf { it.hasKey("expiresAt") && !it.isNull("expiresAt") }
|
|
163
|
+
?.getDouble("expiresAt")?.toLong(),
|
|
164
|
+
)
|
|
165
|
+
if (!credentials.hasToken) {
|
|
166
|
+
throw IllegalStateException("Auth credentials not provided")
|
|
167
|
+
}
|
|
168
|
+
credentials
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
null
|
|
172
|
+
}
|
|
173
|
+
|
|
123
174
|
ChatKindlySDK.start(
|
|
124
175
|
application = app,
|
|
125
176
|
botKey = botKey,
|
|
126
177
|
languageCode = languageCode ?: "en",
|
|
127
178
|
market = market,
|
|
128
179
|
authTokenCallback = authCallback,
|
|
180
|
+
authCredentialsProvider = credentialsProvider,
|
|
129
181
|
)
|
|
130
182
|
}
|
|
131
183
|
|
|
184
|
+
override fun authenticate(promise: Promise) {
|
|
185
|
+
scope.launch {
|
|
186
|
+
runCatching { ChatKindlySDK.authenticate().await() }
|
|
187
|
+
.onSuccess { promise.resolve(null) }
|
|
188
|
+
.onFailure { promise.reject("AUTH_FAILED", it.message, it) }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
override fun deauthenticate(promise: Promise) {
|
|
193
|
+
scope.launch {
|
|
194
|
+
runCatching { ChatKindlySDK.deauthenticate().await() }
|
|
195
|
+
.onSuccess { promise.resolve(null) }
|
|
196
|
+
.onFailure { promise.reject("DEAUTH_FAILED", it.message, it) }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
132
200
|
override fun displayChat(languageCode: String?, triggerDialogueId: String?) {
|
|
133
201
|
val act = getCurrentActivity() ?: return
|
|
134
202
|
ChatKindlySDK.displayChat(act)
|
|
@@ -309,6 +377,10 @@ class KindlyReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
309
377
|
authPending.remove(requestId)?.complete(token)
|
|
310
378
|
}
|
|
311
379
|
|
|
380
|
+
override fun respondToAuthCredentials(requestId: String, credentials: ReadableMap?) {
|
|
381
|
+
credentialsPending.remove(requestId)?.complete(credentials)
|
|
382
|
+
}
|
|
383
|
+
|
|
312
384
|
override fun respondToShouldHandle(requestId: String, value: Boolean) {
|
|
313
385
|
shouldHandlePending.remove(requestId)?.complete(value)
|
|
314
386
|
}
|
|
Binary file
|