@vidiemme/copilot-proxy 0.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/attribution.d.ts +34 -0
  4. package/dist/attribution.js +118 -0
  5. package/dist/attribution.js.map +1 -0
  6. package/dist/autostart.d.ts +10 -0
  7. package/dist/autostart.js +119 -0
  8. package/dist/autostart.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +393 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/collector/forwarder.d.ts +44 -0
  13. package/dist/collector/forwarder.js +203 -0
  14. package/dist/collector/forwarder.js.map +1 -0
  15. package/dist/config.d.ts +44 -0
  16. package/dist/config.js +105 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/defaults.d.ts +33 -0
  19. package/dist/defaults.js +35 -0
  20. package/dist/defaults.js.map +1 -0
  21. package/dist/gitRepository.d.ts +21 -0
  22. package/dist/gitRepository.js +147 -0
  23. package/dist/gitRepository.js.map +1 -0
  24. package/dist/paths.d.ts +12 -0
  25. package/dist/paths.js +39 -0
  26. package/dist/paths.js.map +1 -0
  27. package/dist/projectDetector.d.ts +48 -0
  28. package/dist/projectDetector.js +130 -0
  29. package/dist/projectDetector.js.map +1 -0
  30. package/dist/proxy/handler.d.ts +5 -0
  31. package/dist/proxy/handler.js +149 -0
  32. package/dist/proxy/handler.js.map +1 -0
  33. package/dist/proxy/headers.d.ts +6 -0
  34. package/dist/proxy/headers.js +52 -0
  35. package/dist/proxy/headers.js.map +1 -0
  36. package/dist/proxy/inspectRequest.d.ts +18 -0
  37. package/dist/proxy/inspectRequest.js +41 -0
  38. package/dist/proxy/inspectRequest.js.map +1 -0
  39. package/dist/server.d.ts +7 -0
  40. package/dist/server.js +66 -0
  41. package/dist/server.js.map +1 -0
  42. package/dist/usage/collector.d.ts +25 -0
  43. package/dist/usage/collector.js +63 -0
  44. package/dist/usage/collector.js.map +1 -0
  45. package/dist/usage/parseAnthropic.d.ts +23 -0
  46. package/dist/usage/parseAnthropic.js +83 -0
  47. package/dist/usage/parseAnthropic.js.map +1 -0
  48. package/dist/usage/parseOpenAI.d.ts +24 -0
  49. package/dist/usage/parseOpenAI.js +72 -0
  50. package/dist/usage/parseOpenAI.js.map +1 -0
  51. package/dist/usage/sse.d.ts +17 -0
  52. package/dist/usage/sse.js +59 -0
  53. package/dist/usage/sse.js.map +1 -0
  54. package/dist/usage/types.d.ts +23 -0
  55. package/dist/usage/types.js +19 -0
  56. package/dist/usage/types.js.map +1 -0
  57. package/package.json +50 -0
package/dist/cli.js ADDED
@@ -0,0 +1,393 @@
1
+ #!/usr/bin/env node
2
+ import { mkdirSync, readFileSync, writeFileSync, copyFileSync, statSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ import { parseArgs } from 'node:util';
5
+ import { installAutostart, isAutostartInstalled } from './autostart.js';
6
+ import { loadConfig } from './config.js';
7
+ import { configDir, configFile, vscodeSettingsFile } from './paths.js';
8
+ import { buildApp } from './server.js';
9
+ const OVERRIDE_KEY = 'github.copilot.advanced.debug.overrideCapiUrl';
10
+ const AUTH_TYPE_KEY = 'github.copilot.advanced.debug.overrideAuthType';
11
+ const USAGE = `
12
+ Proxy locale che misura il consumo di token di GitHub Copilot e lo attribuisce
13
+ al progetto su cui si sta lavorando.
14
+
15
+ npx @vidiemme/copilot-proxy setup configura la postazione (una volta sola)
16
+ npx @vidiemme/copilot-proxy start avvia il proxy
17
+ npx @vidiemme/copilot-proxy doctor verifica che tutto sia a posto
18
+
19
+ Opzioni di setup:
20
+ --vscode scrive anche le impostazioni utente di VS Code
21
+ --autostart avvia il proxy a ogni accesso, senza doverlo lanciare
22
+ --workspace-roots <a,b> cartelle sotto cui vivono i repository (default: la home)
23
+ --port <n> porta locale del proxy (default 8787)
24
+ --collector-url <url> endpoint di raccolta, se diverso da quello aziendale
25
+ --salt <valore> salt degli pseudonimi, se diverso da quello aziendale
26
+ --upstream <url> endpoint Copilot (default https://api.githubcopilot.com)
27
+ --force sovrascrive una configurazione esistente
28
+
29
+ L'unica cosa da procurarsi e' il token di raccolta, uguale per tutta l'azienda.
30
+ Non si passa come opzione: finirebbe nella cronologia della shell e nella lista
31
+ dei processi. Va nella variabile COLLECTOR_TOKEN, oppure lo chiede il comando in
32
+ modo nascosto.
33
+
34
+ Opzioni di start:
35
+ --port <n> --log-level <livello> --collector-url <url> --workspace-roots <a,b>
36
+ `.trimStart();
37
+ /** I valori sul filo usano le stesse chiavi delle variabili d'ambiente: un solo vocabolario da documentare. */
38
+ function readConfigFile() {
39
+ let raw;
40
+ try {
41
+ raw = readFileSync(configFile(), 'utf-8');
42
+ }
43
+ catch {
44
+ return {};
45
+ }
46
+ let parsed;
47
+ try {
48
+ parsed = JSON.parse(raw);
49
+ }
50
+ catch {
51
+ throw new Error(`${configFile()} non e' un JSON valido: correggilo o rilancia "setup --force"`);
52
+ }
53
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
54
+ throw new Error(`${configFile()} deve contenere un oggetto JSON`);
55
+ }
56
+ const source = {};
57
+ for (const [key, value] of Object.entries(parsed)) {
58
+ if (typeof value === 'string' || typeof value === 'number')
59
+ source[key] = String(value);
60
+ }
61
+ return source;
62
+ }
63
+ /** Precedenza: opzioni della riga di comando, poi ambiente, poi file di configurazione. */
64
+ function mergedSource(overrides) {
65
+ const merged = { ...readConfigFile() };
66
+ for (const [key, value] of Object.entries(process.env)) {
67
+ if (value !== undefined && value.trim().length > 0)
68
+ merged[key] = value;
69
+ }
70
+ for (const [key, value] of Object.entries(overrides)) {
71
+ if (value !== undefined)
72
+ merged[key] = value;
73
+ }
74
+ return merged;
75
+ }
76
+ function writeConfigFile(values) {
77
+ mkdirSync(configDir(), { recursive: true, mode: 0o700 });
78
+ // Il file contiene il token di raccolta: leggibile solo dal proprietario.
79
+ writeFileSync(configFile(), `${JSON.stringify(values, null, 2)}\n`, { mode: 0o600 });
80
+ }
81
+ /**
82
+ * Legge un segreto dal terminale senza mostrarlo.
83
+ *
84
+ * Serve la modalita' raw perche' `readline` non sa nascondere l'eco: si legge
85
+ * carattere per carattere e si stampa nulla.
86
+ */
87
+ function promptSecret(question) {
88
+ const { stdin, stdout } = process;
89
+ if (!stdin.isTTY) {
90
+ return Promise.reject(new Error('COLLECTOR_TOKEN non impostato e nessun terminale interattivo disponibile: esporta la variabile prima di lanciare il comando'));
91
+ }
92
+ return new Promise((resolve, reject) => {
93
+ stdout.write(question);
94
+ stdin.setRawMode(true);
95
+ stdin.resume();
96
+ stdin.setEncoding('utf8');
97
+ let value = '';
98
+ const finish = (action) => {
99
+ stdin.setRawMode(false);
100
+ stdin.pause();
101
+ stdin.off('data', onData);
102
+ stdout.write('\n');
103
+ action();
104
+ };
105
+ const onData = (chunk) => {
106
+ for (const char of chunk) {
107
+ if (char === '\r' || char === '\n')
108
+ return finish(() => resolve(value));
109
+ if (char === '\u0003')
110
+ return finish(() => reject(new Error('interrotto')));
111
+ if (char === '\u007f' || char === '\b')
112
+ value = value.slice(0, -1);
113
+ else if (char >= ' ')
114
+ value += char;
115
+ }
116
+ };
117
+ stdin.on('data', onData);
118
+ });
119
+ }
120
+ async function commandSetup(flags) {
121
+ const existing = readConfigFile();
122
+ if (Object.keys(existing).length > 0 && flags.force !== true) {
123
+ console.error(`Esiste gia' ${configFile()}. Rilancia con --force per sostituirlo.`);
124
+ return 1;
125
+ }
126
+ const token = process.env.COLLECTOR_TOKEN ?? (await promptSecret('Token di raccolta: '));
127
+ if (token.trim().length === 0) {
128
+ console.error('Token vuoto.');
129
+ return 1;
130
+ }
131
+ // Si scrive solo cio' che si discosta dai default del pacchetto: un file
132
+ // corto e' un file che si capisce, e i default restano aggiornabili con una
133
+ // nuova versione invece che postazione per postazione.
134
+ const values = { COLLECTOR_TOKEN: token.trim() };
135
+ const collectorUrl = flags['collector-url'];
136
+ if (collectorUrl)
137
+ values.COLLECTOR_URL = collectorUrl;
138
+ const salt = flags.salt;
139
+ if (salt)
140
+ values.DEVELOPER_ID_SALT = salt;
141
+ const workspaceRoots = flags['workspace-roots'];
142
+ if (workspaceRoots)
143
+ values.WORKSPACE_ROOTS = workspaceRoots;
144
+ if (flags.port)
145
+ values.PORT = String(flags.port);
146
+ if (flags.upstream)
147
+ values.UPSTREAM_BASE_URL = String(flags.upstream);
148
+ // Meglio scoprire ora che la configurazione non regge, non al primo avvio.
149
+ let config;
150
+ try {
151
+ config = loadConfig({ ...values });
152
+ }
153
+ catch (error) {
154
+ console.error(`Configurazione non valida: ${error.message}`);
155
+ return 1;
156
+ }
157
+ writeConfigFile(values);
158
+ console.log(`Scritto ${configFile()}`);
159
+ console.log(`Raccolta verso ${config.collectorUrl}`);
160
+ console.log(`Progetti cercati sotto ${config.workspaceRoots.join(', ')}`);
161
+ if (flags.vscode === true)
162
+ applyVsCodeSettings(config.port);
163
+ else
164
+ printVsCodeSettings(config.port);
165
+ if (flags.autostart === true) {
166
+ try {
167
+ const service = installAutostart();
168
+ console.log(`\nAvvio automatico attivo: ${service.file}`);
169
+ console.log(`Il proxy e' gia' partito e ripartira' a ogni accesso. Per disattivarlo:\n ${service.removeHint}`);
170
+ if (service.warning)
171
+ console.log(`\nATTENZIONE: ${service.warning}`);
172
+ }
173
+ catch (error) {
174
+ console.error(`\nAvvio automatico non riuscito: ${error.message}`);
175
+ return 1;
176
+ }
177
+ return 0;
178
+ }
179
+ console.log('\nPoi avvia il proxy con: npx @vidiemme/copilot-proxy start');
180
+ console.log('Per non doverlo fare ogni volta, rilancia il setup con --autostart');
181
+ return 0;
182
+ }
183
+ function vsCodeSnippet(port) {
184
+ return { [OVERRIDE_KEY]: `http://127.0.0.1:${port}`, [AUTH_TYPE_KEY]: 'token' };
185
+ }
186
+ function printVsCodeSettings(port) {
187
+ console.log('\nAggiungi alle impostazioni utente di VS Code (Preferences: Open User Settings (JSON)):\n' +
188
+ `${JSON.stringify(vsCodeSnippet(port), null, 2)}\n` +
189
+ 'Oppure rilancia il setup con --vscode per farlo scrivere al comando.');
190
+ }
191
+ function applyVsCodeSettings(port) {
192
+ const file = vscodeSettingsFile();
193
+ let current = {};
194
+ let exists = true;
195
+ try {
196
+ const raw = readFileSync(file, 'utf-8');
197
+ // `settings.json` ammette commenti e virgole finali: se non e' JSON puro,
198
+ // riscriverlo lo distruggerebbe. Meglio arrendersi e dire cosa aggiungere.
199
+ current = JSON.parse(raw);
200
+ copyFileSync(file, `${file}.bak`);
201
+ }
202
+ catch (error) {
203
+ exists = error.code !== 'ENOENT';
204
+ if (exists) {
205
+ console.log(`\nNon riesco a modificare ${file} senza rischiare di rovinarlo (contiene commenti o non e' JSON valido).`);
206
+ printVsCodeSettings(port);
207
+ return;
208
+ }
209
+ }
210
+ mkdirSync(dirname(file), { recursive: true });
211
+ writeFileSync(file, `${JSON.stringify({ ...current, ...vsCodeSnippet(port) }, null, 2)}\n`);
212
+ console.log(`Aggiornato ${file}${exists ? ` (backup in ${file}.bak)` : ''}`);
213
+ console.log("Riavvia VS Code perche' le impostazioni abbiano effetto.");
214
+ }
215
+ async function commandStart(flags) {
216
+ const overrides = {};
217
+ if (flags.port)
218
+ overrides.PORT = String(flags.port);
219
+ if (flags['log-level'])
220
+ overrides.LOG_LEVEL = String(flags['log-level']);
221
+ if (flags['collector-url'])
222
+ overrides.COLLECTOR_URL = String(flags['collector-url']);
223
+ if (flags['workspace-roots'])
224
+ overrides.WORKSPACE_ROOTS = String(flags['workspace-roots']);
225
+ let config;
226
+ try {
227
+ config = loadConfig(mergedSource(overrides));
228
+ }
229
+ catch (error) {
230
+ console.error(`${error.message}\n\nSe e' la prima volta: npx @vidiemme/copilot-proxy setup`);
231
+ return 1;
232
+ }
233
+ const app = await buildApp(config);
234
+ await app.server.listen({ port: config.port, host: config.host });
235
+ const shutdown = (signal) => {
236
+ app.server.log.info({ signal }, 'arresto in corso');
237
+ void app.close().then(() => process.exit(0), (error) => {
238
+ app.server.log.error({ err: error }, 'arresto fallito');
239
+ process.exit(1);
240
+ });
241
+ };
242
+ process.on('SIGINT', () => shutdown('SIGINT'));
243
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
244
+ return 0;
245
+ }
246
+ async function commandDoctor() {
247
+ const results = [];
248
+ const check = (ok, message) => {
249
+ results.push([ok, message]);
250
+ };
251
+ let config;
252
+ try {
253
+ config = loadConfig(mergedSource({}));
254
+ check(true, 'configurazione leggibile e completa');
255
+ }
256
+ catch (error) {
257
+ check(false, `configurazione: ${error.message}`);
258
+ report(results);
259
+ console.log('\nLancia prima: npx @vidiemme/copilot-proxy setup');
260
+ return 1;
261
+ }
262
+ const roots = config.workspaceRoots;
263
+ if (roots.length === 0) {
264
+ check(false, 'WORKSPACE_ROOTS non impostato: il progetto finira\' in "unassigned"');
265
+ }
266
+ else {
267
+ const missing = roots.filter((root) => !existsDir(root));
268
+ check(missing.length === 0, missing.length === 0
269
+ ? `cartelle di lavoro raggiungibili (${roots.join(', ')})`
270
+ : `cartelle di lavoro inesistenti: ${missing.join(', ')}`);
271
+ }
272
+ // Un batch vuoto viene rifiutato dalla validazione, che pero' gira *dopo*
273
+ // l'autenticazione: un 400 dimostra quindi che il token e' stato accettato.
274
+ try {
275
+ const response = await fetch(config.collectorUrl, {
276
+ method: 'POST',
277
+ headers: {
278
+ 'content-type': 'application/json',
279
+ authorization: `Bearer ${config.collectorToken}`,
280
+ },
281
+ body: JSON.stringify({ events: [] }),
282
+ signal: AbortSignal.timeout(config.collectorTimeoutMs),
283
+ });
284
+ if (response.status === 401)
285
+ check(false, 'il servizio di raccolta rifiuta il token');
286
+ else if (response.status === 400)
287
+ check(true, 'servizio di raccolta raggiungibile, token valido');
288
+ else
289
+ check(false, `il servizio di raccolta risponde ${response.status}, inatteso`);
290
+ }
291
+ catch (error) {
292
+ check(false, `servizio di raccolta irraggiungibile: ${error.message}`);
293
+ }
294
+ // Ricerca testuale e non `JSON.parse`: `settings.json` ammette commenti e
295
+ // virgole finali, e una diagnostica non deve fallire per quello.
296
+ try {
297
+ const raw = readFileSync(vscodeSettingsFile(), 'utf-8');
298
+ const match = new RegExp(`"${OVERRIDE_KEY.replace(/\./g, '\\.')}"\\s*:\\s*"([^"]*)"`).exec(raw);
299
+ const expected = `http://127.0.0.1:${config.port}`;
300
+ // `localhost` e `127.0.0.1` sono equivalenti: non vale la pena segnalarlo.
301
+ const ok = match !== null && new RegExp(`^https?://(127\\.0\\.0\\.1|localhost):${config.port}/?$`).test(match[1] ?? '');
302
+ check(ok, ok
303
+ ? `VS Code punta al proxy (${match?.[1] ?? expected})`
304
+ : match
305
+ ? `VS Code punta a ${match[1]}, atteso ${expected}`
306
+ : "VS Code non e' configurato per passare dal proxy");
307
+ }
308
+ catch {
309
+ check(false, `impostazioni di VS Code non trovate in ${vscodeSettingsFile()}`);
310
+ }
311
+ const pending = spoolSize(config.spoolPath);
312
+ check(pending === 0, pending === 0 ? 'nessun evento in attesa di consegna' : `${pending} byte di eventi in attesa nello spool`);
313
+ // L'avvio automatico interessa solo se il proxy non sta gia' girando: chi
314
+ // preferisce lanciarlo a mano non deve vedere un errore.
315
+ let proxyUp = false;
316
+ try {
317
+ const health = await fetch(`http://127.0.0.1:${config.port}/_health`, {
318
+ signal: AbortSignal.timeout(2000),
319
+ });
320
+ proxyUp = health.ok;
321
+ }
322
+ catch {
323
+ proxyUp = false;
324
+ }
325
+ check(proxyUp, proxyUp
326
+ ? `proxy in ascolto${isAutostartInstalled() ? ', con avvio automatico' : ''}`
327
+ : `nessun proxy in ascolto sulla porta ${config.port}: attivalo per sempre con "setup --autostart --force", o una volta sola con "start"`);
328
+ report(results);
329
+ return results.every(([ok]) => ok) ? 0 : 1;
330
+ }
331
+ function report(results) {
332
+ for (const [ok, message] of results)
333
+ console.log(`${ok ? 'OK ' : 'FAIL'} ${message}`);
334
+ }
335
+ function existsDir(path) {
336
+ try {
337
+ return statSync(path).isDirectory();
338
+ }
339
+ catch {
340
+ return false;
341
+ }
342
+ }
343
+ function spoolSize(path) {
344
+ try {
345
+ return statSync(path).size;
346
+ }
347
+ catch {
348
+ return 0;
349
+ }
350
+ }
351
+ async function main() {
352
+ const { values, positionals } = parseArgs({
353
+ args: process.argv.slice(2),
354
+ allowPositionals: true,
355
+ options: {
356
+ 'collector-url': { type: 'string' },
357
+ salt: { type: 'string' },
358
+ 'workspace-roots': { type: 'string' },
359
+ port: { type: 'string' },
360
+ upstream: { type: 'string' },
361
+ 'log-level': { type: 'string' },
362
+ vscode: { type: 'boolean' },
363
+ autostart: { type: 'boolean' },
364
+ force: { type: 'boolean' },
365
+ help: { type: 'boolean', short: 'h' },
366
+ },
367
+ });
368
+ const command = positionals[0] ?? 'start';
369
+ if (values.help === true || command === 'help') {
370
+ console.log(USAGE);
371
+ return;
372
+ }
373
+ switch (command) {
374
+ case 'setup':
375
+ process.exitCode = await commandSetup(values);
376
+ return;
377
+ case 'start':
378
+ process.exitCode = await commandStart(values);
379
+ return;
380
+ case 'doctor':
381
+ process.exitCode = await commandDoctor();
382
+ return;
383
+ default:
384
+ console.error(`Comando sconosciuto: ${command}\n`);
385
+ console.log(USAGE);
386
+ process.exitCode = 1;
387
+ }
388
+ }
389
+ main().catch((error) => {
390
+ console.error(error instanceof Error ? error.message : error);
391
+ process.exit(1);
392
+ });
393
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { type UsageEventPayload } from '@vidiemme/copilot-usage-contract';
2
+ import type { ClientConfig } from '../config.js';
3
+ export interface Logger {
4
+ info: (obj: object, msg: string) => void;
5
+ warn: (obj: object, msg: string) => void;
6
+ error: (obj: object, msg: string) => void;
7
+ }
8
+ /**
9
+ * Inoltra gli eventi di consumo al servizio di raccolta.
10
+ *
11
+ * Il proxy non deve mai dipendere dalla raggiungibilita' del servizio: se la
12
+ * rete manca o il collector e' in manutenzione, le richieste Copilot devono
13
+ * continuare a passare e la misura non deve andare persa. Gli eventi non
14
+ * consegnati finiscono quindi su un file di spool e vengono ritentati; la
15
+ * chiave `requestId` rende i rinvii innocui, perche' il server deduplica.
16
+ */
17
+ export declare class UsageForwarder {
18
+ private readonly config;
19
+ private readonly logger;
20
+ private queue;
21
+ private timer;
22
+ private pending;
23
+ private dropped;
24
+ private readonly batchSize;
25
+ private readonly spoolPath;
26
+ private readonly sendingPath;
27
+ constructor(config: ClientConfig, logger: Logger);
28
+ start(): void;
29
+ enqueue(event: UsageEventPayload): void;
30
+ /**
31
+ * Gli invii sono serializzati: due flush concorrenti si contenderebbero lo
32
+ * spool. Chi chiama attende il proprio turno, cosi' `close()` ha la garanzia
33
+ * che al ritorno non sia rimasto nulla in volo.
34
+ */
35
+ flush(): Promise<void>;
36
+ private runFlush;
37
+ close(): Promise<void>;
38
+ private send;
39
+ private drainSpool;
40
+ private recoverInterrupted;
41
+ private readEvents;
42
+ private spool;
43
+ private trimSpool;
44
+ }
@@ -0,0 +1,203 @@
1
+ import { appendFile, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
2
+ import { MAX_EVENTS_PER_BATCH } from '@vidiemme/copilot-usage-contract';
3
+ /** Oltre questa soglia si scartano eventi per non far crescere la memoria del proxy. */
4
+ const MAX_QUEUE_LENGTH = 10_000;
5
+ /** Tetto agli eventi parcheggiati su disco: si tengono i piu' recenti. */
6
+ const MAX_SPOOL_EVENTS = 50_000;
7
+ function chunk(items, size) {
8
+ const chunks = [];
9
+ for (let index = 0; index < items.length; index += size) {
10
+ chunks.push(items.slice(index, index + size));
11
+ }
12
+ return chunks;
13
+ }
14
+ /**
15
+ * Inoltra gli eventi di consumo al servizio di raccolta.
16
+ *
17
+ * Il proxy non deve mai dipendere dalla raggiungibilita' del servizio: se la
18
+ * rete manca o il collector e' in manutenzione, le richieste Copilot devono
19
+ * continuare a passare e la misura non deve andare persa. Gli eventi non
20
+ * consegnati finiscono quindi su un file di spool e vengono ritentati; la
21
+ * chiave `requestId` rende i rinvii innocui, perche' il server deduplica.
22
+ */
23
+ export class UsageForwarder {
24
+ config;
25
+ logger;
26
+ queue = [];
27
+ timer;
28
+ pending;
29
+ dropped = 0;
30
+ batchSize;
31
+ spoolPath;
32
+ sendingPath;
33
+ constructor(config, logger) {
34
+ this.config = config;
35
+ this.logger = logger;
36
+ this.batchSize = Math.min(config.flushMaxBatch, MAX_EVENTS_PER_BATCH);
37
+ this.spoolPath = config.spoolPath;
38
+ this.sendingPath = `${config.spoolPath}.sending`;
39
+ }
40
+ start() {
41
+ if (this.timer)
42
+ return;
43
+ // Un invio interrotto a meta' lascia indietro il file di lavoro: va
44
+ // recuperato prima di ripartire, altrimenti quegli eventi resterebbero li'.
45
+ // Entra nella catena, cosi' il primo flush lo trova gia' fatto.
46
+ this.pending = this.recoverInterrupted().catch(() => { });
47
+ this.timer = setInterval(() => {
48
+ void this.flush();
49
+ }, this.config.flushIntervalMs);
50
+ this.timer.unref();
51
+ }
52
+ enqueue(event) {
53
+ if (this.queue.length >= MAX_QUEUE_LENGTH) {
54
+ this.dropped += 1;
55
+ if (this.dropped % 100 === 1) {
56
+ this.logger.error({ dropped: this.dropped }, 'coda usage piena, eventi scartati');
57
+ }
58
+ return;
59
+ }
60
+ this.queue.push(event);
61
+ if (this.queue.length >= this.batchSize)
62
+ void this.flush();
63
+ }
64
+ /**
65
+ * Gli invii sono serializzati: due flush concorrenti si contenderebbero lo
66
+ * spool. Chi chiama attende il proprio turno, cosi' `close()` ha la garanzia
67
+ * che al ritorno non sia rimasto nulla in volo.
68
+ */
69
+ async flush() {
70
+ const run = (this.pending ?? Promise.resolve()).then(() => this.runFlush());
71
+ this.pending = run.catch(() => { });
72
+ await run;
73
+ }
74
+ async runFlush() {
75
+ try {
76
+ await this.drainSpool();
77
+ while (this.queue.length > 0) {
78
+ const batch = this.queue.splice(0, this.batchSize);
79
+ if (!(await this.send(batch))) {
80
+ await this.spool([...batch, ...this.queue.splice(0)]);
81
+ return;
82
+ }
83
+ }
84
+ }
85
+ catch (error) {
86
+ this.logger.error({ err: error }, 'inoltro usage fallito');
87
+ }
88
+ }
89
+ async close() {
90
+ if (this.timer)
91
+ clearInterval(this.timer);
92
+ this.timer = undefined;
93
+ await this.flush();
94
+ // Cio' che resta viene parcheggiato: lo riprendera' il prossimo avvio.
95
+ if (this.queue.length > 0)
96
+ await this.spool(this.queue.splice(0));
97
+ }
98
+ async send(events) {
99
+ if (events.length === 0)
100
+ return true;
101
+ let response;
102
+ try {
103
+ response = await fetch(this.config.collectorUrl, {
104
+ method: 'POST',
105
+ headers: {
106
+ 'content-type': 'application/json',
107
+ authorization: `Bearer ${this.config.collectorToken}`,
108
+ },
109
+ body: JSON.stringify({ events }),
110
+ signal: AbortSignal.timeout(this.config.collectorTimeoutMs),
111
+ });
112
+ }
113
+ catch (error) {
114
+ this.logger.warn({ err: error, events: events.length }, 'collector irraggiungibile');
115
+ return false;
116
+ }
117
+ if (response.ok)
118
+ return true;
119
+ // Un 4xx non si risolve ritentando: il payload e' rifiutato o le
120
+ // credenziali sono sbagliate. Tenerlo nello spool bloccherebbe tutto il
121
+ // resto della coda, quindi si scarta e si segnala.
122
+ const permanent = response.status >= 400 && response.status < 500 && response.status !== 429;
123
+ this.logger.error({ status: response.status, events: events.length, permanent }, permanent ? 'collector ha rifiutato gli eventi' : 'collector non disponibile');
124
+ return permanent;
125
+ }
126
+ async drainSpool() {
127
+ try {
128
+ await rename(this.spoolPath, this.sendingPath);
129
+ }
130
+ catch {
131
+ return;
132
+ }
133
+ const events = await this.readEvents(this.sendingPath);
134
+ const batches = chunk(events, this.batchSize);
135
+ for (let index = 0; index < batches.length; index += 1) {
136
+ if (await this.send(batches[index]))
137
+ continue;
138
+ await this.spool(batches.slice(index).flat());
139
+ break;
140
+ }
141
+ await rm(this.sendingPath, { force: true });
142
+ }
143
+ async recoverInterrupted() {
144
+ const events = await this.readEvents(this.sendingPath);
145
+ if (events.length === 0) {
146
+ await rm(this.sendingPath, { force: true });
147
+ return;
148
+ }
149
+ this.logger.warn({ events: events.length }, 'ripresi eventi da un invio interrotto');
150
+ await this.spool(events);
151
+ await rm(this.sendingPath, { force: true });
152
+ }
153
+ async readEvents(path) {
154
+ let content;
155
+ try {
156
+ content = await readFile(path, 'utf-8');
157
+ }
158
+ catch {
159
+ return [];
160
+ }
161
+ const events = [];
162
+ for (const line of content.split('\n')) {
163
+ if (line.trim().length === 0)
164
+ continue;
165
+ try {
166
+ events.push(JSON.parse(line));
167
+ }
168
+ catch {
169
+ // Riga troncata da un arresto brusco: si scarta solo quella.
170
+ }
171
+ }
172
+ return events;
173
+ }
174
+ async spool(events) {
175
+ if (events.length === 0)
176
+ return;
177
+ const lines = `${events.map((event) => JSON.stringify(event)).join('\n')}\n`;
178
+ try {
179
+ const size = await stat(this.spoolPath).then((info) => info.size, () => 0);
180
+ if (size === 0) {
181
+ await writeFile(this.spoolPath, lines, { mode: 0o600 });
182
+ }
183
+ else {
184
+ await appendFile(this.spoolPath, lines);
185
+ }
186
+ await this.trimSpool();
187
+ }
188
+ catch (error) {
189
+ this.logger.error({ err: error, events: events.length }, 'scrittura dello spool fallita');
190
+ }
191
+ }
192
+ async trimSpool() {
193
+ const events = await this.readEvents(this.spoolPath);
194
+ if (events.length <= MAX_SPOOL_EVENTS)
195
+ return;
196
+ const kept = events.slice(-MAX_SPOOL_EVENTS);
197
+ this.logger.error({ dropped: events.length - kept.length }, "spool oltre il limite, scartati gli eventi piu' vecchi");
198
+ await writeFile(this.spoolPath, `${kept.map((event) => JSON.stringify(event)).join('\n')}\n`, {
199
+ mode: 0o600,
200
+ });
201
+ }
202
+ }
203
+ //# sourceMappingURL=forwarder.js.map