@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,8 @@
1
+ "use strict";
2
+ /**
3
+ * External Navigation — SDK types.
4
+ *
5
+ * Describes external destinations (WhatsApp, phone, map, app store, etc.)
6
+ * that buttons under chat messages can redirect to outside the host app.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Type exports for QafkaSDK
3
+ * Self-contained types (no external dependencies)
4
+ */
5
+ export * from './navigation';
6
+ export * from './chat';
7
+ export * from './sdk';
8
+ export * from './components';
9
+ export * from './external-navigation';
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ /**
3
+ * Type exports for QafkaSDK
4
+ * Self-contained types (no external dependencies)
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ __exportStar(require("./navigation"), exports);
22
+ __exportStar(require("./chat"), exports);
23
+ __exportStar(require("./sdk"), exports);
24
+ __exportStar(require("./components"), exports);
25
+ __exportStar(require("./external-navigation"), exports);
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Navigation-related types for React Navigation analysis and mapping
3
+ */
4
+ /**
5
+ * Supported navigation library types
6
+ */
7
+ export type NavigationType = 'react-navigation' | 'expo-router' | 'react-router-native' | 'mixed';
8
+ /**
9
+ * Navigator types
10
+ */
11
+ export type NavigatorType = 'Stack' | 'Tab' | 'Drawer' | 'BottomTab' | 'MaterialTopTab';
12
+ /**
13
+ * Screen parameter type definition
14
+ */
15
+ export interface ScreenParam {
16
+ name: string;
17
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array';
18
+ required: boolean;
19
+ description?: string;
20
+ }
21
+ /**
22
+ * Screen definition in navigation structure
23
+ */
24
+ export interface Screen {
25
+ name: string;
26
+ component: string;
27
+ path: string;
28
+ params: ScreenParam[];
29
+ deeplink?: string;
30
+ description?: string;
31
+ options?: Record<string, any>;
32
+ }
33
+ /**
34
+ * Navigator (Stack, Tab, Drawer) definition
35
+ */
36
+ export interface Navigator {
37
+ name: string;
38
+ type: NavigatorType;
39
+ screens: Screen[];
40
+ initialRouteName?: string;
41
+ }
42
+ /**
43
+ * Complete navigation structure
44
+ */
45
+ export interface Navigation {
46
+ type: NavigationType;
47
+ stacks: Navigator[];
48
+ tabs?: Navigator[];
49
+ drawers?: Navigator[];
50
+ }
51
+ /**
52
+ * Full navigation schema with metadata
53
+ */
54
+ export interface NavigationSchema {
55
+ version: string;
56
+ projectId: string;
57
+ navigation: Navigation;
58
+ analyzedAt: string;
59
+ metadata?: {
60
+ totalScreens: number;
61
+ totalRoutes: number;
62
+ hasDeepLinks: boolean;
63
+ libraryVersion?: string;
64
+ };
65
+ }
66
+ /**
67
+ * Navigation suggestion returned by the backend.
68
+ */
69
+ export interface NavigationSuggestion {
70
+ screenName: string;
71
+ route: string;
72
+ deeplink?: string | null;
73
+ params?: Record<string, any>;
74
+ confirmed?: boolean;
75
+ message?: string;
76
+ reasoning?: string;
77
+ }
78
+ /**
79
+ * Route matching result
80
+ */
81
+ export interface RouteMatch {
82
+ screen: Screen;
83
+ navigator: Navigator;
84
+ score: number;
85
+ path: string[];
86
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * Navigation-related types for React Navigation analysis and mapping
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * SDK configuration types
3
+ */
4
+ /**
5
+ * SDK configuration passed to QafkaSDK.initialize().
6
+ */
7
+ export interface SDKConfig {
8
+ /**
9
+ * Development API key. Present in dev builds (loaded from qafka.config.js);
10
+ * null/undefined in production builds, which authenticate without it.
11
+ */
12
+ apiKey?: string | null;
13
+ /** Sub-project identifier (same apiKey, different sub-project). */
14
+ subProjectId?: string;
15
+ /** Advanced — leave unset for production. */
16
+ apiUrl?: string;
17
+ /** React Navigation ref, used when the SDK navigates on your behalf. */
18
+ navigationRef?: any;
19
+ /** Enable verbose dev logging. */
20
+ debug?: boolean;
21
+ /** Request timeout in ms. */
22
+ timeout?: number;
23
+ /**
24
+ * BCP 47 locale (e.g. "tr", "en", "tr-TR"). When set, the SDK
25
+ * forwards it as `sdkContext.locale` on every chat request and uses it
26
+ * for UI strings.
27
+ */
28
+ locale?: string;
29
+ onStatusChange?: (status: SDKStatusType) => void;
30
+ onNavigationSuggest?: (suggestion: any) => void;
31
+ onError?: (error: Error) => void;
32
+ }
33
+ /**
34
+ * SDK status type
35
+ */
36
+ export type SDKStatusType = 'uninitialized' | 'initializing' | 'ready' | 'error';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * SDK configuration types
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Deep merge utility for theme objects
3
+ * Allows partial theme overrides while preserving default values
4
+ */
5
+ export type DeepPartial<T> = {
6
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
7
+ };
8
+ /**
9
+ * Deep merge two objects
10
+ * Source properties override target properties
11
+ * Nested objects are merged recursively
12
+ */
13
+ export declare function deepMerge<T extends Record<string, any>>(target: T, source: DeepPartial<T> | undefined | null): T;
14
+ /**
15
+ * Example usage:
16
+ *
17
+ * const defaultTheme = {
18
+ * colors: {
19
+ * primary: '#007AFF',
20
+ * background: '#FFFFFF',
21
+ * },
22
+ * spacing: {
23
+ * sm: 8,
24
+ * md: 16,
25
+ * },
26
+ * };
27
+ *
28
+ * const customTheme = {
29
+ * colors: {
30
+ * background: '#F5F5F5', // Override only background
31
+ * },
32
+ * };
33
+ *
34
+ * const mergedTheme = deepMerge(defaultTheme, customTheme);
35
+ * // Result:
36
+ * // {
37
+ * // colors: {
38
+ * // primary: '#007AFF', // Kept from default
39
+ * // background: '#F5F5F5', // Overridden
40
+ * // },
41
+ * // spacing: {
42
+ * // sm: 8, // Kept from default
43
+ * // md: 16, // Kept from default
44
+ * // },
45
+ * // }
46
+ */
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * Deep merge utility for theme objects
4
+ * Allows partial theme overrides while preserving default values
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.deepMerge = deepMerge;
8
+ /**
9
+ * Deep merge two objects
10
+ * Source properties override target properties
11
+ * Nested objects are merged recursively
12
+ */
13
+ function deepMerge(target, source) {
14
+ if (!source)
15
+ return target;
16
+ const result = { ...target };
17
+ for (const key in source) {
18
+ if (source.hasOwnProperty(key)) {
19
+ const targetValue = target[key];
20
+ const sourceValue = source[key];
21
+ if (sourceValue &&
22
+ typeof sourceValue === 'object' &&
23
+ !Array.isArray(sourceValue) &&
24
+ targetValue &&
25
+ typeof targetValue === 'object' &&
26
+ !Array.isArray(targetValue)) {
27
+ // Recursively merge nested objects
28
+ result[key] = deepMerge(targetValue, sourceValue);
29
+ }
30
+ else {
31
+ // Direct assignment for primitives and arrays
32
+ result[key] = sourceValue;
33
+ }
34
+ }
35
+ }
36
+ return result;
37
+ }
38
+ /**
39
+ * Example usage:
40
+ *
41
+ * const defaultTheme = {
42
+ * colors: {
43
+ * primary: '#007AFF',
44
+ * background: '#FFFFFF',
45
+ * },
46
+ * spacing: {
47
+ * sm: 8,
48
+ * md: 16,
49
+ * },
50
+ * };
51
+ *
52
+ * const customTheme = {
53
+ * colors: {
54
+ * background: '#F5F5F5', // Override only background
55
+ * },
56
+ * };
57
+ *
58
+ * const mergedTheme = deepMerge(defaultTheme, customTheme);
59
+ * // Result:
60
+ * // {
61
+ * // colors: {
62
+ * // primary: '#007AFF', // Kept from default
63
+ * // background: '#F5F5F5', // Overridden
64
+ * // },
65
+ * // spacing: {
66
+ * // sm: 8, // Kept from default
67
+ * // md: 16, // Kept from default
68
+ * // },
69
+ * // }
70
+ */
@@ -0,0 +1,8 @@
1
+ import { Theme } from '../themes/types';
2
+ /**
3
+ * Helper to resolve the correct font family from the theme based on weight/style.
4
+ * Priority:
5
+ * 1. theme.typography.fonts[style] (if available)
6
+ * 2. theme.typography.fontFamily (fallback)
7
+ */
8
+ export declare const getFontFamily: (theme: Theme, variant?: "regular" | "medium" | "semibold" | "bold" | "italic" | "light" | "code") => string | undefined;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFontFamily = void 0;
4
+ /**
5
+ * Helper to resolve the correct font family from the theme based on weight/style.
6
+ * Priority:
7
+ * 1. theme.typography.fonts[style] (if available)
8
+ * 2. theme.typography.fontFamily (fallback)
9
+ */
10
+ const getFontFamily = (theme, variant = 'regular') => {
11
+ if (theme.typography.fonts && theme.typography.fonts[variant]) {
12
+ return theme.typography.fonts[variant];
13
+ }
14
+ return theme.typography.fontFamily;
15
+ };
16
+ exports.getFontFamily = getFontFamily;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * End-user prop validation.
3
+ *
4
+ * Runs at SDK mount and again on every chat request so a stale closure or
5
+ * a runtime mutation of the prop is caught at the boundary instead of
6
+ * silently dropping the tracking lane. Failures throw so the host app's
7
+ * red-screen catches them in development; production builds surface them
8
+ * via the same `componentDidCatch` boundaries hosting code already uses.
9
+ *
10
+ * The size cap for `endUserData` does NOT throw — over-limit profiles are
11
+ * dropped from the outgoing payload after a `console.error`. Chat still
12
+ * works, the operator dashboard just shows nothing in the End User card
13
+ * for that request. The asymmetry is intentional: `endUserId` is the
14
+ * tracking anchor (a hard contract failure), while `endUserData` is
15
+ * supplemental detail (degrade gracefully).
16
+ */
17
+ export declare function validateEndUserId(raw: unknown): string;
18
+ export declare function validateEndUserData(raw: unknown): Record<string, unknown> | undefined;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * End-user prop validation.
4
+ *
5
+ * Runs at SDK mount and again on every chat request so a stale closure or
6
+ * a runtime mutation of the prop is caught at the boundary instead of
7
+ * silently dropping the tracking lane. Failures throw so the host app's
8
+ * red-screen catches them in development; production builds surface them
9
+ * via the same `componentDidCatch` boundaries hosting code already uses.
10
+ *
11
+ * The size cap for `endUserData` does NOT throw — over-limit profiles are
12
+ * dropped from the outgoing payload after a `console.error`. Chat still
13
+ * works, the operator dashboard just shows nothing in the End User card
14
+ * for that request. The asymmetry is intentional: `endUserId` is the
15
+ * tracking anchor (a hard contract failure), while `endUserData` is
16
+ * supplemental detail (degrade gracefully).
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.validateEndUserId = validateEndUserId;
20
+ exports.validateEndUserData = validateEndUserData;
21
+ const MAX_END_USER_ID_LENGTH = 256;
22
+ const MAX_END_USER_DATA_BYTES = 8 * 1024;
23
+ function validateEndUserId(raw) {
24
+ if (raw === undefined || raw === null) {
25
+ throw new Error('Qafka: endUserId is required. Pass the signed-in user id, or a stable placeholder like "anonymous" or `anon-${deviceId}` before login.');
26
+ }
27
+ let coerced;
28
+ if (typeof raw === 'number') {
29
+ if (!Number.isFinite(raw)) {
30
+ throw new Error('Qafka: endUserId must be a finite number when passed as number');
31
+ }
32
+ coerced = String(raw);
33
+ }
34
+ else if (typeof raw === 'string') {
35
+ coerced = raw;
36
+ }
37
+ else {
38
+ throw new Error('Qafka: endUserId must be a string or number');
39
+ }
40
+ const trimmed = coerced.trim();
41
+ if (trimmed.length === 0) {
42
+ throw new Error('Qafka: endUserId cannot be empty or whitespace. Use a stable placeholder like "anonymous" for pre-login state.');
43
+ }
44
+ if (trimmed.length > MAX_END_USER_ID_LENGTH) {
45
+ throw new Error(`Qafka: endUserId exceeds ${MAX_END_USER_ID_LENGTH} character limit (got ${trimmed.length}).`);
46
+ }
47
+ return trimmed;
48
+ }
49
+ function validateEndUserData(raw) {
50
+ if (raw === undefined || raw === null)
51
+ return undefined;
52
+ if (typeof raw !== 'object' || Array.isArray(raw)) {
53
+ // Wrong shape → drop. Throwing would prevent chat from working on a
54
+ // pure UI mistake, which is too aggressive for a supplemental field.
55
+ // eslint-disable-next-line no-console
56
+ console.error('Qafka: endUserData must be a plain object — dropped from request.');
57
+ return undefined;
58
+ }
59
+ let serialized;
60
+ try {
61
+ serialized = JSON.stringify(raw);
62
+ }
63
+ catch {
64
+ // eslint-disable-next-line no-console
65
+ console.error('Qafka: endUserData contains circular references or non-serializable values — dropped from request.');
66
+ return undefined;
67
+ }
68
+ if (serialized.length > MAX_END_USER_DATA_BYTES) {
69
+ // eslint-disable-next-line no-console
70
+ console.error(`Qafka: endUserData exceeds ${MAX_END_USER_DATA_BYTES}B serialized limit (got ${serialized.length}B) — dropped from request. Trim the object or move bulky fields to a separate endpoint.`);
71
+ return undefined;
72
+ }
73
+ return raw;
74
+ }
@@ -0,0 +1,57 @@
1
+ const { withEntitlementsPlist, withAppBuildGradle, withInfoPlist, withAndroidManifest } = require('expo/config-plugins');
2
+
3
+ const withQafkaAttestationIOS = (config, { appleEnvironment = 'production' } = {}) => {
4
+ return withEntitlementsPlist(config, (config) => {
5
+ config.modResults['com.apple.developer.devicecheck.appattest-environment'] =
6
+ appleEnvironment;
7
+ return config;
8
+ });
9
+ };
10
+
11
+ const withQafkaAttestationAndroid = (config) => {
12
+ return withAppBuildGradle(config, (config) => {
13
+ const contents = config.modResults.contents;
14
+ const dependency = " implementation 'com.google.android.play:integrity:1.4.0'";
15
+
16
+ if (!contents.includes('play:integrity')) {
17
+ config.modResults.contents = contents.replace(
18
+ /dependencies\s*\{/,
19
+ `dependencies {\n${dependency}`,
20
+ );
21
+ }
22
+
23
+ return config;
24
+ });
25
+ };
26
+
27
+ const withQafkaMicPermissionIOS = (config, { microphoneUsageDescription = 'Voice chat requires microphone access' } = {}) => {
28
+ return withInfoPlist(config, (config) => {
29
+ config.modResults.NSMicrophoneUsageDescription = microphoneUsageDescription;
30
+ return config;
31
+ });
32
+ };
33
+
34
+ const withQafkaMicPermissionAndroid = (config) => {
35
+ return withAndroidManifest(config, (config) => {
36
+ const manifest = config.modResults.manifest;
37
+ const permissions = manifest['uses-permission'] || [];
38
+ const hasMicPermission = permissions.some(
39
+ (p) => p.$['android:name'] === 'android.permission.RECORD_AUDIO'
40
+ );
41
+ if (!hasMicPermission) {
42
+ permissions.push({ $: { 'android:name': 'android.permission.RECORD_AUDIO' } });
43
+ }
44
+ manifest['uses-permission'] = permissions;
45
+ return config;
46
+ });
47
+ };
48
+
49
+ const withQafkaAttestation = (config, props = {}) => {
50
+ config = withQafkaAttestationIOS(config, props || {});
51
+ config = withQafkaAttestationAndroid(config);
52
+ config = withQafkaMicPermissionIOS(config, props || {});
53
+ config = withQafkaMicPermissionAndroid(config);
54
+ return config;
55
+ };
56
+
57
+ module.exports = withQafkaAttestation;
@@ -0,0 +1,25 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(QafkaAttestation, NSObject)
4
+
5
+ RCT_EXTERN_METHOD(isSupported:
6
+ (RCTPromiseResolveBlock)resolve
7
+ rejecter:(RCTPromiseRejectBlock)reject)
8
+
9
+ RCT_EXTERN_METHOD(generateKey:
10
+ (RCTPromiseResolveBlock)resolve
11
+ rejecter:(RCTPromiseRejectBlock)reject)
12
+
13
+ RCT_EXTERN_METHOD(attestKey:
14
+ (NSString *)keyId
15
+ challengeBase64:(NSString *)challengeBase64
16
+ resolver:(RCTPromiseResolveBlock)resolve
17
+ rejecter:(RCTPromiseRejectBlock)reject)
18
+
19
+ RCT_EXTERN_METHOD(generateAssertion:
20
+ (NSString *)keyId
21
+ clientDataBase64:(NSString *)clientDataBase64
22
+ resolver:(RCTPromiseResolveBlock)resolve
23
+ rejecter:(RCTPromiseRejectBlock)reject)
24
+
25
+ @end
@@ -0,0 +1,128 @@
1
+ import Foundation
2
+ import DeviceCheck
3
+ import CryptoKit
4
+ import UIKit
5
+
6
+ @objc(QafkaAttestation)
7
+ class QafkaAttestation: NSObject {
8
+
9
+ @objc static func requiresMainQueueSetup() -> Bool {
10
+ return true
11
+ }
12
+
13
+ @objc func constantsToExport() -> [AnyHashable: Any]! {
14
+ let bundle = Bundle.main
15
+ let bundleId = bundle.bundleIdentifier ?? ""
16
+ let appVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
17
+ let deviceModel = UIDevice.current.model
18
+ return [
19
+ "bundleId": bundleId,
20
+ "appVersion": appVersion,
21
+ "deviceModel": deviceModel,
22
+ ]
23
+ }
24
+
25
+ @objc func isSupported(
26
+ _ resolve: @escaping RCTPromiseResolveBlock,
27
+ rejecter reject: @escaping RCTPromiseRejectBlock
28
+ ) {
29
+ if #available(iOS 14.0, *) {
30
+ resolve(DCAppAttestService.shared.isSupported)
31
+ } else {
32
+ resolve(false)
33
+ }
34
+ }
35
+
36
+ @objc func generateKey(
37
+ _ resolve: @escaping RCTPromiseResolveBlock,
38
+ rejecter reject: @escaping RCTPromiseRejectBlock
39
+ ) {
40
+ guard #available(iOS 14.0, *) else {
41
+ reject("UNSUPPORTED", "App Attest requires iOS 14+", nil)
42
+ return
43
+ }
44
+ let service = DCAppAttestService.shared
45
+ guard service.isSupported else {
46
+ reject("UNSUPPORTED", "App Attest not supported on this device", nil)
47
+ return
48
+ }
49
+
50
+ service.generateKey { keyId, error in
51
+ if let error = error {
52
+ reject("KEY_GEN_FAILED", error.localizedDescription, error)
53
+ } else if let keyId = keyId {
54
+ resolve(keyId)
55
+ } else {
56
+ reject("KEY_GEN_FAILED", "Unknown error generating key", nil)
57
+ }
58
+ }
59
+ }
60
+
61
+ @objc func attestKey(
62
+ _ keyId: String,
63
+ challengeBase64: String,
64
+ resolver resolve: @escaping RCTPromiseResolveBlock,
65
+ rejecter reject: @escaping RCTPromiseRejectBlock
66
+ ) {
67
+ guard #available(iOS 14.0, *) else {
68
+ reject("UNSUPPORTED", "App Attest requires iOS 14+", nil)
69
+ return
70
+ }
71
+
72
+ guard let challengeData = Data(base64Encoded: challengeBase64) else {
73
+ reject("INVALID_INPUT", "Invalid base64 challenge", nil)
74
+ return
75
+ }
76
+
77
+ let hash = Data(SHA256.hash(data: challengeData))
78
+
79
+ DCAppAttestService.shared.attestKey(keyId, clientDataHash: hash) { attestation, error in
80
+ if let error = error {
81
+ let code: String
82
+ if let dcError = error as? DCError {
83
+ switch dcError.code {
84
+ case .invalidKey: code = "INVALID_KEY"
85
+ case .serverUnavailable: code = "SERVER_UNAVAILABLE"
86
+ default: code = "ATTEST_FAILED"
87
+ }
88
+ } else {
89
+ code = "ATTEST_FAILED"
90
+ }
91
+ reject(code, error.localizedDescription, error)
92
+ } else if let attestation = attestation {
93
+ resolve(attestation.base64EncodedString())
94
+ } else {
95
+ reject("ATTEST_FAILED", "Unknown error during attestation", nil)
96
+ }
97
+ }
98
+ }
99
+
100
+ @objc func generateAssertion(
101
+ _ keyId: String,
102
+ clientDataBase64: String,
103
+ resolver resolve: @escaping RCTPromiseResolveBlock,
104
+ rejecter reject: @escaping RCTPromiseRejectBlock
105
+ ) {
106
+ guard #available(iOS 14.0, *) else {
107
+ reject("UNSUPPORTED", "App Attest requires iOS 14+", nil)
108
+ return
109
+ }
110
+
111
+ guard let clientData = Data(base64Encoded: clientDataBase64) else {
112
+ reject("INVALID_INPUT", "Invalid base64 client data", nil)
113
+ return
114
+ }
115
+
116
+ let hash = Data(SHA256.hash(data: clientData))
117
+
118
+ DCAppAttestService.shared.generateAssertion(keyId, clientDataHash: hash) { assertion, error in
119
+ if let error = error {
120
+ reject("ASSERTION_FAILED", error.localizedDescription, error)
121
+ } else if let assertion = assertion {
122
+ resolve(assertion.base64EncodedString())
123
+ } else {
124
+ reject("ASSERTION_FAILED", "Unknown error generating assertion", nil)
125
+ }
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,23 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(QafkaAudio, RCTEventEmitter)
5
+
6
+ RCT_EXTERN_METHOD(startCapture:
7
+ (RCTPromiseResolveBlock)resolve
8
+ rejecter:(RCTPromiseRejectBlock)reject)
9
+
10
+ RCT_EXTERN_METHOD(stopCapture:
11
+ (RCTPromiseResolveBlock)resolve
12
+ rejecter:(RCTPromiseRejectBlock)reject)
13
+
14
+ RCT_EXTERN_METHOD(playAudioChunk:
15
+ (NSString *)base64Data
16
+ resolver:(RCTPromiseResolveBlock)resolve
17
+ rejecter:(RCTPromiseRejectBlock)reject)
18
+
19
+ RCT_EXTERN_METHOD(stopPlayback:
20
+ (RCTPromiseResolveBlock)resolve
21
+ rejecter:(RCTPromiseRejectBlock)reject)
22
+
23
+ @end