@awsmbiz/inbis-notifications 0.0.0-stage → 0.2.0
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 +206 -2
- package/dist/index.d.ts +354 -0
- package/dist/index.js +644 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["render","render"],"sources":["../src/layout.ts","../src/messages.ts","../src/auth-templates.ts","../src/communication-service.ts","../src/errors.ts","../src/communication-service-email.ts","../src/communication-service-sms.ts","../src/log-email.ts","../src/log-sms.ts"],"sourcesContent":["import type { EmailBrand, RenderedEmail } from \"@awsmbiz/inbis-core\";\n\n/** Content of a branded transactional email with one call to action. */\nexport interface BrandedEmailContent {\n brand: EmailBrand;\n subject: string;\n /** Greeting line, e.g. \"Hi Ada,\". */\n greeting: string;\n heading: string;\n /** Paragraphs before the button. */\n intro: readonly string[];\n action: { label: string; url: string };\n /** Line under the button explaining how to copy the link. */\n linkFallback: string;\n /** Paragraphs after the button (expiry, \"ignore if not you\"). */\n outro: readonly string[];\n signOff: string;\n}\n\nconst NEUTRAL_ACCENT = \"#18181b\";\nconst HEX_COLOR = /^#[0-9a-fA-F]{6}$/;\n\nexport function escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\")\n .replaceAll(\"'\", \"'\");\n}\n\nfunction httpsUrl(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n try {\n return new URL(value).protocol === \"https:\" ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction paragraph(text: string, style = \"margin:0 0 16px;font-size:15px;line-height:24px;color:#3f3f46\"): string {\n return `<p style=\"${style}\">${escapeHtml(text)}</p>`;\n}\n\n/**\n * Renders a branded email as plain text and HTML. All text is escaped; the\n * accent colour must be `#rrggbb` and the logo an https URL, otherwise they\n * are left out. Table-based, inline-styled HTML for broad client support.\n */\nexport function renderBrandedEmail(content: BrandedEmailContent): RenderedEmail {\n const { brand } = content;\n const accent = brand.accentColor !== undefined && HEX_COLOR.test(brand.accentColor) ? brand.accentColor : NEUTRAL_ACCENT;\n const logo = httpsUrl(brand.logoUrl);\n const footer = brand.footer?.trim();\n\n const text = [\n content.greeting,\n \"\",\n ...content.intro.flatMap((line) => [line, \"\"]),\n `${content.action.label}: ${content.action.url}`,\n \"\",\n ...content.outro.flatMap((line) => [line, \"\"]),\n content.signOff,\n ...(footer === undefined || footer === \"\" ? [] : [\"\", \"--\", footer]),\n ].join(\"\\n\");\n\n const header =\n logo === undefined\n ? `<p style=\"margin:0;font-size:18px;font-weight:600;color:#18181b\">${escapeHtml(brand.name)}</p>`\n : `<img src=\"${escapeHtml(logo)}\" alt=\"${escapeHtml(brand.name)}\" height=\"32\" style=\"display:block;height:32px;border:0\">`;\n const href = escapeHtml(content.action.url);\n\n const html = `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${escapeHtml(content.subject)}</title>\n</head>\n<body style=\"margin:0;padding:0;background:#f4f4f5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif\">\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f4f4f5;padding:24px 12px\">\n<tr><td align=\"center\">\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:560px;background:#ffffff;border:1px solid #e4e4e7;border-radius:8px\">\n<tr><td style=\"padding:24px 32px 0\">${header}</td></tr>\n<tr><td style=\"padding:24px 32px 8px\">\n<h1 style=\"margin:0 0 16px;font-size:20px;line-height:28px;color:#18181b\">${escapeHtml(content.heading)}</h1>\n${paragraph(content.greeting)}\n${content.intro.map((line) => paragraph(line)).join(\"\\n\")}\n<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 24px\"><tr><td style=\"border-radius:6px;background:${accent}\">\n<a href=\"${href}\" style=\"display:inline-block;padding:12px 20px;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none\">${escapeHtml(content.action.label)}</a>\n</td></tr></table>\n${paragraph(content.linkFallback, \"margin:0 0 8px;font-size:13px;line-height:20px;color:#71717a\")}\n<p style=\"margin:0 0 24px;font-size:13px;line-height:20px;word-break:break-all\"><a href=\"${href}\" style=\"color:${accent}\">${href}</a></p>\n${content.outro.map((line) => paragraph(line)).join(\"\\n\")}\n${paragraph(content.signOff)}\n</td></tr>\n</table>\n${footer === undefined || footer === \"\" ? \"\" : paragraph(footer, \"margin:16px 0 0;font-size:12px;line-height:18px;color:#71717a\")}\n</td></tr>\n</table>\n</body>\n</html>\n`;\n\n return { subject: content.subject, text, html };\n}\n","/**\n * Every string of the email templates, by message key. English only in the\n * baseline; another locale is another catalogue passed to\n * `createAuthEmailTemplates`. `{name}` placeholders are filled at render time.\n */\nexport const emailMessages = {\n \"email.greeting\": \"Hi {name},\",\n \"email.greetingNoName\": \"Hi,\",\n \"email.linkFallback\": \"If the button does not work, copy this link into your browser:\",\n \"email.signOff\": \"— The {appName} team\",\n \"auth.verifyEmail.subject\": \"Verify your email address for {appName}\",\n \"auth.verifyEmail.heading\": \"Verify your email address\",\n \"auth.verifyEmail.intro\": \"Confirm that this is your email address to finish setting up your {appName} account.\",\n \"auth.verifyEmail.action\": \"Verify email address\",\n \"auth.verifyEmail.expiry\": \"This link works for {minutes} minutes.\",\n \"auth.verifyEmail.ignore\": \"If you did not create an account, you can ignore this email.\",\n \"auth.resetPassword.subject\": \"Reset your {appName} password\",\n \"auth.resetPassword.heading\": \"Reset your password\",\n \"auth.resetPassword.intro\": \"Someone asked to reset the password of your {appName} account.\",\n \"auth.resetPassword.action\": \"Reset password\",\n \"auth.resetPassword.expiry\": \"This link works for {minutes} minutes and can be used once.\",\n \"auth.resetPassword.ignore\": \"If you did not ask for this, ignore this email: your password stays the same.\",\n \"auth.invitation.subject\": \"{inviter} invited you to {appName}\",\n \"auth.invitation.subjectNoInviter\": \"You are invited to {appName}\",\n \"auth.invitation.heading\": \"You are invited to {appName}\",\n \"auth.invitation.intro\": \"{inviter} invited you to join {appName}.\",\n \"auth.invitation.introNoInviter\": \"You have been invited to join {appName}.\",\n \"auth.invitation.introAdmin\": \"You will join as an administrator.\",\n \"auth.invitation.action\": \"Accept invitation\",\n \"auth.invitation.expiry\": \"This invitation works for {duration} and can be used once.\",\n \"auth.invitation.ignore\": \"If you were not expecting this invitation, you can ignore this email.\",\n \"duration.minutes\": \"{count} minutes\",\n \"duration.hours\": \"{count} hours\",\n \"duration.days\": \"{count} days\",\n} as const;\n\nexport type EmailMessageKey = keyof typeof emailMessages;\n\n/** A complete message catalogue (one locale). */\nexport type EmailMessages = Record<EmailMessageKey, string>;\n\n/** Fills `{name}` placeholders; unknown placeholders are left as they are. */\nexport function format(template: string, values: Record<string, string>): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, name: string) => values[name] ?? match);\n}\n","import type { AuthEmailTemplates, AuthLinkEmailInput, InvitationEmailInput, RenderedEmail } from \"@awsmbiz/inbis-core\";\nimport { renderBrandedEmail } from \"./layout.js\";\nimport { emailMessages, format, type EmailMessages } from \"./messages.js\";\n\ntype LinkEmail = \"verifyEmail\" | \"resetPassword\";\n\nfunction linkEmail(messages: EmailMessages, kind: LinkEmail, input: AuthLinkEmailInput): RenderedEmail {\n const values = { appName: input.brand.name, minutes: String(input.expiresInMinutes), name: input.recipientName.trim() };\n const m = (key: keyof EmailMessages) => format(messages[key], values);\n return renderBrandedEmail({\n brand: input.brand,\n subject: m(`auth.${kind}.subject`),\n greeting: values.name === \"\" ? m(\"email.greetingNoName\") : m(\"email.greeting\"),\n heading: m(`auth.${kind}.heading`),\n intro: [m(`auth.${kind}.intro`)],\n action: { label: m(`auth.${kind}.action`), url: input.url },\n linkFallback: m(\"email.linkFallback\"),\n outro: [m(`auth.${kind}.expiry`), m(`auth.${kind}.ignore`)],\n signOff: m(\"email.signOff\"),\n });\n}\n\n/** A lifetime in whole days, else whole hours, else minutes. */\nfunction duration(messages: EmailMessages, minutes: number): string {\n if (minutes % 1440 === 0) return format(messages[\"duration.days\"], { count: String(minutes / 1440) });\n if (minutes % 60 === 0) return format(messages[\"duration.hours\"], { count: String(minutes / 60) });\n return format(messages[\"duration.minutes\"], { count: String(minutes) });\n}\n\nfunction invitationEmail(messages: EmailMessages, input: InvitationEmailInput): RenderedEmail {\n const inviter = input.inviterName.trim();\n const name = input.recipientName.trim();\n const values = { appName: input.brand.name, inviter, name, duration: duration(messages, input.expiresInMinutes) };\n const m = (key: keyof EmailMessages) => format(messages[key], values);\n return renderBrandedEmail({\n brand: input.brand,\n subject: inviter === \"\" ? m(\"auth.invitation.subjectNoInviter\") : m(\"auth.invitation.subject\"),\n greeting: name === \"\" ? m(\"email.greetingNoName\") : m(\"email.greeting\"),\n heading: m(\"auth.invitation.heading\"),\n intro: [\n inviter === \"\" ? m(\"auth.invitation.introNoInviter\") : m(\"auth.invitation.intro\"),\n ...(input.role === \"admin\" ? [m(\"auth.invitation.introAdmin\")] : []),\n ],\n action: { label: m(\"auth.invitation.action\"), url: input.url },\n linkFallback: m(\"email.linkFallback\"),\n outro: [m(\"auth.invitation.expiry\"), m(\"auth.invitation.ignore\")],\n signOff: m(\"email.signOff\"),\n });\n}\n\n/** Auth email templates over a message catalogue (one locale). */\nexport function createAuthEmailTemplates(messages: EmailMessages = emailMessages): AuthEmailTemplates {\n return {\n verifyEmail: (input) => linkEmail(messages, \"verifyEmail\", input),\n resetPassword: (input) => linkEmail(messages, \"resetPassword\", input),\n invitation: (input) => invitationEmail(messages, input),\n };\n}\n\n/** The English auth email templates; pass to `createInbisAuth({ emailTemplates })`. */\nexport const authEmailTemplates: AuthEmailTemplates = createAuthEmailTemplates();\n","/**\n * The RPC surface of AWSM's shared communication service\n * (`biz-cloud-communication-service`, entrypoint `CommunicationService`) that\n * Inbis apps use, typed structurally so nothing depends on the private\n * service repo. Apps reach it over a service binding, conventionally named\n * `COMMUNICATION_SERVICE`, and always pass their platform-set `APP_ID`.\n */\n\n/** A mailbox on the SMTP path; the metered Cloudflare path takes plain strings only. */\nexport interface CommunicationServiceMailbox {\n name?: string;\n email: string;\n}\n\n/**\n * The subset of the service's email message (`@workermailer/smtp`\n * `EmailOptions`) that Inbis sends: one recipient, optional reply-to.\n */\nexport interface CommunicationServiceEmail {\n from: string | CommunicationServiceMailbox;\n to: string;\n reply?: string;\n subject: string;\n text?: string;\n html?: string;\n}\n\n/** Per-app SMTP credentials. Pass them from Worker secrets; they are never stored or logged. */\nexport interface SmtpCredentials {\n host: string;\n port: number;\n /** Implicit TLS (port 465). Omit for STARTTLS (port 587). */\n secure?: boolean;\n username: string;\n password: string;\n authType?: \"plain\" | \"login\" | \"cram-md5\" | (\"plain\" | \"login\" | \"cram-md5\")[];\n}\n\n/** One SMS to send through AWSM's Chatico account. */\nexport interface CommunicationServiceSms {\n /** DLT sender header, registered by the app. */\n smsHeader: string;\n /** DLT template ID for that header. */\n templateId: string;\n /** Numbers in the format the Chatico account expects. */\n recipients: string[];\n /** Must match the registered DLT template text. */\n message: string;\n}\n\nexport interface CommunicationServiceSmsResult {\n submissionId: string;\n messageIds: string[];\n smsCount: number;\n}\n\n/**\n * The service binding as Inbis uses it. A `Service<CommunicationService>`\n * stub satisfies it; a binding typed as a plain `Fetcher` (no service types)\n * needs a cast to this interface.\n */\nexport interface CommunicationServiceBinding {\n sendEmail(\n appId: string,\n message: CommunicationServiceEmail,\n smtp?: SmtpCredentials,\n ): Promise<unknown>;\n sendSms(appId: string, message: CommunicationServiceSms): Promise<CommunicationServiceSmsResult>;\n}\n\n/**\n * Types a service binding as the communication service. `wrangler types`\n * types a service binding without the service's source as a plain `Fetcher`;\n * at runtime its RPC methods are callable all the same.\n */\nexport function communicationServiceBinding(binding: Fetcher | CommunicationServiceBinding): CommunicationServiceBinding {\n return binding as CommunicationServiceBinding;\n}\n\n/** Throws at startup when the app ID is missing; it must be the platform-set `APP_ID`. */\nexport function requireAppId(appId: string | undefined): string {\n if (typeof appId !== \"string\" || appId.trim() === \"\") {\n throw new Error(\"The communication service needs the app's APP_ID (set by the platform); it is missing or blank.\");\n }\n return appId;\n}\n\n/** The message of an error that crossed the RPC boundary (custom classes do not survive it). */\nexport function errorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === \"string\") return error;\n return \"\";\n}\n","import { AppError, isPlaceholderEmail } from \"@awsmbiz/inbis-core\";\nimport { errorMessage } from \"./communication-service.js\";\n\n/** Throws `EMAIL_RECIPIENT_PLACEHOLDER` for a phone-only user's placeholder address (plan §5.5). */\nexport function refusePlaceholderRecipient(to: string): void {\n if (isPlaceholderEmail(to)) throw new EmailDeliveryError({ code: \"EMAIL_RECIPIENT_PLACEHOLDER\" });\n}\n\n/** Stable codes of {@link EmailDeliveryError}. */\nexport type EmailDeliveryErrorCode =\n /** The communication service has no app registered under this `APP_ID`. */\n | \"EMAIL_APP_NOT_REGISTERED\"\n /** The app's email channel is suspended in the communication service. */\n | \"EMAIL_APP_SUSPENDED\"\n /** The app has used its monthly email quota; sending works again once it is reset or raised. */\n | \"EMAIL_QUOTA_EXCEEDED\"\n /** The metered (Cloudflare) path cannot send this message; it needs the SMTP path. */\n | \"EMAIL_UNSUPPORTED_FIELD\"\n /** The sending address/domain is not onboarded or verified (`E_SENDER_NOT_VERIFIED`). */\n | \"EMAIL_SENDER_NOT_VERIFIED\"\n /** The recipient is not allowed (`E_RECIPIENT_NOT_ALLOWED`). */\n | \"EMAIL_RECIPIENT_NOT_ALLOWED\"\n /** Short-term rate limit hit (`E_RATE_LIMIT_EXCEEDED`); the service retries it. */\n | \"EMAIL_RATE_LIMITED\"\n /** The sending account's daily quota is used up (`E_DAILY_LIMIT_EXCEEDED`); the service retries it. */\n | \"EMAIL_DAILY_LIMIT_EXCEEDED\"\n /** No `from` address on the message or the sender. */\n | \"EMAIL_SENDER_MISSING\"\n /** The recipient is a phone-only user's placeholder (`.invalid`) address; nothing is sent. */\n | \"EMAIL_RECIPIENT_PLACEHOLDER\"\n /** Anything else, including unknown service or provider errors. */\n | \"EMAIL_DELIVERY_FAILED\";\n\nexport interface EmailDeliveryErrorOptions {\n code: EmailDeliveryErrorCode;\n /** The provider's own code (e.g. `E_RATE_LIMIT_EXCEEDED`), when the service error carried one. */\n providerCode?: string;\n cause?: unknown;\n}\n\nconst DETAILS: Record<EmailDeliveryErrorCode, { message: string; retryable: boolean; status: number }> = {\n EMAIL_APP_NOT_REGISTERED: { message: \"Email is not set up for this app.\", retryable: false, status: 500 },\n EMAIL_APP_SUSPENDED: { message: \"Email sending is suspended for this app.\", retryable: false, status: 503 },\n EMAIL_QUOTA_EXCEEDED: { message: \"The monthly email limit is reached.\", retryable: true, status: 503 },\n EMAIL_UNSUPPORTED_FIELD: { message: \"This email cannot be sent as configured.\", retryable: false, status: 500 },\n EMAIL_SENDER_NOT_VERIFIED: { message: \"The email sender is not verified.\", retryable: false, status: 502 },\n EMAIL_RECIPIENT_NOT_ALLOWED: { message: \"The email recipient is not allowed.\", retryable: false, status: 502 },\n EMAIL_RATE_LIMITED: { message: \"Email sending is rate limited. Try again later.\", retryable: true, status: 503 },\n EMAIL_DAILY_LIMIT_EXCEEDED: { message: \"The daily email limit is reached.\", retryable: true, status: 503 },\n EMAIL_SENDER_MISSING: { message: \"No email sender address is configured.\", retryable: false, status: 500 },\n EMAIL_RECIPIENT_PLACEHOLDER: {\n message: \"This account has no email address to send to.\",\n retryable: false,\n status: 400,\n },\n EMAIL_DELIVERY_FAILED: { message: \"The email could not be sent.\", retryable: false, status: 502 },\n};\n\n/**\n * An email that was not accepted for delivery. Never swallowed: adapters throw it.\n * The message is caller-safe (no addresses or content); the service's error\n * is kept as `cause` for logs.\n */\nexport class EmailDeliveryError extends AppError {\n declare readonly code: EmailDeliveryErrorCode;\n readonly providerCode: string | undefined;\n /** True when sending the same message later may succeed (rate, daily or monthly limits). */\n readonly retryable: boolean;\n\n constructor(options: EmailDeliveryErrorOptions) {\n const details = DETAILS[options.code];\n super({ code: options.code, message: details.message, status: details.status, cause: options.cause });\n this.name = \"EmailDeliveryError\";\n this.providerCode = options.providerCode;\n this.retryable = details.retryable;\n }\n}\n\n/** Stable codes of {@link SmsDeliveryError}. */\nexport type SmsDeliveryErrorCode =\n /** The message has no template, or its template key is not configured. */\n | \"SMS_TEMPLATE_NOT_CONFIGURED\"\n /** The template text needs a variable the message does not carry. */\n | \"SMS_TEMPLATE_VARIABLE_MISSING\"\n /** The recipient is not an E.164 number. */\n | \"SMS_RECIPIENT_INVALID\"\n /** The communication service has no app registered under this `APP_ID`. */\n | \"SMS_APP_NOT_REGISTERED\"\n /** The app's SMS channel is suspended in the communication service. */\n | \"SMS_APP_SUSPENDED\"\n /** The app has used its monthly SMS quota (a quota of 0 means SMS is off). */\n | \"SMS_QUOTA_EXCEEDED\"\n /** The service refused the message as empty or without recipients (`InvalidSmsError`). */\n | \"SMS_INVALID\"\n /** Chatico rejected the text as not matching the DLT template (HTTP 409). */\n | \"SMS_TEMPLATE_MISMATCH\"\n /** Chatico failed in a way the service retries on its own (5xx, 408, 402, 413). */\n | \"SMS_PROVIDER_UNAVAILABLE\"\n /** Anything else: another Chatico rejection, or an unknown service error. */\n | \"SMS_DELIVERY_FAILED\";\n\nconst SMS_DETAILS: Record<SmsDeliveryErrorCode, { message: string; retryable: boolean; status: number }> = {\n SMS_TEMPLATE_NOT_CONFIGURED: { message: \"No SMS template is configured for this message.\", retryable: false, status: 500 },\n SMS_TEMPLATE_VARIABLE_MISSING: { message: \"The SMS template needs a value the message lacks.\", retryable: false, status: 500 },\n SMS_RECIPIENT_INVALID: { message: \"The SMS recipient is not a valid phone number.\", retryable: false, status: 400 },\n SMS_APP_NOT_REGISTERED: { message: \"SMS is not set up for this app.\", retryable: false, status: 500 },\n SMS_APP_SUSPENDED: { message: \"SMS sending is suspended for this app.\", retryable: false, status: 503 },\n SMS_QUOTA_EXCEEDED: { message: \"The monthly SMS limit is reached.\", retryable: true, status: 503 },\n SMS_INVALID: { message: \"The SMS could not be sent as configured.\", retryable: false, status: 500 },\n SMS_TEMPLATE_MISMATCH: { message: \"The SMS does not match its registered template.\", retryable: false, status: 500 },\n SMS_PROVIDER_UNAVAILABLE: { message: \"The SMS provider is unavailable. Try again later.\", retryable: true, status: 503 },\n SMS_DELIVERY_FAILED: { message: \"The SMS could not be sent.\", retryable: false, status: 502 },\n};\n\nexport interface SmsDeliveryErrorOptions {\n code: SmsDeliveryErrorCode;\n /** Chatico's HTTP status, when the service relayed one. */\n providerStatus?: number;\n cause?: unknown;\n}\n\n/**\n * An SMS that was not accepted for delivery, or that could not be sent as configured.\n * Never swallowed: adapters throw it. The message is caller-safe (no numbers or\n * content); the service's error is kept as `cause` for logs.\n */\nexport class SmsDeliveryError extends AppError {\n declare readonly code: SmsDeliveryErrorCode;\n readonly providerStatus: number | undefined;\n /** True when sending the same message later may succeed. */\n readonly retryable: boolean;\n\n constructor(options: SmsDeliveryErrorOptions) {\n const details = SMS_DETAILS[options.code];\n super({ code: options.code, message: details.message, status: details.status, cause: options.cause });\n this.name = \"SmsDeliveryError\";\n this.providerStatus = options.providerStatus;\n this.retryable = details.retryable;\n }\n}\n\n/** Service errors common to email and SMS, matched on `error.message` (they cross RPC as plain `Error`s). */\ntype ServiceErrorKind = \"APP_NOT_REGISTERED\" | \"APP_SUSPENDED\" | \"QUOTA_EXCEEDED\";\n\nfunction serviceErrorKind(message: string): ServiceErrorKind | undefined {\n if (message.startsWith(\"No app registered with app_id\")) return \"APP_NOT_REGISTERED\";\n if (/^App \".*\" is suspended$/.test(message)) return \"APP_SUSPENDED\";\n if (/^App \".*\" has exceeded its monthly quota$/.test(message)) return \"QUOTA_EXCEEDED\";\n return undefined;\n}\n\nconst PROVIDER_CODES: Record<string, EmailDeliveryErrorCode> = {\n E_SENDER_NOT_VERIFIED: \"EMAIL_SENDER_NOT_VERIFIED\",\n E_RECIPIENT_NOT_ALLOWED: \"EMAIL_RECIPIENT_NOT_ALLOWED\",\n E_RATE_LIMIT_EXCEEDED: \"EMAIL_RATE_LIMITED\",\n E_DAILY_LIMIT_EXCEEDED: \"EMAIL_DAILY_LIMIT_EXCEEDED\",\n};\n\n/** Maps an error thrown by the communication service's `sendEmail` to an {@link EmailDeliveryError}. */\nexport function fromServiceEmailError(error: unknown): EmailDeliveryError {\n const message = errorMessage(error);\n const kind = serviceErrorKind(message);\n if (kind !== undefined) return new EmailDeliveryError({ code: `EMAIL_${kind}`, cause: error });\n if (message.startsWith(\"The Cloudflare send path does not support\")) {\n return new EmailDeliveryError({ code: \"EMAIL_UNSUPPORTED_FIELD\", cause: error });\n }\n const providerCode = /\\bE_[A-Z_]+\\b/.exec(message)?.[0];\n const code = (providerCode === undefined ? undefined : PROVIDER_CODES[providerCode]) ?? \"EMAIL_DELIVERY_FAILED\";\n return new EmailDeliveryError({ code, ...(providerCode === undefined ? {} : { providerCode }), cause: error });\n}\n\n/** Chatico statuses the service records for its own automatic retry (besides 5xx). */\nconst RETRIED_CHATICO_STATUSES = new Set([402, 408, 413]);\n\n/** Maps an error thrown by the communication service's `sendSms` to an {@link SmsDeliveryError}. */\nexport function fromServiceSmsError(error: unknown): SmsDeliveryError {\n const message = errorMessage(error);\n const kind = serviceErrorKind(message);\n if (kind !== undefined) return new SmsDeliveryError({ code: `SMS_${kind}`, cause: error });\n if (message.startsWith(\"sendSms needs at least one recipient\")) {\n return new SmsDeliveryError({ code: \"SMS_INVALID\", cause: error });\n }\n const chatico = /^Chatico send failed \\((\\d+)\\)/.exec(message);\n if (chatico?.[1] !== undefined) {\n const providerStatus = Number(chatico[1]);\n const code =\n providerStatus === 409\n ? \"SMS_TEMPLATE_MISMATCH\"\n : providerStatus >= 500 || RETRIED_CHATICO_STATUSES.has(providerStatus)\n ? \"SMS_PROVIDER_UNAVAILABLE\"\n : \"SMS_DELIVERY_FAILED\";\n return new SmsDeliveryError({ code, providerStatus, cause: error });\n }\n return new SmsDeliveryError({ code: \"SMS_DELIVERY_FAILED\", cause: error });\n}\n","import type { DeliveryResult, EmailSender, EmailMessage as InbisEmailMessage } from \"@awsmbiz/inbis-core\";\nimport {\n type CommunicationServiceBinding,\n type CommunicationServiceEmail,\n type CommunicationServiceMailbox,\n requireAppId,\n type SmtpCredentials,\n} from \"./communication-service.js\";\nimport { EmailDeliveryError, fromServiceEmailError, refusePlaceholderRecipient } from \"./errors.js\";\n\nexport interface CommunicationServiceEmailSenderOptions {\n /** The `COMMUNICATION_SERVICE` service binding. */\n binding: CommunicationServiceBinding;\n /** The app's platform-set `APP_ID` (`env.APP_ID`). Never user input. */\n appId: string;\n /** Default sender, e.g. `Billing <no-reply@billing.example.com>`. A message's own `from` wins. */\n from?: string;\n /** Default reply-to address. A message's own `replyTo` wins. */\n replyTo?: string;\n /**\n * The app's own SMTP server (from Worker secrets). When set, every email takes\n * the unmetered SMTP path; otherwise AWSM's metered Cloudflare path.\n */\n smtp?: SmtpCredentials;\n}\n\n/** Splits `Name <address>` into its parts; a bare address has no name. */\nfunction parseMailbox(value: string): CommunicationServiceMailbox {\n const match = /^\\s*(.*?)\\s*<([^<>]+)>\\s*$/.exec(value);\n if (match?.[2] === undefined) return { email: value.trim() };\n const name = match[1]?.replace(/^\"(.*)\"$/, \"$1\") ?? \"\";\n return name === \"\" ? { email: match[2].trim() } : { name, email: match[2].trim() };\n}\n\n/**\n * {@link EmailSender} over AWSM's shared communication service. Without `smtp`\n * it uses the metered Cloudflare path, which takes only plain-string addresses:\n * a display name in `from` is dropped there (kept on the SMTP path). Quota,\n * suspension and retries belong to the service. Every failure is thrown as an\n * {@link EmailDeliveryError}; nothing is swallowed or logged.\n */\nexport function createCommunicationServiceEmailSender(options: CommunicationServiceEmailSenderOptions): EmailSender {\n const appId = requireAppId(options.appId);\n const { binding, smtp } = options;\n\n return {\n async send(message: InbisEmailMessage): Promise<DeliveryResult> {\n refusePlaceholderRecipient(message.to);\n const from = message.from ?? options.from;\n if (from === undefined || from.trim() === \"\") {\n throw new EmailDeliveryError({ code: \"EMAIL_SENDER_MISSING\" });\n }\n const sender = parseMailbox(from);\n const replyTo = message.replyTo ?? options.replyTo;\n const email: CommunicationServiceEmail = {\n from: smtp === undefined ? sender.email : sender,\n to: parseMailbox(message.to).email,\n ...(replyTo === undefined ? {} : { reply: parseMailbox(replyTo).email }),\n subject: message.subject,\n text: message.text,\n ...(message.html === undefined ? {} : { html: message.html }),\n };\n\n let result: unknown;\n try {\n result = smtp === undefined ? await binding.sendEmail(appId, email) : await binding.sendEmail(appId, email, smtp);\n } catch (error) {\n throw fromServiceEmailError(error);\n }\n const messageId =\n typeof result === \"object\" && result !== null && \"messageId\" in result ? result.messageId : undefined;\n return typeof messageId === \"string\" && messageId !== \"\" ? { messageId } : {};\n },\n };\n}\n","import type { DeliveryResult, SmsMessage, SmsSender } from \"@awsmbiz/inbis-core\";\nimport { type CommunicationServiceBinding, requireAppId } from \"./communication-service.js\";\nimport { fromServiceSmsError, SmsDeliveryError } from \"./errors.js\";\n\n/**\n * One SMS type (e.g. `auth.phone-otp`) as registered on DLT. The rendered text\n * must match the registered template exactly, or Chatico rejects it.\n */\nexport interface SmsTemplateConfig {\n /** DLT sender header for this type. Defaults to the sender's `smsHeader`. */\n smsHeader?: string;\n /** The DLT template ID. */\n templateId: string;\n /**\n * The registered template text with `{name}` placeholders for the message's\n * variables, e.g. `\"{code} is your {appName} sign-in code. Valid for {minutes} minutes.\"`.\n * Each placeholder fills one `{#var#}` slot of the DLT template.\n */\n text: string;\n}\n\nexport interface CommunicationServiceSmsSenderOptions {\n /** The `COMMUNICATION_SERVICE` service binding. */\n binding: CommunicationServiceBinding;\n /** The app's platform-set `APP_ID` (`env.APP_ID`). Never user input. */\n appId: string;\n /** Default DLT sender header for templates that do not set their own. */\n smsHeader?: string;\n /** Message template key (`SmsMessage.template.id`) to its DLT configuration. */\n templates: Record<string, SmsTemplateConfig>;\n /**\n * Template keys that must be configured, checked at startup, e.g.\n * `requiredAuthSmsTemplates(config)` from `@awsmbiz/inbis-auth`.\n */\n requiredTemplates?: readonly string[];\n /**\n * Converts an E.164 number (`+919876543210`) to the format the service's\n * Chatico account expects. Default: {@link e164DigitsRecipient} (`919876543210`).\n */\n formatRecipient?: (e164: string) => string;\n}\n\nconst E164 = /^\\+[1-9]\\d{6,14}$/;\n\n/** `+919876543210` → `919876543210` (country code first, no `+`). The default. */\nexport function e164DigitsRecipient(e164: string): string {\n return e164.slice(1);\n}\n\n/** `+919876543210` → `9876543210` (Indian numbers without the country code); other numbers as {@link e164DigitsRecipient}. */\nexport function indianTenDigitRecipient(e164: string): string {\n return /^\\+91\\d{10}$/.test(e164) ? e164.slice(3) : e164.slice(1);\n}\n\nfunction blank(value: string | undefined): boolean {\n return value === undefined || value.trim() === \"\";\n}\n\n/** Throws at startup on any missing or incomplete template, naming it (never any content). */\nfunction validateTemplates(options: CommunicationServiceSmsSenderOptions): void {\n for (const key of options.requiredTemplates ?? []) {\n if (options.templates[key] === undefined) throw new Error(`SMS template \"${key}\" is not configured.`);\n }\n for (const [key, template] of Object.entries(options.templates)) {\n if (blank(template.smsHeader ?? options.smsHeader)) throw new Error(`SMS template \"${key}\" has no DLT sender header.`);\n if (blank(template.templateId)) throw new Error(`SMS template \"${key}\" has no DLT template ID.`);\n if (blank(template.text)) throw new Error(`SMS template \"${key}\" has no text.`);\n }\n}\n\nfunction render(text: string, variables: Record<string, string>): string {\n return text.replace(/\\{(\\w+)\\}/g, (_, name: string) => {\n const value = variables[name];\n if (value === undefined) throw new SmsDeliveryError({ code: \"SMS_TEMPLATE_VARIABLE_MISSING\" });\n return value;\n });\n}\n\n/**\n * {@link SmsSender} over AWSM's shared communication service (Chatico, AWSM as\n * the DLT entity). The message's `template.id` picks the app's DLT\n * configuration; the text sent is that template's text with the message's\n * variables filled in (the message's own `text` is not sent). Quota,\n * suspension and retries belong to the service. Every failure is thrown as an\n * {@link SmsDeliveryError}; nothing is swallowed or logged.\n */\nexport function createCommunicationServiceSmsSender(options: CommunicationServiceSmsSenderOptions): SmsSender {\n const appId = requireAppId(options.appId);\n validateTemplates(options);\n const formatRecipient = options.formatRecipient ?? e164DigitsRecipient;\n const { binding } = options;\n\n return {\n async send(message: SmsMessage): Promise<DeliveryResult> {\n const template = message.template === undefined ? undefined : options.templates[message.template.id];\n if (template === undefined || message.template === undefined) {\n throw new SmsDeliveryError({ code: \"SMS_TEMPLATE_NOT_CONFIGURED\" });\n }\n if (!E164.test(message.to)) throw new SmsDeliveryError({ code: \"SMS_RECIPIENT_INVALID\" });\n const text = render(template.text, message.template.variables);\n\n let result;\n try {\n result = await binding.sendSms(appId, {\n smsHeader: template.smsHeader ?? options.smsHeader ?? \"\",\n templateId: template.templateId,\n recipients: [formatRecipient(message.to)],\n message: text,\n });\n } catch (error) {\n throw fromServiceSmsError(error);\n }\n const messageId = result.messageIds[0] ?? result.submissionId;\n return messageId === \"\" ? {} : { messageId };\n },\n };\n}\n","import { isPlaceholderEmail, type DeliveryResult, type EmailSender, type EmailMessage as InbisEmailMessage } from \"@awsmbiz/inbis-core\";\nimport { EmailDeliveryError } from \"./errors.js\";\n\n/** An email as the log adapter recorded it. */\nexport interface LoggedEmail {\n messageId: string;\n /** ISO timestamp. */\n sentAt: string;\n to: string;\n from: string | undefined;\n replyTo: string | undefined;\n subject: string;\n text: string;\n html: string | undefined;\n /** Every http(s) link in the text and HTML parts, in order, each once. */\n links: string[];\n}\n\nexport interface LogEmailSenderOptions {\n /** Where the rendered email goes. Default `console.info`. */\n print?: (text: string) => void;\n /** Default sender address, as a real adapter would use. */\n from?: string;\n /** How many recent emails {@link LogEmailSender.recent} keeps. Default 50. */\n keep?: number;\n}\n\nexport interface LogEmailSender extends EmailSender {\n /** The most recent emails, newest last (for a local outbox viewer). */\n recent(): readonly LoggedEmail[];\n}\n\nconst TEXT_LINK = /https?:\\/\\/[^\\s<>\"')\\]]+/g;\nconst HREF = /href\\s*=\\s*\"([^\"]+)\"|href\\s*=\\s*'([^']+)'/gi;\n\nfunction decodeEntities(value: string): string {\n return value\n .replaceAll(\""\", '\"')\n .replaceAll(\"'\", \"'\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll(\"&\", \"&\");\n}\n\nfunction linksOf(text: string, html: string | undefined): string[] {\n const links = [...text.matchAll(TEXT_LINK)].map((match) => match[0]);\n for (const match of html?.matchAll(HREF) ?? []) {\n const href = decodeEntities(match[1] ?? match[2] ?? \"\");\n if (/^https?:\\/\\//i.test(href)) links.push(href);\n }\n return [...new Set(links)];\n}\n\nfunction render(email: LoggedEmail): string {\n return [\n `[email ${email.messageId}]`,\n `From: ${email.from ?? \"(none)\"}`,\n `To: ${email.to}`,\n ...(email.replyTo === undefined ? [] : [`Reply-To: ${email.replyTo}`]),\n `Subject: ${email.subject}`,\n \"\",\n email.text,\n \"\",\n email.html === undefined ? \"(no HTML part)\" : `(HTML part: ${email.html.length} characters)`,\n ...(email.links.length === 0 ? [] : [\"Links:\", ...email.links.map((link) => ` ${link}`)]),\n ].join(\"\\n\");\n}\n\n/**\n * Development {@link EmailSender} that prints every email in full (addresses,\n * subject, text body and links) instead of delivering it, and keeps the recent\n * ones for an outbox viewer. Nothing is silently discarded.\n *\n * Never use it where real users expect email: it delivers nothing, and its\n * output (and outbox) contains live verification and reset links.\n */\nexport function createLogEmailSender(options: LogEmailSenderOptions = {}): LogEmailSender {\n const print = options.print ?? ((text: string) => {\n console.info(text);\n });\n const keep = options.keep ?? 50;\n const recent: LoggedEmail[] = [];\n let sent = 0;\n\n return {\n send(message: InbisEmailMessage): Promise<DeliveryResult> {\n if (isPlaceholderEmail(message.to)) {\n return Promise.reject(new EmailDeliveryError({ code: \"EMAIL_RECIPIENT_PLACEHOLDER\" }));\n }\n sent += 1;\n const email: LoggedEmail = {\n messageId: `log-${Date.now().toString(36)}-${sent}`,\n sentAt: new Date().toISOString(),\n to: message.to,\n from: message.from ?? options.from,\n replyTo: message.replyTo,\n subject: message.subject,\n text: message.text,\n html: message.html,\n links: linksOf(message.text, message.html),\n };\n recent.push(email);\n if (recent.length > keep) recent.splice(0, recent.length - keep);\n print(render(email));\n return Promise.resolve({ messageId: email.messageId });\n },\n recent: () => recent,\n };\n}\n","import type { DeliveryResult, SmsMessage, SmsSender } from \"@awsmbiz/inbis-core\";\n\n/** An SMS as the log adapter recorded it. */\nexport interface LoggedSms {\n messageId: string;\n /** ISO timestamp. */\n sentAt: string;\n /** E.164 recipient. */\n to: string;\n text: string;\n /** The message's template key (e.g. `auth.phone-otp`), when it has one. */\n templateId: string | undefined;\n}\n\nexport interface LogSmsSenderOptions {\n /** Where the message goes. Default `console.info`. */\n print?: (text: string) => void;\n /** How many recent messages {@link LogSmsSender.recent} keeps. Default 50. */\n keep?: number;\n}\n\nexport interface LogSmsSender extends SmsSender {\n /** The most recent messages, newest last (for a local outbox viewer). */\n recent(): readonly LoggedSms[];\n}\n\nfunction render(sms: LoggedSms): string {\n return [\n `[sms ${sms.messageId}]`,\n `To: ${sms.to}`,\n ...(sms.templateId === undefined ? [] : [`Template: ${sms.templateId}`]),\n \"\",\n sms.text,\n ].join(\"\\n\");\n}\n\n/**\n * Development {@link SmsSender} that prints every message in full instead of\n * delivering it, and keeps the recent ones for an outbox viewer. Nothing is\n * silently discarded.\n *\n * Never use it where real users expect SMS: it delivers nothing, and its\n * output (and outbox) contains live sign-in codes.\n */\nexport function createLogSmsSender(options: LogSmsSenderOptions = {}): LogSmsSender {\n const print =\n options.print ??\n ((text: string) => {\n console.info(text);\n });\n const keep = options.keep ?? 50;\n const recent: LoggedSms[] = [];\n let sent = 0;\n\n return {\n send(message: SmsMessage): Promise<DeliveryResult> {\n sent += 1;\n const sms: LoggedSms = {\n messageId: `log-sms-${Date.now().toString(36)}-${sent}`,\n sentAt: new Date().toISOString(),\n to: message.to,\n text: message.text,\n templateId: message.template?.id,\n };\n recent.push(sms);\n if (recent.length > keep) recent.splice(0, recent.length - keep);\n print(render(sms));\n return Promise.resolve({ messageId: sms.messageId });\n },\n recent: () => recent,\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awsmbiz/inbis-notifications",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Email and SMS delivery for Inbis apps through the AWSM communication service, log adapters, typed delivery errors and branded templates.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/awsmbiz/inbis-platform.git",
|
|
9
|
+
"directory": "packages/notifications"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"registry": "https://registry.npmjs.org/",
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@awsmbiz/inbis-core": "0.2.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
32
|
+
"@cloudflare/vitest-plugin": "1.4.11",
|
|
33
|
+
"@cloudflare/workers-types": "5.20261030.1",
|
|
34
|
+
"@awsmbiz/inbis-eslint-config": "0.2.0",
|
|
35
|
+
"@awsmbiz/inbis-tsconfig": "0.2.0",
|
|
36
|
+
"eslint": "10.14.0",
|
|
37
|
+
"publint": "0.3.25",
|
|
38
|
+
"tsdown": "0.23.4",
|
|
39
|
+
"typescript": "6.0.3",
|
|
40
|
+
"vitest": "5.0.5"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsdown",
|
|
44
|
+
"typecheck": "tsc",
|
|
45
|
+
"lint": "eslint . --max-warnings 0",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"check:exports": "publint --strict && attw --pack . --profile esm-only"
|
|
48
|
+
}
|
|
6
49
|
}
|