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.
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/__mocks__/better-sqlite3.js +20 -0
- package/dist/__mocks__/better-sqlite3.js.map +1 -0
- package/dist/config/config_constants.js +188 -0
- package/dist/config/config_constants.js.map +1 -0
- package/dist/config/env_parameters.js +62 -0
- package/dist/config/env_parameters.js.map +1 -0
- package/dist/content/model/content_type_detector.js +89 -0
- package/dist/content/model/content_type_detector.js.map +1 -0
- package/dist/core/card-collection.js +279 -0
- package/dist/core/card-collection.js.map +1 -0
- package/dist/core/event-producer.js +132 -0
- package/dist/core/event-producer.js.map +1 -0
- package/dist/core/g_time.js +201 -0
- package/dist/core/g_time.js.map +1 -0
- package/dist/core/hash/enums.js +19 -0
- package/dist/core/hash/enums.js.map +1 -0
- package/dist/core/hash/validator.js +260 -0
- package/dist/core/hash/validator.js.map +1 -0
- package/dist/core/mcard.js +205 -0
- package/dist/core/mcard.js.map +1 -0
- package/dist/engine/sqlite_engine.js +723 -0
- package/dist/engine/sqlite_engine.js.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/mcardPersistenceMiddleware.js +45 -0
- package/dist/middleware/mcardPersistenceMiddleware.js.map +1 -0
- package/dist/models/database_schemas.js +31 -0
- package/dist/models/database_schemas.js.map +1 -0
- package/dist/services/logger.js +80 -0
- package/dist/services/logger.js.map +1 -0
- package/dist/services/mcardStorageService.js +36 -0
- package/dist/services/mcardStorageService.js.map +1 -0
- package/dist/utils/actionHelpers.js +25 -0
- package/dist/utils/actionHelpers.js.map +1 -0
- package/dist/utils/bufferContentHelper.js +393 -0
- package/dist/utils/bufferContentHelper.js.map +1 -0
- package/dist/utils/bufferPolyfill.js +198 -0
- package/dist/utils/bufferPolyfill.js.map +1 -0
- package/dist/utils/content-detection.js +74 -0
- package/dist/utils/content-detection.js.map +1 -0
- package/dist/utils/content-utils.js +269 -0
- package/dist/utils/content-utils.js.map +1 -0
- package/dist/utils/content_type_detector copy.js +480 -0
- package/dist/utils/content_type_detector copy.js.map +1 -0
- package/dist/utils/content_type_detector.js +480 -0
- package/dist/utils/content_type_detector.js.map +1 -0
- package/dist/utils/cryptoPolyfill.js +166 -0
- package/dist/utils/cryptoPolyfill.js.map +1 -0
- package/dist/utils/dotenv-browser.js +35 -0
- package/dist/utils/dotenv-browser.js.map +1 -0
- package/dist/utils/environmentDetector.js +93 -0
- package/dist/utils/environmentDetector.js.map +1 -0
- package/dist/utils/logWriter.js +27 -0
- package/dist/utils/logWriter.js.map +1 -0
- package/dist/utils/serviceWorkerManager.js +118 -0
- package/dist/utils/serviceWorkerManager.js.map +1 -0
- package/dist/utils/test-content-detection.js +79 -0
- package/dist/utils/test-content-detection.js.map +1 -0
- package/dist/utils/test-detection-fix.js +121 -0
- package/dist/utils/test-detection-fix.js.map +1 -0
- package/dist/utils/test-format-conversion.js +170 -0
- package/dist/utils/test-format-conversion.js.map +1 -0
- package/dist/utils/test-mov-viewer.js +57 -0
- package/dist/utils/test-mov-viewer.js.map +1 -0
- package/dist/utils/testDetection.js +21 -0
- package/dist/utils/testDetection.js.map +1 -0
- package/dist/utils/textEncoderPolyfill.js +87 -0
- package/dist/utils/textEncoderPolyfill.js.map +1 -0
- package/package.json +74 -0
- package/src/__mocks__/better-sqlite3.js +14 -0
- package/src/config/config_constants.js +227 -0
- package/src/config/env_parameters.js +69 -0
- package/src/content/model/content_type_detector.js +87 -0
- package/src/core/card-collection.js +300 -0
- package/src/core/event-producer.js +160 -0
- package/src/core/g_time.js +215 -0
- package/src/core/hash/enums.js +13 -0
- package/src/core/hash/validator.js +271 -0
- package/src/core/mcard.js +203 -0
- package/src/engine/sqlite_engine.js +755 -0
- package/src/index.js +10 -0
- package/src/middleware/mcardPersistenceMiddleware.js +45 -0
- package/src/models/database_schemas.js +26 -0
- package/src/services/logger.js +74 -0
- package/src/services/mcardStorageService.js +34 -0
- package/src/utils/actionHelpers.js +13 -0
- package/src/utils/bufferContentHelper.js +436 -0
- package/src/utils/bufferPolyfill.js +202 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/content-detection.js +66 -0
- package/src/utils/content-utils.js +250 -0
- package/src/utils/content_type_detector copy.js +501 -0
- package/src/utils/content_type_detector.js +501 -0
- package/src/utils/cryptoPolyfill.js +180 -0
- package/src/utils/dateUtils.ts +18 -0
- package/src/utils/dbInitializer.ts +27 -0
- package/src/utils/dotenv-browser.js +29 -0
- package/src/utils/environmentDetector.js +92 -0
- package/src/utils/logWriter.js +20 -0
- package/src/utils/serviceWorkerManager.js +122 -0
- package/src/utils/stateWatcher.ts +78 -0
- package/src/utils/storeAdapter copy.ts +157 -0
- package/src/utils/storeAdapter.ts +157 -0
- package/src/utils/test-content-detection.js +71 -0
- package/src/utils/test-detection-fix.js +136 -0
- package/src/utils/test-format-conversion.js +165 -0
- package/src/utils/test-mov-viewer.js +59 -0
- package/src/utils/testDetection.js +16 -0
- package/src/utils/textEncoderPolyfill.js +88 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Middleware for Redux persistence of MCard data
|
|
2
|
+
const createMcardPersistenceMiddleware = (storageService) => (store) => (next) => async (action) => {
|
|
3
|
+
// Call the next middleware in the chain first
|
|
4
|
+
const result = next(action);
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
// Handle persistence based on action type
|
|
8
|
+
if (action && action.type) {
|
|
9
|
+
// Check if this is a persistable action (e.g., todo actions)
|
|
10
|
+
const isPersistable = [
|
|
11
|
+
'todo/addTask',
|
|
12
|
+
'ADD_CARD',
|
|
13
|
+
'test/action' // For testing purposes
|
|
14
|
+
].some(type => action.type === type);
|
|
15
|
+
|
|
16
|
+
if (isPersistable && storageService) {
|
|
17
|
+
// Get the current state from the store
|
|
18
|
+
const state = typeof store.getState === 'function' ? store.getState() : store.state || {};
|
|
19
|
+
|
|
20
|
+
// Create a copy of the action with the current state snapshot
|
|
21
|
+
const actionWithState = {
|
|
22
|
+
...action,
|
|
23
|
+
meta: {
|
|
24
|
+
...(action.meta || {}),
|
|
25
|
+
stateSnapshot: state
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Persist the action using the storage service
|
|
30
|
+
await storageService.createAndStoreMCard(actionWithState);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Error in mcardPersistenceMiddleware:', error);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Create a named export for backward compatibility
|
|
41
|
+
const mcardPersistenceMiddleware = createMcardPersistenceMiddleware;
|
|
42
|
+
|
|
43
|
+
// Export both the named and default exports for flexibility
|
|
44
|
+
export { mcardPersistenceMiddleware };
|
|
45
|
+
export default createMcardPersistenceMiddleware;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// SQLite database schema definitions for mcards
|
|
2
|
+
|
|
3
|
+
export const MCARD_TABLE_SCHEMA = `
|
|
4
|
+
CREATE TABLE IF NOT EXISTS card (
|
|
5
|
+
hash TEXT PRIMARY KEY,
|
|
6
|
+
g_time TEXT NOT NULL,
|
|
7
|
+
content BLOB NOT NULL
|
|
8
|
+
)
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
export const TRIGGERS = {
|
|
12
|
+
ensureUnique: `
|
|
13
|
+
CREATE TRIGGER IF NOT EXISTS ensure_unique_hash
|
|
14
|
+
BEFORE INSERT ON card
|
|
15
|
+
FOR EACH ROW
|
|
16
|
+
BEGIN
|
|
17
|
+
SELECT RAISE(ABORT, 'Card with this hash already exists')
|
|
18
|
+
WHERE EXISTS (SELECT 1 FROM card WHERE hash = NEW.hash);
|
|
19
|
+
END
|
|
20
|
+
`
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
MCARD_TABLE_SCHEMA,
|
|
25
|
+
TRIGGERS
|
|
26
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple logging service with configurable log levels
|
|
3
|
+
*/
|
|
4
|
+
class Logger {
|
|
5
|
+
/**
|
|
6
|
+
* Create a new logger instance
|
|
7
|
+
* @param {string} [level='info'] - Logging level
|
|
8
|
+
*/
|
|
9
|
+
constructor(level = 'info') {
|
|
10
|
+
this.level = level.toLowerCase();
|
|
11
|
+
this.levels = ['error', 'warn', 'info', 'debug'];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if a log level is enabled
|
|
16
|
+
* @param {string} level - Log level to check
|
|
17
|
+
* @returns {boolean} Whether the log level is enabled
|
|
18
|
+
*/
|
|
19
|
+
isLevelEnabled(level) {
|
|
20
|
+
const currentLevelIndex = this.levels.indexOf(this.level);
|
|
21
|
+
const checkLevelIndex = this.levels.indexOf(level);
|
|
22
|
+
return checkLevelIndex <= currentLevelIndex;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Log an error message
|
|
27
|
+
* @param {string} message - Message to log
|
|
28
|
+
* @param {Object} [metadata] - Optional metadata
|
|
29
|
+
*/
|
|
30
|
+
error(message, metadata = {}) {
|
|
31
|
+
if (this.isLevelEnabled('error')) {
|
|
32
|
+
console.error(`[ERROR] ${message}`, metadata);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Log a warning message
|
|
38
|
+
* @param {string} message - Message to log
|
|
39
|
+
* @param {Object} [metadata] - Optional metadata
|
|
40
|
+
*/
|
|
41
|
+
warn(message, metadata = {}) {
|
|
42
|
+
if (this.isLevelEnabled('warn')) {
|
|
43
|
+
console.warn(`[WARN] ${message}`, metadata);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Log an info message
|
|
49
|
+
* @param {string} message - Message to log
|
|
50
|
+
* @param {Object} [metadata] - Optional metadata
|
|
51
|
+
*/
|
|
52
|
+
info(message, metadata = {}) {
|
|
53
|
+
if (this.isLevelEnabled('info')) {
|
|
54
|
+
console.log(`[INFO] ${message}`, metadata);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Log a debug message
|
|
60
|
+
* @param {string} message - Message to log
|
|
61
|
+
* @param {Object} [metadata] - Optional metadata
|
|
62
|
+
*/
|
|
63
|
+
debug(message, metadata = {}) {
|
|
64
|
+
if (this.isLevelEnabled('debug')) {
|
|
65
|
+
console.debug(`[DEBUG] ${message}`, metadata);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Create a default logger instance using environment variable
|
|
71
|
+
const logger = new Logger(process.env.LOG_LEVEL || 'info');
|
|
72
|
+
|
|
73
|
+
export default logger;
|
|
74
|
+
export { Logger };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Service for MCard storage operations
|
|
2
|
+
class McardStorageService {
|
|
3
|
+
constructor(engine) {
|
|
4
|
+
this.engine = engine;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async saveMcards(mcards) {
|
|
8
|
+
if (!this.engine) {
|
|
9
|
+
throw new Error('Storage engine not initialized');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
return await this.engine.saveMcards(mcards);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error('Error saving mcards:', error);
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async loadMcards() {
|
|
21
|
+
if (!this.engine) {
|
|
22
|
+
throw new Error('Storage engine not initialized');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
return await this.engine.loadMcards();
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Error loading mcards:', error);
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default McardStorageService;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const formatTimestamp = (timestamp) => {
|
|
2
|
+
const date = new Date(timestamp);
|
|
3
|
+
return date.toLocaleTimeString();
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export const getActionText = (action) => {
|
|
7
|
+
switch (action.type) {
|
|
8
|
+
case 'ADD': return `Added item: "${action.content}"`;
|
|
9
|
+
case 'REMOVE': return `Removed item: "${action.content}"`;
|
|
10
|
+
case 'SELECT': return `Selected item: "${action.content}"`;
|
|
11
|
+
default: return `Unknown action on: "${action.content}"`;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for handling Buffer content stored in Node.js Buffer JSON format
|
|
3
|
+
* Specifically handles the {"type":"Buffer","data":[...]} format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Converts a Buffer JSON object or string to a regular string
|
|
8
|
+
* Works with all these formats:
|
|
9
|
+
* - {"type":"Buffer","data":[...]} (object)
|
|
10
|
+
* - '{"type":"Buffer","data":[...]}' (string)
|
|
11
|
+
* - Regular string
|
|
12
|
+
* - Regular objects
|
|
13
|
+
*
|
|
14
|
+
* @param {any} content - The content to process
|
|
15
|
+
* @returns {string|null} Decoded string or null if not convertible
|
|
16
|
+
*/
|
|
17
|
+
export const convertBufferToString = (content) => {
|
|
18
|
+
if (!content) return null;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Case 1: Direct Buffer JSON object
|
|
22
|
+
if (typeof content === 'object' && content !== null &&
|
|
23
|
+
content.type === 'Buffer' && Array.isArray(content.data)) {
|
|
24
|
+
const array = new Uint8Array(content.data);
|
|
25
|
+
return new TextDecoder().decode(array);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Case 2: JSON string containing serialized Buffer
|
|
29
|
+
if (typeof content === 'string') {
|
|
30
|
+
if (content.includes('"type":"Buffer"') && content.includes('"data":[')) {
|
|
31
|
+
try {
|
|
32
|
+
const bufferObj = JSON.parse(content);
|
|
33
|
+
if (bufferObj && bufferObj.type === 'Buffer' && Array.isArray(bufferObj.data)) {
|
|
34
|
+
const array = new Uint8Array(bufferObj.data);
|
|
35
|
+
return new TextDecoder().decode(array);
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {
|
|
38
|
+
// If parsing fails, treat as regular string
|
|
39
|
+
console.log("Failed to parse as Buffer JSON:", e);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Regular string, just return it
|
|
44
|
+
return content;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Case 3: Other object, convert to JSON string
|
|
48
|
+
if (typeof content === 'object' && content !== null) {
|
|
49
|
+
return JSON.stringify(content, null, 2);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Case 4: Primitives, convert to string
|
|
53
|
+
return String(content);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error("Error converting buffer to string:", e);
|
|
56
|
+
return typeof content === 'string' ? content : null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get raw binary data from a Buffer-like object
|
|
62
|
+
* @param {any} content - The content to extract binary data from
|
|
63
|
+
* @returns {Uint8Array|null} Binary data as Uint8Array or null
|
|
64
|
+
*/
|
|
65
|
+
export const extractBinaryContent = (content) => {
|
|
66
|
+
if (!content) return null;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// Case 1: Direct Buffer JSON object
|
|
70
|
+
if (typeof content === 'object' && content !== null &&
|
|
71
|
+
content.type === 'Buffer' && Array.isArray(content.data)) {
|
|
72
|
+
return new Uint8Array(content.data);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Case 2: JSON string containing serialized Buffer
|
|
76
|
+
if (typeof content === 'string') {
|
|
77
|
+
if (content.includes('"type":"Buffer"') && content.includes('"data":[')) {
|
|
78
|
+
try {
|
|
79
|
+
const bufferObj = JSON.parse(content);
|
|
80
|
+
if (bufferObj && bufferObj.type === 'Buffer' && Array.isArray(bufferObj.data)) {
|
|
81
|
+
return new Uint8Array(bufferObj.data);
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
// If parsing fails, can't extract binary
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Regular string as bytes
|
|
90
|
+
return new TextEncoder().encode(content);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return null;
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error("Error extracting binary content:", e);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Detects the content type based on actual content analysis
|
|
102
|
+
*
|
|
103
|
+
* @param {any} content - The content to analyze
|
|
104
|
+
* @returns {string|null} Detected content type or null if undetectable
|
|
105
|
+
*/
|
|
106
|
+
export const detectContentType = (content) => {
|
|
107
|
+
// Early handling for direct Buffer format containing CSV data
|
|
108
|
+
if (typeof content === 'object' && content !== null &&
|
|
109
|
+
content.type === 'Buffer' && Array.isArray(content.data)) {
|
|
110
|
+
|
|
111
|
+
// Get the decoded string for analysis
|
|
112
|
+
const decodedStr = convertBufferToString(content);
|
|
113
|
+
|
|
114
|
+
// Debug the decoded content
|
|
115
|
+
console.log("Decoded Buffer content:", decodedStr ? decodedStr.substring(0, 100) : "null");
|
|
116
|
+
|
|
117
|
+
// Simple CSV detection based on commas and line structure
|
|
118
|
+
if (decodedStr &&
|
|
119
|
+
decodedStr.includes(',') &&
|
|
120
|
+
decodedStr.includes('\n') &&
|
|
121
|
+
decodedStr.split('\n').length > 1 &&
|
|
122
|
+
decodedStr.split('\n')[0].includes(',')) {
|
|
123
|
+
|
|
124
|
+
console.log("Detected CSV pattern in buffer data");
|
|
125
|
+
return 'csv';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for TSV
|
|
129
|
+
if (decodedStr &&
|
|
130
|
+
decodedStr.includes('\t') &&
|
|
131
|
+
decodedStr.includes('\n') &&
|
|
132
|
+
decodedStr.split('\n').length > 1 &&
|
|
133
|
+
decodedStr.split('\n')[0].includes('\t')) {
|
|
134
|
+
|
|
135
|
+
console.log("Detected TSV pattern in buffer data");
|
|
136
|
+
return 'tsv';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check for JSON
|
|
140
|
+
if (decodedStr &&
|
|
141
|
+
(decodedStr.trim().startsWith('{') || decodedStr.trim().startsWith('['))) {
|
|
142
|
+
try {
|
|
143
|
+
JSON.parse(decodedStr);
|
|
144
|
+
console.log("Detected JSON pattern in buffer data");
|
|
145
|
+
return 'json';
|
|
146
|
+
} catch (e) {
|
|
147
|
+
// Not valid JSON
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Early detection for Buffer JSON format by examining binary patterns
|
|
153
|
+
const binaryData = extractBinaryContent(content);
|
|
154
|
+
if (binaryData) {
|
|
155
|
+
// Check for file signatures (magic numbers)
|
|
156
|
+
const fileSignature = detectFileSignature(binaryData);
|
|
157
|
+
if (fileSignature) {
|
|
158
|
+
console.log("Detected file type from signature:", fileSignature);
|
|
159
|
+
return fileSignature;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const textContent = convertBufferToString(content);
|
|
164
|
+
if (!textContent) return 'bin';
|
|
165
|
+
|
|
166
|
+
// Size check to avoid processing very large content
|
|
167
|
+
if (textContent.length > 1000000) {
|
|
168
|
+
console.log("Content too large for detailed analysis, using basic checks");
|
|
169
|
+
return detectBasicContentType(textContent.slice(0, 1000));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Debug logging
|
|
173
|
+
console.log("Analyzing content for type detection:", textContent.slice(0, 100) + "...");
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
// Check if it's valid JSON first
|
|
177
|
+
try {
|
|
178
|
+
JSON.parse(textContent);
|
|
179
|
+
if (textContent.trim().startsWith('{') || textContent.trim().startsWith('[')) {
|
|
180
|
+
return 'json';
|
|
181
|
+
}
|
|
182
|
+
} catch (e) {
|
|
183
|
+
// Not JSON, continue with other checks
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check for XML
|
|
187
|
+
if ((textContent.trim().startsWith('<?xml') || textContent.trim().startsWith('<')) &&
|
|
188
|
+
textContent.includes('</') && textContent.includes('>')) {
|
|
189
|
+
return 'xml';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Check for HTML
|
|
193
|
+
if (textContent.includes('<html') || textContent.includes('<body') ||
|
|
194
|
+
textContent.includes('<head') || textContent.includes('<!DOCTYPE html')) {
|
|
195
|
+
return 'html';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check for CSV format - improved detection
|
|
199
|
+
if (textContent.includes(',') &&
|
|
200
|
+
textContent.includes('\n') &&
|
|
201
|
+
textContent.split('\n').filter(line => line.trim()).length > 1) {
|
|
202
|
+
|
|
203
|
+
// Count lines with commas
|
|
204
|
+
const lines = textContent.split('\n').filter(line => line.trim());
|
|
205
|
+
const linesWithCommas = lines.filter(line => line.includes(','));
|
|
206
|
+
|
|
207
|
+
// If more than 50% of lines have commas, likely a CSV
|
|
208
|
+
if (linesWithCommas.length / lines.length > 0.5) {
|
|
209
|
+
console.log("Detected CSV pattern");
|
|
210
|
+
return 'csv';
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check for TSV format
|
|
215
|
+
if (textContent.includes('\t') &&
|
|
216
|
+
textContent.includes('\n') &&
|
|
217
|
+
textContent.split('\n').filter(line => line.trim()).length > 1) {
|
|
218
|
+
|
|
219
|
+
// Count lines with tabs
|
|
220
|
+
const lines = textContent.split('\n').filter(line => line.trim());
|
|
221
|
+
const linesWithTabs = lines.filter(line => line.includes('\t'));
|
|
222
|
+
|
|
223
|
+
// If more than 50% of lines have tabs, likely a TSV
|
|
224
|
+
if (linesWithTabs.length / lines.length > 0.5) {
|
|
225
|
+
return 'tsv';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check for SQL
|
|
230
|
+
if ((textContent.includes('SELECT ') || textContent.includes('INSERT INTO ') ||
|
|
231
|
+
textContent.includes('CREATE TABLE ') || textContent.includes('UPDATE ')) &&
|
|
232
|
+
/;/.test(textContent)) {
|
|
233
|
+
return 'sql';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Check for Markdown
|
|
237
|
+
if ((textContent.includes('# ') || textContent.includes('## ') ||
|
|
238
|
+
textContent.includes('```') || textContent.includes('**')) &&
|
|
239
|
+
!textContent.includes('<html') && !textContent.includes('<body')) {
|
|
240
|
+
return 'md';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check for CSS
|
|
244
|
+
if ((textContent.includes('{') && textContent.includes('}') &&
|
|
245
|
+
textContent.includes(':') && textContent.includes(';')) &&
|
|
246
|
+
/[.#]?[a-zA-Z][a-zA-Z0-9_-]*\s*\{/.test(textContent)) {
|
|
247
|
+
return 'css';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Check for JavaScript
|
|
251
|
+
if ((textContent.includes('function') || textContent.includes('=>') ||
|
|
252
|
+
textContent.includes('var ') || textContent.includes('let ') ||
|
|
253
|
+
textContent.includes('const ') || textContent.includes('import ')) &&
|
|
254
|
+
/[;{}()]/.test(textContent)) {
|
|
255
|
+
return 'js';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check for YAML
|
|
259
|
+
if ((/^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*:/.test(textContent) ||
|
|
260
|
+
/^\s*-\s+[a-zA-Z_][a-zA-Z0-9_]*\s*:/.test(textContent)) &&
|
|
261
|
+
!textContent.includes('{') && !textContent.includes('}')) {
|
|
262
|
+
return 'yaml';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Default to text if content is mostly text characters
|
|
266
|
+
const textChars = textContent.replace(/[\n\r\t ]/g, '').length;
|
|
267
|
+
const nonTextChars = /[^\x20-\x7E\n\r\t]/.test(textContent);
|
|
268
|
+
|
|
269
|
+
if (textChars > 0 && !nonTextChars) {
|
|
270
|
+
return 'txt';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Last resort: binary
|
|
274
|
+
return 'bin';
|
|
275
|
+
} catch (e) {
|
|
276
|
+
console.error("Error during content type detection:", e);
|
|
277
|
+
return 'bin';
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Detects file signatures (magic numbers) from binary data
|
|
283
|
+
* @param {Uint8Array} data - Binary data to analyze
|
|
284
|
+
* @returns {string|null} File type or null if unrecognized
|
|
285
|
+
*/
|
|
286
|
+
const detectFileSignature = (data) => {
|
|
287
|
+
if (!data || data.length < 4) return null;
|
|
288
|
+
|
|
289
|
+
// Common file signatures (magic numbers)
|
|
290
|
+
// PNG: 89 50 4E 47 0D 0A 1A 0A
|
|
291
|
+
if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4E && data[3] === 0x47) {
|
|
292
|
+
return 'png';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// JPEG: FF D8 FF
|
|
296
|
+
if (data[0] === 0xFF && data[1] === 0xD8 && data[2] === 0xFF) {
|
|
297
|
+
return 'jpg';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// GIF87a: 47 49 46 38 37 61
|
|
301
|
+
// GIF89a: 47 49 46 38 39 61
|
|
302
|
+
if (data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46 && data[3] === 0x38 &&
|
|
303
|
+
(data[4] === 0x37 || data[4] === 0x39) && data[5] === 0x61) {
|
|
304
|
+
return 'gif';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// PDF: 25 50 44 46
|
|
308
|
+
if (data[0] === 0x25 && data[1] === 0x50 && data[2] === 0x44 && data[3] === 0x46) {
|
|
309
|
+
return 'pdf';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ZIP: 50 4B 03 04
|
|
313
|
+
if (data[0] === 0x50 && data[1] === 0x4B && data[2] === 0x03 && data[3] === 0x04) {
|
|
314
|
+
return 'zip';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// check for BMP: 42 4D
|
|
318
|
+
if (data[0] === 0x42 && data[1] === 0x4D) {
|
|
319
|
+
return 'bmp';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Check for WEBP: 52 49 46 46 followed by WEBP
|
|
323
|
+
if (data[0] === 0x52 && data[1] === 0x49 && data[2] === 0x46 && data[3] === 0x46 &&
|
|
324
|
+
data.length > 11 && data[8] === 0x57 && data[9] === 0x45 && data[10] === 0x42 && data[11] === 0x50) {
|
|
325
|
+
return 'webp';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Check if it looks like a text file (mostly ASCII characters)
|
|
329
|
+
let textChars = 0;
|
|
330
|
+
let totalChars = Math.min(data.length, 100); // Check first 100 bytes
|
|
331
|
+
|
|
332
|
+
for (let i = 0; i < totalChars; i++) {
|
|
333
|
+
if ((data[i] >= 32 && data[i] <= 126) || data[i] === 9 || data[i] === 10 || data[i] === 13) {
|
|
334
|
+
textChars++;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (textChars / totalChars > 0.9) {
|
|
339
|
+
// This is likely a text file
|
|
340
|
+
return null; // Let the text content analysis take over
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return 'bin'; // Default to binary if no specific format recognized
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Performs basic content type detection for very large files
|
|
348
|
+
* Only looks at the first portion of the content
|
|
349
|
+
*/
|
|
350
|
+
const detectBasicContentType = (sample) => {
|
|
351
|
+
if (!sample) return 'bin';
|
|
352
|
+
|
|
353
|
+
// Quick check for common formats based on initial characters
|
|
354
|
+
if (sample.trim().startsWith('{') || sample.trim().startsWith('[')) {
|
|
355
|
+
try {
|
|
356
|
+
JSON.parse(sample);
|
|
357
|
+
return 'json';
|
|
358
|
+
} catch (e) {
|
|
359
|
+
// Not valid JSON
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (sample.trim().startsWith('<?xml') ||
|
|
364
|
+
(sample.trim().startsWith('<') && sample.includes('</') && sample.includes('>'))) {
|
|
365
|
+
return 'xml';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (sample.includes('<html') || sample.includes('<!DOCTYPE html')) {
|
|
369
|
+
return 'html';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Check if it's mostly text
|
|
373
|
+
const nonTextRatio = (sample.replace(/[\x20-\x7E\r\n\t]/g, '').length / sample.length);
|
|
374
|
+
|
|
375
|
+
if (nonTextRatio < 0.1) {
|
|
376
|
+
// Mostly text, try to detect format
|
|
377
|
+
if (sample.includes('# ') || sample.includes('## ') || sample.includes('```')) {
|
|
378
|
+
return 'md';
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (sample.includes(',') && sample.split(/\r?\n/).some(line => line.includes(','))) {
|
|
382
|
+
return 'csv';
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (sample.includes('\t') && sample.split(/\r?\n/).some(line => line.includes('\t'))) {
|
|
386
|
+
return 'tsv';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return 'txt';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return 'bin';
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Gets the MIME type from a simplified content type extension
|
|
397
|
+
*
|
|
398
|
+
* @param {string} simpleType - Simple content type like 'json', 'csv', etc.
|
|
399
|
+
* @returns {string} The corresponding MIME type
|
|
400
|
+
*/
|
|
401
|
+
export const getMimeType = (simpleType) => {
|
|
402
|
+
if (!simpleType) return 'application/octet-stream';
|
|
403
|
+
|
|
404
|
+
// Avoid generic "data" type
|
|
405
|
+
if (simpleType === 'data') {
|
|
406
|
+
return 'application/octet-stream';
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const mimeMap = {
|
|
410
|
+
'json': 'application/json',
|
|
411
|
+
'js': 'application/javascript',
|
|
412
|
+
'txt': 'text/plain',
|
|
413
|
+
'html': 'text/html',
|
|
414
|
+
'htm': 'text/html',
|
|
415
|
+
'css': 'text/css',
|
|
416
|
+
'svg': 'image/svg+xml',
|
|
417
|
+
'png': 'image/png',
|
|
418
|
+
'jpg': 'image/jpeg',
|
|
419
|
+
'jpeg': 'image/jpeg',
|
|
420
|
+
'gif': 'image/gif',
|
|
421
|
+
'pdf': 'application/pdf',
|
|
422
|
+
'csv': 'text/csv',
|
|
423
|
+
'tsv': 'text/tab-separated-values',
|
|
424
|
+
'xml': 'application/xml',
|
|
425
|
+
'md': 'text/markdown',
|
|
426
|
+
'yaml': 'application/yaml',
|
|
427
|
+
'yml': 'application/yaml',
|
|
428
|
+
'sql': 'application/sql',
|
|
429
|
+
'bin': 'application/octet-stream',
|
|
430
|
+
'zip': 'application/zip',
|
|
431
|
+
'bmp': 'image/bmp',
|
|
432
|
+
'webp': 'image/webp'
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
return mimeMap[simpleType] || `application/${simpleType}`;
|
|
436
|
+
};
|