@abbacchio/transport 0.1.1 → 0.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abbacchio/transport",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Node.js log transports for Pino, Winston, and Bunyan - send logs to Abbacchio",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,10 +27,6 @@
27
27
  "default": "./dist/transports/console.js"
28
28
  }
29
29
  },
30
- "scripts": {
31
- "build": "tsc",
32
- "dev": "tsc --watch"
33
- },
34
30
  "keywords": [
35
31
  "pino",
36
32
  "winston",
@@ -52,5 +48,9 @@
52
48
  },
53
49
  "engines": {
54
50
  "node": ">=18"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc",
54
+ "dev": "tsc --watch"
55
55
  }
56
- }
56
+ }
package/src/client.ts CHANGED
@@ -1,148 +1,148 @@
1
- import { encrypt } from "./encrypt.js";
2
-
3
- export interface AbbacchioClientOptions {
4
- /** Server URL endpoint */
5
- url?: string;
6
- /** Secret key for encryption. If provided, logs will be encrypted before sending */
7
- secretKey?: string;
8
- /** Channel/app name for multi-app support. Defaults to 'default' */
9
- channel?: string;
10
- /** Number of logs to batch before sending. Defaults to 10 */
11
- batchSize?: number;
12
- /** Interval in ms between flushes. Defaults to 1000 */
13
- interval?: number;
14
- /** Additional headers to send with requests */
15
- headers?: Record<string, string>;
16
- }
17
-
18
- /**
19
- * Shared HTTP client for all Abbacchio transports.
20
- * Handles batching, encryption, and HTTP communication.
21
- */
22
- export class AbbacchioClient {
23
- private url: string;
24
- private secretKey?: string;
25
- private channel?: string;
26
- private batchSize: number;
27
- private interval: number;
28
- private headers: Record<string, string>;
29
-
30
- private buffer: unknown[] = [];
31
- private timer: ReturnType<typeof setTimeout> | null = null;
32
-
33
- constructor(options: AbbacchioClientOptions = {}) {
34
- this.url = options.url || "http://localhost:4000/api/logs";
35
- this.secretKey = options.secretKey;
36
- this.channel = options.channel;
37
- this.batchSize = options.batchSize || 10;
38
- this.interval = options.interval || 1000;
39
- this.headers = options.headers || {};
40
- }
41
-
42
- /**
43
- * Process a log entry (encrypt if secretKey is provided)
44
- */
45
- private processLog(log: unknown): unknown {
46
- if (this.secretKey) {
47
- return { encrypted: encrypt(JSON.stringify(log), this.secretKey) };
48
- }
49
- return log;
50
- }
51
-
52
- /**
53
- * Add a log to the buffer and trigger send if needed
54
- */
55
- add(log: unknown): void {
56
- this.buffer.push(this.processLog(log));
57
-
58
- if (this.buffer.length >= this.batchSize) {
59
- this.flush();
60
- } else {
61
- this.scheduleSend();
62
- }
63
- }
64
-
65
- /**
66
- * Add multiple logs at once
67
- */
68
- addBatch(logs: unknown[]): void {
69
- for (const log of logs) {
70
- this.buffer.push(this.processLog(log));
71
- }
72
-
73
- if (this.buffer.length >= this.batchSize) {
74
- this.flush();
75
- } else {
76
- this.scheduleSend();
77
- }
78
- }
79
-
80
- /**
81
- * Send logs immediately without batching
82
- */
83
- async send(logs: unknown[]): Promise<void> {
84
- const processedLogs = logs.map(log => this.processLog(log));
85
- await this.sendToServer(processedLogs);
86
- }
87
-
88
- /**
89
- * Schedule a send after the interval
90
- */
91
- private scheduleSend(): void {
92
- if (this.timer) return;
93
- this.timer = setTimeout(() => {
94
- this.timer = null;
95
- this.flush();
96
- }, this.interval);
97
- }
98
-
99
- /**
100
- * Flush the buffer and send to server
101
- */
102
- async flush(): Promise<void> {
103
- if (this.buffer.length === 0) return;
104
-
105
- const toSend = this.buffer;
106
- this.buffer = [];
107
-
108
- await this.sendToServer(toSend);
109
- }
110
-
111
- /**
112
- * Send logs to the Abbacchio server
113
- */
114
- private async sendToServer(logs: unknown[]): Promise<void> {
115
- try {
116
- await fetch(this.url, {
117
- method: "POST",
118
- headers: {
119
- "Content-Type": "application/json",
120
- "X-Encrypted": this.secretKey ? "true" : "false",
121
- ...(this.channel ? { "X-Channel": this.channel } : {}),
122
- ...this.headers,
123
- },
124
- body: JSON.stringify({ logs }),
125
- });
126
- } catch {
127
- // Silently fail - don't break the app if Abbacchio server is down
128
- }
129
- }
130
-
131
- /**
132
- * Close the client and flush any remaining logs
133
- */
134
- async close(): Promise<void> {
135
- if (this.timer) {
136
- clearTimeout(this.timer);
137
- this.timer = null;
138
- }
139
- await this.flush();
140
- }
141
- }
142
-
143
- /**
144
- * Create a new Abbacchio client instance
145
- */
146
- export function createClient(options?: AbbacchioClientOptions): AbbacchioClient {
147
- return new AbbacchioClient(options);
148
- }
1
+ import { encrypt } from "./encrypt.js";
2
+
3
+ export interface AbbacchioClientOptions {
4
+ /** Server URL endpoint */
5
+ url?: string;
6
+ /** Secret key for encryption. If provided, logs will be encrypted before sending */
7
+ secretKey?: string;
8
+ /** Channel/app name for multi-app support. Defaults to 'default' */
9
+ channel?: string;
10
+ /** Number of logs to batch before sending. Defaults to 10 */
11
+ batchSize?: number;
12
+ /** Interval in ms between flushes. Defaults to 1000 */
13
+ interval?: number;
14
+ /** Additional headers to send with requests */
15
+ headers?: Record<string, string>;
16
+ }
17
+
18
+ /**
19
+ * Shared HTTP client for all Abbacchio transports.
20
+ * Handles batching, encryption, and HTTP communication.
21
+ */
22
+ export class AbbacchioClient {
23
+ private url: string;
24
+ private secretKey?: string;
25
+ private channel?: string;
26
+ private batchSize: number;
27
+ private interval: number;
28
+ private headers: Record<string, string>;
29
+
30
+ private buffer: unknown[] = [];
31
+ private timer: ReturnType<typeof setTimeout> | null = null;
32
+
33
+ constructor(options: AbbacchioClientOptions = {}) {
34
+ this.url = options.url || "http://localhost:4000/api/logs";
35
+ this.secretKey = options.secretKey;
36
+ this.channel = options.channel;
37
+ this.batchSize = options.batchSize || 10;
38
+ this.interval = options.interval || 1000;
39
+ this.headers = options.headers || {};
40
+ }
41
+
42
+ /**
43
+ * Process a log entry (encrypt if secretKey is provided)
44
+ */
45
+ private processLog(log: unknown): unknown {
46
+ if (this.secretKey) {
47
+ return { encrypted: encrypt(JSON.stringify(log), this.secretKey) };
48
+ }
49
+ return log;
50
+ }
51
+
52
+ /**
53
+ * Add a log to the buffer and trigger send if needed
54
+ */
55
+ add(log: unknown): void {
56
+ this.buffer.push(this.processLog(log));
57
+
58
+ if (this.buffer.length >= this.batchSize) {
59
+ this.flush();
60
+ } else {
61
+ this.scheduleSend();
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Add multiple logs at once
67
+ */
68
+ addBatch(logs: unknown[]): void {
69
+ for (const log of logs) {
70
+ this.buffer.push(this.processLog(log));
71
+ }
72
+
73
+ if (this.buffer.length >= this.batchSize) {
74
+ this.flush();
75
+ } else {
76
+ this.scheduleSend();
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Send logs immediately without batching
82
+ */
83
+ async send(logs: unknown[]): Promise<void> {
84
+ const processedLogs = logs.map(log => this.processLog(log));
85
+ await this.sendToServer(processedLogs);
86
+ }
87
+
88
+ /**
89
+ * Schedule a send after the interval
90
+ */
91
+ private scheduleSend(): void {
92
+ if (this.timer) return;
93
+ this.timer = setTimeout(() => {
94
+ this.timer = null;
95
+ this.flush();
96
+ }, this.interval);
97
+ }
98
+
99
+ /**
100
+ * Flush the buffer and send to server
101
+ */
102
+ async flush(): Promise<void> {
103
+ if (this.buffer.length === 0) return;
104
+
105
+ const toSend = this.buffer;
106
+ this.buffer = [];
107
+
108
+ await this.sendToServer(toSend);
109
+ }
110
+
111
+ /**
112
+ * Send logs to the Abbacchio server
113
+ */
114
+ private async sendToServer(logs: unknown[]): Promise<void> {
115
+ try {
116
+ await fetch(this.url, {
117
+ method: "POST",
118
+ headers: {
119
+ "Content-Type": "application/json",
120
+ "X-Encrypted": this.secretKey ? "true" : "false",
121
+ ...(this.channel ? { "X-Channel": this.channel } : {}),
122
+ ...this.headers,
123
+ },
124
+ body: JSON.stringify({ logs }),
125
+ });
126
+ } catch {
127
+ // Silently fail - don't break the app if Abbacchio server is down
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Close the client and flush any remaining logs
133
+ */
134
+ async close(): Promise<void> {
135
+ if (this.timer) {
136
+ clearTimeout(this.timer);
137
+ this.timer = null;
138
+ }
139
+ await this.flush();
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Create a new Abbacchio client instance
145
+ */
146
+ export function createClient(options?: AbbacchioClientOptions): AbbacchioClient {
147
+ return new AbbacchioClient(options);
148
+ }
package/src/encrypt.ts CHANGED
@@ -1,112 +1,112 @@
1
- import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync } from "crypto";
2
-
3
- /**
4
- * Generate a cryptographically secure random key for encryption.
5
- * Use this on the server/producer side to create a key for each channel.
6
- *
7
- * @param length - Length of the key in bytes (default: 32 for 256-bit key)
8
- * @returns A hex-encoded random key
9
- *
10
- * @example
11
- * ```typescript
12
- * import { generateKey } from "@abbacchio/client";
13
- *
14
- * // Generate a key for a channel
15
- * const key = generateKey();
16
- *
17
- * // Use in pino transport
18
- * const logger = pino({
19
- * transport: {
20
- * target: "@abbacchio/client/transports/pino",
21
- * options: {
22
- * url: "http://localhost:4000/api/logs",
23
- * channel: "my-app",
24
- * secretKey: key,
25
- * },
26
- * },
27
- * });
28
- *
29
- * // Share the dashboard URL with the key
30
- * console.log(`Dashboard: http://localhost:4000?channel=my-app&key=${key}`);
31
- * ```
32
- */
33
- export function generateKey(length: number = 32): string {
34
- return randomBytes(length).toString("hex");
35
- }
36
-
37
- const ALGORITHM = "aes-256-gcm";
38
- const IV_LENGTH = 16;
39
- const AUTH_TAG_LENGTH = 16;
40
- const SALT_LENGTH = 32;
41
- const PBKDF2_ITERATIONS = 100000;
42
-
43
- /**
44
- * Derive a key from a password using PBKDF2 (browser-compatible)
45
- */
46
- function deriveKey(password: string, salt: Buffer): Buffer {
47
- return pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, 32, "sha256");
48
- }
49
-
50
- /**
51
- * Encrypt data with AES-256-GCM
52
- * Returns base64 encoded string: salt:iv:authTag:ciphertext
53
- */
54
- export function encrypt(data: string, secretKey: string): string {
55
- const salt = randomBytes(SALT_LENGTH);
56
- const key = deriveKey(secretKey, salt);
57
- const iv = randomBytes(IV_LENGTH);
58
-
59
- const cipher = createCipheriv(ALGORITHM, key, iv);
60
- const encrypted = Buffer.concat([
61
- cipher.update(data, "utf8"),
62
- cipher.final(),
63
- ]);
64
- const authTag = cipher.getAuthTag();
65
-
66
- // Combine: salt + iv + authTag + ciphertext
67
- const combined = Buffer.concat([salt, iv, authTag, encrypted]);
68
- return combined.toString("base64");
69
- }
70
-
71
- /**
72
- * Decrypt data encrypted with encrypt()
73
- */
74
- export function decrypt(encryptedData: string, secretKey: string): string {
75
- const combined = Buffer.from(encryptedData, "base64");
76
-
77
- // Extract components
78
- const salt = combined.subarray(0, SALT_LENGTH);
79
- const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
80
- const authTag = combined.subarray(
81
- SALT_LENGTH + IV_LENGTH,
82
- SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH
83
- );
84
- const ciphertext = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
85
-
86
- const key = deriveKey(secretKey, salt);
87
- const decipher = createDecipheriv(ALGORITHM, key, iv);
88
- decipher.setAuthTag(authTag);
89
-
90
- const decrypted = Buffer.concat([
91
- decipher.update(ciphertext),
92
- decipher.final(),
93
- ]);
94
-
95
- return decrypted.toString("utf8");
96
- }
97
-
98
- /**
99
- * Encrypt a log object
100
- */
101
- export function encryptLog(log: unknown, secretKey: string): { encrypted: string } {
102
- const jsonStr = JSON.stringify(log);
103
- return { encrypted: encrypt(jsonStr, secretKey) };
104
- }
105
-
106
- /**
107
- * Decrypt an encrypted log
108
- */
109
- export function decryptLog<T = unknown>(encryptedLog: { encrypted: string }, secretKey: string): T {
110
- const jsonStr = decrypt(encryptedLog.encrypted, secretKey);
111
- return JSON.parse(jsonStr);
112
- }
1
+ import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync } from "crypto";
2
+
3
+ /**
4
+ * Generate a cryptographically secure random key for encryption.
5
+ * Use this on the server/producer side to create a key for each channel.
6
+ *
7
+ * @param length - Length of the key in bytes (default: 32 for 256-bit key)
8
+ * @returns A hex-encoded random key
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { generateKey } from "@abbacchio/client";
13
+ *
14
+ * // Generate a key for a channel
15
+ * const key = generateKey();
16
+ *
17
+ * // Use in pino transport
18
+ * const logger = pino({
19
+ * transport: {
20
+ * target: "@abbacchio/client/transports/pino",
21
+ * options: {
22
+ * url: "http://localhost:4000/api/logs",
23
+ * channel: "my-app",
24
+ * secretKey: key,
25
+ * },
26
+ * },
27
+ * });
28
+ *
29
+ * // Share the dashboard URL with the key
30
+ * console.log(`Dashboard: http://localhost:4000?channel=my-app&key=${key}`);
31
+ * ```
32
+ */
33
+ export function generateKey(length: number = 32): string {
34
+ return randomBytes(length).toString("hex");
35
+ }
36
+
37
+ const ALGORITHM = "aes-256-gcm";
38
+ const IV_LENGTH = 16;
39
+ const AUTH_TAG_LENGTH = 16;
40
+ const SALT_LENGTH = 32;
41
+ const PBKDF2_ITERATIONS = 100000;
42
+
43
+ /**
44
+ * Derive a key from a password using PBKDF2 (browser-compatible)
45
+ */
46
+ function deriveKey(password: string, salt: Buffer): Buffer {
47
+ return pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, 32, "sha256");
48
+ }
49
+
50
+ /**
51
+ * Encrypt data with AES-256-GCM
52
+ * Returns base64 encoded string: salt:iv:authTag:ciphertext
53
+ */
54
+ export function encrypt(data: string, secretKey: string): string {
55
+ const salt = randomBytes(SALT_LENGTH);
56
+ const key = deriveKey(secretKey, salt);
57
+ const iv = randomBytes(IV_LENGTH);
58
+
59
+ const cipher = createCipheriv(ALGORITHM, key, iv);
60
+ const encrypted = Buffer.concat([
61
+ cipher.update(data, "utf8"),
62
+ cipher.final(),
63
+ ]);
64
+ const authTag = cipher.getAuthTag();
65
+
66
+ // Combine: salt + iv + authTag + ciphertext
67
+ const combined = Buffer.concat([salt, iv, authTag, encrypted]);
68
+ return combined.toString("base64");
69
+ }
70
+
71
+ /**
72
+ * Decrypt data encrypted with encrypt()
73
+ */
74
+ export function decrypt(encryptedData: string, secretKey: string): string {
75
+ const combined = Buffer.from(encryptedData, "base64");
76
+
77
+ // Extract components
78
+ const salt = combined.subarray(0, SALT_LENGTH);
79
+ const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
80
+ const authTag = combined.subarray(
81
+ SALT_LENGTH + IV_LENGTH,
82
+ SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH
83
+ );
84
+ const ciphertext = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
85
+
86
+ const key = deriveKey(secretKey, salt);
87
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
88
+ decipher.setAuthTag(authTag);
89
+
90
+ const decrypted = Buffer.concat([
91
+ decipher.update(ciphertext),
92
+ decipher.final(),
93
+ ]);
94
+
95
+ return decrypted.toString("utf8");
96
+ }
97
+
98
+ /**
99
+ * Encrypt a log object
100
+ */
101
+ export function encryptLog(log: unknown, secretKey: string): { encrypted: string } {
102
+ const jsonStr = JSON.stringify(log);
103
+ return { encrypted: encrypt(jsonStr, secretKey) };
104
+ }
105
+
106
+ /**
107
+ * Decrypt an encrypted log
108
+ */
109
+ export function decryptLog<T = unknown>(encryptedLog: { encrypted: string }, secretKey: string): T {
110
+ const jsonStr = decrypt(encryptedLog.encrypted, secretKey);
111
+ return JSON.parse(jsonStr);
112
+ }
package/src/index.ts CHANGED
@@ -1,19 +1,19 @@
1
- // Core client
2
- export { AbbacchioClient, createClient } from "./client.js";
3
- export type { AbbacchioClientOptions } from "./client.js";
4
-
5
- // Encryption utilities
6
- export { generateKey, encrypt, decrypt, encryptLog, decryptLog } from "./encrypt.js";
7
-
8
- // Re-export transports for convenience
9
- export { default as pinoTransport } from "./transports/pino.js";
10
- export type { PinoTransportOptions } from "./transports/pino.js";
11
-
12
- export { winstonTransport, AbbacchioWinstonTransport } from "./transports/winston.js";
13
- export type { WinstonTransportOptions } from "./transports/winston.js";
14
-
15
- export { bunyanStream, AbbacchioBunyanStream } from "./transports/bunyan.js";
16
- export type { BunyanStreamOptions } from "./transports/bunyan.js";
17
-
18
- export { interceptConsole, restoreConsole, getActiveClient } from "./transports/console.js";
19
- export type { ConsoleInterceptorOptions } from "./transports/console.js";
1
+ // Core client
2
+ export { AbbacchioClient, createClient } from "./client.js";
3
+ export type { AbbacchioClientOptions } from "./client.js";
4
+
5
+ // Encryption utilities
6
+ export { generateKey, encrypt, decrypt, encryptLog, decryptLog } from "./encrypt.js";
7
+
8
+ // Re-export transports for convenience
9
+ export { default as pinoTransport } from "./transports/pino.js";
10
+ export type { PinoTransportOptions } from "./transports/pino.js";
11
+
12
+ export { winstonTransport, AbbacchioWinstonTransport } from "./transports/winston.js";
13
+ export type { WinstonTransportOptions } from "./transports/winston.js";
14
+
15
+ export { bunyanStream, AbbacchioBunyanStream } from "./transports/bunyan.js";
16
+ export type { BunyanStreamOptions } from "./transports/bunyan.js";
17
+
18
+ export { interceptConsole, restoreConsole, getActiveClient } from "./transports/console.js";
19
+ export type { ConsoleInterceptorOptions } from "./transports/console.js";