@futdevpro/fsm-dynamo 1.14.12 → 1.14.13
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/build/_collections/utils/async.util.js +3 -5
- package/build/_collections/utils/async.util.js.map +1 -1
- package/build/_collections/utils/log.util.js +48 -50
- package/build/_collections/utils/log.util.js.map +1 -1
- package/build/_collections/utils/math/box-bounds.util.js +6 -2
- package/build/_collections/utils/math/box-bounds.util.js.map +1 -1
- package/build/_collections/utils/math/math.util.js +1 -3
- package/build/_collections/utils/math/math.util.js.map +1 -1
- package/build/_collections/utils/math/vector2.util.js +60 -62
- package/build/_collections/utils/math/vector2.util.js.map +1 -1
- package/build/_collections/utils/object.util.js +7 -9
- package/build/_collections/utils/object.util.js.map +1 -1
- package/build/_collections/utils/stack.util.spec.js +13 -0
- package/build/_collections/utils/stack.util.spec.js.map +1 -1
- package/build/_collections/utils/time.util.js +28 -28
- package/build/_collections/utils/time.util.js.map +1 -1
- package/build/_models/control-models/data-model-params.control-model.js +21 -0
- package/build/_models/control-models/data-model-params.control-model.js.map +1 -1
- package/build/_models/control-models/data-property-params.control-model.js +59 -3
- package/build/_models/control-models/data-property-params.control-model.js.map +1 -1
- package/build/_models/control-models/error.control-model.js +25 -8
- package/build/_models/control-models/error.control-model.js.map +1 -1
- package/build/_models/control-models/http/http-error-response.control-model.js +7 -5
- package/build/_models/control-models/http/http-error-response.control-model.js.map +1 -1
- package/build/_models/control-models/http/http-headers.control-model.js +17 -4
- package/build/_models/control-models/http/http-headers.control-model.js.map +1 -1
- package/build/_models/control-models/http/http-response.model-base.js +26 -14
- package/build/_models/control-models/http/http-response.model-base.js.map +1 -1
- package/build/_models/control-models/poll.control-model.js +26 -4
- package/build/_models/control-models/poll.control-model.js.map +1 -1
- package/build/_models/control-models/range-value.control-model.js +4 -2
- package/build/_models/control-models/range-value.control-model.js.map +1 -1
- package/build/_models/control-models/server-status.control-model.js +12 -0
- package/build/_models/control-models/server-status.control-model.js.map +1 -1
- package/build/_models/control-models/service-endpoint-settings-base.control-model.js +14 -1
- package/build/_models/control-models/service-endpoint-settings-base.control-model.js.map +1 -1
- package/build/_models/data-models/errors.data-model.js +18 -3
- package/build/_models/data-models/errors.data-model.js.map +1 -1
- package/build/_models/data-models/metadata.data-model.js +12 -0
- package/build/_models/data-models/metadata.data-model.js.map +1 -1
- package/build/_modules/ai/_models/ai-call-settings.interface.js +49 -46
- package/build/_modules/ai/_models/ai-call-settings.interface.js.map +1 -1
- package/build/_modules/ai/_models/ai-settings.interface.js +3 -0
- package/build/_modules/ai/_models/ai-settings.interface.js.map +1 -1
- package/build/_modules/ai/_modules/anthropic/_models/aai-call-settings.control-model.js +2 -2
- package/build/_modules/ai/_modules/anthropic/_models/aai-call-settings.control-model.js.map +1 -1
- package/build/_modules/ai/_modules/anthropic/_models/aai-settings.control-model.js +3 -3
- package/build/_modules/ai/_modules/anthropic/_models/aai-settings.control-model.js.map +1 -1
- package/build/_modules/ai/_modules/google-ai/_models/gai-call-settings.control-model.js +2 -2
- package/build/_modules/ai/_modules/google-ai/_models/gai-call-settings.control-model.js.map +1 -1
- package/build/_modules/ai/_modules/google-ai/_models/gai-settings.control-model.js +3 -3
- package/build/_modules/ai/_modules/google-ai/_models/gai-settings.control-model.js.map +1 -1
- package/build/_modules/ai/_modules/local-ai/_models/lai-call-settings.control-model.js +1 -1
- package/build/_modules/ai/_modules/local-ai/_models/lai-call-settings.control-model.js.map +1 -1
- package/build/_modules/ai/_modules/local-ai/_models/lai-settings.control-model.js +3 -3
- package/build/_modules/ai/_modules/local-ai/_models/lai-settings.control-model.js.map +1 -1
- package/build/_modules/ai/_modules/open-ai/_models/oai-call-settings.control-model.js +2 -2
- package/build/_modules/ai/_modules/open-ai/_models/oai-call-settings.control-model.js.map +1 -1
- package/build/_modules/ai/_modules/open-ai/_models/oai-settings.control-model.js +3 -3
- package/build/_modules/ai/_modules/open-ai/_models/oai-settings.control-model.js.map +1 -1
- package/build/_modules/ci-tools/_models/cit-ci-result-info.data-models.js +12 -1
- package/build/_modules/ci-tools/_models/cit-ci-result-info.data-models.js.map +1 -1
- package/build/_modules/crypto/_collections/{crypto-2-non-stable.util.d.ts → crypto-v1.util.d.ts} +1 -1
- package/build/_modules/crypto/_collections/crypto-v1.util.d.ts.map +1 -0
- package/build/_modules/crypto/_collections/{crypto-2-non-stable.util.js → crypto-v1.util.js} +3 -3
- package/build/_modules/crypto/_collections/crypto-v1.util.js.map +1 -0
- package/build/_modules/crypto/_collections/{crypto-old.util.d.ts → crypto-v2.util.d.ts} +1 -1
- package/build/_modules/crypto/_collections/crypto-v2.util.d.ts.map +1 -0
- package/build/_modules/crypto/_collections/{crypto-old.util.js → crypto-v2.util.js} +9 -9
- package/build/_modules/crypto/_collections/crypto-v2.util.js.map +1 -0
- package/build/_modules/crypto/_collections/crypto-v4.util.d.ts +165 -0
- package/build/_modules/crypto/_collections/crypto-v4.util.d.ts.map +1 -0
- package/build/_modules/crypto/_collections/crypto-v4.util.js +611 -0
- package/build/_modules/crypto/_collections/crypto-v4.util.js.map +1 -0
- package/build/_modules/crypto/_collections/crypto.util.js +9 -9
- package/build/_modules/crypto/_collections/crypto.util.js.map +1 -1
- package/build/_modules/crypto/index.d.ts.map +1 -1
- package/build/_modules/crypto/index.js +7 -7
- package/build/_modules/crypto/index.js.map +1 -1
- package/build/_modules/custom-data/_models/cud.data-model.js +1 -0
- package/build/_modules/custom-data/_models/cud.data-model.js.map +1 -1
- package/build/_modules/data-handler/_models/data-handler-settings.control-model.js +42 -0
- package/build/_modules/data-handler/_models/data-handler-settings.control-model.js.map +1 -1
- package/build/_modules/data-handler/_models/data-handler.control-model.js +51 -11
- package/build/_modules/data-handler/_models/data-handler.control-model.js.map +1 -1
- package/build/_modules/data-handler/_models/data-list-handler.control-model.js +20 -0
- package/build/_modules/data-handler/_models/data-list-handler.control-model.js.map +1 -1
- package/build/_modules/data-handler/_models/data-search-handler.control-model.js +42 -36
- package/build/_modules/data-handler/_models/data-search-handler.control-model.js.map +1 -1
- package/build/_modules/data-handler/_models/list-collector-data-handler.control-model.js +25 -0
- package/build/_modules/data-handler/_models/list-collector-data-handler.control-model.js.map +1 -1
- package/build/_modules/location/_collections/loc-regions.util.js +4 -4
- package/build/_modules/location/_collections/loc-regions.util.js.map +1 -1
- package/build/_modules/messaging/_models/msg-conversation.data-model.js +23 -0
- package/build/_modules/messaging/_models/msg-conversation.data-model.js.map +1 -1
- package/build/_modules/messaging/_models/msg-message.data-model.js +34 -0
- package/build/_modules/messaging/_models/msg-message.data-model.js.map +1 -1
- package/build/_modules/socket/_models/sck-client-params.control-model.js +7 -2
- package/build/_modules/socket/_models/sck-client-params.control-model.js.map +1 -1
- package/build/_modules/socket/_models/sck-socket-event.control-model.js +8 -0
- package/build/_modules/socket/_models/sck-socket-event.control-model.js.map +1 -1
- package/build/_modules/socket/_services/sck-client.service-base.js +72 -69
- package/build/_modules/socket/_services/sck-client.service-base.js.map +1 -1
- package/build/_modules/usage/_models/usg-action.control-model.js +4 -0
- package/build/_modules/usage/_models/usg-action.control-model.js.map +1 -1
- package/build/_modules/usage/_models/usg-daily-usage-data.control-model.js +12 -10
- package/build/_modules/usage/_models/usg-daily-usage-data.control-model.js.map +1 -1
- package/build/_modules/usage/_models/usg-data.control-model.js +8 -2
- package/build/_modules/usage/_models/usg-data.control-model.js.map +1 -1
- package/build/_modules/usage/_models/usg-session.data-model.js +18 -2
- package/build/_modules/usage/_models/usg-session.data-model.js.map +1 -1
- package/futdevpro-fsm-dynamo-01.14.13.tgz +0 -0
- package/package.json +2 -2
- package/src/_modules/crypto/_collections/crypto-v4.util.ts +702 -0
- package/src/_modules/crypto/index.ts +4 -3
- package/tsconfig.json +2 -2
- package/build/_modules/crypto/_collections/crypto-2-non-stable.util.d.ts.map +0 -1
- package/build/_modules/crypto/_collections/crypto-2-non-stable.util.js.map +0 -1
- package/build/_modules/crypto/_collections/crypto-old.util.d.ts.map +0 -1
- package/build/_modules/crypto/_collections/crypto-old.util.js.map +0 -1
- package/futdevpro-fsm-dynamo-01.14.12.tgz +0 -0
- /package/src/_modules/crypto/_collections/{crypto-2-non-stable.util.ts → crypto-v1.util.ts} +0 -0
- /package/src/_modules/crypto/_collections/{crypto-old.util.ts → crypto-v2.util.ts} +0 -0
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
import * as CryptoJS from 'crypto-js';
|
|
2
|
+
import {
|
|
3
|
+
DyFM_Error,
|
|
4
|
+
DyFM_Error_Settings
|
|
5
|
+
} from '../../../_models/control-models/error.control-model';
|
|
6
|
+
import { DyFM_Object } from '../../../_collections/utils/object.util';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Configuration options for encryption/decryption
|
|
11
|
+
*/
|
|
12
|
+
export interface CryptoConfig {
|
|
13
|
+
ivLength?: number;
|
|
14
|
+
saltLength?: number;
|
|
15
|
+
keyIterations?: number;
|
|
16
|
+
keySize?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Compact: about 60–80 character tokens, not 200+
|
|
20
|
+
// Non-standard: hard to reverse-engineer
|
|
21
|
+
// Usable in cookies, headers, URLs
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A utility class for stable encryption and decryption of data
|
|
25
|
+
* Uses AES-256-CBC with deterministic IV and salt for consistent results across systems
|
|
26
|
+
* Prioritizes reliability and cross-platform compatibility over security
|
|
27
|
+
*
|
|
28
|
+
* @important DETERMINISTIC ENCRYPTION: This implementation produces identical encrypted
|
|
29
|
+
* output for identical input data and key across different systems and multiple calls.
|
|
30
|
+
* The same input will ALWAYS generate the same encrypted string on any platform.
|
|
31
|
+
*
|
|
32
|
+
* @warning SECURITY NOTICE: This deterministic behavior is intentional for cross-platform
|
|
33
|
+
* compatibility but reduces security. Identical inputs produce identical outputs, which
|
|
34
|
+
* can be exploited for pattern analysis attacks. Use only when consistency across
|
|
35
|
+
* systems is more important than cryptographic security.
|
|
36
|
+
*/
|
|
37
|
+
export class DyFM_Crypto {
|
|
38
|
+
private static readonly CRYPTO_VERSION = '1.0';
|
|
39
|
+
private static readonly DEFAULT_CONFIG: Required<CryptoConfig> = {
|
|
40
|
+
ivLength: 16, // 128 bits
|
|
41
|
+
saltLength: 16, // 128 bits
|
|
42
|
+
keyIterations: 1000, // Reduced for better performance and stability
|
|
43
|
+
keySize: 8 // 256 bits (8 * 32)
|
|
44
|
+
};
|
|
45
|
+
private static readonly defaultErrorUserMsg =
|
|
46
|
+
`We encountered an unhandled Authentication Error, ` +
|
|
47
|
+
`\nplease contact the responsible development team.`;
|
|
48
|
+
|
|
49
|
+
// Key derivation cache for performance optimization
|
|
50
|
+
private static keyCache = new Map<string, CryptoJS.lib.WordArray>();
|
|
51
|
+
private static readonly MAX_CACHE_SIZE = 100;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Validates the input data and key with comprehensive error messages
|
|
55
|
+
* @throws {DyFM_Error} if validation fails
|
|
56
|
+
*/
|
|
57
|
+
private static validateInput(data: any, key: string, operation: 'encrypt' | 'decrypt'): void {
|
|
58
|
+
// Validate key
|
|
59
|
+
if (!key) {
|
|
60
|
+
throw new DyFM_Error({
|
|
61
|
+
...this.getDefaultErrorSettings(operation),
|
|
62
|
+
errorCode: 'DyFM-CRY-KEY-MISSING',
|
|
63
|
+
message: `Encryption key is required for ${operation} operation. Please provide a valid key.`
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (typeof key !== 'string') {
|
|
68
|
+
throw new DyFM_Error({
|
|
69
|
+
...this.getDefaultErrorSettings(operation),
|
|
70
|
+
errorCode: 'DyFM-CRY-KEY-TYPE',
|
|
71
|
+
message: `Encryption key must be a string, but received ${typeof key}. Please provide a valid string key.`
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (key.trim().length === 0) {
|
|
76
|
+
throw new DyFM_Error({
|
|
77
|
+
...this.getDefaultErrorSettings(operation),
|
|
78
|
+
errorCode: 'DyFM-CRY-KEY-EMPTY',
|
|
79
|
+
message: 'Encryption key cannot be empty or contain only whitespace. Please provide a non-empty key.'
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Only warn about weak keys but don't reject them for backward compatibility
|
|
84
|
+
if (key.length < 8) {
|
|
85
|
+
console.warn('Warning: Encryption key is too short (minimum 8 characters recommended). Consider using a stronger key for better security.');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Validate data based on operation
|
|
89
|
+
if (operation === 'encrypt') {
|
|
90
|
+
this.validateEncryptData(data);
|
|
91
|
+
} else if (operation === 'decrypt') {
|
|
92
|
+
this.validateDecryptData(data);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Validates data for encryption
|
|
98
|
+
*/
|
|
99
|
+
private static validateEncryptData(data: any): void {
|
|
100
|
+
if (data === undefined) {
|
|
101
|
+
throw new DyFM_Error({
|
|
102
|
+
...this.getDefaultErrorSettings('encrypt'),
|
|
103
|
+
errorCode: 'DyFM-CRY-DATA-UNDEFINED',
|
|
104
|
+
message: 'Cannot encrypt undefined data. Please provide valid data to encrypt.'
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (data === null) {
|
|
109
|
+
throw new DyFM_Error({
|
|
110
|
+
...this.getDefaultErrorSettings('encrypt'),
|
|
111
|
+
errorCode: 'DyFM-CRY-DATA-NULL',
|
|
112
|
+
message: 'Cannot encrypt null data. Please provide valid data to encrypt.'
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check for empty strings
|
|
117
|
+
if (typeof data === 'string' && data.trim().length === 0) {
|
|
118
|
+
throw new DyFM_Error({
|
|
119
|
+
...this.getDefaultErrorSettings('encrypt'),
|
|
120
|
+
errorCode: 'DyFM-CRY-DATA-EMPTY-STRING',
|
|
121
|
+
message: 'Cannot encrypt empty string. Please provide non-empty data to encrypt.'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Allow empty objects and arrays for backward compatibility
|
|
126
|
+
// Only reject truly empty data like empty strings
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validates data for decryption
|
|
131
|
+
*/
|
|
132
|
+
private static validateDecryptData(data: any): void {
|
|
133
|
+
if (data === undefined) {
|
|
134
|
+
throw new DyFM_Error({
|
|
135
|
+
...this.getDefaultErrorSettings('decrypt'),
|
|
136
|
+
errorCode: 'DyFM-CRY-ENCRYPTED-UNDEFINED',
|
|
137
|
+
message: 'Cannot decrypt undefined data. Please provide valid encrypted data to decrypt.'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (data === null) {
|
|
142
|
+
throw new DyFM_Error({
|
|
143
|
+
...this.getDefaultErrorSettings('decrypt'),
|
|
144
|
+
errorCode: 'DyFM-CRY-ENCRYPTED-NULL',
|
|
145
|
+
message: 'Cannot decrypt null data. Please provide valid encrypted data to decrypt.'
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof data !== 'string') {
|
|
150
|
+
throw new DyFM_Error({
|
|
151
|
+
...this.getDefaultErrorSettings('decrypt'),
|
|
152
|
+
errorCode: 'DyFM-CRY-ENCRYPTED-TYPE',
|
|
153
|
+
message: `Encrypted data must be a string, but received ${typeof data}. Please provide valid encrypted string data.`
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (data.trim().length === 0) {
|
|
158
|
+
throw new DyFM_Error({
|
|
159
|
+
...this.getDefaultErrorSettings('decrypt'),
|
|
160
|
+
errorCode: 'DyFM-CRY-ENCRYPTED-EMPTY',
|
|
161
|
+
message: 'Cannot decrypt empty string. Please provide valid encrypted data to decrypt.'
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (data.length < 10) {
|
|
166
|
+
throw new DyFM_Error({
|
|
167
|
+
...this.getDefaultErrorSettings('decrypt'),
|
|
168
|
+
errorCode: 'DyFM-CRY-ENCRYPTED-TOO-SHORT',
|
|
169
|
+
message: 'Encrypted data appears to be too short to be valid. Please check the encrypted data.'
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check if it looks like valid encrypted data format
|
|
174
|
+
if (!/^[A-Za-z0-9\-_]+$/.test(data)) {
|
|
175
|
+
throw new DyFM_Error({
|
|
176
|
+
...this.getDefaultErrorSettings('decrypt'),
|
|
177
|
+
errorCode: 'DyFM-CRY-ENCRYPTED-INVALID-FORMAT',
|
|
178
|
+
message: 'Encrypted data does not appear to be in valid format. Expected URL-safe base64 format.'
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Generates a deterministic IV based on the input data and key
|
|
185
|
+
* Uses SHA-256 with proper truncation for maximum stability
|
|
186
|
+
*
|
|
187
|
+
* @important DETERMINISTIC: Same data + key will ALWAYS produce the same IV
|
|
188
|
+
* across all systems and CryptoJS versions for consistent encryption results
|
|
189
|
+
*/
|
|
190
|
+
private static generateIV(data: string, key: string, config: Required<CryptoConfig>): CryptoJS.lib.WordArray {
|
|
191
|
+
// Create a deterministic seed from data and key
|
|
192
|
+
const seed = this.createDeterministicSeed(data, key, 'IV');
|
|
193
|
+
|
|
194
|
+
// Use SHA-256 for better stability and consistency
|
|
195
|
+
const hash = CryptoJS.SHA256(seed);
|
|
196
|
+
|
|
197
|
+
// Extract exactly 16 bytes (128 bits) for IV
|
|
198
|
+
// Use the first 4 words (4 * 4 = 16 bytes) from the hash
|
|
199
|
+
const ivWords = hash.words.slice(0, 4);
|
|
200
|
+
return CryptoJS.lib.WordArray.create(ivWords);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Generates a deterministic salt based on the input data and key
|
|
205
|
+
* Uses SHA-256 with proper truncation for maximum stability
|
|
206
|
+
*
|
|
207
|
+
* @important DETERMINISTIC: Same data + key will ALWAYS produce the same salt
|
|
208
|
+
* across all systems and CryptoJS versions for consistent encryption results
|
|
209
|
+
*/
|
|
210
|
+
private static generateSalt(data: string, key: string, config: Required<CryptoConfig>): CryptoJS.lib.WordArray {
|
|
211
|
+
// Create a deterministic seed from data and key (different from IV)
|
|
212
|
+
const seed = this.createDeterministicSeed(data, key, 'SALT');
|
|
213
|
+
|
|
214
|
+
// Use SHA-256 for better stability and consistency
|
|
215
|
+
const hash = CryptoJS.SHA256(seed);
|
|
216
|
+
|
|
217
|
+
// Extract exactly 16 bytes (128 bits) for salt
|
|
218
|
+
// Use the first 4 words (4 * 4 = 16 bytes) from the hash
|
|
219
|
+
const saltWords = hash.words.slice(0, 4);
|
|
220
|
+
return CryptoJS.lib.WordArray.create(saltWords);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Creates a deterministic seed for IV/salt generation
|
|
225
|
+
* Ensures consistent output across all environments and versions
|
|
226
|
+
*/
|
|
227
|
+
private static createDeterministicSeed(data: string, key: string, purpose: string): string {
|
|
228
|
+
// Create a consistent seed that includes all relevant factors
|
|
229
|
+
// Order matters: data + key + purpose for consistency
|
|
230
|
+
const seed = `${data}|${key}|${purpose}`;
|
|
231
|
+
return seed;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Generates a cache key for the derived key based on the input key and salt
|
|
236
|
+
*/
|
|
237
|
+
private static generateCacheKey(key: string, salt: CryptoJS.lib.WordArray): string {
|
|
238
|
+
// Create a deterministic cache key from key and salt
|
|
239
|
+
const saltHex = salt.toString(CryptoJS.enc.Hex);
|
|
240
|
+
return `${key}|${saltHex}`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Manages cache size by removing oldest entries when limit is exceeded
|
|
245
|
+
*/
|
|
246
|
+
private static manageCacheSize(): void {
|
|
247
|
+
if (this.keyCache.size >= this.MAX_CACHE_SIZE) {
|
|
248
|
+
// Remove oldest entries (Map maintains insertion order)
|
|
249
|
+
const entriesToRemove = this.keyCache.size - this.MAX_CACHE_SIZE + 10; // Remove 10 extra for efficiency
|
|
250
|
+
const keysToRemove = Array.from(this.keyCache.keys()).slice(0, entriesToRemove);
|
|
251
|
+
keysToRemove.forEach(key => this.keyCache.delete(key));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Derives a key using PBKDF2 with reduced iterations for stability
|
|
257
|
+
* Uses caching to avoid redundant PBKDF2 computations for better performance
|
|
258
|
+
*/
|
|
259
|
+
private static deriveKey(key: string, salt: CryptoJS.lib.WordArray, config: Required<CryptoConfig>): CryptoJS.lib.WordArray {
|
|
260
|
+
const cacheKey = this.generateCacheKey(key, salt);
|
|
261
|
+
|
|
262
|
+
// Check cache first
|
|
263
|
+
if (this.keyCache.has(cacheKey)) {
|
|
264
|
+
return this.keyCache.get(cacheKey)!;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Compute new key using PBKDF2
|
|
268
|
+
const derivedKey = CryptoJS.PBKDF2(key, salt, {
|
|
269
|
+
keySize: config.keySize,
|
|
270
|
+
iterations: config.keyIterations
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Store in cache and manage size
|
|
274
|
+
this.keyCache.set(cacheKey, derivedKey);
|
|
275
|
+
this.manageCacheSize();
|
|
276
|
+
|
|
277
|
+
return derivedKey;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Safely serializes data to JSON with deterministic ordering
|
|
282
|
+
* Uses regular JSON.stringify but ensures consistency through other means
|
|
283
|
+
*/
|
|
284
|
+
private static safeSerialize<T>(data: T): string {
|
|
285
|
+
try {
|
|
286
|
+
// Use regular JSON.stringify for backward compatibility
|
|
287
|
+
// The deterministic behavior comes from the IV/salt generation, not serialization
|
|
288
|
+
return JSON.stringify(data);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
throw new DyFM_Error({
|
|
291
|
+
...this.getDefaultErrorSettings('safeSerialize', error),
|
|
292
|
+
errorCode: 'DyFM-CRY-SER',
|
|
293
|
+
message: 'Failed to serialize data'
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Deterministic JSON stringify that produces identical output across environments
|
|
300
|
+
* Uses a hybrid approach: sorts keys for consistency but preserves order for arrays
|
|
301
|
+
*/
|
|
302
|
+
private static deterministicStringify(obj: any): string {
|
|
303
|
+
if (obj === null) return 'null';
|
|
304
|
+
if (obj === undefined) return 'undefined';
|
|
305
|
+
if (typeof obj === 'string') return JSON.stringify(obj);
|
|
306
|
+
if (typeof obj === 'number') return JSON.stringify(obj);
|
|
307
|
+
if (typeof obj === 'boolean') return JSON.stringify(obj);
|
|
308
|
+
|
|
309
|
+
if (Array.isArray(obj)) {
|
|
310
|
+
const items = obj.map(item => this.deterministicStringify(item));
|
|
311
|
+
return '[' + items.join(',') + ']';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (typeof obj === 'object') {
|
|
315
|
+
// For objects, we need to be more careful about key ordering
|
|
316
|
+
// Use a stable sort that preserves original order when possible
|
|
317
|
+
const keys = Object.keys(obj);
|
|
318
|
+
|
|
319
|
+
// Only sort if there are potential ordering issues
|
|
320
|
+
const needsSorting = keys.some((key, index) => {
|
|
321
|
+
if (index === 0) return false;
|
|
322
|
+
return key < keys[index - 1];
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const sortedKeys = needsSorting ? [...keys].sort() : keys;
|
|
326
|
+
const pairs = sortedKeys.map(key => {
|
|
327
|
+
const value = this.deterministicStringify(obj[key]);
|
|
328
|
+
return JSON.stringify(key) + ':' + value;
|
|
329
|
+
});
|
|
330
|
+
return '{' + pairs.join(',') + '}';
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Handle Date objects and other special types
|
|
334
|
+
if (obj instanceof Date) {
|
|
335
|
+
return JSON.stringify(obj.toISOString());
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Fallback to regular JSON.stringify for other types
|
|
339
|
+
return JSON.stringify(obj);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Safely deserializes JSON data with enhanced error handling
|
|
344
|
+
*/
|
|
345
|
+
private static safeDeserialize<T>(data: string): T {
|
|
346
|
+
try {
|
|
347
|
+
if (!data || data.trim().length === 0) {
|
|
348
|
+
throw new DyFM_Error({
|
|
349
|
+
...this.getDefaultErrorSettings('safeDeserialize'),
|
|
350
|
+
errorCode: 'DyFM-CRY-DES-EMPTY',
|
|
351
|
+
message: 'Cannot deserialize empty data. The decrypted data appears to be empty or invalid.'
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
//let parsed = JSON.parse(data);
|
|
356
|
+
let parsed = DyFM_Object.failableSafeParseJSON(data);
|
|
357
|
+
|
|
358
|
+
// Handle double-stringified JSON (or more levels of stringification)
|
|
359
|
+
let maxAttempts = 3; // Prevent infinite loops
|
|
360
|
+
while (typeof parsed === 'string' && maxAttempts > 0) {
|
|
361
|
+
try {
|
|
362
|
+
//const nextParsed = JSON.parse(parsed);
|
|
363
|
+
const nextParsed = DyFM_Object.failableSafeParseJSON(parsed);
|
|
364
|
+
// Only continue if parsing actually changed the result
|
|
365
|
+
if (nextParsed !== parsed) {
|
|
366
|
+
parsed = nextParsed;
|
|
367
|
+
maxAttempts--;
|
|
368
|
+
} else {
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
} catch {
|
|
372
|
+
// If parse fails, return current state
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Handle primitive values
|
|
378
|
+
/* if (typeof parsed === 'string' || typeof parsed === 'number' || typeof parsed === 'boolean') {
|
|
379
|
+
return parsed as T;
|
|
380
|
+
} */
|
|
381
|
+
|
|
382
|
+
return parsed as T;
|
|
383
|
+
} catch (error) {
|
|
384
|
+
if (error instanceof DyFM_Error) {
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
throw new DyFM_Error({
|
|
389
|
+
...this.getDefaultErrorSettings('safeDeserialize', error),
|
|
390
|
+
errorCode: 'DyFM-CRY-DES',
|
|
391
|
+
message: 'Failed to deserialize data. The decrypted data may be corrupted or in an unexpected format.'
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Encrypts data using AES-256-CBC with deterministic IV and salt
|
|
398
|
+
*
|
|
399
|
+
* @important DETERMINISTIC BEHAVIOR: This method will produce identical encrypted
|
|
400
|
+
* output for identical input parameters across different systems, Node.js versions,
|
|
401
|
+
* and multiple function calls. The same data + key combination will ALWAYS generate
|
|
402
|
+
* the same encrypted string.
|
|
403
|
+
*
|
|
404
|
+
* @param data The data to encrypt
|
|
405
|
+
* @param key The encryption key
|
|
406
|
+
* @param config Optional configuration
|
|
407
|
+
* @returns URL-safe encrypted string that is identical across systems for same input
|
|
408
|
+
* @throws {DyFM_Error} if encryption fails
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* // These will produce identical results on any system:
|
|
412
|
+
* const result1 = DyFM_Crypto.encrypt({id: 1}, "mykey");
|
|
413
|
+
* const result2 = DyFM_Crypto.encrypt({id: 1}, "mykey");
|
|
414
|
+
* console.log(result1 === result2); // Always true
|
|
415
|
+
*/
|
|
416
|
+
static encrypt<T>(data: T, key: string, config?: CryptoConfig): string {
|
|
417
|
+
try {
|
|
418
|
+
this.validateInput(data, key, 'encrypt');
|
|
419
|
+
const finalConfig = { ...this.DEFAULT_CONFIG, ...config };
|
|
420
|
+
|
|
421
|
+
// Convert data to string
|
|
422
|
+
const dataStr = this.safeSerialize(data);
|
|
423
|
+
|
|
424
|
+
// Generate deterministic IV and salt based on data and key
|
|
425
|
+
const iv = this.generateIV(dataStr, key, finalConfig);
|
|
426
|
+
const salt = this.generateSalt(dataStr, key, finalConfig);
|
|
427
|
+
|
|
428
|
+
// Derive key using PBKDF2
|
|
429
|
+
const derivedKey = this.deriveKey(key, salt, finalConfig);
|
|
430
|
+
|
|
431
|
+
// Encrypt the data
|
|
432
|
+
const encrypted = CryptoJS.AES.encrypt(dataStr, derivedKey, {
|
|
433
|
+
iv: iv,
|
|
434
|
+
mode: CryptoJS.mode.CBC,
|
|
435
|
+
padding: CryptoJS.pad.Pkcs7
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Combine IV + Salt + Ciphertext (skip version for backward compatibility)
|
|
439
|
+
const combined = iv.concat(salt).concat(encrypted.ciphertext);
|
|
440
|
+
|
|
441
|
+
// Convert to URL-safe base64
|
|
442
|
+
return CryptoJS.enc.Base64.stringify(combined)
|
|
443
|
+
.replace(/\+/g, '-')
|
|
444
|
+
.replace(/\//g, '_')
|
|
445
|
+
.replace(/=+$/, '');
|
|
446
|
+
} catch (error) {
|
|
447
|
+
throw new DyFM_Error({
|
|
448
|
+
...this.getDefaultErrorSettings('encrypt', error),
|
|
449
|
+
errorCode: 'DyFM-CRY-ENC',
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Decrypts data that was encrypted using encrypt()
|
|
456
|
+
* @param encryptedData The encrypted data
|
|
457
|
+
* @param key The decryption key
|
|
458
|
+
* @param config Optional configuration
|
|
459
|
+
* @returns The decrypted data
|
|
460
|
+
* @throws {DyFM_Error} if decryption fails
|
|
461
|
+
*/
|
|
462
|
+
static decrypt<T>(encryptedData: string, key: string, config?: CryptoConfig): T {
|
|
463
|
+
try {
|
|
464
|
+
this.validateInput(encryptedData, key, 'decrypt');
|
|
465
|
+
const finalConfig = { ...this.DEFAULT_CONFIG, ...config };
|
|
466
|
+
|
|
467
|
+
// Convert from URL-safe base64
|
|
468
|
+
const base64 = encryptedData
|
|
469
|
+
.replace(/-/g, '+')
|
|
470
|
+
.replace(/_/g, '/');
|
|
471
|
+
|
|
472
|
+
// Parse the combined data
|
|
473
|
+
const combined = CryptoJS.enc.Base64.parse(base64);
|
|
474
|
+
|
|
475
|
+
// For now, skip version checking to maintain backward compatibility
|
|
476
|
+
// TODO: Implement proper version checking in future versions
|
|
477
|
+
|
|
478
|
+
// Validate minimum length (IV + Salt + minimum ciphertext)
|
|
479
|
+
const minLength = (finalConfig.ivLength + finalConfig.saltLength + 16) / 4; // 16 bytes minimum for ciphertext
|
|
480
|
+
if (combined.words.length < minLength) {
|
|
481
|
+
throw new DyFM_Error({
|
|
482
|
+
...this.getDefaultErrorSettings('decrypt'),
|
|
483
|
+
errorCode: 'DyFM-CRY-DATA-CORRUPTED',
|
|
484
|
+
message: `Encrypted data is corrupted or incomplete. Expected at least ${minLength * 4} bytes, but received ${combined.sigBytes} bytes.`
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Extract IV, salt, and ciphertext (skip version for now)
|
|
489
|
+
const ivStart = 0;
|
|
490
|
+
const saltStart = ivStart + finalConfig.ivLength / 4;
|
|
491
|
+
const cipherStart = saltStart + finalConfig.saltLength / 4;
|
|
492
|
+
|
|
493
|
+
const iv = CryptoJS.lib.WordArray.create(combined.words.slice(ivStart, saltStart));
|
|
494
|
+
const salt = CryptoJS.lib.WordArray.create(combined.words.slice(saltStart, cipherStart));
|
|
495
|
+
const ciphertext = CryptoJS.lib.WordArray.create(combined.words.slice(cipherStart));
|
|
496
|
+
|
|
497
|
+
// Derive key using PBKDF2
|
|
498
|
+
const derivedKey = this.deriveKey(key, salt, finalConfig);
|
|
499
|
+
|
|
500
|
+
// Decrypt the data
|
|
501
|
+
const decrypted = CryptoJS.AES.decrypt(
|
|
502
|
+
{ ciphertext: ciphertext },
|
|
503
|
+
derivedKey,
|
|
504
|
+
{
|
|
505
|
+
iv: iv,
|
|
506
|
+
mode: CryptoJS.mode.CBC,
|
|
507
|
+
padding: CryptoJS.pad.Pkcs7
|
|
508
|
+
}
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
// Parse JSON
|
|
512
|
+
const decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
|
|
513
|
+
|
|
514
|
+
// Check if decryption produced empty result
|
|
515
|
+
if (!decryptedStr || decryptedStr.trim().length === 0) {
|
|
516
|
+
throw new DyFM_Error({
|
|
517
|
+
...this.getDefaultErrorSettings('decrypt'),
|
|
518
|
+
errorCode: 'DyFM-CRY-DECRYPT-EMPTY',
|
|
519
|
+
message: 'Decryption failed - the result is empty. This usually means the encryption key is incorrect or the data is corrupted.'
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return this.safeDeserialize<T>(decryptedStr);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
// Check if it's already a DyFM_Error
|
|
526
|
+
if (error instanceof DyFM_Error) {
|
|
527
|
+
throw error;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Handle specific decryption errors
|
|
531
|
+
if (error instanceof Error) {
|
|
532
|
+
if (error.message.includes('Malformed UTF-8')) {
|
|
533
|
+
throw new DyFM_Error({
|
|
534
|
+
...this.getDefaultErrorSettings('decrypt', error),
|
|
535
|
+
errorCode: 'DyFM-CRY-DECRYPT-UTF8',
|
|
536
|
+
message: 'Decryption failed - invalid UTF-8 data. This usually means the encryption key is incorrect or the data is corrupted.'
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (error.message.includes('Invalid padding')) {
|
|
541
|
+
throw new DyFM_Error({
|
|
542
|
+
...this.getDefaultErrorSettings('decrypt', error),
|
|
543
|
+
errorCode: 'DyFM-CRY-DECRYPT-PADDING',
|
|
544
|
+
message: 'Decryption failed - invalid padding. This usually means the encryption key is incorrect or the data is corrupted.'
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
throw new DyFM_Error({
|
|
550
|
+
...this.getDefaultErrorSettings('decrypt', error),
|
|
551
|
+
errorCode: 'DyFM-CRY-DRY',
|
|
552
|
+
message: 'Decryption failed. Please verify the encryption key and ensure the encrypted data is valid.'
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Generates a secure random key with enhanced complexity
|
|
559
|
+
* @param length Length of the key in characters (default: 32)
|
|
560
|
+
* @param customChars Optional custom character set to use
|
|
561
|
+
* @returns A secure random key with mixed case letters, numbers, and special characters
|
|
562
|
+
*/
|
|
563
|
+
static generateKey(length: number = 32, customChars?: string): string {
|
|
564
|
+
// Use custom character set if provided, otherwise use simple safe characters
|
|
565
|
+
const chars = customChars || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
566
|
+
let complexKey = '';
|
|
567
|
+
|
|
568
|
+
// Generate random characters directly for the desired length
|
|
569
|
+
for (let i = 0; i < length; i++) {
|
|
570
|
+
// Generate random bytes for each character
|
|
571
|
+
const randomBytes = CryptoJS.lib.WordArray.random(1);
|
|
572
|
+
const randomValue = randomBytes.words[0];
|
|
573
|
+
const charIndex = Math.abs(randomValue) % chars.length;
|
|
574
|
+
complexKey += chars[charIndex];
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return complexKey;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Validates if a string is a valid encrypted data
|
|
582
|
+
* @param encryptedData The data to validate
|
|
583
|
+
* @returns true if the data appears to be valid encrypted data
|
|
584
|
+
*/
|
|
585
|
+
static isValidEncryptedData(encryptedData: string): boolean {
|
|
586
|
+
if (!encryptedData || typeof encryptedData !== 'string') {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
return /^[A-Za-z0-9\-_]+$/.test(encryptedData);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Analyzes encrypted data to help with debugging
|
|
594
|
+
* @param encryptedData The encrypted data to analyze
|
|
595
|
+
* @returns Analysis information about the encrypted data
|
|
596
|
+
*/
|
|
597
|
+
static analyzeEncryptedData(encryptedData: string): {
|
|
598
|
+
isValid: boolean;
|
|
599
|
+
version?: string;
|
|
600
|
+
dataLength: number;
|
|
601
|
+
hasValidFormat: boolean;
|
|
602
|
+
error?: string;
|
|
603
|
+
} {
|
|
604
|
+
try {
|
|
605
|
+
if (!this.isValidEncryptedData(encryptedData)) {
|
|
606
|
+
return {
|
|
607
|
+
isValid: false,
|
|
608
|
+
dataLength: encryptedData?.length || 0,
|
|
609
|
+
hasValidFormat: false,
|
|
610
|
+
error: 'Invalid format - not URL-safe base64'
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Convert from URL-safe base64
|
|
615
|
+
const base64 = encryptedData
|
|
616
|
+
.replace(/-/g, '+')
|
|
617
|
+
.replace(/_/g, '/');
|
|
618
|
+
|
|
619
|
+
// Parse the combined data
|
|
620
|
+
const combined = CryptoJS.enc.Base64.parse(base64);
|
|
621
|
+
|
|
622
|
+
// For now, just check if the data has minimum required length
|
|
623
|
+
const minLength = (16 + 16 + 16) / 4; // IV + Salt + minimum ciphertext
|
|
624
|
+
|
|
625
|
+
if (combined.words.length < minLength) {
|
|
626
|
+
return {
|
|
627
|
+
isValid: false,
|
|
628
|
+
dataLength: combined.sigBytes,
|
|
629
|
+
hasValidFormat: false,
|
|
630
|
+
error: 'Data too short to contain valid encrypted data'
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
isValid: true,
|
|
636
|
+
version: 'legacy',
|
|
637
|
+
dataLength: combined.sigBytes,
|
|
638
|
+
hasValidFormat: true,
|
|
639
|
+
error: undefined
|
|
640
|
+
};
|
|
641
|
+
} catch (error) {
|
|
642
|
+
return {
|
|
643
|
+
isValid: false,
|
|
644
|
+
dataLength: encryptedData?.length || 0,
|
|
645
|
+
hasValidFormat: false,
|
|
646
|
+
error: `Analysis failed: ${error}`
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Clears the key derivation cache
|
|
653
|
+
* Useful for testing and memory management
|
|
654
|
+
*/
|
|
655
|
+
static clearKeyCache(): void {
|
|
656
|
+
this.keyCache.clear();
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Gets cache statistics for debugging and monitoring
|
|
661
|
+
* @returns Cache statistics including size and hit ratio
|
|
662
|
+
*/
|
|
663
|
+
static getCacheStats(): {
|
|
664
|
+
size: number;
|
|
665
|
+
maxSize: number;
|
|
666
|
+
utilizationPercent: number;
|
|
667
|
+
} {
|
|
668
|
+
return {
|
|
669
|
+
size: this.keyCache.size,
|
|
670
|
+
maxSize: this.MAX_CACHE_SIZE,
|
|
671
|
+
utilizationPercent: Math.round((this.keyCache.size / this.MAX_CACHE_SIZE) * 100)
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Gets default error settings with enhanced debugging information
|
|
677
|
+
*/
|
|
678
|
+
private static getDefaultErrorSettings(operation: string, error?: any): DyFM_Error_Settings {
|
|
679
|
+
const baseSettings = {
|
|
680
|
+
status: (error as DyFM_Error)?.___status ?? (error as any)?.status ?? 401,
|
|
681
|
+
message: `Crypto operation "${operation}" failed.`,
|
|
682
|
+
error: error,
|
|
683
|
+
errorCode: 'DyFM-CRY-ERR'
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
// Add debugging information for common failure scenarios
|
|
687
|
+
if (operation === 'decrypt') {
|
|
688
|
+
baseSettings.message += '\nThis usually indicates: ' +
|
|
689
|
+
'\n 1) Wrong encryption key, ' +
|
|
690
|
+
'\n 2) Corrupted encrypted data, ' +
|
|
691
|
+
'\n 3) Version incompatibility, ' +
|
|
692
|
+
'\n 4) Data was encrypted with different parameters.';
|
|
693
|
+
} else if (operation === 'encrypt') {
|
|
694
|
+
baseSettings.message += '\nThis usually indicates: ' +
|
|
695
|
+
'\n 1) Invalid input data, ' +
|
|
696
|
+
'\n 2) Invalid encryption key, ' +
|
|
697
|
+
'\n 3) Serialization failure.';
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return baseSettings;
|
|
701
|
+
}
|
|
702
|
+
}
|