@kindly-ai/react-native 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/KindlyReactNative.podspec +36 -0
- package/LICENSE +20 -0
- package/README.md +127 -0
- package/android/build.gradle +113 -0
- package/android/maven-repo/ai/kindly/sdk/1.0.93/sdk-1.0.93.aar +0 -0
- package/android/maven-repo/ai/kindly/sdk/1.0.93/sdk-1.0.93.pom +125 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/kindlyai/reactnative/KindlyReactNativeModule.kt +445 -0
- package/android/src/main/java/com/kindlyai/reactnative/KindlyReactNativePackage.kt +38 -0
- package/ios/Frameworks/Kindly.xcframework/Info.plist +44 -0
- package/ios/Frameworks/Kindly.xcframework/_CodeSignature/CodeDirectory +0 -0
- package/ios/Frameworks/Kindly.xcframework/_CodeSignature/CodeRequirements +0 -0
- package/ios/Frameworks/Kindly.xcframework/_CodeSignature/CodeRequirements-1 +0 -0
- package/ios/Frameworks/Kindly.xcframework/_CodeSignature/CodeResources +578 -0
- 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/Config.plist +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 +331 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Headers/Kindly.h +18 -0
- 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 +28769 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios.private.swiftinterface +547 -0
- 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 +543 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/Modules/module.modulemap +11 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/PrivacyInfo.xcprivacy +83 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64/Kindly.framework/_CodeSignature/CodeResources +223 -0
- 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/Config.plist +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 +658 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Headers/Kindly.h +18 -0
- 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 +28769 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +547 -0
- 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 +543 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/x86_64-apple-ios-simulator.abi.json +28769 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/Kindly.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +547 -0
- 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 +543 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/Modules/module.modulemap +11 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/PrivacyInfo.xcprivacy +83 -0
- package/ios/Frameworks/Kindly.xcframework/ios-arm64_x86_64-simulator/Kindly.framework/_CodeSignature/CodeResources +267 -0
- package/ios/KindlyReactNative.h +18 -0
- package/ios/KindlyReactNative.mm +234 -0
- package/ios/KindlyReactNativeImpl.swift +410 -0
- package/lib/module/NativeKindlyReactNative.js +22 -0
- package/lib/module/NativeKindlyReactNative.js.map +1 -0
- package/lib/module/index.js +280 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeKindlyReactNative.d.ts +47 -0
- package/lib/typescript/src/NativeKindlyReactNative.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +150 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +121 -0
- package/src/NativeKindlyReactNative.ts +80 -0
- package/src/index.tsx +353 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
// Swift implementation of the Kindly RN bridge — receives forwarded calls
|
|
2
|
+
// from `KindlyReactNative.mm` and delegates to the public Kindly iOS SDK
|
|
3
|
+
// (`import Kindly`, then `KindlyChatClient.start(...)` etc.).
|
|
4
|
+
//
|
|
5
|
+
// The bridge between this Swift class and the codegen-generated Obj-C++
|
|
6
|
+
// TurboModule is the auto-generated `KindlyReactNative-Swift.h` header
|
|
7
|
+
// (filename derives from the pod's `s.module_name`/`s.name`).
|
|
8
|
+
//
|
|
9
|
+
// Symmetric with `flutter-sdk/ios/Classes/KindlyPlugin.swift`. The biggest
|
|
10
|
+
// difference: RN can't make synchronous calls JS→native→JS→native, so the
|
|
11
|
+
// blocking-bool callbacks (`shouldHandleLink`, `shouldHandleNotification`)
|
|
12
|
+
// use a request-id round-trip — we put a `CompletableDeferred`-equivalent
|
|
13
|
+
// (`DispatchSemaphore` + locked dictionary) keyed by UUID, fire the request
|
|
14
|
+
// event, JS replies via `respondToShouldHandle(requestId, value)`, native
|
|
15
|
+
// signals the semaphore. 5-second timeout, defaults to `true`. Same
|
|
16
|
+
// semantics as the Flutter pattern.
|
|
17
|
+
|
|
18
|
+
import Combine
|
|
19
|
+
import Foundation
|
|
20
|
+
import Kindly
|
|
21
|
+
import React
|
|
22
|
+
import UIKit
|
|
23
|
+
|
|
24
|
+
@objc public class KindlyReactNativeImpl: NSObject {
|
|
25
|
+
|
|
26
|
+
// The `RCTEventEmitter`-typed Obj-C class instance. Held weakly so we
|
|
27
|
+
// don't extend its lifetime past RN's bridge teardown.
|
|
28
|
+
private weak var emitter: KindlyReactNative?
|
|
29
|
+
|
|
30
|
+
// State publisher cache so `isChatDisplayed` can answer synchronously.
|
|
31
|
+
private var stateSubscription: AnyCancellable?
|
|
32
|
+
private var lastIsChatDisplayed: Bool = false
|
|
33
|
+
|
|
34
|
+
// Pending request maps for the sync round-trips. Each entry pairs a
|
|
35
|
+
// request UUID with a (semaphore, answer) tuple. Native blocks on the
|
|
36
|
+
// semaphore (5s); JS replies via respondToAuthToken / respondToShouldHandle
|
|
37
|
+
// which signals the semaphore.
|
|
38
|
+
private var authPending: [String: PendingAuth] = [:]
|
|
39
|
+
private var shouldHandlePending: [String: PendingBool] = [:]
|
|
40
|
+
private let pendingLock = NSLock()
|
|
41
|
+
|
|
42
|
+
private struct PendingAuth {
|
|
43
|
+
let semaphore: DispatchSemaphore
|
|
44
|
+
var token: String?
|
|
45
|
+
}
|
|
46
|
+
private struct PendingBool {
|
|
47
|
+
let semaphore: DispatchSemaphore
|
|
48
|
+
var value: Bool = true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// KindlyChatClientDelegate bridge — set as the delegate when at least one
|
|
52
|
+
// delegate flag is on. The bridge calls back into this class's helpers.
|
|
53
|
+
private var delegateBridge: KindlyDelegateBridge?
|
|
54
|
+
|
|
55
|
+
@objc public init(emitter: KindlyReactNative) {
|
|
56
|
+
self.emitter = emitter
|
|
57
|
+
super.init()
|
|
58
|
+
// Subscribe to the SDK's state publisher so isChatDisplayed is sync.
|
|
59
|
+
self.stateSubscription = KindlyChatClient.state.sink { [weak self] state in
|
|
60
|
+
self?.lastIsChatDisplayed = state.isChatDisplayed
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// MARK: - Lifecycle
|
|
65
|
+
|
|
66
|
+
@objc public func start(botKey: String,
|
|
67
|
+
languageCode: String?,
|
|
68
|
+
hasAuthCallback: Bool) {
|
|
69
|
+
let lang = languageCode ?? "en"
|
|
70
|
+
|
|
71
|
+
let authCallback: AuthTokenCallback? = hasAuthCallback ? { [weak self] _, promise in
|
|
72
|
+
// Build a UUID-keyed pending entry, fire the JS event, block native
|
|
73
|
+
// for up to 5s waiting for `respondToAuthToken(requestId:token:)`.
|
|
74
|
+
guard let self = self else {
|
|
75
|
+
promise.reject(NSError(domain: "KindlyReactNative",
|
|
76
|
+
code: -1,
|
|
77
|
+
userInfo: [NSLocalizedDescriptionKey: "self deallocated"]))
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
let requestId = UUID().uuidString
|
|
81
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
82
|
+
|
|
83
|
+
self.pendingLock.lock()
|
|
84
|
+
self.authPending[requestId] = PendingAuth(semaphore: semaphore, token: nil)
|
|
85
|
+
self.pendingLock.unlock()
|
|
86
|
+
|
|
87
|
+
self.emit(name: "KindlyGetAuthToken", body: ["requestId": requestId])
|
|
88
|
+
|
|
89
|
+
// Wait off the calling thread (KindlyChatClient invokes the auth
|
|
90
|
+
// callback on a background queue; blocking is OK there).
|
|
91
|
+
DispatchQueue.global().async {
|
|
92
|
+
_ = semaphore.wait(timeout: .now() + 30.0)
|
|
93
|
+
self.pendingLock.lock()
|
|
94
|
+
let entry = self.authPending.removeValue(forKey: requestId)
|
|
95
|
+
self.pendingLock.unlock()
|
|
96
|
+
if let token = entry?.token {
|
|
97
|
+
promise.fulfill(token)
|
|
98
|
+
} else {
|
|
99
|
+
promise.reject(NSError(domain: "KindlyReactNative",
|
|
100
|
+
code: -1,
|
|
101
|
+
userInfo: [NSLocalizedDescriptionKey: "Auth token not provided"]))
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} : nil
|
|
105
|
+
|
|
106
|
+
KindlyChatClient.start(botKey: botKey, languageCode: lang, authTokenCallback: authCallback)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@objc public func displayChat(languageCode: String?, triggerDialogueId: String?) {
|
|
110
|
+
KindlyChatClient.displayChat(languageCode: languageCode, triggerDialogueId: triggerDialogueId)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@objc public func launchChat(triggerDialogueId: String?) {
|
|
114
|
+
KindlyChatClient.displayChat(languageCode: nil, triggerDialogueId: triggerDialogueId)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@objc public func closeChat() {
|
|
118
|
+
KindlyChatClient.closeChat()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@objc public func endChat(resolve: @escaping RCTPromiseResolveBlock,
|
|
122
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
123
|
+
KindlyChatClient.endChat().then { _ in
|
|
124
|
+
resolve(NSNull())
|
|
125
|
+
}.catch { error in
|
|
126
|
+
reject("END_CHAT_FAILED", error.localizedDescription, error)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@objc public func kill(resolve: @escaping RCTPromiseResolveBlock,
|
|
131
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
132
|
+
KindlyChatClient.kill().then { _ in
|
|
133
|
+
resolve(NSNull())
|
|
134
|
+
}.catch { error in
|
|
135
|
+
reject("KILL_FAILED", error.localizedDescription, error)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@objc public func setLanguage(code: String,
|
|
140
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
141
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
142
|
+
KindlyChatClient.setLanguage(code).then { _ in
|
|
143
|
+
resolve(NSNull())
|
|
144
|
+
}.catch { error in
|
|
145
|
+
reject("SET_LANGUAGE_FAILED", error.localizedDescription, error)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// MARK: - Messaging
|
|
150
|
+
|
|
151
|
+
@objc public func sendMessage(text: String,
|
|
152
|
+
newContext: [String: String]?,
|
|
153
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
154
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
155
|
+
KindlyChatClient.sendMessage(text, newContext: newContext).then { reconciled in
|
|
156
|
+
resolve(serializeChatMessage(reconciled))
|
|
157
|
+
}.catch { error in
|
|
158
|
+
reject("SEND_MESSAGE_FAILED", error.localizedDescription, error)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@objc public func setNewContext(_ context: [String: String]) {
|
|
163
|
+
KindlyChatClient.setNewContext(context)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@objc public func clearNewContext() {
|
|
167
|
+
KindlyChatClient.clearNewContext()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@objc public func triggerDialogue(_ dialogueId: String) {
|
|
171
|
+
KindlyChatClient.triggerDialogue(id: dialogueId)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@objc public func handleUrl(_ urlString: String) -> Bool {
|
|
175
|
+
guard let url = URL(string: urlString) else { return false }
|
|
176
|
+
return KindlyChatClient.handleURL(url)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// MARK: - Theme
|
|
180
|
+
|
|
181
|
+
@objc public func setCustomTheme(_ colors: [String: Any]) {
|
|
182
|
+
func color(_ key: String) -> UIColor? {
|
|
183
|
+
guard let raw = colors[key] as? Int else { return nil }
|
|
184
|
+
return UIColor(argb: raw)
|
|
185
|
+
}
|
|
186
|
+
KindlyChatClient.setCustomTheme(
|
|
187
|
+
background: color("background"),
|
|
188
|
+
botMessageBackground: color("botMessageBackground"),
|
|
189
|
+
botMessageText: color("botMessageText"),
|
|
190
|
+
buttonBackground: color("buttonBackground"),
|
|
191
|
+
buttonOutline: color("buttonOutline"),
|
|
192
|
+
buttonSelectedBackground: color("buttonSelectedBackground"),
|
|
193
|
+
buttonText: color("buttonText"),
|
|
194
|
+
defaultShadow: color("defaultShadow"),
|
|
195
|
+
inputBackground: color("inputBackground"),
|
|
196
|
+
inputCursor: color("inputCursor"),
|
|
197
|
+
inputText: color("inputText"),
|
|
198
|
+
navBarBackground: color("navBarBackground"),
|
|
199
|
+
navBarText: color("navBarText"),
|
|
200
|
+
userMessageBackground: color("userMessageBackground"),
|
|
201
|
+
userMessageText: color("userMessageText")
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@objc public func clearCustomTheme() {
|
|
206
|
+
KindlyChatClient.clearCustomTheme()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// MARK: - Push
|
|
210
|
+
|
|
211
|
+
@objc public func setAPNSDeviceToken(_ token: String) {
|
|
212
|
+
KindlyChatClient.setAPNSDeviceToken(token)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@objc public func handleNotification(_ data: [AnyHashable: Any]) {
|
|
216
|
+
KindlyChatClient.notificationReceived(data)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@objc public func isKindlyNotification(_ data: [AnyHashable: Any]) -> Bool {
|
|
220
|
+
return KindlyChatClient.isKindlyNotification(data)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// MARK: - State / settings
|
|
224
|
+
|
|
225
|
+
@objc public func saveAuthToken(_ token: String) -> Bool {
|
|
226
|
+
return KindlyChatClient.saveAuthToken(token)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@objc public func isChatDisplayed() -> Bool {
|
|
230
|
+
return lastIsChatDisplayed
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@objc public func setVerboseLogging(_ enabled: Bool) {
|
|
234
|
+
KindlyChatClient.verboseLogging = enabled
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@objc public func setCrashReporting(_ enabled: Bool) {
|
|
238
|
+
KindlyChatClient.enableCrashReporting = enabled
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// MARK: - Round-trip responders (called by JS)
|
|
242
|
+
|
|
243
|
+
@objc public func respondToAuthToken(_ requestId: String, token: String?) {
|
|
244
|
+
pendingLock.lock()
|
|
245
|
+
defer { pendingLock.unlock() }
|
|
246
|
+
guard var entry = authPending[requestId] else { return }
|
|
247
|
+
entry.token = token
|
|
248
|
+
authPending[requestId] = entry
|
|
249
|
+
entry.semaphore.signal()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@objc public func respondToShouldHandle(_ requestId: String, value: Bool) {
|
|
253
|
+
pendingLock.lock()
|
|
254
|
+
defer { pendingLock.unlock() }
|
|
255
|
+
guard var entry = shouldHandlePending[requestId] else { return }
|
|
256
|
+
entry.value = value
|
|
257
|
+
shouldHandlePending[requestId] = entry
|
|
258
|
+
entry.semaphore.signal()
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// MARK: - Delegate gating
|
|
262
|
+
|
|
263
|
+
@objc public func setDelegate(buttonPressed: Bool,
|
|
264
|
+
shouldHandleLink: Bool,
|
|
265
|
+
shouldHandleNotification: Bool,
|
|
266
|
+
onHandover: Bool) {
|
|
267
|
+
let bridge = delegateBridge ?? KindlyDelegateBridge(impl: self)
|
|
268
|
+
bridge.hasButtonPressed = buttonPressed
|
|
269
|
+
bridge.hasShouldHandleLink = shouldHandleLink
|
|
270
|
+
bridge.hasShouldHandleNotification = shouldHandleNotification
|
|
271
|
+
delegateBridge = bridge
|
|
272
|
+
|
|
273
|
+
let anySet = buttonPressed || shouldHandleLink || shouldHandleNotification
|
|
274
|
+
KindlyChatClient.delegate = anySet ? bridge : nil
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// MARK: - Internal helpers used by the bridge
|
|
278
|
+
|
|
279
|
+
fileprivate func emit(name: String, body: Any?) {
|
|
280
|
+
emitter?.emitEvent(name, body: body)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/// Block native for up to 5s while JS computes a Bool answer. Default
|
|
284
|
+
/// `true` on timeout / no-listener — never hang the chat UI.
|
|
285
|
+
fileprivate func blockingShouldHandle(eventName: String, payload: [String: Any]) -> Bool {
|
|
286
|
+
let requestId = UUID().uuidString
|
|
287
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
288
|
+
|
|
289
|
+
pendingLock.lock()
|
|
290
|
+
shouldHandlePending[requestId] = PendingBool(semaphore: semaphore, value: true)
|
|
291
|
+
pendingLock.unlock()
|
|
292
|
+
|
|
293
|
+
var body = payload
|
|
294
|
+
body["requestId"] = requestId
|
|
295
|
+
DispatchQueue.main.async { [weak self] in
|
|
296
|
+
self?.emit(name: eventName, body: body)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
_ = semaphore.wait(timeout: .now() + 5.0)
|
|
300
|
+
|
|
301
|
+
pendingLock.lock()
|
|
302
|
+
let entry = shouldHandlePending.removeValue(forKey: requestId)
|
|
303
|
+
pendingLock.unlock()
|
|
304
|
+
return entry?.value ?? true
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
fileprivate func emitButtonPressed(button: ExternalChatButton, chatLog: [ExternalChatMessage]) {
|
|
308
|
+
let payload: [String: Any] = [
|
|
309
|
+
"button": serializeExternalChatButton(button),
|
|
310
|
+
"chatLog": chatLog.map { serializeChatMessage($0) },
|
|
311
|
+
]
|
|
312
|
+
DispatchQueue.main.async { [weak self] in
|
|
313
|
+
self?.emit(name: "KindlyOnButtonPressed", body: payload)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// MARK: - KindlyChatClientDelegate bridge
|
|
319
|
+
|
|
320
|
+
/// Bridges the iOS SDK's `KindlyChatClientDelegate` protocol to the JS-side
|
|
321
|
+
/// callbacks registered via `KindlySDK.setDelegate(...)`. Symmetric with the
|
|
322
|
+
/// Flutter SDK's bridge — fire-and-forget for didPressButton, blocking-bool
|
|
323
|
+
/// (5s timeout, default true) for shouldHandleLink / shouldHandleNotification.
|
|
324
|
+
private final class KindlyDelegateBridge: NSObject, KindlyChatClientDelegate {
|
|
325
|
+
weak var impl: KindlyReactNativeImpl?
|
|
326
|
+
init(impl: KindlyReactNativeImpl) { self.impl = impl; super.init() }
|
|
327
|
+
|
|
328
|
+
var hasButtonPressed: Bool = false
|
|
329
|
+
var hasShouldHandleLink: Bool = false
|
|
330
|
+
var hasShouldHandleNotification: Bool = false
|
|
331
|
+
|
|
332
|
+
func didPressButton(chatButton: ExternalChatButton, chatLog: [ExternalChatMessage]) {
|
|
333
|
+
guard hasButtonPressed else { return }
|
|
334
|
+
impl?.emitButtonPressed(button: chatButton, chatLog: chatLog)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
func shouldHandleLink(url: URL) -> Bool {
|
|
338
|
+
guard hasShouldHandleLink, let impl = impl else { return true }
|
|
339
|
+
return impl.blockingShouldHandle(
|
|
340
|
+
eventName: "KindlyShouldHandleLink",
|
|
341
|
+
payload: ["url": url.absoluteString]
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
func shouldHandleNotification(notification: ExternalNotification) -> Bool {
|
|
346
|
+
guard hasShouldHandleNotification, let impl = impl else { return true }
|
|
347
|
+
let payload: [String: Any] = [
|
|
348
|
+
"notification": [
|
|
349
|
+
"id": notification.id,
|
|
350
|
+
"title": notification.title,
|
|
351
|
+
"body": notification.body,
|
|
352
|
+
"data": stringifyUserInfo(notification.userInfo),
|
|
353
|
+
],
|
|
354
|
+
]
|
|
355
|
+
return impl.blockingShouldHandle(
|
|
356
|
+
eventName: "KindlyShouldHandleNotification",
|
|
357
|
+
payload: payload
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// MARK: - Serialization helpers (file-private)
|
|
363
|
+
|
|
364
|
+
private func serializeChatMessage(_ message: ExternalChatMessage) -> [String: Any] {
|
|
365
|
+
var dict = message.toDictionary()
|
|
366
|
+
if let created = dict["created"] as? Date {
|
|
367
|
+
dict["created"] = ISO8601DateFormatter().string(from: created)
|
|
368
|
+
}
|
|
369
|
+
return dict
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/// `ExternalChatButton`'s fields are package-internal — reflect at runtime.
|
|
373
|
+
private func serializeExternalChatButton(_ button: ExternalChatButton) -> [String: Any] {
|
|
374
|
+
var dict: [String: Any] = [:]
|
|
375
|
+
for child in Mirror(reflecting: button).children {
|
|
376
|
+
guard let label = child.label else { continue }
|
|
377
|
+
let value = child.value
|
|
378
|
+
let valueMirror = Mirror(reflecting: value)
|
|
379
|
+
if valueMirror.displayStyle == .optional {
|
|
380
|
+
if let unwrapped = valueMirror.children.first?.value {
|
|
381
|
+
dict[label] = unwrapped
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
dict[label] = value
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return dict
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private func stringifyUserInfo(_ userInfo: [AnyHashable: Any]) -> [String: String] {
|
|
391
|
+
var out: [String: String] = [:]
|
|
392
|
+
for (k, v) in userInfo {
|
|
393
|
+
if let key = k as? String {
|
|
394
|
+
out[key] = String(describing: v)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return out
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
extension UIColor {
|
|
401
|
+
/// Initialise from a 32-bit ARGB integer (Flutter `Color.value` convention).
|
|
402
|
+
fileprivate convenience init(argb: Int) {
|
|
403
|
+
self.init(
|
|
404
|
+
red: CGFloat((argb >> 16) & 0xFF) / 255.0,
|
|
405
|
+
green: CGFloat((argb >> 8) & 0xFF) / 255.0,
|
|
406
|
+
blue: CGFloat(argb & 0xFF) / 255.0,
|
|
407
|
+
alpha: CGFloat((argb >> 24) & 0xFF) / 255.0
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// TurboModule spec for the @kindly-ai/react-native package.
|
|
4
|
+
//
|
|
5
|
+
// Codegen INPUT — `react-native` reads this at build time and generates the
|
|
6
|
+
// abstract Spec class on iOS (Obj-C++ header) and Android (Kotlin abstract
|
|
7
|
+
// class). The native module classes extend those.
|
|
8
|
+
//
|
|
9
|
+
// Convention used here: every method takes flat primitive parameters and
|
|
10
|
+
// returns either a primitive or a Promise. We deliberately avoid nested
|
|
11
|
+
// struct args (`(args: { ... })`) because codegen generates JS::Native*Args
|
|
12
|
+
// C++ structs for those, which add fragility without buying anything for
|
|
13
|
+
// our use case. This matches the scaffolded `multiply()` example pattern.
|
|
14
|
+
//
|
|
15
|
+
// Filename MUST match `Native<ModuleName>.ts`. ModuleName here is
|
|
16
|
+
// `KindlyReactNative` — this becomes the native module name returned by
|
|
17
|
+
// `getName()` and the registry key. Customer-facing imports come from the
|
|
18
|
+
// package, not the module name.
|
|
19
|
+
|
|
20
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
21
|
+
export default TurboModuleRegistry.getEnforcing('KindlyReactNative');
|
|
22
|
+
//# sourceMappingURL=NativeKindlyReactNative.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeKindlyReactNative.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,mBAAmB,QAAQ,cAAc;AA8DlD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,mBAAmB,CAAC","ignoreList":[]}
|