hightjs 0.1.1

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 (131) hide show
  1. package/.idea/HightJS.iml +9 -0
  2. package/.idea/copilot.data.migration.agent.xml +6 -0
  3. package/.idea/copilot.data.migration.ask.xml +6 -0
  4. package/.idea/copilot.data.migration.ask2agent.xml +6 -0
  5. package/.idea/copilot.data.migration.edit.xml +6 -0
  6. package/.idea/inspectionProfiles/Project_Default.xml +13 -0
  7. package/.idea/libraries/test_package.xml +9 -0
  8. package/.idea/libraries/ts_commonjs_default_export.xml +9 -0
  9. package/.idea/misc.xml +7 -0
  10. package/.idea/modules.xml +8 -0
  11. package/.idea/vcs.xml +6 -0
  12. package/LICENSE +13 -0
  13. package/README.md +508 -0
  14. package/dist/adapters/express.d.ts +7 -0
  15. package/dist/adapters/express.js +63 -0
  16. package/dist/adapters/factory.d.ts +23 -0
  17. package/dist/adapters/factory.js +122 -0
  18. package/dist/adapters/fastify.d.ts +25 -0
  19. package/dist/adapters/fastify.js +61 -0
  20. package/dist/adapters/native.d.ts +8 -0
  21. package/dist/adapters/native.js +203 -0
  22. package/dist/adapters/starters/express.d.ts +0 -0
  23. package/dist/adapters/starters/express.js +1 -0
  24. package/dist/adapters/starters/factory.d.ts +0 -0
  25. package/dist/adapters/starters/factory.js +1 -0
  26. package/dist/adapters/starters/fastify.d.ts +0 -0
  27. package/dist/adapters/starters/fastify.js +1 -0
  28. package/dist/adapters/starters/index.d.ts +0 -0
  29. package/dist/adapters/starters/index.js +1 -0
  30. package/dist/adapters/starters/native.d.ts +0 -0
  31. package/dist/adapters/starters/native.js +1 -0
  32. package/dist/api/console.d.ts +92 -0
  33. package/dist/api/console.js +276 -0
  34. package/dist/api/http.d.ts +180 -0
  35. package/dist/api/http.js +467 -0
  36. package/dist/auth/client.d.ts +14 -0
  37. package/dist/auth/client.js +68 -0
  38. package/dist/auth/components.d.ts +29 -0
  39. package/dist/auth/components.js +84 -0
  40. package/dist/auth/core.d.ts +38 -0
  41. package/dist/auth/core.js +124 -0
  42. package/dist/auth/index.d.ts +7 -0
  43. package/dist/auth/index.js +27 -0
  44. package/dist/auth/jwt.d.ts +41 -0
  45. package/dist/auth/jwt.js +169 -0
  46. package/dist/auth/providers.d.ts +5 -0
  47. package/dist/auth/providers.js +14 -0
  48. package/dist/auth/react/index.d.ts +6 -0
  49. package/dist/auth/react/index.js +32 -0
  50. package/dist/auth/react.d.ts +22 -0
  51. package/dist/auth/react.js +175 -0
  52. package/dist/auth/routes.d.ts +16 -0
  53. package/dist/auth/routes.js +104 -0
  54. package/dist/auth/types.d.ts +62 -0
  55. package/dist/auth/types.js +2 -0
  56. package/dist/bin/hightjs.d.ts +2 -0
  57. package/dist/bin/hightjs.js +35 -0
  58. package/dist/builder.d.ts +32 -0
  59. package/dist/builder.js +341 -0
  60. package/dist/client/DefaultNotFound.d.ts +1 -0
  61. package/dist/client/DefaultNotFound.js +53 -0
  62. package/dist/client/ErrorBoundary.d.ts +16 -0
  63. package/dist/client/ErrorBoundary.js +181 -0
  64. package/dist/client/clientRouter.d.ts +58 -0
  65. package/dist/client/clientRouter.js +116 -0
  66. package/dist/client/entry.client.d.ts +1 -0
  67. package/dist/client/entry.client.js +271 -0
  68. package/dist/client/routerContext.d.ts +26 -0
  69. package/dist/client/routerContext.js +62 -0
  70. package/dist/client.d.ts +3 -0
  71. package/dist/client.js +8 -0
  72. package/dist/components/Link.d.ts +7 -0
  73. package/dist/components/Link.js +13 -0
  74. package/dist/eslint/index.d.ts +32 -0
  75. package/dist/eslint/index.js +15 -0
  76. package/dist/eslint/use-client-rule.d.ts +19 -0
  77. package/dist/eslint/use-client-rule.js +99 -0
  78. package/dist/eslintSetup.d.ts +0 -0
  79. package/dist/eslintSetup.js +1 -0
  80. package/dist/example/src/web/routes/index.d.ts +3 -0
  81. package/dist/example/src/web/routes/index.js +15 -0
  82. package/dist/helpers.d.ts +18 -0
  83. package/dist/helpers.js +318 -0
  84. package/dist/hotReload.d.ts +23 -0
  85. package/dist/hotReload.js +292 -0
  86. package/dist/index.d.ts +17 -0
  87. package/dist/index.js +480 -0
  88. package/dist/renderer.d.ts +14 -0
  89. package/dist/renderer.js +106 -0
  90. package/dist/router.d.ts +78 -0
  91. package/dist/router.js +359 -0
  92. package/dist/types/framework.d.ts +37 -0
  93. package/dist/types/framework.js +2 -0
  94. package/dist/types.d.ts +43 -0
  95. package/dist/types.js +2 -0
  96. package/dist/typescript/use-client-plugin.d.ts +5 -0
  97. package/dist/typescript/use-client-plugin.js +113 -0
  98. package/dist/validation.d.ts +0 -0
  99. package/dist/validation.js +1 -0
  100. package/package.json +72 -0
  101. package/src/adapters/express.ts +70 -0
  102. package/src/adapters/factory.ts +96 -0
  103. package/src/adapters/fastify.ts +88 -0
  104. package/src/adapters/native.ts +223 -0
  105. package/src/api/console.ts +285 -0
  106. package/src/api/http.ts +515 -0
  107. package/src/auth/client.ts +74 -0
  108. package/src/auth/components.tsx +109 -0
  109. package/src/auth/core.ts +143 -0
  110. package/src/auth/index.ts +9 -0
  111. package/src/auth/jwt.ts +194 -0
  112. package/src/auth/providers.ts +13 -0
  113. package/src/auth/react/index.ts +9 -0
  114. package/src/auth/react.tsx +209 -0
  115. package/src/auth/routes.ts +133 -0
  116. package/src/auth/types.ts +73 -0
  117. package/src/bin/hightjs.js +40 -0
  118. package/src/builder.js +362 -0
  119. package/src/client/DefaultNotFound.tsx +68 -0
  120. package/src/client/clientRouter.ts +137 -0
  121. package/src/client/entry.client.tsx +302 -0
  122. package/src/client.ts +8 -0
  123. package/src/components/Link.tsx +22 -0
  124. package/src/helpers.ts +316 -0
  125. package/src/hotReload.ts +289 -0
  126. package/src/index.ts +514 -0
  127. package/src/renderer.tsx +122 -0
  128. package/src/router.ts +400 -0
  129. package/src/types/framework.ts +42 -0
  130. package/src/types.ts +54 -0
  131. package/tsconfig.json +17 -0
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "hightjs",
3
+ "version": "0.1.1",
4
+ "description": "HightJS is a high-level framework for building web applications with ease and speed. It provides a robust set of tools and features to streamline development and enhance productivity.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "author": "itsmuzin",
8
+ "license": "Apache-2.0",
9
+ "scripts": {
10
+ "build": "tsc"
11
+ },
12
+ "bin": {
13
+ "hight": "./dist/bin/hightjs.js"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.js"
20
+ },
21
+ "./client": {
22
+ "types": "./dist/client.d.ts",
23
+ "import": "./dist/client.js",
24
+ "require": "./dist/client.js"
25
+ },
26
+ "./helpers": {
27
+ "types": "./dist/helpers.d.ts",
28
+ "import": "./dist/helpers.js",
29
+ "require": "./dist/helpers.js"
30
+ },
31
+ "./auth": {
32
+ "types": "./dist/auth/index.d.ts",
33
+ "import": "./dist/auth/index.js",
34
+ "require": "./dist/auth/index.js"
35
+ },
36
+ "./auth/react": {
37
+ "types": "./dist/auth/react/index.d.ts",
38
+ "import": "./dist/auth/react/index.js",
39
+ "require": "./dist/auth/react/index.js"
40
+ },
41
+ "./eslint": {
42
+ "types": "./dist/eslint/index.d.ts",
43
+ "import": "./dist/eslint/index.js",
44
+ "require": "./dist/eslint/index.js"
45
+ }
46
+ },
47
+ "devDependencies": {
48
+ "@types/express": "^4.17.21",
49
+ "@types/node": "^20.11.24",
50
+ "@types/react": "^19.2.0",
51
+ "@types/react-dom": "^19.2.0",
52
+ "@types/ws": "^8.18.1",
53
+ "ts-loader": "^9.5.4",
54
+ "ts-node": "^10.9.2",
55
+ "typescript": "^5.9.3"
56
+ },
57
+ "dependencies": {
58
+ "@fastify/cookie": "^11.0.2",
59
+ "@fastify/formbody": "^8.0.2",
60
+ "boxen": "^8.0.1",
61
+ "chokidar": "^3.5.3",
62
+ "commander": "^14.0.1",
63
+ "cookie-parser": "^1.4.7",
64
+ "esbuild": "^0.25.10",
65
+ "express": "^4.0.0",
66
+ "fastify": "^5.6.1",
67
+ "framer-motion": "^12.23.22",
68
+ "react": "^19.2.0",
69
+ "react-dom": "^19.2.0",
70
+ "ws": "^8.18.1"
71
+ }
72
+ }
@@ -0,0 +1,70 @@
1
+ import type { Request as ExpressRequest, Response as ExpressResponse } from 'express';
2
+ import { GenericRequest, GenericResponse, FrameworkAdapter, CookieOptions } from '../types/framework';
3
+
4
+ export class ExpressAdapter implements FrameworkAdapter {
5
+ type = 'express' as const;
6
+
7
+ parseRequest(req: ExpressRequest): GenericRequest {
8
+ return {
9
+ method: req.method,
10
+ url: req.url,
11
+ headers: req.headers as Record<string, string | string[]>,
12
+ body: req.body,
13
+ query: req.query as Record<string, any>,
14
+ params: req.params,
15
+ cookies: req.cookies || {},
16
+ raw: req
17
+ };
18
+ }
19
+
20
+ createResponse(res: ExpressResponse): GenericResponse {
21
+ return new ExpressResponseWrapper(res);
22
+ }
23
+ }
24
+
25
+ class ExpressResponseWrapper implements GenericResponse {
26
+ constructor(private res: ExpressResponse) {}
27
+
28
+ get raw() {
29
+ return this.res;
30
+ }
31
+
32
+ status(code: number): GenericResponse {
33
+ this.res.status(code);
34
+ return this;
35
+ }
36
+
37
+ header(name: string, value: string): GenericResponse {
38
+ this.res.setHeader(name, value);
39
+ return this;
40
+ }
41
+
42
+ cookie(name: string, value: string, options?: CookieOptions): GenericResponse {
43
+ this.res.cookie(name, value, options || {});
44
+ return this;
45
+ }
46
+
47
+ clearCookie(name: string, options?: CookieOptions): GenericResponse {
48
+ // Filter out the deprecated 'expires' option to avoid Express deprecation warning
49
+ const { expires, ...filteredOptions } = options || {};
50
+ this.res.clearCookie(name, filteredOptions);
51
+ return this;
52
+ }
53
+
54
+ json(data: any): void {
55
+ this.res.json(data);
56
+ }
57
+
58
+ text(data: string): void {
59
+ this.res.setHeader('Content-Type', 'text/plain; charset=utf-8');
60
+ this.res.send(data);
61
+ }
62
+
63
+ send(data: any): void {
64
+ this.res.send(data);
65
+ }
66
+
67
+ redirect(url: string): void {
68
+ this.res.redirect(url);
69
+ }
70
+ }
@@ -0,0 +1,96 @@
1
+ import { FrameworkAdapter } from '../types/framework';
2
+ import { ExpressAdapter } from './express';
3
+ import { FastifyAdapter } from './fastify';
4
+ import { NativeAdapter } from './native';
5
+ import Console, { Colors} from "../api/console"
6
+ /**
7
+ * Factory para criar o adapter correto baseado no framework detectado
8
+ */
9
+ export class FrameworkAdapterFactory {
10
+ private static adapter: FrameworkAdapter | null = null;
11
+
12
+ /**
13
+ * Detecta automaticamente o framework baseado na requisição/resposta
14
+ */
15
+ static detectFramework(req: any, res: any): FrameworkAdapter {
16
+ // Se já detectamos antes, retorna o mesmo adapter
17
+ if (this.adapter) {
18
+ return this.adapter;
19
+ }
20
+ const msg = Console.dynamicLine(` ${Colors.FgYellow}● ${Colors.Reset}Detectando framework web...`);
21
+
22
+ // Detecta Express
23
+ if (req.app && req.route && res.locals !== undefined) {
24
+ msg.end(` ${Colors.FgGreen}● ${Colors.Reset}Framework detectado: Express`);
25
+ this.adapter = new ExpressAdapter();
26
+ return this.adapter;
27
+ }
28
+
29
+ // Detecta Fastify
30
+ if (req.server && req.routerPath !== undefined && res.request) {
31
+ msg.end(` ${Colors.FgGreen}● ${Colors.Reset}Framework detectado: Fastify`);
32
+ this.adapter = new FastifyAdapter();
33
+ return this.adapter;
34
+ }
35
+
36
+ // Detecta HTTP nativo do Node.js
37
+ if (req.method !== undefined && req.url !== undefined && req.headers !== undefined &&
38
+ res.statusCode !== undefined && res.setHeader !== undefined && res.end !== undefined) {
39
+ msg.end(` ${Colors.FgGreen}● ${Colors.Reset}Framework detectado: HightJS Native (HTTP)`);
40
+ this.adapter = new NativeAdapter();
41
+ return this.adapter;
42
+ }
43
+
44
+ // Fallback mais específico para Express
45
+ if (res.status && res.send && res.json && res.cookie) {
46
+ msg.end(` ${Colors.FgGreen}● ${Colors.Reset}Framework detectado: Express (fallback)`);
47
+ this.adapter = new ExpressAdapter();
48
+ return this.adapter;
49
+ }
50
+
51
+ // Fallback mais específico para Fastify
52
+ if (res.code && res.send && res.type && res.setCookie) {
53
+ msg.end(` ${Colors.FgGreen}● ${Colors.Reset}Framework detectado: Fastify (fallback)`);
54
+ this.adapter = new FastifyAdapter();
55
+ return this.adapter;
56
+ }
57
+
58
+ // Default para HightJS Native se não conseguir detectar
59
+ msg.end(` ${Colors.FgYellow}● ${Colors.Reset}Não foi possível detectar o framework. Usando HightJS Native como padrão.`);
60
+ this.adapter = new NativeAdapter();
61
+ return this.adapter;
62
+ }
63
+
64
+ /**
65
+ * Força o uso de um framework específico
66
+ */
67
+ static setFramework(framework: 'express' | 'fastify' | 'native'): void {
68
+ switch (framework) {
69
+ case 'express':
70
+ this.adapter = new ExpressAdapter();
71
+ break;
72
+ case 'fastify':
73
+ this.adapter = new FastifyAdapter();
74
+ break;
75
+ case 'native':
76
+ this.adapter = new NativeAdapter();
77
+ break;
78
+ default:
79
+ throw new Error(`Framework não suportado: ${framework}`);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Reset do adapter (útil para testes)
85
+ */
86
+ static reset(): void {
87
+ this.adapter = null;
88
+ }
89
+
90
+ /**
91
+ * Retorna o adapter atual (se já foi detectado)
92
+ */
93
+ static getCurrentAdapter(): FrameworkAdapter | null {
94
+ return this.adapter;
95
+ }
96
+ }
@@ -0,0 +1,88 @@
1
+ // Tipos para Fastify (sem import direto para evitar dependência obrigatória)
2
+ interface FastifyRequest {
3
+ method: string;
4
+ url: string;
5
+ headers: Record<string, string | string[]>;
6
+ body?: any;
7
+ query?: Record<string, any>;
8
+ params?: Record<string, string>;
9
+ cookies?: Record<string, string>;
10
+ }
11
+
12
+ interface FastifyReply {
13
+ status(code: number): FastifyReply;
14
+ header(name: string, value: string): FastifyReply;
15
+ setCookie(name: string, value: string, options?: any): FastifyReply;
16
+ clearCookie(name: string, options?: any): FastifyReply;
17
+ type(contentType: string): FastifyReply;
18
+ send(data: any): void;
19
+ redirect(url: string): void;
20
+ }
21
+
22
+ import { GenericRequest, GenericResponse, FrameworkAdapter, CookieOptions } from '../types/framework';
23
+
24
+ export class FastifyAdapter implements FrameworkAdapter {
25
+ type = 'fastify' as const;
26
+
27
+ parseRequest(req: FastifyRequest): GenericRequest {
28
+ return {
29
+ method: req.method,
30
+ url: req.url,
31
+ headers: req.headers as Record<string, string | string[]>,
32
+ body: req.body,
33
+ query: req.query as Record<string, any>,
34
+ params: req.params as Record<string, string>,
35
+ cookies: req.cookies || {},
36
+ raw: req
37
+ };
38
+ }
39
+
40
+ createResponse(reply: FastifyReply): GenericResponse {
41
+ return new FastifyResponseWrapper(reply);
42
+ }
43
+ }
44
+
45
+ class FastifyResponseWrapper implements GenericResponse {
46
+ constructor(private reply: FastifyReply) {}
47
+
48
+ get raw() {
49
+ return this.reply;
50
+ }
51
+
52
+ status(code: number): GenericResponse {
53
+ this.reply.status(code);
54
+ return this;
55
+ }
56
+
57
+ header(name: string, value: string): GenericResponse {
58
+ this.reply.header(name, value);
59
+ return this;
60
+ }
61
+
62
+ cookie(name: string, value: string, options?: CookieOptions): GenericResponse {
63
+ this.reply.setCookie(name, value, options);
64
+ return this;
65
+ }
66
+
67
+ clearCookie(name: string, options?: CookieOptions): GenericResponse {
68
+ this.reply.clearCookie(name, options);
69
+ return this;
70
+ }
71
+
72
+ json(data: any): void {
73
+ this.reply.send(data);
74
+ }
75
+
76
+ text(data: string): void {
77
+ this.reply.type('text/plain; charset=utf-8');
78
+ this.reply.send(data);
79
+ }
80
+
81
+ send(data: any): void {
82
+ this.reply.send(data);
83
+ }
84
+
85
+ redirect(url: string): void {
86
+ this.reply.redirect(url);
87
+ }
88
+ }
@@ -0,0 +1,223 @@
1
+ import type { IncomingMessage, ServerResponse } from 'http';
2
+ import { GenericRequest, GenericResponse, FrameworkAdapter, CookieOptions } from '../types/framework';
3
+ import { parse as parseUrl } from 'url';
4
+
5
+ // --- Funções Auxiliares de Segurança ---
6
+
7
+ /**
8
+ * Remove caracteres de quebra de linha (\r, \n) de uma string para prevenir
9
+ * ataques de HTTP Header Injection (CRLF Injection).
10
+ * @param value O valor a ser sanitizado.
11
+ * @returns A string sanitizada.
12
+ */
13
+ function sanitizeHeaderValue(value: string | number | boolean): string {
14
+ return String(value).replace(/[\r\n]/g, '');
15
+ }
16
+
17
+ /**
18
+ * Valida se o nome de um cookie contém apenas caracteres permitidos pela RFC 6265.
19
+ * Isso previne a criação de cookies com nomes inválidos ou maliciosos.
20
+ * @param name O nome do cookie a ser validado.
21
+ * @returns `true` se o nome for válido, `false` caso contrário.
22
+ */
23
+ function isValidCookieName(name: string): boolean {
24
+ // A RFC 6265 define 'token' como 1 ou mais caracteres que não são controle nem separadores.
25
+ // Separadores: ( ) < > @ , ; : \ " / [ ] ? = { }
26
+ const validCookieNameRegex = /^[a-zA-Z0-9!#$%&'*+-.^_`|~]+$/;
27
+ return validCookieNameRegex.test(name);
28
+ }
29
+
30
+
31
+ export class NativeAdapter implements FrameworkAdapter {
32
+ type = 'native' as const;
33
+
34
+ parseRequest(req: IncomingMessage): GenericRequest {
35
+ const url = parseUrl(req.url || '', true);
36
+
37
+ return {
38
+ method: req.method || 'GET',
39
+ url: req.url || '/',
40
+ headers: req.headers as Record<string, string | string[]>,
41
+ // Adicionado fallback para null para maior segurança caso o body parser não tenha rodado.
42
+ body: (req as any).body ?? null,
43
+ // Tipo mais específico para a query.
44
+ query: url.query as Record<string, string | string[]>,
45
+ params: {}, // Será preenchido pelo roteador
46
+ cookies: this.parseCookies(req.headers.cookie || ''),
47
+ raw: req
48
+ };
49
+ }
50
+
51
+ createResponse(res: ServerResponse): GenericResponse {
52
+ return new NativeResponseWrapper(res);
53
+ }
54
+
55
+ private parseCookies(cookieHeader: string): Record<string, string> {
56
+ const cookies: Record<string, string> = {};
57
+
58
+ if (!cookieHeader) return cookies;
59
+
60
+ cookieHeader.split(';').forEach(cookie => {
61
+ const [name, ...rest] = cookie.trim().split('=');
62
+ if (name && rest.length > 0) {
63
+ try {
64
+ // Tenta decodificar o valor do cookie.
65
+ cookies[name] = decodeURIComponent(rest.join('='));
66
+ } catch (e) {
67
+ // Prevenção de crash: Ignora cookies com valores malformados (e.g., URI inválida).
68
+ console.error(`Aviso: Cookie malformado com nome "${name}" foi ignorado.`);
69
+ }
70
+ }
71
+ });
72
+
73
+ return cookies;
74
+ }
75
+ }
76
+
77
+ class NativeResponseWrapper implements GenericResponse {
78
+ private statusCode = 200;
79
+ private headers: Record<string, string | number> = {};
80
+ private cookiesToSet: string[] = []; // Array para lidar corretamente com múltiplos cookies.
81
+ private finished = false;
82
+
83
+ constructor(private res: ServerResponse) {}
84
+
85
+ get raw() {
86
+ return this.res;
87
+ }
88
+
89
+ status(code: number): GenericResponse {
90
+ this.statusCode = code;
91
+ return this;
92
+ }
93
+
94
+ header(name: string, value: string): GenericResponse {
95
+ // Medida de segurança CRÍTICA: Previne HTTP Header Injection (CRLF Injection).
96
+ // Sanitiza tanto o nome quanto o valor do header para remover quebras de linha.
97
+ const sanitizedName = sanitizeHeaderValue(name);
98
+ const sanitizedValue = sanitizeHeaderValue(value);
99
+
100
+ if (name !== sanitizedName || String(value) !== sanitizedValue) {
101
+ console.warn(`Aviso: Tentativa potencial de HTTP Header Injection foi detectada e sanitizada. Header original: "${name}"`);
102
+ }
103
+
104
+ // Evita setar o header 'Set-Cookie' diretamente para não conflitar com o método cookie().
105
+ if (sanitizedName.toLowerCase() === 'set-cookie') {
106
+ console.warn(`Aviso: Use o método .cookie() para definir cookies, não o .header().`);
107
+ return this;
108
+ }
109
+
110
+ this.headers[sanitizedName] = sanitizedValue;
111
+ return this;
112
+ }
113
+
114
+ cookie(name: string, value: string, options?: CookieOptions): GenericResponse {
115
+ // Medida de segurança: Valida o nome do cookie.
116
+ if (!isValidCookieName(name)) {
117
+ console.error(`Erro: Nome de cookie inválido "${name}". O cookie não será definido.`);
118
+ return this;
119
+ }
120
+
121
+ let cookieString = `${name}=${encodeURIComponent(value)}`;
122
+
123
+ if (options) {
124
+ // Sanitiza as opções que são strings para prevenir Header Injection.
125
+ if (options.domain) cookieString += `; Domain=${sanitizeHeaderValue(options.domain)}`;
126
+ if (options.path) cookieString += `; Path=${sanitizeHeaderValue(options.path)}`;
127
+ if (options.expires) cookieString += `; Expires=${options.expires.toUTCString()}`;
128
+ if (options.maxAge) cookieString += `; Max-Age=${options.maxAge}`;
129
+ if (options.httpOnly) cookieString += '; HttpOnly';
130
+ if (options.secure) cookieString += '; Secure';
131
+ if (options.sameSite) {
132
+ const sameSiteValue = typeof options.sameSite === 'boolean' ? 'Strict' : options.sameSite;
133
+ cookieString += `; SameSite=${sanitizeHeaderValue(sameSiteValue)}`;
134
+ }
135
+ }
136
+
137
+ this.cookiesToSet.push(cookieString);
138
+ return this;
139
+ }
140
+
141
+ clearCookie(name: string, options?: CookieOptions): GenericResponse {
142
+ const clearOptions = { ...options, expires: new Date(0), maxAge: 0 };
143
+ return this.cookie(name, '', clearOptions);
144
+ }
145
+
146
+ private writeHeaders(): void {
147
+ if (this.finished) return;
148
+
149
+ this.res.statusCode = this.statusCode;
150
+
151
+ Object.entries(this.headers).forEach(([name, value]) => {
152
+ this.res.setHeader(name, value);
153
+ });
154
+
155
+ // CORREÇÃO: Envia múltiplos cookies corretamente como headers 'Set-Cookie' separados.
156
+ // O método antigo de juntar com vírgula estava incorreto.
157
+ if (this.cookiesToSet.length > 0) {
158
+ this.res.setHeader('Set-Cookie', this.cookiesToSet);
159
+ }
160
+ }
161
+
162
+ json(data: any): void {
163
+ if (this.finished) return;
164
+
165
+ this.header('Content-Type', 'application/json; charset=utf-8');
166
+ this.writeHeaders();
167
+
168
+ const jsonString = JSON.stringify(data);
169
+ this.res.end(jsonString);
170
+ this.finished = true;
171
+ }
172
+
173
+ text(data: string): void {
174
+ if (this.finished) return;
175
+
176
+ this.header('Content-Type', 'text/plain; charset=utf-8');
177
+ this.writeHeaders();
178
+
179
+ this.res.end(data);
180
+ this.finished = true;
181
+ }
182
+
183
+ send(data: any): void {
184
+ if (this.finished) return;
185
+
186
+ const existingContentType = this.headers['Content-Type'];
187
+
188
+ if (typeof data === 'string') {
189
+ if (!existingContentType) {
190
+ this.header('Content-Type', 'text/plain; charset=utf-8');
191
+ }
192
+ this.writeHeaders();
193
+ this.res.end(data);
194
+ } else if (Buffer.isBuffer(data)) {
195
+ this.writeHeaders();
196
+ this.res.end(data);
197
+ } else if (data !== null && typeof data === 'object') {
198
+ this.json(data); // Reutiliza o método json para consistência
199
+ return; // O método json já finaliza a resposta
200
+ } else {
201
+ if (!existingContentType) {
202
+ this.header('Content-Type', 'text/plain; charset=utf-8');
203
+ }
204
+ this.writeHeaders();
205
+ this.res.end(String(data));
206
+ }
207
+
208
+ this.finished = true;
209
+ }
210
+
211
+ redirect(url: string): void {
212
+ if (this.finished) return;
213
+
214
+ this.status(302);
215
+ // A sanitização no método .header() previne que um URL manipulado
216
+ // cause um ataque de Open Redirect via Header Injection.
217
+ this.header('Location', url);
218
+ this.writeHeaders();
219
+
220
+ this.res.end();
221
+ this.finished = true;
222
+ }
223
+ }