@vailix/mask 0.2.1 → 0.2.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
@@ -1,5 +1,11 @@
1
1
  # @vailix/mask
2
2
 
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 4b6a9d5: fix: ensure database is closed before deletion on key mismatch and add missing internal types
8
+
3
9
  ## 0.2.1
4
10
 
5
11
  ### Patch Changes
package/dist/index.js CHANGED
@@ -775,35 +775,54 @@ var import_expo_sqlite2 = require("expo-sqlite");
775
775
  var import_drizzle_orm2 = require("drizzle-orm");
776
776
  var DB_NAME = "vailix.db";
777
777
  async function initializeDatabase(masterKey) {
778
+ const result = await tryOpenEncryptedDatabase(masterKey);
779
+ if (result.success) {
780
+ return result.db;
781
+ }
782
+ console.warn("Database key mismatch, recreating fresh database");
783
+ if (result.expo) {
784
+ result.expo.closeSync();
785
+ }
786
+ (0, import_expo_sqlite2.deleteDatabaseSync)(DB_NAME);
787
+ const retryResult = await tryOpenEncryptedDatabase(masterKey);
788
+ if (!retryResult.success) {
789
+ if (retryResult.expo) {
790
+ retryResult.expo.closeSync();
791
+ }
792
+ throw retryResult.error;
793
+ }
794
+ return retryResult.db;
795
+ }
796
+ async function tryOpenEncryptedDatabase(masterKey) {
797
+ let expo = null;
778
798
  try {
779
- return await openEncryptedDatabase(masterKey);
799
+ expo = (0, import_expo_sqlite2.openDatabaseSync)(DB_NAME);
800
+ const db = (0, import_expo_sqlite.drizzle)(expo);
801
+ if (!/^[0-9a-f]+$/i.test(masterKey)) {
802
+ throw new Error("Invalid master key format");
803
+ }
804
+ await db.run(import_drizzle_orm2.sql.raw(`PRAGMA key = '${masterKey}'`));
805
+ await db.run(import_drizzle_orm2.sql`SELECT 1`);
806
+ await db.run(import_drizzle_orm2.sql`
807
+ CREATE TABLE IF NOT EXISTS scanned_events (
808
+ id TEXT PRIMARY KEY,
809
+ rpi TEXT NOT NULL,
810
+ metadata_key TEXT NOT NULL,
811
+ timestamp INTEGER NOT NULL
812
+ )
813
+ `);
814
+ await db.run(import_drizzle_orm2.sql`
815
+ CREATE INDEX IF NOT EXISTS rpi_idx ON scanned_events(rpi)
816
+ `);
817
+ return { success: true, db, expo };
780
818
  } catch (error) {
781
- console.warn("Database key mismatch, recreating fresh database");
782
- (0, import_expo_sqlite2.deleteDatabaseSync)(DB_NAME);
783
- return await openEncryptedDatabase(masterKey);
819
+ return {
820
+ success: false,
821
+ error: error instanceof Error ? error : new Error(String(error)),
822
+ expo
823
+ };
784
824
  }
785
825
  }
786
- async function openEncryptedDatabase(masterKey) {
787
- const expo = (0, import_expo_sqlite2.openDatabaseSync)(DB_NAME);
788
- const db = (0, import_expo_sqlite.drizzle)(expo);
789
- if (!/^[0-9a-f]+$/i.test(masterKey)) {
790
- throw new Error("Invalid master key format");
791
- }
792
- await db.run(import_drizzle_orm2.sql.raw(`PRAGMA key = '${masterKey}'`));
793
- await db.run(import_drizzle_orm2.sql`SELECT 1`);
794
- await db.run(import_drizzle_orm2.sql`
795
- CREATE TABLE IF NOT EXISTS scanned_events (
796
- id TEXT PRIMARY KEY,
797
- rpi TEXT NOT NULL,
798
- metadata_key TEXT NOT NULL,
799
- timestamp INTEGER NOT NULL
800
- )
801
- `);
802
- await db.run(import_drizzle_orm2.sql`
803
- CREATE INDEX IF NOT EXISTS rpi_idx ON scanned_events(rpi)
804
- `);
805
- return db;
806
- }
807
826
 
808
827
  // src/index.ts
809
828
  var VailixSDK = class _VailixSDK {
package/dist/index.mjs CHANGED
@@ -739,35 +739,54 @@ import { openDatabaseSync, deleteDatabaseSync } from "expo-sqlite";
739
739
  import { sql } from "drizzle-orm";
740
740
  var DB_NAME = "vailix.db";
741
741
  async function initializeDatabase(masterKey) {
742
+ const result = await tryOpenEncryptedDatabase(masterKey);
743
+ if (result.success) {
744
+ return result.db;
745
+ }
746
+ console.warn("Database key mismatch, recreating fresh database");
747
+ if (result.expo) {
748
+ result.expo.closeSync();
749
+ }
750
+ deleteDatabaseSync(DB_NAME);
751
+ const retryResult = await tryOpenEncryptedDatabase(masterKey);
752
+ if (!retryResult.success) {
753
+ if (retryResult.expo) {
754
+ retryResult.expo.closeSync();
755
+ }
756
+ throw retryResult.error;
757
+ }
758
+ return retryResult.db;
759
+ }
760
+ async function tryOpenEncryptedDatabase(masterKey) {
761
+ let expo = null;
742
762
  try {
743
- return await openEncryptedDatabase(masterKey);
763
+ expo = openDatabaseSync(DB_NAME);
764
+ const db = drizzle(expo);
765
+ if (!/^[0-9a-f]+$/i.test(masterKey)) {
766
+ throw new Error("Invalid master key format");
767
+ }
768
+ await db.run(sql.raw(`PRAGMA key = '${masterKey}'`));
769
+ await db.run(sql`SELECT 1`);
770
+ await db.run(sql`
771
+ CREATE TABLE IF NOT EXISTS scanned_events (
772
+ id TEXT PRIMARY KEY,
773
+ rpi TEXT NOT NULL,
774
+ metadata_key TEXT NOT NULL,
775
+ timestamp INTEGER NOT NULL
776
+ )
777
+ `);
778
+ await db.run(sql`
779
+ CREATE INDEX IF NOT EXISTS rpi_idx ON scanned_events(rpi)
780
+ `);
781
+ return { success: true, db, expo };
744
782
  } catch (error) {
745
- console.warn("Database key mismatch, recreating fresh database");
746
- deleteDatabaseSync(DB_NAME);
747
- return await openEncryptedDatabase(masterKey);
783
+ return {
784
+ success: false,
785
+ error: error instanceof Error ? error : new Error(String(error)),
786
+ expo
787
+ };
748
788
  }
749
789
  }
750
- async function openEncryptedDatabase(masterKey) {
751
- const expo = openDatabaseSync(DB_NAME);
752
- const db = drizzle(expo);
753
- if (!/^[0-9a-f]+$/i.test(masterKey)) {
754
- throw new Error("Invalid master key format");
755
- }
756
- await db.run(sql.raw(`PRAGMA key = '${masterKey}'`));
757
- await db.run(sql`SELECT 1`);
758
- await db.run(sql`
759
- CREATE TABLE IF NOT EXISTS scanned_events (
760
- id TEXT PRIMARY KEY,
761
- rpi TEXT NOT NULL,
762
- metadata_key TEXT NOT NULL,
763
- timestamp INTEGER NOT NULL
764
- )
765
- `);
766
- await db.run(sql`
767
- CREATE INDEX IF NOT EXISTS rpi_idx ON scanned_events(rpi)
768
- `);
769
- return db;
770
- }
771
790
 
772
791
  // src/index.ts
773
792
  var VailixSDK = class _VailixSDK {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vailix/mask",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Privacy-preserving proximity tracing SDK for React Native & Expo",
5
5
  "author": "Gil Eyni",
6
6
  "keywords": [
package/src/ble.ts CHANGED
@@ -21,6 +21,50 @@ const DEFAULT_PROXIMITY_THRESHOLD = -70;
21
21
 
22
22
  import { generateDisplayName } from './utils';
23
23
 
24
+ // ============================================================================
25
+ // Internal Types (not exported - implementation details)
26
+ // ============================================================================
27
+
28
+ /**
29
+ * Internal representation of a nearby user with additional fields for BLE management.
30
+ * The public NearbyUser type hides these implementation details.
31
+ */
32
+ interface InternalNearbyUser {
33
+ id: string;
34
+ displayName: string;
35
+ rssi: number;
36
+ discoveredAt: number;
37
+ paired: boolean;
38
+ hasIncomingRequest: boolean;
39
+ pairedAt?: number;
40
+ /** First 8 bytes (16 hex chars) of RPI from advertisement */
41
+ rpiPrefix: string;
42
+ /** Full RPI (received via GATT exchange) */
43
+ fullRpi?: string;
44
+ /** Metadata key (received via GATT exchange) */
45
+ metadataKey?: string;
46
+ }
47
+
48
+ /**
49
+ * Pending incoming pair request (explicit consent mode).
50
+ * Holds data in memory until user accepts.
51
+ */
52
+ interface PendingPairRequest {
53
+ fullRpi: string;
54
+ metadataKey: string;
55
+ receivedAt: number;
56
+ }
57
+
58
+ /**
59
+ * Configuration options for BleService constructor.
60
+ */
61
+ interface BleServiceConfig {
62
+ discoveryTimeoutMs?: number;
63
+ proximityThreshold?: number;
64
+ autoAccept?: boolean;
65
+ serviceUUID?: string;
66
+ }
67
+
24
68
  /**
25
69
  * Extract RPI prefix from advertisement manufacturer data or service data.
26
70
  */
package/src/db.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { drizzle } from 'drizzle-orm/expo-sqlite';
2
- import { openDatabaseSync, deleteDatabaseSync } from 'expo-sqlite';
2
+ import { openDatabaseSync, deleteDatabaseSync, type SQLiteDatabase } from 'expo-sqlite';
3
3
  import { sql } from 'drizzle-orm';
4
4
  import type { VailixDB } from './types';
5
5
 
@@ -13,45 +13,83 @@ const DB_NAME = 'vailix.db';
13
13
  * @param masterKey The user's master key, used to derive encryption password
14
14
  */
15
15
  export async function initializeDatabase(masterKey: string): Promise<VailixDB> {
16
- try {
17
- return await openEncryptedDatabase(masterKey);
18
- } catch (error) {
19
- // Key mismatch: "file is not a database" or similar SQLCipher error
20
- // This happens when DB was restored from backup but key is different
21
- console.warn('Database key mismatch, recreating fresh database');
22
- deleteDatabaseSync(DB_NAME);
23
- return await openEncryptedDatabase(masterKey);
16
+ const result = await tryOpenEncryptedDatabase(masterKey);
17
+
18
+ if (result.success) {
19
+ return result.db;
20
+ }
21
+
22
+ // Key mismatch: "file is not a database" or similar SQLCipher error
23
+ // This happens when DB was restored from backup but key is different
24
+ console.warn('Database key mismatch, recreating fresh database');
25
+
26
+ // Close the connection before deletion (required by SQLite)
27
+ if (result.expo) {
28
+ result.expo.closeSync();
29
+ }
30
+
31
+ deleteDatabaseSync(DB_NAME);
32
+
33
+ // Retry - if this fails, let it throw (unrecoverable error)
34
+ const retryResult = await tryOpenEncryptedDatabase(masterKey);
35
+ if (!retryResult.success) {
36
+ if (retryResult.expo) {
37
+ retryResult.expo.closeSync();
38
+ }
39
+ throw retryResult.error;
24
40
  }
41
+
42
+ return retryResult.db;
25
43
  }
26
44
 
27
- async function openEncryptedDatabase(masterKey: string): Promise<VailixDB> {
28
- const expo = openDatabaseSync(DB_NAME);
29
- const db = drizzle(expo);
45
+ type OpenResult =
46
+ | { success: true; db: VailixDB; expo: SQLiteDatabase }
47
+ | { success: false; error: Error; expo: SQLiteDatabase | null };
30
48
 
31
- // Enable SQLCipher encryption using master key as password
32
- // This encrypts the entire database at rest (AES-256)
33
- // Validate key is hex to prevent SQL injection
34
- if (!/^[0-9a-f]+$/i.test(masterKey)) {
35
- throw new Error('Invalid master key format');
49
+ /**
50
+ * Attempt to open and configure the encrypted database.
51
+ * Returns a result object that includes the raw expo connection for cleanup.
52
+ */
53
+ async function tryOpenEncryptedDatabase(masterKey: string): Promise<OpenResult> {
54
+ let expo: SQLiteDatabase | null = null;
55
+
56
+ try {
57
+ expo = openDatabaseSync(DB_NAME);
58
+ const db = drizzle(expo);
59
+
60
+ // Validate key is hex to prevent SQL injection
61
+ if (!/^[0-9a-f]+$/i.test(masterKey)) {
62
+ throw new Error('Invalid master key format');
63
+ }
64
+
65
+ // Enable SQLCipher encryption using master key as password
66
+ // This encrypts the entire database at rest (AES-256)
67
+ await db.run(sql.raw(`PRAGMA key = '${masterKey}'`));
68
+
69
+ // Verify key works by attempting a read operation
70
+ // SQLCipher will throw if key is wrong
71
+ await db.run(sql`SELECT 1`);
72
+
73
+ // Create schema
74
+ await db.run(sql`
75
+ CREATE TABLE IF NOT EXISTS scanned_events (
76
+ id TEXT PRIMARY KEY,
77
+ rpi TEXT NOT NULL,
78
+ metadata_key TEXT NOT NULL,
79
+ timestamp INTEGER NOT NULL
80
+ )
81
+ `);
82
+
83
+ await db.run(sql`
84
+ CREATE INDEX IF NOT EXISTS rpi_idx ON scanned_events(rpi)
85
+ `);
86
+
87
+ return { success: true, db, expo };
88
+ } catch (error) {
89
+ return {
90
+ success: false,
91
+ error: error instanceof Error ? error : new Error(String(error)),
92
+ expo
93
+ };
36
94
  }
37
- await db.run(sql.raw(`PRAGMA key = '${masterKey}'`));
38
-
39
- // Verify key works by attempting a read operation
40
- // SQLCipher will throw if key is wrong
41
- await db.run(sql`SELECT 1`);
42
-
43
- await db.run(sql`
44
- CREATE TABLE IF NOT EXISTS scanned_events (
45
- id TEXT PRIMARY KEY,
46
- rpi TEXT NOT NULL,
47
- metadata_key TEXT NOT NULL,
48
- timestamp INTEGER NOT NULL
49
- )
50
- `);
51
-
52
- await db.run(sql`
53
- CREATE INDEX IF NOT EXISTS rpi_idx ON scanned_events(rpi)
54
- `);
55
-
56
- return db;
57
95
  }