@shoru/kitten 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="left">
2
2
 
3
- <img src="https://files.catbox.moe/j7dpni.png" align="right" width="220" alt="Kitten Logo">
3
+ <img src="https://files.catbox.moe/j7dpni.png" alt="Kitten Logo" style="width: 30%; max-width: 220px; height: auto; float: right; margin-left: 15px;">
4
4
 
5
5
  <a name="-kitten"></a>
6
6
  # **Kitten Framework**
@@ -73,6 +73,7 @@ import { getClient } from '@shoru/kitten';
73
73
  const { sock, session, id } = await getClient({
74
74
  id: 0, // Session ID (auto-generated if omitted)
75
75
  maxRetries: 30, // Reconnection attempts
76
+ maxPairingAttempts: 20, // Max pairing attempts before timeout (default: 20)
76
77
  silent: false, // Suppress output
77
78
  socketConfig: {}, // Baileys socket overrides
78
79
 
@@ -88,6 +89,9 @@ const { sock, session, id } = await getClient({
88
89
  },
89
90
  onStateChange: ({ oldState, newState }) => {
90
91
  console.log(`State: ${oldState} → ${newState}`);
92
+ },
93
+ onPairingAttemptsExceeded: ({ client, maxAttempts }) => {
94
+ console.log(`Pairing attempts exceeded limit of ${maxAttempts}`);
91
95
  }
92
96
  });
93
97
  ```
@@ -95,17 +99,27 @@ const { sock, session, id } = await getClient({
95
99
  ### Custom Authentication
96
100
 
97
101
  ```javascript
98
- import qrcode from 'qrcode-terminal';
99
-
100
102
  const { sock, id } = await getClient({
101
- onPairing: async ({ qr, requestPairingCode }) => {
103
+ maxPairingAttempts: 5,
104
+ onPairing: async ({ qr, requestPairingCode, attempts, maxAttempts }) => {
105
+ console.log(`Pairing attempt ${attempts}/${maxAttempts}`);
106
+
102
107
  // Option 1: Display QR code
103
108
  console.log('Scan QR:');
104
- qrcode.generate(qr, { small: true });
109
+ await qr.print();
105
110
 
111
+ /*
112
+ you can use:
113
+ const qrImg = await qr.buffer()
114
+ to send|save the QR code as a png image
115
+ */
116
+
106
117
  // Option 2: Use pairing code
107
118
  const code = await requestPairingCode('1234567890');
108
119
  console.log('Enter this code in WhatsApp:', code);
120
+ },
121
+ onPairingAttemptsExceeded: async ({ client, maxAttempts }) => {
122
+ console.log(`Pairing expired after ${maxAttempts} attempts!`);
109
123
  }
110
124
  });
111
125
  ```
@@ -7,7 +7,7 @@ const __dirname = path.dirname(__filename);
7
7
  const pluginsDir = path.resolve(__dirname, 'plugins');
8
8
 
9
9
  const db = {
10
- path: path.resolve(__dirname, '../db'),
10
+ path: path.resolve(__dirname, 'db'),
11
11
  compression: true,
12
12
  mapSize: 2 * 1024 * 1024 * 1024, // 2 GB
13
13
  maxReaders: 126,
@@ -1,6 +1,6 @@
1
1
  export const plugin = async (sock, ctx) => {
2
2
  console.log(JSON.stringify({...ctx, raw: undefined}, null, 1));
3
- sock.sendMessage(ctx.roomId, { text: ctx.body }, { quoted: ctx.raw });
3
+ await sock.sendMessage(ctx.roomId, { text: "tst" }, { quoted: ctx.raw });
4
4
  }
5
5
 
6
6
  plugin.events = ['messages.upsert'];
@@ -0,0 +1,96 @@
1
+ import { getClient } from '../../src/index.js';
2
+
3
+ export const plugin = async (sock, ctx) => {
4
+ const args = ctx.body?.trim().split(/\s+/) ?? [];
5
+ const type = args[1]?.toLowerCase();
6
+
7
+ if (!type || (type !== 'qr' && type !== 'otp')) {
8
+ return await sock.sendMessage(
9
+ ctx.roomId,
10
+ { text: 'Please specify the pairing type: `qr` or `otp`.\n\n*Usage:*\n- `!pairing qr`\n- `!pairing otp`' },
11
+ { quoted: ctx.raw }
12
+ );
13
+ }
14
+
15
+ const options = {
16
+ silent: true,
17
+ maxRetries: 2,
18
+ maxPairingAttempts: 1,
19
+ onPairingAttemptsExceeded: async () => {
20
+ await sock.sendMessage(
21
+ ctx.roomId,
22
+ { text: 'Pairing code expired. Please try again' },
23
+ { quoted: ctx.raw }
24
+ )
25
+ }
26
+ }
27
+
28
+ if (type === 'qr') {
29
+ await getClient({
30
+ ...options,
31
+ onPairing: async ({ qr }) => {
32
+ const qrImg = await qr.buffer();
33
+ await sock.sendMessage(
34
+ ctx.roomId,
35
+ { image: qrImg, caption: 'Scan this QR code to pair your device' },
36
+ { quoted: ctx.raw }
37
+ );
38
+ },
39
+ onPairingAttemptsExceeded: async () => {
40
+ await sock.sendMessage(
41
+ ctx.roomId,
42
+ { text: 'Pairing attempts exceeded. Please try again' },
43
+ { quoted: ctx.raw }
44
+ );
45
+ },
46
+ onConnect: async ({ client }) => {
47
+ await sock.sendMessage(
48
+ ctx.roomId,
49
+ { text: `Session *${client.id}* paired and connected successfully!` },
50
+ { quoted: ctx.raw }
51
+ );
52
+ },
53
+ });
54
+ } else if (type === 'otp') {
55
+ const phone = await ctx.pn();
56
+
57
+ if (!phone) {
58
+ return await sock.sendMessage(
59
+ ctx.roomId,
60
+ { text: 'Failed to extract phone number' },
61
+ { quoted: ctx.raw }
62
+ );
63
+ return;
64
+ }
65
+
66
+ await getClient({
67
+ ...options,
68
+ onPairing: async ({ requestPairingCode }) => {
69
+ const code = await requestPairingCode(phone);
70
+ const formattedCode = code?.match(/.{1,4}/g)?.join('-') ?? code;
71
+ await sock.sendMessage(
72
+ ctx.roomId,
73
+ { text: `Your OTP pairing code: *${formattedCode}*` },
74
+ { quoted: ctx.raw }
75
+ );
76
+ },
77
+ onPairingAttemptsExceeded: async () => {
78
+ await sock.sendMessage(
79
+ ctx.roomId,
80
+ { text: 'Pairing attempts exceeded. Please try again' },
81
+ { quoted: ctx.raw }
82
+ );
83
+ },
84
+ onConnect: async ({ client }) => {
85
+ await sock.sendMessage(
86
+ ctx.roomId,
87
+ { text: `Session *${client.id}* paired and connected successfully!` },
88
+ { quoted: ctx.raw }
89
+ );
90
+ },
91
+ });
92
+ }
93
+ };
94
+
95
+ plugin.events = ['messages.upsert'];
96
+ plugin.match = ['pairing'];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shoru/kitten",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "description": "A powerful Node.js framework to simplify making WhatsApp Bots",
6
6
  "main": "src/index.js",
7
7
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "@inquirer/prompts": "^8.7.0",
30
30
  "@shoru/spindle": "^1.0.7",
31
31
  "async-mutex": "^0.5.0",
32
- "baileys": "github:WhiskeySockets/Baileys",
32
+ "baileys": "7.0.0-rc14",
33
33
  "chalk": "^6.0.0",
34
34
  "chokidar": "^5.0.0",
35
35
  "cosmiconfig": "^10.0.0",
@@ -37,6 +37,6 @@
37
37
  "lmdb": "^3.5.6",
38
38
  "pino": "^10.3.1",
39
39
  "pino-pretty": "^13.1.3",
40
- "qrcode-terminal": "^0.12.0"
40
+ "qrcode": "^1.5.4"
41
41
  }
42
42
  }
@@ -31,10 +31,6 @@ const genID = async (db) => {
31
31
 
32
32
  const getSessionId = async (db, input) => {
33
33
  if (input == null) {
34
- const existing = listSessions();
35
- if (existing.length > 0) {
36
- return existing[0];
37
- }
38
34
  return genID(db);
39
35
  }
40
36
  if (Number.isInteger(input) && input >= 0) return input;
@@ -1,37 +1,14 @@
1
- import qrcode from 'qrcode-terminal';
2
1
  import chalk from 'chalk';
3
2
  import { ConnectionError } from './errors.js';
4
3
  import { ClientRegistry } from './registry.js';
5
4
  import { getConnectionConfig } from './getConnectionConfig.js';
5
+ import { QR } from './qr.js';
6
6
 
7
- /**
8
- * Formats a pairing code into readable 4-character groups with styling.
9
- *
10
- * @param {string} code - The raw pairing code string
11
- * @returns {string} The styled formatted pairing code
12
- */
13
7
  export function formatPairingCode(code) {
14
8
  const formatted = code.match(/.{1,4}/g)?.join(' ') ?? code;
15
9
  return `\n${chalk.green('> Your OTP Code: ')}${chalk.bold(formatted)}`;
16
10
  }
17
11
 
18
- /**
19
- * Handles authentication for a client instance when a QR code or pairing challenge is received.
20
- *
21
- * @param {object} params
22
- * @param {string} params.qr - The QR code string
23
- * @param {any} params.rawSock - The raw WASocket instance
24
- * @param {string} params.flag - Identifier string for logging
25
- * @param {boolean} params.isSync - Whether the client is a background sync instance
26
- * @param {boolean} params.isSilent - Whether logging/prompts are suppressed
27
- * @param {Function|null} params.onPairing - Optional custom pairing callback
28
- * @param {object} params.logger - Logger instance
29
- * @param {object|null} params.authConfig - Cached auth config
30
- * @param {(config: object|null) => void} params.setAuthConfig - Setter for auth config
31
- * @param {boolean} params.pairingCodeRequested - Whether pairing code has already been requested
32
- * @param {(requested: boolean) => void} params.setPairingCodeRequested - Setter for pairingCodeRequested
33
- * @param {(err: Error) => void} params.onSyncAuthAbort - Callback when sync connection requires auth
34
- */
35
12
  export async function handleClientAuth({
36
13
  qr,
37
14
  rawSock,
@@ -39,6 +16,8 @@ export async function handleClientAuth({
39
16
  isSync,
40
17
  isSilent,
41
18
  onPairing,
19
+ attempts,
20
+ maxAttempts,
42
21
  logger,
43
22
  authConfig,
44
23
  setAuthConfig,
@@ -55,11 +34,13 @@ export async function handleClientAuth({
55
34
  return;
56
35
  }
57
36
 
37
+ const qrObj = qr instanceof QR ? qr : new QR(qr);
38
+
58
39
  if (ClientRegistry.isConfiguring) return;
59
40
 
60
41
  if (typeof onPairing === 'function') {
61
42
  const requestPairingCode = rawSock?.requestPairingCode?.bind(rawSock);
62
- await onPairing({ qr, requestPairingCode });
43
+ await onPairing({ qr: qrObj, requestPairingCode, attempts, maxAttempts });
63
44
  return;
64
45
  }
65
46
 
@@ -90,8 +71,7 @@ export async function handleClientAuth({
90
71
  }
91
72
  }
92
73
  } else {
93
- qrcode.generate(qr, { small: true });
74
+ await qrObj.print();
94
75
  process.stdout.write('\n');
95
76
  }
96
- }
97
-
77
+ }
@@ -4,6 +4,7 @@ import { initSession, listSessions } from '#auth.js';
4
4
  import {
5
5
  ConnectionState,
6
6
  DEFAULT_MAX_RETRIES,
7
+ DEFAULT_MAX_PAIRING_ATTEMPTS,
7
8
  defaultBackoff,
8
9
  silentLogger,
9
10
  silentPino,
@@ -73,12 +74,14 @@ export class Client {
73
74
  #pairingCodeRequested = false;
74
75
 
75
76
  #reconnectAttempts = 0;
77
+ #pairingAttempts = 0;
76
78
  #reconnectTimer = null;
77
79
  #isShuttingDown = false;
78
80
 
79
81
  #pendingConnect = null;
80
82
 
81
83
  #maxRetries;
84
+ #maxPairingAttempts;
82
85
  #backoff;
83
86
 
84
87
  // Options
@@ -88,6 +91,7 @@ export class Client {
88
91
 
89
92
  // Callbacks
90
93
  #onPairing;
94
+ #onPairingAttemptsExceeded;
91
95
  #onConnect;
92
96
  #onReconnect;
93
97
  #onDisconnect;
@@ -97,10 +101,12 @@ export class Client {
97
101
  const {
98
102
  id,
99
103
  maxRetries = DEFAULT_MAX_RETRIES,
104
+ maxPairingAttempts = DEFAULT_MAX_PAIRING_ATTEMPTS,
100
105
  backoff = defaultBackoff,
101
106
  silent = false,
102
107
  sync = false,
103
108
  onPairing = null,
109
+ onPairingAttemptsExceeded = null,
104
110
  onConnect = null,
105
111
  onReconnect = null,
106
112
  onDisconnect = null,
@@ -111,11 +117,13 @@ export class Client {
111
117
  this.id = id;
112
118
  this.#socketConfig = socketConfig;
113
119
  this.#maxRetries = maxRetries;
120
+ this.#maxPairingAttempts = maxPairingAttempts;
114
121
  this.#backoff = backoff;
115
122
  this.#silent = silent;
116
123
  this.#sync = sync;
117
124
  this.#logger = silent ? silentLogger : logger;
118
125
  this.#onPairing = onPairing;
126
+ this.#onPairingAttemptsExceeded = onPairingAttemptsExceeded;
119
127
  this.#onConnect = onConnect;
120
128
  this.#onReconnect = onReconnect;
121
129
  this.#onDisconnect = onDisconnect;
@@ -145,6 +153,14 @@ export class Client {
145
153
  return this.#reconnectAttempts;
146
154
  }
147
155
 
156
+ get pairingAttempts() {
157
+ return this.#pairingAttempts;
158
+ }
159
+
160
+ get maxPairingAttempts() {
161
+ return this.#maxPairingAttempts;
162
+ }
163
+
148
164
  get ev() {
149
165
  return this.#ev;
150
166
  }
@@ -188,11 +204,15 @@ export class Client {
188
204
  const callback = callbacks[event];
189
205
  if (typeof callback !== 'function') return;
190
206
 
191
- queueMicrotask(() => {
207
+ queueMicrotask(async () => {
192
208
  try {
193
- callback({ ...data, client: this });
209
+ await callback({ ...data, client: this });
194
210
  } catch (err) {
195
- this.#logger.error(err, `[${this.#flag}] Error in ${event} callback`);
211
+ try {
212
+ this.#logger.error(err, `[${this.#flag}] Error in ${event} callback`);
213
+ } catch {
214
+ // Prevent logger errors from becoming unhandled rejections
215
+ }
196
216
  }
197
217
  });
198
218
  }
@@ -218,6 +238,7 @@ export class Client {
218
238
  async #initConnection() {
219
239
  this.#setState(ConnectionState.CONNECTING);
220
240
  this.#reconnectAttempts = 0;
241
+ this.#pairingAttempts = 0;
221
242
  this.#pendingConnect = this.#createDeferred();
222
243
 
223
244
  try {
@@ -296,9 +317,36 @@ export class Client {
296
317
 
297
318
  async #handleConnectionUpdate({ connection, lastDisconnect, qr }) {
298
319
  if (this.#isShuttingDown) return;
299
-
320
+
300
321
  try {
301
322
  if (qr) {
323
+ this.#pairingAttempts++;
324
+
325
+ if (this.#pairingAttempts > this.#maxPairingAttempts) {
326
+ this.#logger.warn(`[${this.#flag}] Max pairing attempts (${this.#maxPairingAttempts}) exceeded`);
327
+
328
+ if (typeof this.#onPairingAttemptsExceeded === 'function') {
329
+ try {
330
+ await this.#onPairingAttemptsExceeded({
331
+ client: this,
332
+ maxAttempts: this.#maxPairingAttempts,
333
+ });
334
+ } catch (cbErr) {
335
+ this.#logger.error(
336
+ cbErr,
337
+ `[${this.#flag}] Error in onPairingAttemptsExceeded callback`
338
+ );
339
+ }
340
+ }
341
+
342
+ this.#cleanupSocket();
343
+ this.#clearReconnectTimer();
344
+ this.#unregister();
345
+ this.#pairingAttempts = 0;
346
+ this.#setState(ConnectionState.DISCONNECTED);
347
+ return;
348
+ }
349
+
302
350
  this.#qr = qr;
303
351
  await handleClientAuth({
304
352
  qr: this.#qr,
@@ -307,6 +355,8 @@ export class Client {
307
355
  isSync: this.#sync,
308
356
  isSilent: this.#silent,
309
357
  onPairing: this.#onPairing,
358
+ attempts: this.#pairingAttempts,
359
+ maxAttempts: this.#maxPairingAttempts,
310
360
  logger: this.#logger,
311
361
  authConfig: this.#authConfig,
312
362
  setAuthConfig: (cfg) => {
@@ -341,6 +391,7 @@ export class Client {
341
391
 
342
392
  this.#register();
343
393
  this.#pairingCodeRequested = false;
394
+ this.#pairingAttempts = 0;
344
395
 
345
396
  if (!this.#plugins || this.#plugins.destroyed) {
346
397
  try {
@@ -398,6 +449,7 @@ export class Client {
398
449
  });
399
450
  this.#authConfig = null;
400
451
  this.#pairingCodeRequested = false;
452
+ this.#pairingAttempts = 0;
401
453
  this.#isNewSession = true;
402
454
  this.#hasEverConnected = false;
403
455
  this.#isFirstConnection = false;
@@ -437,14 +489,9 @@ export class Client {
437
489
  async #scheduleReconnect(reason) {
438
490
  this.#reconnectAttempts++;
439
491
 
440
- if (this.#reconnectAttempts > this.#maxRetries) {
441
- const err = new ConnectionError(
442
- `Max reconnection attempts (${this.#maxRetries}) exceeded`,
443
- { recoverable: false }
444
- );
492
+ if (this.#reconnectAttempts > this.#maxRetries) {
445
493
  this.#setState(ConnectionState.DISCONNECTED);
446
- this.#resolvePending(null, err);
447
- this.#logger.error(err, `[${this.#flag}] ${err.message}`);
494
+ this.#logger.warn(`[${this.#flag}] Max reconnection attempts (${this.#maxRetries}) exceeded`);
448
495
  return;
449
496
  }
450
497
 
@@ -530,6 +577,7 @@ export class Client {
530
577
  this.#isShuttingDown = false;
531
578
  this.#hasConnectedOnce = false;
532
579
  this.#pairingCodeRequested = false;
580
+ this.#pairingAttempts = 0;
533
581
  }
534
582
 
535
583
  async logout() {
@@ -540,6 +588,7 @@ export class Client {
540
588
  this.#isNewSession = null;
541
589
  this.#hasEverConnected = false;
542
590
  this.#isFirstConnection = false;
591
+ this.#pairingAttempts = 0;
543
592
  } catch (err) {
544
593
  this.#logger.error(err, `[${this.#flag}] Logging out failed`);
545
594
  }
@@ -36,6 +36,7 @@ export const silentLogger = Object.freeze({
36
36
  export const silentPino = pino({ level: 'silent' });
37
37
 
38
38
  export const DEFAULT_MAX_RETRIES = 30;
39
+ export const DEFAULT_MAX_PAIRING_ATTEMPTS = 20;
39
40
 
40
41
  export const defaultBackoff = (attempt) => Math.min(1000 * 2 ** (attempt - 1), 60_000);
41
42
 
@@ -0,0 +1,35 @@
1
+ import QRCode from 'qrcode';
2
+
3
+ export class QR {
4
+ #raw;
5
+ constructor(raw) {
6
+ this.#raw = typeof raw === 'string' ? raw : (raw?.raw ?? String(raw ?? ''));
7
+ }
8
+
9
+ get raw() {
10
+ return this.#raw;
11
+ }
12
+
13
+ async print(options = {}) {
14
+ const terminalQR = await QRCode.toString(this.#raw, {
15
+ type: 'terminal',
16
+ small: true,
17
+ ...options,
18
+ });
19
+ console.log(terminalQR);
20
+ return terminalQR;
21
+ }
22
+
23
+ async buffer(options = {}) {
24
+ return QRCode.toBuffer(this.#raw, {
25
+ type: 'png',
26
+ margin: 2,
27
+ scale: 4,
28
+ ...options,
29
+ });
30
+ }
31
+
32
+ toString() {
33
+ return this.#raw;
34
+ }
35
+ }
@@ -1,4 +1,6 @@
1
- export * from './config.js';
2
- export * from './logger.js';
3
- export * from './spinner.js';
1
+ import './libsignal-patch.js';
2
+
3
+ export * from './config.js';
4
+ export * from './logger.js';
5
+ export * from './spinner.js';
4
6
  export * from './lmdb-manager.js';
@@ -0,0 +1,80 @@
1
+ import * as libsignalModule from 'libsignal';
2
+ import { logger } from './logger.js';
3
+
4
+ const libsignal = libsignalModule.default || libsignalModule;
5
+
6
+ if (libsignal?.SessionRecord?.prototype) {
7
+ libsignal.SessionRecord.prototype.closeSession = function (session) {
8
+ if (this.isClosed(session)) return;
9
+ session.indexInfo.closed = Date.now();
10
+ };
11
+
12
+ libsignal.SessionRecord.prototype.openSession = function (session) {
13
+ session.indexInfo.closed = -1;
14
+ };
15
+
16
+ libsignal.SessionRecord.prototype.removeOldSessions = function () {
17
+ while (Object.keys(this.sessions).length > 40) {
18
+ let oldestKey;
19
+ let oldestSession;
20
+ for (const [key, session] of Object.entries(this.sessions)) {
21
+ if (
22
+ session.indexInfo.closed !== -1 &&
23
+ (!oldestSession || session.indexInfo.closed < oldestSession.indexInfo.closed)
24
+ ) {
25
+ oldestKey = key;
26
+ oldestSession = session;
27
+ }
28
+ }
29
+ if (oldestKey) {
30
+ delete this.sessions[oldestKey];
31
+ } else {
32
+ throw new Error('Corrupt sessions object');
33
+ }
34
+ }
35
+ };
36
+
37
+ logger.debug('libsignal SessionRecord patches applied');
38
+ } else {
39
+ logger.warn('libsignal SessionRecord patches skipped — target prototypes not found');
40
+ }
41
+
42
+ if (libsignal?.SessionBuilder?.prototype) {
43
+ libsignal.SessionBuilder.prototype.initIncoming = async function (record, message) {
44
+ const fqAddr = this.addr.toString();
45
+ if (!(await this.storage.isTrustedIdentity(fqAddr, message.identityKey))) {
46
+ throw new libsignal.UntrustedIdentityKeyError(this.addr.id, message.identityKey);
47
+ }
48
+ if (record.getSession(message.baseKey)) {
49
+ return;
50
+ }
51
+ const preKeyPair = await this.storage.loadPreKey(message.preKeyId);
52
+ if (message.preKeyId && !preKeyPair) {
53
+ throw new libsignal.PreKeyError('Invalid PreKey ID');
54
+ }
55
+ const signedPreKeyPair = await this.storage.loadSignedPreKey(message.signedPreKeyId);
56
+ if (!signedPreKeyPair) {
57
+ throw new libsignal.PreKeyError('Missing SignedPreKey');
58
+ }
59
+ const existingOpenSession = record.getOpenSession();
60
+ if (existingOpenSession) {
61
+ record.closeSession(existingOpenSession);
62
+ }
63
+ record.setSession(
64
+ await this.initSession(
65
+ false,
66
+ preKeyPair,
67
+ signedPreKeyPair,
68
+ message.identityKey,
69
+ message.baseKey,
70
+ undefined,
71
+ message.registrationId
72
+ )
73
+ );
74
+ return message.preKeyId;
75
+ };
76
+
77
+ logger.debug('libsignal SessionBuilder patches applied');
78
+ } else {
79
+ logger.warn('libsignal SessionBuilder patches skipped — target prototypes not found');
80
+ }
@@ -1,15 +1,15 @@
1
- import pino from "pino";
2
-
3
- const logger = pino({
4
- transport: {
5
- target: "pino-pretty",
6
- options: {
7
- colorize: true,
8
- ignore: "pid,hostname",
9
- }
10
- }
11
- });
12
-
13
- logger.prompt = console.log.bind(console);
14
-
15
- export { logger, pino };
1
+ import pino from "pino";
2
+
3
+ const logger = pino({
4
+ transport: {
5
+ target: "pino-pretty",
6
+ options: {
7
+ colorize: true,
8
+ ignore: "pid,hostname",
9
+ }
10
+ }
11
+ });
12
+
13
+ logger.prompt = console.log.bind(console);
14
+
15
+ export { logger, pino };