@nathapp/nestjs-notification 2.1.4 → 3.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CLAUDE.md +21 -0
  2. package/UPGRADE-PLAN-PHASE7-8.md +307 -0
  3. package/UPGRADE-PLAN-PHASE9.md +361 -0
  4. package/UPGRADE-PLAN.md +425 -0
  5. package/dist/email/email.interface.d.ts +1 -1
  6. package/dist/email/email.provider.d.ts +2 -2
  7. package/dist/email/email.provider.js +7 -5
  8. package/dist/email/email.provider.js.map +1 -1
  9. package/dist/email/index.d.ts +1 -3
  10. package/dist/email/index.js +1 -6
  11. package/dist/email/index.js.map +1 -1
  12. package/dist/index.d.ts +4 -3
  13. package/dist/index.js +22 -3
  14. package/dist/index.js.map +1 -1
  15. package/dist/interface/index.d.ts +3 -3
  16. package/dist/interface/message.interface.d.ts +11 -4
  17. package/dist/interface/payload.interface.d.ts +71 -0
  18. package/dist/{push-notification/push-notification-module.interface.js → interface/payload.interface.js} +1 -2
  19. package/dist/interface/payload.interface.js.map +1 -0
  20. package/dist/interface/result.interface.d.ts +2 -1
  21. package/dist/notification/errors.d.ts +3 -0
  22. package/dist/notification/errors.js +11 -0
  23. package/dist/notification/errors.js.map +1 -0
  24. package/dist/notification/index.d.ts +6 -2
  25. package/dist/notification/index.js +7 -1
  26. package/dist/notification/index.js.map +1 -1
  27. package/dist/notification/notification-provider.factory.d.ts +5 -0
  28. package/dist/notification/notification-provider.factory.js +33 -0
  29. package/dist/notification/notification-provider.factory.js.map +1 -0
  30. package/dist/notification/notification.interceptor.d.ts +18 -0
  31. package/dist/notification/notification.interceptor.js +5 -0
  32. package/dist/notification/notification.interceptor.js.map +1 -0
  33. package/dist/notification/notification.module.d.ts +29 -19
  34. package/dist/notification/notification.module.js +338 -65
  35. package/dist/notification/notification.module.js.map +1 -1
  36. package/dist/notification/notification.service.d.ts +18 -2
  37. package/dist/notification/notification.service.js +277 -16
  38. package/dist/notification/notification.service.js.map +1 -1
  39. package/dist/notification/provider-config.d.ts +17 -0
  40. package/dist/{interface/payload.inteface.js → notification/provider-config.js} +1 -1
  41. package/dist/notification/provider-config.js.map +1 -0
  42. package/dist/push-notification/fcm-push-notification.provider.d.ts +3 -0
  43. package/dist/push-notification/fcm-push-notification.provider.js +17 -2
  44. package/dist/push-notification/fcm-push-notification.provider.js.map +1 -1
  45. package/dist/push-notification/firebase.interface.d.ts +1 -0
  46. package/dist/push-notification/index.d.ts +1 -4
  47. package/dist/push-notification/index.js +1 -5
  48. package/dist/push-notification/index.js.map +1 -1
  49. package/dist/push-notification/push-notification-provider.manager.d.ts +8 -3
  50. package/dist/push-notification/push-notification-provider.manager.js +12 -3
  51. package/dist/push-notification/push-notification-provider.manager.js.map +1 -1
  52. package/dist/push-notification/push-notification.provider.d.ts +1 -0
  53. package/dist/push-notification/push-notification.provider.js.map +1 -1
  54. package/dist/push-notification/push-notification.service.js +3 -2
  55. package/dist/push-notification/push-notification.service.js.map +1 -1
  56. package/dist/push-notification/user-token.store.js +7 -7
  57. package/dist/push-notification/user-token.store.js.map +1 -1
  58. package/dist/sms/constant.d.ts +1 -0
  59. package/dist/sms/constant.js +5 -0
  60. package/dist/sms/constant.js.map +1 -0
  61. package/dist/sms/index.d.ts +3 -2
  62. package/dist/sms/index.js +6 -2
  63. package/dist/sms/index.js.map +1 -1
  64. package/dist/sms/sms.provider.d.ts +9 -0
  65. package/dist/sms/sms.provider.js +7 -0
  66. package/dist/sms/sms.provider.js.map +1 -0
  67. package/dist/sms/sms.service.d.ts +8 -7
  68. package/dist/sms/sms.service.js +30 -2
  69. package/dist/sms/sms.service.js.map +1 -1
  70. package/dist/template/constant.d.ts +1 -0
  71. package/dist/template/constant.js +2 -1
  72. package/dist/template/constant.js.map +1 -1
  73. package/dist/template/engines/string-format.engine.d.ts +6 -0
  74. package/dist/template/engines/string-format.engine.js +17 -0
  75. package/dist/template/engines/string-format.engine.js.map +1 -0
  76. package/dist/template/fake-template.loader.js +14 -18
  77. package/dist/template/fake-template.loader.js.map +1 -1
  78. package/dist/template/index.d.ts +4 -2
  79. package/dist/template/index.js +4 -1
  80. package/dist/template/index.js.map +1 -1
  81. package/dist/template/template-engine.d.ts +5 -0
  82. package/dist/template/template-engine.js +3 -0
  83. package/dist/template/template-engine.js.map +1 -0
  84. package/dist/template/template.loader.d.ts +4 -1
  85. package/dist/template/template.loader.js +22 -14
  86. package/dist/template/template.loader.js.map +1 -1
  87. package/dist/whatsapp/constant.d.ts +2 -0
  88. package/dist/whatsapp/constant.js +6 -0
  89. package/dist/whatsapp/constant.js.map +1 -0
  90. package/dist/whatsapp/index.d.ts +3 -0
  91. package/dist/whatsapp/index.js +11 -0
  92. package/dist/whatsapp/index.js.map +1 -0
  93. package/dist/whatsapp/whatsapp.provider.d.ts +9 -0
  94. package/dist/whatsapp/whatsapp.provider.js +7 -0
  95. package/dist/whatsapp/whatsapp.provider.js.map +1 -0
  96. package/dist/whatsapp/whatsapp.service.d.ts +9 -0
  97. package/dist/whatsapp/whatsapp.service.js +35 -0
  98. package/dist/whatsapp/whatsapp.service.js.map +1 -0
  99. package/docs/20260214-fix-plan-notification.md +149 -0
  100. package/docs/20260214-fix-plan-post-refactor.md +118 -0
  101. package/docs/20260214-nestjs-notification-review.md +390 -0
  102. package/docs/20260214-review-post-refactor.md +285 -0
  103. package/docs/20260215-review-notification-post-fix.md +933 -0
  104. package/package.json +2 -1
  105. package/dist/email/email-provider.factory.d.ts +0 -5
  106. package/dist/email/email-provider.factory.js +0 -18
  107. package/dist/email/email-provider.factory.js.map +0 -1
  108. package/dist/email/email.module.d.ts +0 -27
  109. package/dist/email/email.module.js +0 -110
  110. package/dist/email/email.module.js.map +0 -1
  111. package/dist/interface/payload.inteface.d.ts +0 -22
  112. package/dist/interface/payload.inteface.js.map +0 -1
  113. package/dist/push-notification/push-notification-module.interface.d.ts +0 -33
  114. package/dist/push-notification/push-notification-module.interface.js.map +0 -1
  115. package/dist/push-notification/push-notification-provider-module.factory.d.ts +0 -5
  116. package/dist/push-notification/push-notification-provider-module.factory.js +0 -13
  117. package/dist/push-notification/push-notification-provider-module.factory.js.map +0 -1
  118. package/dist/push-notification/push-notification-provider.factory.d.ts +0 -5
  119. package/dist/push-notification/push-notification-provider.factory.js +0 -16
  120. package/dist/push-notification/push-notification-provider.factory.js.map +0 -1
  121. package/dist/push-notification/push-notification.module.d.ts +0 -15
  122. package/dist/push-notification/push-notification.module.js +0 -189
  123. package/dist/push-notification/push-notification.module.js.map +0 -1
@@ -0,0 +1,149 @@
1
+ # Fix Plan: @nathapp/nestjs-notification Code Review Issues
2
+
3
+ **Date:** 2026-02-14
4
+ **Branch:** `feat/notification-enterprise-upgrade`
5
+ **Priority:** Fix all 🔴 Critical first, then 🟡 Medium, then 🟢 Low
6
+
7
+ ---
8
+
9
+ ## Phase 1: Critical Fixes (🔴)
10
+
11
+ ### Fix 1: SMS Data Corruption in `sendToChannel` [C2]
12
+ **File:** `lib/notification/notification.service.ts`
13
+ **Impact:** Actively corrupting SMS data in production
14
+ **Change:**
15
+ - Replace `countryIso2: payload.phoneNumber` → `countryIso2: payload.countryIso2`
16
+ - Replace `dialCode: payload.phoneNumber` → `dialCode: payload.dialCode`
17
+ - Add `countryIso2` and `dialCode` to `NotificationPayload` interface in `payload.inteface.ts`
18
+
19
+ ### Fix 2: Push Template Args Silently Dropped [C3]
20
+ **File:** `lib/notification/notification.service.ts`
21
+ **Impact:** Template variables not resolved for push notifications
22
+ **Change:**
23
+ - Replace `resolveOption` → `resolvedResolveOption` in the `templateLoader.resolve()` call inside `sendPushNotification`
24
+
25
+ ### Fix 3: Consolidate Duplicate `SendResult` [C5]
26
+ **Files:** `lib/interface/result.interface.ts`, `lib/interface/payload.inteface.ts`
27
+ **Impact:** Type confusion across codebase
28
+ **Change:**
29
+ - Remove `SendResult` from `payload.inteface.ts`
30
+ - Add `error?: any` field to the canonical `SendResult` in `result.interface.ts`
31
+ - Rename `payload.inteface.ts` → `payload.interface.ts` (fixes M1 typo)
32
+ - Update all imports
33
+
34
+ ### Fix 4: `sendPushNotificationMulticastRaw` Returns null [C4]
35
+ **File:** `lib/notification/notification.service.ts`
36
+ **Impact:** Callers NPE on `result.successCount`
37
+ **Change:**
38
+ - Return `{ successCount: 0, failureCount: 0, responses: [] }` instead of `null`
39
+ - Same fix for `sendPushNotificationMulticast`
40
+
41
+ ### Fix 5: Firebase App Memory Leak [C1]
42
+ **File:** `lib/push-notification/fcm-push-notification.provider.ts`
43
+ **Impact:** Memory leak on re-registration / testing
44
+ **Change:**
45
+ - Use unique app name: `fcm-${randomUUID()}`
46
+ - Add `destroy()` method to delete Firebase app
47
+ - Add `OnModuleDestroy` lifecycle hook awareness (document for consumers)
48
+ - Wrap `JSON.parse(serviceAccount)` in try/catch (also fixes M7)
49
+
50
+ ---
51
+
52
+ ## Phase 2: Medium Fixes (🟡)
53
+
54
+ ### Fix 6: Fix `paltform` Typo [M2]
55
+ **Files:** `lib/push-notification/push-notification-provider.manager.ts`, related files
56
+ **Change:**
57
+ - Rename parameter `paltform` → `platform` in `ProviderManager.getProvider()`
58
+ - Keep `paltform` field in `PushMessage` as deprecated alias (backward compat)
59
+
60
+ ### Fix 7: `SmtpHostOption.port` String → Number [M3]
61
+ **File:** `lib/email/email.interface.ts`
62
+ **Change:**
63
+ - Change `port: string` → `port: number`
64
+ - Remove `parseInt()` in `email.provider.ts`
65
+
66
+ ### Fix 8: Remove `async` from `forRootAsync` [M10]
67
+ **File:** `lib/notification/notification.module.ts`
68
+ **Change:**
69
+ - `static async forRootAsync(...)` → `static forRootAsync(...)`
70
+ - Return `DynamicModule` not `Promise<DynamicModule>`
71
+
72
+ ### Fix 9: Fix `sendMulticast` Empty Array Crash [M9]
73
+ **File:** `lib/push-notification/push-notification.service.ts`
74
+ **Change:**
75
+ - Add initial value to `reduce()`: `{ successCount: 0, failureCount: 0, responses: [] }`
76
+
77
+ ### Fix 10: Fix `EmailModule` Custom Provider Returns Class [M5]
78
+ **File:** `lib/email/email.module.ts`
79
+ **Change:**
80
+ - `return moduleOption.option.provider` → `return new moduleOption.option.provider()`
81
+ - Or better: use NestJS DI to instantiate
82
+
83
+ ### Fix 11: Fix `InMemoryUserTokenStore` Single Token [M8]
84
+ **File:** `lib/push-notification/user-token.store.ts`
85
+ **Change:**
86
+ - Change `cache` to store arrays: `Record<string, DeviceToken[]>`
87
+ - `saveUserToken` appends instead of overwrites
88
+ - `getUserDeviceTokens` returns the array directly
89
+
90
+ ### Fix 12: Clean Up Unused Exports [M12]
91
+ **Files:** Multiple
92
+ **Change:**
93
+ - Remove `EMAIL_SERVICE` constant (unused)
94
+ - Remove `WHATSAPP_SERVICE` constant (unused)
95
+ - Remove `import fs from "fs"` from `message.interface.ts`
96
+ - Remove or deprecate `NOTIFICATION_SERVICE`, `TEMPLATE_SERVICE`
97
+ - Remove `DefaultSmtpEmailModuleOptionsFactory` or keep as opt-in
98
+
99
+ ### Fix 13: Fix `sanitizeOptions` No-Op + Typo [M4]
100
+ **File:** `lib/email/email.module.ts`
101
+ **Change:**
102
+ - Rename `santitizeOptions` → `sanitizeOptions`
103
+ - Either implement validation or remove the method
104
+
105
+ ### Fix 14: Type Safety Improvements [M6]
106
+ **Files:** Multiple
107
+ **Change:**
108
+ - Add generic type parameter to `ProviderConfig<C, O>` for options typing
109
+ - Replace critical `any` usages with proper types where feasible
110
+
111
+ ---
112
+
113
+ ## Phase 3: Low Priority Fixes (🟢)
114
+
115
+ ### Fix 15: Grammar Fixes [L1]
116
+ - `"is not configure"` → `"is not configured"`
117
+ - `"template has set"` → `"template is set"`
118
+
119
+ ### Fix 16: Fix Input Mutation in Email Provider [L4]
120
+ - Create new attachment objects instead of mutating input
121
+
122
+ ### Fix 17: Fix `result.response` Destructuring [L7]
123
+ - Use `result.messageId` directly (nodemailer puts it on the result object, not response)
124
+
125
+ ### Fix 18: Fix `FakeTemplateLoader` Promise Pattern [L3]
126
+ - Use `async` / `Promise.resolve()` instead of `new Promise()`
127
+
128
+ ### Fix 19: Fix `CachableTemplateLoader` Constructor [L6]
129
+ - Move `cacheOptions` to be last or use options object pattern
130
+
131
+ ### Fix 20: Deprecate Old Modules [L5]
132
+ - Add `@deprecated` JSDoc to `EmailModule`, `PushNotificationModule`
133
+ - Add deprecation log warning when used
134
+ - Document migration path to `NotificationModule`
135
+
136
+ ---
137
+
138
+ ## Test Strategy
139
+
140
+ - Run full test suite after each phase
141
+ - Target: all existing 159 tests passing + new tests for fixed edge cases
142
+ - Add tests for: empty multicast array, null provider scenarios, Firebase cleanup
143
+
144
+ ## Commits
145
+
146
+ One commit per phase:
147
+ 1. `fix: resolve all critical bugs (C1-C5)`
148
+ 2. `fix: resolve medium issues (M2-M14)`
149
+ 3. `chore: clean up low priority issues (L1-L7)`
@@ -0,0 +1,118 @@
1
+ # Fix Plan: Post-Refactor Cleanup
2
+
3
+ **Date:** 2026-02-14
4
+ **Branch:** `feat/notification-enterprise-upgrade`
5
+ **Review Source:** `docs/20260214-review-post-refactor.md`
6
+
7
+ ---
8
+
9
+ ## Phase 1: Standardize Attachment Support (M1)
10
+
11
+ ### Fix 1: Rename `isSupportAttachments` to capability query pattern
12
+
13
+ **Files:**
14
+ - `lib/email/email.provider.ts`
15
+ - `lib/whatsapp/whatsapp.provider.ts`
16
+ - `lib/sms/sms.provider.ts`
17
+ - `lib/push-notification/push-notification.provider.ts`
18
+
19
+ **Change:**
20
+ - Rename `isSupportAttachments` → `supportsAttachments` (cleaner naming)
21
+ - Add `supportsAttachments` as optional with default `false` to ALL provider base classes
22
+ - Email and WhatsApp default to abstract (must implement), SMS and Push default to `false`
23
+ - Update `SmtpEmailProvider` to use new name
24
+ - Update any references in tests
25
+
26
+ ## Phase 2: Document Push Provider (M2)
27
+
28
+ ### Fix 2: Add JSDoc to PushNotificationProvider
29
+
30
+ **File:** `lib/push-notification/push-notification.provider.ts`
31
+
32
+ **Change:**
33
+ - Add comprehensive JSDoc explaining why push requires `type` and `sendMulticast`
34
+ - Document that `type` maps to platform (firebase, huawei, apns, etc.)
35
+ - Add usage examples in JSDoc
36
+
37
+ ## Phase 3: Clean Public API Exports (M3)
38
+
39
+ ### Fix 3: Restructure barrel exports
40
+
41
+ **File:** `lib/index.ts`
42
+
43
+ **Change:**
44
+ Remove internal exports:
45
+ - `EMAIL_OPTIONS`, `EMAIL_SERVICE` — internal/unused tokens
46
+ - `EmailOption`, `SmtpOption`, `SmtpHostOption`, `SmtpUriOption`, `CustomEmailOption` — old config types
47
+ - `EmailProviderFactory` — superseded
48
+ - `SmtpEmailProvider` — built-in impl, keep but document as convenience export
49
+ - `FCMPushNotificationOption`, `PushNotificationOption` — FCM-specific, move to push-specific export
50
+ - `DefaultProviderManager`, `InMemoryUserTokenStore` — internal implementations
51
+ - `WHATSAPP_SERVICE` — unused
52
+
53
+ Keep public API:
54
+ - Abstract providers: `EmailProvider`, `SmsProvider`, `WhatsAppProvider`, `PushNotificationProvider`
55
+ - Services: `EmailService`, `SmsService`, `WhatsAppService`, `PushNotificationService`
56
+ - Provider tokens: `EMAIL_PROVIDER`, `SMS_PROVIDER`, `WHATSAPP_PROVIDER`, `PUSH_NOTIFICATION_PROVIDERS`
57
+ - Message types: all from `./interface`
58
+ - Module: `NotificationModule` and option interfaces from `./notification`
59
+ - Template: all from `./template`
60
+ - Built-in providers (convenience): `SmtpEmailProvider`, `FCMPushNotificationProvider`
61
+ - Push extras (needed by push provider authors): `PushNotificationPlatform`, `PlatformOS`, `ProviderManager`, `UserTokenStore`, `DeviceToken`, `TokenId`
62
+
63
+ ## Phase 4: Type Safety for ProviderConfig (M4)
64
+
65
+ ### Fix 4: Add generic options type
66
+
67
+ **File:** `lib/notification/provider-config.ts`
68
+
69
+ **Change:**
70
+ - Keep `options?: any` for backward compat in `ProviderConfig`
71
+ - Add typed helper pattern and document it:
72
+ ```typescript
73
+ /**
74
+ * Helper to create a typed provider config.
75
+ * External packages should export a similar helper:
76
+ *
77
+ * ```typescript
78
+ * // In @nathapp/nestjs-notification-brevo
79
+ * export function brevoEmailConfig(options: BrevoEmailOptions): ProviderConfig<'email'> {
80
+ * return { channel: 'email', provider: BrevoEmailProvider, options };
81
+ * }
82
+ * ```
83
+ */
84
+ ```
85
+
86
+ ## Phase 5: Remove Legacy Modules (M5)
87
+
88
+ ### Fix 5: Remove deprecated modules and factories
89
+
90
+ **Files to DELETE:**
91
+ - `lib/email/email.module.ts`
92
+ - `lib/email/email-provider.factory.ts`
93
+ - `lib/push-notification/push-notification.module.ts`
94
+ - `lib/push-notification/push-notification-provider.factory.ts`
95
+ - `lib/push-notification/push-notification-provider-module.factory.ts`
96
+ - `lib/push-notification/push-notification-module.interface.ts`
97
+
98
+ **Files to UPDATE:**
99
+ - `lib/email/index.ts` — remove module/factory exports
100
+ - `lib/push-notification/index.ts` — remove module/factory exports
101
+
102
+ **Document:** These modules were removed in v2.3.0 (or next version). Migration: use `NotificationModule.forRoot()` with unified `providers` array.
103
+
104
+ ---
105
+
106
+ ## Test Strategy
107
+
108
+ - Run full test suite after each phase
109
+ - All 159+ existing tests must pass
110
+ - No new tests needed (this is cleanup, not new functionality)
111
+
112
+ ## Commits
113
+
114
+ 1. `refactor: standardize attachment support across all providers (M1)`
115
+ 2. `docs: add JSDoc to PushNotificationProvider (M2)`
116
+ 3. `refactor: clean public API exports (M3)`
117
+ 4. `feat: add typed provider config helper pattern (M4)`
118
+ 5. `refactor!: remove legacy EmailModule, PushNotificationModule and old factories (M5)`
@@ -0,0 +1,390 @@
1
+ # 🔍 Deep Code Review: `@nathapp/nestjs-notification`
2
+
3
+ **Date:** 2026-02-14
4
+ **Branch:** `feat/notification-enterprise-upgrade`
5
+ **Reviewer:** Sabrina (AI)
6
+
7
+ ---
8
+
9
+ ## 🔴 Critical Issues (5)
10
+
11
+ ### C1: Firebase App Never Cleaned Up — Memory Leak
12
+
13
+ **File:** `lib/push-notification/fcm-push-notification.provider.ts`
14
+
15
+ Every `FCMPushNotificationProvider` calls `Firebase.initializeApp()` without a unique name, and there's **no cleanup**. If the module is re-registered or during testing, this leaks Firebase app instances.
16
+
17
+ ```typescript
18
+ // Problem: no name → collides; no cleanup → leaks
19
+ this.client = Firebase.initializeApp({
20
+ credential: Firebase.credential.cert(JSON.parse(option.serviceAccount)),
21
+ databaseURL: option.databaseUrl,
22
+ });
23
+ ```
24
+
25
+ **Fix:** Use unique app name + add `destroy()` method:
26
+ ```typescript
27
+ import { randomUUID } from 'crypto';
28
+
29
+ const appName = `fcm-${randomUUID()}`;
30
+ this.client = Firebase.initializeApp({ ... }, appName);
31
+
32
+ async destroy(): Promise<void> {
33
+ await this.client.delete();
34
+ }
35
+ ```
36
+
37
+ ---
38
+
39
+ ### C2: `sendToChannel('sms')` Passes Phone Number as `countryIso2` AND `dialCode`
40
+
41
+ **File:** `lib/notification/notification.service.ts` (sendToChannel method)
42
+
43
+ ```typescript
44
+ case 'sms':
45
+ return this.sendSms({
46
+ templateCode: payload.templateCode,
47
+ phoneNumber: payload.phoneNumber,
48
+ options: payload.options,
49
+ countryIso2: payload.phoneNumber, // ❌ BUG: should be payload.countryIso2
50
+ dialCode: payload.phoneNumber, // ❌ BUG: should be payload.dialCode
51
+ });
52
+ ```
53
+
54
+ This **actively corrupts data** sent to downstream SMS providers. The phone number is being passed as both country ISO code and dial code.
55
+
56
+ ---
57
+
58
+ ### C3: `sendPushNotification` Uses Wrong Variable for Template Resolution
59
+
60
+ **File:** `lib/notification/notification.service.ts` (sendPushNotification method)
61
+
62
+ ```typescript
63
+ const templateMessage = await this.templateLoader.resolve(
64
+ resolvedTemplateCode,
65
+ resolveOption // ❌ should be resolvedResolveOption
66
+ );
67
+ ```
68
+
69
+ When called via the payload overload `sendPushNotification(payload)`, the `resolveOption` parameter is `undefined` (it's only set in the legacy 3-arg overload). Template arguments are **silently dropped**.
70
+
71
+ ---
72
+
73
+ ### C4: `sendPushNotificationMulticastRaw` Returns `null` Instead of `BatchSendResult`
74
+
75
+ **File:** `lib/notification/notification.service.ts`
76
+
77
+ ```typescript
78
+ if (this.strict) {
79
+ throw new NotificationConfigError("PushNotificationService");
80
+ }
81
+ return null; // ❌ violates return type Promise<BatchSendResult>
82
+ ```
83
+
84
+ Same issue in `sendPushNotificationMulticast`. Callers will get a null pointer exception when accessing `result.successCount`.
85
+
86
+ ---
87
+
88
+ ### C5: Duplicate `SendResult` Interface — Conflicting Definitions
89
+
90
+ **Files:** `lib/interface/result.interface.ts` vs `lib/interface/payload.inteface.ts`
91
+
92
+ Two different `SendResult` interfaces with different fields:
93
+
94
+ ```typescript
95
+ // result.interface.ts
96
+ export interface SendResult {
97
+ success: boolean;
98
+ message?: string;
99
+ requestId?: string; // ← has requestId
100
+ extra?: any;
101
+ }
102
+
103
+ // payload.inteface.ts (also exported)
104
+ export interface SendResult {
105
+ success: boolean;
106
+ message?: string;
107
+ error?: any; // ← has error field instead
108
+ }
109
+ ```
110
+
111
+ Both are exported from `interface/index.ts`. TypeScript may merge or shadow depending on import order — a silent bug source.
112
+
113
+ ---
114
+
115
+ ## 🟡 Medium Issues (12)
116
+
117
+ ### M1: Typo in Filename
118
+
119
+ **File:** `lib/interface/payload.inteface.ts`
120
+
121
+ Missing 'r' in filename. Should be `payload.interface.ts`.
122
+
123
+ ---
124
+
125
+ ### M2: Deprecated `paltform` Typo Persists
126
+
127
+ **Files:** `lib/interface/message.interface.ts`, `lib/push-notification/push-notification-provider.manager.ts`
128
+
129
+ ```typescript
130
+ // message.interface.ts
131
+ /** @deprecated Use `platform` instead */
132
+ paltform?: PlatformOS; // typo of "platform"
133
+
134
+ // push-notification-provider.manager.ts
135
+ getProvider(paltform?: PlatformOS) // parameter still uses typo
136
+ ```
137
+
138
+ ---
139
+
140
+ ### M3: `SmtpHostOption.port` Is `string` Instead of `number`
141
+
142
+ **File:** `lib/email/email.interface.ts`
143
+
144
+ ```typescript
145
+ export interface SmtpHostOption {
146
+ port: string; // ← should be number; parseInt() is used later in email.provider.ts
147
+ }
148
+ ```
149
+
150
+ ---
151
+
152
+ ### M4: `sanitizeOptions` Is a No-Op (+ Typo)
153
+
154
+ **File:** `lib/email/email.module.ts`
155
+
156
+ ```typescript
157
+ private static santitizeOptions<T>(options: T): T {
158
+ return options; // does nothing
159
+ }
160
+ ```
161
+
162
+ Also note typo: `santitize` → `sanitize` (fixed in notification module but not email module).
163
+
164
+ ---
165
+
166
+ ### M5: `EmailModule.createEmailProvider` Returns Class Instead of Instance for Custom Type
167
+
168
+ **File:** `lib/email/email.module.ts`
169
+
170
+ ```typescript
171
+ if (moduleOption.option.type == "custom") {
172
+ return moduleOption.option.provider; // ← returns the CLASS, not an instance
173
+ }
174
+ ```
175
+
176
+ Should be `new moduleOption.option.provider()` or use NestJS DI to resolve.
177
+
178
+ ---
179
+
180
+ ### M6: Extensive `any` Usage — Weak Type Safety
181
+
182
+ **Files:** Multiple locations
183
+
184
+ - `ProviderConfig.options?: any` — no type constraint on provider options
185
+ - `resolveOption.args?: any` in payload interfaces
186
+ - `error?: any` in `SendResult`
187
+ - `useFactory?: (...args: any[])` everywhere
188
+ - Multiple `as any` casts in `notification.service.ts`
189
+
190
+ ---
191
+
192
+ ### M7: No Try/Catch on `JSON.parse(serviceAccount)` in FCM Provider
193
+
194
+ **File:** `lib/push-notification/fcm-push-notification.provider.ts`
195
+
196
+ ```typescript
197
+ credential: Firebase.credential.cert(JSON.parse(option.serviceAccount)),
198
+ ```
199
+
200
+ If `serviceAccount` is malformed JSON, this throws an unhelpful parse error during module initialization, crashing the entire app.
201
+
202
+ ---
203
+
204
+ ### M8: `InMemoryUserTokenStore` Only Stores One Token Per User
205
+
206
+ **File:** `lib/push-notification/user-token.store.ts`
207
+
208
+ ```typescript
209
+ async saveUserToken(userId, token): Promise<TokenId> {
210
+ this.cache[userId] = token; // ← silently overwrites previous token
211
+ }
212
+ ```
213
+
214
+ But `getUserDeviceTokens` (plural) wraps result in an array, implying multi-token support. The store silently drops old tokens.
215
+
216
+ ---
217
+
218
+ ### M9: `sendMulticast` Crashes on Empty Array
219
+
220
+ **File:** `lib/push-notification/push-notification.service.ts`
221
+
222
+ ```typescript
223
+ return results.reduce((a, b) => { ... }) // ← no initial value; crashes if results is empty
224
+ ```
225
+
226
+ `Array.reduce()` without initial value on an empty array throws `TypeError`.
227
+
228
+ ---
229
+
230
+ ### M10: `forRootAsync` Returns `Promise<DynamicModule>` — Should Be Sync
231
+
232
+ **File:** `lib/notification/notification.module.ts`
233
+
234
+ ```typescript
235
+ static async forRootAsync(options): Promise<DynamicModule> {
236
+ ```
237
+
238
+ NestJS `forRootAsync` should return `DynamicModule` synchronously. The `async` keyword here is unnecessary and could cause issues with module scanning.
239
+
240
+ ---
241
+
242
+ ### M11: `apnsPushType` Set in Wrong Location
243
+
244
+ **File:** `lib/push-notification/fcm-push-notification.provider.ts`
245
+
246
+ ```typescript
247
+ aps: {
248
+ mutableContent: true,
249
+ contentAvailable: true,
250
+ apnsPushType: "background" // ← should be in APNs headers, not aps payload
251
+ }
252
+ ```
253
+
254
+ This relies on `[customData: string]: any` catch-all in the `Aps` interface.
255
+
256
+ ---
257
+
258
+ ### M12: Unused Exports/Constants
259
+
260
+ - `EMAIL_SERVICE` — defined, exported, never used
261
+ - `WHATSAPP_SERVICE` — defined, exported, never used
262
+ - `NOTIFICATION_SERVICE`, `TEMPLATE_SERVICE` — defined, exported, never used as injection tokens
263
+ - `DefaultSmtpEmailModuleOptionsFactory` — exported but likely unused
264
+ - `import fs from "fs"` in `message.interface.ts` — Node.js import in an interface file (never used)
265
+
266
+ ---
267
+
268
+ ## 🟢 Low Issues (7)
269
+
270
+ ### L1: Inconsistent Error Message Grammar
271
+
272
+ **File:** `lib/notification/notification.service.ts`
273
+
274
+ `"is not configure"` → `"is not configured"`, `"template has set"` → `"template is set"`.
275
+
276
+ ---
277
+
278
+ ### L2: `DefaultSmtpEmailModuleOptionsFactory` Reads `process.env` Directly
279
+
280
+ **File:** `lib/email/email.module.ts`
281
+
282
+ Should use NestJS `ConfigService` for testability.
283
+
284
+ ---
285
+
286
+ ### L3: `FakeTemplateLoader` Uses Unnecessary Promise Constructor
287
+
288
+ **File:** `lib/template/fake-template.loader.ts`
289
+
290
+ ```typescript
291
+ return new Promise<MessageTemplate>((resolve, reject) => {
292
+ resolve({...});
293
+ });
294
+ // Better: return Promise.resolve({...}) or use async
295
+ ```
296
+
297
+ ---
298
+
299
+ ### L4: `email.provider.ts` Mutates Input Attachment Objects
300
+
301
+ **File:** `lib/email/email.provider.ts`
302
+
303
+ ```typescript
304
+ emailMessage.attachments?.map(item => {
305
+ if(typeof item.content == "string") {
306
+ item.content = Buffer.from(item.content, "base64") // ← mutates original object
307
+ }
308
+ ```
309
+
310
+ Should create new object: `return { ...item, content: Buffer.from(...) }`.
311
+
312
+ ---
313
+
314
+ ### L5: Inconsistent Module Registration Patterns
315
+
316
+ - `EmailModule` uses `forRoot` / `forRootAsync`
317
+ - `PushNotificationModule` uses `register` / `registerAsync`
318
+ - `NotificationModule` uses `forRoot` / `forRootAsync`
319
+ - SMS and WhatsApp have no standalone modules
320
+
321
+ ---
322
+
323
+ ### L6: `CachableTemplateLoader` Constructor Parameter Order Issue
324
+
325
+ **File:** `lib/template/template.loader.ts`
326
+
327
+ ```typescript
328
+ constructor(
329
+ @Inject(CACHE_MANAGER) private cacheManager: Cache,
330
+ cacheOptions?: CacheOptions, // ← plain param between DI params
331
+ @Optional() @Inject(TEMPLATE_ENGINE) engine?: TemplateEngine
332
+ )
333
+ ```
334
+
335
+ Having a non-DI parameter between DI parameters breaks NestJS injection.
336
+
337
+ ---
338
+
339
+ ### L7: `result.response` Destructuring Bug in SmtpEmailProvider
340
+
341
+ **File:** `lib/email/email.provider.ts`
342
+
343
+ ```typescript
344
+ const result = await this.transporter.sendMail(...);
345
+ const { messageId } = result.response; // ← result.response is a STRING like "250 OK", not an object
346
+ ```
347
+
348
+ `messageId` will always be `undefined` here.
349
+
350
+ ---
351
+
352
+ ## 🔒 Security Notes
353
+
354
+ ### S1 (🟡): Firebase Service Account JSON as Plain String
355
+
356
+ **File:** `lib/push-notification/fcm-push-notification.provider.ts`
357
+
358
+ `option.serviceAccount` contains entire Firebase service account JSON. If logged or serialized in error objects, credentials leak.
359
+
360
+ ### S2 (🟢): No HTML Sanitization on Email Body
361
+
362
+ **File:** `lib/email/email.provider.ts`
363
+
364
+ `message` is passed directly as `html` in nodemailer. If user-controlled content reaches this, it's an XSS/phishing vector in email clients.
365
+
366
+ ---
367
+
368
+ ## 📊 Summary
369
+
370
+ | Severity | Count | Key Themes |
371
+ |----------|-------|------------|
372
+ | 🔴 Critical | 5 | Data corruption (SMS), wrong variable (push template), null returns, duplicate types, Firebase leak |
373
+ | 🟡 Medium | 12 | Typos, type safety, no-op methods, async module, empty array crash |
374
+ | 🟢 Low | 7 | Style, input mutation, grammar, constructor ordering |
375
+
376
+ ### Top 3 Priorities
377
+
378
+ 1. **C2** — SMS `countryIso2`/`dialCode` bug is **actively corrupting data**
379
+ 2. **C3** — Push template args **silently dropped**
380
+ 3. **C5** — Duplicate `SendResult` causing **type confusion across codebase**
381
+
382
+ ### Deprecated / Dead Code Candidates for Removal
383
+
384
+ - `EmailModule` — superseded by `NotificationModule`
385
+ - `PushNotificationModule` — superseded by `NotificationModule`
386
+ - `EmailProviderFactory` — superseded by `NotificationProviderFactory`
387
+ - `push-notification-provider.factory.ts` — superseded by unified factory
388
+ - `push-notification-provider-module.factory.ts` — superseded
389
+ - `push-notification-module.interface.ts` — superseded
390
+ - `email-provider.factory.ts` — superseded