hightjs 0.2.42 → 0.2.45
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/.idea/copilotDiffState.xml +67 -0
- package/README.md +26 -514
- package/dist/auth/core.js +3 -3
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.js +2 -1
- package/dist/auth/providers/google.d.ts +63 -0
- package/dist/auth/providers/google.js +186 -0
- package/dist/auth/providers.d.ts +1 -0
- package/dist/auth/providers.js +3 -1
- package/dist/auth/types.d.ts +6 -7
- package/dist/bin/hightjs.js +393 -0
- package/dist/client/entry.client.js +11 -1
- package/dist/hotReload.d.ts +8 -1
- package/dist/hotReload.js +304 -144
- package/dist/index.d.ts +2 -1
- package/dist/index.js +20 -33
- package/dist/renderer.js +1 -1
- package/dist/router.d.ts +24 -1
- package/dist/router.js +201 -2
- package/dist/types.d.ts +19 -1
- package/docs/README.md +59 -0
- package/docs/adapters.md +7 -0
- package/docs/arquivos-especiais.md +10 -0
- package/docs/autenticacao.md +212 -0
- package/docs/checklist.md +9 -0
- package/docs/cli.md +21 -0
- package/docs/estrutura.md +20 -0
- package/docs/faq.md +10 -0
- package/docs/hot-reload.md +5 -0
- package/docs/middlewares.md +73 -0
- package/docs/rotas-backend.md +45 -0
- package/docs/rotas-frontend.md +66 -0
- package/docs/seguranca.md +8 -0
- package/docs/websocket.md +45 -0
- package/package.json +1 -1
- package/src/auth/core.ts +3 -3
- package/src/auth/index.ts +2 -3
- package/src/auth/providers/google.ts +218 -0
- package/src/auth/providers.ts +1 -1
- package/src/auth/types.ts +3 -8
- package/src/bin/hightjs.js +475 -0
- package/src/client/entry.client.tsx +12 -1
- package/src/hotReload.ts +333 -147
- package/src/index.ts +58 -51
- package/src/renderer.tsx +1 -1
- package/src/router.ts +230 -3
- package/src/types.ts +24 -1
- package/dist/adapters/starters/express.d.ts +0 -0
- package/dist/adapters/starters/express.js +0 -1
- package/dist/adapters/starters/factory.d.ts +0 -0
- package/dist/adapters/starters/factory.js +0 -1
- package/dist/adapters/starters/fastify.d.ts +0 -0
- package/dist/adapters/starters/fastify.js +0 -1
- package/dist/adapters/starters/index.d.ts +0 -0
- package/dist/adapters/starters/index.js +0 -1
- package/dist/adapters/starters/native.d.ts +0 -0
- package/dist/adapters/starters/native.js +0 -1
- package/dist/auth/example.d.ts +0 -40
- package/dist/auth/example.js +0 -104
- package/dist/client/ErrorBoundary.d.ts +0 -16
- package/dist/client/ErrorBoundary.js +0 -181
- package/dist/client/routerContext.d.ts +0 -26
- package/dist/client/routerContext.js +0 -62
- package/dist/eslint/index.d.ts +0 -32
- package/dist/eslint/index.js +0 -15
- package/dist/eslint/use-client-rule.d.ts +0 -19
- package/dist/eslint/use-client-rule.js +0 -99
- package/dist/eslintSetup.d.ts +0 -0
- package/dist/eslintSetup.js +0 -1
- package/dist/example/src/web/routes/index.d.ts +0 -3
- package/dist/example/src/web/routes/index.js +0 -15
- package/dist/typescript/use-client-plugin.d.ts +0 -5
- package/dist/typescript/use-client-plugin.js +0 -113
- package/dist/validation.d.ts +0 -0
- package/dist/validation.js +0 -1
- package/src/auth/example.ts +0 -115
package/src/index.ts
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
1
|
import path from 'path';
|
|
3
2
|
import fs from 'fs';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
import {ExpressAdapter} from './adapters/express';
|
|
4
|
+
import {build, buildWithChunks, watch, watchWithChunks} from './builder';
|
|
5
|
+
import {
|
|
6
|
+
BackendHandler,
|
|
7
|
+
BackendRouteConfig,
|
|
8
|
+
HightJSOptions,
|
|
9
|
+
HightMiddleware,
|
|
10
|
+
RequestHandler,
|
|
11
|
+
RouteConfig
|
|
12
|
+
} from './types';
|
|
13
|
+
import {
|
|
14
|
+
findMatchingBackendRoute,
|
|
15
|
+
findMatchingRoute,
|
|
16
|
+
getLayout,
|
|
17
|
+
getNotFound,
|
|
18
|
+
loadBackendRoutes,
|
|
19
|
+
loadLayout,
|
|
20
|
+
loadNotFound,
|
|
21
|
+
loadRoutes,
|
|
22
|
+
processWebSocketRoutes,
|
|
23
|
+
setupWebSocketUpgrade
|
|
24
|
+
} from './router';
|
|
25
|
+
import {render} from './renderer';
|
|
26
|
+
import {HightJSRequest, HightJSResponse} from './api/http';
|
|
27
|
+
import {HotReloadManager} from './hotReload';
|
|
28
|
+
import {FrameworkAdapterFactory} from './adapters/factory';
|
|
29
|
+
import {GenericRequest, GenericResponse} from './types/framework';
|
|
13
30
|
import Console, {Colors} from "./api/console"
|
|
14
31
|
|
|
15
32
|
// Exporta apenas os tipos e classes para o backend
|
|
@@ -25,7 +42,8 @@ export type { GenericRequest, GenericResponse, CookieOptions } from './types/fra
|
|
|
25
42
|
// Exporta os helpers para facilitar integração
|
|
26
43
|
export { app } from './helpers';
|
|
27
44
|
|
|
28
|
-
// Exporta o sistema de
|
|
45
|
+
// Exporta o sistema de WebSocket
|
|
46
|
+
export type { WebSocketContext, WebSocketHandler } from './types';
|
|
29
47
|
|
|
30
48
|
// Função para verificar se o projeto é grande o suficiente para se beneficiar de chunks
|
|
31
49
|
function isLargeProject(projectDir: string): boolean {
|
|
@@ -207,22 +225,26 @@ export default function hweb(options: HightJSOptions) {
|
|
|
207
225
|
entryPoint = createEntryFile(dir, frontendRoutes);
|
|
208
226
|
};
|
|
209
227
|
|
|
210
|
-
|
|
228
|
+
return {
|
|
211
229
|
prepare: async () => {
|
|
212
230
|
const isProduction = !dev;
|
|
213
231
|
|
|
214
232
|
if (!isProduction) {
|
|
215
|
-
// Inicia hot reload apenas em desenvolvimento (
|
|
233
|
+
// Inicia hot reload apenas em desenvolvimento (com suporte ao main)
|
|
216
234
|
hotReloadManager = new HotReloadManager(dir);
|
|
217
235
|
await hotReloadManager.start();
|
|
218
236
|
|
|
219
|
-
// Adiciona callback para recarregar
|
|
237
|
+
// Adiciona callback para recarregar TUDO quando qualquer arquivo mudar
|
|
220
238
|
hotReloadManager.onBackendApiChange(() => {
|
|
239
|
+
Console.info('🔄 Recarregando backend e dependências...');
|
|
240
|
+
|
|
221
241
|
loadBackendRoutes(userBackendRoutesDir);
|
|
242
|
+
processWebSocketRoutes(); // Processa rotas WS após recarregar backend
|
|
222
243
|
});
|
|
223
244
|
|
|
224
245
|
// Adiciona callback para regenerar entry file quando frontend mudar
|
|
225
246
|
hotReloadManager.onFrontendChange(() => {
|
|
247
|
+
Console.info('🔄 Regenerando frontend...');
|
|
226
248
|
regenerateEntryFile();
|
|
227
249
|
});
|
|
228
250
|
}
|
|
@@ -231,13 +253,16 @@ export default function hweb(options: HightJSOptions) {
|
|
|
231
253
|
frontendRoutes = loadRoutes(userWebRoutesDir);
|
|
232
254
|
loadBackendRoutes(userBackendRoutesDir);
|
|
233
255
|
|
|
234
|
-
|
|
256
|
+
// Processa rotas WebSocket após carregar backend
|
|
257
|
+
processWebSocketRoutes();
|
|
258
|
+
|
|
259
|
+
// Carrega layout.tsx ANTES de criar o entry file
|
|
235
260
|
const layout = loadLayout(userWebDir);
|
|
236
261
|
|
|
237
|
-
|
|
262
|
+
const notFound = loadNotFound(userWebDir);
|
|
238
263
|
|
|
239
264
|
const outDir = path.join(dir, 'hweb-dist');
|
|
240
|
-
fs.mkdirSync(outDir, {
|
|
265
|
+
fs.mkdirSync(outDir, {recursive: true});
|
|
241
266
|
|
|
242
267
|
entryPoint = createEntryFile(dir, frontendRoutes);
|
|
243
268
|
|
|
@@ -278,6 +303,15 @@ export default function hweb(options: HightJSOptions) {
|
|
|
278
303
|
const instrumentationPath = path.join(dir, 'src', instrumentationFile);
|
|
279
304
|
// dar require, e executar a função principal do arquivo
|
|
280
305
|
const instrumentation = require(instrumentationPath);
|
|
306
|
+
|
|
307
|
+
// Registra o listener de hot reload se existir
|
|
308
|
+
if (instrumentation.hotReloadListener && typeof instrumentation.hotReloadListener === 'function') {
|
|
309
|
+
if (hotReloadManager) {
|
|
310
|
+
hotReloadManager.setHotReloadListener(instrumentation.hotReloadListener);
|
|
311
|
+
Console.info('✅ Hot reload listener registrado');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
281
315
|
if (typeof instrumentation === 'function') {
|
|
282
316
|
instrumentation();
|
|
283
317
|
} else if (typeof instrumentation.default === 'function') {
|
|
@@ -298,7 +332,7 @@ export default function hweb(options: HightJSOptions) {
|
|
|
298
332
|
(genericReq as any).hwebDev = dev;
|
|
299
333
|
(genericReq as any).hotReloadManager = hotReloadManager;
|
|
300
334
|
|
|
301
|
-
const {
|
|
335
|
+
const {pathname} = new URL(genericReq.url, `http://${genericReq.headers.host || 'localhost'}`);
|
|
302
336
|
const method = genericReq.method.toUpperCase();
|
|
303
337
|
|
|
304
338
|
// 1. Verifica se é WebSocket upgrade para hot reload
|
|
@@ -456,43 +490,18 @@ export default function hweb(options: HightJSOptions) {
|
|
|
456
490
|
|
|
457
491
|
// Método para configurar WebSocket upgrade nos servidores Express e Fastify
|
|
458
492
|
setupWebSocket: (server: any) => {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (isExpressServer) {
|
|
465
|
-
|
|
466
|
-
server.on('upgrade', (request: any, socket: any, head: Buffer) => {
|
|
467
|
-
const { pathname } = new URL(request.url, `http://${request.headers.host}`);
|
|
493
|
+
// Detecta se é um servidor Express ou Fastify
|
|
494
|
+
const isExpressServer = FrameworkAdapterFactory.getCurrentAdapter() instanceof ExpressAdapter;
|
|
495
|
+
const actualServer = isExpressServer ? server : (server.server || server);
|
|
468
496
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
} else {
|
|
472
|
-
socket.destroy();
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
} else {
|
|
476
|
-
|
|
477
|
-
// Fastify usa um approach diferente para WebSockets
|
|
478
|
-
const actualServer = server.server || server;
|
|
479
|
-
actualServer.on('upgrade', (request: any, socket: any, head: Buffer) => {
|
|
480
|
-
const { pathname } = new URL(request.url, `http://${request.headers.host}`);
|
|
481
|
-
|
|
482
|
-
if (pathname === '/hweb-hotreload/') {
|
|
483
|
-
hotReloadManager!.handleUpgrade(request, socket, head);
|
|
484
|
-
} else {
|
|
485
|
-
socket.destroy();
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
}
|
|
497
|
+
// Usa o sistema coordenado de WebSocket upgrade que integra hot-reload e rotas de usuário
|
|
498
|
+
setupWebSocketUpgrade(actualServer, hotReloadManager);
|
|
490
499
|
},
|
|
491
500
|
|
|
492
501
|
build: async () => {
|
|
493
502
|
const msg = Console.dynamicLine(` ${Colors.FgYellow}● ${Colors.Reset}Iniciando build do cliente para produção`);
|
|
494
503
|
const outDir = path.join(dir, 'hweb-dist');
|
|
495
|
-
fs.mkdirSync(outDir, {
|
|
504
|
+
fs.mkdirSync(outDir, {recursive: true});
|
|
496
505
|
|
|
497
506
|
const routes = loadRoutes(userWebRoutesDir);
|
|
498
507
|
const entryPoint = createEntryFile(dir, routes);
|
|
@@ -509,6 +518,4 @@ export default function hweb(options: HightJSOptions) {
|
|
|
509
518
|
}
|
|
510
519
|
}
|
|
511
520
|
};
|
|
512
|
-
|
|
513
|
-
return app;
|
|
514
521
|
}
|
package/src/renderer.tsx
CHANGED
|
@@ -15,7 +15,7 @@ function encodeInitialData(data: any): string {
|
|
|
15
15
|
|
|
16
16
|
function createDecodeScript(): string {
|
|
17
17
|
return `
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
window.__HWEB_DECODE__ = function(encoded) { const base64 = encoded.replace('hweb_', '').replace('_config', ''); const jsonStr = atob(base64); return JSON.parse(jsonStr); };
|
|
20
20
|
`;
|
|
21
21
|
}
|
package/src/router.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { RouteConfig, BackendRouteConfig, HightMiddleware } from './types';
|
|
3
|
+
import { RouteConfig, BackendRouteConfig, HightMiddleware, WebSocketHandler, WebSocketContext } from './types';
|
|
4
|
+
import { WebSocketServer as WSServer, WebSocket } from 'ws';
|
|
5
|
+
import { IncomingMessage } from 'http';
|
|
6
|
+
import { URL } from 'url';
|
|
4
7
|
import Console from "./api/console"
|
|
8
|
+
import {FrameworkAdapterFactory} from "./adapters/factory";
|
|
9
|
+
import {HightJSRequest} from "./api/http";
|
|
5
10
|
|
|
6
11
|
// --- Roteamento do Frontend ---
|
|
7
12
|
|
|
@@ -198,8 +203,19 @@ export function findMatchingRoute(pathname: string) {
|
|
|
198
203
|
for (const route of allRoutes) {
|
|
199
204
|
if (!route.pattern) continue;
|
|
200
205
|
|
|
201
|
-
|
|
202
|
-
|
|
206
|
+
const regexPattern = route.pattern
|
|
207
|
+
// [[...param]] → opcional catch-all
|
|
208
|
+
.replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
|
|
209
|
+
// [...param] → obrigatório catch-all
|
|
210
|
+
.replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
|
|
211
|
+
// /[[param]] → opcional com barra também opcional
|
|
212
|
+
.replace(/\/\[\[(\w+)\]\]/g, '(?:/(?<$1>[^/]+))?')
|
|
213
|
+
// [[param]] → segmento opcional (sem barra anterior)
|
|
214
|
+
.replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
|
|
215
|
+
// [param] → segmento obrigatório
|
|
216
|
+
.replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
|
|
217
|
+
|
|
218
|
+
// permite / opcional no final
|
|
203
219
|
const regex = new RegExp(`^${regexPattern}/?$`);
|
|
204
220
|
const match = pathname.match(regex);
|
|
205
221
|
|
|
@@ -210,9 +226,11 @@ export function findMatchingRoute(pathname: string) {
|
|
|
210
226
|
};
|
|
211
227
|
}
|
|
212
228
|
}
|
|
229
|
+
|
|
213
230
|
return null;
|
|
214
231
|
}
|
|
215
232
|
|
|
233
|
+
|
|
216
234
|
// --- Roteamento do Backend ---
|
|
217
235
|
|
|
218
236
|
// Guarda todas as rotas de API encontradas
|
|
@@ -406,3 +424,212 @@ export function loadNotFound(webDir: string): { componentPath: string } | null {
|
|
|
406
424
|
export function getNotFound(): { componentPath: string } | null {
|
|
407
425
|
return notFoundComponent;
|
|
408
426
|
}
|
|
427
|
+
|
|
428
|
+
// --- WebSocket Functions ---
|
|
429
|
+
|
|
430
|
+
// Guarda todas as rotas WebSocket encontradas
|
|
431
|
+
let allWebSocketRoutes: { pattern: string; handler: WebSocketHandler; middleware?: HightMiddleware[] }[] = [];
|
|
432
|
+
|
|
433
|
+
// Conexões WebSocket ativas
|
|
434
|
+
let wsConnections: Set<WebSocket> = new Set();
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Processa e registra rotas WebSocket encontradas nas rotas backend
|
|
438
|
+
*/
|
|
439
|
+
export function processWebSocketRoutes() {
|
|
440
|
+
allWebSocketRoutes = [];
|
|
441
|
+
|
|
442
|
+
for (const route of allBackendRoutes) {
|
|
443
|
+
if (route.WS) {
|
|
444
|
+
const wsRoute = {
|
|
445
|
+
pattern: route.pattern,
|
|
446
|
+
handler: route.WS,
|
|
447
|
+
middleware: route.middleware
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
allWebSocketRoutes.push(wsRoute);
|
|
451
|
+
Console.info(`WebSocket route registered: ${route.pattern}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Encontra a rota WebSocket correspondente para uma URL
|
|
458
|
+
*/
|
|
459
|
+
export function findMatchingWebSocketRoute(pathname: string) {
|
|
460
|
+
for (const route of allWebSocketRoutes) {
|
|
461
|
+
if (!route.pattern) continue;
|
|
462
|
+
|
|
463
|
+
const regexPattern = route.pattern
|
|
464
|
+
.replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
|
|
465
|
+
.replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
|
|
466
|
+
.replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
|
|
467
|
+
.replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
|
|
468
|
+
|
|
469
|
+
const regex = new RegExp(`^${regexPattern}/?$`);
|
|
470
|
+
const match = pathname.match(regex);
|
|
471
|
+
|
|
472
|
+
if (match) {
|
|
473
|
+
return {
|
|
474
|
+
route,
|
|
475
|
+
params: match.groups || {}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Trata uma nova conexão WebSocket
|
|
484
|
+
*/
|
|
485
|
+
function handleWebSocketConnection(ws: WebSocket, req: IncomingMessage, hwebReq: HightJSRequest) {
|
|
486
|
+
if (!req.url) return;
|
|
487
|
+
|
|
488
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
489
|
+
const pathname = url.pathname;
|
|
490
|
+
|
|
491
|
+
const matchedRoute = findMatchingWebSocketRoute(pathname);
|
|
492
|
+
if (!matchedRoute) {
|
|
493
|
+
ws.close(1000, 'Rota não encontrada');
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const params = extractWebSocketParams(pathname, matchedRoute.route.pattern);
|
|
498
|
+
const query = Object.fromEntries(url.searchParams.entries());
|
|
499
|
+
|
|
500
|
+
const context: WebSocketContext = {
|
|
501
|
+
hightReq: hwebReq,
|
|
502
|
+
ws,
|
|
503
|
+
req,
|
|
504
|
+
url,
|
|
505
|
+
params,
|
|
506
|
+
query,
|
|
507
|
+
send: (data: any) => {
|
|
508
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
509
|
+
const message = typeof data === 'string' ? data : JSON.stringify(data);
|
|
510
|
+
ws.send(message);
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
close: (code?: number, reason?: string) => {
|
|
514
|
+
ws.close(code || 1000, reason);
|
|
515
|
+
},
|
|
516
|
+
broadcast: (data: any, exclude?: WebSocket[]) => {
|
|
517
|
+
const message = typeof data === 'string' ? data : JSON.stringify(data);
|
|
518
|
+
const excludeSet = new Set(exclude || []);
|
|
519
|
+
wsConnections.forEach(connection => {
|
|
520
|
+
if (connection.readyState === WebSocket.OPEN && !excludeSet.has(connection)) {
|
|
521
|
+
connection.send(message);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
matchedRoute.route.handler(context);
|
|
529
|
+
} catch (error) {
|
|
530
|
+
console.error('Erro no handler WebSocket:', error);
|
|
531
|
+
ws.close(1011, 'Erro interno do servidor');
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Extrai parâmetros da URL para WebSocket
|
|
537
|
+
*/
|
|
538
|
+
function extractWebSocketParams(pathname: string, pattern: string): Record<string, string> {
|
|
539
|
+
const params: Record<string, string> = {};
|
|
540
|
+
|
|
541
|
+
const regexPattern = pattern
|
|
542
|
+
.replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
|
|
543
|
+
.replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
|
|
544
|
+
.replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
|
|
545
|
+
.replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
|
|
546
|
+
|
|
547
|
+
const regex = new RegExp(`^${regexPattern}/?$`);
|
|
548
|
+
const match = pathname.match(regex);
|
|
549
|
+
|
|
550
|
+
if (match && match.groups) {
|
|
551
|
+
Object.assign(params, match.groups);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return params;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Configura WebSocket upgrade no servidor HTTP existente
|
|
559
|
+
* @param server Servidor HTTP (Express, Fastify ou Native)
|
|
560
|
+
* @param hotReloadManager Instância do gerenciador de hot-reload para coordenação
|
|
561
|
+
*/
|
|
562
|
+
export function setupWebSocketUpgrade(server: any, hotReloadManager?: any) {
|
|
563
|
+
// NÃO remove listeners existentes para preservar hot-reload
|
|
564
|
+
// Em vez disso, coordena com o sistema existente
|
|
565
|
+
|
|
566
|
+
// Verifica se já existe um listener de upgrade
|
|
567
|
+
const existingListeners = server.listeners('upgrade');
|
|
568
|
+
|
|
569
|
+
// Se não há listeners, ou se o hot-reload ainda não foi configurado, adiciona o nosso
|
|
570
|
+
if (existingListeners.length === 0) {
|
|
571
|
+
server.on('upgrade', (request: any, socket: any, head: Buffer) => {
|
|
572
|
+
handleWebSocketUpgrade(request, socket, head, hotReloadManager);
|
|
573
|
+
});
|
|
574
|
+
} else {
|
|
575
|
+
// Se já existe um listener (provavelmente do hot-reload),
|
|
576
|
+
// vamos interceptar e coordenar
|
|
577
|
+
console.log('🔧 Coordenando WebSocket upgrade com sistema existente');
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function handleWebSocketUpgrade(request: any, socket: any, head: Buffer, hotReloadManager?: any) {
|
|
582
|
+
const adapter = FrameworkAdapterFactory.getCurrentAdapter()
|
|
583
|
+
if (!adapter) {
|
|
584
|
+
console.error('❌ Framework adapter não detectado. Não é possível processar upgrade WebSocket.');
|
|
585
|
+
socket.destroy();
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const genericReq = adapter.parseRequest(request);
|
|
589
|
+
const hwebReq = new HightJSRequest(genericReq);
|
|
590
|
+
const { pathname } = new URL(request.url, `http://${request.headers.host}`);
|
|
591
|
+
|
|
592
|
+
// Prioridade 1: Hot reload (sistema interno)
|
|
593
|
+
if (pathname === '/hweb-hotreload/') {
|
|
594
|
+
if (hotReloadManager) {
|
|
595
|
+
hotReloadManager.handleUpgrade(request, socket, head);
|
|
596
|
+
} else {
|
|
597
|
+
socket.destroy();
|
|
598
|
+
}
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Prioridade 2: Rotas WebSocket do usuário
|
|
603
|
+
const matchedRoute = findMatchingWebSocketRoute(pathname);
|
|
604
|
+
if (matchedRoute) {
|
|
605
|
+
// Faz upgrade para WebSocket usando noServer
|
|
606
|
+
const wss = new WSServer({
|
|
607
|
+
noServer: true,
|
|
608
|
+
perMessageDeflate: false, // Melhor performance
|
|
609
|
+
maxPayload: 1024 * 1024 // Limite de 1MB
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
613
|
+
wsConnections.add(ws);
|
|
614
|
+
console.log(`✅ WebSocket conectado em ${pathname}`);
|
|
615
|
+
|
|
616
|
+
ws.on('close', () => {
|
|
617
|
+
wsConnections.delete(ws);
|
|
618
|
+
console.log(`❌ WebSocket desconectado de ${pathname}`);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
ws.on('error', (error) => {
|
|
622
|
+
console.error(`💥 Erro WebSocket em ${pathname}:`, error);
|
|
623
|
+
wsConnections.delete(ws);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// Processa a conexão
|
|
627
|
+
handleWebSocketConnection(ws, request, hwebReq);
|
|
628
|
+
});
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Nenhuma rota encontrada - rejeita conexão
|
|
633
|
+
console.log(`🚫 Nenhuma rota WebSocket encontrada para: ${pathname}`);
|
|
634
|
+
socket.destroy();
|
|
635
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import type { ComponentType } from 'react';
|
|
2
2
|
import type { GenericRequest } from './types/framework';
|
|
3
3
|
import {HightJSRequest, HightJSResponse} from "./api/http";
|
|
4
|
+
import { WebSocket } from 'ws';
|
|
5
|
+
import { IncomingMessage } from 'http';
|
|
6
|
+
|
|
7
|
+
// Interface do contexto WebSocket simplificada
|
|
8
|
+
export interface WebSocketContext {
|
|
9
|
+
ws: WebSocket;
|
|
10
|
+
req: IncomingMessage;
|
|
11
|
+
hightReq: HightJSRequest;
|
|
12
|
+
url: URL;
|
|
13
|
+
params: Record<string, string>;
|
|
14
|
+
query: Record<string, string>;
|
|
15
|
+
send: (data: any) => void;
|
|
16
|
+
close: (code?: number, reason?: string) => void;
|
|
17
|
+
broadcast: (data: any, exclude?: WebSocket[]) => void;
|
|
18
|
+
}
|
|
4
19
|
|
|
5
20
|
// --- Tipos do Frontend (sem alteração) ---
|
|
6
21
|
export interface HightJSOptions {
|
|
@@ -42,7 +57,14 @@ export type HightMiddleware = (
|
|
|
42
57
|
|
|
43
58
|
|
|
44
59
|
/**
|
|
45
|
-
* Define
|
|
60
|
+
* Define o formato de uma função que manipula uma rota WebSocket.
|
|
61
|
+
*/
|
|
62
|
+
export type WebSocketHandler = (
|
|
63
|
+
context: WebSocketContext
|
|
64
|
+
) => Promise<void> | void;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Define a estrutura de cada rota da API, com suporte para métodos HTTP e WebSocket.
|
|
46
68
|
*/
|
|
47
69
|
export interface BackendRouteConfig {
|
|
48
70
|
pattern: string;
|
|
@@ -50,5 +72,6 @@ export interface BackendRouteConfig {
|
|
|
50
72
|
POST?: BackendHandler;
|
|
51
73
|
PUT?: BackendHandler;
|
|
52
74
|
DELETE?: BackendHandler;
|
|
75
|
+
WS?: WebSocketHandler; // Suporte para WebSocket
|
|
53
76
|
middleware?: HightMiddleware[]; // Permite adicionar middlewares específicos à rota
|
|
54
77
|
}
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
package/dist/auth/example.d.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exemplo de como usar os novos providers baseados em classes
|
|
3
|
-
*/
|
|
4
|
-
export declare const authRoutes: {
|
|
5
|
-
pattern: string;
|
|
6
|
-
GET(req: import("..").HightJSRequest, params: {
|
|
7
|
-
[key: string]: string;
|
|
8
|
-
}): Promise<any>;
|
|
9
|
-
POST(req: import("..").HightJSRequest, params: {
|
|
10
|
-
[key: string]: string;
|
|
11
|
-
}): Promise<any>;
|
|
12
|
-
auth: import("./core").HWebAuth;
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* Como usar em suas rotas API:
|
|
16
|
-
*
|
|
17
|
-
* // arquivo: /api/auth/[...value].ts
|
|
18
|
-
* import { authRoutes } from '../../../src/auth/example';
|
|
19
|
-
*
|
|
20
|
-
* export const GET = authRoutes.GET;
|
|
21
|
-
* export const POST = authRoutes.POST;
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* Rotas disponíveis automaticamente:
|
|
25
|
-
*
|
|
26
|
-
* Core routes:
|
|
27
|
-
* - GET /api/auth/session - Obter sessão atual
|
|
28
|
-
* - GET /api/auth/providers - Listar providers
|
|
29
|
-
* - GET /api/auth/csrf - Obter token CSRF
|
|
30
|
-
* - POST /api/auth/signin - Login
|
|
31
|
-
* - POST /api/auth/signout - Logout
|
|
32
|
-
*
|
|
33
|
-
* Provider específico (CredentialsProvider):
|
|
34
|
-
* - GET /api/auth/credentials/config - Config do provider
|
|
35
|
-
*
|
|
36
|
-
* Provider específico (DiscordProvider):
|
|
37
|
-
* - GET /api/auth/signin/discord - Iniciar OAuth Discord
|
|
38
|
-
* - GET /api/auth/callback/discord - Callback OAuth Discord
|
|
39
|
-
* - GET /api/auth/discord/config - Config do provider Discord
|
|
40
|
-
*/
|
package/dist/auth/example.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Exemplo de como usar os novos providers baseados em classes
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.authRoutes = void 0;
|
|
7
|
-
const routes_1 = require("./routes");
|
|
8
|
-
const providers_1 = require("./providers");
|
|
9
|
-
// Exemplo de configuração com os novos providers
|
|
10
|
-
const authConfig = {
|
|
11
|
-
providers: [
|
|
12
|
-
// Provider de credenciais customizado
|
|
13
|
-
new providers_1.CredentialsProvider({
|
|
14
|
-
name: "Login com Email",
|
|
15
|
-
credentials: {
|
|
16
|
-
email: {
|
|
17
|
-
label: "Email",
|
|
18
|
-
type: "email",
|
|
19
|
-
placeholder: "seu@email.com"
|
|
20
|
-
},
|
|
21
|
-
password: {
|
|
22
|
-
label: "Senha",
|
|
23
|
-
type: "password"
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
async authorize(credentials) {
|
|
27
|
-
// Aqui você faz a validação com seu banco de dados
|
|
28
|
-
const { email, password } = credentials;
|
|
29
|
-
// Exemplo de validação (substitua pela sua lógica)
|
|
30
|
-
if (email === "admin@example.com" && password === "123456") {
|
|
31
|
-
return {
|
|
32
|
-
id: "1",
|
|
33
|
-
name: "Admin User",
|
|
34
|
-
email: email,
|
|
35
|
-
role: "admin"
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
// Retorna null se credenciais inválidas
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}),
|
|
42
|
-
// Provider do Discord
|
|
43
|
-
new providers_1.DiscordProvider({
|
|
44
|
-
clientId: process.env.DISCORD_CLIENT_ID,
|
|
45
|
-
clientSecret: process.env.DISCORD_CLIENT_SECRET,
|
|
46
|
-
callbackUrl: "http://localhost:3000/api/auth/callback/discord"
|
|
47
|
-
})
|
|
48
|
-
],
|
|
49
|
-
secret: process.env.HWEB_AUTH_SECRET || "seu-super-secret-aqui-32-chars-min",
|
|
50
|
-
session: {
|
|
51
|
-
strategy: 'jwt',
|
|
52
|
-
maxAge: 86400 // 24 horas
|
|
53
|
-
},
|
|
54
|
-
pages: {
|
|
55
|
-
signIn: '/auth/signin',
|
|
56
|
-
signOut: '/auth/signout'
|
|
57
|
-
},
|
|
58
|
-
callbacks: {
|
|
59
|
-
async signIn(user, account, profile) {
|
|
60
|
-
// Lógica customizada antes do login
|
|
61
|
-
console.log(`Usuário ${user.email} fazendo login via ${account.provider}`);
|
|
62
|
-
return true; // permitir login
|
|
63
|
-
},
|
|
64
|
-
async session(session, user) {
|
|
65
|
-
// Adicionar dados customizados à sessão
|
|
66
|
-
return {
|
|
67
|
-
...session,
|
|
68
|
-
user: {
|
|
69
|
-
...session.user,
|
|
70
|
-
customData: "dados extras"
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
// Criar as rotas de autenticação
|
|
77
|
-
exports.authRoutes = (0, routes_1.createAuthRoutes)(authConfig);
|
|
78
|
-
/**
|
|
79
|
-
* Como usar em suas rotas API:
|
|
80
|
-
*
|
|
81
|
-
* // arquivo: /api/auth/[...value].ts
|
|
82
|
-
* import { authRoutes } from '../../../src/auth/example';
|
|
83
|
-
*
|
|
84
|
-
* export const GET = authRoutes.GET;
|
|
85
|
-
* export const POST = authRoutes.POST;
|
|
86
|
-
*/
|
|
87
|
-
/**
|
|
88
|
-
* Rotas disponíveis automaticamente:
|
|
89
|
-
*
|
|
90
|
-
* Core routes:
|
|
91
|
-
* - GET /api/auth/session - Obter sessão atual
|
|
92
|
-
* - GET /api/auth/providers - Listar providers
|
|
93
|
-
* - GET /api/auth/csrf - Obter token CSRF
|
|
94
|
-
* - POST /api/auth/signin - Login
|
|
95
|
-
* - POST /api/auth/signout - Logout
|
|
96
|
-
*
|
|
97
|
-
* Provider específico (CredentialsProvider):
|
|
98
|
-
* - GET /api/auth/credentials/config - Config do provider
|
|
99
|
-
*
|
|
100
|
-
* Provider específico (DiscordProvider):
|
|
101
|
-
* - GET /api/auth/signin/discord - Iniciar OAuth Discord
|
|
102
|
-
* - GET /api/auth/callback/discord - Callback OAuth Discord
|
|
103
|
-
* - GET /api/auth/discord/config - Config do provider Discord
|
|
104
|
-
*/
|