@venizia/ignis 0.3.8 → 0.4.1
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/LICENSE.md +1 -1
- package/dist/base/applications/base.js +1 -1
- package/dist/base/applications/base.js.map +1 -1
- package/dist/base/applications/server.js +4 -4
- package/dist/base/applications/server.js.map +1 -1
- package/dist/base/middlewares/app-error/app-error.middleware.js +1 -1
- package/dist/base/middlewares/app-error/app-error.middleware.js.map +1 -1
- package/dist/components/auth/authenticate/controllers/factory.js +1 -1
- package/dist/components/auth/authenticate/controllers/factory.js.map +1 -1
- package/dist/components/auth/authenticate/controllers/jwks/controller.js.map +1 -1
- package/dist/components/auth/authenticate/controllers/service-certs/controller.js.map +1 -1
- package/dist/components/auth/authenticate/services/bearer/jwks/abstract.service.js +1 -1
- package/dist/components/auth/authenticate/services/bearer/jwks/abstract.service.js.map +1 -1
- package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.js +1 -1
- package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.js.map +1 -1
- package/dist/components/auth/authorize/adapters/custom-grant-expander.js +3 -3
- package/dist/components/auth/authorize/adapters/custom-grant-expander.js.map +1 -1
- package/dist/components/auth/authorize/adapters/scoped-casbin/adapter.d.ts.map +1 -1
- package/dist/components/auth/authorize/adapters/scoped-casbin/adapter.js +3 -3
- package/dist/components/auth/authorize/adapters/scoped-casbin/adapter.js.map +1 -1
- package/dist/components/auth/authorize/enforcers/casbin.enforcer.js +2 -2
- package/dist/components/auth/authorize/enforcers/casbin.enforcer.js.map +1 -1
- package/dist/components/auth/authorize/enforcers/user-policy-line-cache.js +1 -1
- package/dist/components/auth/authorize/enforcers/user-policy-line-cache.js.map +1 -1
- package/dist/components/health-check/controller.js +2 -2
- package/dist/components/health-check/controller.js.map +1 -1
- package/dist/components/mail/helpers/executors/bull-mq-executor.helper.js +2 -2
- package/dist/components/mail/helpers/executors/bull-mq-executor.helper.js.map +1 -1
- package/dist/components/mail/helpers/executors/internal-queue-executor.helper.js +2 -2
- package/dist/components/mail/helpers/executors/internal-queue-executor.helper.js.map +1 -1
- package/dist/components/mail/helpers/transporters/mailgun-transporter.helper.js +1 -1
- package/dist/components/mail/helpers/transporters/mailgun-transporter.helper.js.map +1 -1
- package/dist/components/mail/services/mail.service.js +2 -2
- package/dist/components/mail/services/mail.service.js.map +1 -1
- package/dist/components/mail/utilities/attachment.utility.js +2 -2
- package/dist/components/mail/utilities/attachment.utility.js.map +1 -1
- package/dist/components/mail/utilities/mime.utility.js +3 -3
- package/dist/components/mail/utilities/mime.utility.js.map +1 -1
- package/dist/components/static-asset/component.js +1 -1
- package/dist/components/static-asset/component.js.map +1 -1
- package/dist/components/static-asset/controller/factory.d.ts.map +1 -1
- package/dist/components/static-asset/controller/factory.js +9 -9
- package/dist/components/static-asset/controller/factory.js.map +1 -1
- package/dist/components/static-asset/controller/key-scope.js +1 -1
- package/dist/components/static-asset/controller/key-scope.js.map +1 -1
- package/dist/components/static-asset/controller/meta-link.js +2 -2
- package/dist/components/static-asset/controller/meta-link.js.map +1 -1
- package/dist/components/static-asset/models/base.model.js +1 -1
- package/dist/components/static-asset/models/base.model.js.map +1 -1
- package/dist/presets/app/files/.configs/formatters/.ignore +2 -2
- package/dist/presets/app/files/src/smoke.test.ts +1 -1
- package/dist/presets/app/files/tsconfig.json +2 -8
- package/dist/presets/app/preset.json +4 -5
- package/dist/presets/lib/files/.configs/formatters/.ignore +2 -2
- package/dist/presets/lib/files/tsconfig.json +2 -7
- package/dist/presets/lib/preset.json +3 -4
- package/package.json +51 -52
- package/dist/presets/app/files/.configs/formatters/oxfmt.config.mts +0 -4
- package/dist/presets/lib/files/.configs/formatters/oxfmt.config.mts +0 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bull-mq-executor.helper.js","sourceRoot":"","sources":["../../../../../src/components/mail/helpers/executors/bull-mq-executor.helper.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseHelper, getError } from '@venizia/ignis-helpers/core';\nimport { RedisSingleHelper } from '@venizia/ignis-helpers';\nimport type { TConstValue } from '@/helpers';\nimport { BullMQHelper } from '@venizia/ignis-helpers/bullmq';\nimport type {\n IBullMQMailExecutorOpts,\n IMailProcessorResult,\n IMailQueueExecutor,\n IMailQueueOptions,\n IMailQueueResult,\n IQueueJobPayload,\n} from '../../common';\nimport { BullMQExecutorModes, MailErrorCodes, MailExecutorErrors } from '../../common';\nimport type { Job } from 'bullmq';\n\nexport class BullMQMailExecutorHelper extends BaseHelper implements IMailQueueExecutor {\n private queueIdentifier: string;\n private queueName: string;\n private mode: TConstValue<typeof BullMQExecutorModes>;\n\n private queueHelper?: BullMQHelper<IQueueJobPayload, IMailQueueResult>;\n private workerHelpers: BullMQHelper<IQueueJobPayload, IMailProcessorResult>[] = [];\n private redisConnection: RedisSingleHelper;\n private readonly bullmqModule?: typeof import('bullmq');\n private jobIdCounter = 0;\n\n private processor?: (email: string) => Promise<IMailProcessorResult>;\n\n constructor(opts: IBullMQMailExecutorOpts) {\n super({ scope: BullMQMailExecutorHelper.name });\n\n this.mode = opts.mode;\n\n this.redisConnection = new RedisSingleHelper({\n ...opts.redis,\n maxRetry: undefined,\n });\n\n this.bullmqModule = opts.module;\n this.queueIdentifier = opts.queue.identifier;\n this.queueName = opts.queue.name;\n\n this.initQueue(opts.queue);\n }\n\n initQueue(queue: { identifier: string; name: string }) {\n switch (this.mode) {\n case BullMQExecutorModes.QUEUE_ONLY:\n case BullMQExecutorModes.BOTH: {\n this.queueHelper = new BullMQHelper<IQueueJobPayload, IMailQueueResult>({\n identifier: queue.identifier,\n queueName: queue.name,\n redisConnection: this.redisConnection,\n role: 'queue',\n module: this.bullmqModule,\n });\n\n this.logger\n .for(this.constructor.name)\n .info(\n 'BullMQ queue executor initialized (queue enabled) | Identifier: %s | Mode: %s',\n this.queueIdentifier,\n this.mode,\n );\n break;\n }\n default: {\n this.logger\n .for(this.constructor.name)\n .info(\n 'BullMQ queue executor initialized (worker-only mode) | Identifier: %s | Mode: %s',\n this.queueIdentifier,\n this.mode,\n );\n\n break;\n }\n }\n }\n\n async setProcessor(\n processor: (email: string) => Promise<IMailProcessorResult>,\n opts?: {\n numberOfWorkers?: number;\n concurrencyPerWorker?: number;\n lockDuration?: number;\n },\n ): Promise<void> {\n this.processor = processor;\n\n if (this.mode === BullMQExecutorModes.QUEUE_ONLY) {\n this.logger\n .for(this.setProcessor.name)\n .warn('Skipping worker creation in queue-only mode. Workers will not be started.');\n return;\n }\n\n const numberOfWorkers = opts?.numberOfWorkers ?? 1;\n const concurrencyPerWorker = opts?.concurrencyPerWorker ?? 5;\n const lockDuration = opts?.lockDuration ?? 30000;\n\n await this.clearWorkers();\n\n for (let i = 0; i < numberOfWorkers; i++) {\n this.addWorker({\n workerIdentifier: `${this.queueIdentifier}-worker-${i}`,\n concurrency: concurrencyPerWorker,\n lockDuration,\n });\n }\n\n this.logger\n .for(this.setProcessor.name)\n .info(\n 'Processor registered | Mode: %s | Workers: %d | Concurrency per worker: %d',\n this.mode,\n numberOfWorkers,\n concurrencyPerWorker,\n );\n }\n\n addWorker(opts: { workerIdentifier: string; concurrency?: number; lockDuration?: number }): void {\n if (!this.processor) {\n throw getError({ message: MailExecutorErrors.PROCESSOR_NOT_SET });\n }\n\n const workerIdentifier = opts.workerIdentifier;\n const concurrency = opts?.concurrency ?? 5;\n const lockDuration = opts?.lockDuration ?? 30000;\n\n const workerHelper = new BullMQHelper<IQueueJobPayload, IMailProcessorResult>({\n identifier: workerIdentifier,\n queueName: this.queueName,\n redisConnection: this.redisConnection,\n role: 'worker',\n module: this.bullmqModule,\n numberOfWorker: concurrency,\n lockDuration,\n onWorkerData: async (job: Job<IQueueJobPayload, IMailProcessorResult>) => {\n const maxAttempts = job.data.options?.attempts ?? 3;\n\n this.logger\n .for('onWorkerData')\n .info(\n 'Processing job | Worker: %s | jobId: %s | email: %s | attempt: %d/%d',\n workerIdentifier,\n job.data.id,\n job.data.email,\n job.attemptsMade + 1,\n maxAttempts,\n );\n\n if (!this.processor) {\n // Returning undefined would let BullMQ mark the job COMPLETED and (with removeOnComplete) delete it - mail silently dropped.\n throw getError({\n messageCode: MailErrorCodes.SEND_FAILED,\n message: `[onWorkerData] ${MailExecutorErrors.PROCESSOR_NOT_SET} | jobId: ${job.data.id}`,\n });\n }\n\n const result = await this.processor(job.data.email);\n\n // A processor reports SMTP rejection by RETURNING `{ success: false }`; BullMQ only treats a REJECTION as failure, so resolving completes the job - no retry, no DLQ.\n if (result?.success === false) {\n throw getError({\n messageCode: MailErrorCodes.SEND_FAILED,\n message: `[onWorkerData] Processor reported failure | jobId: ${job.data.id} | reason: ${result.message}`,\n });\n }\n\n return result;\n },\n onWorkerDataCompleted: async (job: Job<IQueueJobPayload, IMailProcessorResult>) => {\n this.logger\n .for('onWorkerDataCompleted')\n .info(\n 'Job completed | Worker: %s | jobId: %s | email: %s',\n workerIdentifier,\n job.data.id,\n job.data.email,\n );\n },\n onWorkerDataFail: async (\n job: Job<IQueueJobPayload, IMailProcessorResult> | undefined,\n error: Error,\n ) => {\n if (job) {\n const maxAttempts = job.data.options?.attempts ?? 3;\n this.logger\n .for('onWorkerDataFail')\n .error(\n 'Job failed | Worker: %s | jobId: %s | email: %s | attempt: %d/%d | error: %s',\n workerIdentifier,\n job.data.id,\n job.data.email,\n job.attemptsMade,\n maxAttempts,\n error.message,\n );\n } else {\n this.logger\n .for('onWorkerDataFail')\n .error(\n 'Worker error (no job) | Worker: %s | error: %s',\n workerIdentifier,\n error.message,\n );\n }\n },\n });\n\n this.workerHelpers.push(workerHelper);\n\n this.logger\n .for(this.addWorker.name)\n .info(\n 'Worker added | Identifier: %s | Concurrency: %d | Total workers: %d',\n workerIdentifier,\n concurrency,\n this.workerHelpers.length,\n );\n }\n\n async removeWorker(index: number): Promise<boolean> {\n if (index < 0 || index >= this.workerHelpers.length) {\n this.logger.for(this.removeWorker.name).warn('Invalid worker index: %d', index);\n return false;\n }\n\n // Dropped from the list BEFORE the close is awaited: a rejecting close must not leave a half-closed worker still counted and still handed jobs.\n const [workerHelper] = this.workerHelpers.splice(index, 1);\n await workerHelper.worker.close();\n\n this.logger\n .for(this.removeWorker.name)\n .info('Worker removed | Index: %d | Remaining workers: %d', index, this.workerHelpers.length);\n\n return true;\n }\n\n /** Closes every worker without stopping at the first failure, always empties the list, and returns failures instead of throwing so teardown can continue. */\n private async closeWorkers(): Promise<Error[]> {\n const workerHelpers = this.workerHelpers;\n this.workerHelpers = [];\n\n const results = await Promise.allSettled(\n workerHelpers.map(workerHelper => workerHelper.worker.close()),\n );\n\n const failures: Error[] = [];\n\n for (const result of results) {\n if (result.status !== 'rejected') {\n continue;\n }\n\n const error =\n result.reason instanceof Error\n ? result.reason\n : getError({ message: `Worker close failed | reason: ${String(result.reason)}` });\n\n failures.push(error);\n this.logger.for('closeWorkers').error('Failed to close worker | error: %s', error.message);\n }\n\n return failures;\n }\n\n async clearWorkers(): Promise<void> {\n const count = this.workerHelpers.length;\n await this.closeWorkers();\n\n this.logger.for(this.clearWorkers.name).info('All workers cleared | Count: %d', count);\n }\n\n /** Full teardown: workers -> queue -> Redis. Every step runs even if an earlier one failed (skipping leaks a queue client + Redis socket); failures are reported once, at the end. */\n async close(): Promise<void> {\n const failures: Error[] = [...(await this.closeWorkers())];\n\n if (this.queueHelper) {\n try {\n await this.queueHelper.queue.close();\n } catch (error) {\n const closeError = error instanceof Error ? error : getError({ message: String(error) });\n failures.push(closeError);\n this.logger.for(this.close.name).error('Failed to close queue | error: %s', closeError);\n }\n }\n\n try {\n await this.redisConnection.disconnect();\n } catch (error) {\n const disconnectError = error instanceof Error ? error : getError({ message: String(error) });\n failures.push(disconnectError);\n this.logger\n .for(this.close.name)\n .error('Failed to disconnect redis | error: %s', disconnectError);\n }\n\n if (failures.length > 0) {\n throw getError({\n message: `[close] Incomplete BullMQ mail executor teardown | failures: ${failures\n .map(failure => failure.message)\n .join(' | ')}`,\n });\n }\n\n this.logger.for(this.close.name).info('BullMQ mail executor closed | Mode: %s', this.mode);\n }\n\n getWorkerCount(): number {\n return this.workerHelpers.length;\n }\n\n getMode(): TConstValue<typeof BullMQExecutorModes> {\n return this.mode;\n }\n\n async enqueueVerificationEmail(\n email: string,\n options?: IMailQueueOptions,\n ): Promise<IMailQueueResult> {\n if (this.mode === BullMQExecutorModes.WORKER_ONLY) {\n throw getError({\n message: 'Cannot enqueue jobs in worker-only mode. Set mode to \"queue-only\" or \"both\".',\n });\n }\n\n if (!this.queueHelper) {\n throw getError({\n message: 'Queue helper not initialized. This should not happen in queue-enabled mode.',\n });\n }\n\n if (!this.processor && this.mode !== BullMQExecutorModes.QUEUE_ONLY) {\n throw getError({ message: MailExecutorErrors.PROCESSOR_NOT_SET });\n }\n\n const jobId = `job_${++this.jobIdCounter}_${Date.now()}`;\n const job: IQueueJobPayload = {\n id: jobId,\n email,\n options,\n attempts: 0,\n scheduledAt: Date.now() + (options?.delay ?? 0),\n };\n\n this.logger\n .for(this.enqueueVerificationEmail.name)\n .info(\n 'Queuing email to BullMQ/Redis | jobId: %s | email: %s | mode: %s',\n jobId,\n email,\n this.mode,\n );\n\n const bullJob = await this.queueHelper.queue.add('send-verification-email', job, {\n priority: options?.priority,\n delay: options?.delay,\n attempts: options?.attempts ?? 3,\n backoff: {\n type: options?.backoff?.type ?? 'exponential',\n delay: options?.backoff?.delay ?? 1000,\n },\n removeOnComplete: true,\n removeOnFail: false,\n });\n\n return {\n jobId: bullJob.id ?? jobId,\n queued: true,\n message: 'Email queued successfully (BullMQ/Redis)',\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bull-mq-executor.helper.js","sourceRoot":"","sources":["../../../../../src/components/mail/helpers/executors/bull-mq-executor.helper.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseHelper, getError } from '@venizia/ignis-helpers/core';\nimport { RedisSingleHelper } from '@venizia/ignis-helpers';\nimport type { TConstValue } from '@/helpers';\nimport { BullMQHelper } from '@venizia/ignis-helpers/bullmq';\nimport type {\n IBullMQMailExecutorOpts,\n IMailProcessorResult,\n IMailQueueExecutor,\n IMailQueueOptions,\n IMailQueueResult,\n IQueueJobPayload,\n} from '../../common';\nimport { BullMQExecutorModes, MailErrorCodes, MailExecutorErrors } from '../../common';\nimport type { Job } from 'bullmq';\n\nexport class BullMQMailExecutorHelper extends BaseHelper implements IMailQueueExecutor {\n private queueIdentifier: string;\n private queueName: string;\n private mode: TConstValue<typeof BullMQExecutorModes>;\n\n private queueHelper?: BullMQHelper<IQueueJobPayload, IMailQueueResult>;\n private workerHelpers: BullMQHelper<IQueueJobPayload, IMailProcessorResult>[] = [];\n private redisConnection: RedisSingleHelper;\n private readonly bullmqModule?: typeof import('bullmq');\n private jobIdCounter = 0;\n\n private processor?: (email: string) => Promise<IMailProcessorResult>;\n\n constructor(opts: IBullMQMailExecutorOpts) {\n super({ scope: BullMQMailExecutorHelper.name });\n\n this.mode = opts.mode;\n\n this.redisConnection = new RedisSingleHelper({\n ...opts.redis,\n maxRetry: undefined,\n });\n\n this.bullmqModule = opts.module;\n this.queueIdentifier = opts.queue.identifier;\n this.queueName = opts.queue.name;\n\n this.initQueue(opts.queue);\n }\n\n initQueue(queue: { identifier: string; name: string }) {\n switch (this.mode) {\n case BullMQExecutorModes.QUEUE_ONLY:\n case BullMQExecutorModes.BOTH: {\n this.queueHelper = new BullMQHelper<IQueueJobPayload, IMailQueueResult>({\n identifier: queue.identifier,\n queueName: queue.name,\n redisConnection: this.redisConnection,\n role: 'queue',\n module: this.bullmqModule,\n });\n\n this.logger\n .for(this.constructor.name)\n .info(\n 'BullMQ queue executor initialized (queue enabled) | Identifier: %s | Mode: %s',\n this.queueIdentifier,\n this.mode,\n );\n break;\n }\n default: {\n this.logger\n .for(this.constructor.name)\n .info(\n 'BullMQ queue executor initialized (worker-only mode) | Identifier: %s | Mode: %s',\n this.queueIdentifier,\n this.mode,\n );\n\n break;\n }\n }\n }\n\n async setProcessor(\n processor: (email: string) => Promise<IMailProcessorResult>,\n opts?: {\n numberOfWorkers?: number;\n concurrencyPerWorker?: number;\n lockDuration?: number;\n },\n ): Promise<void> {\n this.processor = processor;\n\n if (this.mode === BullMQExecutorModes.QUEUE_ONLY) {\n this.logger\n .for(this.setProcessor.name)\n .warn('Skipping worker creation in queue-only mode. Workers will not be started.');\n return;\n }\n\n const numberOfWorkers = opts?.numberOfWorkers ?? 1;\n const concurrencyPerWorker = opts?.concurrencyPerWorker ?? 5;\n const lockDuration = opts?.lockDuration ?? 30000;\n\n await this.clearWorkers();\n\n for (let i = 0; i < numberOfWorkers; i++) {\n this.addWorker({\n workerIdentifier: `${this.queueIdentifier}-worker-${i}`,\n concurrency: concurrencyPerWorker,\n lockDuration,\n });\n }\n\n this.logger\n .for(this.setProcessor.name)\n .info(\n 'Processor registered | Mode: %s | Workers: %d | Concurrency per worker: %d',\n this.mode,\n numberOfWorkers,\n concurrencyPerWorker,\n );\n }\n\n addWorker(opts: { workerIdentifier: string; concurrency?: number; lockDuration?: number }): void {\n if (!this.processor) {\n throw getError({ message: MailExecutorErrors.PROCESSOR_NOT_SET });\n }\n\n const workerIdentifier = opts.workerIdentifier;\n const concurrency = opts?.concurrency ?? 5;\n const lockDuration = opts?.lockDuration ?? 30000;\n\n const workerHelper = new BullMQHelper<IQueueJobPayload, IMailProcessorResult>({\n identifier: workerIdentifier,\n queueName: this.queueName,\n redisConnection: this.redisConnection,\n role: 'worker',\n module: this.bullmqModule,\n numberOfWorker: concurrency,\n lockDuration,\n onWorkerData: async (job: Job<IQueueJobPayload, IMailProcessorResult>) => {\n const maxAttempts = job.data.options?.attempts ?? 3;\n\n this.logger\n .for('onWorkerData')\n .info(\n 'Processing job | Worker: %s | jobId: %s | email: %s | attempt: %d/%d',\n workerIdentifier,\n job.data.id,\n job.data.email,\n job.attemptsMade + 1,\n maxAttempts,\n );\n\n if (!this.processor) {\n // Returning undefined would let BullMQ mark the job COMPLETED and (with removeOnComplete) delete it - mail silently dropped.\n throw getError({\n messageCode: MailErrorCodes.SEND_FAILED,\n message: `[onWorkerData] ${MailExecutorErrors.PROCESSOR_NOT_SET} | jobId: ${job.data.id}`,\n });\n }\n\n const result = await this.processor(job.data.email);\n\n // A processor reports SMTP rejection by RETURNING `{ success: false }`; BullMQ only treats a REJECTION as failure, so resolving completes the job - no retry, no DLQ.\n if (result?.success === false) {\n throw getError({\n messageCode: MailErrorCodes.SEND_FAILED,\n message: `[onWorkerData] Processor reported failure | jobId: ${job.data.id} | reason: ${result.message}`,\n });\n }\n\n return result;\n },\n onWorkerDataCompleted: async (job: Job<IQueueJobPayload, IMailProcessorResult>) => {\n this.logger\n .for('onWorkerDataCompleted')\n .info(\n 'Job completed | Worker: %s | jobId: %s | email: %s',\n workerIdentifier,\n job.data.id,\n job.data.email,\n );\n },\n onWorkerDataFail: async (\n job: Job<IQueueJobPayload, IMailProcessorResult> | undefined,\n error: Error,\n ) => {\n if (job) {\n const maxAttempts = job.data.options?.attempts ?? 3;\n this.logger\n .for('onWorkerDataFail')\n .error(\n 'Job failed | Worker: %s | jobId: %s | email: %s | attempt: %d/%d | error: %s',\n workerIdentifier,\n job.data.id,\n job.data.email,\n job.attemptsMade,\n maxAttempts,\n error.message,\n );\n } else {\n this.logger\n .for('onWorkerDataFail')\n .error(\n 'Worker error (no job) | Worker: %s | error: %s',\n workerIdentifier,\n error.message,\n );\n }\n },\n });\n\n this.workerHelpers.push(workerHelper);\n\n this.logger\n .for(this.addWorker.name)\n .info(\n 'Worker added | Identifier: %s | Concurrency: %d | Total workers: %d',\n workerIdentifier,\n concurrency,\n this.workerHelpers.length,\n );\n }\n\n async removeWorker(index: number): Promise<boolean> {\n if (index < 0 || index >= this.workerHelpers.length) {\n this.logger.for(this.removeWorker.name).warn('Invalid worker index: %d', index);\n return false;\n }\n\n // Dropped from the list BEFORE the close is awaited: a rejecting close must not leave a half-closed worker still counted and still handed jobs.\n const [workerHelper] = this.workerHelpers.splice(index, 1);\n await workerHelper.worker.close();\n\n this.logger\n .for(this.removeWorker.name)\n .info('Worker removed | Index: %d | Remaining workers: %d', index, this.workerHelpers.length);\n\n return true;\n }\n\n /** Closes every worker without stopping at the first failure, always empties the list, and returns failures instead of throwing so teardown can continue. */\n private async closeWorkers(): Promise<Error[]> {\n const workerHelpers = this.workerHelpers;\n this.workerHelpers = [];\n\n const results = await Promise.allSettled(\n workerHelpers.map((workerHelper) => workerHelper.worker.close()),\n );\n\n const failures: Error[] = [];\n\n for (const result of results) {\n if (result.status !== 'rejected') {\n continue;\n }\n\n const error =\n result.reason instanceof Error\n ? result.reason\n : getError({ message: `Worker close failed | reason: ${String(result.reason)}` });\n\n failures.push(error);\n this.logger.for('closeWorkers').error('Failed to close worker | error: %s', error.message);\n }\n\n return failures;\n }\n\n async clearWorkers(): Promise<void> {\n const count = this.workerHelpers.length;\n await this.closeWorkers();\n\n this.logger.for(this.clearWorkers.name).info('All workers cleared | Count: %d', count);\n }\n\n /** Full teardown: workers -> queue -> Redis. Every step runs even if an earlier one failed (skipping leaks a queue client + Redis socket); failures are reported once, at the end. */\n async close(): Promise<void> {\n const failures: Error[] = [...(await this.closeWorkers())];\n\n if (this.queueHelper) {\n try {\n await this.queueHelper.queue.close();\n } catch (error) {\n const closeError = error instanceof Error ? error : getError({ message: String(error) });\n failures.push(closeError);\n this.logger.for(this.close.name).error('Failed to close queue | error: %s', closeError);\n }\n }\n\n try {\n await this.redisConnection.disconnect();\n } catch (error) {\n const disconnectError = error instanceof Error ? error : getError({ message: String(error) });\n failures.push(disconnectError);\n this.logger\n .for(this.close.name)\n .error('Failed to disconnect redis | error: %s', disconnectError);\n }\n\n if (failures.length > 0) {\n throw getError({\n message: `[close] Incomplete BullMQ mail executor teardown | failures: ${failures\n .map((failure) => failure.message)\n .join(' | ')}`,\n });\n }\n\n this.logger.for(this.close.name).info('BullMQ mail executor closed | Mode: %s', this.mode);\n }\n\n getWorkerCount(): number {\n return this.workerHelpers.length;\n }\n\n getMode(): TConstValue<typeof BullMQExecutorModes> {\n return this.mode;\n }\n\n async enqueueVerificationEmail(\n email: string,\n options?: IMailQueueOptions,\n ): Promise<IMailQueueResult> {\n if (this.mode === BullMQExecutorModes.WORKER_ONLY) {\n throw getError({\n message: 'Cannot enqueue jobs in worker-only mode. Set mode to \"queue-only\" or \"both\".',\n });\n }\n\n if (!this.queueHelper) {\n throw getError({\n message: 'Queue helper not initialized. This should not happen in queue-enabled mode.',\n });\n }\n\n if (!this.processor && this.mode !== BullMQExecutorModes.QUEUE_ONLY) {\n throw getError({ message: MailExecutorErrors.PROCESSOR_NOT_SET });\n }\n\n const jobId = `job_${++this.jobIdCounter}_${Date.now()}`;\n const job: IQueueJobPayload = {\n id: jobId,\n email,\n options,\n attempts: 0,\n scheduledAt: Date.now() + (options?.delay ?? 0),\n };\n\n this.logger\n .for(this.enqueueVerificationEmail.name)\n .info(\n 'Queuing email to BullMQ/Redis | jobId: %s | email: %s | mode: %s',\n jobId,\n email,\n this.mode,\n );\n\n const bullJob = await this.queueHelper.queue.add('send-verification-email', job, {\n priority: options?.priority,\n delay: options?.delay,\n attempts: options?.attempts ?? 3,\n backoff: {\n type: options?.backoff?.type ?? 'exponential',\n delay: options?.backoff?.delay ?? 1000,\n },\n removeOnComplete: true,\n removeOnFail: false,\n });\n\n return {\n jobId: bullJob.id ?? jobId,\n queued: true,\n message: 'Email queued successfully (BullMQ/Redis)',\n };\n }\n}\n"]}
|
|
@@ -47,7 +47,7 @@ class InternalQueueMailExecutorHelper extends core_1.BaseHelper {
|
|
|
47
47
|
.info('Queuing email | jobId: %s | email: %s', jobId, email);
|
|
48
48
|
if (options?.delay && options.delay > 0) {
|
|
49
49
|
const timeout = setTimeout(() => {
|
|
50
|
-
this.queue.enqueue(job).catch(error => {
|
|
50
|
+
this.queue.enqueue(job).catch((error) => {
|
|
51
51
|
this.logger
|
|
52
52
|
.for(this.enqueueVerificationEmail.name)
|
|
53
53
|
.error('Failed to enqueue delayed job | jobId: %s | error: %s', jobId, error);
|
|
@@ -107,7 +107,7 @@ class InternalQueueMailExecutorHelper extends core_1.BaseHelper {
|
|
|
107
107
|
.for(this.processJob.name)
|
|
108
108
|
.warn('Job failed, retrying | jobId: %s | attempt: %d/%d | retryIn: %dms | error: %s', job.id, job.attempts, maxAttempts, backoffDelay, errorMsg);
|
|
109
109
|
const timeout = setTimeout(() => {
|
|
110
|
-
this.queue.enqueue(job).catch(reenqueueError => {
|
|
110
|
+
this.queue.enqueue(job).catch((reenqueueError) => {
|
|
111
111
|
this.logger
|
|
112
112
|
.for(this.processJob.name)
|
|
113
113
|
.error('Failed to re-enqueue job | jobId: %s | error: %s', job.id, reenqueueError);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal-queue-executor.helper.js","sourceRoot":"","sources":["../../../../../src/components/mail/helpers/executors/internal-queue-executor.helper.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseHelper, getError } from '@venizia/ignis-helpers/core';\nimport { SequentialQueueHelper } from '@venizia/ignis-helpers';\nimport type {\n IMailQueueOptions,\n IMailQueueExecutor,\n IMailProcessorResult,\n IInternalQueueMailExecutorOpts,\n IMailQueueResult,\n IQueueJobPayload,\n} from '../../common';\nimport { MailErrorCodes, MailExecutorErrors } from '../../common';\n\nexport class InternalQueueMailExecutorHelper extends BaseHelper implements IMailQueueExecutor {\n private queue: SequentialQueueHelper<IQueueJobPayload>;\n private jobIdCounter = 0;\n private delayedJobs: Map<string, NodeJS.Timeout> = new Map();\n\n private processor?: (email: string) => Promise<IMailProcessorResult>;\n\n constructor(opts: IInternalQueueMailExecutorOpts) {\n super({ scope: InternalQueueMailExecutorHelper.name });\n\n this.queue = new SequentialQueueHelper<IQueueJobPayload>({\n identifier: opts.identifier,\n autoDispatch: true,\n onMessage: async ({ queueElement }) => {\n await this.processJob(queueElement.payload);\n },\n onDataEnqueue: ({ queueElement }) => {\n this.logger\n .for('onDataEnqueue')\n .info(\n 'Job enqueued | jobId: %s | email: %s',\n queueElement.payload.id,\n queueElement.payload.email,\n );\n },\n onStateChange: ({ from, to }) => {\n this.logger.for('onStateChange').debug('Queue state changed | from: %s | to: %s', from, to);\n },\n });\n\n this.logger.for(this.constructor.name).info('Internal queue executor initialized');\n }\n\n setProcessor(processor: (email: string) => Promise<IMailProcessorResult>): void {\n this.processor = processor;\n this.logger.for(this.setProcessor.name).info('Processor registered');\n }\n\n async enqueueVerificationEmail(\n email: string,\n options?: IMailQueueOptions,\n ): Promise<IMailQueueResult> {\n if (!this.processor) {\n throw getError({ message: MailExecutorErrors.PROCESSOR_NOT_SET });\n }\n\n const jobId = `job_${++this.jobIdCounter}_${Date.now()}`;\n const job: IQueueJobPayload = {\n id: jobId,\n email,\n options,\n attempts: 0,\n scheduledAt: Date.now() + (options?.delay ?? 0),\n };\n\n this.logger\n .for(this.enqueueVerificationEmail.name)\n .info('Queuing email | jobId: %s | email: %s', jobId, email);\n\n if (options?.delay && options.delay > 0) {\n const timeout = setTimeout(() => {\n this.queue.enqueue(job).catch(error => {\n this.logger\n .for(this.enqueueVerificationEmail.name)\n .error('Failed to enqueue delayed job | jobId: %s | error: %s', jobId, error);\n });\n this.delayedJobs.delete(jobId);\n }, options.delay);\n\n this.delayedJobs.set(jobId, timeout);\n\n this.logger\n .for(this.enqueueVerificationEmail.name)\n .info('Job scheduled with delay | jobId: %s | delay: %dms', jobId, options.delay);\n } else {\n await this.queue.enqueue(job);\n }\n\n return {\n jobId,\n queued: true,\n message: 'Email queued successfully (internal queue)',\n };\n }\n\n /** Releases every pending delayed/backoff timer - a live `setTimeout` holds the event loop open past the executor. */\n async close(): Promise<void> {\n const pendingCount = this.delayedJobs.size;\n\n const delayedJobsValues = this.delayedJobs.values();\n for (const timeout of delayedJobsValues) {\n clearTimeout(timeout);\n }\n\n this.delayedJobs.clear();\n\n this.logger.for(this.close.name).info('Executor closed | Cleared timers: %d', pendingCount);\n }\n\n private async processJob(job: IQueueJobPayload): Promise<void> {\n if (!this.processor) {\n this.logger.for(this.processJob.name).error('Processor not set | jobId: %s', job.id);\n return;\n }\n\n const maxAttempts = job.options?.attempts ?? 3;\n\n this.logger\n .for(this.processJob.name)\n .info(\n 'Processing job | jobId: %s | email: %s | attempt: %d/%d',\n job.id,\n job.email,\n job.attempts + 1,\n maxAttempts,\n );\n\n try {\n const result = await this.processor(job.email);\n\n // A processor reports SMTP rejection by RETURNING `{ success: false }`; watching only for throws completes the job and drops the mail.\n if (result?.success === false) {\n throw getError({\n messageCode: MailErrorCodes.SEND_FAILED,\n message: `[processJob] Processor reported failure | jobId: ${job.id} | reason: ${result.message}`,\n });\n }\n\n this.logger.for(this.processJob.name).info('Job completed successfully | jobId: %s', job.id);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : error;\n job.attempts++;\n\n if (job.attempts < maxAttempts) {\n const backoffDelay = this.calculateBackoff(job);\n\n this.logger\n .for(this.processJob.name)\n .warn(\n 'Job failed, retrying | jobId: %s | attempt: %d/%d | retryIn: %dms | error: %s',\n job.id,\n job.attempts,\n maxAttempts,\n backoffDelay,\n errorMsg,\n );\n\n const timeout = setTimeout(() => {\n this.queue.enqueue(job).catch(reenqueueError => {\n this.logger\n .for(this.processJob.name)\n .error('Failed to re-enqueue job | jobId: %s | error: %s', job.id, reenqueueError);\n });\n this.delayedJobs.delete(job.id);\n }, backoffDelay);\n\n this.delayedJobs.set(job.id, timeout);\n } else {\n this.logger\n .for(this.processJob.name)\n .error(\n 'Job failed permanently after %d attempts | jobId: %s | error: %s',\n maxAttempts,\n job.id,\n errorMsg,\n );\n }\n }\n }\n\n private calculateBackoff(job: IQueueJobPayload): number {\n const backoff = job.options?.backoff;\n\n if (!backoff) {\n return 1000;\n }\n\n if (backoff.type === 'exponential') {\n return backoff.delay * Math.pow(2, job.attempts - 1);\n }\n\n return backoff.delay;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"internal-queue-executor.helper.js","sourceRoot":"","sources":["../../../../../src/components/mail/helpers/executors/internal-queue-executor.helper.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseHelper, getError } from '@venizia/ignis-helpers/core';\nimport { SequentialQueueHelper } from '@venizia/ignis-helpers';\nimport type {\n IMailQueueOptions,\n IMailQueueExecutor,\n IMailProcessorResult,\n IInternalQueueMailExecutorOpts,\n IMailQueueResult,\n IQueueJobPayload,\n} from '../../common';\nimport { MailErrorCodes, MailExecutorErrors } from '../../common';\n\nexport class InternalQueueMailExecutorHelper extends BaseHelper implements IMailQueueExecutor {\n private queue: SequentialQueueHelper<IQueueJobPayload>;\n private jobIdCounter = 0;\n private delayedJobs: Map<string, NodeJS.Timeout> = new Map();\n\n private processor?: (email: string) => Promise<IMailProcessorResult>;\n\n constructor(opts: IInternalQueueMailExecutorOpts) {\n super({ scope: InternalQueueMailExecutorHelper.name });\n\n this.queue = new SequentialQueueHelper<IQueueJobPayload>({\n identifier: opts.identifier,\n autoDispatch: true,\n onMessage: async ({ queueElement }) => {\n await this.processJob(queueElement.payload);\n },\n onDataEnqueue: ({ queueElement }) => {\n this.logger\n .for('onDataEnqueue')\n .info(\n 'Job enqueued | jobId: %s | email: %s',\n queueElement.payload.id,\n queueElement.payload.email,\n );\n },\n onStateChange: ({ from, to }) => {\n this.logger.for('onStateChange').debug('Queue state changed | from: %s | to: %s', from, to);\n },\n });\n\n this.logger.for(this.constructor.name).info('Internal queue executor initialized');\n }\n\n setProcessor(processor: (email: string) => Promise<IMailProcessorResult>): void {\n this.processor = processor;\n this.logger.for(this.setProcessor.name).info('Processor registered');\n }\n\n async enqueueVerificationEmail(\n email: string,\n options?: IMailQueueOptions,\n ): Promise<IMailQueueResult> {\n if (!this.processor) {\n throw getError({ message: MailExecutorErrors.PROCESSOR_NOT_SET });\n }\n\n const jobId = `job_${++this.jobIdCounter}_${Date.now()}`;\n const job: IQueueJobPayload = {\n id: jobId,\n email,\n options,\n attempts: 0,\n scheduledAt: Date.now() + (options?.delay ?? 0),\n };\n\n this.logger\n .for(this.enqueueVerificationEmail.name)\n .info('Queuing email | jobId: %s | email: %s', jobId, email);\n\n if (options?.delay && options.delay > 0) {\n const timeout = setTimeout(() => {\n this.queue.enqueue(job).catch((error) => {\n this.logger\n .for(this.enqueueVerificationEmail.name)\n .error('Failed to enqueue delayed job | jobId: %s | error: %s', jobId, error);\n });\n this.delayedJobs.delete(jobId);\n }, options.delay);\n\n this.delayedJobs.set(jobId, timeout);\n\n this.logger\n .for(this.enqueueVerificationEmail.name)\n .info('Job scheduled with delay | jobId: %s | delay: %dms', jobId, options.delay);\n } else {\n await this.queue.enqueue(job);\n }\n\n return {\n jobId,\n queued: true,\n message: 'Email queued successfully (internal queue)',\n };\n }\n\n /** Releases every pending delayed/backoff timer - a live `setTimeout` holds the event loop open past the executor. */\n async close(): Promise<void> {\n const pendingCount = this.delayedJobs.size;\n\n const delayedJobsValues = this.delayedJobs.values();\n for (const timeout of delayedJobsValues) {\n clearTimeout(timeout);\n }\n\n this.delayedJobs.clear();\n\n this.logger.for(this.close.name).info('Executor closed | Cleared timers: %d', pendingCount);\n }\n\n private async processJob(job: IQueueJobPayload): Promise<void> {\n if (!this.processor) {\n this.logger.for(this.processJob.name).error('Processor not set | jobId: %s', job.id);\n return;\n }\n\n const maxAttempts = job.options?.attempts ?? 3;\n\n this.logger\n .for(this.processJob.name)\n .info(\n 'Processing job | jobId: %s | email: %s | attempt: %d/%d',\n job.id,\n job.email,\n job.attempts + 1,\n maxAttempts,\n );\n\n try {\n const result = await this.processor(job.email);\n\n // A processor reports SMTP rejection by RETURNING `{ success: false }`; watching only for throws completes the job and drops the mail.\n if (result?.success === false) {\n throw getError({\n messageCode: MailErrorCodes.SEND_FAILED,\n message: `[processJob] Processor reported failure | jobId: ${job.id} | reason: ${result.message}`,\n });\n }\n\n this.logger.for(this.processJob.name).info('Job completed successfully | jobId: %s', job.id);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : error;\n job.attempts++;\n\n if (job.attempts < maxAttempts) {\n const backoffDelay = this.calculateBackoff(job);\n\n this.logger\n .for(this.processJob.name)\n .warn(\n 'Job failed, retrying | jobId: %s | attempt: %d/%d | retryIn: %dms | error: %s',\n job.id,\n job.attempts,\n maxAttempts,\n backoffDelay,\n errorMsg,\n );\n\n const timeout = setTimeout(() => {\n this.queue.enqueue(job).catch((reenqueueError) => {\n this.logger\n .for(this.processJob.name)\n .error('Failed to re-enqueue job | jobId: %s | error: %s', job.id, reenqueueError);\n });\n this.delayedJobs.delete(job.id);\n }, backoffDelay);\n\n this.delayedJobs.set(job.id, timeout);\n } else {\n this.logger\n .for(this.processJob.name)\n .error(\n 'Job failed permanently after %d attempts | jobId: %s | error: %s',\n maxAttempts,\n job.id,\n errorMsg,\n );\n }\n }\n }\n\n private calculateBackoff(job: IQueueJobPayload): number {\n const backoff = job.options?.backoff;\n\n if (!backoff) {\n return 1000;\n }\n\n if (backoff.type === 'exponential') {\n return backoff.delay * Math.pow(2, job.attempts - 1);\n }\n\n return backoff.delay;\n }\n}\n"]}
|
|
@@ -19,7 +19,7 @@ class MailgunTransportHelper extends core_1.BaseHelper {
|
|
|
19
19
|
}
|
|
20
20
|
/** Credentials and domain are checked here, not on the first send; the error never echoes a credential value back. */
|
|
21
21
|
validateConfig(config) {
|
|
22
|
-
const missingKeys = ['username', 'key', 'domain'].filter(key => {
|
|
22
|
+
const missingKeys = ['username', 'key', 'domain'].filter((key) => {
|
|
23
23
|
return !config?.[key];
|
|
24
24
|
});
|
|
25
25
|
if (missingKeys.length === 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mailgun-transporter.helper.js","sourceRoot":"","sources":["../../../../../src/components/mail/helpers/transporters/mailgun-transporter.helper.ts"],"names":[],"mappings":";;;AAAA,0DAAqD;AAErD,sDAAmE;AACnE,0DAAuD;AASvD,yCAA8C;AAC9C,2EAG4C;AAE5C,MAAa,sBAAuB,SAAQ,iBAAU;IAKpD,YAAY,IAAyD;QACnE,KAAK,CAAC,EAAE,KAAK,EAAE,sBAAsB,CAAC,IAAI,EAAE,CAAC,CAAC;QAE9C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAED,SAAS,CAAC,MAAsB;QAC9B,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAE5B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACzC,CAAC;IAED,sHAAsH;IAC9G,cAAc,CAAC,MAAsB;QAC3C,MAAM,WAAW,GAAI,CAAC,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAW,CAAC,MAAM,CAAC,GAAG,
|
|
1
|
+
{"version":3,"file":"mailgun-transporter.helper.js","sourceRoot":"","sources":["../../../../../src/components/mail/helpers/transporters/mailgun-transporter.helper.ts"],"names":[],"mappings":"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","sourcesContent":["import { HTTP } from '@venizia/ignis-helpers/common';\nimport type { AnyType } from '@venizia/ignis-helpers/common';\nimport { BaseHelper, getError } from '@venizia/ignis-helpers/core';\nimport { ModuleUtility } from '@venizia/ignis-helpers';\nimport type {\n IMailAttachment,\n IMailMessage,\n IMailSendResult,\n IMailTransport,\n TMailgunConfig,\n TMailgunModule,\n} from '../../common';\nimport { MailErrorCodes } from '../../common';\nimport {\n releaseAttachmentStreams,\n resolveMailAttachments,\n} from '../../utilities/attachment.utility';\n\nexport class MailgunTransportHelper extends BaseHelper implements IMailTransport {\n private client: AnyType; // IMessagesClient from mailgun.js\n private domain: string;\n private module?: TMailgunModule;\n\n constructor(opts: { config: TMailgunConfig; module?: TMailgunModule }) {\n super({ scope: MailgunTransportHelper.name });\n\n this.module = opts.module;\n this.configure(opts.config);\n }\n\n configure(config: TMailgunConfig) {\n this.validateConfig(config);\n\n this.domain = config.domain;\n this.client = this.buildClient(config);\n }\n\n /** Credentials and domain are checked here, not on the first send; the error never echoes a credential value back. */\n private validateConfig(config: TMailgunConfig): void {\n const missingKeys = (['username', 'key', 'domain'] as const).filter((key) => {\n return !config?.[key];\n });\n\n if (missingKeys.length === 0) {\n return;\n }\n\n this.logger\n .for(this.configure.name)\n .error('Invalid Mailgun configuration | Missing keys: %s', missingKeys.join(', '));\n\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.INVALID_CONFIGURATION,\n message: `Invalid Mailgun configuration | Missing required keys: ${missingKeys.join(', ')}`,\n });\n }\n\n /** Client factory seam - overridden in tests to run the helper without the mailgun.js peer. */\n protected buildClient(config: TMailgunConfig): AnyType {\n const Mailgun = this.module ?? ModuleUtility.loadSync<AnyType>({ module: 'mailgun.js' });\n const mailgun = new Mailgun(FormData);\n const client = mailgun.client(config);\n\n return client.messages;\n }\n\n async send(message: IMailMessage): Promise<IMailSendResult> {\n try {\n const mailgunMessage: AnyType = {\n from: message.from,\n to: Array.isArray(message.to) ? message.to : [message.to],\n subject: message.subject,\n text: message.text,\n html: message.html,\n cc: message.cc,\n bcc: message.bcc,\n };\n\n if (message.replyTo) {\n mailgunMessage['h:Reply-To'] = message.replyTo;\n }\n\n if (message.headers) {\n Object.entries(message.headers).forEach(([key, value]) => {\n mailgunMessage[`h:${key}`] = value;\n });\n }\n\n if (message.attachments && message.attachments.length > 0) {\n // No root: MailService resolved paths already; one that reaches here is refused, not read.\n const attachments = await resolveMailAttachments({ attachments: message.attachments });\n\n mailgunMessage.attachment = attachments.map((attachment: IMailAttachment) => {\n return { filename: attachment.filename, data: attachment.content };\n });\n }\n\n this.logger.for(this.send.name).debug('Sending email with Mailgun to: %s', mailgunMessage.to);\n const result: AnyType = await this.client.create(this.domain, mailgunMessage);\n\n return {\n success: true,\n messageId: result.id,\n response: result,\n };\n } catch (error) {\n this.logger.for(this.send.name).error('Mailgun send failed: %s', error);\n releaseAttachmentStreams({ attachments: message.attachments });\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n async verify(): Promise<boolean> {\n try {\n this.logger.for(this.verify.name).info('Verifying Mailgun API connection');\n // Mailgun has no verify endpoint - a test-mode send is the lightweight credential check.\n await this.client.create(this.domain, {\n from: 'verify@' + this.domain,\n to: ['verify@' + this.domain],\n subject: 'Verification Test',\n text: 'This is a verification test',\n 'o:testmode': 'yes', // Use test mode to avoid actually sending\n });\n\n this.logger.for(this.verify.name).info('Mailgun API connection verified successfully');\n return true;\n } catch (error) {\n this.logger.for(this.verify.name).error('Mailgun API verification failed: %s', error);\n return false;\n }\n }\n}\n"]}
|
|
@@ -81,7 +81,7 @@ let MailService = MailService_1 = class MailService extends ignis_kernel_1.BaseS
|
|
|
81
81
|
this.logger
|
|
82
82
|
.for(this.sendBatch.name)
|
|
83
83
|
.info('Sending batch of %d emails with concurrency: %d', messages.length, concurrency);
|
|
84
|
-
const tasks = messages.map(message => async () => {
|
|
84
|
+
const tasks = messages.map((message) => async () => {
|
|
85
85
|
try {
|
|
86
86
|
return await this.send(message);
|
|
87
87
|
}
|
|
@@ -96,7 +96,7 @@ let MailService = MailService_1 = class MailService extends ignis_kernel_1.BaseS
|
|
|
96
96
|
tasks,
|
|
97
97
|
limit: concurrency,
|
|
98
98
|
});
|
|
99
|
-
const successCount = results.filter(r => r.success).length;
|
|
99
|
+
const successCount = results.filter((r) => r.success).length;
|
|
100
100
|
this.logger
|
|
101
101
|
.for(this.sendBatch.name)
|
|
102
102
|
.info('Batch send completed. Success: %d, Failed: %d', successCount, results.length - successCount);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mail.service.js","sourceRoot":"","sources":["../../../../src/components/mail/services/mail.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { HTTP } from '@venizia/ignis-helpers/common';\nimport { BaseService } from '@venizia/ignis-kernel';\nimport type {\n IMailMessage,\n IMailSendResult,\n IMailService,\n IMailTemplateEngine,\n IMailTransport,\n TMailOptions,\n} from '../common';\nimport { MailDefaults, MailErrorCodes, MailErrors, MailKeys } from '../common';\nimport { inject } from '@/base/metadata';\nimport { getError, isApplicationError } from '@venizia/ignis-helpers/core';\nimport { type AnyType } from '@venizia/ignis-helpers/common';\nimport { executePromiseWithLimit } from '@venizia/ignis-helpers';\nimport { releaseAttachmentStreams, resolveMailAttachments } from '../utilities/attachment.utility';\nimport { assertMailBodyIsText } from '../utilities/body.utility';\n\nexport class MailService extends BaseService implements IMailService {\n constructor(\n @inject({ key: MailKeys.MAIL_OPTIONS })\n protected options: TMailOptions,\n @inject({ key: MailKeys.MAIL_TRANSPORT_INSTANCE })\n protected transport: IMailTransport,\n @inject({ key: MailKeys.MAIL_TEMPLATE_ENGINE, isOptional: true })\n protected templateEngine?: IMailTemplateEngine,\n ) {\n super({ scope: MailService.name });\n this.logger\n .for(this.constructor.name)\n .info('Mail service initialized with provider: %s', this.options.provider);\n }\n\n async send(message: IMailMessage): Promise<IMailSendResult> {\n try {\n this.validateMessage(message);\n\n const emailMessage: IMailMessage = {\n ...message,\n from: message.from ?? this.getDefaultFrom(),\n };\n\n // The one place the attachment policy applies: every transport receives bytes, never a path.\n if (message.attachments) {\n emailMessage.attachments = await resolveMailAttachments({\n attachments: message.attachments,\n attachmentRoot: this.options.attachmentRoot,\n maxBytes: this.options.maxAttachmentBytes ?? MailDefaults.MAX_ATTACHMENT_BYTES,\n });\n }\n\n this.logger.for(this.send.name).debug('Sending email to: %s', emailMessage.to);\n const result = await this.transport.send(emailMessage);\n\n if (result.success) {\n this.logger\n .for(this.send.name)\n .debug('Email sent successfully. MessageId: %s', result.messageId);\n } else {\n this.logger.for(this.send.name).debug('Email send failed: %s', result.error);\n }\n\n return result;\n } catch (error) {\n this.logger.for(this.send.name).error('Error sending email: %s', error);\n releaseAttachmentStreams({ attachments: message.attachments });\n\n // An error the framework already shaped carries its own status and message code; re-wrapping as 500 would report a caller mistake as a server fault.\n if (isApplicationError(error)) {\n throw error;\n }\n\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.SEND_FAILED,\n // The raw text can carry a file path or a provider detail; it rides in `cause`, which only development responses show.\n message: 'Failed to send email',\n cause: error,\n });\n }\n }\n\n async sendBatch(\n messages: IMailMessage[],\n options?: { concurrency?: number },\n ): Promise<IMailSendResult[]> {\n try {\n const concurrency = options?.concurrency ?? MailDefaults.BATCH_CONCURRENCY;\n this.logger\n .for(this.sendBatch.name)\n .info('Sending batch of %d emails with concurrency: %d', messages.length, concurrency);\n\n const tasks = messages.map(message => async () => {\n try {\n return await this.send(message);\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n });\n\n const results = await executePromiseWithLimit<IMailSendResult>({\n tasks,\n limit: concurrency,\n });\n\n const successCount = results.filter(r => r.success).length;\n this.logger\n .for(this.sendBatch.name)\n .info(\n 'Batch send completed. Success: %d, Failed: %d',\n successCount,\n results.length - successCount,\n );\n\n return results;\n } catch (error) {\n this.logger.for(this.sendBatch.name).error('Error sending batch emails: %s', error);\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.BATCH_SEND_FAILED,\n message: 'Failed to send batch emails',\n cause: error,\n });\n }\n }\n\n async sendTemplate(opts: {\n templateName: string;\n data: Record<string, AnyType>;\n recipients: string | string[];\n options?: Partial<IMailMessage>;\n }): Promise<IMailSendResult> {\n const { templateName, data, recipients, options } = opts;\n\n try {\n if (!this.templateEngine) {\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.INVALID_CONFIGURATION,\n message: 'Template engine not configured',\n });\n }\n\n this.logger.for(this.sendTemplate.name).debug('Rendering template: %s', templateName);\n const html = this.templateEngine.render({\n templateName,\n data,\n requireValidate: options?.requireValidate,\n });\n\n const templateData = this.templateEngine.getTemplate(templateName);\n\n const message: IMailMessage = {\n to: recipients,\n subject:\n options?.subject ??\n (templateData?.subject !== undefined\n ? this.templateEngine.render({\n templateData: templateData.subject,\n data,\n requireValidate: options?.requireValidate,\n })\n : 'No Subject'),\n html,\n ...options,\n };\n\n return await this.send(message);\n } catch (error) {\n this.logger.for(this.sendTemplate.name).error('Error sending template email: %s', error);\n throw error;\n }\n }\n\n async verify(): Promise<boolean> {\n try {\n this.logger.for(this.verify.name).debug('Verifying mail transport connection');\n const isValid = await this.transport.verify();\n this.logger.for(this.verify.name).debug('Verification result: %s', isValid);\n return isValid;\n } catch (error) {\n this.logger.for(this.verify.name).error('Verification failed: %s', error);\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.VERIFICATION_FAILED,\n message: 'Mail transport verification failed',\n cause: error,\n });\n }\n }\n\n protected validateMessage(message: IMailMessage): void {\n if (!message.to || (Array.isArray(message.to) && message.to.length === 0)) {\n throw getError({\n error: MailErrors.INVALID_RECIPIENT,\n message: 'Recipient email address is required',\n });\n }\n\n if (!message.subject) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message: 'Email subject is required',\n });\n }\n\n if (!message.text && !message.html) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message: 'Email must have either text or html content',\n });\n }\n\n assertMailBodyIsText({ message });\n }\n\n protected getDefaultFrom(): string {\n const { from, fromName } = this.options;\n\n // A display name is only renderable around an address: `\"Name\" <undefined>` is not a sender.\n if (!from) {\n return MailDefaults.FALLBACK_FROM;\n }\n\n if (!fromName) {\n return from;\n }\n\n return `\"${fromName}\" <${from}>`;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"mail.service.js","sourceRoot":"","sources":["../../../../src/components/mail/services/mail.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { HTTP } from '@venizia/ignis-helpers/common';\nimport { BaseService } from '@venizia/ignis-kernel';\nimport type {\n IMailMessage,\n IMailSendResult,\n IMailService,\n IMailTemplateEngine,\n IMailTransport,\n TMailOptions,\n} from '../common';\nimport { MailDefaults, MailErrorCodes, MailErrors, MailKeys } from '../common';\nimport { inject } from '@/base/metadata';\nimport { getError, isApplicationError } from '@venizia/ignis-helpers/core';\nimport { type AnyType } from '@venizia/ignis-helpers/common';\nimport { executePromiseWithLimit } from '@venizia/ignis-helpers';\nimport { releaseAttachmentStreams, resolveMailAttachments } from '../utilities/attachment.utility';\nimport { assertMailBodyIsText } from '../utilities/body.utility';\n\nexport class MailService extends BaseService implements IMailService {\n constructor(\n @inject({ key: MailKeys.MAIL_OPTIONS })\n protected options: TMailOptions,\n @inject({ key: MailKeys.MAIL_TRANSPORT_INSTANCE })\n protected transport: IMailTransport,\n @inject({ key: MailKeys.MAIL_TEMPLATE_ENGINE, isOptional: true })\n protected templateEngine?: IMailTemplateEngine,\n ) {\n super({ scope: MailService.name });\n this.logger\n .for(this.constructor.name)\n .info('Mail service initialized with provider: %s', this.options.provider);\n }\n\n async send(message: IMailMessage): Promise<IMailSendResult> {\n try {\n this.validateMessage(message);\n\n const emailMessage: IMailMessage = {\n ...message,\n from: message.from ?? this.getDefaultFrom(),\n };\n\n // The one place the attachment policy applies: every transport receives bytes, never a path.\n if (message.attachments) {\n emailMessage.attachments = await resolveMailAttachments({\n attachments: message.attachments,\n attachmentRoot: this.options.attachmentRoot,\n maxBytes: this.options.maxAttachmentBytes ?? MailDefaults.MAX_ATTACHMENT_BYTES,\n });\n }\n\n this.logger.for(this.send.name).debug('Sending email to: %s', emailMessage.to);\n const result = await this.transport.send(emailMessage);\n\n if (result.success) {\n this.logger\n .for(this.send.name)\n .debug('Email sent successfully. MessageId: %s', result.messageId);\n } else {\n this.logger.for(this.send.name).debug('Email send failed: %s', result.error);\n }\n\n return result;\n } catch (error) {\n this.logger.for(this.send.name).error('Error sending email: %s', error);\n releaseAttachmentStreams({ attachments: message.attachments });\n\n // An error the framework already shaped carries its own status and message code; re-wrapping as 500 would report a caller mistake as a server fault.\n if (isApplicationError(error)) {\n throw error;\n }\n\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.SEND_FAILED,\n // The raw text can carry a file path or a provider detail; it rides in `cause`, which only development responses show.\n message: 'Failed to send email',\n cause: error,\n });\n }\n }\n\n async sendBatch(\n messages: IMailMessage[],\n options?: { concurrency?: number },\n ): Promise<IMailSendResult[]> {\n try {\n const concurrency = options?.concurrency ?? MailDefaults.BATCH_CONCURRENCY;\n this.logger\n .for(this.sendBatch.name)\n .info('Sending batch of %d emails with concurrency: %d', messages.length, concurrency);\n\n const tasks = messages.map((message) => async () => {\n try {\n return await this.send(message);\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n });\n\n const results = await executePromiseWithLimit<IMailSendResult>({\n tasks,\n limit: concurrency,\n });\n\n const successCount = results.filter((r) => r.success).length;\n this.logger\n .for(this.sendBatch.name)\n .info(\n 'Batch send completed. Success: %d, Failed: %d',\n successCount,\n results.length - successCount,\n );\n\n return results;\n } catch (error) {\n this.logger.for(this.sendBatch.name).error('Error sending batch emails: %s', error);\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.BATCH_SEND_FAILED,\n message: 'Failed to send batch emails',\n cause: error,\n });\n }\n }\n\n async sendTemplate(opts: {\n templateName: string;\n data: Record<string, AnyType>;\n recipients: string | string[];\n options?: Partial<IMailMessage>;\n }): Promise<IMailSendResult> {\n const { templateName, data, recipients, options } = opts;\n\n try {\n if (!this.templateEngine) {\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.INVALID_CONFIGURATION,\n message: 'Template engine not configured',\n });\n }\n\n this.logger.for(this.sendTemplate.name).debug('Rendering template: %s', templateName);\n const html = this.templateEngine.render({\n templateName,\n data,\n requireValidate: options?.requireValidate,\n });\n\n const templateData = this.templateEngine.getTemplate(templateName);\n\n const message: IMailMessage = {\n to: recipients,\n subject:\n options?.subject ??\n (templateData?.subject !== undefined\n ? this.templateEngine.render({\n templateData: templateData.subject,\n data,\n requireValidate: options?.requireValidate,\n })\n : 'No Subject'),\n html,\n ...options,\n };\n\n return await this.send(message);\n } catch (error) {\n this.logger.for(this.sendTemplate.name).error('Error sending template email: %s', error);\n throw error;\n }\n }\n\n async verify(): Promise<boolean> {\n try {\n this.logger.for(this.verify.name).debug('Verifying mail transport connection');\n const isValid = await this.transport.verify();\n this.logger.for(this.verify.name).debug('Verification result: %s', isValid);\n return isValid;\n } catch (error) {\n this.logger.for(this.verify.name).error('Verification failed: %s', error);\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n messageCode: MailErrorCodes.VERIFICATION_FAILED,\n message: 'Mail transport verification failed',\n cause: error,\n });\n }\n }\n\n protected validateMessage(message: IMailMessage): void {\n if (!message.to || (Array.isArray(message.to) && message.to.length === 0)) {\n throw getError({\n error: MailErrors.INVALID_RECIPIENT,\n message: 'Recipient email address is required',\n });\n }\n\n if (!message.subject) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message: 'Email subject is required',\n });\n }\n\n if (!message.text && !message.html) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message: 'Email must have either text or html content',\n });\n }\n\n assertMailBodyIsText({ message });\n }\n\n protected getDefaultFrom(): string {\n const { from, fromName } = this.options;\n\n // A display name is only renderable around an address: `\"Name\" <undefined>` is not a sender.\n if (!from) {\n return MailDefaults.FALLBACK_FROM;\n }\n\n if (!fromName) {\n return from;\n }\n\n return `\"${fromName}\" <${from}>`;\n }\n}\n"]}
|
|
@@ -93,7 +93,7 @@ const readConfinedFile = async (opts) => {
|
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
95
|
const filePath = await resolveConfinedPath({ path, attachmentRoot });
|
|
96
|
-
const handle = await (0, promises_1.open)(filePath, OPEN_FLAGS).catch(error => {
|
|
96
|
+
const handle = await (0, promises_1.open)(filePath, OPEN_FLAGS).catch((error) => {
|
|
97
97
|
throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON, cause: error });
|
|
98
98
|
});
|
|
99
99
|
try {
|
|
@@ -196,7 +196,7 @@ const releaseAttachmentStreams = (opts) => {
|
|
|
196
196
|
const { content } = attachment;
|
|
197
197
|
if (content instanceof ReadableStream) {
|
|
198
198
|
if (!content.locked) {
|
|
199
|
-
content.cancel().catch(error => {
|
|
199
|
+
content.cancel().catch((error) => {
|
|
200
200
|
logger.warn('[releaseAttachmentStreams] Cancelling a web stream failed | error: %s', error);
|
|
201
201
|
});
|
|
202
202
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment.utility.js","sourceRoot":"","sources":["../../../../src/components/mail/utilities/attachment.utility.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from 'node:fs';\nimport { open, realpath } from 'node:fs/promises';\nimport { basename, isAbsolute, relative, resolve, sep } from 'node:path';\nimport { ContentTypeTable } from '@venizia/ignis-helpers/common';\nimport { getError } from '@venizia/ignis-helpers/core';\nimport { LoggerFactory } from '@venizia/ignis-helpers';\nimport { MailErrors, type IMailAttachment } from '../common';\n\nconst logger = LoggerFactory.getLogger(['MailAttachments']);\n\n// O_NONBLOCK: opening a FIFO must not wait for a writer; the fstat check then refuses it.\n// Windows has neither flag; there the realpath containment check stands alone.\nconst OPEN_FLAGS = constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0);\n\n// One reason for missing, directory, unreadable and escaping paths: no existence oracle.\nconst PATH_OUTSIDE_REASON =\n 'attachment.path must name a readable file inside attachmentRoot | pass the bytes as content instead';\n\ntype TAttachmentStream = AsyncIterable<Uint8Array | string>;\n\nconst buildPathRefusedError = (opts: { reason: string; cause?: unknown }) => {\n return getError({\n error: MailErrors.ATTACHMENT_PATH_REFUSED,\n message: `Mail attachment path refused | ${opts.reason}`,\n cause: opts.cause,\n });\n};\n\nconst buildTooLargeError = (opts: { maxBytes: number }) => {\n return getError({\n error: MailErrors.ATTACHMENT_TOO_LARGE,\n message: `Mail attachments are over maxAttachmentBytes (${opts.maxBytes} bytes per message) | raise maxAttachmentBytes on the mail options, or send smaller attachments`,\n });\n};\n\nconst assertWithinLimit = (opts: { byteLength: number; usedBytes: number; maxBytes?: number }) => {\n const { byteLength, usedBytes, maxBytes } = opts;\n\n if (maxBytes === undefined || usedBytes + byteLength <= maxBytes) {\n return;\n }\n\n throw buildTooLargeError({ maxBytes });\n};\n\nconst isAttachmentStream = (value: unknown): value is TAttachmentStream => {\n return typeof value === 'object' && value !== null && Symbol.asyncIterator in value;\n};\n\nconst toBuffer = (chunk: Uint8Array | string): Buffer => {\n if (typeof chunk === 'string') {\n return Buffer.from(chunk);\n }\n\n return Buffer.isBuffer(chunk)\n ? chunk\n : Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n};\n\n/** Throwing out of `for await` destroys a Node Readable and cancels a web ReadableStream. */\nconst drainStream = async (opts: {\n stream: TAttachmentStream;\n usedBytes: number;\n maxBytes?: number;\n}): Promise<Buffer> => {\n const { stream, usedBytes, maxBytes } = opts;\n const chunks: Buffer[] = [];\n let streamBytes = 0;\n\n for await (const chunk of stream) {\n const buffer = toBuffer(chunk);\n streamBytes += buffer.byteLength;\n\n // Per chunk: a stream has no length up front, and the rest must never be buffered.\n assertWithinLimit({ byteLength: streamBytes, usedBytes, maxBytes });\n chunks.push(buffer);\n }\n\n return Buffer.concat(chunks, streamBytes);\n};\n\n/**\n * Both sides go through realpath, so neither `..` nor a symlink can leave the root. Not closed: a\n * directory inside the root swapped for a symlink between this check and `open` - that takes write\n * access inside the root, so keep the root writable only by what the application trusts.\n */\nconst resolveConfinedPath = async (opts: {\n path: string;\n attachmentRoot: string;\n}): Promise<string> => {\n const { path, attachmentRoot } = opts;\n\n let rootPath: string;\n let filePath: string;\n\n // `resolve` throws synchronously on a non-string `path` from an untyped caller; it lands here too.\n try {\n [rootPath, filePath] = await Promise.all([\n realpath(attachmentRoot),\n realpath(resolve(attachmentRoot, path)),\n ]);\n } catch (error) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON, cause: error });\n }\n\n const relation = relative(rootPath, filePath);\n const isOutside =\n relation === '' || relation === '..' || relation.startsWith(`..${sep}`) || isAbsolute(relation);\n\n if (isOutside) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON });\n }\n\n return filePath;\n};\n\nconst readConfinedFile = async (opts: {\n path: string;\n usedBytes: number;\n attachmentRoot?: string;\n maxBytes?: number;\n}): Promise<Buffer> => {\n const { path, usedBytes, attachmentRoot, maxBytes } = opts;\n\n if (!attachmentRoot) {\n throw buildPathRefusedError({\n reason:\n 'attachment.path is read only under attachmentRoot, which is not set | set attachmentRoot on the mail options, or pass the bytes as content',\n });\n }\n\n const filePath = await resolveConfinedPath({ path, attachmentRoot });\n const handle = await open(filePath, OPEN_FLAGS).catch(error => {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON, cause: error });\n });\n\n try {\n const stats = await handle.stat();\n\n if (!stats.isFile()) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON });\n }\n\n // Sized before reading, so a file known to be oversized is never opened for reading.\n assertWithinLimit({ byteLength: stats.size, usedBytes, maxBytes });\n\n // Counted while read, and read no further than one byte past the budget: the file may have\n // grown since fstat.\n const stream = handle.createReadStream({\n autoClose: false,\n end: maxBytes === undefined ? Infinity : maxBytes - usedBytes,\n });\n\n return await drainStream({ stream, usedBytes, maxBytes });\n } finally {\n await handle.close();\n }\n};\n\n/**\n * Reads one attachment's bytes. A string `content` is raw text unless `encoding` says otherwise\n * (nodemailer's convention); `null` counts as absent. A `path` is read only inside\n * `attachmentRoot` and refused without one. `maxBytes` caps the whole message; `usedBytes` is what\n * earlier attachments already took.\n */\nexport const readAttachmentContent = async (opts: {\n attachment: IMailAttachment;\n attachmentRoot?: string;\n maxBytes?: number;\n usedBytes?: number;\n}): Promise<Buffer> => {\n const { attachment, attachmentRoot, maxBytes, usedBytes = 0 } = opts;\n const { content } = attachment;\n\n if (content !== undefined && content !== null) {\n if (typeof content === 'string') {\n const encoding = attachment.encoding ?? 'utf-8';\n assertWithinLimit({ byteLength: Buffer.byteLength(content, encoding), usedBytes, maxBytes });\n return Buffer.from(content, encoding);\n }\n\n if (content instanceof Uint8Array) {\n assertWithinLimit({ byteLength: content.byteLength, usedBytes, maxBytes });\n return toBuffer(content);\n }\n\n if (!isAttachmentStream(content)) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message:\n 'Invalid mail attachment | attachment.content must be a Buffer, a Uint8Array, a string or a stream',\n });\n }\n\n return drainStream({ stream: content, usedBytes, maxBytes });\n }\n\n if (attachment.path !== undefined && attachment.path !== null) {\n return readConfinedFile({ path: attachment.path, usedBytes, attachmentRoot, maxBytes });\n }\n\n return Buffer.alloc(0);\n};\n\n/**\n * Turns every attachment into `content` bytes and drops `path`, so no transport is ever handed a\n * file or URL to read on its own. `maxBytes` is shared by all attachments of the message. An\n * attachment read from `path` keeps the file's name and, when none is given, its content type.\n */\nexport const resolveMailAttachments = async (opts: {\n attachments: IMailAttachment[];\n attachmentRoot?: string;\n maxBytes?: number;\n}): Promise<IMailAttachment[]> => {\n const { attachments, attachmentRoot, maxBytes } = opts;\n const resolved: IMailAttachment[] = [];\n let usedBytes = 0;\n\n // Sequential on purpose: each read spends the budget the previous ones left.\n for (const attachment of attachments) {\n const { path, href, raw, content, encoding, ...rest } = attachment;\n\n // nodemailer reads both on its own - `href` fetches a URL, `raw` can carry a `path`.\n if ((href !== undefined && href !== null) || (raw !== undefined && raw !== null)) {\n throw buildPathRefusedError({\n reason: 'attachment.href and attachment.raw are not supported | pass the bytes as content',\n });\n }\n\n const bytes = await readAttachmentContent({\n attachment: { content, encoding, path },\n attachmentRoot,\n maxBytes,\n usedBytes,\n });\n\n usedBytes += bytes.byteLength;\n\n const isReadFromPath = (content === undefined || content === null) && typeof path === 'string';\n const filename = rest.filename ?? (isReadFromPath ? basename(path) : undefined);\n const contentType =\n rest.contentType ??\n (isReadFromPath && filename ? ContentTypeTable.resolve({ filename }) : undefined);\n\n resolved.push({\n ...rest,\n ...(filename === undefined ? {} : { filename }),\n ...(contentType === undefined ? {} : { contentType }),\n content: bytes,\n });\n }\n\n return resolved;\n};\n\n/**\n * Releases every attachment stream of a refused send, read or not, so none keeps a descriptor\n * open. A drained stream is already closed; releasing it again is harmless.\n */\nexport const releaseAttachmentStreams = (opts: { attachments?: IMailAttachment[] }): void => {\n for (const attachment of opts.attachments ?? []) {\n const { content } = attachment;\n\n if (content instanceof ReadableStream) {\n if (!content.locked) {\n content.cancel().catch(error => {\n logger.warn(\n '[releaseAttachmentStreams] Cancelling a web stream failed | error: %s',\n error,\n );\n });\n }\n\n continue;\n }\n\n if (\n typeof content === 'object' &&\n content !== null &&\n 'destroy' in content &&\n typeof content.destroy === 'function'\n ) {\n content.destroy();\n }\n }\n};\n"]}
|
|
1
|
+
{"version":3,"file":"attachment.utility.js","sourceRoot":"","sources":["../../../../src/components/mail/utilities/attachment.utility.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from 'node:fs';\nimport { open, realpath } from 'node:fs/promises';\nimport { basename, isAbsolute, relative, resolve, sep } from 'node:path';\nimport { ContentTypeTable } from '@venizia/ignis-helpers/common';\nimport { getError } from '@venizia/ignis-helpers/core';\nimport { LoggerFactory } from '@venizia/ignis-helpers';\nimport { MailErrors, type IMailAttachment } from '../common';\n\nconst logger = LoggerFactory.getLogger(['MailAttachments']);\n\n// O_NONBLOCK: opening a FIFO must not wait for a writer; the fstat check then refuses it.\n// Windows has neither flag; there the realpath containment check stands alone.\nconst OPEN_FLAGS = constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0);\n\n// One reason for missing, directory, unreadable and escaping paths: no existence oracle.\nconst PATH_OUTSIDE_REASON =\n 'attachment.path must name a readable file inside attachmentRoot | pass the bytes as content instead';\n\ntype TAttachmentStream = AsyncIterable<Uint8Array | string>;\n\nconst buildPathRefusedError = (opts: { reason: string; cause?: unknown }) => {\n return getError({\n error: MailErrors.ATTACHMENT_PATH_REFUSED,\n message: `Mail attachment path refused | ${opts.reason}`,\n cause: opts.cause,\n });\n};\n\nconst buildTooLargeError = (opts: { maxBytes: number }) => {\n return getError({\n error: MailErrors.ATTACHMENT_TOO_LARGE,\n message: `Mail attachments are over maxAttachmentBytes (${opts.maxBytes} bytes per message) | raise maxAttachmentBytes on the mail options, or send smaller attachments`,\n });\n};\n\nconst assertWithinLimit = (opts: { byteLength: number; usedBytes: number; maxBytes?: number }) => {\n const { byteLength, usedBytes, maxBytes } = opts;\n\n if (maxBytes === undefined || usedBytes + byteLength <= maxBytes) {\n return;\n }\n\n throw buildTooLargeError({ maxBytes });\n};\n\nconst isAttachmentStream = (value: unknown): value is TAttachmentStream => {\n return typeof value === 'object' && value !== null && Symbol.asyncIterator in value;\n};\n\nconst toBuffer = (chunk: Uint8Array | string): Buffer => {\n if (typeof chunk === 'string') {\n return Buffer.from(chunk);\n }\n\n return Buffer.isBuffer(chunk)\n ? chunk\n : Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n};\n\n/** Throwing out of `for await` destroys a Node Readable and cancels a web ReadableStream. */\nconst drainStream = async (opts: {\n stream: TAttachmentStream;\n usedBytes: number;\n maxBytes?: number;\n}): Promise<Buffer> => {\n const { stream, usedBytes, maxBytes } = opts;\n const chunks: Buffer[] = [];\n let streamBytes = 0;\n\n for await (const chunk of stream) {\n const buffer = toBuffer(chunk);\n streamBytes += buffer.byteLength;\n\n // Per chunk: a stream has no length up front, and the rest must never be buffered.\n assertWithinLimit({ byteLength: streamBytes, usedBytes, maxBytes });\n chunks.push(buffer);\n }\n\n return Buffer.concat(chunks, streamBytes);\n};\n\n/**\n * Both sides go through realpath, so neither `..` nor a symlink can leave the root. Not closed: a\n * directory inside the root swapped for a symlink between this check and `open` - that takes write\n * access inside the root, so keep the root writable only by what the application trusts.\n */\nconst resolveConfinedPath = async (opts: {\n path: string;\n attachmentRoot: string;\n}): Promise<string> => {\n const { path, attachmentRoot } = opts;\n\n let rootPath: string;\n let filePath: string;\n\n // `resolve` throws synchronously on a non-string `path` from an untyped caller; it lands here too.\n try {\n [rootPath, filePath] = await Promise.all([\n realpath(attachmentRoot),\n realpath(resolve(attachmentRoot, path)),\n ]);\n } catch (error) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON, cause: error });\n }\n\n const relation = relative(rootPath, filePath);\n const isOutside =\n relation === '' || relation === '..' || relation.startsWith(`..${sep}`) || isAbsolute(relation);\n\n if (isOutside) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON });\n }\n\n return filePath;\n};\n\nconst readConfinedFile = async (opts: {\n path: string;\n usedBytes: number;\n attachmentRoot?: string;\n maxBytes?: number;\n}): Promise<Buffer> => {\n const { path, usedBytes, attachmentRoot, maxBytes } = opts;\n\n if (!attachmentRoot) {\n throw buildPathRefusedError({\n reason:\n 'attachment.path is read only under attachmentRoot, which is not set | set attachmentRoot on the mail options, or pass the bytes as content',\n });\n }\n\n const filePath = await resolveConfinedPath({ path, attachmentRoot });\n const handle = await open(filePath, OPEN_FLAGS).catch((error) => {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON, cause: error });\n });\n\n try {\n const stats = await handle.stat();\n\n if (!stats.isFile()) {\n throw buildPathRefusedError({ reason: PATH_OUTSIDE_REASON });\n }\n\n // Sized before reading, so a file known to be oversized is never opened for reading.\n assertWithinLimit({ byteLength: stats.size, usedBytes, maxBytes });\n\n // Counted while read, and read no further than one byte past the budget: the file may have\n // grown since fstat.\n const stream = handle.createReadStream({\n autoClose: false,\n end: maxBytes === undefined ? Infinity : maxBytes - usedBytes,\n });\n\n return await drainStream({ stream, usedBytes, maxBytes });\n } finally {\n await handle.close();\n }\n};\n\n/**\n * Reads one attachment's bytes. A string `content` is raw text unless `encoding` says otherwise\n * (nodemailer's convention); `null` counts as absent. A `path` is read only inside\n * `attachmentRoot` and refused without one. `maxBytes` caps the whole message; `usedBytes` is what\n * earlier attachments already took.\n */\nexport const readAttachmentContent = async (opts: {\n attachment: IMailAttachment;\n attachmentRoot?: string;\n maxBytes?: number;\n usedBytes?: number;\n}): Promise<Buffer> => {\n const { attachment, attachmentRoot, maxBytes, usedBytes = 0 } = opts;\n const { content } = attachment;\n\n if (content !== undefined && content !== null) {\n if (typeof content === 'string') {\n const encoding = attachment.encoding ?? 'utf-8';\n assertWithinLimit({ byteLength: Buffer.byteLength(content, encoding), usedBytes, maxBytes });\n return Buffer.from(content, encoding);\n }\n\n if (content instanceof Uint8Array) {\n assertWithinLimit({ byteLength: content.byteLength, usedBytes, maxBytes });\n return toBuffer(content);\n }\n\n if (!isAttachmentStream(content)) {\n throw getError({\n error: MailErrors.INVALID_CONFIGURATION,\n message:\n 'Invalid mail attachment | attachment.content must be a Buffer, a Uint8Array, a string or a stream',\n });\n }\n\n return drainStream({ stream: content, usedBytes, maxBytes });\n }\n\n if (attachment.path !== undefined && attachment.path !== null) {\n return readConfinedFile({ path: attachment.path, usedBytes, attachmentRoot, maxBytes });\n }\n\n return Buffer.alloc(0);\n};\n\n/**\n * Turns every attachment into `content` bytes and drops `path`, so no transport is ever handed a\n * file or URL to read on its own. `maxBytes` is shared by all attachments of the message. An\n * attachment read from `path` keeps the file's name and, when none is given, its content type.\n */\nexport const resolveMailAttachments = async (opts: {\n attachments: IMailAttachment[];\n attachmentRoot?: string;\n maxBytes?: number;\n}): Promise<IMailAttachment[]> => {\n const { attachments, attachmentRoot, maxBytes } = opts;\n const resolved: IMailAttachment[] = [];\n let usedBytes = 0;\n\n // Sequential on purpose: each read spends the budget the previous ones left.\n for (const attachment of attachments) {\n const { path, href, raw, content, encoding, ...rest } = attachment;\n\n // nodemailer reads both on its own - `href` fetches a URL, `raw` can carry a `path`.\n if ((href !== undefined && href !== null) || (raw !== undefined && raw !== null)) {\n throw buildPathRefusedError({\n reason: 'attachment.href and attachment.raw are not supported | pass the bytes as content',\n });\n }\n\n const bytes = await readAttachmentContent({\n attachment: { content, encoding, path },\n attachmentRoot,\n maxBytes,\n usedBytes,\n });\n\n usedBytes += bytes.byteLength;\n\n const isReadFromPath = (content === undefined || content === null) && typeof path === 'string';\n const filename = rest.filename ?? (isReadFromPath ? basename(path) : undefined);\n const contentType =\n rest.contentType ??\n (isReadFromPath && filename ? ContentTypeTable.resolve({ filename }) : undefined);\n\n resolved.push({\n ...rest,\n ...(filename === undefined ? {} : { filename }),\n ...(contentType === undefined ? {} : { contentType }),\n content: bytes,\n });\n }\n\n return resolved;\n};\n\n/**\n * Releases every attachment stream of a refused send, read or not, so none keeps a descriptor\n * open. A drained stream is already closed; releasing it again is harmless.\n */\nexport const releaseAttachmentStreams = (opts: { attachments?: IMailAttachment[] }): void => {\n for (const attachment of opts.attachments ?? []) {\n const { content } = attachment;\n\n if (content instanceof ReadableStream) {\n if (!content.locked) {\n content.cancel().catch((error) => {\n logger.warn(\n '[releaseAttachmentStreams] Cancelling a web stream failed | error: %s',\n error,\n );\n });\n }\n\n continue;\n }\n\n if (\n typeof content === 'object' &&\n content !== null &&\n 'destroy' in content &&\n typeof content.destroy === 'function'\n ) {\n content.destroy();\n }\n }\n};\n"]}
|
|
@@ -247,7 +247,7 @@ function escapeQuotedString(value) {
|
|
|
247
247
|
}
|
|
248
248
|
/** RFC 2231 percent-encoding for the `filename*=`/`name*=` extended parameter form. */
|
|
249
249
|
function encodeRfc2231(value) {
|
|
250
|
-
return encodeURIComponent(value).replace(/[!'()*]/g, char => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
250
|
+
return encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
251
251
|
}
|
|
252
252
|
/**
|
|
253
253
|
* Builds the `; name="..."`/`; filename="..."` (and, for non-ASCII names, the RFC 2231
|
|
@@ -312,8 +312,8 @@ function newBoundary(label) {
|
|
|
312
312
|
async function buildRawMimeMessage(message) {
|
|
313
313
|
const headers = buildHeaders(message);
|
|
314
314
|
const attachments = message.attachments ?? [];
|
|
315
|
-
const inlineAttachments = attachments.filter(attachment => Boolean(attachment.cid));
|
|
316
|
-
const fileAttachments = attachments.filter(attachment => !attachment.cid);
|
|
315
|
+
const inlineAttachments = attachments.filter((attachment) => Boolean(attachment.cid));
|
|
316
|
+
const fileAttachments = attachments.filter((attachment) => !attachment.cid);
|
|
317
317
|
const hasBothBodies = Boolean(message.text) && Boolean(message.html);
|
|
318
318
|
const singleContentType = message.html ? 'text/html' : 'text/plain';
|
|
319
319
|
const singleContent = message.html ?? message.text ?? '';
|