@telepapo/sdk 0.0.0-stage → 0.2.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,434 @@
1
+ /* @ts-self-types="./telepapo_signal.d.ts" */
2
+
3
+ class Signal {
4
+ static __wrap(ptr) {
5
+ const obj = Object.create(Signal.prototype);
6
+ obj.__wbg_ptr = ptr;
7
+ SignalFinalization.register(obj, obj.__wbg_ptr, obj);
8
+ return obj;
9
+ }
10
+ __destroy_into_raw() {
11
+ const ptr = this.__wbg_ptr;
12
+ this.__wbg_ptr = 0;
13
+ SignalFinalization.unregister(this);
14
+ return ptr;
15
+ }
16
+ free() {
17
+ const ptr = this.__destroy_into_raw();
18
+ wasm.__wbg_signal_free(ptr, 0);
19
+ }
20
+ /**
21
+ * X3DH + Kyber (PQXDH): o libsignal confere as assinaturas e recusa pacote forjado.
22
+ * @param {string} usuario
23
+ * @param {string} pacote_json
24
+ * @param {number} ms
25
+ */
26
+ abrirSessao(usuario, pacote_json, ms) {
27
+ const ptr0 = passStringToWasm0(usuario, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
28
+ const len0 = WASM_VECTOR_LEN;
29
+ const ptr1 = passStringToWasm0(pacote_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
30
+ const len1 = WASM_VECTOR_LEN;
31
+ const ret = wasm.signal_abrirSessao(this.__wbg_ptr, ptr0, len0, ptr1, len1, ms);
32
+ if (ret[1]) {
33
+ throw takeFromExternrefTable0(ret[0]);
34
+ }
35
+ }
36
+ /**
37
+ * @param {string} usuario
38
+ * @param {number} dispositivo
39
+ */
40
+ apagarSessao(usuario, dispositivo) {
41
+ const ptr0 = passStringToWasm0(usuario, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
42
+ const len0 = WASM_VECTOR_LEN;
43
+ wasm.signal_apagarSessao(this.__wbg_ptr, ptr0, len0, dispositivo);
44
+ }
45
+ /**
46
+ * @param {string} json
47
+ * @returns {Signal}
48
+ */
49
+ static carregar(json) {
50
+ const ptr0 = passStringToWasm0(json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
51
+ const len0 = WASM_VECTOR_LEN;
52
+ const ret = wasm.signal_carregar(ptr0, len0);
53
+ if (ret[2]) {
54
+ throw takeFromExternrefTable0(ret[1]);
55
+ }
56
+ return Signal.__wrap(ret[0]);
57
+ }
58
+ /**
59
+ * Pacote inicial (o `chaves` do pedido de vínculo), no formato da API.
60
+ * @param {number} ms
61
+ * @returns {string}
62
+ */
63
+ chavesIniciais(ms) {
64
+ let deferred2_0;
65
+ let deferred2_1;
66
+ try {
67
+ const ret = wasm.signal_chavesIniciais(this.__wbg_ptr, ms);
68
+ var ptr1 = ret[0];
69
+ var len1 = ret[1];
70
+ if (ret[3]) {
71
+ ptr1 = 0; len1 = 0;
72
+ throw takeFromExternrefTable0(ret[2]);
73
+ }
74
+ deferred2_0 = ptr1;
75
+ deferred2_1 = len1;
76
+ return getStringFromWasm0(ptr1, len1);
77
+ } finally {
78
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
79
+ }
80
+ }
81
+ /**
82
+ * Cifra para um aparelho: `{tipo: PREKEY|WHISPER, conteudo, registrationId}`.
83
+ * @param {string} usuario
84
+ * @param {number} dispositivo
85
+ * @param {Uint8Array} claro
86
+ * @param {number} ms
87
+ * @returns {string}
88
+ */
89
+ cifrar(usuario, dispositivo, claro, ms) {
90
+ let deferred4_0;
91
+ let deferred4_1;
92
+ try {
93
+ const ptr0 = passStringToWasm0(usuario, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
94
+ const len0 = WASM_VECTOR_LEN;
95
+ const ptr1 = passArray8ToWasm0(claro, wasm.__wbindgen_malloc);
96
+ const len1 = WASM_VECTOR_LEN;
97
+ const ret = wasm.signal_cifrar(this.__wbg_ptr, ptr0, len0, dispositivo, ptr1, len1, ms);
98
+ var ptr3 = ret[0];
99
+ var len3 = ret[1];
100
+ if (ret[3]) {
101
+ ptr3 = 0; len3 = 0;
102
+ throw takeFromExternrefTable0(ret[2]);
103
+ }
104
+ deferred4_0 = ptr3;
105
+ deferred4_1 = len3;
106
+ return getStringFromWasm0(ptr3, len3);
107
+ } finally {
108
+ wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
109
+ }
110
+ }
111
+ /**
112
+ * Decifra um envelope. Erro com "duplicate" no texto = mensagem repetida.
113
+ * @param {string} usuario
114
+ * @param {number} dispositivo
115
+ * @param {string} tipo
116
+ * @param {string} conteudo_b64
117
+ * @returns {Uint8Array}
118
+ */
119
+ decifrar(usuario, dispositivo, tipo, conteudo_b64) {
120
+ const ptr0 = passStringToWasm0(usuario, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
121
+ const len0 = WASM_VECTOR_LEN;
122
+ const ptr1 = passStringToWasm0(tipo, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
123
+ const len1 = WASM_VECTOR_LEN;
124
+ const ptr2 = passStringToWasm0(conteudo_b64, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
125
+ const len2 = WASM_VECTOR_LEN;
126
+ const ret = wasm.signal_decifrar(this.__wbg_ptr, ptr0, len0, dispositivo, ptr1, len1, ptr2, len2);
127
+ if (ret[3]) {
128
+ throw takeFromExternrefTable0(ret[2]);
129
+ }
130
+ var v4 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
131
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
132
+ return v4;
133
+ }
134
+ /**
135
+ * Depois do vínculo: o endereço deste navegador.
136
+ * @param {string} usuario
137
+ * @param {number} dispositivo
138
+ */
139
+ definirEndereco(usuario, dispositivo) {
140
+ const ptr0 = passStringToWasm0(usuario, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
141
+ const len0 = WASM_VECTOR_LEN;
142
+ const ret = wasm.signal_definirEndereco(this.__wbg_ptr, ptr0, len0, dispositivo);
143
+ if (ret[1]) {
144
+ throw takeFromExternrefTable0(ret[0]);
145
+ }
146
+ }
147
+ /**
148
+ * Números dos aparelhos de alguém com quem há sessão.
149
+ * @param {string} usuario
150
+ * @returns {Uint32Array}
151
+ */
152
+ dispositivosComSessao(usuario) {
153
+ const ptr0 = passStringToWasm0(usuario, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
154
+ const len0 = WASM_VECTOR_LEN;
155
+ const ret = wasm.signal_dispositivosComSessao(this.__wbg_ptr, ptr0, len0);
156
+ var v2 = getArrayU32FromWasm0(ret[0], ret[1]).slice();
157
+ wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
158
+ return v2;
159
+ }
160
+ /**
161
+ * @returns {string}
162
+ */
163
+ exportar() {
164
+ let deferred2_0;
165
+ let deferred2_1;
166
+ try {
167
+ const ret = wasm.signal_exportar(this.__wbg_ptr);
168
+ var ptr1 = ret[0];
169
+ var len1 = ret[1];
170
+ if (ret[3]) {
171
+ ptr1 = 0; len1 = 0;
172
+ throw takeFromExternrefTable0(ret[2]);
173
+ }
174
+ deferred2_0 = ptr1;
175
+ deferred2_1 = len1;
176
+ return getStringFromWasm0(ptr1, len1);
177
+ } finally {
178
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
179
+ }
180
+ }
181
+ /**
182
+ * Chave pública de identidade (base64): vai no QR para o celular conferir.
183
+ * @returns {string}
184
+ */
185
+ identidadePublica() {
186
+ let deferred2_0;
187
+ let deferred2_1;
188
+ try {
189
+ const ret = wasm.signal_identidadePublica(this.__wbg_ptr);
190
+ var ptr1 = ret[0];
191
+ var len1 = ret[1];
192
+ if (ret[3]) {
193
+ ptr1 = 0; len1 = 0;
194
+ throw takeFromExternrefTable0(ret[2]);
195
+ }
196
+ deferred2_0 = ptr1;
197
+ deferred2_1 = len1;
198
+ return getStringFromWasm0(ptr1, len1);
199
+ } finally {
200
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
201
+ }
202
+ }
203
+ /**
204
+ * Contatos que trocaram de identidade desde a última leitura.
205
+ * @returns {string[]}
206
+ */
207
+ lerTrocas() {
208
+ const ret = wasm.signal_lerTrocas(this.__wbg_ptr);
209
+ var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]);
210
+ wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
211
+ return v1;
212
+ }
213
+ /**
214
+ * Identidade nova (navegador nunca vinculado).
215
+ * @returns {Signal}
216
+ */
217
+ static novo() {
218
+ const ret = wasm.signal_novo();
219
+ return Signal.__wrap(ret);
220
+ }
221
+ /**
222
+ * @returns {number}
223
+ */
224
+ registrationId() {
225
+ const ret = wasm.signal_registrationId(this.__wbg_ptr);
226
+ return ret >>> 0;
227
+ }
228
+ /**
229
+ * Reposição de pré-chaves (`PUT /v1/chaves`).
230
+ * @param {number} ms
231
+ * @param {number} pre_keys
232
+ * @param {number} kyber
233
+ * @returns {string}
234
+ */
235
+ reposicao(ms, pre_keys, kyber) {
236
+ let deferred2_0;
237
+ let deferred2_1;
238
+ try {
239
+ const ret = wasm.signal_reposicao(this.__wbg_ptr, ms, pre_keys, kyber);
240
+ var ptr1 = ret[0];
241
+ var len1 = ret[1];
242
+ if (ret[3]) {
243
+ ptr1 = 0; len1 = 0;
244
+ throw takeFromExternrefTable0(ret[2]);
245
+ }
246
+ deferred2_0 = ptr1;
247
+ deferred2_1 = len1;
248
+ return getStringFromWasm0(ptr1, len1);
249
+ } finally {
250
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
251
+ }
252
+ }
253
+ }
254
+ if (Symbol.dispose) Signal.prototype[Symbol.dispose] = Signal.prototype.free;
255
+ exports.Signal = Signal;
256
+ function __wbg_get_imports() {
257
+ const import0 = {
258
+ __proto__: null,
259
+ __wbg_Error_30c8987f7c2ed4e2: function(arg0, arg1) {
260
+ const ret = Error(getStringFromWasm0(arg0, arg1));
261
+ return ret;
262
+ },
263
+ __wbg___wbindgen_throw_41e9ee4f547fc59a: function(arg0, arg1) {
264
+ throw new Error(getStringFromWasm0(arg0, arg1));
265
+ },
266
+ __wbg_getRandomValues_a608c4436c19407a: function() { return handleError(function (arg0, arg1) {
267
+ globalThis.crypto.getRandomValues(getArrayU8FromWasm0(arg0, arg1));
268
+ }, arguments); },
269
+ __wbindgen_generic_0000000000000001: function(arg0, arg1) {
270
+ // Cast intrinsic for `Ref(String) -> Externref`.
271
+ const ret = getStringFromWasm0(arg0, arg1);
272
+ return ret;
273
+ },
274
+ __wbindgen_init_externref_table: function() {
275
+ const table = wasm.__wbindgen_externrefs;
276
+ const offset = table.grow(4);
277
+ table.set(0, undefined);
278
+ table.set(offset + 0, undefined);
279
+ table.set(offset + 1, null);
280
+ table.set(offset + 2, true);
281
+ table.set(offset + 3, false);
282
+ },
283
+ };
284
+ return {
285
+ __proto__: null,
286
+ "./telepapo_signal_bg.js": import0,
287
+ };
288
+ }
289
+
290
+ const SignalFinalization = (typeof FinalizationRegistry === 'undefined')
291
+ ? { register: () => {}, unregister: () => {} }
292
+ : new FinalizationRegistry(ptr => wasm.__wbg_signal_free(ptr, 1));
293
+
294
+ function addToExternrefTable0(obj) {
295
+ const idx = wasm.__externref_table_alloc();
296
+ wasm.__wbindgen_externrefs.set(idx, obj);
297
+ return idx;
298
+ }
299
+
300
+ function getArrayJsValueFromWasm0(ptr, len) {
301
+ ptr = ptr >>> 0;
302
+ const mem = getDataViewMemory0();
303
+ const result = [];
304
+ for (let i = ptr; i < ptr + 4 * len; i += 4) {
305
+ result.push(wasm.__wbindgen_externrefs.get(mem.getUint32(i, true)));
306
+ }
307
+ wasm.__externref_drop_slice(ptr, len);
308
+ return result;
309
+ }
310
+
311
+ function getArrayU32FromWasm0(ptr, len) {
312
+ ptr = ptr >>> 0;
313
+ return getUint32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len);
314
+ }
315
+
316
+ function getArrayU8FromWasm0(ptr, len) {
317
+ ptr = ptr >>> 0;
318
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
319
+ }
320
+
321
+ let cachedDataViewMemory0 = null;
322
+ function getDataViewMemory0() {
323
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
324
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
325
+ }
326
+ return cachedDataViewMemory0;
327
+ }
328
+
329
+ function getStringFromWasm0(ptr, len) {
330
+ return decodeText(ptr >>> 0, len);
331
+ }
332
+
333
+ let cachedUint32ArrayMemory0 = null;
334
+ function getUint32ArrayMemory0() {
335
+ if (cachedUint32ArrayMemory0 === null || cachedUint32ArrayMemory0.byteLength === 0) {
336
+ cachedUint32ArrayMemory0 = new Uint32Array(wasm.memory.buffer);
337
+ }
338
+ return cachedUint32ArrayMemory0;
339
+ }
340
+
341
+ let cachedUint8ArrayMemory0 = null;
342
+ function getUint8ArrayMemory0() {
343
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
344
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
345
+ }
346
+ return cachedUint8ArrayMemory0;
347
+ }
348
+
349
+ function handleError(f, args) {
350
+ try {
351
+ return f.apply(this, args);
352
+ } catch (e) {
353
+ const idx = addToExternrefTable0(e);
354
+ wasm.__wbindgen_exn_store(idx);
355
+ }
356
+ }
357
+
358
+ function passArray8ToWasm0(arg, malloc) {
359
+ const ptr = malloc(arg.length * 1, 1) >>> 0;
360
+ getUint8ArrayMemory0().set(arg, ptr / 1);
361
+ WASM_VECTOR_LEN = arg.length;
362
+ return ptr;
363
+ }
364
+
365
+ function passStringToWasm0(arg, malloc, realloc) {
366
+ if (realloc === undefined) {
367
+ const buf = cachedTextEncoder.encode(arg);
368
+ const ptr = malloc(buf.length, 1) >>> 0;
369
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
370
+ WASM_VECTOR_LEN = buf.length;
371
+ return ptr;
372
+ }
373
+
374
+ let len = arg.length;
375
+ let ptr = malloc(len, 1) >>> 0;
376
+
377
+ const mem = getUint8ArrayMemory0();
378
+
379
+ let offset = 0;
380
+
381
+ for (; offset < len; offset++) {
382
+ const code = arg.charCodeAt(offset);
383
+ if (code > 0x7F) break;
384
+ mem[ptr + offset] = code;
385
+ }
386
+ if (offset !== len) {
387
+ if (offset !== 0) {
388
+ arg = arg.slice(offset);
389
+ }
390
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
391
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
392
+ const ret = cachedTextEncoder.encodeInto(arg, view);
393
+
394
+ offset += ret.written;
395
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
396
+ }
397
+
398
+ WASM_VECTOR_LEN = offset;
399
+ return ptr;
400
+ }
401
+
402
+ function takeFromExternrefTable0(idx) {
403
+ const value = wasm.__wbindgen_externrefs.get(idx);
404
+ wasm.__externref_table_dealloc(idx);
405
+ return value;
406
+ }
407
+
408
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
409
+ cachedTextDecoder.decode();
410
+ function decodeText(ptr, len) {
411
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
412
+ }
413
+
414
+ const cachedTextEncoder = new TextEncoder();
415
+
416
+ if (!('encodeInto' in cachedTextEncoder)) {
417
+ cachedTextEncoder.encodeInto = function (arg, view) {
418
+ const buf = cachedTextEncoder.encode(arg);
419
+ view.set(buf);
420
+ return {
421
+ read: arg.length,
422
+ written: buf.length
423
+ };
424
+ };
425
+ }
426
+
427
+ let WASM_VECTOR_LEN = 0;
428
+
429
+ const wasmPath = `${__dirname}/telepapo_signal_bg.wasm`;
430
+ const wasmBytes = require('fs').readFileSync(wasmPath);
431
+ const wasmModule = new WebAssembly.Module(wasmBytes);
432
+ let wasmInstance = new WebAssembly.Instance(wasmModule, __wbg_get_imports());
433
+ let wasm = wasmInstance.exports;
434
+ wasm.__wbindgen_start();
Binary file
@@ -0,0 +1,27 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ export const memory: WebAssembly.Memory;
4
+ export const __wbg_signal_free: (a: number, b: number) => void;
5
+ export const signal_abrirSessao: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number];
6
+ export const signal_apagarSessao: (a: number, b: number, c: number, d: number) => void;
7
+ export const signal_carregar: (a: number, b: number) => [number, number, number];
8
+ export const signal_chavesIniciais: (a: number, b: number) => [number, number, number, number];
9
+ export const signal_cifrar: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number];
10
+ export const signal_decifrar: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => [number, number, number, number];
11
+ export const signal_definirEndereco: (a: number, b: number, c: number, d: number) => [number, number];
12
+ export const signal_dispositivosComSessao: (a: number, b: number, c: number) => [number, number];
13
+ export const signal_exportar: (a: number) => [number, number, number, number];
14
+ export const signal_identidadePublica: (a: number) => [number, number, number, number];
15
+ export const signal_lerTrocas: (a: number) => [number, number];
16
+ export const signal_novo: () => number;
17
+ export const signal_registrationId: (a: number) => number;
18
+ export const signal_reposicao: (a: number, b: number, c: number, d: number) => [number, number, number, number];
19
+ export const __wbindgen_exn_store: (a: number) => void;
20
+ export const __externref_table_alloc: () => number;
21
+ export const __wbindgen_externrefs: WebAssembly.Table;
22
+ export const __wbindgen_malloc: (a: number, b: number) => number;
23
+ export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
24
+ export const __externref_table_dealloc: (a: number) => void;
25
+ export const __wbindgen_free: (a: number, b: number, c: number) => void;
26
+ export const __externref_drop_slice: (a: number, b: number) => void;
27
+ export const __wbindgen_start: () => void;
package/src/anexos.js ADDED
@@ -0,0 +1,38 @@
1
+ // Anexos no mesmo esquema do app e do TelePapo Web (o do Signal):
2
+ // AES-256-CBC + HMAC-SHA256. A chave (64 bytes: 32 de AES, 32 de HMAC) e o
3
+ // digest viajam só dentro da mensagem cifrada; o servidor guarda
4
+ // `iv | cifrado | mac` e nada mais.
5
+ import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
6
+
7
+ export function cifrarAnexo(dado, chave) {
8
+ const iv = randomBytes(16);
9
+ const c = createCipheriv('aes-256-cbc', chave.subarray(0, 32), iv);
10
+ const corpo = Buffer.concat([iv, c.update(dado), c.final()]);
11
+ const mac = createHmac('sha256', chave.subarray(32, 64)).update(corpo).digest();
12
+ return Buffer.concat([corpo, mac]);
13
+ }
14
+
15
+ export function decifrarAnexo(blob, chave) {
16
+ if (blob.length < 16 + 16 + 32) throw new Error('Anexo curto demais.');
17
+ const corpo = blob.subarray(0, blob.length - 32);
18
+ // MAC conferido ANTES de decifrar: CBC sem MAC aceita blob adulterado.
19
+ const mac = createHmac('sha256', chave.subarray(32, 64)).update(corpo).digest();
20
+ if (!timingSafeEqual(mac, blob.subarray(blob.length - 32))) throw new Error('Anexo adulterado (MAC).');
21
+ const d = createDecipheriv('aes-256-cbc', chave.subarray(0, 32), corpo.subarray(0, 16));
22
+ return Buffer.concat([d.update(corpo.subarray(16)), d.final()]);
23
+ }
24
+
25
+ /** Cifra e devolve o blob e o que vai na mensagem ({chave, digest} em base64). */
26
+ export function prepararAnexo(dado) {
27
+ const chave = randomBytes(64);
28
+ const blob = cifrarAnexo(Buffer.from(dado), chave);
29
+ return { blob, chave: chave.toString('base64'), digest: createHash('sha256').update(blob).digest('base64') };
30
+ }
31
+
32
+ /** Confere o digest (vindo da mensagem cifrada) e decifra. */
33
+ export function abrirAnexo(blob, anexo) {
34
+ const digest = createHash('sha256').update(blob).digest();
35
+ const esperado = Buffer.from(anexo.digest ?? '', 'base64');
36
+ if (digest.length !== esperado.length || !timingSafeEqual(digest, esperado)) throw new Error('Anexo adulterado (digest).');
37
+ return decifrarAnexo(blob, Buffer.from(anexo.chave, 'base64'));
38
+ }
@@ -0,0 +1,57 @@
1
+ // Onde o SDK guarda o estado de cada conta autorizada: tokens e o estado do
2
+ // Signal (chaves PRIVADAS do aparelho do app e as sessões). Guarde em lugar
3
+ // seguro (disco cifrado, banco com controle de acesso): quem tem esse estado
4
+ // lê as mensagens das contas.
5
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
6
+ import { join } from 'node:path';
7
+
8
+ /** Para testes e protótipos: some ao reiniciar o processo. */
9
+ export class ArmazenamentoMemoria {
10
+ constructor() {
11
+ this.dados = new Map();
12
+ }
13
+
14
+ async ler(chave) {
15
+ return this.dados.get(chave) ?? null;
16
+ }
17
+
18
+ async gravar(chave, valor) {
19
+ this.dados.set(chave, valor);
20
+ }
21
+
22
+ async apagar(chave) {
23
+ this.dados.delete(chave);
24
+ }
25
+ }
26
+
27
+ /** Um arquivo JSON por chave, numa pasta (gravação atômica). */
28
+ export class ArmazenamentoArquivo {
29
+ constructor(pasta) {
30
+ this.pasta = pasta;
31
+ }
32
+
33
+ caminho(chave) {
34
+ return join(this.pasta, encodeURIComponent(chave) + '.json');
35
+ }
36
+
37
+ async ler(chave) {
38
+ try {
39
+ return JSON.parse(await readFile(this.caminho(chave), 'utf8'));
40
+ } catch (e) {
41
+ if (e.code === 'ENOENT') return null;
42
+ throw e;
43
+ }
44
+ }
45
+
46
+ async gravar(chave, valor) {
47
+ await mkdir(this.pasta, { recursive: true, mode: 0o700 });
48
+ const tmp = this.caminho(chave) + '.tmp';
49
+ await writeFile(tmp, JSON.stringify(valor), { mode: 0o600 });
50
+ await rename(tmp, this.caminho(chave));
51
+ }
52
+
53
+ async apagar(chave) {
54
+ const { rm } = await import('node:fs/promises');
55
+ await rm(this.caminho(chave), { force: true });
56
+ }
57
+ }
@@ -0,0 +1,43 @@
1
+ // Categoria e impressão que vão em claro para o servidor (estatística e
2
+ // detecção de spam). Precisam dar EXATAMENTE o que o app Android calcula: o
3
+ // app de quem recebe confere e, se não bater, avisa o servidor que o
4
+ // remetente mentiu. Ver Mensageiro.kt (categoria, impressao).
5
+
6
+ // Mesmo padrão de link do app (Previas.kt).
7
+ const LINK = /\b((?:https?:\/\/|www\.)[^\s<>"']+[^\s<>"'.,;:!?)\]])/i;
8
+ // `\s` do Java é só ASCII; o do JavaScript pega espaços Unicode. Igual ao app:
9
+ const ESPACOS = /[ \t\n\x0B\f\r]+/g;
10
+ const SEM_ACENTO = /\p{M}+/gu;
11
+
12
+ export function categoria(c) {
13
+ switch (c.tipo) {
14
+ case 'texto':
15
+ return c.previa || LINK.test(c.texto ?? '') ? 'link' : 'texto';
16
+ case 'midia': {
17
+ const mime = c.anexo?.mime ?? '';
18
+ if (c.figurinha) return 'figurinha';
19
+ if (c.gif) return 'gif';
20
+ if (c.recado) return 'recado_video';
21
+ if (c.unica) return 'visualizacao_unica';
22
+ if (mime.startsWith('image/')) return 'foto';
23
+ if (mime.startsWith('video/')) return 'video';
24
+ if (mime.startsWith('audio/')) return 'audio';
25
+ return 'documento';
26
+ }
27
+ case 'localizacao': case 'contato': case 'produto': case 'reacao': case 'recibo': case 'apagar': case 'chamada': case 'animacao':
28
+ return c.tipo;
29
+ default:
30
+ return 'controle';
31
+ }
32
+ }
33
+
34
+ /** 20 bits de SHA-256 do texto normalizado, em 5 dígitos hexadecimais. */
35
+ export async function impressao(c) {
36
+ const cat = categoria(c);
37
+ if (cat !== 'texto' && cat !== 'link') return null;
38
+ const normal = (c.texto ?? '').normalize('NFKD').replace(SEM_ACENTO, '').toLowerCase().replace(ESPACOS, ' ').trim();
39
+ if (!normal) return null;
40
+ const h = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode('telepapo-impressao-v1\n' + normal)));
41
+ const v = (h[0] << 12) | (h[1] << 4) | (h[2] >> 4);
42
+ return v.toString(16).padStart(5, '0');
43
+ }