@qafka/react-native 2.1.0 → 2.2.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 +6 -0
- package/dist/QafkaSDK.js +14 -1
- package/dist/components/Qafka.js +2 -1
- package/dist/components/Qafka.types.d.ts +11 -0
- package/dist/components/VoicePage.js +3 -1
- package/dist/hooks/useSDK.d.ts +5 -2
- package/dist/hooks/useSDK.js +7 -8
- package/dist/resolve-runtime-config-source.d.ts +9 -0
- package/dist/resolve-runtime-config-source.js +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.1.1] — 2026-06-03
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Chat surface could get stuck on the loading indicator (greeting + typing dots, no input) after navigating away from the chat screen and returning to it. Re-entering an already-initialized SDK now reports the ready state to the new mount, and tearing down a superseded instance no longer clears a newer live one.
|
|
15
|
+
|
|
10
16
|
## [2.0.0] — 2026-05-17
|
|
11
17
|
|
|
12
18
|
Initial public release. See [README.md](./README.md) for the full feature list and installation instructions.
|
package/dist/QafkaSDK.js
CHANGED
|
@@ -47,6 +47,14 @@ class QafkaSDK {
|
|
|
47
47
|
if (currentApiKey === newApiKey &&
|
|
48
48
|
currentSubProjectId === newSubProjectId &&
|
|
49
49
|
currentProjectId === newProjectId) {
|
|
50
|
+
// Already initialized with an identical config. A second component
|
|
51
|
+
// mount shares this singleton — the host can re-enter or re-push the
|
|
52
|
+
// chat screen via forward navigation before the previous instance has
|
|
53
|
+
// finished tearing down. Re-running init is unnecessary, but we MUST
|
|
54
|
+
// still notify this caller's status listener; otherwise its local
|
|
55
|
+
// `sdkReady` never flips true and the UI stalls on the loading gate
|
|
56
|
+
// with the input hidden.
|
|
57
|
+
config.onStatusChange?.('ready');
|
|
50
58
|
return;
|
|
51
59
|
}
|
|
52
60
|
await this.destroy();
|
|
@@ -457,7 +465,12 @@ class QafkaSDK {
|
|
|
457
465
|
this.config = null;
|
|
458
466
|
this.themePrefetchPromise = null;
|
|
459
467
|
this.status = 'uninitialized';
|
|
460
|
-
|
|
468
|
+
// Only clear the shared singleton slot when WE are still the current
|
|
469
|
+
// instance. An outgoing component unmounting must not null a newer
|
|
470
|
+
// instance that a freshly mounted component already created/initialized.
|
|
471
|
+
if (QafkaSDK.instance === this) {
|
|
472
|
+
QafkaSDK.instance = null;
|
|
473
|
+
}
|
|
461
474
|
}
|
|
462
475
|
}
|
|
463
476
|
exports.QafkaSDK = QafkaSDK;
|
package/dist/components/Qafka.js
CHANGED
|
@@ -86,11 +86,12 @@ const { width: SCREEN_WIDTH } = react_native_1.Dimensions.get('window');
|
|
|
86
86
|
* />
|
|
87
87
|
* ```
|
|
88
88
|
*/
|
|
89
|
-
exports.Qafka = (0, react_1.forwardRef)(function Qafka({ style, apiUrl: apiUrlProp, subProjectId, projectId, locale, mode = 'fullscreen', theme: themeName = 'light', customTheme, themeOverride, isAuthenticated, endUserId, endUserData, enableStreaming = true, voiceEnabled: voiceEnabledProp = true, context: userContext, contextDescription, components: customComponents, showTimestamps = true, placeholder = 'Type a message...', maxMessageLength = 500, greetingMessage, onReady, onMessageSent, onResponseReceived, onError, onNavigationSuggest, onNavigationAction, onExternalSuggestion, onCardDeepLink, onCardSuggestMessage, onCardExternalNavigation, onCardShare, onCardCopy, onCardToolTrigger, onCardCTAClick, onToolSuggested, onToolDataRequested, onActionResult, onStepCompleted, onFileUploadRequest, onExtractionResult, onClose, onBack, CloseComponent, BackComponent, navigationLabelFormat, NavigationButtonComponent, voiceComponents, voiceTranscript = 'centered', toolRenderMode, }, ref) {
|
|
89
|
+
exports.Qafka = (0, react_1.forwardRef)(function Qafka({ style, apiUrl: apiUrlProp, devConfig, subProjectId, projectId, locale, mode = 'fullscreen', theme: themeName = 'light', customTheme, themeOverride, isAuthenticated, endUserId, endUserData, enableStreaming = true, voiceEnabled: voiceEnabledProp = true, context: userContext, contextDescription, components: customComponents, showTimestamps = true, placeholder = 'Type a message...', maxMessageLength = 500, greetingMessage, onReady, onMessageSent, onResponseReceived, onError, onNavigationSuggest, onNavigationAction, onExternalSuggestion, onCardDeepLink, onCardSuggestMessage, onCardExternalNavigation, onCardShare, onCardCopy, onCardToolTrigger, onCardCTAClick, onToolSuggested, onToolDataRequested, onActionResult, onStepCompleted, onFileUploadRequest, onExtractionResult, onClose, onBack, CloseComponent, BackComponent, navigationLabelFormat, NavigationButtonComponent, voiceComponents, voiceTranscript = 'centered', toolRenderMode, }, ref) {
|
|
90
90
|
// === ALL HOOKS FIRST (Rules of Hooks compliance) ===
|
|
91
91
|
const insets = (0, react_native_safe_area_context_1.useSafeAreaInsets)();
|
|
92
92
|
const { sdkReady, error: sdkError, resolvedApiKey, resolvedApiUrl } = (0, useSDK_1.useSDK)({
|
|
93
93
|
apiUrl: apiUrlProp,
|
|
94
|
+
devConfig,
|
|
94
95
|
subProjectId,
|
|
95
96
|
projectId,
|
|
96
97
|
locale,
|
|
@@ -4,6 +4,7 @@ import { ComponentRegistry } from '../types/components';
|
|
|
4
4
|
import { ExternalSuggestion } from '../types/external-navigation';
|
|
5
5
|
import { NavigationSuggestion } from '../types/navigation';
|
|
6
6
|
import { VoiceChatState } from './VoicePage';
|
|
7
|
+
import type { RuntimeConfig } from '../runtime-config-loader';
|
|
7
8
|
/**
|
|
8
9
|
* Augmentation hook for consumer-side projectId literal types.
|
|
9
10
|
*
|
|
@@ -256,6 +257,16 @@ export interface QafkaProps {
|
|
|
256
257
|
* Backend API URL (OPTIONAL — advanced, leave unset for production).
|
|
257
258
|
*/
|
|
258
259
|
apiUrl?: string;
|
|
260
|
+
/**
|
|
261
|
+
* Dev-only runtime config the SDK reads for the simulator/emulator
|
|
262
|
+
* `__DEV__` auth path. Pass the CLI-managed file:
|
|
263
|
+
* `devConfig={__DEV__ ? require('../.qafka/qafka-runtime') : undefined}`
|
|
264
|
+
* Omit on real devices / production — attestation handles auth there, and
|
|
265
|
+
* `require` under `__DEV__` is dead-code-eliminated from release bundles so
|
|
266
|
+
* no key reaches the prod build. When omitted, the SDK falls back to the
|
|
267
|
+
* legacy `node_modules` config (backward compatible).
|
|
268
|
+
*/
|
|
269
|
+
devConfig?: RuntimeConfig | null;
|
|
259
270
|
/**
|
|
260
271
|
* Sub-Project Identifier (OPTIONAL)
|
|
261
272
|
*
|
|
@@ -569,7 +569,9 @@ function VoicePage({ state, transcript, userTranscript, transcriptHistory, trans
|
|
|
569
569
|
|
|
570
570
|
{showMute && !hasToolUI ? (<react_native_1.View style={{
|
|
571
571
|
position: 'absolute',
|
|
572
|
-
|
|
572
|
+
// Absolute children aren't pushed by the parent's safe-area paddingTop,
|
|
573
|
+
// so add the inset here to keep the button clear of the status bar / notch.
|
|
574
|
+
top: insets.top + 12,
|
|
573
575
|
right: 16,
|
|
574
576
|
}}>
|
|
575
577
|
<MuteButton isMuted={isMuted} onToggle={onToggleMute} theme={theme} state={state}/>
|
package/dist/hooks/useSDK.d.ts
CHANGED
|
@@ -10,12 +10,15 @@ export interface UseSDKOptions {
|
|
|
10
10
|
locale?: string;
|
|
11
11
|
onReady?: () => void;
|
|
12
12
|
onError?: (error: Error) => void;
|
|
13
|
+
devConfig?: unknown;
|
|
13
14
|
}
|
|
14
15
|
export interface UseSDKResult {
|
|
15
16
|
sdkReady: boolean;
|
|
16
17
|
error: string | null;
|
|
17
18
|
/**
|
|
18
|
-
* Dev-only: the resolved developmentKey from
|
|
19
|
+
* Dev-only: the resolved developmentKey from the consumer dev config — the
|
|
20
|
+
* `devConfig` prop (`.qafka/qafka-runtime.js`) or the legacy node_modules
|
|
21
|
+
* fallback. Null in
|
|
19
22
|
* production builds (Metro dead-code-eliminates the dev config branch).
|
|
20
23
|
* Used by `useVoiceChat` whose WebSocket auth still relies on apiKey.
|
|
21
24
|
*/
|
|
@@ -28,4 +31,4 @@ export interface UseSDKResult {
|
|
|
28
31
|
/**
|
|
29
32
|
* Custom hook for managing SDK initialization and lifecycle
|
|
30
33
|
*/
|
|
31
|
-
export declare const useSDK: ({ apiUrl, subProjectId, projectId, locale, onReady, onError, }: UseSDKOptions) => UseSDKResult;
|
|
34
|
+
export declare const useSDK: ({ apiUrl, subProjectId, projectId, locale, onReady, onError, devConfig, }: UseSDKOptions) => UseSDKResult;
|
package/dist/hooks/useSDK.js
CHANGED
|
@@ -5,10 +5,11 @@ const react_1 = require("react");
|
|
|
5
5
|
const QafkaSDK_1 = require("../QafkaSDK");
|
|
6
6
|
const runtime_config_loader_1 = require("../runtime-config-loader");
|
|
7
7
|
const resolve_project_config_1 = require("../resolve-project-config");
|
|
8
|
+
const resolve_runtime_config_source_1 = require("../resolve-runtime-config-source");
|
|
8
9
|
/**
|
|
9
10
|
* Custom hook for managing SDK initialization and lifecycle
|
|
10
11
|
*/
|
|
11
|
-
const useSDK = ({ apiUrl, subProjectId, projectId, locale, onReady, onError, }) => {
|
|
12
|
+
const useSDK = ({ apiUrl, subProjectId, projectId, locale, onReady, onError, devConfig, }) => {
|
|
12
13
|
const [sdkReady, setSdkReady] = (0, react_1.useState)(false);
|
|
13
14
|
const [error, setError] = (0, react_1.useState)(null);
|
|
14
15
|
const [resolvedApiKey, setResolvedApiKey] = (0, react_1.useState)(null);
|
|
@@ -27,18 +28,16 @@ const useSDK = ({ apiUrl, subProjectId, projectId, locale, onReady, onError, })
|
|
|
27
28
|
let apiKey = null;
|
|
28
29
|
let resolvedUrl = apiUrl;
|
|
29
30
|
if (__DEV__) {
|
|
30
|
-
// Dev path:
|
|
31
|
-
// Metro dead-code elimination removes this
|
|
32
|
-
//
|
|
31
|
+
// Dev path: prefer the consumer-supplied .qafka/ config, else the legacy
|
|
32
|
+
// node_modules config. Metro dead-code elimination removes this whole
|
|
33
|
+
// branch (and both requires) from production builds.
|
|
33
34
|
try {
|
|
34
|
-
const cfg = (0, resolve_project_config_1.resolveProjectConfig)({ apiUrl, projectId }, (0, runtime_config_loader_1.loadRuntimeConfig)());
|
|
35
|
+
const cfg = (0, resolve_project_config_1.resolveProjectConfig)({ apiUrl, projectId }, (0, resolve_runtime_config_source_1.resolveRuntimeConfigSource)(devConfig, (0, runtime_config_loader_1.loadRuntimeConfig)()));
|
|
35
36
|
apiKey = cfg.apiKey;
|
|
36
37
|
resolvedUrl = cfg.apiUrl;
|
|
37
38
|
}
|
|
38
39
|
catch (err) {
|
|
39
|
-
|
|
40
|
-
console.warn('[Qafka] Dev config missing or invalid:', err instanceof Error ? err.message : err);
|
|
41
|
-
}
|
|
40
|
+
console.warn('[Qafka] Dev config missing or invalid:', err instanceof Error ? err.message : err);
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
// Expose resolved values so `useVoiceChat` (WebSocket auth still
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type RuntimeConfig } from './runtime-config-loader';
|
|
2
|
+
/**
|
|
3
|
+
* Pick the runtime config the SDK should use in dev builds.
|
|
4
|
+
*
|
|
5
|
+
* Precedence: a valid `devConfig` (the consumer's `require('../.qafka/qafka-runtime')`,
|
|
6
|
+
* shape-validated) wins; otherwise the value loaded from the SDK package
|
|
7
|
+
* (`loadRuntimeConfig()`). Returns null when neither yields a usable config.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveRuntimeConfigSource(devConfig: unknown, loaded: RuntimeConfig | null): RuntimeConfig | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveRuntimeConfigSource = resolveRuntimeConfigSource;
|
|
4
|
+
const runtime_config_loader_1 = require("./runtime-config-loader");
|
|
5
|
+
/**
|
|
6
|
+
* Pick the runtime config the SDK should use in dev builds.
|
|
7
|
+
*
|
|
8
|
+
* Precedence: a valid `devConfig` (the consumer's `require('../.qafka/qafka-runtime')`,
|
|
9
|
+
* shape-validated) wins; otherwise the value loaded from the SDK package
|
|
10
|
+
* (`loadRuntimeConfig()`). Returns null when neither yields a usable config.
|
|
11
|
+
*/
|
|
12
|
+
function resolveRuntimeConfigSource(devConfig, loaded) {
|
|
13
|
+
const provided = devConfig != null ? (0, runtime_config_loader_1.normalizeRuntimeConfig)(devConfig) : null;
|
|
14
|
+
return provided ?? loaded;
|
|
15
|
+
}
|
package/package.json
CHANGED