@volcanicminds/tools 0.0.3 → 0.0.4

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
@@ -19,46 +19,120 @@ npm install @volcanicminds/tools
19
19
  npm run upgrade-deps
20
20
  ```
21
21
 
22
- ## Environment
23
-
24
- ```bash
25
- # or automatically use LOG_LEVEL
26
- SOME_KEY=true
27
- ```
28
-
29
22
  ## Usage
30
23
 
31
- This package supports both root imports and sub-path imports to optimize bundle size.
32
-
33
- ### Import everything
34
-
35
- ```typescript
36
- import { mfa, log } from '@volcanicminds/tools'
37
- ```
24
+ This package supports both root imports and sub-path imports to optimize bundle size and tree-shaking.
38
25
 
39
- ### Import specific features (Recommended for smaller bundles)
26
+ ### Import specific features (Recommended)
40
27
 
41
28
  ```typescript
42
29
  import * as mfa from '@volcanicminds/tools/mfa'
30
+ import { Mailer } from '@volcanicminds/tools/mailer'
43
31
  import * as logger from '@volcanicminds/tools/logger'
44
32
  ```
45
33
 
34
+ ---
35
+
46
36
  ## Features
47
37
 
48
38
  ### MFA (Multi-Factor Authentication)
49
39
 
50
- Utilities for generating secrets, QR codes, and verifying TOTP tokens.
40
+ Utilities for generating secrets, QR codes, and verifying TOTP tokens based on `otpauth`.
51
41
 
52
42
  ```typescript
53
43
  import * as mfa from '@volcanicminds/tools/mfa'
54
44
 
55
- // Generate Setup
56
- const { secret, uri, qrCode } = await mfa.generateSetupDetails('MyApp', 'user@example.com')
45
+ // 1. Generate a generic base32 secret (Optional, useful if you need to store it before setup)
46
+ const secret = mfa.generateSecret()
47
+
48
+ // 2. Generate Setup Details for the User (returns secret, otpauth URI, and QR Code Data URL)
49
+ // If secret is omitted, a new one is generated automatically.
50
+ const setup = await mfa.generateSetupDetails('MyApp', 'user@example.com', secret)
51
+
52
+ console.log(setup.secret) // Save this to DB
53
+ console.log(setup.qrCode) // Send this to Frontend to display QR
54
+
55
+ // 3. Verify a Token provided by the user
56
+ const userToken = '123456' // From input
57
+ const isValid = mfa.verifyToken(userToken, setup.secret)
57
58
 
58
- // Verify Token
59
- const isValid = mfa.verifyToken('123456', secret)
59
+ if (isValid) {
60
+ // Proceed with login/action
61
+ }
62
+
63
+ // 4. Generate a valid token (Useful for testing or recovery codes)
64
+ const currentToken = mfa.generateToken(setup.secret)
60
65
  ```
61
66
 
62
- ## Logging
67
+ ---
68
+
69
+ ### Mailer
70
+
71
+ A wrapper around `nodemailer` designed for simplicity and configuration injection. It automatically handles HTML-to-Text conversion if the text body is missing.
63
72
 
64
- Use Pino logger if in your project you have a `global.log` with a valid instance.
73
+ #### Configuration & Initialization
74
+
75
+ ```typescript
76
+ import { Mailer } from '@volcanicminds/tools/mailer'
77
+
78
+ // Initialize with a config object (not bound to process.env)
79
+ const mailer = new Mailer({
80
+ host: 'smtp.example.com',
81
+ port: 587,
82
+ secure: false, // true for 465, false for other ports
83
+ auth: {
84
+ user: 'my-user',
85
+ pass: 'my-password'
86
+ },
87
+ defaultFrom: '"My Service" <noreply@example.com>', // Optional: used if not specified in send()
88
+ defaultReplyTo: 'support@example.com' // Optional
89
+ })
90
+
91
+ // Optional: Verify connection on startup
92
+ const isConnected = await mailer.verifyConnection()
93
+ if (isConnected) console.log('SMTP Ready')
94
+ ```
95
+
96
+ #### Sending Emails
97
+
98
+ ```typescript
99
+ try {
100
+ const info = await mailer.send({
101
+ // Optional if defaultFrom is set in config, otherwise Mandatory
102
+ from: '"Support Team" <support@example.com>',
103
+
104
+ to: 'user@destination.com', // Can be a string or array of strings
105
+ cc: ['manager@destination.com'],
106
+ subject: 'Welcome to Volcanic Tools',
107
+
108
+ // Text version is automatically generated from HTML if omitted,
109
+ // converting <br> to newlines and stripping tags.
110
+ text: 'Hello, World! Welcome aboard.',
111
+ html: '<p>Hello, <strong>World</strong>!<br/>Welcome aboard.</p>',
112
+
113
+ attachments: [
114
+ {
115
+ filename: 'license.txt',
116
+ content: 'MIT License...'
117
+ }
118
+ ]
119
+ })
120
+
121
+ console.log('Message sent: %s', info.messageId)
122
+ } catch (error) {
123
+ console.error('Error sending email:', error)
124
+ }
125
+ ```
126
+
127
+ ---
128
+
129
+ ### Logging
130
+
131
+ Use Pino logger wrapper if in your project you have a `global.log` with a valid instance.
132
+
133
+ ```typescript
134
+ import * as log from '@volcanicminds/tools/logger'
135
+
136
+ log.info('Application started')
137
+ log.error({ err: new Error('Oops') }, 'Something went wrong')
138
+ ```
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * as mfa from './lib/mfa/index.js';
2
+ export * as mailer from './lib/mailer/index.js';
2
3
  export * as log from './lib/util/logger.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,oBAAoB,CAAA;AACzC,OAAO,KAAK,GAAG,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,oBAAoB,CAAA;AACzC,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAA;AAC/C,OAAO,KAAK,GAAG,MAAM,sBAAsB,CAAA"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * as mfa from './lib/mfa/index.js';
2
+ export * as mailer from './lib/mailer/index.js';
2
3
  export * as log from './lib/util/logger.js';
3
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,oBAAoB,CAAA;AACzC,OAAO,KAAK,GAAG,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,oBAAoB,CAAA;AACzC,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAA;AAC/C,OAAO,KAAK,GAAG,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,43 @@
1
+ export interface MailerConfig {
2
+ host: string;
3
+ port: number;
4
+ secure?: boolean;
5
+ auth?: {
6
+ user: string;
7
+ pass: string;
8
+ };
9
+ defaultFrom?: string;
10
+ defaultReplyTo?: string;
11
+ tls?: {
12
+ rejectUnauthorized?: boolean;
13
+ ciphers?: string;
14
+ };
15
+ }
16
+ export interface MailAttachment {
17
+ filename: string;
18
+ content?: string | Buffer;
19
+ path?: string;
20
+ contentType?: string;
21
+ cid?: string;
22
+ }
23
+ export interface MailOptions {
24
+ to: string | string[];
25
+ subject: string;
26
+ html: string;
27
+ text?: string;
28
+ from?: string;
29
+ cc?: string | string[];
30
+ bcc?: string | string[];
31
+ replyTo?: string;
32
+ attachments?: MailAttachment[];
33
+ }
34
+ export declare class Mailer {
35
+ private transporter;
36
+ private defaultFrom?;
37
+ private defaultReplyTo?;
38
+ constructor(config: MailerConfig);
39
+ private stripHtml;
40
+ send(options: MailOptions): Promise<any>;
41
+ verifyConnection(): Promise<boolean>;
42
+ }
43
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/mailer/index.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;IACD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,GAAG,CAAC,EAAE;QACJ,kBAAkB,CAAC,EAAE,OAAO,CAAA;QAC5B,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB,CAAA;CACF;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACtB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAA;CAC/B;AAMD,qBAAa,MAAM;IACjB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,WAAW,CAAC,CAAQ;IAC5B,OAAO,CAAC,cAAc,CAAC,CAAQ;gBAEnB,MAAM,EAAE,YAAY;IAqBhC,OAAO,CAAC,SAAS;IAcJ,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;IAwCxC,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;CASlD"}
@@ -0,0 +1,73 @@
1
+ import nodemailer from 'nodemailer';
2
+ export class Mailer {
3
+ transporter;
4
+ defaultFrom;
5
+ defaultReplyTo;
6
+ constructor(config) {
7
+ this.defaultFrom = config.defaultFrom;
8
+ this.defaultReplyTo = config.defaultReplyTo;
9
+ this.transporter = nodemailer.createTransport({
10
+ host: config.host,
11
+ port: config.port,
12
+ secure: config.secure ?? config.port === 465,
13
+ auth: config.auth
14
+ ? {
15
+ user: config.auth.user,
16
+ pass: config.auth.pass
17
+ }
18
+ : undefined,
19
+ tls: config.tls
20
+ });
21
+ }
22
+ stripHtml(html) {
23
+ if (!html)
24
+ return '';
25
+ return html
26
+ .replace(/<br\s*\/?>/gi, '\n')
27
+ .replace(/<\/p>/gi, '\n\n')
28
+ .replace(/<[^>]*>?/gm, '')
29
+ .trim();
30
+ }
31
+ async send(options) {
32
+ const from = options.from || this.defaultFrom;
33
+ const replyTo = options.replyTo || this.defaultReplyTo;
34
+ if (!from) {
35
+ throw new Error('Email "from" address is required (either in options or config.defaultFrom)');
36
+ }
37
+ const mailPayload = {
38
+ from,
39
+ to: Array.isArray(options.to) ? options.to.join(', ') : options.to,
40
+ subject: options.subject,
41
+ html: options.html,
42
+ text: options.text || this.stripHtml(options.html),
43
+ attachments: options.attachments
44
+ };
45
+ if (options.cc) {
46
+ mailPayload.cc = Array.isArray(options.cc) ? options.cc.join(', ') : options.cc;
47
+ }
48
+ if (options.bcc) {
49
+ mailPayload.bcc = Array.isArray(options.bcc) ? options.bcc.join(', ') : options.bcc;
50
+ }
51
+ if (replyTo) {
52
+ mailPayload.replyTo = replyTo;
53
+ }
54
+ try {
55
+ const info = await this.transporter.sendMail(mailPayload);
56
+ return info;
57
+ }
58
+ catch (error) {
59
+ throw new Error(`Failed to send email to ${mailPayload.to}: ${error.message}`);
60
+ }
61
+ }
62
+ async verifyConnection() {
63
+ try {
64
+ await this.transporter.verify();
65
+ return true;
66
+ }
67
+ catch (error) {
68
+ console.error('Mailer connection verification failed:', error);
69
+ return false;
70
+ }
71
+ }
72
+ }
73
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/mailer/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ import nodemailer, { Transporter, SendMailOptions } from 'nodemailer'
2
+
3
+ export interface MailerConfig {
4
+ host: string
5
+ port: number
6
+ secure?: boolean // true for 465, false for other ports
7
+ auth?: {
8
+ user: string
9
+ pass: string
10
+ }
11
+ defaultFrom?: string
12
+ defaultReplyTo?: string
13
+ tls?: {
14
+ rejectUnauthorized?: boolean
15
+ ciphers?: string
16
+ }
17
+ }
18
+
19
+ export interface MailAttachment {
20
+ filename: string
21
+ content?: string | Buffer
22
+ path?: string
23
+ contentType?: string
24
+ cid?: string
25
+ }
26
+
27
+ export interface MailOptions {
28
+ to: string | string[]
29
+ subject: string
30
+ html: string
31
+ text?: string // If not provided, it will be generated from html
32
+ from?: string
33
+ cc?: string | string[]
34
+ bcc?: string | string[]
35
+ replyTo?: string
36
+ attachments?: MailAttachment[]
37
+ }
38
+
39
+ /**
40
+ * Mailer class wrapper around nodemailer.
41
+ * Designed to be tree-shakeable and configuration-driven without relying on process.env directly.
42
+ */
43
+ export class Mailer {
44
+ private transporter: Transporter
45
+ private defaultFrom?: string
46
+ private defaultReplyTo?: string
47
+
48
+ constructor(config: MailerConfig) {
49
+ this.defaultFrom = config.defaultFrom
50
+ this.defaultReplyTo = config.defaultReplyTo
51
+
52
+ this.transporter = nodemailer.createTransport({
53
+ host: config.host,
54
+ port: config.port,
55
+ secure: config.secure ?? config.port === 465, // Default secure if port is 465
56
+ auth: config.auth
57
+ ? {
58
+ user: config.auth.user,
59
+ pass: config.auth.pass
60
+ }
61
+ : undefined,
62
+ tls: config.tls
63
+ })
64
+ }
65
+
66
+ /**
67
+ * Helper to strip HTML tags for plain text fallback
68
+ */
69
+ private stripHtml(html: string): string {
70
+ if (!html) return ''
71
+ return html
72
+ .replace(/<br\s*\/?>/gi, '\n') // Replace <br> with newlines
73
+ .replace(/<\/p>/gi, '\n\n') // Replace </p> with double newlines
74
+ .replace(/<[^>]*>?/gm, '') // Remove all other tags
75
+ .trim()
76
+ }
77
+
78
+ /**
79
+ * Sends an email.
80
+ * @param options Mail options including to, subject, html, etc.
81
+ * @returns The info object from nodemailer.
82
+ */
83
+ public async send(options: MailOptions): Promise<any> {
84
+ const from = options.from || this.defaultFrom
85
+ const replyTo = options.replyTo || this.defaultReplyTo
86
+
87
+ if (!from) {
88
+ throw new Error('Email "from" address is required (either in options or config.defaultFrom)')
89
+ }
90
+
91
+ const mailPayload: SendMailOptions = {
92
+ from,
93
+ to: Array.isArray(options.to) ? options.to.join(', ') : options.to,
94
+ subject: options.subject,
95
+ html: options.html,
96
+ text: options.text || this.stripHtml(options.html),
97
+ attachments: options.attachments
98
+ }
99
+
100
+ if (options.cc) {
101
+ mailPayload.cc = Array.isArray(options.cc) ? options.cc.join(', ') : options.cc
102
+ }
103
+
104
+ if (options.bcc) {
105
+ mailPayload.bcc = Array.isArray(options.bcc) ? options.bcc.join(', ') : options.bcc
106
+ }
107
+
108
+ if (replyTo) {
109
+ mailPayload.replyTo = replyTo
110
+ }
111
+
112
+ try {
113
+ const info = await this.transporter.sendMail(mailPayload)
114
+ return info
115
+ } catch (error: any) {
116
+ throw new Error(`Failed to send email to ${mailPayload.to}: ${error.message}`)
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Verifies the connection configuration.
122
+ */
123
+ public async verifyConnection(): Promise<boolean> {
124
+ try {
125
+ await this.transporter.verify()
126
+ return true
127
+ } catch (error) {
128
+ console.error('Mailer connection verification failed:', error)
129
+ return false
130
+ }
131
+ }
132
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volcanicminds/tools",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Tools for the volcanic (minds) backend",
5
5
  "keywords": [
6
6
  "volcanic",
@@ -10,7 +10,9 @@
10
10
  "esm",
11
11
  "mfa",
12
12
  "otp",
13
- "totp"
13
+ "totp",
14
+ "mailer",
15
+ "nodemailer"
14
16
  ],
15
17
  "homepage": "https://volcanicminds.com",
16
18
  "bugs": {
@@ -41,6 +43,11 @@
41
43
  "import": "./dist/lib/mfa/index.js",
42
44
  "require": "./dist/lib/mfa/index.js"
43
45
  },
46
+ "./mailer": {
47
+ "types": "./dist/lib/mailer/index.d.ts",
48
+ "import": "./dist/lib/mailer/index.js",
49
+ "require": "./dist/lib/mailer/index.js"
50
+ },
44
51
  "./logger": {
45
52
  "types": "./dist/lib/util/logger.d.ts",
46
53
  "import": "./dist/lib/util/logger.js",
@@ -69,12 +76,14 @@
69
76
  "combine": "node combine.js"
70
77
  },
71
78
  "dependencies": {
79
+ "nodemailer": "^6.10.1",
72
80
  "otpauth": "^9.3.5",
73
81
  "qrcode": "^1.5.4"
74
82
  },
75
83
  "devDependencies": {
76
84
  "@eslint/js": "^9.39.1",
77
85
  "@types/node": "^24.10.1",
86
+ "@types/nodemailer": "^6.4.14",
78
87
  "@types/qrcode": "^1.5.5",
79
88
  "eslint": "^9.39.1",
80
89
  "globals": "^16.5.0",
@@ -95,6 +104,9 @@
95
104
  "mfa": [
96
105
  "dist/lib/mfa/index.d.ts"
97
106
  ],
107
+ "mailer": [
108
+ "dist/lib/mailer/index.d.ts"
109
+ ],
98
110
  "logger": [
99
111
  "dist/lib/util/logger.d.ts"
100
112
  ]