@qafka/react-native 2.0.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.
Files changed (178) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/CONTRIBUTING.md +92 -0
  3. package/LICENSE +22 -0
  4. package/README.md +109 -0
  5. package/SECURITY.md +67 -0
  6. package/android/build.gradle +35 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/java/com/qafka/attestation/QafkaAttestationModule.kt +92 -0
  9. package/android/src/main/java/com/qafka/attestation/QafkaAttestationPackage.kt +22 -0
  10. package/android/src/main/java/com/qafka/audio/QafkaAudioModule.kt +290 -0
  11. package/android/src/main/java/com/qafka/clipboard/QafkaClipboardModule.kt +28 -0
  12. package/android/src/main/java/com/qafka/storage/QafkaStorageModule.kt +80 -0
  13. package/app.plugin.js +1 -0
  14. package/dist/QafkaSDK.d.ts +174 -0
  15. package/dist/QafkaSDK.js +461 -0
  16. package/dist/cards/bindings/resolveFieldName.d.ts +25 -0
  17. package/dist/cards/bindings/resolveFieldName.js +82 -0
  18. package/dist/cards/cta/CardContext.d.ts +16 -0
  19. package/dist/cards/cta/CardContext.js +58 -0
  20. package/dist/cards/cta/dispatcher.d.ts +7 -0
  21. package/dist/cards/cta/dispatcher.js +90 -0
  22. package/dist/cards/cta/types.d.ts +66 -0
  23. package/dist/cards/cta/types.js +2 -0
  24. package/dist/cards/index.d.ts +20 -0
  25. package/dist/cards/index.js +34 -0
  26. package/dist/cards/primitives/QButton.d.ts +10 -0
  27. package/dist/cards/primitives/QButton.js +115 -0
  28. package/dist/cards/primitives/QDivider.d.ts +7 -0
  29. package/dist/cards/primitives/QDivider.js +17 -0
  30. package/dist/cards/primitives/QIcon.d.ts +13 -0
  31. package/dist/cards/primitives/QIcon.js +26 -0
  32. package/dist/cards/primitives/QImage.d.ts +9 -0
  33. package/dist/cards/primitives/QImage.js +22 -0
  34. package/dist/cards/primitives/QText.d.ts +9 -0
  35. package/dist/cards/primitives/QText.js +30 -0
  36. package/dist/cards/primitives/QView.d.ts +8 -0
  37. package/dist/cards/primitives/QView.js +19 -0
  38. package/dist/cards/renderer/CardRenderer.d.ts +19 -0
  39. package/dist/cards/renderer/CardRenderer.js +64 -0
  40. package/dist/cards/renderer/renderNode.d.ts +13 -0
  41. package/dist/cards/renderer/renderNode.js +42 -0
  42. package/dist/cards/types.d.ts +110 -0
  43. package/dist/cards/types.js +6 -0
  44. package/dist/components/ActionResultBadge.d.ts +12 -0
  45. package/dist/components/ActionResultBadge.js +58 -0
  46. package/dist/components/ChatPage.d.ts +44 -0
  47. package/dist/components/ChatPage.js +84 -0
  48. package/dist/components/DataChip.d.ts +8 -0
  49. package/dist/components/DataChip.js +80 -0
  50. package/dist/components/DataChipList.d.ts +13 -0
  51. package/dist/components/DataChipList.js +21 -0
  52. package/dist/components/FloatingButton.d.ts +11 -0
  53. package/dist/components/FloatingButton.js +162 -0
  54. package/dist/components/InputArea.d.ts +57 -0
  55. package/dist/components/InputArea.js +142 -0
  56. package/dist/components/MarkdownText.d.ts +15 -0
  57. package/dist/components/MarkdownText.js +283 -0
  58. package/dist/components/MessageBubble.d.ts +134 -0
  59. package/dist/components/MessageBubble.js +384 -0
  60. package/dist/components/NavigationSuggestion.d.ts +11 -0
  61. package/dist/components/NavigationSuggestion.js +109 -0
  62. package/dist/components/Qafka.d.ts +39 -0
  63. package/dist/components/Qafka.handlers.d.ts +21 -0
  64. package/dist/components/Qafka.handlers.js +54 -0
  65. package/dist/components/Qafka.js +493 -0
  66. package/dist/components/Qafka.styles.d.ts +19 -0
  67. package/dist/components/Qafka.styles.js +101 -0
  68. package/dist/components/Qafka.types.d.ts +744 -0
  69. package/dist/components/Qafka.types.js +2 -0
  70. package/dist/components/Qafka.utils.d.ts +7 -0
  71. package/dist/components/Qafka.utils.js +34 -0
  72. package/dist/components/QafkaProvider.d.ts +12 -0
  73. package/dist/components/QafkaProvider.js +87 -0
  74. package/dist/components/QuickReplies.d.ts +14 -0
  75. package/dist/components/QuickReplies.js +48 -0
  76. package/dist/components/StepProgressIndicator.d.ts +12 -0
  77. package/dist/components/StepProgressIndicator.js +48 -0
  78. package/dist/components/SuggestionButton.d.ts +42 -0
  79. package/dist/components/SuggestionButton.js +67 -0
  80. package/dist/components/ToolStatusPill.d.ts +20 -0
  81. package/dist/components/ToolStatusPill.js +43 -0
  82. package/dist/components/TypingIndicator.d.ts +28 -0
  83. package/dist/components/TypingIndicator.js +109 -0
  84. package/dist/components/VoicePage.d.ts +48 -0
  85. package/dist/components/VoicePage.js +683 -0
  86. package/dist/components/defaults/DefaultCard.d.ts +14 -0
  87. package/dist/components/defaults/DefaultCard.js +156 -0
  88. package/dist/components/defaults/DefaultDetail.d.ts +14 -0
  89. package/dist/components/defaults/DefaultDetail.js +138 -0
  90. package/dist/components/defaults/DefaultList.d.ts +12 -0
  91. package/dist/components/defaults/DefaultList.js +98 -0
  92. package/dist/components/defaults/DefaultTable.d.ts +14 -0
  93. package/dist/components/defaults/DefaultTable.js +204 -0
  94. package/dist/components/defaults/index.d.ts +14 -0
  95. package/dist/components/defaults/index.js +25 -0
  96. package/dist/components/index.d.ts +22 -0
  97. package/dist/components/index.js +36 -0
  98. package/dist/constants.d.ts +10 -0
  99. package/dist/constants.js +13 -0
  100. package/dist/hooks/useChatMessages.d.ts +72 -0
  101. package/dist/hooks/useChatMessages.js +505 -0
  102. package/dist/hooks/useContextManager.d.ts +12 -0
  103. package/dist/hooks/useContextManager.js +46 -0
  104. package/dist/hooks/useProjectTheme.d.ts +19 -0
  105. package/dist/hooks/useProjectTheme.js +163 -0
  106. package/dist/hooks/useSDK.d.ts +31 -0
  107. package/dist/hooks/useSDK.js +103 -0
  108. package/dist/hooks/useVoiceChat.d.ts +110 -0
  109. package/dist/hooks/useVoiceChat.js +436 -0
  110. package/dist/index.d.ts +13 -0
  111. package/dist/index.js +59 -0
  112. package/dist/native/QafkaAttestation.d.ts +23 -0
  113. package/dist/native/QafkaAttestation.js +70 -0
  114. package/dist/native/QafkaAudio.d.ts +14 -0
  115. package/dist/native/QafkaAudio.js +31 -0
  116. package/dist/native/QafkaClipboard.d.ts +11 -0
  117. package/dist/native/QafkaClipboard.js +14 -0
  118. package/dist/native/QafkaStorage.d.ts +15 -0
  119. package/dist/native/QafkaStorage.js +12 -0
  120. package/dist/resolve-project-config.d.ts +35 -0
  121. package/dist/resolve-project-config.js +41 -0
  122. package/dist/runtime-config-loader.d.ts +37 -0
  123. package/dist/runtime-config-loader.js +53 -0
  124. package/dist/services/AttestationManager.d.ts +38 -0
  125. package/dist/services/AttestationManager.js +296 -0
  126. package/dist/services/BackendService.d.ts +156 -0
  127. package/dist/services/BackendService.js +755 -0
  128. package/dist/services/ConversationManager.d.ts +43 -0
  129. package/dist/services/ConversationManager.js +96 -0
  130. package/dist/services/NavigationHandler.d.ts +29 -0
  131. package/dist/services/NavigationHandler.js +70 -0
  132. package/dist/services/RealtimeService.d.ts +83 -0
  133. package/dist/services/RealtimeService.js +203 -0
  134. package/dist/services/storage.d.ts +11 -0
  135. package/dist/services/storage.js +15 -0
  136. package/dist/services/storageCore.d.ts +17 -0
  137. package/dist/services/storageCore.js +46 -0
  138. package/dist/themes/dark.d.ts +5 -0
  139. package/dist/themes/dark.js +129 -0
  140. package/dist/themes/index.d.ts +12 -0
  141. package/dist/themes/index.js +33 -0
  142. package/dist/themes/light.d.ts +5 -0
  143. package/dist/themes/light.js +129 -0
  144. package/dist/themes/types.d.ts +155 -0
  145. package/dist/themes/types.js +5 -0
  146. package/dist/types/chat.d.ts +126 -0
  147. package/dist/types/chat.js +5 -0
  148. package/dist/types/components.d.ts +56 -0
  149. package/dist/types/components.js +16 -0
  150. package/dist/types/external-navigation.d.ts +19 -0
  151. package/dist/types/external-navigation.js +8 -0
  152. package/dist/types/index.d.ts +9 -0
  153. package/dist/types/index.js +25 -0
  154. package/dist/types/navigation.d.ts +86 -0
  155. package/dist/types/navigation.js +5 -0
  156. package/dist/types/sdk.d.ts +36 -0
  157. package/dist/types/sdk.js +5 -0
  158. package/dist/utils/deepMerge.d.ts +46 -0
  159. package/dist/utils/deepMerge.js +70 -0
  160. package/dist/utils/fontUtils.d.ts +8 -0
  161. package/dist/utils/fontUtils.js +16 -0
  162. package/dist/validate-end-user.d.ts +18 -0
  163. package/dist/validate-end-user.js +74 -0
  164. package/expo-plugin/withQafkaAttestation.js +57 -0
  165. package/ios/QafkaAttestation.m +25 -0
  166. package/ios/QafkaAttestation.swift +128 -0
  167. package/ios/QafkaAudio.m +23 -0
  168. package/ios/QafkaAudio.swift +519 -0
  169. package/ios/QafkaClipboard.m +10 -0
  170. package/ios/QafkaClipboard.swift +21 -0
  171. package/ios/QafkaReactImports.h +2 -0
  172. package/ios/QafkaStorage.m +26 -0
  173. package/ios/QafkaStorage.swift +118 -0
  174. package/package.json +82 -0
  175. package/qafka.config.d.ts +9 -0
  176. package/qafka.config.js +9 -0
  177. package/react-native-qafka.podspec +28 -0
  178. package/react-native.config.js +14 -0
@@ -0,0 +1,290 @@
1
+ package com.qafka.audio
2
+
3
+ import android.Manifest
4
+ import android.content.pm.PackageManager
5
+ import android.media.AudioFormat
6
+ import android.media.AudioRecord
7
+ import android.media.AudioTrack
8
+ import android.media.MediaRecorder
9
+ import android.media.audiofx.AcousticEchoCanceler
10
+ import android.media.audiofx.AutomaticGainControl
11
+ import android.media.audiofx.NoiseSuppressor
12
+ import android.util.Base64
13
+ import android.util.Log
14
+ import androidx.core.content.ContextCompat
15
+ import com.facebook.react.bridge.*
16
+ import com.facebook.react.modules.core.DeviceEventManagerModule
17
+
18
+ class QafkaAudioModule(reactContext: ReactApplicationContext) :
19
+ ReactContextBaseJavaModule(reactContext) {
20
+
21
+ override fun getName(): String = "QafkaAudio"
22
+
23
+ private var audioRecord: AudioRecord? = null
24
+ private var audioTrack: AudioTrack? = null
25
+ private var captureThread: Thread? = null
26
+ @Volatile private var isCapturing = false
27
+ private var listenerCount = 0
28
+
29
+ // iOS VPIO equivalent — hardware AEC + NS + AGC (android.media.audiofx).
30
+ // Enables barge-in by cancelling playback echo at mic level.
31
+ // Device-dependent; isAvailable() + null checks keep it safe.
32
+ private var echoCanceler: AcousticEchoCanceler? = null
33
+ private var noiseSuppressor: NoiseSuppressor? = null
34
+ private var automaticGainControl: AutomaticGainControl? = null
35
+
36
+ companion object {
37
+ private const val TAG = "QafkaAudio"
38
+ private const val CAPTURE_SAMPLE_RATE = 16000
39
+ private const val PLAYBACK_SAMPLE_RATE = 24000
40
+ private const val CHANNEL_IN = AudioFormat.CHANNEL_IN_MONO
41
+ private const val CHANNEL_OUT = AudioFormat.CHANNEL_OUT_MONO
42
+ private const val AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT
43
+ }
44
+
45
+ /**
46
+ * True when the consumer app is built in debug mode. Used to gate noisy
47
+ * info/warn logs so they never reach release builds. Real errors still
48
+ * call Log.e directly.
49
+ */
50
+ private val isDebugBuild: Boolean by lazy {
51
+ (reactApplicationContext.applicationInfo.flags and
52
+ android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0
53
+ }
54
+
55
+ private fun dlogI(message: String) {
56
+ if (isDebugBuild) Log.i(TAG, message)
57
+ }
58
+
59
+ private fun dlogW(message: String) {
60
+ if (isDebugBuild) Log.w(TAG, message)
61
+ }
62
+
63
+ // MARK: - Event Emitter support
64
+
65
+ @ReactMethod
66
+ fun addListener(eventName: String) {
67
+ listenerCount++
68
+ }
69
+
70
+ @ReactMethod
71
+ fun removeListeners(count: Int) {
72
+ listenerCount -= count
73
+ if (listenerCount < 0) listenerCount = 0
74
+ }
75
+
76
+ private fun sendEvent(eventName: String, params: WritableMap) {
77
+ if (listenerCount > 0) {
78
+ reactApplicationContext
79
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
80
+ .emit(eventName, params)
81
+ }
82
+ }
83
+
84
+ // MARK: - Capture
85
+
86
+ @ReactMethod
87
+ fun startCapture(promise: Promise) {
88
+ if (isCapturing) {
89
+ promise.resolve(true)
90
+ return
91
+ }
92
+
93
+ // Check permission
94
+ val permission = ContextCompat.checkSelfPermission(
95
+ reactApplicationContext,
96
+ Manifest.permission.RECORD_AUDIO
97
+ )
98
+ if (permission != PackageManager.PERMISSION_GRANTED) {
99
+ promise.reject("PERMISSION_DENIED", "RECORD_AUDIO permission not granted")
100
+ return
101
+ }
102
+
103
+ val minBufferSize = AudioRecord.getMinBufferSize(
104
+ CAPTURE_SAMPLE_RATE, CHANNEL_IN, AUDIO_ENCODING
105
+ )
106
+ if (minBufferSize == AudioRecord.ERROR || minBufferSize == AudioRecord.ERROR_BAD_VALUE) {
107
+ promise.reject("AUDIO_ERROR", "Failed to get minimum buffer size for AudioRecord")
108
+ return
109
+ }
110
+
111
+ val bufferSize = maxOf(minBufferSize, 4096)
112
+
113
+ try {
114
+ val record = AudioRecord(
115
+ MediaRecorder.AudioSource.VOICE_COMMUNICATION,
116
+ CAPTURE_SAMPLE_RATE,
117
+ CHANNEL_IN,
118
+ AUDIO_ENCODING,
119
+ bufferSize
120
+ )
121
+
122
+ if (record.state != AudioRecord.STATE_INITIALIZED) {
123
+ record.release()
124
+ promise.reject("AUDIO_ERROR", "Failed to initialize AudioRecord")
125
+ return
126
+ }
127
+
128
+ // iOS VPIO equivalent — hardware AEC + NS + AGC, bound to AudioRecord sessionId.
129
+ val sessionId = record.audioSessionId
130
+
131
+ if (AcousticEchoCanceler.isAvailable()) {
132
+ echoCanceler = AcousticEchoCanceler.create(sessionId)?.also {
133
+ it.enabled = true
134
+ dlogI("✅ AcousticEchoCanceler enabled (sessionId=$sessionId)")
135
+ }
136
+ if (echoCanceler == null) dlogW("⚠️ AcousticEchoCanceler.create returned null")
137
+ } else {
138
+ dlogW("⚠️ AcousticEchoCanceler NOT available on this device")
139
+ }
140
+
141
+ if (NoiseSuppressor.isAvailable()) {
142
+ noiseSuppressor = NoiseSuppressor.create(sessionId)?.also {
143
+ it.enabled = true
144
+ dlogI("✅ NoiseSuppressor enabled")
145
+ }
146
+ if (noiseSuppressor == null) dlogW("⚠️ NoiseSuppressor.create returned null")
147
+ } else {
148
+ dlogW("⚠️ NoiseSuppressor NOT available on this device")
149
+ }
150
+
151
+ if (AutomaticGainControl.isAvailable()) {
152
+ automaticGainControl = AutomaticGainControl.create(sessionId)?.also {
153
+ it.enabled = true
154
+ dlogI("✅ AutomaticGainControl enabled")
155
+ }
156
+ if (automaticGainControl == null) dlogW("⚠️ AutomaticGainControl.create returned null")
157
+ } else {
158
+ dlogW("⚠️ AutomaticGainControl NOT available on this device")
159
+ }
160
+
161
+ // Initialize AudioTrack for playback
162
+ val playMinBuffer = AudioTrack.getMinBufferSize(
163
+ PLAYBACK_SAMPLE_RATE, CHANNEL_OUT, AUDIO_ENCODING
164
+ )
165
+ val playBufferSize = maxOf(playMinBuffer, 4096)
166
+
167
+ val track = AudioTrack.Builder()
168
+ .setAudioFormat(
169
+ AudioFormat.Builder()
170
+ .setSampleRate(PLAYBACK_SAMPLE_RATE)
171
+ .setChannelMask(CHANNEL_OUT)
172
+ .setEncoding(AUDIO_ENCODING)
173
+ .build()
174
+ )
175
+ .setBufferSizeInBytes(playBufferSize)
176
+ .setTransferMode(AudioTrack.MODE_STREAM)
177
+ .build()
178
+
179
+ track.play()
180
+
181
+ audioRecord = record
182
+ audioTrack = track
183
+ isCapturing = true
184
+
185
+ record.startRecording()
186
+
187
+ // Start capture thread
188
+ captureThread = Thread {
189
+ val readBuffer = ShortArray(bufferSize / 2)
190
+
191
+ while (isCapturing) {
192
+ val shortsRead = record.read(readBuffer, 0, readBuffer.size)
193
+ if (shortsRead > 0) {
194
+ // Convert shorts to bytes (little-endian PCM 16-bit)
195
+ val byteBuffer = ByteArray(shortsRead * 2)
196
+ for (i in 0 until shortsRead) {
197
+ byteBuffer[i * 2] = (readBuffer[i].toInt() and 0xFF).toByte()
198
+ byteBuffer[i * 2 + 1] = (readBuffer[i].toInt() shr 8 and 0xFF).toByte()
199
+ }
200
+
201
+ val base64 = Base64.encodeToString(byteBuffer, Base64.NO_WRAP)
202
+ val params = Arguments.createMap().apply {
203
+ putString("audio", base64)
204
+ }
205
+ sendEvent("onAudioData", params)
206
+ }
207
+ }
208
+ }.apply {
209
+ name = "QafkaAudioCapture"
210
+ start()
211
+ }
212
+
213
+ promise.resolve(true)
214
+ } catch (e: Exception) {
215
+ promise.reject("AUDIO_ERROR", "Failed to start capture: ${e.message}", e)
216
+ }
217
+ }
218
+
219
+ // MARK: - Stop Capture
220
+
221
+ @ReactMethod
222
+ fun stopCapture(promise: Promise) {
223
+ isCapturing = false
224
+
225
+ try {
226
+ captureThread?.join(2000)
227
+ } catch (_: InterruptedException) {}
228
+ captureThread = null
229
+
230
+ // Release audio effects BEFORE AudioRecord — they depend on its sessionId
231
+ try { echoCanceler?.release() } catch (_: Exception) {}
232
+ echoCanceler = null
233
+ try { noiseSuppressor?.release() } catch (_: Exception) {}
234
+ noiseSuppressor = null
235
+ try { automaticGainControl?.release() } catch (_: Exception) {}
236
+ automaticGainControl = null
237
+
238
+ try {
239
+ audioRecord?.stop()
240
+ audioRecord?.release()
241
+ } catch (_: Exception) {}
242
+ audioRecord = null
243
+
244
+ try {
245
+ audioTrack?.stop()
246
+ audioTrack?.release()
247
+ } catch (_: Exception) {}
248
+ audioTrack = null
249
+
250
+ promise.resolve(true)
251
+ }
252
+
253
+ // MARK: - Playback
254
+
255
+ @ReactMethod
256
+ fun playAudioChunk(base64Data: String, promise: Promise) {
257
+ val track = audioTrack
258
+ if (track == null) {
259
+ promise.reject("NOT_RUNNING", "Audio is not running. Call startCapture first.")
260
+ return
261
+ }
262
+
263
+ try {
264
+ val audioBytes = Base64.decode(base64Data, Base64.DEFAULT)
265
+ track.write(audioBytes, 0, audioBytes.size)
266
+ promise.resolve(true)
267
+ } catch (e: Exception) {
268
+ promise.reject("PLAYBACK_ERROR", "Failed to play audio chunk: ${e.message}", e)
269
+ }
270
+ }
271
+
272
+ // MARK: - Stop Playback
273
+
274
+ @ReactMethod
275
+ fun stopPlayback(promise: Promise) {
276
+ val track = audioTrack ?: run {
277
+ promise.resolve(true)
278
+ return
279
+ }
280
+
281
+ try {
282
+ track.pause()
283
+ track.flush()
284
+ track.play()
285
+ promise.resolve(true)
286
+ } catch (e: Exception) {
287
+ promise.reject("PLAYBACK_ERROR", "Failed to stop playback: ${e.message}", e)
288
+ }
289
+ }
290
+ }
@@ -0,0 +1,28 @@
1
+ package com.qafka.clipboard
2
+
3
+ import android.content.ClipData
4
+ import android.content.ClipboardManager
5
+ import android.content.Context
6
+ import com.facebook.react.bridge.Promise
7
+ import com.facebook.react.bridge.ReactApplicationContext
8
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
9
+ import com.facebook.react.bridge.ReactMethod
10
+
11
+ class QafkaClipboardModule(reactContext: ReactApplicationContext) :
12
+ ReactContextBaseJavaModule(reactContext) {
13
+
14
+ override fun getName(): String = "QafkaClipboard"
15
+
16
+ @ReactMethod
17
+ fun setString(value: String, promise: Promise) {
18
+ try {
19
+ val clipboard = reactApplicationContext.getSystemService(
20
+ Context.CLIPBOARD_SERVICE
21
+ ) as ClipboardManager
22
+ clipboard.setPrimaryClip(ClipData.newPlainText("Qafka", value))
23
+ promise.resolve(null)
24
+ } catch (e: Exception) {
25
+ promise.reject("CLIPBOARD_WRITE_FAILED", e.message, e)
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,80 @@
1
+ package com.qafka.storage
2
+
3
+ import android.content.SharedPreferences
4
+ import androidx.security.crypto.EncryptedSharedPreferences
5
+ import androidx.security.crypto.MasterKey
6
+ import com.facebook.react.bridge.Arguments
7
+ import com.facebook.react.bridge.Promise
8
+ import com.facebook.react.bridge.ReactApplicationContext
9
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
10
+ import com.facebook.react.bridge.ReactMethod
11
+ import com.facebook.react.bridge.ReadableArray
12
+
13
+ class QafkaStorageModule(reactContext: ReactApplicationContext) :
14
+ ReactContextBaseJavaModule(reactContext) {
15
+
16
+ override fun getName(): String = "QafkaStorage"
17
+
18
+ private val prefs: SharedPreferences by lazy {
19
+ val masterKey = MasterKey.Builder(reactApplicationContext)
20
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
21
+ .build()
22
+
23
+ EncryptedSharedPreferences.create(
24
+ reactApplicationContext,
25
+ FILE_NAME,
26
+ masterKey,
27
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
28
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
29
+ )
30
+ }
31
+
32
+ @ReactMethod
33
+ fun getItem(key: String, promise: Promise) {
34
+ try {
35
+ val value = prefs.getString(key, null)
36
+ promise.resolve(value)
37
+ } catch (e: Exception) {
38
+ promise.reject("STORAGE_READ_FAILED", e.message, e)
39
+ }
40
+ }
41
+
42
+ @ReactMethod
43
+ fun setItem(key: String, value: String, promise: Promise) {
44
+ try {
45
+ prefs.edit().putString(key, value).apply()
46
+ promise.resolve(null)
47
+ } catch (e: Exception) {
48
+ promise.reject("STORAGE_WRITE_FAILED", e.message, e)
49
+ }
50
+ }
51
+
52
+ @ReactMethod
53
+ fun removeItem(key: String, promise: Promise) {
54
+ try {
55
+ prefs.edit().remove(key).apply()
56
+ promise.resolve(null)
57
+ } catch (e: Exception) {
58
+ promise.reject("STORAGE_DELETE_FAILED", e.message, e)
59
+ }
60
+ }
61
+
62
+ @ReactMethod
63
+ fun multiRemove(keys: ReadableArray, promise: Promise) {
64
+ try {
65
+ val editor = prefs.edit()
66
+ for (i in 0 until keys.size()) {
67
+ val key = keys.getString(i) ?: continue
68
+ editor.remove(key)
69
+ }
70
+ editor.apply()
71
+ promise.resolve(null)
72
+ } catch (e: Exception) {
73
+ promise.reject("STORAGE_DELETE_FAILED", e.message, e)
74
+ }
75
+ }
76
+
77
+ companion object {
78
+ private const val FILE_NAME = "qafka_secure_prefs"
79
+ }
80
+ }
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./expo-plugin/withQafkaAttestation');
@@ -0,0 +1,174 @@
1
+ import { SDKConfig, SDKStatusType, ChatMessage, ChatResponse } from './types';
2
+ /**
3
+ * QafkaSDK - Main SDK class
4
+ * Provides AI-powered conversational interface for React Native apps
5
+ */
6
+ export declare class QafkaSDK {
7
+ private static instance;
8
+ private config;
9
+ private status;
10
+ private subProjectId;
11
+ private backendService;
12
+ private attestationManager;
13
+ private conversationManager;
14
+ private navigationHandler;
15
+ private themePrefetchPromise;
16
+ private constructor();
17
+ /**
18
+ * Get singleton instance
19
+ */
20
+ static getInstance(): QafkaSDK;
21
+ /**
22
+ * Initialize SDK
23
+ */
24
+ initialize(config: SDKConfig): Promise<void>;
25
+ /**
26
+ * developer-driven locale (BCP 47 — e.g. "tr", "en", "tr-TR").
27
+ * Forwarded to BackendService so it lands in `sdkContext.locale` on every
28
+ * outgoing chat request, which the server then echoes into the user-message
29
+ * context prefix. Same value should drive UI strings on the SDK side.
30
+ *
31
+ * Pass `null` to clear (e.g. when switching back to instructions-default).
32
+ * Calling before initialize() is a no-op; pass `locale` via SDKConfig instead.
33
+ */
34
+ setLocale(locale: string | null): void;
35
+ /**
36
+ * Tool Data Channel: register the partner-provided resolver
37
+ * that supplies opaque PII bags for `{{tooldata.X}}` substitution on
38
+ * the backend. Called from `Qafka.tsx` whenever the `onToolDataRequested`
39
+ * prop changes. Pass `null` to clear.
40
+ */
41
+ setOnToolDataRequested(resolver: ((tool: {
42
+ key: string;
43
+ name: string;
44
+ }) => Record<string, unknown> | Promise<Record<string, unknown>>) | null): void;
45
+ /**
46
+ * Returns the current device-attestation session token (or `null` when
47
+ * attestation is unavailable). Voice mode passes this in the auth frame so
48
+ * the realtime WS opens against an attested session.
49
+ */
50
+ getSessionToken(): Promise<string | null>;
51
+ /**
52
+ * Propagate the validated end-user identity from the React component
53
+ * into the transport layer. Called by `<Qafka />` in an effect whenever
54
+ * the `endUserId` / `endUserData` props change. Validation happens at
55
+ * the component boundary (`validate-end-user.ts`).
56
+ */
57
+ setEndUser(endUserId: string, endUserData?: Record<string, unknown>): void;
58
+ /**
59
+ * Send a chat message
60
+ */
61
+ sendMessage(message: string, context?: Record<string, any>, contextDescription?: string, isInitialMessage?: boolean): Promise<ChatResponse>;
62
+ /**
63
+ * Send message with streaming response
64
+ */
65
+ sendMessageStream(message: string, onChunk: (chunk: string) => void, onComplete?: (response: ChatResponse) => void, onError?: (error: Error) => void, context?: Record<string, any>, contextDescription?: string, onToolSuggestions?: (tools: any[], conversationId?: string, messageId?: string) => void, // Tool suggestions callback
66
+ isInitialMessage?: boolean, onActionResult?: (results: Array<{
67
+ actionType: string;
68
+ success: boolean;
69
+ message: string;
70
+ }>) => void, // Action result callback
71
+ onStepCompleted?: (result: {
72
+ tool: string;
73
+ step: string;
74
+ data: Record<string, any>;
75
+ actionResults?: Array<{
76
+ actionType: string;
77
+ success: boolean;
78
+ message: string;
79
+ }>;
80
+ }) => void, // Step completed callback
81
+ onFileUploadRequest?: (request: any) => void, // File upload request callback
82
+ onExtractionResult?: (result: any) => void, // Extraction result callback
83
+ onToolStatus?: (event: {
84
+ toolKey: string;
85
+ message: string;
86
+ stage: string;
87
+ }) => void, onToolResultPayload?: (event: {
88
+ toolKey: string;
89
+ data: unknown;
90
+ uiConfig?: any;
91
+ }) => void, onFinalChunk?: (content: string) => void): Promise<void>;
92
+ /**
93
+ * Get conversation history
94
+ */
95
+ getConversationHistory(): Promise<ChatMessage[]>;
96
+ /**
97
+ * Clear conversation history
98
+ */
99
+ clearConversation(): Promise<void>;
100
+ /**
101
+ * Start a new conversation
102
+ * Clears history and returns the new session ID
103
+ */
104
+ startNewConversation(): Promise<string>;
105
+ /**
106
+ * Add tool response to conversation
107
+ * Use this after executing a tool to display the results
108
+ */
109
+ addToolResponse(toolKey: string, data: any, tool: any): ChatMessage;
110
+ /**
111
+ * Post a tool result back to the backend so the AI can continue its turn.
112
+ */
113
+ postToolResult(payload: {
114
+ conversationId: string;
115
+ messageId: string;
116
+ toolKey: string;
117
+ data: unknown;
118
+ sdkContext?: Record<string, unknown>;
119
+ }, onFinalChunk: (content: string) => void, onDone: () => void, onError: (err: Error) => void, onCard?: (card: {
120
+ templateId: string;
121
+ templateSlug: string;
122
+ definition: any;
123
+ data: any;
124
+ }) => void): Promise<void>;
125
+ /**
126
+ * Upload a file attached to a tool flow.
127
+ */
128
+ uploadFile(toolId: string, file: {
129
+ uri: string;
130
+ name: string;
131
+ type: string;
132
+ }, conversationId?: string): Promise<any>;
133
+ /**
134
+ * Get project theme configuration
135
+ */
136
+ getProjectTheme(): Promise<any>;
137
+ /**
138
+ * Returns the in-flight theme prefetch (kicked off in initialize() parallel
139
+ * with attestation). Used by `useProjectTheme` to paint the branded greeting
140
+ * without waiting for `status === 'ready'`. Returns `null` if initialize()
141
+ * hasn't been called yet or if prefetch already settled.
142
+ */
143
+ getThemePrefetch(): Promise<any> | null;
144
+ /**
145
+ * Fetches the project theme via BackendService and writes the result to
146
+ * persistent cache so subsequent chat opens paint instantly.
147
+ */
148
+ private prefetchAndCacheTheme;
149
+ /**
150
+ * Get SDK status
151
+ */
152
+ getStatus(): SDKStatusType;
153
+ /**
154
+ * Get SDK configuration
155
+ */
156
+ getConfig(): SDKConfig | null;
157
+ /**
158
+ * Navigate to screen
159
+ */
160
+ navigate(screenName: string, params?: any): void;
161
+ /**
162
+ * Generate unique ID
163
+ */
164
+ private generateId;
165
+ /**
166
+ * Resolve API key from config
167
+ */
168
+ private resolveApiKey;
169
+ /**
170
+ * Destroy SDK instance
171
+ */
172
+ destroy(): Promise<void>;
173
+ }
174
+ export declare const getSDK: () => QafkaSDK;