@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.
Files changed (44) hide show
  1. package/README.md +54 -1
  2. package/android/src/main/java/com/kindlyai/reactnative/KindlyReactNativeModule.kt +73 -1
  3. package/ios/Frameworks/Kindly.xcframework/_CodeSignature/CodeDirectory +0 -0
  4. package/ios/Frameworks/Kindly.xcframework/_CodeSignature/CodeResources +52 -172
  5. package/ios/Frameworks/Kindly.xcframework/_CodeSignature/CodeSignature +0 -0
  6. package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Assets.car +0 -0
  7. package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/ConfirmationWindow.nib +0 -0
  8. package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Headers/Kindly-Swift.h +72 -7
  9. package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Info.plist +0 -0
  10. package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Kindly +0 -0
  11. package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios.abi.json +598 -432
  12. package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios.private.swiftinterface +4 -4
  13. package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  14. package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios.swiftinterface +3 -4
  15. package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/module.modulemap +0 -1
  16. package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/_CodeSignature/CodeResources +17 -17
  17. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Assets.car +0 -0
  18. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/ConfirmationWindow.nib +0 -0
  19. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Headers/Kindly-Swift.h +146 -16
  20. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Info.plist +0 -0
  21. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Kindly +0 -0
  22. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios-simulator.abi.json +598 -432
  23. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +4 -4
  24. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  25. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios-simulator.swiftinterface +3 -4
  26. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/x86_64-apple-ios-simulator.abi.json +598 -432
  27. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +4 -4
  28. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  29. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +3 -4
  30. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/module.modulemap +0 -1
  31. package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/_CodeSignature/CodeResources +25 -25
  32. package/ios/KindlyReactNative.mm +24 -6
  33. package/ios/KindlyReactNativeImpl.swift +91 -3
  34. package/lib/module/NativeKindlyReactNative.js.map +1 -1
  35. package/lib/module/index.js +67 -1
  36. package/lib/module/index.js.map +1 -1
  37. package/lib/typescript/src/NativeKindlyReactNative.d.ts +6 -1
  38. package/lib/typescript/src/NativeKindlyReactNative.d.ts.map +1 -1
  39. package/lib/typescript/src/index.d.ts +75 -0
  40. package/lib/typescript/src/index.d.ts.map +1 -1
  41. package/package.json +1 -1
  42. package/src/NativeKindlyReactNative.ts +8 -1
  43. package/src/index.tsx +113 -1
  44. 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(botKey: String, languageCode: String?, market: String, hasAuthCallback: Boolean) {
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
  }