mcard-js 1.0.0

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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +117 -0
  3. package/dist/__mocks__/better-sqlite3.js +20 -0
  4. package/dist/__mocks__/better-sqlite3.js.map +1 -0
  5. package/dist/config/config_constants.js +188 -0
  6. package/dist/config/config_constants.js.map +1 -0
  7. package/dist/config/env_parameters.js +62 -0
  8. package/dist/config/env_parameters.js.map +1 -0
  9. package/dist/content/model/content_type_detector.js +89 -0
  10. package/dist/content/model/content_type_detector.js.map +1 -0
  11. package/dist/core/card-collection.js +279 -0
  12. package/dist/core/card-collection.js.map +1 -0
  13. package/dist/core/event-producer.js +132 -0
  14. package/dist/core/event-producer.js.map +1 -0
  15. package/dist/core/g_time.js +201 -0
  16. package/dist/core/g_time.js.map +1 -0
  17. package/dist/core/hash/enums.js +19 -0
  18. package/dist/core/hash/enums.js.map +1 -0
  19. package/dist/core/hash/validator.js +260 -0
  20. package/dist/core/hash/validator.js.map +1 -0
  21. package/dist/core/mcard.js +205 -0
  22. package/dist/core/mcard.js.map +1 -0
  23. package/dist/engine/sqlite_engine.js +723 -0
  24. package/dist/engine/sqlite_engine.js.map +1 -0
  25. package/dist/index.js +10 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/middleware/mcardPersistenceMiddleware.js +45 -0
  28. package/dist/middleware/mcardPersistenceMiddleware.js.map +1 -0
  29. package/dist/models/database_schemas.js +31 -0
  30. package/dist/models/database_schemas.js.map +1 -0
  31. package/dist/services/logger.js +80 -0
  32. package/dist/services/logger.js.map +1 -0
  33. package/dist/services/mcardStorageService.js +36 -0
  34. package/dist/services/mcardStorageService.js.map +1 -0
  35. package/dist/utils/actionHelpers.js +25 -0
  36. package/dist/utils/actionHelpers.js.map +1 -0
  37. package/dist/utils/bufferContentHelper.js +393 -0
  38. package/dist/utils/bufferContentHelper.js.map +1 -0
  39. package/dist/utils/bufferPolyfill.js +198 -0
  40. package/dist/utils/bufferPolyfill.js.map +1 -0
  41. package/dist/utils/content-detection.js +74 -0
  42. package/dist/utils/content-detection.js.map +1 -0
  43. package/dist/utils/content-utils.js +269 -0
  44. package/dist/utils/content-utils.js.map +1 -0
  45. package/dist/utils/content_type_detector copy.js +480 -0
  46. package/dist/utils/content_type_detector copy.js.map +1 -0
  47. package/dist/utils/content_type_detector.js +480 -0
  48. package/dist/utils/content_type_detector.js.map +1 -0
  49. package/dist/utils/cryptoPolyfill.js +166 -0
  50. package/dist/utils/cryptoPolyfill.js.map +1 -0
  51. package/dist/utils/dotenv-browser.js +35 -0
  52. package/dist/utils/dotenv-browser.js.map +1 -0
  53. package/dist/utils/environmentDetector.js +93 -0
  54. package/dist/utils/environmentDetector.js.map +1 -0
  55. package/dist/utils/logWriter.js +27 -0
  56. package/dist/utils/logWriter.js.map +1 -0
  57. package/dist/utils/serviceWorkerManager.js +118 -0
  58. package/dist/utils/serviceWorkerManager.js.map +1 -0
  59. package/dist/utils/test-content-detection.js +79 -0
  60. package/dist/utils/test-content-detection.js.map +1 -0
  61. package/dist/utils/test-detection-fix.js +121 -0
  62. package/dist/utils/test-detection-fix.js.map +1 -0
  63. package/dist/utils/test-format-conversion.js +170 -0
  64. package/dist/utils/test-format-conversion.js.map +1 -0
  65. package/dist/utils/test-mov-viewer.js +57 -0
  66. package/dist/utils/test-mov-viewer.js.map +1 -0
  67. package/dist/utils/testDetection.js +21 -0
  68. package/dist/utils/testDetection.js.map +1 -0
  69. package/dist/utils/textEncoderPolyfill.js +87 -0
  70. package/dist/utils/textEncoderPolyfill.js.map +1 -0
  71. package/package.json +74 -0
  72. package/src/__mocks__/better-sqlite3.js +14 -0
  73. package/src/config/config_constants.js +227 -0
  74. package/src/config/env_parameters.js +69 -0
  75. package/src/content/model/content_type_detector.js +87 -0
  76. package/src/core/card-collection.js +300 -0
  77. package/src/core/event-producer.js +160 -0
  78. package/src/core/g_time.js +215 -0
  79. package/src/core/hash/enums.js +13 -0
  80. package/src/core/hash/validator.js +271 -0
  81. package/src/core/mcard.js +203 -0
  82. package/src/engine/sqlite_engine.js +755 -0
  83. package/src/index.js +10 -0
  84. package/src/middleware/mcardPersistenceMiddleware.js +45 -0
  85. package/src/models/database_schemas.js +26 -0
  86. package/src/services/logger.js +74 -0
  87. package/src/services/mcardStorageService.js +34 -0
  88. package/src/utils/actionHelpers.js +13 -0
  89. package/src/utils/bufferContentHelper.js +436 -0
  90. package/src/utils/bufferPolyfill.js +202 -0
  91. package/src/utils/cn.ts +6 -0
  92. package/src/utils/content-detection.js +66 -0
  93. package/src/utils/content-utils.js +250 -0
  94. package/src/utils/content_type_detector copy.js +501 -0
  95. package/src/utils/content_type_detector.js +501 -0
  96. package/src/utils/cryptoPolyfill.js +180 -0
  97. package/src/utils/dateUtils.ts +18 -0
  98. package/src/utils/dbInitializer.ts +27 -0
  99. package/src/utils/dotenv-browser.js +29 -0
  100. package/src/utils/environmentDetector.js +92 -0
  101. package/src/utils/logWriter.js +20 -0
  102. package/src/utils/serviceWorkerManager.js +122 -0
  103. package/src/utils/stateWatcher.ts +78 -0
  104. package/src/utils/storeAdapter copy.ts +157 -0
  105. package/src/utils/storeAdapter.ts +157 -0
  106. package/src/utils/test-content-detection.js +71 -0
  107. package/src/utils/test-detection-fix.js +136 -0
  108. package/src/utils/test-format-conversion.js +165 -0
  109. package/src/utils/test-mov-viewer.js +59 -0
  110. package/src/utils/testDetection.js +16 -0
  111. package/src/utils/textEncoderPolyfill.js +88 -0
@@ -0,0 +1,160 @@
1
+ import { HashAlgorithm as HashAlgorithmEnum } from './hash/enums.js';
2
+ import HashValidator from './hash/validator.js';
3
+ import { GTime as GTimeUtil } from './g_time.js';
4
+ import { HASH_ALGORITHM_HIERARCHY as ALGORITHM_HIERARCHY } from '../config/config_constants.js';
5
+
6
+ // Use global Buffer if available, otherwise use the polyfill
7
+ const Buffer = global.Buffer || require('buffer').Buffer;
8
+
9
+ // Destructure the enum values
10
+ const {
11
+ MD5,
12
+ SHA1,
13
+ SHA224,
14
+ SHA256,
15
+ SHA384,
16
+ SHA512,
17
+ DEFAULT
18
+ } = HashAlgorithmEnum;
19
+
20
+ // Event type constants
21
+ const TYPE = 'type';
22
+ const HASH = 'hash';
23
+ const CONTENT_TYPE = 'content_type';
24
+ const TIMESTAMP = 'timestamp';
25
+ const EXISTING_CARD_HASH = 'existing_card_hash';
26
+ const NEW_CARD_HASH = 'new_card_hash';
27
+ const FIRST_G_TIME = 'first_g_time';
28
+ const CONTENT_SIZE = 'content_size';
29
+ const COLLISION_TIME = 'collision_time';
30
+ const UPGRADED_FUNCTION = 'upgraded_function';
31
+ const UPGRADED_HASH = 'upgraded_hash';
32
+ const DUPLICATE_TIME = 'duplicate_time';
33
+ const DUPLICATE_EVENT_TYPE = 'duplicate';
34
+ const COLLISION_EVENT_TYPE = 'collision';
35
+
36
+ // Predefined hash function progression order
37
+ const HASH_FUNCTION_ORDER = ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'];
38
+
39
+ // Create a new HashValidator instance for use
40
+ let hashValidator;
41
+
42
+ // Initialize hashValidator in a try-catch to handle potential initialization errors
43
+ try {
44
+ hashValidator = new HashValidator(Buffer.from(''), 'sha256');
45
+ } catch (error) {
46
+ console.warn('Failed to initialize hashValidator:', error);
47
+ // Create a mock hashValidator if initialization fails
48
+ hashValidator = {
49
+ validate: () => Promise.resolve(true),
50
+ computeHash: () => 'mocked-hash'
51
+ };
52
+ }
53
+
54
+ // Explicitly define HASH_ALGORITHM_HIERARCHY
55
+ const HASH_ALGORITHM_HIERARCHY = {
56
+ [MD5]: [SHA1],
57
+ [SHA1]: [SHA224],
58
+ [SHA224]: [SHA256],
59
+ [SHA256]: [SHA384],
60
+ [SHA384]: [SHA512],
61
+ [SHA512]: []
62
+ };
63
+
64
+ // Replace existing nextHashFunction with method from instance
65
+ function nextHashFunction(currentHashFunction) {
66
+ const algMap = {
67
+ 'md5': MD5,
68
+ 'sha1': SHA1,
69
+ 'sha224': SHA224,
70
+ 'sha256': SHA256,
71
+ 'sha384': SHA384,
72
+ 'sha512': SHA512
73
+ };
74
+
75
+ const currFunc = algMap[currentHashFunction] || currentHashFunction;
76
+
77
+ const hashFunctions = Object.values(HashAlgorithmEnum).filter(
78
+ func => func !== currFunc && typeof func === 'string'
79
+ );
80
+
81
+ const strongerFunctions = hashFunctions.filter(func => {
82
+ const currentStrongerFuncs = HASH_ALGORITHM_HIERARCHY[currFunc] || [];
83
+ return currentStrongerFuncs.includes(func);
84
+ });
85
+
86
+ return strongerFunctions.length > 0
87
+ ? strongerFunctions[0]
88
+ : currFunc;
89
+ }
90
+
91
+ // Generate a duplication event for the given card
92
+ // @param {Object} card - The card being duplicated
93
+ // @returns {string} JSON-stringified duplication event
94
+ function generateDuplicationEvent(card) {
95
+ const event = {
96
+ [TYPE]: DUPLICATE_EVENT_TYPE,
97
+ [HASH]: card.hash,
98
+ [CONTENT_TYPE]: card.contentType,
99
+ [TIMESTAMP]: GTimeUtil.stampNow(card.hashFunction || HashAlgorithmEnum.DEFAULT),
100
+ [DUPLICATE_TIME]: card.g_time
101
+ };
102
+ return JSON.stringify(event);
103
+ }
104
+
105
+ // Generate a collision event for the given event data
106
+ // @param {Object} newCard - The new card
107
+ // @param {Object} existingCard - The existing card
108
+ // @returns {string} JSON-stringified collision event
109
+ function generateCollisionEvent(newCard, existingCard = null) {
110
+ const event = {
111
+ type: 'collision',
112
+ original_hash: existingCard ? existingCard.hash : null,
113
+ new_hash: newCard.hash,
114
+ timestamp: GTimeUtil.stampNow(
115
+ newCard.hashFunction ||
116
+ existingCard?.hashFunction ||
117
+ HashAlgorithmEnum.DEFAULT
118
+ ),
119
+ content_size: typeof newCard.content === 'string' ?
120
+ Buffer.from(newCard.content).length :
121
+ newCard.content.length
122
+ };
123
+
124
+ if (existingCard) {
125
+ const upgradedFunction = nextHashFunction(existingCard.hashFunction);
126
+ event.upgraded_function = upgradedFunction;
127
+ event.upgraded_hash = existingCard.hash;
128
+ event.hash_algorithm = upgradedFunction;
129
+ }
130
+
131
+ return JSON.stringify(event);
132
+ }
133
+
134
+ export {
135
+ MD5,
136
+ SHA1,
137
+ SHA224,
138
+ SHA256,
139
+ SHA384,
140
+ SHA512,
141
+ TYPE,
142
+ HASH,
143
+ CONTENT_TYPE,
144
+ TIMESTAMP,
145
+ EXISTING_CARD_HASH,
146
+ NEW_CARD_HASH,
147
+ FIRST_G_TIME,
148
+ CONTENT_SIZE,
149
+ COLLISION_TIME,
150
+ UPGRADED_FUNCTION,
151
+ UPGRADED_HASH,
152
+ DUPLICATE_TIME,
153
+ DUPLICATE_EVENT_TYPE,
154
+ COLLISION_EVENT_TYPE,
155
+ HASH_FUNCTION_ORDER,
156
+ HASH_ALGORITHM_HIERARCHY,
157
+ nextHashFunction,
158
+ generateDuplicationEvent,
159
+ generateCollisionEvent
160
+ };
@@ -0,0 +1,215 @@
1
+ import { HashAlgorithm } from '../config/config_constants.js';
2
+
3
+ export class GTime {
4
+ /**
5
+ * Get current timestamp in ISO format with hash function and region code
6
+ * @param {string|HashAlgorithm} hashFunction - Hash function to use
7
+ * @returns {string} Formatted timestamp string
8
+ */
9
+ static stampNow(hashFunction) {
10
+ // Use default hash algorithm if no function is provided
11
+ if (hashFunction === null || hashFunction === undefined) {
12
+ hashFunction = HashAlgorithm.DEFAULT;
13
+ }
14
+
15
+ // Convert string to HashAlgorithm if needed
16
+ let normalizedHashFunction = hashFunction;
17
+ if (typeof hashFunction === 'string') {
18
+ const trimmedFunc = hashFunction.toLowerCase().trim();
19
+ if (!Object.values(HashAlgorithm).filter(func => typeof func === 'string').map(func => func.toLowerCase()).includes(trimmedFunc)) {
20
+ throw new Error(`Invalid hash function: ${hashFunction}`);
21
+ }
22
+ try {
23
+ normalizedHashFunction = HashAlgorithm[trimmedFunc.toUpperCase()];
24
+ } catch (error) {
25
+ throw new Error(`Invalid hash function: ${hashFunction}`);
26
+ }
27
+ }
28
+
29
+ const now = new Date();
30
+ const year = now.getFullYear();
31
+ const month = String(now.getMonth() + 1).padStart(2, '0');
32
+ const day = String(now.getDate()).padStart(2, '0');
33
+ const hours = String(now.getHours()).padStart(2, '0');
34
+ const minutes = String(now.getMinutes()).padStart(2, '0');
35
+ const seconds = String(now.getSeconds()).padStart(2, '0');
36
+
37
+ // Ensure 6 decimal places for microseconds
38
+ const microseconds = String(Math.floor(performance.now() % 1 * 1000000)).padStart(6, '0').slice(0, 6);
39
+
40
+ const timestamp = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${microseconds}Z`;
41
+ const regionCode = Intl.DateTimeFormat().resolvedOptions().timeZone.split('/')[0].toUpperCase();
42
+
43
+ return `${normalizedHashFunction}|${timestamp}|${regionCode}`;
44
+ }
45
+
46
+ /**
47
+ * Alias for stampNow to maintain backwards compatibility
48
+ * @param {string|HashAlgorithm} hashFunction - Hash function to use
49
+ * @returns {string} Formatted timestamp string
50
+ */
51
+ static stamp_now(hashFunction) {
52
+ return this.stampNow(hashFunction);
53
+ }
54
+
55
+ /**
56
+ * Get the hash function from the formatted string
57
+ * @param {string} stringValue - Formatted timestamp string
58
+ * @returns {string} Hash function
59
+ */
60
+ static get_hash_function(stringValue) {
61
+ // Validate input is a non-empty string
62
+ if (!stringValue || typeof stringValue !== 'string' || stringValue.trim() === '') {
63
+ throw new Error('Invalid hash function: Empty or non-string input');
64
+ }
65
+
66
+ // Validate exact number of parts
67
+ const parts = stringValue.split('|');
68
+ if (parts.length !== 3) {
69
+ throw new Error('Invalid hash function: Incorrect number of components');
70
+ }
71
+
72
+ // Validate each part is non-empty and has no extra whitespace
73
+ const [hashFunctionStr, timestamp, regionCode] = parts;
74
+ if (!hashFunctionStr || !timestamp || !regionCode) {
75
+ throw new Error('Invalid hash function: Missing components');
76
+ }
77
+
78
+ // Validate hash function format (must be exactly lowercase, no extra whitespace)
79
+ const trimmedHashFunc = hashFunctionStr.trim();
80
+ const validLowercaseHashes = Object.values(HashAlgorithm).filter(func => typeof func === 'string').map(func => func.toLowerCase());
81
+
82
+ if (!validLowercaseHashes.includes(trimmedHashFunc) ||
83
+ trimmedHashFunc !== hashFunctionStr) {
84
+ throw new Error(`Invalid hash function: ${hashFunctionStr}`);
85
+ }
86
+
87
+ // Validate timestamp format (strict ISO 8601 with exactly 6 decimal places)
88
+ const timestampRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/;
89
+ const trimmedTimestamp = timestamp.trim();
90
+
91
+ if (!timestampRegex.test(trimmedTimestamp) ||
92
+ trimmedTimestamp !== timestamp) {
93
+ throw new Error('Invalid hash function: Incorrect timestamp format');
94
+ }
95
+
96
+ // Validate region code format (must be all uppercase letters, no extra whitespace)
97
+ const trimmedRegionCode = regionCode.trim();
98
+ if (!/^[A-Z]+$/.test(trimmedRegionCode) ||
99
+ trimmedRegionCode !== regionCode) {
100
+ throw new Error('Invalid hash function: Incorrect region code format');
101
+ }
102
+
103
+ try {
104
+ return HashAlgorithm[trimmedHashFunc.toUpperCase()];
105
+ } catch (error) {
106
+ throw new Error(`Invalid hash function: ${trimmedHashFunc}`);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Get the timestamp from the formatted string
112
+ * @param {string} stringValue - Formatted timestamp string
113
+ * @returns {string} Timestamp in ISO format
114
+ */
115
+ static get_timestamp(stringValue) {
116
+ return stringValue.split('|')[1];
117
+ }
118
+
119
+ /**
120
+ * Get the region code from the formatted string
121
+ * @param {string} stringValue - Formatted timestamp string
122
+ * @returns {string} Region code
123
+ */
124
+ static get_region_code(stringValue) {
125
+ return stringValue.split('|')[2];
126
+ }
127
+
128
+ /**
129
+ * Check if the provided hash function is valid
130
+ * @param {string|HashAlgorithm} hashFunction - Hash function to validate
131
+ * @returns {boolean} Whether the hash function is valid
132
+ */
133
+ static is_valid_hash_function(hashFunction) {
134
+ // Strict validation for null or undefined
135
+ if (hashFunction === null || hashFunction === undefined) {
136
+ return false;
137
+ }
138
+
139
+ // Reject any non-string, non-object inputs
140
+ if (typeof hashFunction !== 'string' &&
141
+ typeof hashFunction !== 'object' &&
142
+ typeof hashFunction !== 'boolean') {
143
+ return false;
144
+ }
145
+
146
+ // Reject non-string objects
147
+ if (typeof hashFunction === 'object' &&
148
+ !(hashFunction instanceof String)) {
149
+ return false;
150
+ }
151
+
152
+ // Reject empty strings or whitespace
153
+ if (typeof hashFunction === 'string' &&
154
+ (hashFunction.trim() === '' || hashFunction !== hashFunction.trim())) {
155
+ return false;
156
+ }
157
+
158
+ // For string inputs, be extremely strict
159
+ if (typeof hashFunction === 'string' || hashFunction instanceof String) {
160
+ // Convert to string
161
+ const strFunc = String(hashFunction);
162
+
163
+ // All valid hash functions, lowercase
164
+ const validLowercaseHashes = Object.values(HashAlgorithm).filter(func => typeof func === 'string').map(func => func.toLowerCase());
165
+
166
+ // Reject any input that doesn't match exactly
167
+ const isValid = validLowercaseHashes.includes(strFunc) &&
168
+ strFunc === strFunc.toLowerCase() &&
169
+ strFunc.trim() === strFunc;
170
+
171
+ // Extra checks to reject inputs like 'md 5', 'md5 hash', 'SHA-256', 'MD5', etc.
172
+ if (!isValid || strFunc.includes(' ')) {
173
+ return false;
174
+ }
175
+
176
+ return true;
177
+ }
178
+
179
+ // If we reach here, it means the input is a valid HashAlgorithm value
180
+ return Object.values(HashAlgorithm).includes(hashFunction);
181
+ }
182
+
183
+ /**
184
+ * Check if the provided region code is valid
185
+ * @param {string} regionCode - Region code to validate
186
+ * @returns {boolean} Whether the region code is valid
187
+ */
188
+ static is_valid_region_code(regionCode) {
189
+ if (regionCode === null || regionCode === undefined) {
190
+ return false;
191
+ }
192
+
193
+ return typeof regionCode === 'string' &&
194
+ regionCode.trim().length > 0 &&
195
+ regionCode.trim() === regionCode.trim().toUpperCase() &&
196
+ regionCode.trim() === regionCode; // No extra whitespace
197
+ }
198
+
199
+ /**
200
+ * Check if the provided timestamp is in ISO format
201
+ * @param {string} timestamp - Timestamp to validate
202
+ * @returns {boolean} Whether the timestamp is in ISO format
203
+ */
204
+ /**
205
+ * Validate ISO 8601 format with 6 decimal places
206
+ * @param {string} timestamp
207
+ * @returns {boolean}
208
+ */
209
+ static is_iso_format(timestamp) {
210
+ const isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/;
211
+ return isoRegex.test(timestamp);
212
+ }
213
+ }
214
+
215
+ export default GTime;
@@ -0,0 +1,13 @@
1
+ // Hash algorithm enums
2
+ export const HashAlgorithm = {
3
+ MD5: 'md5',
4
+ SHA1: 'sha1',
5
+ SHA224: 'sha224',
6
+ SHA256: 'sha256',
7
+ SHA384: 'sha384',
8
+ SHA512: 'sha512'
9
+ };
10
+
11
+ export default {
12
+ HashAlgorithm
13
+ };
@@ -0,0 +1,271 @@
1
+ // Replace direct crypto import with environment-aware implementation
2
+ import { encodeText } from '../../utils/textEncoderPolyfill.js';
3
+ import { SafeBuffer } from '../../utils/bufferPolyfill.js';
4
+ import { createHash } from '../../utils/cryptoPolyfill.js';
5
+ import {
6
+ HashAlgorithm,
7
+ HASH_ALGORITHM_HIERARCHY,
8
+ VALID_HASH_FUNCTIONS
9
+ } from '../../config/config_constants.js';
10
+
11
+ // Check if we're in a browser environment
12
+ const isBrowser = typeof window !== 'undefined';
13
+
14
+ export default class HashValidator {
15
+ /**
16
+ * Constructor for HashValidator
17
+ */
18
+ constructor(content, hashAlgorithm = HashAlgorithm.DEFAULT) {
19
+ // Convert content to Buffer/Uint8Array if it's a string
20
+ this.content = SafeBuffer.isBuffer(content)
21
+ ? content
22
+ : encodeText(content);
23
+
24
+ this.hashAlgorithm = this.normalizeHashAlgorithm(hashAlgorithm);
25
+
26
+ // In browser environments, we can't synchronously compute crypto hashes
27
+ if (isBrowser) {
28
+ this.hashValue = "computing...";
29
+ this._computeHashAsync().then(hash => {
30
+ this.hashValue = hash;
31
+ });
32
+ } else {
33
+ this.hashValue = this.computeHash();
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Async computation of hash for browser environments
39
+ * @private
40
+ */
41
+ async _computeHashAsync() {
42
+ const hash = createHash(this.hashAlgorithm);
43
+ hash.update(this.content);
44
+ return await hash.digest('hex');
45
+ }
46
+
47
+ /**
48
+ * Normalizes the hash algorithm input
49
+ * @param {string|Object} hashAlgorithm - The hash algorithm to normalize
50
+ * @returns {string} Normalized hash algorithm
51
+ */
52
+ normalizeHashAlgorithm(hashAlgorithm) {
53
+ // Handle undefined or null input
54
+ if (hashAlgorithm === undefined || hashAlgorithm === null) {
55
+ return HashAlgorithm.DEFAULT || 'sha256';
56
+ }
57
+
58
+ // If input is an object, try to extract the type
59
+ if (typeof hashAlgorithm === 'object') {
60
+ hashAlgorithm = hashAlgorithm.type || hashAlgorithm.value || HashAlgorithm.DEFAULT || 'sha256';
61
+ }
62
+
63
+ // Convert to lowercase string and remove dashes for consistency
64
+ const normalizedAlgo = String(hashAlgorithm).toLowerCase().replace(/-/g, '');
65
+
66
+ // Validate the hash algorithm
67
+ if (!HashValidator.isValidHashFunction(normalizedAlgo)) {
68
+ console.warn(`Invalid hash algorithm: ${normalizedAlgo}, using default instead`);
69
+ return HashAlgorithm.DEFAULT || 'sha256';
70
+ }
71
+
72
+ return normalizedAlgo;
73
+ }
74
+
75
+ /**
76
+ * Validates a hash function
77
+ * @param {string} hashFunction - Hash function to validate
78
+ * @returns {boolean} Whether the hash function is valid
79
+ */
80
+ static isValidHashFunction(hashFunction) {
81
+ if (!hashFunction) return false;
82
+
83
+ const normalizedFunc = String(hashFunction).toLowerCase().trim();
84
+
85
+ // Accept both formats: with dashes (Web Crypto format) and without dashes (Node.js format)
86
+ const validAlgorithms = [
87
+ // Node.js format
88
+ 'md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512',
89
+ // Web Crypto format
90
+ 'sha-1', 'sha-256', 'sha-384', 'sha-512'
91
+ ];
92
+
93
+ return validAlgorithms.includes(normalizedFunc) ||
94
+ validAlgorithms.includes(normalizedFunc.replace(/-/g, ''));
95
+ }
96
+
97
+ /**
98
+ * Compute hash from content
99
+ * @returns {string|Promise<string>} Computed hash or promise to hash
100
+ */
101
+ computeHash() {
102
+ try {
103
+ const hash = createHash(this.hashAlgorithm);
104
+ hash.update(this.content);
105
+ const result = hash.digest('hex');
106
+
107
+ // Handle the case where result is a Promise
108
+ if (result instanceof Promise) {
109
+ return "computing...";
110
+ }
111
+
112
+ return result;
113
+ } catch (e) {
114
+ console.error('Error computing hash:', e);
115
+ return '';
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Getter for hash value
121
+ * @returns {string} Hash value
122
+ */
123
+ getHashValue() {
124
+ return this.hashValue;
125
+ }
126
+
127
+ /**
128
+ * Getter for hash algorithm
129
+ * @returns {string} Hash algorithm
130
+ */
131
+ getHashAlgorithm() {
132
+ return this.hashAlgorithm;
133
+ }
134
+
135
+ /**
136
+ * Static method to compute hash
137
+ * @param {string|Buffer} content - Content to hash
138
+ * @param {string} hashAlgorithm - Algorithm to use
139
+ * @returns {string|Promise<string>} Computed hash or Promise of hash in browser
140
+ */
141
+ static computeHash(content, hashAlgorithm = HashAlgorithm.DEFAULT) {
142
+ const buffer = SafeBuffer.isBuffer(content)
143
+ ? content
144
+ : encodeText(content);
145
+
146
+ const hash = createHash(hashAlgorithm);
147
+ hash.update(buffer);
148
+ return hash.digest('hex');
149
+ }
150
+
151
+ /**
152
+ * Validate hash against an expected hash
153
+ * @param {string} [expectedHash] - Expected hash value
154
+ * @returns {boolean|Promise<boolean>} True if hash matches, false otherwise
155
+ */
156
+ validate(expectedHash) {
157
+ if (!expectedHash) return false;
158
+
159
+ if (isBrowser) {
160
+ // In browser, we need to handle async validation
161
+ return this._computeHashAsync().then(computedHash => {
162
+ return computedHash === expectedHash;
163
+ });
164
+ }
165
+
166
+ return this.hashValue === expectedHash;
167
+ }
168
+
169
+ /**
170
+ * Return string representation
171
+ * @returns {string} String representation
172
+ */
173
+ toString() {
174
+ return `HashValidator(alg=${this.hashAlgorithm}, hash=${this.hashValue})`;
175
+ }
176
+
177
+ /**
178
+ * Gets the strength order of hash algorithms
179
+ * @returns {string[]} Ordered list of hash algorithms by strength
180
+ */
181
+ getHashAlgorithmStrengthOrder() {
182
+ return [
183
+ HashAlgorithm.MD5,
184
+ HashAlgorithm.SHA1,
185
+ HashAlgorithm.SHA224,
186
+ HashAlgorithm.SHA256,
187
+ HashAlgorithm.SHA384,
188
+ HashAlgorithm.SHA512
189
+ ];
190
+ }
191
+
192
+ /**
193
+ * Gets the strength index of a hash algorithm
194
+ * @param {string} algorithm - Hash algorithm
195
+ * @returns {number} Strength index
196
+ */
197
+ getHashAlgorithmStrength(algorithm) {
198
+ return this.getHashAlgorithmStrengthOrder().indexOf(algorithm);
199
+ }
200
+
201
+ /**
202
+ * Checks if one hash algorithm is stronger than another
203
+ * @param {string} current - Current hash algorithm
204
+ * @param {string} upgrade - Potential upgrade hash algorithm
205
+ * @returns {boolean} Whether the upgrade is stronger
206
+ */
207
+ isStrongerHashAlgorithm(current, upgrade) {
208
+ return this.getHashAlgorithmStrength(upgrade) > this.getHashAlgorithmStrength(current);
209
+ }
210
+
211
+ /**
212
+ * Determines the next hash algorithm in the upgrade path
213
+ * @param {string|Object} currentHashFunction - Current hash function
214
+ * @returns {string} Next hash algorithm
215
+ */
216
+ nextHashFunction(currentHashFunction) {
217
+ const strengthOrder = this.getHashAlgorithmStrengthOrder();
218
+
219
+ // Handle undefined or null input
220
+ if (currentHashFunction === undefined || currentHashFunction === null) {
221
+ return HashAlgorithm.MD5;
222
+ }
223
+
224
+ // Extract hash function value if it's an object
225
+ const currentHash = typeof currentHashFunction === 'object'
226
+ ? currentHashFunction.value || currentHashFunction.type
227
+ : currentHashFunction;
228
+
229
+ // Normalize to lowercase
230
+ const normalizedHash = (currentHash || '').toLowerCase();
231
+
232
+ // Find current index
233
+ const currentIndex = strengthOrder.indexOf(normalizedHash);
234
+
235
+ // Special case for SHA512 - wrap around to SHA1
236
+ if (normalizedHash === HashAlgorithm.SHA512) {
237
+ return HashAlgorithm.SHA1;
238
+ }
239
+
240
+ // If not found or last in order, return default (first hash function)
241
+ if (currentIndex === -1 || currentIndex === strengthOrder.length - 1) {
242
+ return HashAlgorithm[strengthOrder[0].toUpperCase()];
243
+ }
244
+
245
+ // Return the next hash function in the order as an enum
246
+ return HashAlgorithm[strengthOrder[currentIndex + 1].toUpperCase()];
247
+ }
248
+
249
+ /**
250
+ * Static method to get supported hash algorithms
251
+ * @returns {string[]} List of supported hash algorithms
252
+ */
253
+ static getSupportedAlgorithms() {
254
+ return [
255
+ HashAlgorithm.MD5,
256
+ HashAlgorithm.SHA1,
257
+ HashAlgorithm.SHA256,
258
+ HashAlgorithm.SHA512
259
+ ];
260
+ }
261
+
262
+ static compute_hash(content, hashAlgorithm = HashAlgorithm.DEFAULT) {
263
+ return HashValidator.computeHash(content, hashAlgorithm);
264
+ }
265
+
266
+ static HashAlgorithm = HashAlgorithm;
267
+ static HASH_ALGORITHM_HIERARCHY = HASH_ALGORITHM_HIERARCHY;
268
+ static VALID_HASH_FUNCTIONS = VALID_HASH_FUNCTIONS;
269
+ }
270
+
271
+ export { HashAlgorithm, HASH_ALGORITHM_HIERARCHY, VALID_HASH_FUNCTIONS };