Skip to content

Commit 91916b1

Browse files
committed
Refactor entry point and enhance native fallback logic
Changed package.json to use src/index.ts as the source entry, removed src/index.tsx, and refactored src/index.ts to add robust fallback logic for native module methods. This improves reliability when native modules are unavailable and centralizes fallback implementations for storage operations.
1 parent ea876dc commit 91916b1

File tree

3 files changed

+191
-72
lines changed

3 files changed

+191
-72
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"types": "./lib/typescript/src/index.d.ts",
77
"exports": {
88
".": {
9-
"source": "./src/index.tsx",
9+
"source": "./src/index.ts",
1010
"types": "./lib/typescript/src/index.d.ts",
1111
"default": "./lib/module/index.js"
1212
},

src/index.ts

Lines changed: 190 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,125 @@ export {
9999
*/
100100
const mockStorage: Record<string, string> = {};
101101

102+
const FALLBACK_CAPABILITIES: DeviceCapabilities = {
103+
secureEnclave: false,
104+
strongBox: false,
105+
biometry: false,
106+
deviceCredential: false,
107+
iCloudSync: false,
108+
};
109+
110+
type NativeSetOptions = StorageOptions & { service: string };
111+
type NativeGetOptions = RetrievalOptions & { service: string };
112+
type NativeScopedOptions = { service: string };
113+
114+
type NativeSensitiveInfoModule = {
115+
setItem?: (
116+
key: string,
117+
value: string,
118+
options: NativeSetOptions
119+
) => Promise<OperationResult>;
120+
getItem?: (
121+
key: string,
122+
options: NativeGetOptions
123+
) => Promise<{ value?: string | null } | null>;
124+
hasItem?: (key: string, options: NativeScopedOptions) => Promise<boolean>;
125+
deleteItem?: (key: string, options: NativeScopedOptions) => Promise<void>;
126+
getAllItems?: (options: NativeScopedOptions) => Promise<string[]>;
127+
clearService?: (options: NativeScopedOptions) => Promise<void>;
128+
getSupportedSecurityLevels?: () => Promise<DeviceCapabilities>;
129+
};
130+
131+
type NativeMethod = keyof NativeSensitiveInfoModule;
132+
133+
interface NativeInvocationResult<T> {
134+
readonly didInvoke: boolean;
135+
readonly result?: T;
136+
}
137+
138+
interface KeychainScopedOptions {
139+
readonly keychainService?: string;
140+
}
141+
142+
function resolveService(options?: KeychainScopedOptions): string {
143+
return options?.keychainService || DEFAULT_KEYCHAIN_SERVICE;
144+
}
145+
146+
async function invokeNative<T>(
147+
nativeModule: NativeSensitiveInfoModule | null,
148+
method: NativeMethod,
149+
args: unknown[]
150+
): Promise<NativeInvocationResult<T>> {
151+
const candidate = nativeModule?.[method];
152+
153+
if (typeof candidate !== 'function') {
154+
return { didInvoke: false };
155+
}
156+
157+
const result = await (candidate as (...innerArgs: unknown[]) => Promise<T>)(
158+
...args
159+
);
160+
161+
return { didInvoke: true, result };
162+
}
163+
164+
function fallbackSetItem(
165+
service: string,
166+
key: string,
167+
value: string,
168+
accessControl?: StorageOptions['accessControl']
169+
): OperationResult {
170+
const storageKey = createStorageKey(service, key);
171+
mockStorage[storageKey] = value;
172+
173+
return {
174+
metadata: {
175+
timestamp: Math.floor(Date.now() / 1000),
176+
securityLevel: 'software',
177+
accessControl: accessControl ?? DEFAULT_ACCESS_CONTROL,
178+
backend: 'androidKeystore',
179+
},
180+
};
181+
}
182+
183+
function fallbackGetItem(service: string, key: string): string | null {
184+
const storageKey = createStorageKey(service, key);
185+
return mockStorage[storageKey] ?? null;
186+
}
187+
188+
function fallbackHasItem(service: string, key: string): boolean {
189+
const storageKey = createStorageKey(service, key);
190+
return storageKey in mockStorage;
191+
}
192+
193+
function fallbackDeleteItem(service: string, key: string): void {
194+
const storageKey = createStorageKey(service, key);
195+
delete mockStorage[storageKey];
196+
}
197+
198+
function fallbackGetAllItems(service: string): string[] {
199+
const prefix = createStorageKey(service, '');
200+
return Object.keys(mockStorage)
201+
.filter((key) => key.startsWith(prefix))
202+
.map((key) => key.substring(prefix.length));
203+
}
204+
205+
function fallbackClearService(service: string): void {
206+
const prefix = createStorageKey(service, '');
207+
Object.keys(mockStorage).forEach((key) => {
208+
if (key.startsWith(prefix)) {
209+
delete mockStorage[key];
210+
}
211+
});
212+
}
213+
102214
/**
103215
* Get native module instance
104216
* @private
105217
*/
106-
function getNativeModule() {
218+
function getNativeModule(): NativeSensitiveInfoModule | null {
107219
try {
108-
return NativeModules.SensitiveInfo;
220+
return NativeModules.SensitiveInfo as NativeSensitiveInfoModule;
109221
} catch {
110222
return null;
111223
}
@@ -119,7 +231,7 @@ function createStorageKey(service: string, key: string): string {
119231
return `${service}:${key}`;
120232
}
121233

122-
/**
234+
/*
123235
* Securely stores a value in encrypted storage with optional biometric protection
124236
*
125237
* The storage operation will:
@@ -177,30 +289,21 @@ export async function setItem(
177289
throw new Error('Key and value are required');
178290
}
179291

180-
const service = options?.keychainService || DEFAULT_KEYCHAIN_SERVICE;
292+
const service = resolveService(options);
181293
const nativeModule = getNativeModule();
182294

183295
try {
184-
if (nativeModule?.setItem) {
185-
// Use native implementation when available
186-
return await nativeModule.setItem(key, value, {
187-
service,
188-
...options,
189-
});
296+
const { didInvoke, result } = await invokeNative<OperationResult>(
297+
nativeModule,
298+
'setItem',
299+
[key, value, { service, ...options }]
300+
);
301+
302+
if (didInvoke) {
303+
return result as OperationResult;
190304
}
191305

192-
// Development/example fallback
193-
const storageKey = createStorageKey(service, key);
194-
mockStorage[storageKey] = value;
195-
196-
return {
197-
metadata: {
198-
timestamp: Math.floor(Date.now() / 1000),
199-
securityLevel: 'software',
200-
accessControl: options?.accessControl || DEFAULT_ACCESS_CONTROL,
201-
backend: 'androidKeystore',
202-
},
203-
};
306+
return fallbackSetItem(service, key, value, options?.accessControl);
204307
} catch (error: any) {
205308
const errorCode = error?.code || ErrorCode.ENCRYPTION_FAILED;
206309
const message = `Failed to store "${key}": ${error?.message}`;
@@ -297,23 +400,25 @@ export async function getItem(
297400
throw new Error('Key is required');
298401
}
299402

300-
const service = options?.keychainService || DEFAULT_KEYCHAIN_SERVICE;
403+
const service = resolveService(options);
301404
const nativeModule = getNativeModule();
302405

303406
try {
304-
if (nativeModule?.getItem) {
305-
// Use native implementation when available
306-
const result = await nativeModule.getItem(key, {
407+
const { didInvoke, result } = await invokeNative<{
408+
value?: string | null;
409+
} | null>(nativeModule, 'getItem', [
410+
key,
411+
{
307412
service,
308413
...options,
309-
});
310-
// Extract the value field from the native result object
414+
},
415+
]);
416+
417+
if (didInvoke) {
311418
return result?.value ?? null;
312419
}
313420

314-
// Development/example fallback
315-
const storageKey = createStorageKey(service, key);
316-
return mockStorage[storageKey] ?? null;
421+
return fallbackGetItem(service, key);
317422
} catch (error: any) {
318423
const errorCode = error?.code || ErrorCode.DECRYPTION_FAILED;
319424
const message = `Failed to retrieve "${key}": ${error?.message}`;
@@ -364,17 +469,21 @@ export async function hasItem(
364469
throw new Error('Key is required');
365470
}
366471

367-
const service = options?.keychainService || DEFAULT_KEYCHAIN_SERVICE;
472+
const service = resolveService(options);
368473
const nativeModule = getNativeModule();
369474

370475
try {
371-
if (nativeModule?.hasItem) {
372-
return await nativeModule.hasItem(key, { service });
476+
const { didInvoke, result } = await invokeNative<boolean>(
477+
nativeModule,
478+
'hasItem',
479+
[key, { service }]
480+
);
481+
482+
if (didInvoke) {
483+
return Boolean(result);
373484
}
374485

375-
// Development/example fallback
376-
const storageKey = createStorageKey(service, key);
377-
return storageKey in mockStorage;
486+
return fallbackHasItem(service, key);
378487
} catch (error: any) {
379488
const message = `Failed to check "${key}": ${error?.message}`;
380489
throw Object.assign(new Error(message), {
@@ -424,18 +533,20 @@ export async function deleteItem(
424533
throw new Error('Key is required');
425534
}
426535

427-
const service = options?.keychainService || DEFAULT_KEYCHAIN_SERVICE;
536+
const service = resolveService(options);
428537
const nativeModule = getNativeModule();
429538

430539
try {
431-
if (nativeModule?.deleteItem) {
432-
await nativeModule.deleteItem(key, { service });
540+
const { didInvoke } = await invokeNative<void>(nativeModule, 'deleteItem', [
541+
key,
542+
{ service },
543+
]);
544+
545+
if (didInvoke) {
433546
return;
434547
}
435548

436-
// Development/example fallback
437-
const storageKey = createStorageKey(service, key);
438-
delete mockStorage[storageKey];
549+
fallbackDeleteItem(service, key);
439550
} catch (error: any) {
440551
const message = `Failed to delete "${key}": ${error?.message}`;
441552
throw Object.assign(new Error(message), {
@@ -479,19 +590,21 @@ export async function deleteItem(
479590
export async function getAllItems(
480591
options?: Pick<StorageOptions, 'keychainService'>
481592
): Promise<string[]> {
482-
const service = options?.keychainService || DEFAULT_KEYCHAIN_SERVICE;
593+
const service = resolveService(options);
483594
const nativeModule = getNativeModule();
484595

485596
try {
486-
if (nativeModule?.getAllItems) {
487-
return await nativeModule.getAllItems({ service });
597+
const { didInvoke, result } = await invokeNative<string[]>(
598+
nativeModule,
599+
'getAllItems',
600+
[{ service }]
601+
);
602+
603+
if (didInvoke) {
604+
return result ?? [];
488605
}
489606

490-
// Development/example fallback
491-
const prefix = createStorageKey(service, '');
492-
return Object.keys(mockStorage)
493-
.filter((k) => k.startsWith(prefix))
494-
.map((k) => k.substring(prefix.length));
607+
return fallbackGetAllItems(service);
495608
} catch (error: any) {
496609
throw Object.assign(new Error(`Failed to list items: ${error?.message}`), {
497610
code: ErrorCode.KEYSTORE_UNAVAILABLE,
@@ -545,22 +658,21 @@ export async function getAllItems(
545658
export async function clearService(
546659
options?: Pick<StorageOptions, 'keychainService'>
547660
): Promise<void> {
548-
const service = options?.keychainService || DEFAULT_KEYCHAIN_SERVICE;
661+
const service = resolveService(options);
549662
const nativeModule = getNativeModule();
550663

551664
try {
552-
if (nativeModule?.clearService) {
553-
await nativeModule.clearService({ service });
665+
const { didInvoke } = await invokeNative<void>(
666+
nativeModule,
667+
'clearService',
668+
[{ service }]
669+
);
670+
671+
if (didInvoke) {
554672
return;
555673
}
556674

557-
// Development/example fallback
558-
const prefix = createStorageKey(service, '');
559-
Object.keys(mockStorage).forEach((key) => {
560-
if (key.startsWith(prefix)) {
561-
delete mockStorage[key];
562-
}
563-
});
675+
fallbackClearService(service);
564676
} catch (error: any) {
565677
const message = `Failed to clear service: ${error?.message}`;
566678
throw Object.assign(new Error(message), {
@@ -594,13 +706,23 @@ export async function clearService(
594706
* ```
595707
*/
596708
export async function getSupportedSecurityLevels(): Promise<DeviceCapabilities> {
597-
return {
598-
secureEnclave: true,
599-
strongBox: true,
600-
biometry: false,
601-
deviceCredential: true,
602-
iCloudSync: false,
603-
};
709+
const nativeModule = getNativeModule();
710+
711+
try {
712+
const { didInvoke, result } = await invokeNative<DeviceCapabilities>(
713+
nativeModule,
714+
'getSupportedSecurityLevels',
715+
[]
716+
);
717+
718+
if (didInvoke && result) {
719+
return result;
720+
}
721+
722+
return FALLBACK_CAPABILITIES;
723+
} catch {
724+
return FALLBACK_CAPABILITIES;
725+
}
604726
}
605727

606728
/**

src/index.tsx

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)