@hiliosai/sdk 0.1.4 → 0.1.6
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/package.json +1 -1
- package/src/configs/constants.ts +13 -0
- package/src/datasources/base.datasource.ts +37 -5
- package/src/datasources/extensions/index.ts +11 -0
- package/src/datasources/extensions/retry.extension.ts +91 -0
- package/src/datasources/extensions/soft-delete.extension.ts +113 -0
- package/src/datasources/extensions/tenant.extension.ts +104 -0
- package/src/datasources/index.ts +2 -0
- package/src/datasources/prisma.datasource.ts +325 -0
- package/src/mixins/datasource.mixin.ts +13 -1
- package/src/service/define-integration.ts +142 -83
- package/src/service/define-service.ts +6 -2
- package/src/types/channels.ts +60 -0
- package/src/types/context.ts +22 -2
- package/src/types/index.ts +1 -0
- package/src/types/integration.ts +0 -1
- package/src/types/message.ts +2 -3
- package/src/types/service.ts +34 -31
- package/src/types/user.ts +2 -2
- package/INTEGRATION_SERVICE.md +0 -394
- package/src/service/example-user/datasources/index.ts +0 -8
- package/src/service/example-user/datasources/user.datasource.ts +0 -7
- package/src/service/example-user/user.service.ts +0 -23
- package/src/service/example-user/utils.ts +0 -0
|
@@ -2,6 +2,7 @@ import omit from 'lodash/omit';
|
|
|
2
2
|
import type {ServiceSchema as MoleculerServiceSchema} from 'moleculer';
|
|
3
3
|
|
|
4
4
|
import {MemoizeMixin} from '../middlewares';
|
|
5
|
+
import type {DatasourceConstructorRegistry} from '../middlewares/datasource.middleware';
|
|
5
6
|
import {DatasourceMixin} from '../mixins';
|
|
6
7
|
import type {ServiceConfig} from '../types/service';
|
|
7
8
|
|
|
@@ -34,8 +35,11 @@ import type {ServiceConfig} from '../types/service';
|
|
|
34
35
|
* });
|
|
35
36
|
* ```
|
|
36
37
|
*/
|
|
37
|
-
export function defineService<
|
|
38
|
-
|
|
38
|
+
export function defineService<
|
|
39
|
+
TSettings = unknown,
|
|
40
|
+
TDatasourceConstructors extends DatasourceConstructorRegistry = DatasourceConstructorRegistry
|
|
41
|
+
>(
|
|
42
|
+
config: ServiceConfig<TSettings, TDatasourceConstructors>
|
|
39
43
|
): MoleculerServiceSchema<TSettings> {
|
|
40
44
|
const propsToOmit = ['datasources'];
|
|
41
45
|
const serviceSchema = omit(
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type {IntegrationPlatform} from './platform';
|
|
2
|
+
import type {NormalizedMessage} from './message';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Channel event payload types for reliable messaging via @moleculer/channels
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Integration message events
|
|
9
|
+
export interface IntegrationMessageReceivedPayload {
|
|
10
|
+
tenantId: string;
|
|
11
|
+
channelId: string;
|
|
12
|
+
platform: IntegrationPlatform;
|
|
13
|
+
message: NormalizedMessage;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
metadata?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface IntegrationMessageSentPayload {
|
|
19
|
+
tenantId: string;
|
|
20
|
+
channelId: string;
|
|
21
|
+
platform: IntegrationPlatform;
|
|
22
|
+
messageId?: string;
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface IntegrationMessageFailedPayload {
|
|
28
|
+
tenantId: string;
|
|
29
|
+
channelId: string;
|
|
30
|
+
platform: IntegrationPlatform;
|
|
31
|
+
error: string; // Serialized error message
|
|
32
|
+
message?: NormalizedMessage;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
retryCount?: number;
|
|
35
|
+
metadata?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// System events
|
|
39
|
+
export interface IntegrationRegisteredPayload {
|
|
40
|
+
tenantId: string;
|
|
41
|
+
channelId: string;
|
|
42
|
+
platform: IntegrationPlatform;
|
|
43
|
+
config: Record<string, unknown>;
|
|
44
|
+
timestamp: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface IntegrationUnregisteredPayload {
|
|
48
|
+
tenantId: string;
|
|
49
|
+
channelId: string;
|
|
50
|
+
platform: IntegrationPlatform;
|
|
51
|
+
timestamp: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Re-export constants to avoid duplication
|
|
55
|
+
export {
|
|
56
|
+
CHANNELS,
|
|
57
|
+
INTEGRATION_CHANNELS,
|
|
58
|
+
NAMESPACE,
|
|
59
|
+
type IntegrationChannelName,
|
|
60
|
+
} from '../configs/constants';
|
package/src/types/context.ts
CHANGED
|
@@ -2,15 +2,32 @@ import type {Context} from 'moleculer';
|
|
|
2
2
|
import type {Tenant} from './tenant';
|
|
3
3
|
import type {User} from './user';
|
|
4
4
|
|
|
5
|
+
// Moleculer Channels types
|
|
6
|
+
export interface ChannelSendOptions {
|
|
7
|
+
group?: string;
|
|
8
|
+
maxInFlight?: number;
|
|
9
|
+
maxRetries?: number;
|
|
10
|
+
deadLettering?: {
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
queueName?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SendToChannelMethod {
|
|
17
|
+
(
|
|
18
|
+
channelName: string,
|
|
19
|
+
payload: unknown,
|
|
20
|
+
options?: ChannelSendOptions
|
|
21
|
+
): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
5
24
|
export interface AppMeta {
|
|
6
25
|
user?: User;
|
|
7
26
|
tenantId?: string;
|
|
8
27
|
tenantName?: string;
|
|
9
28
|
userId?: string;
|
|
10
|
-
integrationId?: string;
|
|
11
29
|
channelId?: string;
|
|
12
30
|
requestId?: string;
|
|
13
|
-
correlationId?: string;
|
|
14
31
|
userAgent?: string;
|
|
15
32
|
clientIP?: string;
|
|
16
33
|
[key: string]: unknown;
|
|
@@ -41,4 +58,7 @@ export type AppContext<
|
|
|
41
58
|
> = Context<TParams, TMeta, TLocals> &
|
|
42
59
|
PermissionHelpers & {
|
|
43
60
|
datasources: TDatasources;
|
|
61
|
+
broker: Context['broker'] & {
|
|
62
|
+
sendToChannel: SendToChannelMethod;
|
|
63
|
+
};
|
|
44
64
|
};
|
package/src/types/index.ts
CHANGED
package/src/types/integration.ts
CHANGED
package/src/types/message.ts
CHANGED
|
@@ -77,12 +77,11 @@ export interface PlatformMessage {
|
|
|
77
77
|
payload: any;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export interface WebhookEvent {
|
|
80
|
+
export interface WebhookEvent<TPayload = any> {
|
|
81
81
|
tenantId: string;
|
|
82
82
|
channelId: string;
|
|
83
|
-
integrationId: string;
|
|
84
83
|
platform: IntegrationPlatform;
|
|
85
|
-
payload:
|
|
84
|
+
payload: TPayload;
|
|
86
85
|
headers: Record<string, string>;
|
|
87
86
|
timestamp: number;
|
|
88
87
|
}
|
package/src/types/service.ts
CHANGED
|
@@ -9,13 +9,10 @@ import type {
|
|
|
9
9
|
|
|
10
10
|
import type {DatasourceConstructorRegistry} from '../middlewares/datasource.middleware';
|
|
11
11
|
import type {AppContext} from './context';
|
|
12
|
+
import type {DatasourceInstanceTypes} from './datasource';
|
|
12
13
|
import type {BaseSpec, IntegrationConfig} from './integration';
|
|
13
|
-
import type {
|
|
14
|
-
|
|
15
|
-
PlatformMessage,
|
|
16
|
-
SendResult,
|
|
17
|
-
WebhookEvent,
|
|
18
|
-
} from './message';
|
|
14
|
+
import type {NormalizedMessage, SendResult, WebhookEvent} from './message';
|
|
15
|
+
|
|
19
16
|
|
|
20
17
|
// Type to infer TypeScript types from Moleculer parameter schemas
|
|
21
18
|
type InferParamsType<T> = T extends Record<string, any>
|
|
@@ -121,28 +118,34 @@ export interface ServiceSchema<TSettings = unknown, TDatasources = unknown>
|
|
|
121
118
|
// ServiceConfig is what users provide to defineService
|
|
122
119
|
export type ServiceConfig<
|
|
123
120
|
TSettings = unknown,
|
|
124
|
-
|
|
125
|
-
> = ServiceSchema<
|
|
126
|
-
|
|
121
|
+
TDatasourceConstructors extends DatasourceConstructorRegistry = DatasourceConstructorRegistry
|
|
122
|
+
> = ServiceSchema<
|
|
123
|
+
TSettings,
|
|
124
|
+
DatasourceInstanceTypes<TDatasourceConstructors>
|
|
125
|
+
> & {
|
|
126
|
+
datasources?: TDatasourceConstructors;
|
|
127
127
|
};
|
|
128
128
|
|
|
129
129
|
// Integration-specific types
|
|
130
130
|
export interface IntegrationServiceConfig<
|
|
131
|
-
TPlatformMessage extends PlatformMessage = PlatformMessage,
|
|
132
131
|
TSettings = unknown,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
TDatasourceConstructors extends DatasourceConstructorRegistry = DatasourceConstructorRegistry,
|
|
133
|
+
TContext extends AppContext<
|
|
134
|
+
DatasourceInstanceTypes<TDatasourceConstructors>
|
|
135
|
+
> = AppContext<DatasourceInstanceTypes<TDatasourceConstructors>>
|
|
136
|
+
> extends ServiceConfig<TSettings, TDatasourceConstructors> {
|
|
136
137
|
name: string;
|
|
137
138
|
spec: BaseSpec;
|
|
138
139
|
|
|
139
140
|
// Core integration methods
|
|
140
|
-
normalize
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
141
|
+
normalize<TPayload = any>(
|
|
142
|
+
webhook: WebhookEvent<TPayload>
|
|
143
|
+
): Promise<NormalizedMessage[]>;
|
|
144
|
+
getChannelConfig(
|
|
145
|
+
ctx: TContext,
|
|
146
|
+
channelId: string
|
|
147
|
+
): Promise<IntegrationConfig | null>;
|
|
148
|
+
validateWebhook?<TPayload = any>(webhook: WebhookEvent<TPayload>): boolean;
|
|
146
149
|
sendMessage(
|
|
147
150
|
ctx: TContext,
|
|
148
151
|
message: NormalizedMessage,
|
|
@@ -170,20 +173,20 @@ export interface IntegrationServiceConfig<
|
|
|
170
173
|
validateCredentials?(credentials: Record<string, string>): Promise<boolean>;
|
|
171
174
|
|
|
172
175
|
// Optional signature validation
|
|
173
|
-
validateSignature
|
|
176
|
+
validateSignature?<TPayload = any>(webhook: WebhookEvent<TPayload>): boolean;
|
|
174
177
|
}
|
|
175
178
|
|
|
176
|
-
export interface IntegrationServiceSchema<
|
|
177
|
-
|
|
178
|
-
TSettings = unknown
|
|
179
|
-
> extends MoleculerServiceSchema<TSettings> {
|
|
179
|
+
export interface IntegrationServiceSchema<TSettings = unknown>
|
|
180
|
+
extends MoleculerServiceSchema<TSettings> {
|
|
180
181
|
spec: BaseSpec;
|
|
181
|
-
normalize
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
normalize<TPayload = any>(
|
|
183
|
+
webhook: WebhookEvent<TPayload>
|
|
184
|
+
): Promise<NormalizedMessage[]>;
|
|
185
|
+
getChannelConfig(
|
|
186
|
+
ctx: AppContext,
|
|
187
|
+
channelId: string
|
|
188
|
+
): Promise<IntegrationConfig | null>;
|
|
189
|
+
validateWebhook?<TPayload = any>(webhook: WebhookEvent<TPayload>): boolean;
|
|
187
190
|
sendMessage(
|
|
188
191
|
ctx: AppContext,
|
|
189
192
|
message: NormalizedMessage,
|
|
@@ -203,5 +206,5 @@ export interface IntegrationServiceSchema<
|
|
|
203
206
|
details?: Record<string, any>;
|
|
204
207
|
}>;
|
|
205
208
|
validateCredentials?(credentials: Record<string, string>): Promise<boolean>;
|
|
206
|
-
validateSignature
|
|
209
|
+
validateSignature?<TPayload = any>(webhook: WebhookEvent<TPayload>): boolean;
|
|
207
210
|
}
|
package/src/types/user.ts
CHANGED
package/INTEGRATION_SERVICE.md
DELETED
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
# Integration Service Architecture
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
Integration Services are specialized Moleculer services that provide a standardized way to connect external messaging platforms (WhatsApp, Telegram, Discord, etc.) with the application. They handle webhook reception, message normalization, and platform-specific communication patterns.
|
|
6
|
-
|
|
7
|
-
## Purpose
|
|
8
|
-
|
|
9
|
-
The Integration Service pattern solves several key challenges:
|
|
10
|
-
|
|
11
|
-
1. **Platform Abstraction**: Provides unified interface for different messaging platforms
|
|
12
|
-
2. **Webhook Security**: Implements signature validation and replay attack prevention
|
|
13
|
-
3. **Message Normalization**: Converts platform-specific messages to unified format
|
|
14
|
-
4. **Retry Logic**: Handles transient failures with exponential backoff
|
|
15
|
-
5. **Event Broadcasting**: Emits standardized events for message processing
|
|
16
|
-
6. **Health Monitoring**: Provides health checks and status reporting
|
|
17
|
-
|
|
18
|
-
## Architecture
|
|
19
|
-
|
|
20
|
-
```mermaid
|
|
21
|
-
graph TB
|
|
22
|
-
subgraph "External Platforms"
|
|
23
|
-
WA[WhatsApp]
|
|
24
|
-
TG[Telegram]
|
|
25
|
-
DC[Discord]
|
|
26
|
-
SL[Slack]
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
subgraph "Integration Layer"
|
|
30
|
-
subgraph "Integration Services"
|
|
31
|
-
WAS[WhatsApp Service]
|
|
32
|
-
TGS[Telegram Service]
|
|
33
|
-
DCS[Discord Service]
|
|
34
|
-
SLS[Slack Service]
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
subgraph "Service Actions"
|
|
38
|
-
RW[i_receiveWebhook]
|
|
39
|
-
SM[i_sendMessage]
|
|
40
|
-
HC[i_healthCheck]
|
|
41
|
-
VW[i_verifyWebhook]
|
|
42
|
-
ST[i_status]
|
|
43
|
-
VC[i_validateCredentials]
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
subgraph "Core Application"
|
|
48
|
-
subgraph "Message Processing"
|
|
49
|
-
MP[Message Processor]
|
|
50
|
-
MR[Message Router]
|
|
51
|
-
MS[Message Store]
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
subgraph "Event System"
|
|
55
|
-
EB[Event Bus]
|
|
56
|
-
EH[Event Handlers]
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
subgraph "Infrastructure"
|
|
61
|
-
DS[Datasources]
|
|
62
|
-
CA[Cache]
|
|
63
|
-
PE[Permissions]
|
|
64
|
-
CH[Context Helpers]
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
%% External webhook flows
|
|
68
|
-
WA -->|Webhook| WAS
|
|
69
|
-
TG -->|Webhook| TGS
|
|
70
|
-
DC -->|Webhook| DCS
|
|
71
|
-
SL -->|Webhook| SLS
|
|
72
|
-
|
|
73
|
-
%% Integration service actions
|
|
74
|
-
WAS --> RW
|
|
75
|
-
WAS --> SM
|
|
76
|
-
WAS --> HC
|
|
77
|
-
|
|
78
|
-
%% Service infrastructure
|
|
79
|
-
WAS --> DS
|
|
80
|
-
TGS --> CA
|
|
81
|
-
DCS --> PE
|
|
82
|
-
SLS --> CH
|
|
83
|
-
|
|
84
|
-
%% Event flows
|
|
85
|
-
RW -->|integration.message.received| EB
|
|
86
|
-
SM -->|integration.message.sent| EB
|
|
87
|
-
SM -->|integration.message.failed| EB
|
|
88
|
-
|
|
89
|
-
%% Processing flows
|
|
90
|
-
EB --> EH
|
|
91
|
-
EH --> MP
|
|
92
|
-
MP --> MR
|
|
93
|
-
MR --> MS
|
|
94
|
-
|
|
95
|
-
%% Response flows
|
|
96
|
-
MP -->|Send Response| SM
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Integration Service Structure
|
|
100
|
-
|
|
101
|
-
### Core Components
|
|
102
|
-
|
|
103
|
-
1. **defineIntegration()** - Factory function that creates integration services
|
|
104
|
-
2. **Security Helpers** - Webhook validation, timestamp checking, correlation IDs
|
|
105
|
-
3. **Retry Mechanism** - Exponential backoff for failed operations
|
|
106
|
-
4. **Standard Actions** - Predefined actions for common integration operations
|
|
107
|
-
|
|
108
|
-
### Standard Actions
|
|
109
|
-
|
|
110
|
-
All integration services automatically get these actions:
|
|
111
|
-
|
|
112
|
-
| Action | REST Endpoint | Purpose |
|
|
113
|
-
| ----------------------- | ---------------- | ------------------------------------- |
|
|
114
|
-
| `i_receiveWebhook` | `POST /webhook` | Receive and process incoming webhooks |
|
|
115
|
-
| `i_sendMessage` | `POST /send` | Send messages to external platform |
|
|
116
|
-
| `i_healthCheck` | `GET /health` | Check integration health status |
|
|
117
|
-
| `i_verifyWebhook` | `GET /webhook` | Verify webhook subscriptions |
|
|
118
|
-
| `i_status` | `GET /status` | Get integration status information |
|
|
119
|
-
| `i_validateCredentials` | `POST /validate` | Validate platform credentials |
|
|
120
|
-
|
|
121
|
-
### Service Methods
|
|
122
|
-
|
|
123
|
-
Integration-specific logic is implemented as service methods:
|
|
124
|
-
|
|
125
|
-
| Method | Purpose | Required |
|
|
126
|
-
| ----------------------- | --------------------------------------------- | -------- |
|
|
127
|
-
| `normalize()` | Convert platform message to unified format | ✅ |
|
|
128
|
-
| `sendMessage()` | Send message to platform | ✅ |
|
|
129
|
-
| `transform()` | Transform outbound message to platform format | ❌ |
|
|
130
|
-
| `validateWebhook()` | Validate webhook payload structure | ❌ |
|
|
131
|
-
| `validateSignature()` | Validate webhook signature | ❌ |
|
|
132
|
-
| `verifyWebhook()` | Handle webhook verification challenge | ❌ |
|
|
133
|
-
| `checkHealth()` | Custom health check logic | ❌ |
|
|
134
|
-
| `validateCredentials()` | Validate platform credentials | ❌ |
|
|
135
|
-
|
|
136
|
-
## Usage Example
|
|
137
|
-
|
|
138
|
-
### Basic WhatsApp Integration
|
|
139
|
-
|
|
140
|
-
```typescript
|
|
141
|
-
import {defineIntegration} from '@pkg/sdk';
|
|
142
|
-
import type {BaseSpec, NormalizedMessage, WebhookEvent} from '@pkg/sdk';
|
|
143
|
-
|
|
144
|
-
const whatsappIntegration: BaseSpec = {
|
|
145
|
-
id: 'whatsapp-business',
|
|
146
|
-
name: 'WhatsApp Business',
|
|
147
|
-
platform: 'whatsapp',
|
|
148
|
-
version: '1.0.0',
|
|
149
|
-
status: 'ACTIVE',
|
|
150
|
-
capabilities: ['SEND_MESSAGE', 'RECEIVE_MESSAGE', 'WEBHOOK_VALIDATION']
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
export default defineIntegration({
|
|
154
|
-
name: 'whatsapp',
|
|
155
|
-
spec: whatsappIntegration,
|
|
156
|
-
|
|
157
|
-
// Required: Convert WhatsApp webhook to normalized message
|
|
158
|
-
async normalize(webhook: WebhookEvent): Promise<NormalizedMessage[]> {
|
|
159
|
-
const messages: NormalizedMessage[] = [];
|
|
160
|
-
|
|
161
|
-
for (const entry of webhook.payload.entry || []) {
|
|
162
|
-
for (const change of entry.changes || []) {
|
|
163
|
-
if (change.field === 'messages') {
|
|
164
|
-
for (const message of change.value.messages || []) {
|
|
165
|
-
messages.push({
|
|
166
|
-
id: message.id,
|
|
167
|
-
conversationId: message.from,
|
|
168
|
-
from: {
|
|
169
|
-
id: message.from,
|
|
170
|
-
name: change.value.contacts?.[0]?.profile?.name,
|
|
171
|
-
},
|
|
172
|
-
to: {
|
|
173
|
-
id: webhook.tenantId,
|
|
174
|
-
},
|
|
175
|
-
content: {
|
|
176
|
-
type: message.type === 'text' ? 'TEXT' : 'FILE',
|
|
177
|
-
text: message.text?.body,
|
|
178
|
-
media: message.image ? {
|
|
179
|
-
url: message.image.id,
|
|
180
|
-
mimeType: message.image.mime_type
|
|
181
|
-
} : undefined
|
|
182
|
-
},
|
|
183
|
-
timestamp: parseInt(message.timestamp) * 1000,
|
|
184
|
-
platform: 'whatsapp',
|
|
185
|
-
metadata: {
|
|
186
|
-
phoneNumberId: change.value.metadata.phone_number_id
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return messages;
|
|
195
|
-
},
|
|
196
|
-
|
|
197
|
-
// Required: Send message to WhatsApp
|
|
198
|
-
async sendMessage(ctx, message, config) {
|
|
199
|
-
const phoneNumberId = config.credentials.phoneNumberId;
|
|
200
|
-
const accessToken = config.credentials.accessToken;
|
|
201
|
-
|
|
202
|
-
const response = await fetch(
|
|
203
|
-
`https://graph.facebook.com/v18.0/${phoneNumberId}/messages`,
|
|
204
|
-
{
|
|
205
|
-
method: 'POST',
|
|
206
|
-
headers: {
|
|
207
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
208
|
-
'Content-Type': 'application/json'
|
|
209
|
-
},
|
|
210
|
-
body: JSON.stringify({
|
|
211
|
-
messaging_product: 'whatsapp',
|
|
212
|
-
to: message.to.id,
|
|
213
|
-
text: { body: message.content.text }
|
|
214
|
-
})
|
|
215
|
-
}
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
if (!response.ok) {
|
|
219
|
-
const error = await response.text();
|
|
220
|
-
return {
|
|
221
|
-
success: false,
|
|
222
|
-
error: new Error(`WhatsApp API error: ${error}`)
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const result = await response.json();
|
|
227
|
-
return {
|
|
228
|
-
success: true,
|
|
229
|
-
messageId: result.messages[0].id,
|
|
230
|
-
metadata: { phoneNumberId }
|
|
231
|
-
};
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
// Optional: Validate webhook signature
|
|
235
|
-
validateSignature(webhook) {
|
|
236
|
-
const signature = webhook.headers['x-hub-signature-256'];
|
|
237
|
-
const secret = process.env.WHATSAPP_WEBHOOK_SECRET;
|
|
238
|
-
|
|
239
|
-
if (!signature || !secret) return false;
|
|
240
|
-
|
|
241
|
-
const expectedSignature = 'sha256=' +
|
|
242
|
-
crypto.createHmac('sha256', secret)
|
|
243
|
-
.update(webhook.rawBody || '')
|
|
244
|
-
.digest('hex');
|
|
245
|
-
|
|
246
|
-
return SecurityHelpers.secureCompare(signature, expectedSignature);
|
|
247
|
-
},
|
|
248
|
-
|
|
249
|
-
// Optional: Handle webhook verification
|
|
250
|
-
verifyWebhook(params) {
|
|
251
|
-
const { mode, token, challenge } = params;
|
|
252
|
-
const verifyToken = process.env.WHATSAPP_VERIFY_TOKEN;
|
|
253
|
-
|
|
254
|
-
if (mode === 'subscribe' && token === verifyToken) {
|
|
255
|
-
return challenge;
|
|
256
|
-
}
|
|
257
|
-
return null;
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
// Optional: Custom health check
|
|
261
|
-
async checkHealth(ctx, config) {
|
|
262
|
-
try {
|
|
263
|
-
const response = await fetch(
|
|
264
|
-
`https://graph.facebook.com/v18.0/${config.credentials.phoneNumberId}`,
|
|
265
|
-
{
|
|
266
|
-
headers: {
|
|
267
|
-
'Authorization': `Bearer ${config.credentials.accessToken}`
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
return {
|
|
273
|
-
status: response.ok ? 'healthy' : 'unhealthy',
|
|
274
|
-
message: response.ok ? 'WhatsApp API accessible' : 'WhatsApp API error',
|
|
275
|
-
details: {
|
|
276
|
-
statusCode: response.status,
|
|
277
|
-
timestamp: new Date().toISOString()
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
} catch (error) {
|
|
281
|
-
return {
|
|
282
|
-
status: 'unhealthy',
|
|
283
|
-
message: error.message,
|
|
284
|
-
details: { timestamp: new Date().toISOString() }
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### Advanced Integration with Datasources and Cache
|
|
292
|
-
|
|
293
|
-
```typescript
|
|
294
|
-
import {defineIntegration} from '@pkg/sdk';
|
|
295
|
-
import {WhatsAppDatasource} from './datasources';
|
|
296
|
-
|
|
297
|
-
export default defineIntegration({
|
|
298
|
-
name: 'whatsapp',
|
|
299
|
-
spec: whatsappIntegration,
|
|
300
|
-
|
|
301
|
-
// Configure per-service datasources
|
|
302
|
-
datasources: {
|
|
303
|
-
whatsapp: WhatsAppDatasource
|
|
304
|
-
},
|
|
305
|
-
|
|
306
|
-
// Configure per-service cache
|
|
307
|
-
cache: {
|
|
308
|
-
redisUrl: process.env.WHATSAPP_REDIS_URL,
|
|
309
|
-
ttl: 5 * 60 * 1000, // 5 minutes
|
|
310
|
-
namespace: 'whatsapp'
|
|
311
|
-
},
|
|
312
|
-
|
|
313
|
-
async normalize(webhook) {
|
|
314
|
-
// Access datasource via context in actions
|
|
315
|
-
// this.datasources is not available in methods
|
|
316
|
-
// Use dependency injection pattern instead
|
|
317
|
-
return await this.processWebhookMessages(webhook);
|
|
318
|
-
},
|
|
319
|
-
|
|
320
|
-
async sendMessage(ctx, message, config) {
|
|
321
|
-
// Use cache for rate limiting
|
|
322
|
-
const rateLimitKey = `rate_limit:${config.credentials.phoneNumberId}`;
|
|
323
|
-
const currentCount = await ctx.cache.get(rateLimitKey) || 0;
|
|
324
|
-
|
|
325
|
-
if (currentCount >= 1000) {
|
|
326
|
-
return {
|
|
327
|
-
success: false,
|
|
328
|
-
error: new Error('Rate limit exceeded')
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Send message...
|
|
333
|
-
const result = await this.sendToWhatsApp(message, config);
|
|
334
|
-
|
|
335
|
-
// Update rate limit counter
|
|
336
|
-
await ctx.cache.set(rateLimitKey, currentCount + 1, 60 * 60 * 1000);
|
|
337
|
-
|
|
338
|
-
return result;
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
## Event Flow
|
|
344
|
-
|
|
345
|
-
### Inbound Message Flow
|
|
346
|
-
|
|
347
|
-
1. **Webhook Received** → `i_receiveWebhook` action
|
|
348
|
-
2. **Security Validation** → Timestamp, signature, payload checks
|
|
349
|
-
3. **Message Normalization** → Convert to unified format
|
|
350
|
-
4. **Event Emission** → `integration.message.received` event
|
|
351
|
-
5. **Message Processing** → Application-specific logic
|
|
352
|
-
6. **Response Generation** → Optional automated responses
|
|
353
|
-
|
|
354
|
-
### Outbound Message Flow
|
|
355
|
-
|
|
356
|
-
1. **Send Request** → `i_sendMessage` action
|
|
357
|
-
2. **Message Transformation** → Convert to platform format
|
|
358
|
-
3. **Retry Logic** → Exponential backoff for failures
|
|
359
|
-
4. **Platform API Call** → Send via platform API
|
|
360
|
-
5. **Event Emission** → `integration.message.sent` or `integration.message.failed`
|
|
361
|
-
6. **Status Tracking** → Update message delivery status
|
|
362
|
-
|
|
363
|
-
## Security Features
|
|
364
|
-
|
|
365
|
-
### Webhook Validation
|
|
366
|
-
|
|
367
|
-
- **Signature Verification**: HMAC-SHA256 validation using platform secrets
|
|
368
|
-
- **Replay Attack Prevention**: Timestamp validation (5-minute window)
|
|
369
|
-
- **Timing Attack Protection**: Constant-time string comparison
|
|
370
|
-
- **Correlation IDs**: Request tracking for debugging and audit trails
|
|
371
|
-
|
|
372
|
-
### Best Practices
|
|
373
|
-
|
|
374
|
-
1. **Always validate webhook signatures** in production
|
|
375
|
-
2. **Use environment variables** for secrets and tokens
|
|
376
|
-
3. **Implement proper error handling** with structured errors
|
|
377
|
-
4. **Add correlation IDs** for request tracking
|
|
378
|
-
5. **Use retry logic** for transient failures
|
|
379
|
-
6. **Monitor health endpoints** for platform availability
|
|
380
|
-
7. **Cache frequently accessed data** to reduce API calls
|
|
381
|
-
|
|
382
|
-
## Infrastructure Integration
|
|
383
|
-
|
|
384
|
-
Integration services automatically inherit:
|
|
385
|
-
|
|
386
|
-
- **Datasource Access**: Database connections, external APIs
|
|
387
|
-
- **Cache Layer**: Redis/in-memory caching with TTL
|
|
388
|
-
- **Permission System**: Role-based access control
|
|
389
|
-
- **Context Helpers**: Common utilities and operations
|
|
390
|
-
- **Memoization**: Action result caching
|
|
391
|
-
- **Health Monitoring**: Automated health checks
|
|
392
|
-
- **Event System**: Pub/sub event broadcasting
|
|
393
|
-
|
|
394
|
-
This architecture provides a robust, scalable foundation for building messaging platform integrations while maintaining consistency, security, and observability across all platforms.
|