nyte 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +59 -0
  3. package/dist/adapters/express.d.ts +7 -0
  4. package/dist/adapters/express.js +63 -0
  5. package/dist/adapters/factory.d.ts +23 -0
  6. package/dist/adapters/factory.js +121 -0
  7. package/dist/adapters/fastify.d.ts +25 -0
  8. package/dist/adapters/fastify.js +61 -0
  9. package/dist/adapters/native.d.ts +8 -0
  10. package/dist/adapters/native.js +200 -0
  11. package/dist/api/console.d.ts +81 -0
  12. package/dist/api/console.js +318 -0
  13. package/dist/api/http.d.ts +180 -0
  14. package/dist/api/http.js +469 -0
  15. package/dist/bin/nytejs.d.ts +2 -0
  16. package/dist/bin/nytejs.js +277 -0
  17. package/dist/builder.d.ts +32 -0
  18. package/dist/builder.js +634 -0
  19. package/dist/client/DefaultNotFound.d.ts +1 -0
  20. package/dist/client/DefaultNotFound.js +79 -0
  21. package/dist/client/client.d.ts +4 -0
  22. package/dist/client/client.js +27 -0
  23. package/dist/client/clientRouter.d.ts +58 -0
  24. package/dist/client/clientRouter.js +132 -0
  25. package/dist/client/entry.client.d.ts +1 -0
  26. package/dist/client/entry.client.js +455 -0
  27. package/dist/client/rpc.d.ts +8 -0
  28. package/dist/client/rpc.js +97 -0
  29. package/dist/components/Link.d.ts +7 -0
  30. package/dist/components/Link.js +13 -0
  31. package/dist/global/global.d.ts +117 -0
  32. package/dist/global/global.js +17 -0
  33. package/dist/helpers.d.ts +20 -0
  34. package/dist/helpers.js +604 -0
  35. package/dist/hotReload.d.ts +32 -0
  36. package/dist/hotReload.js +545 -0
  37. package/dist/index.d.ts +18 -0
  38. package/dist/index.js +515 -0
  39. package/dist/loaders.d.ts +1 -0
  40. package/dist/loaders.js +138 -0
  41. package/dist/renderer.d.ts +14 -0
  42. package/dist/renderer.js +380 -0
  43. package/dist/router.d.ts +101 -0
  44. package/dist/router.js +659 -0
  45. package/dist/rpc/server.d.ts +11 -0
  46. package/dist/rpc/server.js +166 -0
  47. package/dist/rpc/types.d.ts +22 -0
  48. package/dist/rpc/types.js +20 -0
  49. package/dist/types/framework.d.ts +37 -0
  50. package/dist/types/framework.js +2 -0
  51. package/dist/types.d.ts +218 -0
  52. package/dist/types.js +2 -0
  53. package/package.json +87 -0
  54. package/src/adapters/express.ts +87 -0
  55. package/src/adapters/factory.ts +112 -0
  56. package/src/adapters/fastify.ts +104 -0
  57. package/src/adapters/native.ts +245 -0
  58. package/src/api/console.ts +348 -0
  59. package/src/api/http.ts +535 -0
  60. package/src/bin/nytejs.js +331 -0
  61. package/src/builder.js +690 -0
  62. package/src/client/DefaultNotFound.tsx +119 -0
  63. package/src/client/client.ts +24 -0
  64. package/src/client/clientRouter.ts +153 -0
  65. package/src/client/entry.client.tsx +529 -0
  66. package/src/client/rpc.ts +101 -0
  67. package/src/components/Link.tsx +38 -0
  68. package/src/global/global.ts +171 -0
  69. package/src/helpers.ts +657 -0
  70. package/src/hotReload.ts +566 -0
  71. package/src/index.ts +582 -0
  72. package/src/loaders.js +160 -0
  73. package/src/renderer.tsx +421 -0
  74. package/src/router.ts +732 -0
  75. package/src/rpc/server.ts +190 -0
  76. package/src/rpc/types.ts +45 -0
  77. package/src/types/framework.ts +58 -0
  78. package/src/types.ts +288 -0
package/src/helpers.ts ADDED
@@ -0,0 +1,657 @@
1
+ #!/usr/bin/env node
2
+
3
+ /*
4
+ * This file is part of the Nyte.js Project.
5
+ * Copyright (c) 2026 itsmuzin
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+
20
+ // Imports Nativos do Node.js (movidos para o topo)
21
+ import http, {IncomingMessage, Server, ServerResponse} from 'http';
22
+ import os from 'os';
23
+ import {URLSearchParams} from 'url'; // API moderna, substitui 'querystring'
24
+ import path from 'path';
25
+ // Helpers para integração com diferentes frameworks
26
+ import hweb, {FrameworkAdapterFactory} from './index'; // Importando o tipo
27
+ import type {NyteOptions, NyteConfig, NyteConfigFunction} from './types';
28
+ import Console, {Colors} from "./api/console";
29
+ import https, { Server as HttpsServer } from 'https'; // <-- ADICIONAR
30
+ import fs from 'fs'; // <-- ADICIONAR
31
+
32
+ // Registra loaders customizados para importar arquivos não-JS
33
+ const { registerLoaders } = require('./loaders');
34
+ registerLoaders();
35
+
36
+ // --- Tipagem ---
37
+
38
+ /**
39
+ * Interface para a instância principal do hweb, inferida pelo uso.
40
+ */
41
+ interface HWebApp {
42
+ prepare: () => Promise<void>;
43
+ // O handler pode ter assinaturas diferentes dependendo do framework
44
+ getRequestHandler: () => (req: any, res: any, next?: any) => Promise<void> | void;
45
+ setupWebSocket: (server: Server | any) => void; // Aceita http.Server ou app (Express/Fastify)
46
+ executeInstrumentation: () => void;
47
+ }
48
+
49
+ /**
50
+ * Estende a request nativa do Node para incluir o body parseado.
51
+ */
52
+ interface HWebIncomingMessage extends IncomingMessage {
53
+ body?: object | string | null;
54
+ }
55
+
56
+ // --- Helpers ---
57
+
58
+ /**
59
+ * Encontra o IP externo local (rede)
60
+ */
61
+ function getLocalExternalIp(): string {
62
+ const interfaces = os.networkInterfaces();
63
+ for (const name of Object.keys(interfaces)) {
64
+ const ifaceList = interfaces[name];
65
+ if (ifaceList) {
66
+ for (const iface of ifaceList) {
67
+ if (iface.family === 'IPv4' && !iface.internal) {
68
+ return iface.address;
69
+ }
70
+ }
71
+ }
72
+ }
73
+ return 'localhost'; // Fallback
74
+ }
75
+
76
+ const sendBox = (options: NyteOptions) => {
77
+ const isDev = options.dev;
78
+ const isSSL = options.ssl && options.ssl.key && options.ssl.cert;
79
+ const protocol = isSSL ? 'https' : 'http';
80
+ const localIp = getLocalExternalIp();
81
+
82
+ // Estilos Clean
83
+ const labelStyle = Colors.FgGray;
84
+ const urlStyle = Colors.Bright + Colors.FgCyan; // Ciano para links é o padrão mais legível
85
+ const now = new Date();
86
+ const time = now.toLocaleTimeString('pt-BR', { hour12: false });
87
+ const timer = ` ${Colors.FgGray}${time}${Colors.Reset} `
88
+
89
+ // Pequeno espaçamento visual antes dos logs de acesso
90
+ console.log('');
91
+ console.log(timer + labelStyle + ' Access on:')
92
+ console.log(' ')
93
+ // 1. Local (Alinhamento: Local tem 6 letras + 4 espaços = 10)
94
+ console.info(timer + `${labelStyle} ┃ Local:${Colors.Reset} ${urlStyle}${protocol}://localhost:${options.port}${Colors.Reset}`);
95
+
96
+ // 2. Network (Alinhamento: Network tem 8 letras + 2 espaços = 10)
97
+ if (localIp) {
98
+ console.info(timer + `${labelStyle} ┃ Network:${Colors.Reset} ${urlStyle}${protocol}://${localIp}:${options.port}${Colors.Reset}`);
99
+ }
100
+
101
+ // 3. Infos Extras (Redirect HTTP -> HTTPS)
102
+ if (isSSL && options.ssl?.redirectPort) {
103
+ console.info(timer + `${labelStyle} ┃ Redirect:${Colors.Reset} ${labelStyle}port ${options.ssl.redirectPort} ➜ https${Colors.Reset}`);
104
+ }
105
+
106
+ // 4. Info de Ambiente
107
+ if (isDev) {
108
+ console.info(timer + `${labelStyle} ┃ Mode:${Colors.Reset} ${Colors.FgAlmostWhite}development${Colors.Reset}`);
109
+ }
110
+
111
+ // Espaçamento final
112
+ console.log('\n');
113
+ }
114
+
115
+ /**
116
+ * Carrega o arquivo de configuração nytejs.config.ts ou nytejs.config.js do projeto
117
+ * @param projectDir Diretório raiz do projeto
118
+ * @param phase Fase de execução ('development' ou 'production')
119
+ * @returns Configuração mesclada com os valores padrão
120
+ */
121
+ async function loadNyteConfig(projectDir: string, phase: string): Promise<NyteConfig> {
122
+ const defaultConfig: NyteConfig = {
123
+ maxHeadersCount: 100,
124
+ headersTimeout: 60000,
125
+ requestTimeout: 30000,
126
+ serverTimeout: 35000,
127
+ individualRequestTimeout: 30000,
128
+ maxUrlLength: 2048,
129
+ accessLogging: true,
130
+ };
131
+
132
+ try {
133
+ // Tenta primeiro .ts, depois .js
134
+ const possiblePaths = [
135
+ path.join(projectDir, 'nytejs.config.ts'),
136
+ path.join(projectDir, 'nytejs.config.js'),
137
+ ];
138
+
139
+ let configPath: string | null = null;
140
+ for (const p of possiblePaths) {
141
+ if (fs.existsSync(p)) {
142
+ configPath = p;
143
+ break;
144
+ }
145
+ }
146
+
147
+ if (!configPath) {
148
+ return defaultConfig;
149
+ }
150
+
151
+ // Remove do cache para permitir hot reload da configuração em dev
152
+ delete require.cache[require.resolve(configPath)];
153
+
154
+ const configModule = require(configPath);
155
+ const configExport = configModule.default || configModule;
156
+
157
+ let userConfig: NyteConfig;
158
+
159
+ if (typeof configExport === 'function') {
160
+ // Suporta tanto função síncrona quanto assíncrona
161
+ userConfig = await Promise.resolve(
162
+ (configExport as NyteConfigFunction)(phase, { defaultConfig })
163
+ );
164
+ } else {
165
+ userConfig = configExport;
166
+ }
167
+
168
+ // Mescla a configuração do usuário com a padrão
169
+ const mergedConfig = { ...defaultConfig, ...userConfig };
170
+
171
+ const configFileName = path.basename(configPath);
172
+ Console.info(`Loaded ${configFileName}`);
173
+
174
+ return mergedConfig;
175
+ } catch (error) {
176
+ if (error instanceof Error) {
177
+ Console.warn(`${Colors.FgYellow}[Config]${Colors.Reset} Error loading nytejs.config: ${error.message}`);
178
+ Console.warn(`${Colors.FgYellow}[Config]${Colors.Reset} Using default configuration`);
179
+ }
180
+ return defaultConfig;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Aplica headers CORS na resposta baseado na configuração.
186
+ * @param req Requisição HTTP
187
+ * @param res Resposta HTTP
188
+ * @param corsConfig Configuração de CORS
189
+ * @returns true se a requisição foi finalizada (OPTIONS), false caso contrário
190
+ */
191
+ function applyCors(req: IncomingMessage, res: ServerResponse, corsConfig?: NyteConfig['cors']): boolean {
192
+ if (!corsConfig || !corsConfig.enabled) {
193
+ return false;
194
+ }
195
+
196
+ const origin = req.headers.origin || req.headers.referer;
197
+
198
+ // Verifica se a origem é permitida
199
+ let allowOrigin = false;
200
+ if (corsConfig.origin === '*') {
201
+ res.setHeader('Access-Control-Allow-Origin', '*');
202
+ allowOrigin = true;
203
+ } else if (typeof corsConfig.origin === 'string' && origin === corsConfig.origin) {
204
+ res.setHeader('Access-Control-Allow-Origin', corsConfig.origin);
205
+ allowOrigin = true;
206
+ } else if (Array.isArray(corsConfig.origin) && origin && corsConfig.origin.includes(origin)) {
207
+ res.setHeader('Access-Control-Allow-Origin', origin);
208
+ allowOrigin = true;
209
+ } else if (typeof corsConfig.origin === 'function' && origin) {
210
+ try {
211
+ if (corsConfig.origin(origin)) {
212
+ res.setHeader('Access-Control-Allow-Origin', origin);
213
+ allowOrigin = true;
214
+ }
215
+ } catch (error) {
216
+ Console.warn(`${Colors.FgYellow}[CORS]${Colors.Reset} Error validating origin: ${error instanceof Error ? error.message : 'Unknown error'}`);
217
+ }
218
+ }
219
+
220
+ // Se a origem não for permitida e não for wildcard, não aplica outros headers
221
+ if (!allowOrigin && corsConfig.origin !== '*') {
222
+ return false;
223
+ }
224
+
225
+ // Credenciais (não pode ser usado com origin: '*')
226
+ if (corsConfig.credentials && corsConfig.origin !== '*') {
227
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
228
+ }
229
+
230
+ // Métodos permitidos
231
+ const methods = corsConfig.methods || ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'];
232
+ res.setHeader('Access-Control-Allow-Methods', methods.join(', '));
233
+
234
+ // Headers permitidos
235
+ const allowedHeaders = corsConfig.allowedHeaders || ['Content-Type', 'Authorization'];
236
+ res.setHeader('Access-Control-Allow-Headers', allowedHeaders.join(', '));
237
+
238
+ // Headers expostos
239
+ if (corsConfig.exposedHeaders && corsConfig.exposedHeaders.length > 0) {
240
+ res.setHeader('Access-Control-Expose-Headers', corsConfig.exposedHeaders.join(', '));
241
+ }
242
+
243
+ // Max age para cache de preflight
244
+ const maxAge = corsConfig.maxAge !== undefined ? corsConfig.maxAge : 86400;
245
+ res.setHeader('Access-Control-Max-Age', maxAge.toString());
246
+
247
+ // Responde requisições OPTIONS (preflight)
248
+ if (req.method === 'OPTIONS') {
249
+ res.statusCode = 204; // No Content
250
+ res.end();
251
+ return true;
252
+ }
253
+
254
+ return false;
255
+ }
256
+
257
+ /**
258
+ * Middleware para parsing do body com proteções de segurança (versão melhorada).
259
+ */
260
+
261
+ const parseBody = (req: IncomingMessage): Promise<object | string | null> => {
262
+ // Constantes para limites de segurança
263
+ const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB limite total
264
+ const MAX_JSON_SIZE = 1 * 1024 * 1024; // 1MB limite para JSON
265
+ const BODY_TIMEOUT = 30000; // 30 segundos
266
+
267
+ return new Promise((resolve, reject) => {
268
+ if (req.method === 'GET' || req.method === 'HEAD') {
269
+ resolve(null);
270
+ return;
271
+ }
272
+
273
+ let body = '';
274
+ let totalSize = 0;
275
+
276
+ // Timeout para requisições lentas
277
+ const timeout = setTimeout(() => {
278
+ req.destroy();
279
+ reject(new Error('Request body timeout'));
280
+ }, BODY_TIMEOUT);
281
+
282
+ req.on('data', (chunk: Buffer) => {
283
+ totalSize += chunk.length;
284
+
285
+ // Proteção contra DoS (Payload Too Large)
286
+ if (totalSize > MAX_BODY_SIZE) {
287
+ clearTimeout(timeout);
288
+ req.destroy();
289
+ reject(new Error('Request body too large'));
290
+ return;
291
+ }
292
+ body += chunk.toString();
293
+ });
294
+
295
+ req.on('end', () => {
296
+ clearTimeout(timeout);
297
+
298
+ if (!body) {
299
+ resolve(null);
300
+ return;
301
+ }
302
+
303
+ try {
304
+ const contentType = req.headers['content-type'] || '';
305
+
306
+ if (contentType.includes('application/json')) {
307
+ if (body.length > MAX_JSON_SIZE) {
308
+ reject(new Error('JSON body too large'));
309
+ return;
310
+ }
311
+ // Rejeita promise se o JSON for inválido
312
+ try {
313
+ resolve(JSON.parse(body));
314
+ } catch (e) {
315
+ reject(new Error('Invalid JSON body'));
316
+ }
317
+ } else if (contentType.includes('application/x-www-form-urlencoded')) {
318
+ // Usa API moderna URLSearchParams (segura contra prototype pollution)
319
+ resolve(Object.fromEntries(new URLSearchParams(body)));
320
+ } else {
321
+ resolve(body); // Fallback para texto plano
322
+ }
323
+ } catch (error) {
324
+ // Pega qualquer outro erro síncrono
325
+ reject(error);
326
+ }
327
+ });
328
+
329
+ req.on('error', (error: Error) => {
330
+ clearTimeout(timeout);
331
+ reject(error);
332
+ });
333
+ });
334
+ };
335
+
336
+ /**
337
+ * Inicializa servidor nativo do Nyte.js usando HTTP ou HTTPS
338
+ */
339
+ async function initNativeServer(hwebApp: HWebApp, options: NyteOptions, port: number, hostname: string) {
340
+ const time = Date.now();
341
+
342
+ await hwebApp.prepare();
343
+
344
+ // Carrega a configuração do arquivo nytejs.config.js
345
+ const projectDir = options.dir || process.cwd();
346
+ const phase = options.dev ? 'development' : 'production';
347
+ const nyteConfig = await loadNyteConfig(projectDir, phase);
348
+
349
+ const handler = hwebApp.getRequestHandler();
350
+ const msg = Console.dynamicLine(`${Colors.Bright}Starting Nyte.js on port ${options.port}${Colors.Reset}`);
351
+
352
+ // --- LÓGICA DO LISTENER (REUTILIZÁVEL) ---
353
+ // Extraímos a lógica principal para uma variável
354
+ // para que possa ser usada tanto pelo servidor HTTP quanto HTTPS.
355
+ const requestListener = async (req: HWebIncomingMessage, res: ServerResponse) => {
356
+ const requestStartTime = Date.now();
357
+ const method = req.method || 'GET';
358
+ const url = req.url || '/';
359
+
360
+ // Aplica CORS se configurado
361
+ const corsHandled = applyCors(req, res, nyteConfig.cors);
362
+ if (corsHandled) {
363
+ // Requisição OPTIONS foi respondida pelo CORS
364
+ if (nyteConfig.accessLogging) {
365
+ const duration = Date.now() - requestStartTime;
366
+ Console.logCustomLevel('OPTIONS', true, Colors.BgMagenta, `${url} ${Colors.FgGreen}204${Colors.Reset} ${Colors.FgGray}${duration}ms${Colors.Reset} ${Colors.FgCyan}[CORS]${Colors.Reset}`);
367
+ }
368
+ return;
369
+ }
370
+
371
+ // Configurações de segurança básicas
372
+ res.setHeader('X-Content-Type-Options', 'nosniff');
373
+ res.setHeader('X-Frame-Options', 'DENY');
374
+ res.setHeader('X-XSS-Protection', '1; mode=block');
375
+ res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
376
+
377
+ // Aplica headers de segurança configurados
378
+ if (nyteConfig.security?.contentSecurityPolicy) {
379
+ res.setHeader('Content-Security-Policy', nyteConfig.security.contentSecurityPolicy);
380
+ }
381
+
382
+ if (nyteConfig.security?.permissionsPolicy) {
383
+ res.setHeader('Permissions-Policy', nyteConfig.security.permissionsPolicy);
384
+ }
385
+
386
+ const hstsValue = nyteConfig.security?.strictTransportSecurity || 'max-age=31536000; includeSubDomains';
387
+ res.setHeader('Strict-Transport-Security', hstsValue);
388
+
389
+ // Aplica headers personalizados
390
+ if (nyteConfig.customHeaders) {
391
+ for (const [headerName, headerValue] of Object.entries(nyteConfig.customHeaders)) {
392
+ res.setHeader(headerName, headerValue);
393
+ }
394
+ }
395
+
396
+ // Timeout por requisição (usa configuração personalizada)
397
+ req.setTimeout(nyteConfig.individualRequestTimeout || 30000, () => {
398
+ res.statusCode = 408; // Request Timeout
399
+ res.end('Request timeout');
400
+
401
+ // Log de timeout
402
+ if (nyteConfig.accessLogging) {
403
+ const duration = Date.now() - requestStartTime;
404
+ Console.info(`${Colors.FgYellow}${method}${Colors.Reset} ${url} ${Colors.FgRed}408${Colors.Reset} ${Colors.FgGray}${duration}ms${Colors.Reset}`);
405
+ }
406
+ });
407
+
408
+ // Intercepta o método end() para logar quando a resposta for enviada
409
+ const originalEnd = res.end.bind(res);
410
+ let hasEnded = false;
411
+
412
+ res.end = function(this: ServerResponse, ...args: any[]): any {
413
+ if (!hasEnded && nyteConfig.accessLogging) {
414
+ hasEnded = true;
415
+ const duration = Date.now() - requestStartTime;
416
+ const statusCode = res.statusCode || 200;
417
+
418
+ // Define cor baseada no status code
419
+ let statusColor = Colors.FgGreen; // 2xx
420
+ if (statusCode >= 500) statusColor = Colors.FgRed; // 5xx
421
+ else if (statusCode >= 400) statusColor = Colors.FgYellow; // 4xx
422
+ else if (statusCode >= 300) statusColor = Colors.FgCyan; // 3xx
423
+
424
+ // Formata o método com cor
425
+ let methodColor = Colors.FgCyan;
426
+ if (method === 'POST') methodColor = Colors.FgGreen;
427
+ else if (method === 'PUT') methodColor = Colors.FgYellow;
428
+ else if (method === 'DELETE') methodColor = Colors.FgRed;
429
+ else if (method === 'PATCH') methodColor = Colors.FgMagenta;
430
+ Console.logCustomLevel(method, true, methodColor, `${url} ${statusColor}${statusCode}${Colors.Reset} ${Colors.FgGray}${duration}ms${Colors.Reset}`);
431
+ }
432
+ // @ts-ignore
433
+ return originalEnd.apply(this, args);
434
+ } as any;
435
+
436
+ try {
437
+ // Validação básica de URL (usa configuração personalizada)
438
+ const maxUrlLength = nyteConfig.maxUrlLength || 2048;
439
+ if (url.length > maxUrlLength) {
440
+ res.statusCode = 414; // URI Too Long
441
+ res.end('URL too long');
442
+ return;
443
+ }
444
+
445
+ // Parse do body com proteções
446
+ req.body = await parseBody(req); // Assumindo que parseBody existe
447
+
448
+ // Adiciona host se não existir (necessário para `new URL`)
449
+ req.headers.host = req.headers.host || `localhost:${port}`;
450
+
451
+ // Chama o handler do Nyte.js
452
+ await handler(req, res);
453
+
454
+ } catch (error) {
455
+ // Log do erro no servidor
456
+ if (error instanceof Error) {
457
+ Console.error(`Native server error: ${error.message}`);
458
+ } else {
459
+ Console.error('Unknown native server error:', error);
460
+ }
461
+
462
+ // Tratamento de erro (idêntico ao seu original)
463
+ if (!res.headersSent) {
464
+ res.setHeader('Content-Type', 'text/plain');
465
+ if (error instanceof Error) {
466
+ if (error.message.includes('too large')) {
467
+ res.statusCode = 413; // Payload Too Large
468
+ res.end('Request too large');
469
+ } else if (error.message.includes('timeout')) {
470
+ res.statusCode = 408; // Request Timeout
471
+ res.end('Request timeout');
472
+ } else if (error.message.includes('Invalid JSON')) {
473
+ res.statusCode = 400; // Bad Request
474
+ res.end('Invalid JSON body');
475
+ } else {
476
+ res.statusCode = 500;
477
+ res.end('Internal server error');
478
+ }
479
+ } else {
480
+ res.statusCode = 500;
481
+ res.end('Internal server error');
482
+ }
483
+ }
484
+ }
485
+ };
486
+ // --- FIM DO LISTENER ---
487
+
488
+ let server: Server | HttpsServer; // O tipo do servidor pode variar
489
+ const isSSL = options.ssl && options.ssl.key && options.ssl.cert;
490
+
491
+ if (isSSL && options.ssl) {
492
+
493
+ const sslOptions = {
494
+ key: fs.readFileSync(options.ssl.key),
495
+ cert: fs.readFileSync(options.ssl.cert),
496
+ ca: options.ssl.ca ? fs.readFileSync(options.ssl.ca) : undefined
497
+ };
498
+
499
+ // 1. Cria o servidor HTTPS principal
500
+ server = https.createServer(sslOptions, requestListener as any); // (any para contornar HWebIncomingMessage)
501
+
502
+ // 2. Cria o servidor de REDIRECIONAMENTO (HTTP -> HTTPS)
503
+ const httpRedirectPort = options.ssl.redirectPort;
504
+ http.createServer((req, res) => {
505
+ const host = req.headers['host'] || hostname;
506
+ // Remove a porta do host (ex: meusite.com:80)
507
+ const hostWithoutPort = host.split(':')[0];
508
+
509
+ // Monta a URL de redirecionamento
510
+ let redirectUrl = `https://${hostWithoutPort}`;
511
+ // Adiciona a porta HTTPS apenas se não for a padrão (443)
512
+ if (port !== 443) {
513
+ redirectUrl += `:${port}`;
514
+ }
515
+ redirectUrl += req.url || '/';
516
+
517
+ res.writeHead(301, { 'Location': redirectUrl });
518
+ res.end();
519
+ }).listen(httpRedirectPort, hostname, () => {});
520
+
521
+ } else {
522
+ // --- MODO HTTP (Original) ---
523
+ // Cria o servidor HTTP nativo
524
+ server = http.createServer(requestListener as any); // (any para contornar HWebIncomingMessage)
525
+ }
526
+
527
+ // Configurações de segurança do servidor (usa configuração personalizada)
528
+ server.setTimeout(nyteConfig.serverTimeout || 35000); // Timeout geral do servidor
529
+ server.maxHeadersCount = nyteConfig.maxHeadersCount || 100; // Limita número de headers
530
+ server.headersTimeout = nyteConfig.headersTimeout || 60000; // Timeout para headers
531
+ server.requestTimeout = nyteConfig.requestTimeout || 30000; // Timeout para requisições
532
+
533
+ server.listen(port, hostname, () => {
534
+ sendBox({ ...options, port });
535
+ msg.end(`${Colors.Bright}Ready on port ${Colors.BgGreen} ${options.port} ${Colors.Reset}${Colors.Bright} in ${Date.now() - time}ms${Colors.Reset}\n`);
536
+ });
537
+
538
+ // Configura WebSocket para hot reload (Comum a ambos)
539
+ hwebApp.setupWebSocket(server);
540
+ hwebApp.executeInstrumentation();
541
+ return server;
542
+ }
543
+
544
+ // --- Função Principal ---
545
+
546
+ export function app(options: NyteOptions = {}) {
547
+ const framework = options.framework || 'native';
548
+ FrameworkAdapterFactory.setFramework(framework)
549
+
550
+ // Tipando a app principal do hweb
551
+ const hwebApp: HWebApp = hweb(options);
552
+
553
+ return {
554
+ ...hwebApp,
555
+
556
+ /**
557
+ * Integra com uma aplicação de qualquer framework (Express, Fastify, etc)
558
+ * O 'serverApp: any' é mantido para flexibilidade, já que pode ser de tipos diferentes.
559
+ */
560
+ integrate: async (serverApp: any) => {
561
+ await hwebApp.prepare();
562
+ const handler = hwebApp.getRequestHandler();
563
+
564
+ // O framework é setado nas opções do hweb, que deve
565
+ // retornar o handler correto em getRequestHandler()
566
+ // A lógica de integração original parece correta.
567
+
568
+ if (framework === 'express') {
569
+ const express = require('express');
570
+ try {
571
+ const cookieParser = require('cookie-parser');
572
+ serverApp.use(cookieParser());
573
+ } catch (e) {
574
+ Console.error("Could not find cookie-parser");
575
+ }
576
+ serverApp.use(express.json());
577
+ serverApp.use(express.urlencoded({ extended: true }));
578
+ serverApp.use(handler);
579
+ hwebApp.setupWebSocket(serverApp);
580
+
581
+ } else if (framework === 'fastify') {
582
+ try {
583
+ await serverApp.register(require('@fastify/cookie'));
584
+ } catch (e) {
585
+ Console.error("Could not find @fastify/cookie");
586
+ }
587
+ try {
588
+ await serverApp.register(require('@fastify/formbody'));
589
+ } catch (e) {
590
+ Console.error("Could not find @fastify/formbody");
591
+ }
592
+ await serverApp.register(async (fastify: any) => {
593
+ fastify.all('*', handler);
594
+ });
595
+ hwebApp.setupWebSocket(serverApp);
596
+
597
+ } else {
598
+ // Generic integration (assume Express-like)
599
+ serverApp.use(handler);
600
+ hwebApp.setupWebSocket(serverApp);
601
+ }
602
+
603
+ hwebApp.executeInstrumentation();
604
+ return serverApp;
605
+ },
606
+
607
+ /**
608
+ * Inicia um servidor Nyte.js fechado (o usuário não tem acesso ao framework)
609
+ */
610
+ init: async () => {
611
+ const currentVersion = require('../package.json').version;
612
+
613
+ async function verifyVersion(): Promise<string> {
614
+ // node fetch
615
+ try {
616
+ const response = await fetch('https://registry.npmjs.org/nyte/latest');
617
+ const data = await response.json();
618
+ return data.version;
619
+ } catch (error) {
620
+ Console.error('Could not check for the latest Nyte.js version:', error);
621
+ return currentVersion; // Retorna a versão atual em caso de erro
622
+ }
623
+ }
624
+ const latestVersion = await verifyVersion();
625
+ const isUpToDate = latestVersion === currentVersion;
626
+ let message;
627
+ if (!isUpToDate) {
628
+ message = `${Colors.FgRed} A new version is available (v${latestVersion})${Colors.FgMagenta}`
629
+ } else {
630
+ message = `${Colors.FgGreen} You are on the latest version${Colors.FgMagenta}`
631
+ }
632
+ // JS STICK LETTERS
633
+
634
+ console.log(`${Colors.Bright + Colors.FgCyan}
635
+ ${Colors.Bright + Colors.FgCyan} ___ ___ ${Colors.FgWhite} __
636
+ ${Colors.Bright + Colors.FgCyan} |\\ | \\ / | |__ ${Colors.FgWhite} | /__\` ${Colors.Bright + Colors.FgCyan}Nyte${Colors.FgWhite}.js ${Colors.FgGray}(v${require('../package.json').version}) - itsmuzin${Colors.FgMagenta}
637
+ ${Colors.Bright + Colors.FgCyan} | \\| | | |___ .${Colors.FgWhite} \\__/ .__/ ${message}
638
+
639
+ `)
640
+
641
+
642
+ const actualPort = options.port || 3000;
643
+ const actualHostname = options.hostname || "0.0.0.0";
644
+
645
+ if (framework !== 'native') {
646
+ Console.warn(`The "${framework}" framework was selected, but the init() method only works with the "native" framework. Starting native server...`);
647
+ }
648
+
649
+
650
+
651
+ return await initNativeServer(hwebApp, options, actualPort, actualHostname);
652
+ }
653
+ }
654
+ }
655
+
656
+ // Exporta a função 'app' como nomeada e também como padrão
657
+ export default app;