@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.
- package/CHANGELOG.md +12 -0
- package/CONTRIBUTING.md +92 -0
- package/LICENSE +22 -0
- package/README.md +109 -0
- package/SECURITY.md +67 -0
- package/android/build.gradle +35 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/qafka/attestation/QafkaAttestationModule.kt +92 -0
- package/android/src/main/java/com/qafka/attestation/QafkaAttestationPackage.kt +22 -0
- package/android/src/main/java/com/qafka/audio/QafkaAudioModule.kt +290 -0
- package/android/src/main/java/com/qafka/clipboard/QafkaClipboardModule.kt +28 -0
- package/android/src/main/java/com/qafka/storage/QafkaStorageModule.kt +80 -0
- package/app.plugin.js +1 -0
- package/dist/QafkaSDK.d.ts +174 -0
- package/dist/QafkaSDK.js +461 -0
- package/dist/cards/bindings/resolveFieldName.d.ts +25 -0
- package/dist/cards/bindings/resolveFieldName.js +82 -0
- package/dist/cards/cta/CardContext.d.ts +16 -0
- package/dist/cards/cta/CardContext.js +58 -0
- package/dist/cards/cta/dispatcher.d.ts +7 -0
- package/dist/cards/cta/dispatcher.js +90 -0
- package/dist/cards/cta/types.d.ts +66 -0
- package/dist/cards/cta/types.js +2 -0
- package/dist/cards/index.d.ts +20 -0
- package/dist/cards/index.js +34 -0
- package/dist/cards/primitives/QButton.d.ts +10 -0
- package/dist/cards/primitives/QButton.js +115 -0
- package/dist/cards/primitives/QDivider.d.ts +7 -0
- package/dist/cards/primitives/QDivider.js +17 -0
- package/dist/cards/primitives/QIcon.d.ts +13 -0
- package/dist/cards/primitives/QIcon.js +26 -0
- package/dist/cards/primitives/QImage.d.ts +9 -0
- package/dist/cards/primitives/QImage.js +22 -0
- package/dist/cards/primitives/QText.d.ts +9 -0
- package/dist/cards/primitives/QText.js +30 -0
- package/dist/cards/primitives/QView.d.ts +8 -0
- package/dist/cards/primitives/QView.js +19 -0
- package/dist/cards/renderer/CardRenderer.d.ts +19 -0
- package/dist/cards/renderer/CardRenderer.js +64 -0
- package/dist/cards/renderer/renderNode.d.ts +13 -0
- package/dist/cards/renderer/renderNode.js +42 -0
- package/dist/cards/types.d.ts +110 -0
- package/dist/cards/types.js +6 -0
- package/dist/components/ActionResultBadge.d.ts +12 -0
- package/dist/components/ActionResultBadge.js +58 -0
- package/dist/components/ChatPage.d.ts +44 -0
- package/dist/components/ChatPage.js +84 -0
- package/dist/components/DataChip.d.ts +8 -0
- package/dist/components/DataChip.js +80 -0
- package/dist/components/DataChipList.d.ts +13 -0
- package/dist/components/DataChipList.js +21 -0
- package/dist/components/FloatingButton.d.ts +11 -0
- package/dist/components/FloatingButton.js +162 -0
- package/dist/components/InputArea.d.ts +57 -0
- package/dist/components/InputArea.js +142 -0
- package/dist/components/MarkdownText.d.ts +15 -0
- package/dist/components/MarkdownText.js +283 -0
- package/dist/components/MessageBubble.d.ts +134 -0
- package/dist/components/MessageBubble.js +384 -0
- package/dist/components/NavigationSuggestion.d.ts +11 -0
- package/dist/components/NavigationSuggestion.js +109 -0
- package/dist/components/Qafka.d.ts +39 -0
- package/dist/components/Qafka.handlers.d.ts +21 -0
- package/dist/components/Qafka.handlers.js +54 -0
- package/dist/components/Qafka.js +493 -0
- package/dist/components/Qafka.styles.d.ts +19 -0
- package/dist/components/Qafka.styles.js +101 -0
- package/dist/components/Qafka.types.d.ts +744 -0
- package/dist/components/Qafka.types.js +2 -0
- package/dist/components/Qafka.utils.d.ts +7 -0
- package/dist/components/Qafka.utils.js +34 -0
- package/dist/components/QafkaProvider.d.ts +12 -0
- package/dist/components/QafkaProvider.js +87 -0
- package/dist/components/QuickReplies.d.ts +14 -0
- package/dist/components/QuickReplies.js +48 -0
- package/dist/components/StepProgressIndicator.d.ts +12 -0
- package/dist/components/StepProgressIndicator.js +48 -0
- package/dist/components/SuggestionButton.d.ts +42 -0
- package/dist/components/SuggestionButton.js +67 -0
- package/dist/components/ToolStatusPill.d.ts +20 -0
- package/dist/components/ToolStatusPill.js +43 -0
- package/dist/components/TypingIndicator.d.ts +28 -0
- package/dist/components/TypingIndicator.js +109 -0
- package/dist/components/VoicePage.d.ts +48 -0
- package/dist/components/VoicePage.js +683 -0
- package/dist/components/defaults/DefaultCard.d.ts +14 -0
- package/dist/components/defaults/DefaultCard.js +156 -0
- package/dist/components/defaults/DefaultDetail.d.ts +14 -0
- package/dist/components/defaults/DefaultDetail.js +138 -0
- package/dist/components/defaults/DefaultList.d.ts +12 -0
- package/dist/components/defaults/DefaultList.js +98 -0
- package/dist/components/defaults/DefaultTable.d.ts +14 -0
- package/dist/components/defaults/DefaultTable.js +204 -0
- package/dist/components/defaults/index.d.ts +14 -0
- package/dist/components/defaults/index.js +25 -0
- package/dist/components/index.d.ts +22 -0
- package/dist/components/index.js +36 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +13 -0
- package/dist/hooks/useChatMessages.d.ts +72 -0
- package/dist/hooks/useChatMessages.js +505 -0
- package/dist/hooks/useContextManager.d.ts +12 -0
- package/dist/hooks/useContextManager.js +46 -0
- package/dist/hooks/useProjectTheme.d.ts +19 -0
- package/dist/hooks/useProjectTheme.js +163 -0
- package/dist/hooks/useSDK.d.ts +31 -0
- package/dist/hooks/useSDK.js +103 -0
- package/dist/hooks/useVoiceChat.d.ts +110 -0
- package/dist/hooks/useVoiceChat.js +436 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +59 -0
- package/dist/native/QafkaAttestation.d.ts +23 -0
- package/dist/native/QafkaAttestation.js +70 -0
- package/dist/native/QafkaAudio.d.ts +14 -0
- package/dist/native/QafkaAudio.js +31 -0
- package/dist/native/QafkaClipboard.d.ts +11 -0
- package/dist/native/QafkaClipboard.js +14 -0
- package/dist/native/QafkaStorage.d.ts +15 -0
- package/dist/native/QafkaStorage.js +12 -0
- package/dist/resolve-project-config.d.ts +35 -0
- package/dist/resolve-project-config.js +41 -0
- package/dist/runtime-config-loader.d.ts +37 -0
- package/dist/runtime-config-loader.js +53 -0
- package/dist/services/AttestationManager.d.ts +38 -0
- package/dist/services/AttestationManager.js +296 -0
- package/dist/services/BackendService.d.ts +156 -0
- package/dist/services/BackendService.js +755 -0
- package/dist/services/ConversationManager.d.ts +43 -0
- package/dist/services/ConversationManager.js +96 -0
- package/dist/services/NavigationHandler.d.ts +29 -0
- package/dist/services/NavigationHandler.js +70 -0
- package/dist/services/RealtimeService.d.ts +83 -0
- package/dist/services/RealtimeService.js +203 -0
- package/dist/services/storage.d.ts +11 -0
- package/dist/services/storage.js +15 -0
- package/dist/services/storageCore.d.ts +17 -0
- package/dist/services/storageCore.js +46 -0
- package/dist/themes/dark.d.ts +5 -0
- package/dist/themes/dark.js +129 -0
- package/dist/themes/index.d.ts +12 -0
- package/dist/themes/index.js +33 -0
- package/dist/themes/light.d.ts +5 -0
- package/dist/themes/light.js +129 -0
- package/dist/themes/types.d.ts +155 -0
- package/dist/themes/types.js +5 -0
- package/dist/types/chat.d.ts +126 -0
- package/dist/types/chat.js +5 -0
- package/dist/types/components.d.ts +56 -0
- package/dist/types/components.js +16 -0
- package/dist/types/external-navigation.d.ts +19 -0
- package/dist/types/external-navigation.js +8 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.js +25 -0
- package/dist/types/navigation.d.ts +86 -0
- package/dist/types/navigation.js +5 -0
- package/dist/types/sdk.d.ts +36 -0
- package/dist/types/sdk.js +5 -0
- package/dist/utils/deepMerge.d.ts +46 -0
- package/dist/utils/deepMerge.js +70 -0
- package/dist/utils/fontUtils.d.ts +8 -0
- package/dist/utils/fontUtils.js +16 -0
- package/dist/validate-end-user.d.ts +18 -0
- package/dist/validate-end-user.js +74 -0
- package/expo-plugin/withQafkaAttestation.js +57 -0
- package/ios/QafkaAttestation.m +25 -0
- package/ios/QafkaAttestation.swift +128 -0
- package/ios/QafkaAudio.m +23 -0
- package/ios/QafkaAudio.swift +519 -0
- package/ios/QafkaClipboard.m +10 -0
- package/ios/QafkaClipboard.swift +21 -0
- package/ios/QafkaReactImports.h +2 -0
- package/ios/QafkaStorage.m +26 -0
- package/ios/QafkaStorage.swift +118 -0
- package/package.json +82 -0
- package/qafka.config.d.ts +9 -0
- package/qafka.config.js +9 -0
- package/react-native-qafka.podspec +28 -0
- 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,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,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,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
|
+
}
|
package/ios/QafkaAudio.m
ADDED
|
@@ -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
|