fuse-core-express 1.0.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,128 @@
1
+ # FuseCore Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+ - **Redis TLS Support**: Added `enableTls` parameter to Redis socket configuration
7
+ - New `enableTls` option in Redis configuration for secure connections
8
+ - Default value is `false` for backward compatibility
9
+ - Supports both standard Redis connections and TLS-enabled connections
10
+ - Updated documentation and examples to include TLS configuration
11
+ - **Redis Configuration Cleanup**: Removed unsupported parameters from Redis configuration
12
+ - Removed `connectTimeout` and `commandTimeout` parameters (not in official docs)
13
+ - Kept only officially supported socket parameters: `tls`, `reconnectStrategy`
14
+ - Configuration now strictly follows Redis official documentation
15
+
16
+ ### Changed
17
+ - **Build Process**: Separated version bumping from crypto build process
18
+ - Version increment and key generation moved to `npm run version:bump`
19
+ - Crypto build now uses existing keys instead of generating new ones
20
+ - New keys and version changes are automatically committed to git
21
+ - Added git tag creation for version tracking
22
+
23
+ ### Added
24
+ - **Version & Keys Management**: New `build/version-keys.mjs` script for version and key management
25
+ - Automatic version increment with SemVer compliance
26
+ - Git integration for committing keys and version changes
27
+ - Git tag creation for release tracking
28
+ - Clean working directory validation
29
+ - **MIT License Compliance**: Private keys now included in published packages
30
+ - Private key automatically copied to `dist-encrypted` directory
31
+ - Package `files` field updated to include private key
32
+ - README updated with MIT compliance notices
33
+ - Users can now use the package without external key management
34
+ - **MIT License Documentation**: Added complete MIT license documentation
35
+ - Created `LICENSE` file with full MIT license text
36
+ - Added MIT license badge to README header
37
+ - Added complete MIT license text to README
38
+ - Updated package `files` field to include LICENSE file
39
+ - **Package Name Update**: Changed npm package name from `fuse-core` to `fusecore` to `fuse-core-express`
40
+ - Updated package.json name field
41
+ - Updated all README examples to use new package name
42
+ - Updated encrypted version documentation
43
+ - **Encrypted Version Path Fix**: Fixed module path resolution in encrypted version
44
+ - Fixed decrypt loader to correctly resolve `.fec` file paths
45
+ - Updated path resolution to include `cjs/` directory prefix
46
+ - Resolved ENOENT errors when loading encrypted modules
47
+ - **ESM/CJS Compatibility**: Enhanced encrypted version to support both module systems
48
+ - Added intelligent path resolution for ESM and CJS directories
49
+ - Prioritizes ESM modules when available (ESM-first approach)
50
+ - Falls back to CJS modules when ESM not available
51
+ - Maintains backward compatibility with existing installations
52
+ - **ESM Runtime Compatibility**: Fixed `require is not defined` error in ESM environments
53
+ - Implemented dual-mode module loading system for CommonJS and ESM
54
+ - Added async/await support for ESM module execution
55
+ - Updated decrypt loader to handle both runtime environments seamlessly
56
+ - Fixed module execution context for encrypted code in ESM mode
57
+ - **Global Singleton Pattern**: Fixed ESM module import inconsistency issues
58
+ - Implemented global singleton using Symbol.for() to ensure single instance across all imports
59
+ - Fixed issue where multiple imports of FuseCore created different instances
60
+ - Ensured consistent state across app.mjs and route1.mjs imports
61
+ - Added proper state management for initialization status across module boundaries
62
+ - **Proxy Architecture**: Redesigned encrypted version to proxy to original API
63
+ - Generated index.mjs now only handles decryption logic
64
+ - After decryption, all API calls are proxied to the original source code
65
+ - Maintains complete compatibility with original FuseCore API
66
+ - Simplified architecture: decrypt once, then use original functionality
67
+ - Eliminates need to reimplement API logic in encrypted version
68
+
69
+ ## [1.1.0] - 2025-07-31
70
+
71
+ ### Added
72
+ - **Redis Cache Support**: FuseCore now supports Redis as a cache backend
73
+ - Full Redis integration with connection management
74
+ - Support for Redis-specific operations: `exists`, `expire`, `ttl`, `keys`, `flushAll`
75
+ - Configurable connection options including timeout and client settings
76
+ - Automatic reconnection and error handling
77
+
78
+ ### Fixed
79
+ - **Redis Cache Type Preservation**: Fixed critical bug where JSON strings were incorrectly parsed as objects
80
+ - JSON strings now maintain their string type when stored and retrieved
81
+ - Objects are properly serialized/deserialized
82
+ - Backward compatibility with existing cached data
83
+
84
+ ### Removed
85
+ - **Nestia-specific UA Properties**: Removed Nestia-related properties from getUAInfo function
86
+ - Removed: `isNestiaServer`, `isNestiaClient`, `isNestiaAgentClient`, `nestiaClientId`
87
+ - Removed: `isNestiaBusClient`, `isNestiaAppClient`, `isNestiaLotteryClient`, `isNestiaMalaysiaLotteryClient`
88
+ - Cleaned up function implementation and documentation
89
+ - Updated README documentation to reflect changes
90
+ - Fixed code quality warnings (assignment in conditions, unused variables, logic errors)
91
+
92
+ ### Changed
93
+ - **Cache API**: All cache operations are now asynchronous (`async/await`)
94
+ - Memory cache methods updated to return Promises for consistency
95
+ - Tests updated to use async/await pattern
96
+
97
+ ### Configuration
98
+ New Redis cache configuration options:
99
+ ```javascript
100
+ {
101
+ cache: {
102
+ impl: 'redis', // 'memory' or 'redis'
103
+ redis: {
104
+ url: 'redis://localhost:6379',
105
+ enableTls: false, // Enable TLS/SSL connection
106
+ clientOptions: {}
107
+ }
108
+ }
109
+ };
110
+ ```
111
+
112
+ ### Migration Guide
113
+ - Update cache usage to use `await` for all cache operations
114
+ - No breaking changes for existing memory cache usage
115
+ - Redis cache requires `redis` npm package (automatically installed)
116
+
117
+ ## [1.0.0] - 2025-07-31
118
+
119
+ ### Added
120
+ - Initial release
121
+ - Migrated from nestia-web to FuseCore
122
+ - Core modules: Ajax, Cache (memory), Logger, Manifest, Monitor
123
+ - Complete test suite
124
+ - Documentation and examples
125
+
126
+ ### Removed
127
+ - Config module (deprecated)
128
+ - Internal network dependencies replaced with public APIs
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Ds.3783
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,588 @@
1
+ # FuseCore - Express.js Development Toolkit
2
+
3
+ > 🔐 **This package contains encrypted and obfuscated code**
4
+ >
5
+ > **Version:** 1.0.2
6
+ > **Protection:** JavaScript Obfuscation + AES-256-CBC + RSA-2048
7
+ > **MIT Compliant:** Private key included in package for open source compliance
8
+
9
+ ## ⚠️ Security Notice
10
+
11
+ This package contains encrypted code that is protected by multiple layers of security. The private key is included in the package to comply with MIT open source license requirements. The code is protected by:
12
+
13
+ - **JavaScript Obfuscation** - Code structure completely obfuscated
14
+ - **AES-256-CBC Encryption** - All source code encrypted
15
+ - **RSA-2048 Key Protection** - AES keys encrypted with RSA
16
+ - **Digital Signature** - Code integrity verification
17
+
18
+ ## Quick Start
19
+
20
+ ```javascript
21
+ import FuseCore from 'fuse-core-express';
22
+
23
+ // Initialize decryption with included private key (MIT compliant)
24
+ FuseCore.initDecryption('./private-1.0.2.pem');
25
+
26
+ // Then use normally
27
+ await FuseCore.init();
28
+ ```
29
+
30
+ ---
31
+
32
+
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
34
+
35
+ **FuseCore** - A comprehensive Express.js toolkit that provides essential web development utilities including caching, logging, monitoring, AJAX handling, and more. Built with MIT open source license.
36
+
37
+ ### init options
38
+
39
+ ```
40
+
41
+ {
42
+ common: {
43
+ expressApp: null,
44
+ listenHostname: '',
45
+ listenPort: 3000,
46
+ },
47
+ cache: {
48
+ impl: 'memory', // 'memory' or 'redis'
49
+ redis: {
50
+ url: 'redis://localhost:6379',
51
+ connectTimeout: 10000,
52
+ commandTimeout: 5000,
53
+ enableTls: false,
54
+ clientOptions: {}
55
+ }
56
+ },
57
+ log: {
58
+ path: '',
59
+ level: '',
60
+ extraZipStreams: [],
61
+ takeOverConsole: false,
62
+ extraOutputStreams: {
63
+ info: process.stdout,
64
+ error: process.stderr,
65
+ },
66
+ },
67
+ ajax: {
68
+ timeout: 10000,
69
+ slowThreshold: 3000,
70
+ defaultHeaders: {
71
+ "X-Requested-With": "FuseCore Web component V1.0"
72
+ },
73
+ agentOptions:{}
74
+ },
75
+ manifest: {
76
+ path: '',
77
+ name: '',
78
+ },
79
+
80
+ monitor: {
81
+ prefix: '',
82
+ suffix: '',
83
+ mem: true,
84
+ cpu: true,
85
+ req404: true,
86
+ req5xx: true,
87
+ },
88
+ isPrimaryProcess: cluster.isPrimary || cluster.isMaster
89
+ }
90
+
91
+ ```
92
+
93
+ * common.expressApp (object) optional,Express.app object.
94
+ * common.listenHostname (string) optional, hostname or ip http server listens on, used for config server callback.
95
+ * common.listenPort (number) optional,port number http server listens on, used for config server callback.
96
+ * cache.impl (string) optional, cache implementation, 'memory' or 'redis', default is memory.
97
+ * cache.redis.url (string) optional, Redis connection URL, default is 'redis://localhost:6379'.
98
+
99
+ * cache.redis.enableTls (boolean) optional, enable TLS/SSL for Redis connection, default is false.
100
+ * cache.redis.clientOptions (object) optional, additional Redis client options.
101
+ * log.path (string) optional, directory to save log files.
102
+ * log.level (string) optional, minimal level written to log file,default is info.
103
+ * log.extraZipStreams (array) optional, streams for access log.
104
+ * log.takeOverConsole (bool) optional, default false, indicate log module take over system console, thus all console message will be pipe to log file.
105
+ * log.extraOutputStreams (Object) optional, default null, extra log output stream, should be {info:[...Writable],error:[...Writable]}.
106
+ * ajax.timeout (number) optional, default timeout in millisecond, default is 10000.
107
+ * ajax.slowThreshold (number) optional, default slow log time limit in millisecond, all backend response time greater than limit will be logged to slow log file, default value is 50.
108
+ * ajax.defaultHeaders (object) optional, default headers send to backend, default value is {'x-requested-with':'FuseCore Web Server 1.0'}.
109
+ * ajax.agentOptions (object) optional, extra agent options will pass to http/https agent, also will applied to globalAgent.
110
+ * manifest.path (string) optional, directory contain manifest file.
111
+ * manifest.name (string) optional, manifest name to load.
112
+
113
+ * monitor.prefix (string) optional, prefix of all monitor keys.
114
+ * monitor.suffix (string) optional, suffix of all monitor keys.
115
+ * monitor.mem (bool) optional, auto monitor memory usage, default is false.
116
+ * monitor.cpu (bool) optional, auto monitor cpu usage, default is false.
117
+ * monitor.req404 (bool) optional, monitor 404 requests, default is false.
118
+ * monitor.req5xx (bool) optional, monitor 5xx requests, default is false.
119
+ * isPrimaryProcess (bool) optional, indicate current process is primary in cluster mode, monitor module will act differently when run as a worker process.
120
+
121
+
122
+
123
+ ## Monitor
124
+
125
+ ### Usage:
126
+
127
+ #### Init:
128
+ @deprecated
129
+
130
+ var FuseCore = require('fuse-core-express');
131
+ //this line must @ top ,before any routes or app filters.
132
+ app.use(FuseCore.requestFilter());
133
+
134
+ .....
135
+
136
+
137
+ var Monitor = FuseCore.monitor;
138
+ Monitor.init(configObj);
139
+
140
+ #### Monitor Config:
141
+ {
142
+ app: Express.app object (obj,required)
143
+ prefix: monitor default prefix (string,optional)
144
+ suffix: monitor default suffix (string,optional)
145
+ mem: monitor memory usage (boolean,optional)
146
+ cpu: monitor cpu usage (boolean,optional)
147
+ mon404: monitor 404 response (boolean,optional)
148
+ mon5xx: monitor 5xx response (boolean,optional)
149
+ monitorPath: the path for express which remove graph drawer used to get monitor indicators and values
150
+ }
151
+
152
+
153
+ #### Request filter
154
+ 1. Used to adept nestia ip rule,when using request.ip property.
155
+ 1. add req.realUrl property presents the url send to nginx.
156
+
157
+
158
+ #### Request Utils
159
+ Provides function to resolve request headers,such as accept-language or user-agent.
160
+
161
+ ##### getUAInfo
162
+
163
+ param:
164
+
165
+ * req: express request object
166
+
167
+ return:
168
+ ```
169
+ {
170
+ isBot: false,
171
+ isWinPhone: false,
172
+ isIPhone: false,
173
+ isIPad: false,
174
+ isAndroid: false,
175
+ isAndroidTablet: false,
176
+ isTablet: false,
177
+ isOtherMobile: false,
178
+ isMobile: false,
179
+ isWechatMiniProg: false,
180
+ platform: "windows" | "ios" | "android" | "linux" | "macos" | "whatsapp" | "compatible" | "unknown",
181
+ platformVersion: "10.3.1",
182
+ browser: "msie" | "opera" | "firefox" | "chrome" | "facebook" | "weixin" | "safari" | "unknown",
183
+ browserVersion: "10.3.1"
184
+ }
185
+ ```
186
+
187
+ ##### getLangInfo
188
+
189
+ param:
190
+
191
+ * req: express request object
192
+ * is4PC: true means client is requesting a web page for PC not mobile device.
193
+
194
+ return:
195
+
196
+ ```
197
+ {
198
+ languages: {
199
+ "zh-CN": "0,8",
200
+ "en": "0,6"
201
+ },
202
+ primaryLanguage: ["zh-CN"],
203
+ isEnglish: false,
204
+ lang: "zh-cn" | "en"
205
+ }
206
+ ```
207
+
208
+
209
+ #### Log
210
+
211
+ 1. provide an logger by using getLogger
212
+ 1. automatically zip logs
213
+ 1. automatically removes oldlogs
214
+ 1. Requires init
215
+ 1. FuseCore.logger's method can be called at any time, but it won't output any data until init finished.
216
+
217
+ init options:
218
+
219
+ ```
220
+ {
221
+ dir:path.join(__dirname,'/../logs'),
222
+ streams:FileStreamRotator.getStream({...}) | [FileStreamRotator.getStream({...})]
223
+ }
224
+ ```
225
+
226
+ Usage Example:
227
+
228
+ ```
229
+ FuseCore.logger.info('some text',someObject);
230
+
231
+ ```
232
+
233
+ #### Manifest
234
+ ```
235
+ let FuseCore=require('fuse-core-express');
236
+ let manifest=FuseCore.manifest;
237
+
238
+ let value=manifest.get('prop1.prop2.prop3');
239
+ ......
240
+ ```
241
+
242
+ #### Ajax
243
+
244
+ Ajax API
245
+
246
+ ###### Request Options
247
+
248
+ demo:
249
+ ```
250
+
251
+ {
252
+ server:'lottery',
253
+ version:'v5.0'
254
+ path:'/toto/broadcast'
255
+ data:{
256
+ key1:1234,
257
+ key2:5678
258
+ },
259
+ method:'POST',
260
+ timeout:800,
261
+ reqContentType:'form',
262
+ resContentType:'json',
263
+ headers:{
264
+ SomeUserDefinedHeader:'this will pass to server'
265
+ },
266
+ isWeb:true,
267
+ anonymous:true,
268
+ cname:'toto_broadcast',
269
+ passClientIP:false,
270
+ req:req,
271
+ res:res
272
+ }
273
+
274
+ ```
275
+
276
+ * server: (string) (required) Server code defined in manifest.
277
+ * version: (string) (optional) Version to replace '${version}' part in url.
278
+ * path: (string) (required) Api path apart from url defined in manifest.
279
+ * data: (object) (optional) Data will send to server.It should be a simple object.
280
+ * method: (string) (optional) Http method,default is 'GET'.
281
+ * timeout: (number) (optional) Timeout (microseconds) before server complete response,default is defined in config or init option DEFAULT_TIMEOUT.
282
+ * reqContentType (deprecated alias dataType): (string) (optional) request data format,only support 'json' and 'form'(default).
283
+ * resContentType (deprecated alias contentType): (string) (optional) Content format.If contentType set 'json' or server response with header 'content-type:application/json', response body will be decode automatically.
284
+ * headers: (object) (optional) Headers passed to server.Note:if req object is set,most of req.headers' property will passed to backend,no need to redefine headers in this option.
285
+ * isWeb: (bool) (optional) If true, and exists cookie named 'N1',then cookie N1's value will replace default Accept-Language value.Default is false.
286
+ * anonymous: (bool) (optional) If false, and exists cookie named 'token',then a header named 'Authorization' will be set with value of cookie 'token'.
287
+ * noCache: (bool) (optional) If true, headers named 'If-Modified-Since','If-None-Match' will be removed from header ,and 'Cache-Control' will be set 'no-cache'. Default is true.
288
+ * passClientIP: (bool) (optional) If true, headers ('X-Forwarded-For','X-Real-IP') will be passed to server. Default is true.
289
+ * req: (obj) (optional) (*required by proxy) By default,most of headers in req will pass to backend request.In proxy method,request's body stream will piped to backend request.
290
+ * res: (obj) (unnecessary)(*required by proxy) Proxy will handle response,when reject happened.Only if ret.status === 0 ,proxy don't handle response,you should end response, such as res.status(500).end() .
291
+
292
+ ###### Response Data (also as reject error)
293
+
294
+ ```
295
+
296
+ {
297
+ ok: false,
298
+ status: 0,
299
+ message: '',
300
+ error: null,
301
+ data: {},
302
+ raw: null,
303
+ headers: null,
304
+ totalCount: null,
305
+ duration: [0, 123000]
306
+ }
307
+
308
+ ```
309
+
310
+ * ok: (bool) indicates request is successful.
311
+ * status: (number) http response code from backend server.
312
+ * message: (string) error message when exception or error happened.
313
+ * error: (Error) Error object,if exists.
314
+ * data: (*) parsed server response content.Object if content-type option set "json",string if content-type set something else.
315
+ * raw: (string) original server response text,null when calling proxy method.
316
+ * headers: (object) response headers.
317
+ * duration (array) \[seconds,nanoseconds\], time used from request start to finish.
318
+ * totalCount: (number) same value of response's headers\["X-Total-Count"\],null if header not exists.
319
+
320
+
321
+ ##### Ajax.request
322
+
323
+ ```
324
+ FuseCore.ajax.request({
325
+ server: 'property',
326
+ version: 'v4.6',
327
+ timeout: 3000,
328
+ path: path,
329
+ method: 'POST',
330
+ isWeb:true,
331
+ req: req,
332
+ performance: false,
333
+ proxy: 'http://someuser:password@127.0.0.1:7666',
334
+ headers: {
335
+ 'origin': 'https://property-staging.nestia.com'
336
+ }
337
+ }).then((data) => {
338
+ res.render('somepage',data.data);
339
+ }, (err) => {
340
+ req.app.locals.logger.error('error request backend API:' + err.message, err);
341
+ if (err.status) {
342
+ res.status(err.status).end();
343
+ }else{
344
+ res.status(500).end();
345
+ }
346
+ });
347
+ ```
348
+
349
+ ##### Ajax.proxy
350
+
351
+ ```
352
+ FuseCore.ajax.proxy({
353
+ server: 'property',
354
+ path: path,
355
+ method: 'POST',
356
+ isWeb:true,
357
+ req: req,
358
+ res: res,
359
+ headers: {
360
+ 'origin': 'https://property-staging.nestia.com'
361
+ }
362
+ }).then((data) => {
363
+ }, (err) => {
364
+ FuseCore.logger.error('error upload property image:' + err.message, err);
365
+ if (!err.status) {
366
+ res.status(500).end();
367
+ }
368
+ });
369
+ ```
370
+
371
+ ##### Ajax.requestAll
372
+
373
+ * This method is a little like Promise.all
374
+ * When one of requests fails,you will always get a resolve callback, which is different from Promise.all.
375
+ * You can use data\[n\].ok to check whether request fails, and also you can get status, and raw data if exists.
376
+
377
+ ```
378
+ FuseCore.ajax.request([
379
+ {
380
+ server: 'property',
381
+ version: 'v4.6',
382
+ timeout: 3000,
383
+ path: '/nearby'
384
+ },
385
+ {
386
+ server: 'news',
387
+ version: 'v4.8',
388
+ path: '/news'
389
+ }
390
+ ]).then((datas) => {
391
+ let propertyData=datas[0];
392
+ let newsData=datas[1];
393
+
394
+ //assume property backend never fails.
395
+ if(!newsData.ok){
396
+ //news api fails
397
+ res.render('onlyProperty',propertyData.data);
398
+ }else{
399
+ res.render('fullContent',{property:propertyData.data,news:newsData.data});
400
+ }
401
+ });
402
+ ```
403
+
404
+ #### Cache
405
+
406
+ FuseCore supports two cache implementations: memory cache (memory) and Redis cache (redis).
407
+
408
+ ##### Memory Cache (Default)
409
+
410
+ ```javascript
411
+ let FuseCore = require('fuse-core-express');
412
+
413
+ // Initialize with memory cache
414
+ await FuseCore.init({
415
+ cache: {
416
+ impl: 'memory'
417
+ }
418
+ });
419
+
420
+ let cache = FuseCore.cache;
421
+ // Set a cache
422
+ // timeout is numeric represents seconds, default value is 300 seconds.
423
+ // when timeout is negative number, the cache record will never expire.
424
+ await cache.set('propName', 'propValue', 30);
425
+
426
+ // Get value
427
+ let val = await cache.get('propName');
428
+ console.log(val); // 'propValue'
429
+
430
+ // Memory cache cleanup has 60 seconds deviation
431
+ setTimeout(async function(){
432
+ let val = await cache.get('propName');
433
+ console.log(val); // should be null
434
+ }, 30 * 1000 + 60000);
435
+
436
+ // Setting null value deletes the key
437
+ await cache.set('anotherPropName', 'anotherPropValue', 30);
438
+ await cache.set('anotherPropName', null, 30); // This deletes the key
439
+
440
+ let val = await cache.get('anotherPropName');
441
+ console.log(val); // null
442
+ ```
443
+
444
+ ##### Redis Cache
445
+
446
+ Use Redis as cache storage, supporting distributed caching and persistence.
447
+
448
+ **Configure Redis Cache:**
449
+
450
+ ```javascript
451
+ let FuseCore = require('fuse-core-express');
452
+
453
+ // Initialize with Redis cache
454
+ await FuseCore.init({
455
+ cache: {
456
+ impl: 'redis',
457
+ redis: {
458
+ url: 'redis://localhost:6379', // Redis connection URL
459
+ enableTls: false, // Enable TLS/SSL connection
460
+ clientOptions: { // Additional Redis client options
461
+ // Optional configuration, such as password, database, etc.
462
+ // password: 'your-password',
463
+ // database: 0
464
+ }
465
+ }
466
+ }
467
+ });
468
+
469
+ // Example with TLS enabled
470
+ await FuseCore.init({
471
+ cache: {
472
+ impl: 'redis',
473
+ redis: {
474
+ url: 'redis://redis-server:6380', // Redis server URL
475
+ enableTls: true, // Enable TLS/SSL connection
476
+ clientOptions: {
477
+ // Additional TLS options if needed
478
+ // password: 'your-password',
479
+ // database: 0
480
+ }
481
+ }
482
+ }
483
+ });
484
+ ```
485
+
486
+ **Redis Connection URL Format:**
487
+
488
+ ```
489
+ redis://[:password@]host[:port][/database]
490
+ redis://localhost:6379 // Local Redis, default port
491
+ redis://:mypassword@localhost:6379/1 // With password, using database 1
492
+ redis://redis-server:6379 // Remote Redis server
493
+ rediss://ssl-redis-server:6380 // SSL encrypted connection
494
+ redis://tls-redis-server:6380 // TLS enabled connection (with enableTls: true)
495
+ ```
496
+
497
+ **Basic Usage:**
498
+
499
+ ```javascript
500
+ let cache = FuseCore.cache;
501
+
502
+ // Set cache (asynchronous operation)
503
+ await cache.set('user:123', { name: 'John', age: 30 }, 3600); // Expires in 1 hour
504
+ await cache.set('session:abc', 'session-data', 0); // Never expires
505
+
506
+ // Get cache
507
+ let user = await cache.get('user:123');
508
+ console.log(user); // { name: 'John', age: 30 }
509
+
510
+ // Important: Type preservation (JSON strings are not automatically parsed into objects)
511
+ await cache.set('json-string', '{"name":"John"}'); // Store JSON string
512
+ let jsonStr = await cache.get('json-string'); // Retrieved is still a string
513
+ console.log(typeof jsonStr); // "string"
514
+
515
+ // Delete cache
516
+ await cache.set('user:123', null); // Delete key
517
+
518
+ // Check if key exists (Redis-specific method)
519
+ let exists = await cache.exists('user:123');
520
+ console.log(exists); // false
521
+
522
+ // Set expiration time (Redis-specific method)
523
+ await cache.set('temp:data', 'some-value');
524
+ await cache.expire('temp:data', 600); // Expires in 10 minutes
525
+
526
+ // Get TTL (Redis-specific method)
527
+ let ttl = await cache.ttl('temp:data');
528
+ console.log(ttl); // Remaining seconds
529
+
530
+ // Get matching keys (Redis-specific method)
531
+ let keys = await cache.keys('user:*');
532
+ console.log(keys); // ['user:123', 'user:456', ...]
533
+
534
+ // Clear all cache (Redis-specific method)
535
+ await cache.flushAll();
536
+ ```
537
+
538
+ **Error Handling:**
539
+
540
+ ```javascript
541
+ try {
542
+ await cache.set('mykey', 'myvalue');
543
+ let value = await cache.get('mykey');
544
+ } catch (error) {
545
+ console.error('Cache operation failed:', error);
546
+ }
547
+ ```
548
+
549
+ **Close Connection:**
550
+
551
+ ```javascript
552
+ // FuseCore will automatically close Redis connection when the application shuts down
553
+ await FuseCore.shutdown();
554
+ ```
555
+
556
+ ---
557
+
558
+ ## 📄 License
559
+
560
+ **FuseCore** follows the [MIT License](LICENSE) open source license.
561
+
562
+ ### MIT License
563
+
564
+ Copyright (c) 2024 Ds.3783
565
+
566
+ Permission is hereby granted, free of charge, to any person obtaining a copy
567
+ of this software and associated documentation files (the "Software"), to deal
568
+ in the Software without restriction, including without limitation the rights
569
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
570
+ copies of the Software, and to permit persons to whom the Software is
571
+ furnished to do so, subject to the following conditions:
572
+
573
+ The above copyright notice and this permission notice shall be included in all
574
+ copies or substantial portions of the Software.
575
+
576
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
577
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
578
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
579
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
580
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
581
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
582
+ SOFTWARE.
583
+
584
+
585
+
586
+
587
+
588
+
@@ -0,0 +1,301 @@
1
+ /**
2
+ * FuseCore Decrypt Loader (Bundle Version)
3
+ * 运行时束解密加载器 - 解密并加载文件束到内存
4
+ */
5
+ import crypto from "crypto";
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import { createGunzip } from "zlib";
9
+ import { fileURLToPath } from "url";
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ export class FuseCoreDecryptLoader {
15
+ constructor(privateKeyPath) {
16
+ this.privateKey = this.loadPrivateKey(privateKeyPath);
17
+ this.memoryCache = new Map(); // 内存文件缓存
18
+ this.bundleLoaded = false;
19
+ this.bundlePath = null;
20
+ }
21
+
22
+ loadPrivateKey(privateKeyPath) {
23
+ if (!fs.existsSync(privateKeyPath)) {
24
+ throw new Error(`Private key not found: ${privateKeyPath}`);
25
+ }
26
+ return fs.readFileSync(privateKeyPath, "utf8");
27
+ }
28
+
29
+ decryptAESKey(encryptedAESKey) {
30
+ const encryptedBuffer = Buffer.from(encryptedAESKey, "base64");
31
+ const decryptedAESKey = crypto.privateDecrypt({
32
+ key: this.privateKey,
33
+ padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
34
+ oaepHash: "sha256"
35
+ }, encryptedBuffer);
36
+ return decryptedAESKey;
37
+ }
38
+
39
+ decryptData(encryptedData, aesKey) {
40
+ const { algorithm, iv, data } = encryptedData;
41
+ const decipher = crypto.createDecipheriv(algorithm, aesKey, Buffer.from(iv, "hex"));
42
+ let decryptedBuffer = Buffer.concat([
43
+ decipher.update(Buffer.from(data, "hex")),
44
+ decipher.final()
45
+ ]);
46
+ return decryptedBuffer;
47
+ }
48
+
49
+ async decompressData(compressedData) {
50
+ return new Promise((resolve, reject) => {
51
+ const gunzip = createGunzip();
52
+ const chunks = [];
53
+
54
+ gunzip.on("data", chunk => chunks.push(chunk));
55
+ gunzip.on("end", () => resolve(Buffer.concat(chunks)));
56
+ gunzip.on("error", reject);
57
+
58
+ gunzip.write(compressedData);
59
+ gunzip.end();
60
+ });
61
+ }
62
+
63
+ verifySignature(data, signature) {
64
+ return true; // 简化实现
65
+ }
66
+
67
+ async loadBundle(bundlePath) {
68
+ if (this.bundleLoaded && this.bundlePath === bundlePath) {
69
+ return; // 已加载同一个束
70
+ }
71
+
72
+ console.log(`🔓 Loading encrypted bundle: ${bundlePath}`);
73
+
74
+ try {
75
+ const encryptedPackage = JSON.parse(fs.readFileSync(bundlePath, "utf8"));
76
+
77
+ if (encryptedPackage.type !== "bundle") {
78
+ throw new Error("Not a bundle file");
79
+ }
80
+
81
+ if (!this.verifySignature(encryptedPackage.encryptedData.data, encryptedPackage.signature)) {
82
+ throw new Error("Invalid signature");
83
+ }
84
+
85
+ console.log(`📊 Bundle info: ${encryptedPackage.fileCount} files, compressed from ${encryptedPackage.metadata.originalSize} to ${encryptedPackage.metadata.compressedSize} bytes`);
86
+
87
+ // 解密AES密钥
88
+ const aesKey = this.decryptAESKey(encryptedPackage.encryptedAESKey);
89
+
90
+ // 解密压缩数据
91
+ const decryptedCompressed = this.decryptData(encryptedPackage.encryptedData, aesKey);
92
+
93
+ // 解压缩数据
94
+ const decompressedBuffer = await this.decompressData(decryptedCompressed);
95
+ const bundleData = JSON.parse(decompressedBuffer.toString("utf8"));
96
+
97
+ // 将所有文件加载到内存缓存
98
+ this.memoryCache.clear();
99
+ for (const [filePath, fileInfo] of Object.entries(bundleData)) {
100
+ if (fileInfo.type === "binary") {
101
+ this.memoryCache.set(filePath, {
102
+ type: fileInfo.type,
103
+ content: Buffer.from(fileInfo.content, "base64"),
104
+ size: fileInfo.size
105
+ });
106
+ } else {
107
+ this.memoryCache.set(filePath, {
108
+ type: fileInfo.type,
109
+ content: fileInfo.content,
110
+ size: fileInfo.size
111
+ });
112
+ }
113
+ console.log(`📁 Loaded to memory: ${filePath} (${fileInfo.size} bytes)`);
114
+ }
115
+
116
+ this.bundleLoaded = true;
117
+ this.bundlePath = bundlePath;
118
+ console.log(`✅ Bundle loaded successfully: ${this.memoryCache.size} files in memory`);
119
+
120
+ } catch (error) {
121
+ console.error(`❌ Failed to load bundle ${bundlePath}:`, error.message);
122
+ throw error;
123
+ }
124
+ }
125
+
126
+ async require(moduleName, moduleType = "auto") {
127
+ // 检测模块系统
128
+ const isESM = typeof require === "undefined";
129
+
130
+ // 如果还没有加载束,尝试加载
131
+ if (!this.bundleLoaded) {
132
+ const bundlePath = path.resolve(__dirname, "bundle.fec");
133
+ await this.loadBundle(bundlePath);
134
+ }
135
+
136
+ // 构建文件路径
137
+ let targetPaths = [];
138
+
139
+ if (moduleType === "auto") {
140
+ // 自动检测:根据当前环境选择合适的模块
141
+ if (isESM) {
142
+ targetPaths = [
143
+ `esm/${moduleName}.mjs`,
144
+ `esm/${moduleName}.js`,
145
+ `cjs/${moduleName}.js`,
146
+ `${moduleName}.mjs`,
147
+ `${moduleName}.js`
148
+ ];
149
+ } else {
150
+ targetPaths = [
151
+ `cjs/${moduleName}.js`,
152
+ `esm/${moduleName}.mjs`,
153
+ `${moduleName}.js`,
154
+ `${moduleName}.mjs`
155
+ ];
156
+ }
157
+ } else if (moduleType === "esm") {
158
+ targetPaths = [`esm/${moduleName}.mjs`, `esm/${moduleName}.js`];
159
+ } else if (moduleType === "cjs") {
160
+ targetPaths = [`cjs/${moduleName}.js`];
161
+ }
162
+
163
+ // 在内存缓存中查找文件
164
+ let fileInfo = null;
165
+ let foundPath = null;
166
+
167
+ for (const targetPath of targetPaths) {
168
+ if (this.memoryCache.has(targetPath)) {
169
+ fileInfo = this.memoryCache.get(targetPath);
170
+ foundPath = targetPath;
171
+ break;
172
+ }
173
+ }
174
+
175
+ if (!fileInfo) {
176
+ throw new Error(`Module not found in bundle: ${moduleName} (tried: ${targetPaths.join(", ")})`);
177
+ }
178
+
179
+ if (fileInfo.type !== "javascript") {
180
+ throw new Error(`Not a JavaScript module: ${foundPath}`);
181
+ }
182
+
183
+ console.log(`🔄 Loading module from memory: ${foundPath}`);
184
+
185
+ try {
186
+ // 执行代码
187
+ if (isESM) {
188
+ // ESM模式:使用自定义模块解析器
189
+ const moduleCode = fileInfo.content;
190
+
191
+ // 创建临时目录来模拟真实的文件结构
192
+ const tempDir = path.join(__dirname, `.temp_modules_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`);
193
+ try {
194
+ // 创建临时目录
195
+ if (!fs.existsSync(tempDir)) {
196
+ fs.mkdirSync(tempDir, { recursive: true });
197
+ }
198
+
199
+ // 将所有相关模块写入临时目录
200
+ const moduleDir = path.dirname(foundPath);
201
+ for (const [filePath, fileInfo] of this.memoryCache) {
202
+ if (filePath.startsWith(moduleDir)) {
203
+ const relativePath = path.relative(moduleDir, filePath);
204
+ const tempFilePath = path.join(tempDir, relativePath);
205
+ const tempFileDir = path.dirname(tempFilePath);
206
+ if (!fs.existsSync(tempFileDir)) {
207
+ fs.mkdirSync(tempFileDir, { recursive: true });
208
+ }
209
+ fs.writeFileSync(tempFilePath, fileInfo.content);
210
+ }
211
+ }
212
+
213
+ // 写入当前模块
214
+ const tempModulePath = path.join(tempDir, path.basename(foundPath));
215
+ fs.writeFileSync(tempModulePath, moduleCode);
216
+
217
+ // 导入模块
218
+ const moduleResult = await import(`file://${tempModulePath}`);
219
+ return moduleResult;
220
+ } catch (error) {
221
+ console.warn(`Module loading failed for ${foundPath}:`, error.message);
222
+ // 如果加载失败,返回一个基本的模块对象
223
+ return {
224
+ default: {},
225
+ __esModule: true
226
+ };
227
+ } finally {
228
+ // 清理临时目录
229
+ if (fs.existsSync(tempDir)) {
230
+ const cleanupDir = (dir) => {
231
+ const files = fs.readdirSync(dir);
232
+ for (const file of files) {
233
+ const filePath = path.join(dir, file);
234
+ const stat = fs.statSync(filePath);
235
+ if (stat.isDirectory()) {
236
+ cleanupDir(filePath);
237
+ } else {
238
+ fs.unlinkSync(filePath);
239
+ }
240
+ }
241
+ fs.rmdirSync(dir);
242
+ };
243
+ try {
244
+ cleanupDir(tempDir);
245
+ } catch (cleanupError) {
246
+ // 忽略清理错误
247
+ }
248
+ }
249
+ }
250
+ } else {
251
+ // CommonJS模式:使用require和vm
252
+ const Module = require("module");
253
+ const vm = require("vm");
254
+ const moduleCode = `(function(exports, require, module, __filename, __dirname) { ${fileInfo.content} });`;
255
+ const compiledWrapper = vm.runInThisContext(moduleCode, { filename: foundPath });
256
+ const moduleObj = { exports: {}, require: require, id: foundPath, filename: foundPath, loaded: false };
257
+ compiledWrapper.call(moduleObj.exports, moduleObj.exports, require, moduleObj, foundPath, path.dirname(foundPath));
258
+ moduleObj.loaded = true;
259
+ return moduleObj.exports;
260
+ }
261
+ } catch (error) {
262
+ console.error(`❌ Failed to execute module ${foundPath}:`, error.message);
263
+ throw error;
264
+ }
265
+ }
266
+
267
+ // 获取内存中的文件列表
268
+ getLoadedFiles() {
269
+ return Array.from(this.memoryCache.keys());
270
+ }
271
+
272
+ // 获取内存使用统计
273
+ getMemoryStats() {
274
+ let totalSize = 0;
275
+ const fileTypes = new Map();
276
+
277
+ for (const [filePath, fileInfo] of this.memoryCache) {
278
+ totalSize += fileInfo.size;
279
+ const type = fileInfo.type;
280
+ fileTypes.set(type, (fileTypes.get(type) || 0) + 1);
281
+ }
282
+
283
+ return {
284
+ totalFiles: this.memoryCache.size,
285
+ totalSize,
286
+ fileTypes: Object.fromEntries(fileTypes)
287
+ };
288
+ }
289
+ }
290
+
291
+ // 单例模式 - 确保全局只有一个decryptLoader实例
292
+ let _globalDecryptLoader = null;
293
+
294
+ export function createDecryptLoader(privateKeyPath) {
295
+ if (!_globalDecryptLoader) {
296
+ _globalDecryptLoader = new FuseCoreDecryptLoader(privateKeyPath);
297
+ }
298
+ return _globalDecryptLoader;
299
+ }
300
+
301
+ export default FuseCoreDecryptLoader;
package/index.mjs ADDED
@@ -0,0 +1,218 @@
1
+ /**
2
+ * FuseCore Encrypted Entry Point
3
+ * 加密版本入口文件 - 解密后代理到原始API
4
+ */
5
+ import { createDecryptLoader } from "./decrypt-loader.mjs";
6
+ import path from "path";
7
+ import fs from "fs";
8
+ import { fileURLToPath } from "url";
9
+ import { EventEmitter } from "events";
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ // 全局单例状态
15
+ const state = global.__FUSECORE_STATE__ || (global.__FUSECORE_STATE__ = {
16
+ decryptLoader: null,
17
+ decrypted: false,
18
+ originalFuseCore: null, // 解密后的原始FuseCore实例
19
+ eventEmitter: new EventEmitter()
20
+ });
21
+
22
+ // 临时文件目录
23
+ const TEMP_DIR = path.join(__dirname, '.temp_fusecore');
24
+
25
+ // 清理临时文件
26
+ function cleanupTempFiles() {
27
+ if (fs.existsSync(TEMP_DIR)) {
28
+ try {
29
+ const cleanupDir = (dir) => {
30
+ const files = fs.readdirSync(dir);
31
+ for (const file of files) {
32
+ const filePath = path.join(dir, file);
33
+ const stat = fs.statSync(filePath);
34
+ if (stat.isDirectory()) {
35
+ cleanupDir(filePath);
36
+ fs.rmdirSync(filePath);
37
+ } else {
38
+ fs.unlinkSync(filePath);
39
+ }
40
+ }
41
+ };
42
+ cleanupDir(TEMP_DIR);
43
+ fs.rmdirSync(TEMP_DIR);
44
+ console.log("🧹 Temporary files cleaned up");
45
+ } catch (error) {
46
+ console.warn("⚠️ Failed to cleanup temporary files:", error.message);
47
+ }
48
+ }
49
+ }
50
+
51
+ // 重置状态函数
52
+ function resetState() {
53
+ state.decrypted = false;
54
+ state.originalFuseCore = null;
55
+ if (state.decryptLoader) {
56
+ state.decryptLoader = null;
57
+ }
58
+ }
59
+
60
+
61
+ // 核心方法
62
+ const initDecryption = async function(privateKeyPath) {
63
+ if (!privateKeyPath) {
64
+ throw new Error("Private key path is required for encrypted FuseCore");
65
+ }
66
+
67
+ if (!state.decryptLoader) {
68
+ state.decryptLoader = createDecryptLoader(privateKeyPath);
69
+ console.log("🔐 FuseCore decryption initialized");
70
+ }
71
+
72
+ // 如果还没有解密,先解密所有模块
73
+ if (!state.decrypted) {
74
+ console.log("🔓 Decrypting all modules and generating temporary files...");
75
+
76
+ try {
77
+ // 1. 加载并解密bundle
78
+ await state.decryptLoader.loadBundle(path.resolve(__dirname, 'bundle.fec'));
79
+
80
+ // 2. 创建临时目录
81
+ if (!fs.existsSync(TEMP_DIR)) {
82
+ fs.mkdirSync(TEMP_DIR, { recursive: true });
83
+ }
84
+
85
+ // 3. 将所有解密后的文件写入磁盘
86
+ const loadedFiles = state.decryptLoader.getLoadedFiles();
87
+ console.log(`📁 Generating ${loadedFiles.length} temporary files...`);
88
+
89
+ for (const filePath of loadedFiles) {
90
+ const fileInfo = state.decryptLoader.memoryCache.get(filePath);
91
+ if (fileInfo && fileInfo.type === 'javascript') {
92
+ const tempFilePath = path.join(TEMP_DIR, filePath);
93
+ const tempFileDir = path.dirname(tempFilePath);
94
+
95
+ // 确保目录存在
96
+ if (!fs.existsSync(tempFileDir)) {
97
+ fs.mkdirSync(tempFileDir, { recursive: true });
98
+ }
99
+
100
+ // 写入文件
101
+ fs.writeFileSync(tempFilePath, fileInfo.content, 'utf8');
102
+ console.log(`📄 Generated: ${filePath}`);
103
+ }
104
+ }
105
+
106
+ // 4. 根据环境选择正确的index文件
107
+ let originalIndex;
108
+ if (typeof require === 'undefined') {
109
+ // ESM环境 - 加载esm/index.mjs
110
+ const originalIndexPath = path.join(TEMP_DIR, 'esm/index.mjs');
111
+ originalIndex = await import(`file://${originalIndexPath}`);
112
+ } else {
113
+ // CJS环境 - 加载cjs/index.js
114
+ const originalIndexPath = path.join(TEMP_DIR, 'cjs/index.js');
115
+ originalIndex = require(originalIndexPath);
116
+ }
117
+ state.originalFuseCore = originalIndex.default || originalIndex;
118
+ state.decrypted = true;
119
+
120
+ // 验证状态一致性
121
+ if (!state.originalFuseCore) {
122
+ throw new Error("Failed to load original FuseCore after decryption");
123
+ }
124
+
125
+ console.log("✅ All modules decrypted and temporary files generated successfully");
126
+ } catch (error) {
127
+ console.error("❌ Failed to decrypt modules:", error.message);
128
+ // 清理临时文件
129
+ cleanupTempFiles();
130
+ // 重置状态
131
+ resetState();
132
+ throw error;
133
+ }
134
+ }
135
+
136
+ return this;
137
+ };
138
+
139
+ const init = async function(options = {}) {
140
+ if (!state.decrypted) {
141
+ throw new Error("FuseCore not decrypted. Call initDecryption(privateKeyPath) first.");
142
+ }
143
+
144
+ console.log("🚀 Initializing FuseCore...");
145
+
146
+ try {
147
+ // 直接调用原始FuseCore的init方法
148
+ const result = await state.originalFuseCore.init(options);
149
+
150
+ console.log("✅ FuseCore initialized successfully");
151
+
152
+ // 初始化完成后清理临时文件
153
+ cleanupTempFiles();
154
+
155
+ return result;
156
+ } catch (error) {
157
+ // 如果初始化失败,也要清理临时文件
158
+ cleanupTempFiles();
159
+ throw error;
160
+ }
161
+ };
162
+
163
+ const shutdown = async function() {
164
+ if (!state.decrypted) {
165
+ throw new Error("FuseCore not decrypted. Call initDecryption(privateKeyPath) first.");
166
+ }
167
+
168
+ console.log("🔒 Shutting down FuseCore...");
169
+
170
+ try {
171
+ // 直接调用原始FuseCore的shutdown方法
172
+ await state.originalFuseCore.shutdown();
173
+ } finally {
174
+ // 确保在关闭时清理临时文件
175
+ cleanupTempFiles();
176
+ }
177
+ };
178
+
179
+ // 创建 Proxy 对象,代理到原始 FuseCore
180
+ const FuseCore = new Proxy({}, {
181
+ get: function (target, prop) {
182
+ // 特殊方法直接返回
183
+ if (prop === "initDecryption") {
184
+ return initDecryption;
185
+ }
186
+ if (prop === "init") {
187
+ return init;
188
+ }
189
+ if (prop === "shutdown") {
190
+ return shutdown;
191
+ }
192
+
193
+ // 如果已经解密,代理到原始FuseCore
194
+ if (state.decrypted && state.originalFuseCore) {
195
+ return state.originalFuseCore[prop];
196
+ }
197
+
198
+ // 状态不一致的情况处理
199
+ if (state.decrypted && !state.originalFuseCore) {
200
+ // 重置状态,强制重新解密
201
+ state.decrypted = false;
202
+ state.originalFuseCore = null;
203
+ throw new Error("FuseCore state corrupted. Please call initDecryption() again.");
204
+ }
205
+
206
+ // 如果还没有解密,返回错误提示
207
+ if (prop === "isInitialized") {
208
+ return false;
209
+ }
210
+
211
+ // 其他属性返回错误提示
212
+ return function(...args) {
213
+ throw new Error(`FuseCore not decrypted. Call initDecryption(privateKeyPath) first.`);
214
+ };
215
+ }
216
+ });
217
+
218
+ export default FuseCore;
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "fuse-core-express",
3
+ "version": "1.0.2",
4
+ "description": "FuseCore Web Component",
5
+ "scripts": {
6
+ "postinstall": "echo \"⚠️ This is an encrypted version of FuseCore. Private key is included for MIT compliance.\""
7
+ },
8
+ "main": "index.mjs",
9
+ "exports": {
10
+ ".": {
11
+ "node": {
12
+ "import": "./index.mjs",
13
+ "require": "./index.mjs"
14
+ },
15
+ "default": "./index.mjs"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "module": "./index.mjs",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/ds3783/FuseCore.git"
23
+ },
24
+ "author": "Ds.3783",
25
+ "license": "MIT",
26
+ "bugs": {
27
+ "url": "https://github.com/ds3783/FuseCore/issues"
28
+ },
29
+ "homepage": "https://github.com/ds3783/FuseCore#readme",
30
+ "publishConfig": {
31
+ "registry": "https://registry.npmjs.org"
32
+ },
33
+ "dependencies": {
34
+ "better-https-proxy-agent": "^1.0.8",
35
+ "cookie": "~0.7.0",
36
+ "dot-prop": "^9.0.0",
37
+ "form-data": "^4.0.0",
38
+ "node-cron": "^4.2.1",
39
+ "on-finished": "^2.3.0",
40
+ "redis": "^4.6.0",
41
+ "tracer": "^1.3.0"
42
+ },
43
+ "husky": {
44
+ "hooks": {
45
+ "pre-commit": "npm run build",
46
+ "pre-push": "npm test"
47
+ }
48
+ },
49
+ "type": "module",
50
+ "files": [
51
+ "index.mjs",
52
+ "decrypt-loader.mjs",
53
+ "private-1.0.2.pem",
54
+ "cjs/",
55
+ "esm/",
56
+ "README.md",
57
+ "CHANGELOG.md",
58
+ "LICENSE"
59
+ ],
60
+ "keywords": [
61
+ "encrypted",
62
+ "obfuscated",
63
+ "protected",
64
+ "secure"
65
+ ],
66
+ "engines": {
67
+ "node": ">=14.0.0"
68
+ },
69
+ "encrypted": {
70
+ "version": "1.0.2",
71
+ "algorithm": "AES-256-CBC + RSA-2048",
72
+ "obfuscated": true,
73
+ "integrity": "RSA-SHA256",
74
+ "keyRequired": "private-1.0.2.pem"
75
+ }
76
+ }
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCchsSkY8vrSW1x
3
+ vNAuamsiSr1uvxqigGxnX/ydC3Dupl9i6FiwtyNHGE9zuQmzMOoLFcVw4XzL2EpL
4
+ 2wjfGm3Ct4x1s7LCjbTWHjupUoTJrJI6K2WNLl4hqLQvC/Nk5RXQvJ1eVZ0RaTeb
5
+ a84k0QJEUYrTj1vQAk3TGFGj/k6Ch+Ia6U1zG7BlGpAbsTTDe/4ytljKplwMrPoC
6
+ D9DD92BIC/v6QwtyAlGXoKsiBlrVgvj4nMJii2+C/NFrcqZspoy8G7QI7bIvatwn
7
+ n4NNFhJefSKAfVJnxVUvQ2v25FHXCC9joSR3HGoZNIQFSx/eArCxHg4v4R1z+pyW
8
+ m0MLVwTjAgMBAAECggEAGrNknPRHJ1pNuJ6wVpKhtuZP/THNTdYhZ9r4YSO//n3T
9
+ /K/M3UxIUMrlVsTEUlRwQDbVCOX2H2HEfr8c+s3r1lDwPW3LeyOoKvRaUg55B1wQ
10
+ T6rydkxoE07Pec0MlnP7rjFWmO56zisIKNORhTdALz5Hs8IbM9cIxo858gMiCmL2
11
+ SgXoC9zjofJnztr1ZHvZ1gRTghKrXKVHOhVK8UVn1TPqVdtDAvOyIfimTuRykwMA
12
+ s8DrOpWu0LGUVgDXubWWSWnb0lkA81WsuHR2z0qfSkpOBtG3vpNUf5CRYofp5tZp
13
+ Yyab0PVoQRzVqK3Z9SY/wORs3BzQkjRRUkz8zfS9HQKBgQDPGHG/GTevkmDlhexA
14
+ q67112JJnP+1Y8tOYK0iO0T+XhEg3neEE1yB1NqzG4OY8XtkIAhVGoF4b4NANFD4
15
+ UWdbkIs4LB4LpPVYmj534wTEm8gLA9aDj3cOEOuburwnXhoSX4MYdHx/EX+vr5vU
16
+ FaX9+d1jafqPbxrIDEOwtAlRXwKBgQDBfUUqQ7+siRS902J1fzIukR1RiscCRYff
17
+ 7G/KeCfdG8G7uwtXDD2ecaKvu24sAYKosZGH0kJ4ielEpoUaQeqLuXdtSFZwY2be
18
+ YN7bfgC2Jd6cp06Tk1IWvDgTlC9707TNpYKQJdC+1Z/EfP2hbNRwguyWYW1Pvp9h
19
+ WA9kM2Km/QKBgCS+hZAOfV7dbevLczuWOpjL+lhtTsF0T2I8rPth4L/xGMDoN/Rr
20
+ KwKuLY+R1iuQzP/a3x+acFZaEsOVhLhKWThZ0RnKWhpezllGJMdItFPeAARUTf9R
21
+ uSH5xpP+8dtaSu3vnGb+ZAh3plwZoGBk5urFJo65AwfoAqxz4J69ktmpAoGAeuy/
22
+ GE7aYx7j0oWNM+CPXQ5MdhWYwVSzMgvgKd2UVeDggAgg+DiKnTLMjKCjKcn73HiT
23
+ YJqwKCxVyouQXFIsVICL4x3l7Jj6LaWVcBTpFs+QUi3oudEKge8qISYv1Jd3cn8I
24
+ J9Qefl7xlb9i0z9059YFwLQ84kDUuN9dR+dtcXECgYAUoO4xQPtrRcWXeE0FKeBa
25
+ hNkJCkEF3SGIpMCFeiiOE7S0oRkPC1UteG17fKUFWMEwuyK/eKAZr4Cup7LIYzy4
26
+ yr2WmtgbaZO8vLaJLB4AUCsE/zU+QSQUXbsT77hcbVQxt5JJUGySYFK0UUaYw7B8
27
+ TjKwFwRfiCAS0Vy/yEb92Q==
28
+ -----END PRIVATE KEY-----