@qafka/react-native 2.4.0 → 2.4.2

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 CHANGED
@@ -7,6 +7,18 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.4.2] — 2026-06-22
11
+
12
+ ### Fixed
13
+
14
+ - Voice could fail to connect (the orb stayed on the connecting/waiting state) in apps that did not pass an explicit `apiUrl`. The realtime WebSocket now falls back to the same default backend host as the REST/chat client, instead of building a host-less relative URL that could never complete its handshake. Apps relying on the default backend no longer need to set `apiUrl` just to make voice work.
15
+
16
+ ## [2.4.1] — 2026-06-21
17
+
18
+ ### Fixed
19
+
20
+ - Development experience: when a development (test) API key has been rotated and the app is still running the old key, chat no longer fails silently. In development builds the SDK now logs an actionable warning and shows an in-chat message telling the developer to run `qafka refresh` and restart Metro with `--clear`. This guidance is stripped from release builds (`__DEV__`-gated) and only triggers for development keys, so it can never appear in production.
21
+
10
22
  ## [2.4.0] — 2026-06-21
11
23
 
12
24
  ### Added
package/dist/QafkaSDK.js CHANGED
@@ -6,6 +6,7 @@ const BackendService_1 = require("./services/BackendService");
6
6
  const AttestationManager_1 = require("./services/AttestationManager");
7
7
  const ConversationManager_1 = require("./services/ConversationManager");
8
8
  const NavigationHandler_1 = require("./services/NavigationHandler");
9
+ const constants_1 = require("./constants");
9
10
  /**
10
11
  * QafkaSDK - Main SDK class
11
12
  * Provides AI-powered conversational interface for React Native apps
@@ -85,7 +86,7 @@ class QafkaSDK {
85
86
  await this.conversationManager.initialize();
86
87
  this.navigationHandler = new NavigationHandler_1.NavigationHandler(config.navigationRef);
87
88
  // Initialize device attestation
88
- const rawUrl = (config.apiUrl || 'https://api.qafka.com/api/v1').replace(/\/$/, '');
89
+ const rawUrl = (config.apiUrl || `${constants_1.DEFAULT_API_BASE}/api/v1`).replace(/\/$/, '');
89
90
  const attestApiUrl = rawUrl.includes('/api/v1') ? rawUrl : `${rawUrl}/api/v1`;
90
91
  this.attestationManager = new AttestationManager_1.AttestationManager({
91
92
  apiUrl: attestApiUrl,
@@ -49,6 +49,7 @@ const validate_end_user_1 = require("../validate-end-user");
49
49
  const ChatPage_1 = require("./ChatPage");
50
50
  const VoicePage_1 = require("./VoicePage");
51
51
  const useVoiceChat_1 = require("../hooks/useVoiceChat");
52
+ const constants_1 = require("../constants");
52
53
  const { width: SCREEN_WIDTH } = react_native_1.Dimensions.get('window');
53
54
  /**
54
55
  * Qafka Component
@@ -126,10 +127,10 @@ exports.Qafka = (0, react_1.forwardRef)(function Qafka({ style, apiUrl: apiUrlPr
126
127
  debounceMs: 500,
127
128
  });
128
129
  const voiceChat = (0, useVoiceChat_1.useVoiceChat)({
129
- // Voice WebSocket still uses apiKey-based auth. In dev, the resolved
130
- // developmentKey from qafka.config.js flows through. In production, this
131
- // is null and voice will fail at WS handshake.
132
- apiUrl: resolvedApiUrl || apiUrlProp || '',
130
+ // Voice realtime WebSocket. Fall back to the shared default backend host so
131
+ // the WS URL never resolves host-less (an empty apiUrl produced a relative
132
+ // `/v1/realtime` URL → handshake failure). Mirrors the REST client default.
133
+ apiUrl: resolvedApiUrl || apiUrlProp || constants_1.DEFAULT_API_BASE,
133
134
  apiKey: resolvedApiKey || '',
134
135
  userContext: currentContext,
135
136
  contextDescription,
@@ -311,6 +312,20 @@ exports.Qafka = (0, react_1.forwardRef)(function Qafka({ style, apiUrl: apiUrlPr
311
312
  setIsInitialMessageLoading(false);
312
313
  if (__DEV__) {
313
314
  console.warn('[Qafka] Initial message error:', err.message);
315
+ // Dev-only: a stale rotated test key would otherwise fail silently.
316
+ // Surface the actionable hint in the transcript so it is visible.
317
+ const devErr = err;
318
+ if (devErr.code === 'DEV_KEY_INVALID') {
319
+ const devErrMsg = {
320
+ id: `dev-key-error-${Date.now()}`,
321
+ role: 'assistant',
322
+ text: err.message,
323
+ content: err.message,
324
+ timestamp: new Date(),
325
+ metadata: { isInitialMessage: true },
326
+ };
327
+ setMessages((prev) => [...prev, devErrMsg]);
328
+ }
314
329
  }
315
330
  }, currentContext, contextDescription, undefined, // onToolSuggestions
316
331
  true);
@@ -8,10 +8,11 @@ import type { RuntimeConfig } from '../runtime-config-loader';
8
8
  /**
9
9
  * Augmentation hook for consumer-side projectId literal types.
10
10
  *
11
- * The Qafka CLI generates a `qafka.config.d.ts` file alongside
12
- * `qafka.config.js` whenever it writes the runtime config. That `.d.ts`
13
- * augments this interface with the consumer's project ids as keys —
14
- * giving `<Qafka projectId="..." />` autocomplete + typo validation.
11
+ * The Qafka CLI generates a `qafka.config.d.ts` file at the consumer
12
+ * project root whenever it writes the runtime config; the consumer's
13
+ * tsconfig file scan picks it up. That `.d.ts` augments this interface
14
+ * with the consumer's project ids as keys — giving
15
+ * `<Qafka projectId="..." />` autocomplete + typo validation.
15
16
  *
16
17
  * When the augmentation is absent (fresh project, `qafka init` not yet
17
18
  * run), `keyof QafkaProjectIds` is `never` and `ProjectIdOf` falls back
@@ -3,6 +3,16 @@
3
3
  */
4
4
  export declare const API_VERSION = "v1";
5
5
  export declare const DEFAULT_API_TIMEOUT = 30000;
6
+ /**
7
+ * Default Qafka backend base host (scheme + host only).
8
+ *
9
+ * The REST client appends `/api/v1` and the realtime voice client appends
10
+ * `/v1/realtime`, so this holds the bare base. Used as the fallback when neither
11
+ * an explicit `apiUrl` prop nor a runtime-config `apiUrl` is provided — keeps the
12
+ * REST and voice transports pointed at the same host instead of one silently
13
+ * defaulting while the other resolves to an empty (host-less) URL.
14
+ */
15
+ export declare const DEFAULT_API_BASE = "https://api.qafka.com";
6
16
  export declare const STORAGE_KEYS: {
7
17
  readonly CONFIG: "@qafka/config";
8
18
  readonly CONVERSATION_HISTORY: "@qafka/conversation_history";
package/dist/constants.js CHANGED
@@ -3,9 +3,19 @@
3
3
  * Shared constants for SDK
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.STORAGE_KEYS = exports.DEFAULT_API_TIMEOUT = exports.API_VERSION = void 0;
6
+ exports.STORAGE_KEYS = exports.DEFAULT_API_BASE = exports.DEFAULT_API_TIMEOUT = exports.API_VERSION = void 0;
7
7
  exports.API_VERSION = 'v1';
8
8
  exports.DEFAULT_API_TIMEOUT = 30000;
9
+ /**
10
+ * Default Qafka backend base host (scheme + host only).
11
+ *
12
+ * The REST client appends `/api/v1` and the realtime voice client appends
13
+ * `/v1/realtime`, so this holds the bare base. Used as the fallback when neither
14
+ * an explicit `apiUrl` prop nor a runtime-config `apiUrl` is provided — keeps the
15
+ * REST and voice transports pointed at the same host instead of one silently
16
+ * defaulting while the other resolves to an empty (host-less) URL.
17
+ */
18
+ exports.DEFAULT_API_BASE = 'https://api.qafka.com';
9
19
  exports.STORAGE_KEYS = {
10
20
  CONFIG: '@qafka/config',
11
21
  CONVERSATION_HISTORY: '@qafka/conversation_history',
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- /// <reference path="../qafka.config.d.ts" />
2
1
  /**
3
2
  * QafkaSDK for React Native
4
3
  * AI-powered conversational interface with navigation intelligence
@@ -601,12 +601,27 @@ class BackendService {
601
601
  });
602
602
  }
603
603
  else {
604
+ let serverMessage;
604
605
  try {
605
- const errorData = JSON.parse(xhr.responseText);
606
- onError(new Error(errorData.message || `HTTP Error: ${xhr.status}`));
606
+ serverMessage = JSON.parse(xhr.responseText)?.message;
607
607
  }
608
608
  catch {
609
- onError(new Error(`HTTP Error: ${xhr.status}`));
609
+ // non-JSON error body; fall through to a status-based message
610
+ }
611
+ // In dev, a 401 with a test key almost always means the rotated dev
612
+ // key is stale. Surface an actionable hint instead of a bare HTTP error.
613
+ if (__DEV__ && xhr.status === 401 && this.apiKey?.startsWith('qafka_test_')) {
614
+ const hint = 'Qafka dev key is invalid or was rotated. Run `qafka refresh`, then restart Metro with --clear.';
615
+ console.warn(`[Qafka] ${hint}`);
616
+ const devErr = new Error(hint);
617
+ devErr.code = 'DEV_KEY_INVALID';
618
+ devErr.status = xhr.status;
619
+ onError(devErr);
620
+ }
621
+ else {
622
+ const httpErr = new Error(serverMessage || `HTTP Error: ${xhr.status}`);
623
+ httpErr.status = xhr.status;
624
+ onError(httpErr);
610
625
  }
611
626
  }
612
627
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qafka/react-native",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "Drop-in AI assistant for React Native: chat, voice, and tool execution with screen-aware navigation.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "LICENSE"
24
24
  ],
25
25
  "scripts": {
26
- "build": "tsc && node -e \"const fs=require('fs'),p='dist/index.d.ts',c=fs.readFileSync(p,'utf-8');if(!c.startsWith('/// <reference'))fs.writeFileSync(p,'/// <reference path=\\\"../qafka.config.d.ts\\\" />\\n'+c,'utf-8');\"",
26
+ "build": "tsc",
27
27
  "dev": "tsc --watch",
28
28
  "clean": "rm -rf dist",
29
29
  "typecheck": "tsc --noEmit",