• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

agentic-dev-library / thumbcode / 21961208242

12 Feb 2026 07:29PM UTC coverage: 51.569% (-13.4%) from 64.947%
21961208242

push

github

web-flow
feat: complete Expo → Capacitor/Vite migration (#130)

## Summary

Full framework migration from Expo/React Native to Vite + Capacitor + React Router:

- **Framework**: Expo SDK 52 → Vite 6 + Capacitor 7
- **Routing**: expo-router → react-router-dom v7
- **Styling**: NativeWind → Tailwind CSS v4
- **Testing**: Jest → Vitest + @testing-library/react
- **Components**: React Native primitives → HTML/JSX (View→div, Text→span, etc.)
- **Build**: Metro bundler → Vite with HMR

### Key changes
- Removed all React Native and Expo dependencies
- Migrated 50+ components from RN to web HTML/JSX
- Rewrote 70 test files (844 tests passing)
- Replaced deploy-gh-pages.yml: Astro docs → Vite static site deployment
- Added android-release.yml: per-architecture debug APKs on GitHub releases
- Added ABI splits to build.gradle (armeabi-v7a, arm64-v8a, x86_64, universal)
- Fixed all biome lint, TypeScript, and E2E test issues
- Deleted dead `app/` directory (old Expo Router screens)

### CI Status
All critical checks passing: Lint & Type Check, Run Tests (844 passing), Build Web, Build Web + Capacitor Sync, E2E Tests (Web), Security Scan, CodeQL, Validate PR.

1329 of 2944 branches covered (45.14%)

Branch coverage included in aggregate %.

138 of 866 new or added lines in 82 files covered. (15.94%)

120 existing lines in 23 files now uncovered.

2188 of 3876 relevant lines covered (56.45%)

9.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.63
/packages/core/src/credentials/KeyStorage.ts
1
/**
2
 * Key Storage
3
 *
4
 * Handles secure credential storage and retrieval using Capacitor Secure Storage
5
 * with hardware-backed encryption. Includes biometric authentication support.
6
 */
7

8
import { BiometricAuth, type BiometryType } from '@aparajita/capacitor-biometric-auth';
9
import { SecureStoragePlugin } from 'capacitor-secure-storage-plugin';
10
import type {
11
  BiometricResult,
12
  CredentialType,
13
  RetrieveOptions,
14
  RetrieveResult,
15
  SecureCredential,
16
  StoreOptions,
17
  ValidationResult,
18
} from './types';
19
import { validate } from './validation';
20
import type { KeyValidator } from './KeyValidator';
21

22
// SecureStore key prefixes for different credential types
23
const SECURE_STORE_KEYS: Record<CredentialType, string> = {
7✔
24
  github: 'thumbcode_cred_github',
25
  anthropic: 'thumbcode_cred_anthropic',
26
  openai: 'thumbcode_cred_openai',
27
  mcp_server: 'thumbcode_cred_mcp',
28
  gitlab: 'thumbcode_cred_gitlab',
29
  bitbucket: 'thumbcode_cred_bitbucket',
30
  mcp_signing_secret: 'thumbcode_cred_mcp_signing_secret',
31
};
32

33
export class KeyStorage {
34
  constructor(private validator: KeyValidator) {}
24✔
35

36
  /**
37
   * Check if biometric authentication is available on the device
38
   */
39
  async isBiometricAvailable(): Promise<boolean> {
40
    const result = await BiometricAuth.checkBiometry();
4✔
41
    return result.isAvailable;
4✔
42
  }
43

44
  /**
45
   * Get the available biometric authentication types
46
   */
47
  async getBiometricTypes(): Promise<BiometryType[]> {
NEW
48
    const result = await BiometricAuth.checkBiometry();
×
49
    // Return array with the detected biometry type, or empty if none
NEW
50
    return result.isAvailable ? [result.biometryType] : [];
×
51
  }
52

53
  /**
54
   * Perform biometric authentication
55
   */
56
  async authenticateWithBiometrics(
57
    promptMessage = 'Authenticate to access your credentials'
7✔
58
  ): Promise<BiometricResult> {
59
    try {
7✔
60
      await BiometricAuth.authenticate({
7✔
61
        reason: promptMessage,
62
        cancelTitle: 'Cancel',
63
        allowDeviceCredential: true,
64
      });
65

66
      // If authenticate resolves without throwing, auth succeeded
67
      return { success: true };
3✔
68
    } catch (error) {
69
      return {
4✔
70
        success: false,
71
        error: error instanceof Error ? error.message : 'Authentication failed',
4!
72
      };
73
    }
74
  }
75

76
  /**
77
   * Store a credential securely
78
   */
79
  async store(
80
    type: CredentialType,
81
    secret: string,
82
    options: StoreOptions = {}
23✔
83
  ): Promise<ValidationResult> {
84
    const { requireBiometric = false, skipValidation = false } = options;
23✔
85

86
    if (!validate(type, secret)) {
23✔
87
      return { isValid: false, message: 'Invalid credential format' };
7✔
88
    }
89

90
    // Biometric check if required
91
    if (requireBiometric) {
16✔
92
      const biometricResult = await this.authenticateWithBiometrics();
4✔
93
      if (!biometricResult.success) {
4✔
94
        return { isValid: false, message: 'Biometric authentication failed' };
2✔
95
      }
96
    }
97

98
    // Validate the credential before storing (unless skipped)
99
    if (!skipValidation) {
14✔
100
      const validation = await this.validator.validateCredential(type, secret);
4✔
101
      if (!validation.isValid) {
4✔
102
        return validation;
1✔
103
      }
104
    }
105

106
    // Store the secret in Capacitor Secure Storage
107
    const key = SECURE_STORE_KEYS[type];
13✔
108
    try {
13✔
109
      const payload: SecureCredential = {
13✔
110
        secret,
111
        storedAt: new Date().toISOString(),
112
        type,
113
      };
114

115
      await SecureStoragePlugin.set({ key, value: JSON.stringify(payload) });
13✔
116

117
      return { isValid: true, message: 'Credential stored successfully' };
12✔
118
    } catch (error) {
119
      return {
1✔
120
        isValid: false,
121
        message: error instanceof Error ? error.message : 'Failed to store credential',
1!
122
      };
123
    }
124
  }
125

126
  /**
127
   * Retrieve a credential securely
128
   */
129
  async retrieve(type: CredentialType, options: RetrieveOptions = {}): Promise<RetrieveResult> {
14✔
130
    const { requireBiometric = false } = options;
14✔
131

132
    // Biometric check if required
133
    if (requireBiometric) {
14✔
134
      const biometricResult = await this.authenticateWithBiometrics();
3✔
135
      if (!biometricResult.success) {
3✔
136
        return { secret: null };
2✔
137
      }
138
    }
139

140
    try {
12✔
141
      const key = SECURE_STORE_KEYS[type];
12✔
142
      const result = await SecureStoragePlugin.get({ key });
12✔
143
      const payload = result.value;
8✔
144

145
      if (!payload) {
8!
UNCOV
146
        return { secret: null };
×
147
      }
148

149
      const data: SecureCredential = JSON.parse(payload);
8✔
150

151
      return {
8✔
152
        secret: data.secret,
153
        metadata: {
154
          storedAt: data.storedAt,
155
          type: data.type,
156
        },
157
      };
158
    } catch (error) {
159
      // SecureStoragePlugin.get throws when key is not found
160
      console.error('Failed to retrieve credential:', error);
4✔
161
      return { secret: null };
4✔
162
    }
163
  }
164

165
  /**
166
   * Delete a credential securely
167
   */
168
  async delete(type: CredentialType): Promise<boolean> {
169
    try {
3✔
170
      const key = SECURE_STORE_KEYS[type];
3✔
171
      await SecureStoragePlugin.remove({ key });
3✔
172
      return true;
2✔
173
    } catch (error) {
174
      console.error('Failed to delete credential:', error);
1✔
175
      return false;
1✔
176
    }
177
  }
178

179
  /**
180
   * Check if a credential exists
181
   */
182
  async exists(type: CredentialType): Promise<boolean> {
183
    try {
32✔
184
      const key = SECURE_STORE_KEYS[type];
32✔
185
      const result = await SecureStoragePlugin.get({ key });
32✔
186
      return result.value !== null && result.value !== undefined;
9✔
187
    } catch {
188
      // SecureStoragePlugin.get throws when key is not found
189
      return false;
23✔
190
    }
191
  }
192

193
  /**
194
   * Get all stored credential types
195
   */
196
  async getStoredCredentialTypes(): Promise<CredentialType[]> {
197
    const types = Object.keys(SECURE_STORE_KEYS) as CredentialType[];
4✔
198
    const results = await Promise.all(types.map((type) => this.exists(type)));
28✔
199
    return types.filter((_, index) => results[index]);
28✔
200
  }
201

202
  /**
203
   * Validate all stored credentials and return results
204
   */
205
  async validateAllStored(): Promise<Map<CredentialType, ValidationResult>> {
206
    const results = new Map<CredentialType, ValidationResult>();
1✔
207
    const storedTypes = await this.getStoredCredentialTypes();
1✔
208

209
    await Promise.all(
1✔
210
      storedTypes.map(async (type) => {
211
        const { secret } = await this.retrieve(type);
3✔
212
        if (secret) {
3!
213
          const result = await this.validator.validateCredential(type, secret);
3✔
214
          results.set(type, result);
3✔
215
        }
216
      })
217
    );
218

219
    return results;
1✔
220
  }
221
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc