@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,43 @@
1
+ import { ChatMessage } from '../types/chat';
2
+ /**
3
+ * Conversation Manager
4
+ * Handles conversation history and session management
5
+ */
6
+ export declare class ConversationManager {
7
+ private sessionId;
8
+ private messages;
9
+ private maxMessages;
10
+ constructor(sessionId?: string);
11
+ /**
12
+ * Initialize manager
13
+ */
14
+ initialize(): Promise<void>;
15
+ /**
16
+ * Add message to conversation
17
+ */
18
+ addMessage(message: ChatMessage): void;
19
+ /**
20
+ * Get conversation history
21
+ */
22
+ getHistory(limit?: number): ChatMessage[];
23
+ /**
24
+ * Get session ID
25
+ */
26
+ getSessionId(): string;
27
+ /**
28
+ * Clear conversation
29
+ */
30
+ clear(): Promise<void>;
31
+ /**
32
+ * Load from storage
33
+ */
34
+ private loadFromStorage;
35
+ /**
36
+ * Save to storage
37
+ */
38
+ private saveToStorage;
39
+ /**
40
+ * Generate session ID
41
+ */
42
+ private generateSessionId;
43
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConversationManager = void 0;
4
+ const storage_1 = require("./storage");
5
+ const constants_1 = require("../constants");
6
+ /**
7
+ * Conversation Manager
8
+ * Handles conversation history and session management
9
+ */
10
+ class ConversationManager {
11
+ sessionId;
12
+ messages = [];
13
+ maxMessages = 50;
14
+ constructor(sessionId) {
15
+ this.sessionId = sessionId || this.generateSessionId();
16
+ }
17
+ /**
18
+ * Initialize manager
19
+ */
20
+ async initialize() {
21
+ await this.loadFromStorage();
22
+ }
23
+ /**
24
+ * Add message to conversation
25
+ */
26
+ addMessage(message) {
27
+ this.messages.push(message);
28
+ // Keep only last N messages
29
+ if (this.messages.length > this.maxMessages) {
30
+ this.messages = this.messages.slice(-this.maxMessages);
31
+ }
32
+ this.saveToStorage();
33
+ }
34
+ /**
35
+ * Get conversation history
36
+ */
37
+ getHistory(limit) {
38
+ if (limit) {
39
+ return this.messages.slice(-limit);
40
+ }
41
+ return [...this.messages];
42
+ }
43
+ /**
44
+ * Get session ID
45
+ */
46
+ getSessionId() {
47
+ return this.sessionId;
48
+ }
49
+ /**
50
+ * Clear conversation
51
+ */
52
+ async clear() {
53
+ this.messages = [];
54
+ this.sessionId = this.generateSessionId();
55
+ await storage_1.storage.removeItem(constants_1.STORAGE_KEYS.CONVERSATION_HISTORY);
56
+ }
57
+ /**
58
+ * Load from storage
59
+ */
60
+ async loadFromStorage() {
61
+ try {
62
+ const stored = await storage_1.storage.getItem(constants_1.STORAGE_KEYS.CONVERSATION_HISTORY);
63
+ if (stored) {
64
+ const data = JSON.parse(stored);
65
+ this.messages = data.messages || [];
66
+ this.sessionId = data.sessionId || this.sessionId;
67
+ }
68
+ }
69
+ catch {
70
+ // Silent error handling
71
+ }
72
+ }
73
+ /**
74
+ * Save to storage
75
+ */
76
+ async saveToStorage() {
77
+ try {
78
+ const data = {
79
+ sessionId: this.sessionId,
80
+ messages: this.messages,
81
+ updatedAt: new Date().toISOString(),
82
+ };
83
+ await storage_1.storage.setItem(constants_1.STORAGE_KEYS.CONVERSATION_HISTORY, JSON.stringify(data));
84
+ }
85
+ catch {
86
+ // Silent error handling
87
+ }
88
+ }
89
+ /**
90
+ * Generate session ID
91
+ */
92
+ generateSessionId() {
93
+ return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
94
+ }
95
+ }
96
+ exports.ConversationManager = ConversationManager;
@@ -0,0 +1,29 @@
1
+ import { NavigationContainerRef } from '@react-navigation/native';
2
+ /**
3
+ * Navigation Handler
4
+ * Handles navigation based on AI suggestions
5
+ */
6
+ export declare class NavigationHandler {
7
+ private navigationRef;
8
+ constructor(navigationRef?: NavigationContainerRef<any>);
9
+ /**
10
+ * Set navigation reference
11
+ */
12
+ setNavigationRef(ref: NavigationContainerRef<any>): void;
13
+ /**
14
+ * Navigate to screen
15
+ */
16
+ navigate(screenName: string, params?: any): void;
17
+ /**
18
+ * Go back
19
+ */
20
+ goBack(): void;
21
+ /**
22
+ * Get current route name
23
+ */
24
+ getCurrentRoute(): string | undefined;
25
+ /**
26
+ * Reset navigation
27
+ */
28
+ reset(routeName: string, params?: any): void;
29
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NavigationHandler = void 0;
4
+ /**
5
+ * Navigation Handler
6
+ * Handles navigation based on AI suggestions
7
+ */
8
+ class NavigationHandler {
9
+ navigationRef;
10
+ constructor(navigationRef) {
11
+ this.navigationRef = navigationRef || null;
12
+ }
13
+ /**
14
+ * Set navigation reference
15
+ */
16
+ setNavigationRef(ref) {
17
+ this.navigationRef = ref;
18
+ }
19
+ /**
20
+ * Navigate to screen
21
+ */
22
+ navigate(screenName, params) {
23
+ if (!this.navigationRef) {
24
+ return;
25
+ }
26
+ if (!this.navigationRef.isReady()) {
27
+ return;
28
+ }
29
+ try {
30
+ // @ts-ignore - navigate accepts screen name
31
+ this.navigationRef.navigate(screenName, params);
32
+ }
33
+ catch {
34
+ // Navigation error silently ignored
35
+ }
36
+ }
37
+ /**
38
+ * Go back
39
+ */
40
+ goBack() {
41
+ if (!this.navigationRef) {
42
+ return;
43
+ }
44
+ if (this.navigationRef.canGoBack()) {
45
+ this.navigationRef.goBack();
46
+ }
47
+ }
48
+ /**
49
+ * Get current route name
50
+ */
51
+ getCurrentRoute() {
52
+ if (!this.navigationRef || !this.navigationRef.isReady()) {
53
+ return undefined;
54
+ }
55
+ return this.navigationRef.getCurrentRoute()?.name;
56
+ }
57
+ /**
58
+ * Reset navigation
59
+ */
60
+ reset(routeName, params) {
61
+ if (!this.navigationRef || !this.navigationRef.isReady()) {
62
+ return;
63
+ }
64
+ this.navigationRef.reset({
65
+ index: 0,
66
+ routes: [{ name: routeName, params }],
67
+ });
68
+ }
69
+ }
70
+ exports.NavigationHandler = NavigationHandler;
@@ -0,0 +1,83 @@
1
+ export type RealtimeEvent = {
2
+ type: 'session.ready';
3
+ } | {
4
+ type: 'session.closed';
5
+ reason: string;
6
+ } | {
7
+ type: 'response.audio.delta';
8
+ delta: string;
9
+ } | {
10
+ type: 'response.text.delta';
11
+ delta: string;
12
+ } | {
13
+ type: 'response.done';
14
+ } | {
15
+ type: 'conversation.item.input_audio_transcription.completed';
16
+ transcript: string;
17
+ } | {
18
+ type: 'error';
19
+ message: string;
20
+ } | {
21
+ type: 'tool_status';
22
+ toolCallId: string;
23
+ toolKey: string;
24
+ loadingMessage: string;
25
+ } | {
26
+ type: 'tool_suggested';
27
+ toolCallId: string;
28
+ tools: any[];
29
+ conversationId: string;
30
+ messageId?: string;
31
+ } | {
32
+ type: 'tool_done';
33
+ toolCallId: string;
34
+ };
35
+ export type RealtimeEventHandler = (event: RealtimeEvent) => void;
36
+ export declare class RealtimeService {
37
+ private apiUrl;
38
+ private apiKey;
39
+ private ws;
40
+ private audioUnsubscribe;
41
+ private eventHandler;
42
+ private audioChunksSent;
43
+ private sessionTokenGetter;
44
+ private appVersion;
45
+ constructor(apiUrl: string, apiKey: string);
46
+ setSessionTokenGetter(fn: () => Promise<string | null>): void;
47
+ connect(onEvent: RealtimeEventHandler, options?: {
48
+ userContext?: Record<string, any>;
49
+ contextDescription?: string;
50
+ endUserId?: string;
51
+ endUserData?: Record<string, unknown>;
52
+ }): Promise<void>;
53
+ private audioDeltasReceived;
54
+ private handleEvent;
55
+ private attachAudioListener;
56
+ private detachAudioListener;
57
+ private startAudioPipeline;
58
+ /**
59
+ * Fully stop the native audio engine (capture + playback). Use when leaving
60
+ * voice mode entirely (e.g. scrolling back to chat page). NOT for tool-flow
61
+ * pauses — that breaks the AI's turn-2 audio playback because iOS
62
+ * AVAudioEngine unifies capture + playback in one engine instance.
63
+ */
64
+ pauseMic(): Promise<void>;
65
+ resumeMic(): Promise<void>;
66
+ /**
67
+ * Stop forwarding mic audio to the server WHILE keeping the native audio
68
+ * engine running so the AI's turn-2 audio playback continues. Use during
69
+ * tool flows where the user shouldn't interrupt but the AI may still speak.
70
+ */
71
+ muteMic(): void;
72
+ unmuteMic(): void;
73
+ sendToolResult(payload: {
74
+ toolCallId: string;
75
+ toolName: string;
76
+ output: any;
77
+ ok: boolean;
78
+ error?: string;
79
+ }): void;
80
+ disconnect(): Promise<void>;
81
+ private cleanup;
82
+ get isConnected(): boolean;
83
+ }
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RealtimeService = void 0;
4
+ const QafkaAudio_1 = require("../native/QafkaAudio");
5
+ const QafkaAttestation_1 = require("../native/QafkaAttestation");
6
+ class RealtimeService {
7
+ apiUrl;
8
+ apiKey;
9
+ ws = null;
10
+ audioUnsubscribe = null;
11
+ eventHandler = null;
12
+ audioChunksSent = 0;
13
+ sessionTokenGetter = null;
14
+ appVersion = null;
15
+ constructor(apiUrl, apiKey) {
16
+ this.apiUrl = apiUrl;
17
+ this.apiKey = apiKey;
18
+ const deviceInfo = (0, QafkaAttestation_1.getDeviceInfo)();
19
+ this.appVersion = deviceInfo.appVersion || null;
20
+ }
21
+ setSessionTokenGetter(fn) {
22
+ this.sessionTokenGetter = fn;
23
+ }
24
+ async connect(onEvent, options) {
25
+ this.eventHandler = onEvent;
26
+ // Force wss:// in production builds. If the host app supplies an http://
27
+ // apiUrl in a non-dev build, that's a misconfiguration we'd rather hard-
28
+ // fail (with a TLS handshake failure) than silently downgrade to a
29
+ // plaintext WebSocket vulnerable to MITM.
30
+ const isDev = typeof __DEV__ !== 'undefined' && __DEV__;
31
+ const wsUrl = this.apiUrl.replace(/^https?:/, (m) => (m === 'https:' ? 'wss:' : isDev ? 'ws:' : 'wss:'));
32
+ const params = new URLSearchParams();
33
+ if (options?.userContext && Object.keys(options.userContext).length > 0) {
34
+ params.set('context', JSON.stringify(options.userContext));
35
+ }
36
+ if (options?.contextDescription &&
37
+ options.contextDescription.trim().length > 0) {
38
+ params.set('contextDescription', options.contextDescription);
39
+ }
40
+ if (options?.endUserId && options.endUserId.trim().length > 0) {
41
+ params.set('endUserId', options.endUserId);
42
+ }
43
+ if (options?.endUserData &&
44
+ typeof options.endUserData === 'object' &&
45
+ Object.keys(options.endUserData).length > 0) {
46
+ params.set('endUserData', JSON.stringify(options.endUserData));
47
+ }
48
+ const qs = params.toString();
49
+ const fullUrl = `${wsUrl}/v1/realtime${qs ? `?${qs}` : ''}`;
50
+ // Start the audio engine FIRST so the player is ready to schedule buffers
51
+ // the instant the backend starts streaming audio. The realtime service can
52
+ // emit audio deltas before native startCapture finishes if we start it after
53
+ // the WebSocket — the first ~20 chunks would drop with "engine not running".
54
+ // Doing it in this order closes that race.
55
+ await this.startAudioPipeline();
56
+ // Resolve the device-attestation session token BEFORE opening the WS so
57
+ // that it is ready to ship in the first auth frame. The getter is async
58
+ // (it may need to refresh an expired token via native attestation).
59
+ const sessionToken = this.sessionTokenGetter
60
+ ? await this.sessionTokenGetter()
61
+ : null;
62
+ return new Promise((resolve, reject) => {
63
+ this.ws = new WebSocket(fullUrl);
64
+ this.ws.onopen = () => {
65
+ // Send auth as the first frame instead of leaking the API key in the
66
+ // URL (avoids exposure via access logs / proxy histories). Session
67
+ // token, when available, lets the server tie this socket to a
68
+ // device-attested session.
69
+ const authPayload = {
70
+ type: 'auth',
71
+ apiKey: this.apiKey,
72
+ };
73
+ if (sessionToken)
74
+ authPayload.sessionToken = sessionToken;
75
+ if (this.appVersion)
76
+ authPayload.appVersion = this.appVersion;
77
+ try {
78
+ this.ws?.send(JSON.stringify(authPayload));
79
+ }
80
+ catch {
81
+ // ignore — onerror/onclose will surface failures
82
+ }
83
+ resolve();
84
+ };
85
+ this.ws.onmessage = (event) => {
86
+ try {
87
+ const data = JSON.parse(event.data);
88
+ this.handleEvent(data);
89
+ }
90
+ catch {
91
+ // Ignore malformed messages
92
+ }
93
+ };
94
+ this.ws.onerror = () => {
95
+ reject(new Error('WebSocket connection failed'));
96
+ };
97
+ this.ws.onclose = (event) => {
98
+ this.cleanup();
99
+ this.eventHandler?.({
100
+ type: 'session.closed',
101
+ reason: event.reason || 'CONNECTION_CLOSED',
102
+ });
103
+ };
104
+ });
105
+ }
106
+ audioDeltasReceived = 0;
107
+ handleEvent(event) {
108
+ if (event.type === 'response.audio.delta' && 'delta' in event) {
109
+ this.audioDeltasReceived++;
110
+ QafkaAudio_1.QafkaAudio.playAudioChunk(event.delta).catch(() => {
111
+ // playback errors are surfaced to the consumer via eventHandler
112
+ });
113
+ }
114
+ this.eventHandler?.(event);
115
+ }
116
+ attachAudioListener() {
117
+ if (this.audioUnsubscribe)
118
+ return; // already attached
119
+ this.audioUnsubscribe = QafkaAudio_1.QafkaAudio.onAudioData(({ audio }) => {
120
+ if (this.ws?.readyState === WebSocket.OPEN) {
121
+ this.ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio }));
122
+ this.audioChunksSent++;
123
+ }
124
+ });
125
+ }
126
+ detachAudioListener() {
127
+ this.audioUnsubscribe?.();
128
+ this.audioUnsubscribe = null;
129
+ }
130
+ async startAudioPipeline() {
131
+ try {
132
+ // Attach listener BEFORE startCapture so the native tap
133
+ // doesn't drop early buffers (hasListeners/listenerCount gate).
134
+ this.attachAudioListener();
135
+ await QafkaAudio_1.QafkaAudio.startCapture();
136
+ }
137
+ catch (error) {
138
+ this.eventHandler?.({
139
+ type: 'error',
140
+ message: `Mic capture failed: ${error}`,
141
+ });
142
+ }
143
+ }
144
+ /**
145
+ * Fully stop the native audio engine (capture + playback). Use when leaving
146
+ * voice mode entirely (e.g. scrolling back to chat page). NOT for tool-flow
147
+ * pauses — that breaks the AI's turn-2 audio playback because iOS
148
+ * AVAudioEngine unifies capture + playback in one engine instance.
149
+ */
150
+ async pauseMic() {
151
+ await QafkaAudio_1.QafkaAudio.stopCapture();
152
+ this.detachAudioListener();
153
+ }
154
+ async resumeMic() {
155
+ await this.startAudioPipeline();
156
+ }
157
+ /**
158
+ * Stop forwarding mic audio to the server WHILE keeping the native audio
159
+ * engine running so the AI's turn-2 audio playback continues. Use during
160
+ * tool flows where the user shouldn't interrupt but the AI may still speak.
161
+ */
162
+ muteMic() {
163
+ this.detachAudioListener();
164
+ }
165
+ unmuteMic() {
166
+ this.attachAudioListener();
167
+ }
168
+ sendToolResult(payload) {
169
+ if (this.ws?.readyState !== WebSocket.OPEN) {
170
+ return;
171
+ }
172
+ this.ws.send(JSON.stringify({
173
+ type: 'tool_result',
174
+ toolCallId: payload.toolCallId,
175
+ toolName: payload.toolName,
176
+ output: payload.output,
177
+ ok: payload.ok,
178
+ error: payload.error,
179
+ }));
180
+ }
181
+ async disconnect() {
182
+ await this.cleanup();
183
+ if (this.ws?.readyState === WebSocket.OPEN) {
184
+ this.ws.close();
185
+ }
186
+ this.ws = null;
187
+ this.audioChunksSent = 0;
188
+ }
189
+ async cleanup() {
190
+ this.audioUnsubscribe?.();
191
+ this.audioUnsubscribe = null;
192
+ try {
193
+ await QafkaAudio_1.QafkaAudio.stopCapture();
194
+ }
195
+ catch {
196
+ // Ignore cleanup errors
197
+ }
198
+ }
199
+ get isConnected() {
200
+ return this.ws?.readyState === WebSocket.OPEN;
201
+ }
202
+ }
203
+ exports.RealtimeService = RealtimeService;
@@ -0,0 +1,11 @@
1
+ import { type QafkaStorage } from './storageCore';
2
+ /**
3
+ * Singleton storage wrapper used throughout the SDK.
4
+ *
5
+ * - Backed by Keychain (iOS) / EncryptedSharedPreferences (Android) when the
6
+ * native QafkaStorage module is registered.
7
+ * - Falls back to in-memory Map (with a single console.warn) if the native
8
+ * module is missing — which should only happen if autolinking failed or the
9
+ * host app was not rebuilt after upgrading the SDK.
10
+ */
11
+ export declare const storage: QafkaStorage;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.storage = void 0;
4
+ const QafkaStorage_1 = require("../native/QafkaStorage");
5
+ const storageCore_1 = require("./storageCore");
6
+ /**
7
+ * Singleton storage wrapper used throughout the SDK.
8
+ *
9
+ * - Backed by Keychain (iOS) / EncryptedSharedPreferences (Android) when the
10
+ * native QafkaStorage module is registered.
11
+ * - Falls back to in-memory Map (with a single console.warn) if the native
12
+ * module is missing — which should only happen if autolinking failed or the
13
+ * host app was not rebuilt after upgrading the SDK.
14
+ */
15
+ exports.storage = (0, storageCore_1.createStorage)((0, QafkaStorage_1.getNativeStorage)());
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Native storage contract. Mirrors AsyncStorage's surface for the methods
3
+ * this SDK actually uses (getItem / setItem / removeItem / multiRemove).
4
+ */
5
+ export interface QafkaStorageNative {
6
+ getItem(key: string): Promise<string | null>;
7
+ setItem(key: string, value: string): Promise<void>;
8
+ removeItem(key: string): Promise<void>;
9
+ multiRemove(keys: string[]): Promise<void>;
10
+ }
11
+ export interface QafkaStorage {
12
+ getItem(key: string): Promise<string | null>;
13
+ setItem(key: string, value: string): Promise<void>;
14
+ removeItem(key: string): Promise<void>;
15
+ multiRemove(keys: string[]): Promise<void>;
16
+ }
17
+ export declare function createStorage(native: QafkaStorageNative | null): QafkaStorage;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createStorage = createStorage;
4
+ const FALLBACK_WARNING = '[Qafka] Native QafkaStorage module not registered — falling back to ' +
5
+ 'in-memory storage. Persistence will be lost when the app closes. ' +
6
+ 'This usually means autolinking failed or the app was not rebuilt after ' +
7
+ 'upgrading the SDK.';
8
+ function createStorage(native) {
9
+ if (native) {
10
+ return {
11
+ getItem: (key) => native.getItem(key),
12
+ setItem: (key, value) => native.setItem(key, value),
13
+ removeItem: (key) => native.removeItem(key),
14
+ multiRemove: (keys) => native.multiRemove(keys),
15
+ };
16
+ }
17
+ const memory = new Map();
18
+ let warned = false;
19
+ const warnOnce = () => {
20
+ if (!warned) {
21
+ if (typeof __DEV__ !== 'undefined' && __DEV__) {
22
+ console.warn(FALLBACK_WARNING);
23
+ }
24
+ warned = true;
25
+ }
26
+ };
27
+ return {
28
+ async getItem(key) {
29
+ warnOnce();
30
+ return memory.has(key) ? memory.get(key) : null;
31
+ },
32
+ async setItem(key, value) {
33
+ warnOnce();
34
+ memory.set(key, value);
35
+ },
36
+ async removeItem(key) {
37
+ warnOnce();
38
+ memory.delete(key);
39
+ },
40
+ async multiRemove(keys) {
41
+ warnOnce();
42
+ for (const k of keys)
43
+ memory.delete(k);
44
+ },
45
+ };
46
+ }
@@ -0,0 +1,5 @@
1
+ import { Theme } from './types';
2
+ /**
3
+ * Dark theme configuration
4
+ */
5
+ export declare const darkTheme: Theme;