@qafka/react-native 2.0.0 → 2.1.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/dist/QafkaSDK.js CHANGED
@@ -40,9 +40,13 @@ class QafkaSDK {
40
40
  if (this.status === 'ready') {
41
41
  const currentApiKey = this.config?.apiKey ?? null;
42
42
  const currentSubProjectId = this.subProjectId;
43
+ const currentProjectId = this.config?.projectId ?? null;
43
44
  const newApiKey = config.apiKey ?? null;
44
45
  const newSubProjectId = config.subProjectId ?? null;
45
- if (currentApiKey === newApiKey && currentSubProjectId === newSubProjectId) {
46
+ const newProjectId = config.projectId ?? null;
47
+ if (currentApiKey === newApiKey &&
48
+ currentSubProjectId === newSubProjectId &&
49
+ currentProjectId === newProjectId) {
46
50
  return;
47
51
  }
48
52
  await this.destroy();
@@ -78,6 +82,7 @@ class QafkaSDK {
78
82
  this.attestationManager = new AttestationManager_1.AttestationManager({
79
83
  apiUrl: attestApiUrl,
80
84
  apiKey: apiKey ?? null,
85
+ projectId: config.projectId ?? null,
81
86
  debug: config.debug,
82
87
  });
83
88
  try {
@@ -233,16 +233,16 @@ export interface QafkaHandle {
233
233
  export interface QafkaProps {
234
234
  style?: ViewStyle;
235
235
  /**
236
- * Project identifier when the runtime config registers more than one
237
- * project. Selects which project's development key the SDK uses in
238
- * development builds (loaded from `qafka.config.js`).
236
+ * Target Qafka project identifier. Required.
239
237
  *
240
- * Leave undefined to use the runtime config's default project. Pass a
241
- * project id that is unknown to the runtime config and the SDK throws on
242
- * mountsilent fallback would mask a typo.
238
+ * Identifies which project this SDK instance talks to. It is sent with
239
+ * device attestation in keyless (production) builds, so it cannot be
240
+ * omitteda missing value throws on mount.
243
241
  *
244
- * In production builds this prop is ignored by the key-resolution path,
245
- * which does not rely on a bundled key.
242
+ * In development builds it also selects which project's development key
243
+ * the SDK uses (loaded from `qafka.config.js`). Pass a project id that is
244
+ * unknown to the runtime config and the SDK throws on mount — silent
245
+ * fallback would mask a typo.
246
246
  *
247
247
  * When the Qafka CLI has been run (`qafka init` / `qafka project` /
248
248
  * `qafka sync`), this prop is narrowed to the registered project ids —
@@ -251,7 +251,7 @@ export interface QafkaProps {
251
251
  *
252
252
  * @example "proj_abc123"
253
253
  */
254
- projectId?: ProjectIdOf;
254
+ projectId: ProjectIdOf;
255
255
  /**
256
256
  * Backend API URL (OPTIONAL — advanced, leave unset for production).
257
257
  */
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { QafkaProps } from './Qafka.types';
3
- type QafkaProviderProps = Pick<QafkaProps, 'style' | 'apiUrl' | 'theme' | 'themeOverride' | 'customTheme' | 'enableStreaming' | 'isAuthenticated' | 'endUserId' | 'endUserData' | 'context' | 'contextDescription' | 'components' | 'showTimestamps' | 'placeholder' | 'maxMessageLength' | 'greetingMessage' | 'title' | 'showHeader' | 'onReady' | 'onMessageSent' | 'onResponseReceived' | 'onError' | 'onNavigationSuggest' | 'onNavigationAction' | 'onToolSuggested' | 'onActionResult' | 'onStepCompleted' | 'onClose' | 'onBack' | 'CloseComponent' | 'BackComponent' | 'navigationLabelFormat' | 'NavigationButtonComponent'> & {
3
+ type QafkaProviderProps = Pick<QafkaProps, 'style' | 'projectId' | 'apiUrl' | 'theme' | 'themeOverride' | 'customTheme' | 'enableStreaming' | 'isAuthenticated' | 'endUserId' | 'endUserData' | 'context' | 'contextDescription' | 'components' | 'showTimestamps' | 'placeholder' | 'maxMessageLength' | 'greetingMessage' | 'title' | 'showHeader' | 'onReady' | 'onMessageSent' | 'onResponseReceived' | 'onError' | 'onNavigationSuggest' | 'onNavigationAction' | 'onToolSuggested' | 'onActionResult' | 'onStepCompleted' | 'onClose' | 'onBack' | 'CloseComponent' | 'BackComponent' | 'navigationLabelFormat' | 'NavigationButtonComponent'> & {
4
4
  mode?: 'floating' | 'fullscreen' | 'inline';
5
5
  position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
6
6
  };
@@ -18,6 +18,12 @@ const useSDK = ({ apiUrl, subProjectId, projectId, locale, onReady, onError, })
18
18
  onReadyRef.current = onReady;
19
19
  onErrorRef.current = onError;
20
20
  const initializeSDK = async () => {
21
+ if (!projectId) {
22
+ const err = new Error('Qafka: `projectId` prop is required.');
23
+ setError(err.message);
24
+ onErrorRef.current?.(err);
25
+ return;
26
+ }
21
27
  let apiKey = null;
22
28
  let resolvedUrl = apiUrl;
23
29
  if (__DEV__) {
@@ -45,6 +51,7 @@ const useSDK = ({ apiUrl, subProjectId, projectId, locale, onReady, onError, })
45
51
  apiKey, // null in prod; developmentKey in dev
46
52
  apiUrl: resolvedUrl,
47
53
  subProjectId,
54
+ projectId,
48
55
  locale,
49
56
  onStatusChange: (status) => {
50
57
  if (status === 'ready') {
@@ -1,6 +1,7 @@
1
1
  interface AttestationConfig {
2
2
  apiUrl: string;
3
3
  apiKey: string | null;
4
+ projectId?: string | null;
4
5
  debug?: boolean;
5
6
  }
6
7
  export declare class AttestationManager {
@@ -9,6 +10,8 @@ export declare class AttestationManager {
9
10
  private keyId;
10
11
  constructor(config: AttestationConfig);
11
12
  private log;
13
+ private storageKeyIdName;
14
+ private androidAlias;
12
15
  initialize(): Promise<void>;
13
16
  private initializeIos;
14
17
  private performIosAttestation;
@@ -38,7 +38,6 @@ const react_native_1 = require("react-native");
38
38
  const storage_1 = require("./storage");
39
39
  const NativeAttestation = __importStar(require("../native/QafkaAttestation"));
40
40
  const QafkaAttestation_1 = require("../native/QafkaAttestation");
41
- const STORAGE_KEY_ID = '@qafka/attestation_key_id';
42
41
  class AttestationManager {
43
42
  config;
44
43
  sessionToken = null;
@@ -53,6 +52,20 @@ class AttestationManager {
53
52
  const verbose = this.config.debug || __DEV__;
54
53
  console.warn(verbose && verboseMessage ? verboseMessage : message);
55
54
  }
55
+ // Namespace the persisted key identifier per project so each project gets its
56
+ // own device-bound key instead of sharing one across projects.
57
+ storageKeyIdName() {
58
+ return this.config.projectId
59
+ ? `@qafka/attestation_key_id:${this.config.projectId}`
60
+ : '@qafka/attestation_key_id';
61
+ }
62
+ // Namespace the native key alias per project, sanitised to characters that
63
+ // are safe for a keystore alias.
64
+ androidAlias() {
65
+ return this.config.projectId
66
+ ? `qafka_attest_${String(this.config.projectId).replace(/[^a-zA-Z0-9_]/g, '_')}`
67
+ : 'qafka_attest';
68
+ }
56
69
  async initialize() {
57
70
  const moduleLinked = (0, QafkaAttestation_1.isAttestationAvailable)();
58
71
  // Diagnostic logs are intentionally NOT __DEV__-gated. Init is a once-per-
@@ -90,10 +103,10 @@ class AttestationManager {
90
103
  }
91
104
  }
92
105
  async initializeIos() {
93
- this.keyId = await storage_1.storage.getItem(STORAGE_KEY_ID);
106
+ this.keyId = await storage_1.storage.getItem(this.storageKeyIdName());
94
107
  if (!this.keyId) {
95
108
  this.keyId = await NativeAttestation.generateKey();
96
- await storage_1.storage.setItem(STORAGE_KEY_ID, this.keyId);
109
+ await storage_1.storage.setItem(this.storageKeyIdName(), this.keyId);
97
110
  }
98
111
  // Always perform a fresh attestation at session start. Lighter
99
112
  // assertions (`performIosAssertion`) are used later to refresh expired
@@ -122,9 +135,9 @@ class AttestationManager {
122
135
  throw err;
123
136
  }
124
137
  this.log(`[Qafka:init] initial attestation failed (${msg}), regenerating key and retrying once`);
125
- await storage_1.storage.removeItem(STORAGE_KEY_ID);
138
+ await storage_1.storage.removeItem(this.storageKeyIdName());
126
139
  this.keyId = await NativeAttestation.generateKey();
127
- await storage_1.storage.setItem(STORAGE_KEY_ID, this.keyId);
140
+ await storage_1.storage.setItem(this.storageKeyIdName(), this.keyId);
128
141
  await this.performIosAttestation();
129
142
  }
130
143
  }
@@ -162,8 +175,9 @@ class AttestationManager {
162
175
  }
163
176
  async performAndroidAttestation() {
164
177
  const challenge = await this.requestChallenge('android');
165
- await NativeAttestation.generateKeyPair('qafka_attest', challenge);
166
- const certChain = await NativeAttestation.getAttestationCertChain('qafka_attest');
178
+ const alias = this.androidAlias();
179
+ await NativeAttestation.generateKeyPair(alias, challenge);
180
+ const certChain = await NativeAttestation.getAttestationCertChain(alias);
167
181
  const response = await this.submitAttestation({
168
182
  platform: 'android',
169
183
  certChain,
@@ -249,7 +263,7 @@ class AttestationManager {
249
263
  response = await fetch(url, {
250
264
  method: 'POST',
251
265
  headers: this.getHeaders(),
252
- body: JSON.stringify({ platform }),
266
+ body: JSON.stringify({ platform, ...(this.config.projectId ? { projectId: this.config.projectId } : {}) }),
253
267
  });
254
268
  }
255
269
  catch (err) {
@@ -272,7 +286,10 @@ class AttestationManager {
272
286
  response = await fetch(url, {
273
287
  method: 'POST',
274
288
  headers: this.getHeaders(),
275
- body: JSON.stringify(body),
289
+ body: JSON.stringify({
290
+ ...body,
291
+ ...(this.config.projectId ? { projectId: this.config.projectId } : {}),
292
+ }),
276
293
  });
277
294
  }
278
295
  catch (err) {
@@ -290,7 +307,7 @@ class AttestationManager {
290
307
  async reset() {
291
308
  this.sessionToken = null;
292
309
  this.keyId = null;
293
- await storage_1.storage.removeItem(STORAGE_KEY_ID);
310
+ await storage_1.storage.removeItem(this.storageKeyIdName());
294
311
  }
295
312
  }
296
313
  exports.AttestationManager = AttestationManager;
@@ -12,6 +12,8 @@ export interface SDKConfig {
12
12
  apiKey?: string | null;
13
13
  /** Sub-project identifier (same apiKey, different sub-project). */
14
14
  subProjectId?: string;
15
+ /** Target Qafka project id. Required in keyless (production) builds. */
16
+ projectId?: string;
15
17
  /** Advanced — leave unset for production. */
16
18
  apiUrl?: string;
17
19
  /** React Navigation ref, used when the SDK navigates on your behalf. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qafka/react-native",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
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",