@tetherto/wdk-react-native-secure-storage 1.0.0-beta.1 → 1.0.0-beta.3
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/constants.d.ts +11 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +13 -0
- package/dist/errors.d.ts +58 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +99 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/keychainHelpers.d.ts +15 -0
- package/dist/keychainHelpers.d.ts.map +1 -0
- package/dist/keychainHelpers.js +56 -0
- package/dist/logger.d.ts +75 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +90 -0
- package/dist/secureStorage.d.ts +104 -0
- package/dist/secureStorage.d.ts.map +1 -0
- package/dist/secureStorage.js +558 -0
- package/dist/utils.d.ts +59 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +170 -0
- package/dist/validation.d.ts +48 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +130 -0
- package/package.json +8 -3
- package/src/__tests__/__mocks__/expo-local-authentication.ts +1 -1
- package/src/__tests__/__mocks__/react-native-keychain.ts +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeout constants for keychain operations
|
|
3
|
+
*/
|
|
4
|
+
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
5
|
+
export declare const MIN_TIMEOUT_MS = 1000;
|
|
6
|
+
export declare const MAX_TIMEOUT_MS: number;
|
|
7
|
+
/**
|
|
8
|
+
* Cache TTL for device authentication availability (5 minutes)
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEVICE_AUTH_CACHE_TTL_MS: number;
|
|
11
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,kBAAkB,QAAQ,CAAA;AACvC,eAAO,MAAM,cAAc,OAAO,CAAA;AAClC,eAAO,MAAM,cAAc,QAAgB,CAAA;AAE3C;;GAEG;AACH,eAAO,MAAM,wBAAwB,QAAgB,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEVICE_AUTH_CACHE_TTL_MS = exports.MAX_TIMEOUT_MS = exports.MIN_TIMEOUT_MS = exports.DEFAULT_TIMEOUT_MS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Timeout constants for keychain operations
|
|
6
|
+
*/
|
|
7
|
+
exports.DEFAULT_TIMEOUT_MS = 30000;
|
|
8
|
+
exports.MIN_TIMEOUT_MS = 1000;
|
|
9
|
+
exports.MAX_TIMEOUT_MS = 5 * 60 * 1000;
|
|
10
|
+
/**
|
|
11
|
+
* Cache TTL for device authentication availability (5 minutes)
|
|
12
|
+
*/
|
|
13
|
+
exports.DEVICE_AUTH_CACHE_TTL_MS = 5 * 60 * 1000;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all secure storage errors
|
|
3
|
+
*/
|
|
4
|
+
export declare class SecureStorageError extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
readonly cause?: Error | undefined;
|
|
7
|
+
constructor(message: string, code: string, cause?: Error | undefined);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Error thrown when keychain operations fail
|
|
11
|
+
*
|
|
12
|
+
* Note: This is a base error class for completeness. In practice, use
|
|
13
|
+
* KeychainWriteError or KeychainReadError for more specific error handling.
|
|
14
|
+
*/
|
|
15
|
+
export declare class KeychainError extends SecureStorageError {
|
|
16
|
+
constructor(message: string, cause?: Error);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Error thrown when keychain write operations fail
|
|
20
|
+
*/
|
|
21
|
+
export declare class KeychainWriteError extends SecureStorageError {
|
|
22
|
+
constructor(message: string, cause?: Error);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Error thrown when keychain read operations fail
|
|
26
|
+
*/
|
|
27
|
+
export declare class KeychainReadError extends SecureStorageError {
|
|
28
|
+
constructor(message: string, cause?: Error);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Error thrown when authentication fails or is required but unavailable
|
|
32
|
+
*/
|
|
33
|
+
export declare class AuthenticationError extends SecureStorageError {
|
|
34
|
+
constructor(message: string, cause?: Error);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Error thrown when input validation fails
|
|
38
|
+
*/
|
|
39
|
+
export declare class ValidationError extends SecureStorageError {
|
|
40
|
+
constructor(message: string);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Error thrown when an operation times out
|
|
44
|
+
*/
|
|
45
|
+
export declare class TimeoutError extends SecureStorageError {
|
|
46
|
+
constructor(message: string);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Error thrown when device security (PIN/pattern/password/biometrics) is not enabled
|
|
50
|
+
*
|
|
51
|
+
* This error can be used by apps that want to enforce device security as a prerequisite.
|
|
52
|
+
* Whether to require device security is up to the app - the library will still function
|
|
53
|
+
* on devices without authentication configured (data remains encrypted at rest).
|
|
54
|
+
*/
|
|
55
|
+
export declare class DeviceSecurityNotEnabledError extends SecureStorageError {
|
|
56
|
+
constructor(message?: string, cause?: Error);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;aAGzB,IAAI,EAAE,MAAM;aACZ,KAAK,CAAC,EAAE,KAAK;gBAF7B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,KAAK,YAAA;CAYhC;AAED;;;;;GAKG;AACH,qBAAa,aAAc,SAAQ,kBAAkB;gBACvC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;CAI3C;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,kBAAkB;gBAC5C,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;CAI3C;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,kBAAkB;gBAC3C,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;CAI3C;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,kBAAkB;gBAC7C,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;CAI3C;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,kBAAkB;gBACzC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,kBAAkB;gBACtC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;GAMG;AACH,qBAAa,6BAA8B,SAAQ,kBAAkB;gBACvD,OAAO,GAAE,MAA0I,EAAE,KAAK,CAAC,EAAE,KAAK;CAI/K"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeviceSecurityNotEnabledError = exports.TimeoutError = exports.ValidationError = exports.AuthenticationError = exports.KeychainReadError = exports.KeychainWriteError = exports.KeychainError = exports.SecureStorageError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Base error class for all secure storage errors
|
|
6
|
+
*/
|
|
7
|
+
class SecureStorageError extends Error {
|
|
8
|
+
constructor(message, code, cause) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
this.name = 'SecureStorageError';
|
|
13
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
14
|
+
// Note: Error.captureStackTrace is V8-specific (Node.js, Chrome). In React Native,
|
|
15
|
+
// this will typically work on Android (V8) but may not work on iOS (JavaScriptCore).
|
|
16
|
+
// If unavailable, the standard Error stack trace will be used instead.
|
|
17
|
+
if (Error.captureStackTrace) {
|
|
18
|
+
Error.captureStackTrace(this, this.constructor);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.SecureStorageError = SecureStorageError;
|
|
23
|
+
/**
|
|
24
|
+
* Error thrown when keychain operations fail
|
|
25
|
+
*
|
|
26
|
+
* Note: This is a base error class for completeness. In practice, use
|
|
27
|
+
* KeychainWriteError or KeychainReadError for more specific error handling.
|
|
28
|
+
*/
|
|
29
|
+
class KeychainError extends SecureStorageError {
|
|
30
|
+
constructor(message, cause) {
|
|
31
|
+
super(message, 'KEYCHAIN_ERROR', cause);
|
|
32
|
+
this.name = 'KeychainError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.KeychainError = KeychainError;
|
|
36
|
+
/**
|
|
37
|
+
* Error thrown when keychain write operations fail
|
|
38
|
+
*/
|
|
39
|
+
class KeychainWriteError extends SecureStorageError {
|
|
40
|
+
constructor(message, cause) {
|
|
41
|
+
super(message, 'KEYCHAIN_WRITE_ERROR', cause);
|
|
42
|
+
this.name = 'KeychainWriteError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.KeychainWriteError = KeychainWriteError;
|
|
46
|
+
/**
|
|
47
|
+
* Error thrown when keychain read operations fail
|
|
48
|
+
*/
|
|
49
|
+
class KeychainReadError extends SecureStorageError {
|
|
50
|
+
constructor(message, cause) {
|
|
51
|
+
super(message, 'KEYCHAIN_READ_ERROR', cause);
|
|
52
|
+
this.name = 'KeychainReadError';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.KeychainReadError = KeychainReadError;
|
|
56
|
+
/**
|
|
57
|
+
* Error thrown when authentication fails or is required but unavailable
|
|
58
|
+
*/
|
|
59
|
+
class AuthenticationError extends SecureStorageError {
|
|
60
|
+
constructor(message, cause) {
|
|
61
|
+
super(message, 'AUTHENTICATION_ERROR', cause);
|
|
62
|
+
this.name = 'AuthenticationError';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.AuthenticationError = AuthenticationError;
|
|
66
|
+
/**
|
|
67
|
+
* Error thrown when input validation fails
|
|
68
|
+
*/
|
|
69
|
+
class ValidationError extends SecureStorageError {
|
|
70
|
+
constructor(message) {
|
|
71
|
+
super(message, 'VALIDATION_ERROR');
|
|
72
|
+
this.name = 'ValidationError';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.ValidationError = ValidationError;
|
|
76
|
+
/**
|
|
77
|
+
* Error thrown when an operation times out
|
|
78
|
+
*/
|
|
79
|
+
class TimeoutError extends SecureStorageError {
|
|
80
|
+
constructor(message) {
|
|
81
|
+
super(message, 'TIMEOUT_ERROR');
|
|
82
|
+
this.name = 'TimeoutError';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.TimeoutError = TimeoutError;
|
|
86
|
+
/**
|
|
87
|
+
* Error thrown when device security (PIN/pattern/password/biometrics) is not enabled
|
|
88
|
+
*
|
|
89
|
+
* This error can be used by apps that want to enforce device security as a prerequisite.
|
|
90
|
+
* Whether to require device security is up to the app - the library will still function
|
|
91
|
+
* on devices without authentication configured (data remains encrypted at rest).
|
|
92
|
+
*/
|
|
93
|
+
class DeviceSecurityNotEnabledError extends SecureStorageError {
|
|
94
|
+
constructor(message = 'Device security is not enabled. Please set up a PIN, pattern, or password in your device settings to use secure wallet storage.', cause) {
|
|
95
|
+
super(message, 'DEVICE_SECURITY_NOT_ENABLED', cause);
|
|
96
|
+
this.name = 'DeviceSecurityNotEnabledError';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.DeviceSecurityNotEnabledError = DeviceSecurityNotEnabledError;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @tetherto/wdk-react-native-secure-storage
|
|
3
|
+
*
|
|
4
|
+
* Secure storage abstractions for React Native
|
|
5
|
+
* Provides secure storage for sensitive data (encrypted seeds, keys)
|
|
6
|
+
*
|
|
7
|
+
* **Note on Internal Types:** Some internal types like `StorageKey` are intentionally
|
|
8
|
+
* not exported. These are implementation details that consumers should not use directly.
|
|
9
|
+
* Use the public API methods (setEncryptionKey, getEncryptionKey, etc.) instead.
|
|
10
|
+
*/
|
|
11
|
+
export type { SecureStorage, SecureStorageOptions, AuthenticationOptions, SecureStorageItemOptions, } from './secureStorage';
|
|
12
|
+
export { createSecureStorage } from './secureStorage';
|
|
13
|
+
export { SecureStorageError, KeychainError, KeychainWriteError, KeychainReadError, AuthenticationError, ValidationError, TimeoutError, DeviceSecurityNotEnabledError, } from './errors';
|
|
14
|
+
export type { Logger, LogEntry } from './logger';
|
|
15
|
+
export { LogLevel, defaultLogger } from './logger';
|
|
16
|
+
export { MAX_IDENTIFIER_LENGTH, MAX_VALUE_LENGTH } from './validation';
|
|
17
|
+
export { MIN_TIMEOUT_MS, MAX_TIMEOUT_MS } from './constants';
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAGrD,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,YAAY,EACZ,6BAA6B,GAC9B,MAAM,UAAU,CAAA;AAGjB,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAGlD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAGtE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @tetherto/wdk-react-native-secure-storage
|
|
4
|
+
*
|
|
5
|
+
* Secure storage abstractions for React Native
|
|
6
|
+
* Provides secure storage for sensitive data (encrypted seeds, keys)
|
|
7
|
+
*
|
|
8
|
+
* **Note on Internal Types:** Some internal types like `StorageKey` are intentionally
|
|
9
|
+
* not exported. These are implementation details that consumers should not use directly.
|
|
10
|
+
* Use the public API methods (setEncryptionKey, getEncryptionKey, etc.) instead.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.MAX_TIMEOUT_MS = exports.MIN_TIMEOUT_MS = exports.MAX_VALUE_LENGTH = exports.MAX_IDENTIFIER_LENGTH = exports.defaultLogger = exports.LogLevel = exports.DeviceSecurityNotEnabledError = exports.TimeoutError = exports.ValidationError = exports.AuthenticationError = exports.KeychainReadError = exports.KeychainWriteError = exports.KeychainError = exports.SecureStorageError = exports.createSecureStorage = void 0;
|
|
14
|
+
var secureStorage_1 = require("./secureStorage");
|
|
15
|
+
Object.defineProperty(exports, "createSecureStorage", { enumerable: true, get: function () { return secureStorage_1.createSecureStorage; } });
|
|
16
|
+
// Error classes
|
|
17
|
+
var errors_1 = require("./errors");
|
|
18
|
+
Object.defineProperty(exports, "SecureStorageError", { enumerable: true, get: function () { return errors_1.SecureStorageError; } });
|
|
19
|
+
Object.defineProperty(exports, "KeychainError", { enumerable: true, get: function () { return errors_1.KeychainError; } });
|
|
20
|
+
Object.defineProperty(exports, "KeychainWriteError", { enumerable: true, get: function () { return errors_1.KeychainWriteError; } });
|
|
21
|
+
Object.defineProperty(exports, "KeychainReadError", { enumerable: true, get: function () { return errors_1.KeychainReadError; } });
|
|
22
|
+
Object.defineProperty(exports, "AuthenticationError", { enumerable: true, get: function () { return errors_1.AuthenticationError; } });
|
|
23
|
+
Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return errors_1.ValidationError; } });
|
|
24
|
+
Object.defineProperty(exports, "TimeoutError", { enumerable: true, get: function () { return errors_1.TimeoutError; } });
|
|
25
|
+
Object.defineProperty(exports, "DeviceSecurityNotEnabledError", { enumerable: true, get: function () { return errors_1.DeviceSecurityNotEnabledError; } });
|
|
26
|
+
var logger_1 = require("./logger");
|
|
27
|
+
Object.defineProperty(exports, "LogLevel", { enumerable: true, get: function () { return logger_1.LogLevel; } });
|
|
28
|
+
Object.defineProperty(exports, "defaultLogger", { enumerable: true, get: function () { return logger_1.defaultLogger; } });
|
|
29
|
+
// Validation constants
|
|
30
|
+
var validation_1 = require("./validation");
|
|
31
|
+
Object.defineProperty(exports, "MAX_IDENTIFIER_LENGTH", { enumerable: true, get: function () { return validation_1.MAX_IDENTIFIER_LENGTH; } });
|
|
32
|
+
Object.defineProperty(exports, "MAX_VALUE_LENGTH", { enumerable: true, get: function () { return validation_1.MAX_VALUE_LENGTH; } });
|
|
33
|
+
// Timeout constants
|
|
34
|
+
var constants_1 = require("./constants");
|
|
35
|
+
Object.defineProperty(exports, "MIN_TIMEOUT_MS", { enumerable: true, get: function () { return constants_1.MIN_TIMEOUT_MS; } });
|
|
36
|
+
Object.defineProperty(exports, "MAX_TIMEOUT_MS", { enumerable: true, get: function () { return constants_1.MAX_TIMEOUT_MS; } });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as Keychain from 'react-native-keychain';
|
|
2
|
+
/**
|
|
3
|
+
* Keychain options for setGenericPassword
|
|
4
|
+
*/
|
|
5
|
+
export type KeychainOptions = Parameters<typeof Keychain.setGenericPassword>[2];
|
|
6
|
+
/**
|
|
7
|
+
* Create keychain options with conditional access control
|
|
8
|
+
*
|
|
9
|
+
* @param deviceAuthAvailable - Whether device authentication (biometrics/PIN) is available
|
|
10
|
+
* @param requireAuth - Whether authentication should be required for this operation
|
|
11
|
+
* @param syncable - Whether the value should sync across devices (default: true)
|
|
12
|
+
* @returns Keychain options object with appropriate access control settings
|
|
13
|
+
*/
|
|
14
|
+
export declare function createKeychainOptions(deviceAuthAvailable: boolean, requireAuth?: boolean, syncable?: boolean): KeychainOptions;
|
|
15
|
+
//# sourceMappingURL=keychainHelpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keychainHelpers.d.ts","sourceRoot":"","sources":["../src/keychainHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,uBAAuB,CAAA;AAEjD;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAA;AAE/E;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,mBAAmB,EAAE,OAAO,EAC5B,WAAW,GAAE,OAAc,EAC3B,QAAQ,GAAE,OAAc,GACvB,eAAe,CAYjB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createKeychainOptions = createKeychainOptions;
|
|
37
|
+
const Keychain = __importStar(require("react-native-keychain"));
|
|
38
|
+
/**
|
|
39
|
+
* Create keychain options with conditional access control
|
|
40
|
+
*
|
|
41
|
+
* @param deviceAuthAvailable - Whether device authentication (biometrics/PIN) is available
|
|
42
|
+
* @param requireAuth - Whether authentication should be required for this operation
|
|
43
|
+
* @param syncable - Whether the value should sync across devices (default: true)
|
|
44
|
+
* @returns Keychain options object with appropriate access control settings
|
|
45
|
+
*/
|
|
46
|
+
function createKeychainOptions(deviceAuthAvailable, requireAuth = true, syncable = true) {
|
|
47
|
+
const options = {
|
|
48
|
+
accessible: syncable
|
|
49
|
+
? Keychain.ACCESSIBLE.WHEN_UNLOCKED
|
|
50
|
+
: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
|
51
|
+
};
|
|
52
|
+
if (requireAuth && deviceAuthAvailable) {
|
|
53
|
+
options.accessControl = Keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE;
|
|
54
|
+
}
|
|
55
|
+
return options;
|
|
56
|
+
}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log levels in order of severity
|
|
3
|
+
*/
|
|
4
|
+
export declare enum LogLevel {
|
|
5
|
+
DEBUG = 0,
|
|
6
|
+
INFO = 1,
|
|
7
|
+
WARN = 2,
|
|
8
|
+
ERROR = 3
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Log entry structure
|
|
12
|
+
*/
|
|
13
|
+
export interface LogEntry {
|
|
14
|
+
level: LogLevel;
|
|
15
|
+
message: string;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
context?: Record<string, unknown>;
|
|
18
|
+
error?: Error;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Logger interface for structured logging
|
|
22
|
+
*/
|
|
23
|
+
export interface Logger {
|
|
24
|
+
debug(message: string, context?: Record<string, unknown>): void;
|
|
25
|
+
info(message: string, context?: Record<string, unknown>): void;
|
|
26
|
+
warn(message: string, context?: Record<string, unknown>): void;
|
|
27
|
+
error(message: string, error?: Error, context?: Record<string, unknown>): void;
|
|
28
|
+
/**
|
|
29
|
+
* Set the minimum log level
|
|
30
|
+
* Logs below this level will be ignored
|
|
31
|
+
*/
|
|
32
|
+
setLevel?(level: LogLevel): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Default logger implementation
|
|
36
|
+
* Uses console methods but provides structured logging interface
|
|
37
|
+
*
|
|
38
|
+
* **Default Log Level:** ERROR
|
|
39
|
+
*
|
|
40
|
+
* The default log level is set to ERROR to minimize log noise in production.
|
|
41
|
+
* This ensures that only critical errors are logged by default, which is appropriate
|
|
42
|
+
* for production environments where excessive logging can impact performance and
|
|
43
|
+
* expose sensitive information.
|
|
44
|
+
*
|
|
45
|
+
* For development or debugging, you can change the log level:
|
|
46
|
+
* ```typescript
|
|
47
|
+
* defaultLogger.setLevel(LogLevel.DEBUG)
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* For production monitoring of security events, consider setting to WARN:
|
|
51
|
+
* ```typescript
|
|
52
|
+
* defaultLogger.setLevel(LogLevel.WARN)
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
declare class DefaultLogger implements Logger {
|
|
56
|
+
private minLevel;
|
|
57
|
+
/**
|
|
58
|
+
* Set the minimum log level
|
|
59
|
+
*/
|
|
60
|
+
setLevel(level: LogLevel): void;
|
|
61
|
+
/**
|
|
62
|
+
* Internal log method
|
|
63
|
+
*/
|
|
64
|
+
private log;
|
|
65
|
+
debug(message: string, context?: Record<string, unknown>): void;
|
|
66
|
+
info(message: string, context?: Record<string, unknown>): void;
|
|
67
|
+
warn(message: string, context?: Record<string, unknown>): void;
|
|
68
|
+
error(message: string, error?: Error, context?: Record<string, unknown>): void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Default logger instance
|
|
72
|
+
*/
|
|
73
|
+
export declare const defaultLogger: DefaultLogger;
|
|
74
|
+
export {};
|
|
75
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,oBAAY,QAAQ;IAClB,KAAK,IAAI;IACT,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;CACV;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,QAAQ,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC/D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC9D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC9D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC9E;;;OAGG;IACH,QAAQ,CAAC,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,cAAM,aAAc,YAAW,MAAM;IACnC,OAAO,CAAC,QAAQ,CAA2B;IAE3C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI/B;;OAEG;IACH,OAAO,CAAC,GAAG;IAiCX,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI/D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI9D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI9D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAG/E;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,eAAsB,CAAA"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultLogger = exports.LogLevel = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Log levels in order of severity
|
|
6
|
+
*/
|
|
7
|
+
var LogLevel;
|
|
8
|
+
(function (LogLevel) {
|
|
9
|
+
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
10
|
+
LogLevel[LogLevel["INFO"] = 1] = "INFO";
|
|
11
|
+
LogLevel[LogLevel["WARN"] = 2] = "WARN";
|
|
12
|
+
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
|
|
13
|
+
})(LogLevel || (exports.LogLevel = LogLevel = {}));
|
|
14
|
+
/**
|
|
15
|
+
* Default logger implementation
|
|
16
|
+
* Uses console methods but provides structured logging interface
|
|
17
|
+
*
|
|
18
|
+
* **Default Log Level:** ERROR
|
|
19
|
+
*
|
|
20
|
+
* The default log level is set to ERROR to minimize log noise in production.
|
|
21
|
+
* This ensures that only critical errors are logged by default, which is appropriate
|
|
22
|
+
* for production environments where excessive logging can impact performance and
|
|
23
|
+
* expose sensitive information.
|
|
24
|
+
*
|
|
25
|
+
* For development or debugging, you can change the log level:
|
|
26
|
+
* ```typescript
|
|
27
|
+
* defaultLogger.setLevel(LogLevel.DEBUG)
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* For production monitoring of security events, consider setting to WARN:
|
|
31
|
+
* ```typescript
|
|
32
|
+
* defaultLogger.setLevel(LogLevel.WARN)
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
class DefaultLogger {
|
|
36
|
+
constructor() {
|
|
37
|
+
this.minLevel = LogLevel.ERROR;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Set the minimum log level
|
|
41
|
+
*/
|
|
42
|
+
setLevel(level) {
|
|
43
|
+
this.minLevel = level;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Internal log method
|
|
47
|
+
*/
|
|
48
|
+
log(level, message, error, context) {
|
|
49
|
+
if (level < this.minLevel) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const entry = {
|
|
53
|
+
level,
|
|
54
|
+
message,
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
context,
|
|
57
|
+
error,
|
|
58
|
+
};
|
|
59
|
+
// In production, this could send to a logging service
|
|
60
|
+
// For now, use console with structured output
|
|
61
|
+
// Note: Sensitive data (encryption keys, seeds, etc.) should never be logged
|
|
62
|
+
// The logger only logs error messages and metadata, never the actual stored values
|
|
63
|
+
const logMessage = JSON.stringify(entry, null, 2);
|
|
64
|
+
if (level >= LogLevel.ERROR) {
|
|
65
|
+
console.error(logMessage);
|
|
66
|
+
}
|
|
67
|
+
else if (level >= LogLevel.WARN) {
|
|
68
|
+
console.warn(logMessage);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(logMessage);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
debug(message, context) {
|
|
75
|
+
this.log(LogLevel.DEBUG, message, undefined, context);
|
|
76
|
+
}
|
|
77
|
+
info(message, context) {
|
|
78
|
+
this.log(LogLevel.INFO, message, undefined, context);
|
|
79
|
+
}
|
|
80
|
+
warn(message, context) {
|
|
81
|
+
this.log(LogLevel.WARN, message, undefined, context);
|
|
82
|
+
}
|
|
83
|
+
error(message, error, context) {
|
|
84
|
+
this.log(LogLevel.ERROR, message, error, context);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Default logger instance
|
|
89
|
+
*/
|
|
90
|
+
exports.defaultLogger = new DefaultLogger();
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Logger } from './logger';
|
|
2
|
+
/**
|
|
3
|
+
* Authentication options for biometric prompts
|
|
4
|
+
*/
|
|
5
|
+
export interface AuthenticationOptions {
|
|
6
|
+
promptMessage?: string;
|
|
7
|
+
cancelLabel?: string;
|
|
8
|
+
disableDeviceFallback?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Options for a single secure storage item
|
|
12
|
+
*/
|
|
13
|
+
export interface SecureStorageItemOptions {
|
|
14
|
+
requireBiometrics?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Options for creating secure storage instance
|
|
18
|
+
*/
|
|
19
|
+
export interface SecureStorageOptions {
|
|
20
|
+
logger?: Logger;
|
|
21
|
+
authentication?: AuthenticationOptions;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Secure storage interface
|
|
26
|
+
*
|
|
27
|
+
* All methods accept an optional identifier parameter to support multiple wallets.
|
|
28
|
+
* When identifier is provided, it's used to create unique storage keys for each wallet.
|
|
29
|
+
* When identifier is undefined or empty, default keys are used (backward compatibility).
|
|
30
|
+
*
|
|
31
|
+
* Error Handling:
|
|
32
|
+
* - Getters return null when data is not found
|
|
33
|
+
* - All methods throw SecureStorageError or subclasses on failure
|
|
34
|
+
* - Validation errors are thrown before any operations
|
|
35
|
+
*/
|
|
36
|
+
export interface SecureStorage {
|
|
37
|
+
isDeviceSecurityEnabled(): Promise<boolean>;
|
|
38
|
+
isBiometricAvailable(): Promise<boolean>;
|
|
39
|
+
authenticate(): Promise<boolean>;
|
|
40
|
+
setEncryptionKey(key: string, identifier?: string, options?: SecureStorageItemOptions): Promise<void>;
|
|
41
|
+
getEncryptionKey(identifier?: string, options?: SecureStorageItemOptions): Promise<string | null>;
|
|
42
|
+
setEncryptedSeed(encryptedSeed: string, identifier?: string): Promise<void>;
|
|
43
|
+
getEncryptedSeed(identifier?: string): Promise<string | null>;
|
|
44
|
+
setEncryptedEntropy(encryptedEntropy: string, identifier?: string): Promise<void>;
|
|
45
|
+
getEncryptedEntropy(identifier?: string): Promise<string | null>;
|
|
46
|
+
getAllEncrypted(identifier?: string): Promise<{
|
|
47
|
+
encryptedSeed: string | null;
|
|
48
|
+
encryptedEntropy: string | null;
|
|
49
|
+
encryptionKey: string | null;
|
|
50
|
+
}>;
|
|
51
|
+
hasWallet(identifier?: string): Promise<boolean>;
|
|
52
|
+
deleteWallet(identifier?: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Cleanup method to release resources associated with this storage instance
|
|
55
|
+
*
|
|
56
|
+
* This method should be called when the storage instance is no longer needed
|
|
57
|
+
* to ensure proper cleanup of resources. Note that this does NOT delete stored
|
|
58
|
+
* wallet data - use deleteWallet() for that purpose.
|
|
59
|
+
*
|
|
60
|
+
* Currently, this is a no-op as the module uses shared resources, but it's
|
|
61
|
+
* provided for future extensibility and to maintain a consistent API.
|
|
62
|
+
*/
|
|
63
|
+
cleanup(): void;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Secure storage wrapper factory for wallet credentials
|
|
67
|
+
*
|
|
68
|
+
* Uses react-native-keychain which provides encrypted storage with selective cloud sync.
|
|
69
|
+
* Creates a new instance each time it's called, allowing different configurations
|
|
70
|
+
* for different use cases. For most apps, you should create one instance and reuse it.
|
|
71
|
+
*
|
|
72
|
+
* SECURITY:
|
|
73
|
+
* - Storage is app-scoped by the OS (isolated by bundle ID/package name)
|
|
74
|
+
* - iOS: Uses Keychain Services with iCloud Keychain sync (when user signed into iCloud)
|
|
75
|
+
* - Android: Uses KeyStore with Google Cloud backup (when device backup enabled)
|
|
76
|
+
* - Data is ALWAYS encrypted at rest by Keychain (iOS) / KeyStore (Android)
|
|
77
|
+
* - Cloud sync behavior:
|
|
78
|
+
* - Encryption key: ACCESSIBLE.WHEN_UNLOCKED enables iCloud Keychain sync (iOS) and Google Cloud backup (Android)
|
|
79
|
+
* - Encrypted seed and entropy: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY prevents cloud sync (device-only storage)
|
|
80
|
+
* - Data is encrypted by Apple/Google's E2EE infrastructure
|
|
81
|
+
* - Encryption key requires device unlock + biometric/PIN authentication to access (when available)
|
|
82
|
+
* - Encrypted seed and entropy do not require authentication but are still encrypted at rest
|
|
83
|
+
* - On devices without authentication, data is still encrypted at rest but accessible when device is unlocked
|
|
84
|
+
* - Device-level keychain/keystore provides rate limiting and lockout mechanisms
|
|
85
|
+
* - Input validation prevents injection attacks
|
|
86
|
+
*
|
|
87
|
+
* Two different apps will NOT share data because storage is isolated by bundle ID/package name.
|
|
88
|
+
*
|
|
89
|
+
* @param options - Optional configuration for logger, authentication messages, and timeouts
|
|
90
|
+
* @returns SecureStorage instance
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const storage = createSecureStorage({
|
|
95
|
+
* logger: customLogger,
|
|
96
|
+
* authentication: {
|
|
97
|
+
* promptMessage: 'Authenticate to access wallet',
|
|
98
|
+
* },
|
|
99
|
+
* timeoutMs: 30000,
|
|
100
|
+
* })
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export declare function createSecureStorage(options?: SecureStorageOptions): SecureStorage;
|
|
104
|
+
//# sourceMappingURL=secureStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secureStorage.d.ts","sourceRoot":"","sources":["../src/secureStorage.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,MAAM,EAAiB,MAAM,UAAU,CAAA;AAoBhD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,qBAAqB,CAAA;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa;IAC5B,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3C,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAChC,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrG,gBAAgB,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACjG,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3E,gBAAgB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAC7D,mBAAmB,CAAC,gBAAgB,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjF,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAChE,eAAe,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAC5C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;QAC5B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;QAC/B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;KAC7B,CAAC,CAAA;IACF,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAChD,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD;;;;;;;;;OASG;IACH,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,aAAa,CAsjBjF"}
|