Skip to content

Feature/whatsapp#8

Closed
y-aithnini wants to merge 12 commits intodevelopfrom
feature/whatsapp
Closed

Feature/whatsapp#8
y-aithnini wants to merge 12 commits intodevelopfrom
feature/whatsapp

Conversation

@y-aithnini
Copy link
Contributor

Summary

  • What does this PR change?

Why

  • Why is this change needed?

Checklist

  • Added/updated tests (if behavior changed)
  • npm run lint passes
  • npm run typecheck passes
  • npm test passes
  • npm run build passes
  • Added a changeset (npx changeset) if this affects consumers

Notes

  • Anything reviewers should pay attention to?

Copilot AI review requested due to automatic review settings March 11, 2026 08:58
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces WhatsApp as a first-class notification channel and expands the package documentation/API surface to support WhatsApp delivery (including a Twilio-based sender and a mock sender), alongside extensive inline documentation updates across core and Nest integration.

Changes:

  • Add NotificationChannel.WHATSAPP plus Twilio and mock WhatsApp sender implementations, and export them via infra index barrels.
  • Expand NestJS integration docs and provider/module factory comments, and update README + add a template configuration guide.
  • Update core layer documentation/comments across ports, DTOs, errors, and service.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
src/nest/providers.ts Adds extensive provider-factory documentation; adjusts provider definitions for defaults/dynamic imports.
src/nest/module.ts Expands module documentation and refactors registerAsync() provider setup.
src/nest/index.ts Adds public API documentation and keeps Nest integration barrel exports.
src/infra/senders/whatsapp/twilio-whatsapp.sender.ts New Twilio-based WhatsApp sender (media + template support).
src/infra/senders/whatsapp/mock-whatsapp.sender.ts New mock WhatsApp sender for development/testing.
src/infra/senders/whatsapp/index.ts Barrel export for WhatsApp senders.
src/infra/senders/index.ts Exports WhatsApp senders from the infra senders index.
src/infra/senders/email/nodemailer.sender.ts Adds extensive documentation/comments to the Nodemailer sender.
src/index.ts Adds package-level entrypoint documentation; continues exporting core/infra/nest.
src/core/types.ts Adds WHATSAPP channel and expands inline documentation.
src/core/ports.ts Expands inline documentation for ports/interfaces.
src/core/notification.service.ts Adds extensive documentation/comments describing service behavior.
src/core/index.ts Adds documentation and clarifies export intent for the core layer.
src/core/errors.ts Expands error documentation and examples.
src/core/dtos.ts Expands DTO/schema documentation; keeps validation logic.
docs/TEMPLATE_CONFIGURATION.md Adds a comprehensive template configuration guide (provider vs global templates).
README.md Updates positioning/features and adds WhatsApp + template/content guidance sections.
Comments suppressed due to low confidence (2)

src/core/dtos.ts:156

  • CreateNotificationDtoSchema’s cross-field validation doesn’t include the new WHATSAPP channel. As a result, DTO validation can accept WhatsApp notifications without recipient.phone even though the docs/state that phone is required. Extend the refine logic to require recipient.phone for NotificationChannel.WHATSAPP (and consider aligning the earlier comment block too).
      // Cross-field validation: Ensure recipient has required contact info for channel
      // Email channel requires email address
      if (data.channel === NotificationChannel.EMAIL && !data.recipient.email) {
        return false;
      }
      // SMS channel requires phone number
      if (data.channel === NotificationChannel.SMS && !data.recipient.phone) {
        return false;
      }
      // Push channel requires device token
      if (data.channel === NotificationChannel.PUSH && !data.recipient.deviceToken) {

src/nest/providers.ts:33

  • Unused imports DateTimeProvider as _DateTimeProvider and UuidGenerator as _UuidGenerator will fail lint/typecheck (and are not needed since the implementation uses dynamic imports). Remove them.
import { NotificationService } from "../core/notification.service";
import { DateTimeProvider as _DateTimeProvider } from "../infra/providers/datetime.provider";
import { UuidGenerator as _UuidGenerator } from "../infra/providers/id-generator.provider";

Comment on lines +174 to 182
// Create a factory provider that creates NotificationKit providers
// once the module options are available
const providersFactory: Provider = {
provide: "NOTIFICATION_PROVIDERS",
useFactory: (moduleOptions: NotificationKitModuleOptions) => {
return createNotificationKitProviders(moduleOptions);
},
inject: [NOTIFICATION_KIT_OPTIONS],
inject: [NOTIFICATION_KIT_OPTIONS], // Wait for options to be resolved
};
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

registerAsync() currently registers a provider ("NOTIFICATION_PROVIDERS") whose value is an array of providers returned by createNotificationKitProviders(). Nest will not treat that returned array as providers, so NOTIFICATION_SERVICE (and related tokens) will not actually be registered in async mode. Refactor async registration to register the real providers themselves (e.g., create providers that inject: [NOTIFICATION_KIT_OPTIONS] and read from resolved options, or add an async-aware createNotificationKitProviders variant that builds providers around the options token).

Copilot uses AI. Check for mistakes.
Comment on lines 167 to 199
static registerAsync(options: NotificationKitModuleAsyncOptions): DynamicModule {
// Create provider that resolves module options asynchronously
const asyncOptionsProvider = this.createAsyncOptionsProvider(options);

// Create any additional async providers (useClass providers)
const asyncProviders = this.createAsyncProviders(options);

// We can't conditionally load controllers in async mode without the options
// So we'll need to always include them and they can handle being disabled internally
// Or we can create a factory provider that returns empty array
// Create a factory provider that creates NotificationKit providers
// once the module options are available
const providersFactory: Provider = {
provide: "NOTIFICATION_PROVIDERS",
useFactory: (moduleOptions: NotificationKitModuleOptions) => {
return createNotificationKitProviders(moduleOptions);
},
inject: [NOTIFICATION_KIT_OPTIONS],
inject: [NOTIFICATION_KIT_OPTIONS], // Wait for options to be resolved
};

// Combine all providers
const allProviders = [asyncOptionsProvider, ...asyncProviders, providersFactory];

// Export providers for injection
const exports = allProviders.map((p) =>
typeof p === "object" && "provide" in p ? p.provide : p,
);

return {
global: true,
module: NotificationKitModule,
imports: options.imports || [],
global: true, // Module is global
module: NotificationKitModule, // This module class
imports: options.imports || [], // Import dependencies (ConfigModule, etc.)
controllers: [], // Controllers disabled in async mode for simplicity
providers: allProviders,
exports,
providers: allProviders, // Async providers + factory
exports, // Make providers available for injection
};
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no test asserting that registerAsync() exposes NOTIFICATION_SERVICE / functional providers (the current tests only check NOTIFICATION_KIT_OPTIONS). Add a module test that moduleRef.get(NOTIFICATION_SERVICE) works in async mode to prevent regressions like the current provider-factory issue.

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +41
* Usage with NotificationKit:
* ```typescript
* NotificationKitModule.forRoot({
* senders: [whatsappSender],
* // ... other config
* });
* ```
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage example references NotificationKitModule.forRoot(...), but the module API is NotificationKitModule.register(...) / registerAsync(...). Update the example to match the actual public API.

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +114
```typescript
await notificationService.send({
channel: NotificationChannel.WHATSAPP,
recipient: { phone: "+14155551234" },
content: {
templateId: "orderShipped", // Maps to 'order_shipped_v1'
templateVars: {
orderNumber: "12345",
trackingUrl: "https://track.com/12345",
},
},
});
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc example is missing required fields according to the package’s types/DTOs: recipient.id is required, and content currently requires title and body (even when using templates). Update the example (or relax the DTO/types) so it reflects what actually validates and runs.

Copilot uses AI. Check for mistakes.
return {
success: true,
notificationId: _recipient.id,
providerMessageId: `mock-whatsapp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Math.random().toString(36).substr(...) uses the legacy substr() API. Use slice()/substring() instead to avoid deprecated APIs and potential lint failures.

Suggested change
providerMessageId: `mock-whatsapp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
providerMessageId: `mock-whatsapp-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,

Copilot uses AI. Check for mistakes.
error: errorCode ? `[${errorCode}] ${errorMessage}` : errorMessage,
metadata: {
errorCode,
rawError: error,
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On failure you return rawError: error in metadata. Twilio errors can include request/response details and other sensitive information; returning the full object can leak secrets/PII to callers or logs. Prefer returning only a sanitized subset (e.g., code, message, moreInfo, status) and omit the raw error object.

Suggested change
rawError: error,
message: errorMessage,
status: error?.status,
moreInfo: error?.moreInfo,

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +35
* Usage with NotificationKit:
* ```typescript
* NotificationKitModule.forRoot({
* senders: [mockSender],
* // ... other config
* });
* ```
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage example references NotificationKitModule.forRoot(...), but the module API is NotificationKitModule.register(...) / registerAsync(...). Update the example to match the actual public API.

Copilot uses AI. Check for mistakes.
Comment on lines +135 to +136
import { NotificationKitModule } from '@ciscode/notification-kit';
import { SimpleTemplateEngine } from '@ciscode/notification-kit/infra';
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Examples import from @ciscode/notification-kit/infra, but package.json currently exports only the root entry (.). These subpath imports will fail for consumers unless you add subpath exports. Prefer importing infrastructure exports from @ciscode/notification-kit (or update exports).

Suggested change
import { NotificationKitModule } from '@ciscode/notification-kit';
import { SimpleTemplateEngine } from '@ciscode/notification-kit/infra';
import { NotificationKitModule, SimpleTemplateEngine } from '@ciscode/notification-kit';

Copilot uses AI. Check for mistakes.
* HTTP Status: 503 Service Unavailable
*
* Common causes:
* - Forgot to register sender: NotificationKitModule.forRoot({ senders: [emailSender] })
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation mentions NotificationKitModule.forRoot(...), but the module API in this package is NotificationKitModule.register(...) / registerAsync(...). Update this reference to the correct method name.

Suggested change
* - Forgot to register sender: NotificationKitModule.forRoot({ senders: [emailSender] })
* - Forgot to register sender: NotificationKitModule.register({ senders: [emailSender] })

Copilot uses AI. Check for mistakes.
Comment on lines +2 to +6
* NotificationKit NestJS Integration - Public API
*
* This file exports all public NestJS integration components for NotificationKit.
* Import from '@ciscode/notification-kit/nest' to use NotificationKit with NestJS.
*
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file’s JSDoc suggests consumers should import from @ciscode/notification-kit/nest, but package.json exports only the root entry (exports: {".": ...}), so that subpath import will fail in Node/bundlers. Update the docs/examples to import from @ciscode/notification-kit (or add the /nest subpath export if that’s intended).

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot

See analysis details on SonarQube Cloud

@y-aithnini y-aithnini closed this Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants