attachment-utils.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { Logger } from '@vendure/core';
  2. import { Readable, Stream } from 'stream';
  3. import { format, Url } from 'url';
  4. import { loggerCtx } from './constants';
  5. import { EmailAttachment, SerializedAttachment } from './types';
  6. export async function serializeAttachments(attachments: EmailAttachment[]): Promise<SerializedAttachment[]> {
  7. const promises = attachments.map(async a => {
  8. const stringPath = (path: string | Url) => (typeof path === 'string' ? path : format(path));
  9. const content = a.content instanceof Stream ? await streamToBuffer(a.content) : a.content;
  10. return {
  11. filename: null,
  12. cid: null,
  13. encoding: null,
  14. contentType: null,
  15. contentTransferEncoding: null,
  16. contentDisposition: null,
  17. headers: null,
  18. ...a,
  19. path: a.path ? stringPath(a.path) : null,
  20. content: JSON.stringify(content),
  21. };
  22. });
  23. return Promise.all(promises);
  24. }
  25. export function deserializeAttachments(serializedAttachments: SerializedAttachment[]): EmailAttachment[] {
  26. return serializedAttachments.map(a => {
  27. const content = parseContent(a.content);
  28. if (content instanceof Buffer && 50 * 1024 <= content.length) {
  29. Logger.warn(
  30. `Email has a large 'content' attachment (${Math.round(
  31. content.length / 1024,
  32. )}k). Consider using the 'path' instead for improved performance.`,
  33. loggerCtx,
  34. );
  35. }
  36. return {
  37. filename: nullToUndefined(a.filename),
  38. cid: nullToUndefined(a.cid),
  39. encoding: nullToUndefined(a.encoding),
  40. contentType: nullToUndefined(a.contentType),
  41. contentTransferEncoding: nullToUndefined(a.contentTransferEncoding),
  42. contentDisposition: nullToUndefined(a.contentDisposition),
  43. headers: nullToUndefined(a.headers),
  44. path: nullToUndefined(a.path),
  45. content,
  46. };
  47. });
  48. }
  49. function parseContent(content: string | null): string | Buffer | undefined {
  50. try {
  51. const parsedContent = content && JSON.parse(content);
  52. if (typeof parsedContent === 'string') {
  53. return parsedContent;
  54. } else if (parsedContent.hasOwnProperty('data')) {
  55. return Buffer.from(parsedContent.data);
  56. }
  57. } catch (e: any) {
  58. // empty
  59. }
  60. }
  61. function streamToBuffer(stream: Readable): Promise<Buffer> {
  62. const chunks: Buffer[] = [];
  63. return new Promise((resolve, reject) => {
  64. stream.on('data', chunk => {
  65. chunks.push(Buffer.from(chunk));
  66. });
  67. stream.on('error', err => reject(err));
  68. stream.on('end', () => {
  69. resolve(Buffer.concat(chunks));
  70. });
  71. });
  72. }
  73. function nullToUndefined<T>(input: T | null): T | undefined {
  74. return input == null ? undefined : input;
  75. }