keypointjs 1.1.1 → 1.2.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/src/keypointJS.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /*
2
- KeypointJS Main Module © 2026
2
+ KeypointJS Main Module © 2026 - By AnasBex
3
3
  __________________________________________
4
+ Do not make changes to the core of this file, unless you understand all of its code structure and paths.
4
5
 
5
6
  */
6
7
 
@@ -22,22 +23,28 @@ import { AccessDecision } from './policy/AccessDecision.js';
22
23
 
23
24
  export class KeypointJS {
24
25
  constructor(options = {}) {
25
- this.options = {
26
- requireKeypoint: true,
27
- strictMode: true,
28
- validateOrigin: true,
29
- validateProtocol: true,
30
- enableCORS: false,
31
- corsOrigins: ['*'],
32
- maxRequestSize: '10mb',
33
- defaultResponseHeaders: {
34
- 'X-Powered-By': 'KeypointJS',
35
- 'X-Content-Type-Options': 'nosniff'
36
- },
37
- errorHandler: this.defaultErrorHandler.bind(this),
38
- trustedProxies: [], // TAMBAH: untuk ProtocolEngine
39
- ...options
40
- };
26
+ this.options = {
27
+ requireKeypoint: true,
28
+ strictMode: true,
29
+ validateOrigin: true,
30
+ validateProtocol: true,
31
+ enableCORS: false,
32
+ corsOrigins: ['*'],
33
+ maxRequestSize: '10mb',
34
+ defaultResponseHeaders: {
35
+ 'X-Powered-By': 'KeypointJS',
36
+ 'X-Content-Type-Options': 'nosniff'
37
+ },
38
+ errorHandler: this.defaultErrorHandler.bind(this),
39
+ trustedProxies: [],
40
+ // NEW OPTIONS FOR MULTI-PROTOCOL:
41
+ enableWebSocket: true, // Enable WebSocket support
42
+ enableGrpc: false, // Enable gRPC support
43
+ enableHttp2: true, // Enable HTTP/2
44
+ enableHttp3: false, // Enable HTTP/3
45
+ protocolEngines: {}, // Custom protocol engines
46
+ ...options
47
+ };
41
48
 
42
49
  // Initialize core components
43
50
  this.initializeCore();
@@ -98,12 +105,17 @@ export class KeypointJS {
98
105
  }
99
106
 
100
107
  initializeCore() {
101
- // Core protocol engine
102
- this.protocolEngine = new ProtocolEngine({
103
- maxBodySize: this.options.maxRequestSize,
104
- parseJSON: true,
105
- parseForm: true
106
- });
108
+ // Core protocol engine with multi-protocol support
109
+ this.protocolEngine = new ProtocolEngine({
110
+ maxBodySize: this.options.maxRequestSize,
111
+ parseJSON: true,
112
+ parseForm: true,
113
+ validateProtocol: this.options.validateProtocol,
114
+ trustedProxies: this.options.trustedProxies,
115
+ enableWebSocket: this.options.enableWebSocket,
116
+ enableGrpc: this.options.enableGrpc,
117
+ protocolEngines: this.options.protocolEngines
118
+ });
107
119
 
108
120
  // Keypoint system
109
121
  this.keypointStorage = this.options.keypointStorage || new MemoryKeypointStorage();
@@ -126,6 +138,107 @@ export class KeypointJS {
126
138
  this.wsGuard = null;
127
139
  }
128
140
 
141
+ // NEW METHOD: Create multi-protocol server
142
+ createMultiProtocolServer() {
143
+ return new Promise(async (resolve, reject) => {
144
+ try {
145
+ const servers = {};
146
+ const http = await import('http');
147
+ const https = await import('https');
148
+
149
+ // Create HTTP server
150
+ servers.http = http.createServer(async (req, res) => {
151
+ await this.handleHttpRequest(req, res);
152
+ });
153
+
154
+ // Create HTTPS server if certs provided
155
+ if (this.options.https) {
156
+ servers.https = https.createServer(
157
+ this.options.https,
158
+ async (req, res) => {
159
+ await this.handleHttpRequest(req, res);
160
+ }
161
+ );
162
+ }
163
+
164
+ // Setup WebSocket support
165
+ if (this.wsGuard && this.options.enableWebSocket) {
166
+ this.wsGuard.attachToServer(servers.http, this);
167
+ if (servers.https) {
168
+ this.wsGuard.attachToServer(servers.https, this);
169
+ }
170
+ }
171
+
172
+ // Setup gRPC server if enabled
173
+ if (this.options.enableGrpc) {
174
+ servers.grpc = await this.createGrpcServer();
175
+ }
176
+
177
+ resolve(servers);
178
+
179
+ } catch (error) {
180
+ reject(new Error(`Failed to create servers: ${error.message}`));
181
+ }
182
+ });
183
+ }
184
+
185
+ // NEW METHOD: Handle HTTP requests (supports HTTP/HTTPS)
186
+ async handleHttpRequest(req, res) {
187
+ try {
188
+ const response = await this.handleRequest(req, res);
189
+
190
+ res.statusCode = response.status || 200;
191
+
192
+ // Set headers
193
+ if (response.headers) {
194
+ Object.entries(response.headers).forEach(([key, value]) => {
195
+ if (value !== undefined && value !== null) {
196
+ res.setHeader(key, value);
197
+ }
198
+ });
199
+ }
200
+
201
+ // Send body
202
+ if (response.body !== undefined && response.body !== null) {
203
+ const body = typeof response.body === 'string'
204
+ ? response.body
205
+ : JSON.stringify(response.body);
206
+ res.end(body);
207
+ } else {
208
+ res.end();
209
+ }
210
+
211
+ } catch (error) {
212
+ console.error('HTTP request error:', error);
213
+ res.statusCode = 500;
214
+ res.setHeader('Content-Type', 'application/json');
215
+ res.end(JSON.stringify({
216
+ error: 'Internal Server Error',
217
+ timestamp: new Date().toISOString()
218
+ }));
219
+ }
220
+ }
221
+
222
+ // NEW METHOD: Create gRPC server
223
+ async createGrpcServer() {
224
+ try {
225
+ // Note: In production, use @grpc/grpc-js
226
+ console.log('gRPC server support requires additional setup');
227
+ console.log('Install: npm install @grpc/grpc-js @grpc/proto-loader');
228
+
229
+ return {
230
+ start: (port) => {
231
+ console.log(`gRPC server would start on port ${port}`);
232
+ console.log('See docs for gRPC implementation');
233
+ },
234
+ stop: () => console.log('gRPC server stopped')
235
+ };
236
+ } catch (error) {
237
+ console.warn('gRPC support not available:', error.message);
238
+ return null;
239
+ }
240
+ }
241
+
129
242
  initializeLayers() {
130
243
  // Layer 0: Pre-processing (hooks)
131
244
  this.use(async (ctx, next) => {
@@ -134,43 +247,58 @@ export class KeypointJS {
134
247
  });
135
248
 
136
249
  // Layer 1: Protocol Engine
250
+ // Protocol Engine (UPDATED for multi-protocol)
137
251
  this.use(async (ctx, next) => {
138
252
  try {
139
- // Make sure ctx.request is a native Node.js Request object.
140
253
  if (!ctx.request || typeof ctx.request !== 'object') {
141
254
  throw new ProtocolError('Invalid request object', 400);
142
255
  }
143
256
 
144
257
  const processed = await this.protocolEngine.process(ctx.request);
145
258
 
146
- // Update context dengan data processed
259
+ // Store original protocol before context updates
260
+ const originalProtocol = processed.protocol;
261
+
262
+ // Update context
147
263
  Object.assign(ctx, {
148
264
  id: processed.id,
149
265
  timestamp: processed.timestamp,
150
266
  metadata: {
151
267
  ...ctx.metadata,
152
- ...processed.metadata
268
+ ...processed.metadata,
269
+ protocolEngine: processed.metadata.engine,
270
+ protocolVersion: processed.metadata.protocolVersion
153
271
  }
154
272
  });
155
273
 
156
- // Update request object - pertahankan original request
274
+ // Preserve original request and add processed data
157
275
  ctx.request = {
158
276
  ...ctx.request,
159
277
  ...processed.request,
160
- originalRequest: ctx.request // Save reference to original
278
+ originalRequest: ctx.request
161
279
  };
162
280
 
163
- // Set protocol and IP
164
- ctx.setState('_protocol', processed.protocol);
281
+ // Set protocol-specific properties
282
+ ctx.setState('_protocol', originalProtocol);
165
283
  ctx.setState('_ip', processed.request?.ip || '0.0.0.0');
284
+ ctx._protocol = originalProtocol;
166
285
 
167
- // Set protocol and ip properties in context
168
- if (!ctx._protocol) {
169
- ctx._protocol = processed.protocol;
286
+ // Add protocol-specific helpers based on detected protocol
287
+ if (originalProtocol.startsWith('ws')) {
288
+ ctx.isWebSocket = true;
289
+ ctx.webSocket = {
290
+ key: processed.request?.key,
291
+ version: processed.request?.version
292
+ };
293
+ } else if (originalProtocol.startsWith('grpc')) {
294
+ ctx.isGrpc = true;
295
+ ctx.grpc = {
296
+ service: processed.request?.service,
297
+ method: processed.request?.method
298
+ };
170
299
  }
171
300
 
172
301
  } catch (error) {
173
- // Use ProtocolError if available, otherwise KeypointError
174
302
  if (error.name === 'ProtocolError') {
175
303
  throw error;
176
304
  }
@@ -469,18 +597,20 @@ this.use(async (ctx, next) => {
469
597
  this.stats.requests++;
470
598
 
471
599
  try {
472
- // Run the middleware chain
473
600
  await this.runMiddlewareChain(ctx);
474
601
  this.stats.successful++;
475
602
 
476
- // Emit success event
603
+ // Track protocol usage
604
+ const protocol = ctx.protocol || 'unknown';
605
+ if (!this.stats.protocols) this.stats.protocols = {};
606
+ this.stats.protocols[protocol] = (this.stats.protocols[protocol] || 0) + 1;
607
+
477
608
  this.emit('request:success', {
478
609
  ctx,
479
610
  timestamp: new Date(),
480
611
  duration: ctx.response?.duration || 0
481
612
  });
482
613
 
483
- // Return response
484
614
  return ctx.response || {
485
615
  status: 404,
486
616
  headers: { 'Content-Type': 'application/json' },
@@ -490,14 +620,12 @@ this.use(async (ctx, next) => {
490
620
  } catch (error) {
491
621
  this.stats.failed++;
492
622
 
493
- // Emit error event
494
623
  this.emit('request:error', {
495
624
  ctx,
496
625
  error,
497
626
  timestamp: new Date()
498
627
  });
499
628
 
500
- // Handle error via error handler
501
629
  return this.options.errorHandler(error, ctx, response);
502
630
  }
503
631
  }
@@ -568,53 +696,136 @@ createServer() {
568
696
  });
569
697
  }
570
698
 
571
- // SINGLE listen method
699
+ // listen method with multi-protocol support
572
700
  listen(port, hostname = '0.0.0.0', callback) {
573
701
  return new Promise(async (resolve, reject) => {
574
702
  try {
575
- const server = await this.createServer();
703
+ const servers = await this.createMultiProtocolServer();
576
704
 
577
- server.listen(port, hostname, () => {
578
- const address = server.address();
579
- const actualHost = address.address;
580
- const actualPort = address.port;
705
+ // Start HTTP server
706
+ servers.http.listen(port, hostname, () => {
707
+ const address = servers.http.address();
708
+ this.logServerStart(address, servers);
581
709
 
582
- console.log(`
710
+ if (callback) callback(servers);
711
+ resolve(servers);
712
+ });
713
+
714
+ // Start HTTPS server if available
715
+ if (servers.https) {
716
+ const httpsPort = this.options.httpsPort || port + 1;
717
+ servers.https.listen(httpsPort, hostname, () => {
718
+ console.log(`HTTPS server started on port ${httpsPort}`);
719
+ });
720
+ }
721
+
722
+ // Start gRPC server if available
723
+ if (servers.grpc && this.options.enableGrpc) {
724
+ const grpcPort = this.options.grpcPort || port + 2;
725
+ servers.grpc.start(grpcPort);
726
+ }
727
+
728
+ // Error handling
729
+ servers.http.on('error', (error) => {
730
+ this.emit('server:error', { protocol: 'http', error, timestamp: new Date() });
731
+ });
732
+
733
+ // Graceful shutdown
734
+ process.on('SIGTERM', () => this.shutdown(servers));
735
+ process.on('SIGINT', () => this.shutdown(servers));
736
+
737
+ } catch (error) {
738
+ reject(error);
739
+ }
740
+ });
741
+ }
742
+
743
+ // NEW METHOD: Improved server startup logging
744
+ logServerStart(address, servers) {
745
+ const actualHost = address.address;
746
+ const actualPort = address.port;
747
+
748
+ const protocols = ['HTTP'];
749
+ if (servers.https) protocols.push('HTTPS');
750
+ if (this.options.enableWebSocket) protocols.push('WebSocket');
751
+ if (this.options.enableGrpc) protocols.push('gRPC');
752
+
753
+ const protocolStr = protocols.join(' / ');
754
+
755
+ console.log(`
583
756
  ╔═══════════════════════════════════════════════╗
584
757
  ║ KeypointJS Server Started ║
585
758
  ╠═══════════════════════════════════════════════╣
586
759
  ║ Address: ${actualHost}:${actualPort}${' '.repeat(20 - (actualHost.length + actualPort.toString().length))}║
587
760
  ║ Mode: ${this.options.requireKeypoint ? 'Strict' : 'Permissive'}${' '.repeat(25 - (this.options.requireKeypoint ? 6 : 9))}║
588
- ║ Protocols: HTTP/HTTPS${this.wsGuard ? ' + WebSocket' : ''}${' '.repeat(25 - (this.wsGuard ? 21 : 10))}║
761
+ ║ Protocols: ${protocolStr}${' '.repeat(25 - protocolStr.length)}║
589
762
  ║ Plugins: ${this.pluginManager.getPluginNames().length} loaded${' '.repeat(20 - this.pluginManager.getPluginNames().length.toString().length)}║
590
763
  ║ Keypoints: ${this.keypointStorage.store.size} registered${' '.repeat(20 - this.keypointStorage.store.size.toString().length)}║
764
+ ║ Engines: ${this.protocolEngine.engines.size} available${' '.repeat(20 - this.protocolEngine.engines.size.toString().length)}║
591
765
  ╚═══════════════════════════════════════════════╝
592
- `);
593
-
594
- // Emit event
595
- this.emit('server:started', {
596
- host: actualHost,
597
- port: actualPort,
598
- timestamp: new Date()
599
- });
600
-
601
- if (callback) callback(server);
602
- resolve(server);
603
- });
766
+ `);
767
+
768
+ this.emit('server:started', {
769
+ host: actualHost,
770
+ port: actualPort,
771
+ protocols,
772
+ timestamp: new Date()
773
+ });
774
+ }
775
+
776
+ // Protocol-specific configuration methods
777
+ enableProtocol(protocol, options = {}) {
778
+ switch (protocol.toLowerCase()) {
779
+ case 'websocket':
780
+ case 'ws':
781
+ this.options.enableWebSocket = true;
782
+ if (!this.wsGuard) {
783
+ this.enableWebSocket(options);
784
+ }
785
+ break;
604
786
 
605
- server.on('error', (error) => {
606
- this.emit('server:error', { error, timestamp: new Date() });
607
- reject(error);
608
- });
787
+ case 'grpc':
788
+ this.options.enableGrpc = true;
789
+ this.configureProtocolEngine({ enableGrpc: true });
790
+ break;
609
791
 
610
- // Handle graceful shutdown
611
- process.on('SIGTERM', () => this.shutdown());
612
- process.on('SIGINT', () => this.shutdown());
792
+ case 'http2':
793
+ this.options.enableHttp2 = true;
794
+ this.configureProtocolEngine({ http2: true });
795
+ break;
613
796
 
614
- } catch (error) {
615
- reject(error);
797
+ default:
798
+ console.warn(`Unknown protocol: ${protocol}`);
799
+ }
800
+ return this;
801
+ }
802
+
803
+ // Get available protocols
804
+ getAvailableProtocols() {
805
+ const protocols = Array.from(this.protocolEngine.engines.keys());
806
+ const available = [];
807
+
808
+ for (const protocol of protocols) {
809
+ if (protocol === 'http' || protocol === 'https') {
810
+ available.push(protocol.toUpperCase());
811
+ } else if (protocol === 'ws' || protocol === 'wss') {
812
+ if (this.options.enableWebSocket) {
813
+ available.push('WebSocket');
814
+ }
815
+ } else if (protocol.startsWith('grpc')) {
816
+ if (this.options.enableGrpc) {
817
+ available.push('gRPC');
818
+ }
616
819
  }
617
- });
820
+ }
821
+
822
+ return [...new Set(available)]; // Remove duplicates
823
+ }
824
+
825
+ // Register custom protocol engine
826
+ registerProtocolEngine(protocol, engine) {
827
+ this.protocolEngine.registerEngine(protocol, engine);
828
+ return this;
618
829
  }
619
830
 
620
831
 
@@ -709,32 +920,45 @@ listen(port, hostname = '0.0.0.0', callback) {
709
920
  // Statistics
710
921
 
711
922
  getStats() {
712
- const uptime = Date.now() - this.stats.startTime;
713
-
714
- return {
715
- ...this.stats,
716
- uptime,
717
- uptimeFormatted: this.formatUptime(uptime),
718
- successRate: this.stats.requests > 0
719
- ? (this.stats.successful / this.stats.requests * 100).toFixed(2) + '%'
720
- : '0%',
721
- plugins: this.pluginManager.getStats(),
722
- keypoints: {
723
- total: this.keypointStorage.store.size,
724
- expired: (async () => {
725
- const all = await this.keypointStorage.list();
726
- return all.filter(k => k.isExpired()).length;
727
- })(),
728
- active: (async () => {
729
- const all = await this.keypointStorage.list();
730
- return all.filter(k => !k.isExpired()).length;
731
- })()
732
- },
733
- routes: this.router.routes.size,
734
- policies: this.policyEngine.rules.length
735
- };
923
+ const uptime = Date.now() - this.stats.startTime;
924
+
925
+ // Get protocol distribution from recent requests
926
+ const protocolStats = {};
927
+ if (this.stats.protocols) {
928
+ for (const [protocol, count] of Object.entries(this.stats.protocols)) {
929
+ protocolStats[protocol] = count;
930
+ }
736
931
  }
737
932
 
933
+ return {
934
+ ...this.stats,
935
+ uptime,
936
+ uptimeFormatted: this.formatUptime(uptime),
937
+ successRate: this.stats.requests > 0 ?
938
+ (this.stats.successful / this.stats.requests * 100).toFixed(2) + '%' :
939
+ '0%',
940
+ protocols: {
941
+ available: this.getAvailableProtocols(),
942
+ distribution: protocolStats,
943
+ engines: this.protocolEngine.engines.size
944
+ },
945
+ plugins: this.pluginManager.getStats(),
946
+ keypoints: {
947
+ total: this.keypointStorage.store.size,
948
+ expired: (async () => {
949
+ const all = await this.keypointStorage.list();
950
+ return all.filter(k => k.isExpired()).length;
951
+ })(),
952
+ active: (async () => {
953
+ const all = await this.keypointStorage.list();
954
+ return all.filter(k => !k.isExpired()).length;
955
+ })()
956
+ },
957
+ routes: this.router.routes.size,
958
+ policies: this.policyEngine.rules.length
959
+ };
960
+ }
961
+
738
962
  formatUptime(ms) {
739
963
  const seconds = Math.floor(ms / 1000);
740
964
  const days = Math.floor(seconds / 86400);
@@ -846,9 +1070,41 @@ export class ValidationError extends Error {
846
1070
 
847
1071
  // Export utilities
848
1072
 
1073
+ // At the bottom of the file, add example exports
1074
+ export const ProtocolExamples = {
1075
+ // Example WebSocket configuration
1076
+ webSocketConfig: {
1077
+ path: '/ws',
1078
+ requireKeypoint: true,
1079
+ pingInterval: 30000,
1080
+ maxConnections: 1000
1081
+ },
1082
+
1083
+ // Example gRPC configuration
1084
+ grpcConfig: {
1085
+ enableReflection: true,
1086
+ maxMessageSize: '4mb',
1087
+ keepaliveTime: 7200000
1088
+ },
1089
+
1090
+ // Example multi-protocol configuration
1091
+ multiProtocolConfig: {
1092
+ enableWebSocket: true,
1093
+ enableGrpc: true,
1094
+ enableHttp2: true,
1095
+ trustedProxies: ['192.168.1.0/24', '10.0.0.0/8'],
1096
+ protocolEngines: {
1097
+ // Custom protocol engines can be added here
1098
+ }
1099
+ }
1100
+ };
1101
+
849
1102
  export {
850
1103
  Context,
851
1104
  ProtocolEngine,
1105
+ ProtocolError,
1106
+ // Protocol engines (optional exports)
1107
+ // Note: These are re-exported from ProtocolEngine.js
852
1108
  Keypoint,
853
1109
  KeypointContext,
854
1110
  KeypointValidator,
@@ -861,5 +1117,14 @@ export {
861
1117
  BuiltInHooks,
862
1118
  RateLimiter,
863
1119
  AuditLogger,
864
- WebSocketGuard
865
- };
1120
+ WebSocketGuard,
1121
+ AccessDecision,
1122
+ };
1123
+
1124
+ // TypeScript/IntelliSense,
1125
+ /*
1126
+ * @typedef {import('./core/ProtocolEngine.js').HttpEngine} HttpEngine
1127
+ * @typedef {import('./core/ProtocolEngine.js').GrpcEngine} GrpcEngine
1128
+ * @typedef {import('./core/ProtocolEngine.js').WsEngine} WsEngine
1129
+ * @typedef {import('./core/ProtocolEngine.js').ProtocolAdapter} ProtocolAdapter
1130
+ */