localhostly 0.0.0-stage → 1.1.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.
@@ -0,0 +1,96 @@
1
+ // Os quadros que atravessam o WebSocket entre o servidor e o agente.
2
+ //
3
+ // Cada quadro binário: 1 byte de tipo, 4 bytes de id (uint32 BE) e o payload.
4
+ // - inícios (REQ_INICIO, RES_INICIO, WS_ABRIR), WS_FECHAR e ABORTAR: JSON;
5
+ // - corpos (REQ_CORPO, RES_CORPO): bytes, em pedaços de até 64 KiB;
6
+ // - fins (REQ_FIM, RES_FIM): vazios;
7
+ // - WS_DADO: 1 byte (1 = binário, 0 = texto) e os bytes da mensagem;
8
+ // - JANELA: uint32 BE, os bytes que o receptor já entregou ao destino daquele id
9
+ // (o crédito do controle de fluxo por id, ver lib/fluxo.js).
10
+ // As mensagens de controle (hello, ready, erro) vão em quadros de texto JSON.
11
+
12
+ export const TIPO = Object.freeze({
13
+ REQ_INICIO: 1,
14
+ REQ_CORPO: 2,
15
+ REQ_FIM: 3,
16
+ RES_INICIO: 4,
17
+ RES_CORPO: 5,
18
+ RES_FIM: 6,
19
+ WS_ABRIR: 7,
20
+ WS_DADO: 8,
21
+ WS_FECHAR: 9,
22
+ ABORTAR: 10,
23
+ JANELA: 11,
24
+ });
25
+
26
+ export const NOME_DO_TIPO = Object.freeze(
27
+ Object.fromEntries(Object.entries(TIPO).map(([nome, n]) => [n, nome])),
28
+ );
29
+
30
+ export const CABECALHO = 5;
31
+ export const PEDACO_MAX = 64 * 1024;
32
+ const ID_MAX = 0xffffffff;
33
+
34
+ const TIPOS_JSON = new Set([TIPO.REQ_INICIO, TIPO.RES_INICIO, TIPO.WS_ABRIR, TIPO.WS_FECHAR, TIPO.ABORTAR]);
35
+
36
+ function corpoDe(tipo, payload) {
37
+ if (TIPOS_JSON.has(tipo)) return Buffer.from(JSON.stringify(payload ?? {}), 'utf8');
38
+ if (tipo === TIPO.JANELA) {
39
+ if (!Number.isInteger(payload) || payload < 0 || payload > ID_MAX) {
40
+ throw new RangeError(`janela inválida: ${payload}`);
41
+ }
42
+ const b = Buffer.allocUnsafe(4);
43
+ b.writeUInt32BE(payload, 0);
44
+ return b;
45
+ }
46
+ if (tipo === TIPO.WS_DADO) {
47
+ const dados = Buffer.isBuffer(payload?.dados) ? payload.dados : Buffer.from(payload?.dados ?? '');
48
+ const b = Buffer.allocUnsafe(1 + dados.length);
49
+ b[0] = payload?.binario ? 1 : 0;
50
+ dados.copy(b, 1);
51
+ return b;
52
+ }
53
+ if (payload == null) return Buffer.alloc(0);
54
+ if (Buffer.isBuffer(payload)) return payload;
55
+ if (payload instanceof Uint8Array) return Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength);
56
+ throw new TypeError(`payload de ${NOME_DO_TIPO[tipo]} tem de ser bytes`);
57
+ }
58
+
59
+ export function codificar(tipo, id, payload) {
60
+ if (!NOME_DO_TIPO[tipo]) throw new RangeError(`tipo desconhecido: ${tipo}`);
61
+ if (!Number.isInteger(id) || id < 0 || id > ID_MAX) throw new RangeError(`id inválido: ${id}`);
62
+ const corpo = corpoDe(tipo, payload);
63
+ const quadro = Buffer.allocUnsafe(CABECALHO + corpo.length);
64
+ quadro[0] = tipo;
65
+ quadro.writeUInt32BE(id, 1);
66
+ corpo.copy(quadro, CABECALHO);
67
+ return quadro;
68
+ }
69
+
70
+ export function decodificar(dados) {
71
+ const buf = Buffer.isBuffer(dados) ? dados : Buffer.from(dados);
72
+ if (buf.length < CABECALHO) throw new RangeError(`quadro curto: ${buf.length} bytes`);
73
+ const tipo = buf[0];
74
+ if (!NOME_DO_TIPO[tipo]) throw new RangeError(`tipo desconhecido: ${tipo}`);
75
+ const id = buf.readUInt32BE(1);
76
+ const resto = buf.subarray(CABECALHO);
77
+ let payload = resto;
78
+ if (TIPOS_JSON.has(tipo)) {
79
+ payload = resto.length ? JSON.parse(resto.toString('utf8')) : {};
80
+ } else if (tipo === TIPO.JANELA) {
81
+ if (resto.length !== 4) throw new RangeError('janela com tamanho errado');
82
+ payload = resto.readUInt32BE(0);
83
+ } else if (tipo === TIPO.WS_DADO) {
84
+ if (resto.length < 1) throw new RangeError('ws-dado sem marca');
85
+ payload = { binario: resto[0] === 1, dados: resto.subarray(1) };
86
+ }
87
+ return { tipo, id, payload };
88
+ }
89
+
90
+ // Os códigos que podem ir num quadro de fechamento de WebSocket (RFC 6455, 7.4).
91
+ export function codigoEnviavel(codigo) {
92
+ return (
93
+ Number.isInteger(codigo) &&
94
+ ((codigo >= 1000 && codigo <= 1003) || (codigo >= 1007 && codigo <= 1014) || (codigo >= 3000 && codigo <= 4999))
95
+ );
96
+ }
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "localhostly",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.1.0",
4
+ "description": "Expose any local port on a public HTTPS URL, behind NAT, without opening ports.",
5
+ "keywords": [
6
+ "tunnel",
7
+ "localhost",
8
+ "https",
9
+ "expose",
10
+ "webhook",
11
+ "cli"
12
+ ],
13
+ "homepage": "https://localhostly.iblabs.io",
14
+ "type": "module",
15
+ "bin": {
16
+ "localhostly": "bin/localhostly.js"
17
+ },
18
+ "files": [
19
+ "bin",
20
+ "agente",
21
+ "lib",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "engines": {
26
+ "node": ">=24"
27
+ },
28
+ "scripts": {
29
+ "test": "node --test \"testes/*.test.mjs\"",
30
+ "servidor": "node server/index.js",
31
+ "seo:check": "node scripts/seo/check.mjs"
32
+ },
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "ws": "^8.22.2"
36
+ },
37
+ "devDependencies": {
38
+ "cheerio": "1.2.0"
39
+ }
40
+ }