@hiliosai/sdk 0.1.2 → 0.1.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.
- package/INTEGRATION_SERVICE.md +53 -53
- package/package.json +1 -1
- package/src/service/define-integration.ts +22 -26
- package/src/types/integration.ts +1 -1
- package/src/types/message.ts +2 -3
- package/src/types/service.ts +3 -3
package/INTEGRATION_SERVICE.md
CHANGED
|
@@ -9,7 +9,7 @@ Integration Services are specialized Moleculer services that provide a standardi
|
|
|
9
9
|
The Integration Service pattern solves several key challenges:
|
|
10
10
|
|
|
11
11
|
1. **Platform Abstraction**: Provides unified interface for different messaging platforms
|
|
12
|
-
2. **Webhook Security**: Implements signature validation and replay attack prevention
|
|
12
|
+
2. **Webhook Security**: Implements signature validation and replay attack prevention
|
|
13
13
|
3. **Message Normalization**: Converts platform-specific messages to unified format
|
|
14
14
|
4. **Retry Logic**: Handles transient failures with exponential backoff
|
|
15
15
|
5. **Event Broadcasting**: Emits standardized events for message processing
|
|
@@ -25,15 +25,15 @@ graph TB
|
|
|
25
25
|
DC[Discord]
|
|
26
26
|
SL[Slack]
|
|
27
27
|
end
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
subgraph "Integration Layer"
|
|
30
30
|
subgraph "Integration Services"
|
|
31
31
|
WAS[WhatsApp Service]
|
|
32
|
-
TGS[Telegram Service]
|
|
32
|
+
TGS[Telegram Service]
|
|
33
33
|
DCS[Discord Service]
|
|
34
34
|
SLS[Slack Service]
|
|
35
35
|
end
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
subgraph "Service Actions"
|
|
38
38
|
RW[i_receiveWebhook]
|
|
39
39
|
SM[i_sendMessage]
|
|
@@ -43,20 +43,20 @@ graph TB
|
|
|
43
43
|
VC[i_validateCredentials]
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
subgraph "Core Application"
|
|
48
48
|
subgraph "Message Processing"
|
|
49
49
|
MP[Message Processor]
|
|
50
50
|
MR[Message Router]
|
|
51
51
|
MS[Message Store]
|
|
52
52
|
end
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
subgraph "Event System"
|
|
55
55
|
EB[Event Bus]
|
|
56
56
|
EH[Event Handlers]
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
subgraph "Infrastructure"
|
|
61
61
|
DS[Datasources]
|
|
62
62
|
CA[Cache]
|
|
@@ -69,29 +69,29 @@ graph TB
|
|
|
69
69
|
TG -->|Webhook| TGS
|
|
70
70
|
DC -->|Webhook| DCS
|
|
71
71
|
SL -->|Webhook| SLS
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
%% Integration service actions
|
|
74
74
|
WAS --> RW
|
|
75
75
|
WAS --> SM
|
|
76
76
|
WAS --> HC
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
%% Service infrastructure
|
|
79
79
|
WAS --> DS
|
|
80
80
|
TGS --> CA
|
|
81
81
|
DCS --> PE
|
|
82
82
|
SLS --> CH
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
%% Event flows
|
|
85
85
|
RW -->|integration.message.received| EB
|
|
86
86
|
SM -->|integration.message.sent| EB
|
|
87
87
|
SM -->|integration.message.failed| EB
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
%% Processing flows
|
|
90
90
|
EB --> EH
|
|
91
91
|
EH --> MP
|
|
92
92
|
MP --> MR
|
|
93
93
|
MR --> MS
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
%% Response flows
|
|
96
96
|
MP -->|Send Response| SM
|
|
97
97
|
```
|
|
@@ -109,29 +109,29 @@ graph TB
|
|
|
109
109
|
|
|
110
110
|
All integration services automatically get these actions:
|
|
111
111
|
|
|
112
|
-
| Action
|
|
113
|
-
|
|
114
|
-
| `i_receiveWebhook`
|
|
115
|
-
| `i_sendMessage`
|
|
116
|
-
| `i_healthCheck`
|
|
117
|
-
| `i_verifyWebhook`
|
|
118
|
-
| `i_status`
|
|
119
|
-
| `i_validateCredentials` | `POST /validate` | Validate platform credentials
|
|
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
120
|
|
|
121
121
|
### Service Methods
|
|
122
122
|
|
|
123
123
|
Integration-specific logic is implemented as service methods:
|
|
124
124
|
|
|
125
|
-
| Method
|
|
126
|
-
|
|
127
|
-
| `normalize()`
|
|
128
|
-
| `sendMessage()`
|
|
129
|
-
| `transform()`
|
|
130
|
-
| `validateWebhook()`
|
|
131
|
-
| `validateSignature()`
|
|
132
|
-
| `verifyWebhook()`
|
|
133
|
-
| `checkHealth()`
|
|
134
|
-
| `validateCredentials()` | Validate platform credentials
|
|
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
135
|
|
|
136
136
|
## Usage Example
|
|
137
137
|
|
|
@@ -139,9 +139,9 @@ Integration-specific logic is implemented as service methods:
|
|
|
139
139
|
|
|
140
140
|
```typescript
|
|
141
141
|
import {defineIntegration} from '@pkg/sdk';
|
|
142
|
-
import type {
|
|
142
|
+
import type {BaseSpec, NormalizedMessage, WebhookEvent} from '@pkg/sdk';
|
|
143
143
|
|
|
144
|
-
const whatsappIntegration:
|
|
144
|
+
const whatsappIntegration: BaseSpec = {
|
|
145
145
|
id: 'whatsapp-business',
|
|
146
146
|
name: 'WhatsApp Business',
|
|
147
147
|
platform: 'whatsapp',
|
|
@@ -152,13 +152,13 @@ const whatsappIntegration: BaseIntegration = {
|
|
|
152
152
|
|
|
153
153
|
export default defineIntegration({
|
|
154
154
|
name: 'whatsapp',
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
spec: whatsappIntegration,
|
|
156
|
+
|
|
157
157
|
// Required: Convert WhatsApp webhook to normalized message
|
|
158
158
|
async normalize(webhook: WebhookEvent): Promise<NormalizedMessage[]> {
|
|
159
159
|
const messages: NormalizedMessage[] = [];
|
|
160
|
-
|
|
161
|
-
for (const entry of webhook.
|
|
160
|
+
|
|
161
|
+
for (const entry of webhook.payload.entry || []) {
|
|
162
162
|
for (const change of entry.changes || []) {
|
|
163
163
|
if (change.field === 'messages') {
|
|
164
164
|
for (const message of change.value.messages || []) {
|
|
@@ -190,7 +190,7 @@ export default defineIntegration({
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
|
-
|
|
193
|
+
|
|
194
194
|
return messages;
|
|
195
195
|
},
|
|
196
196
|
|
|
@@ -198,7 +198,7 @@ export default defineIntegration({
|
|
|
198
198
|
async sendMessage(ctx, message, config) {
|
|
199
199
|
const phoneNumberId = config.credentials.phoneNumberId;
|
|
200
200
|
const accessToken = config.credentials.accessToken;
|
|
201
|
-
|
|
201
|
+
|
|
202
202
|
const response = await fetch(
|
|
203
203
|
`https://graph.facebook.com/v18.0/${phoneNumberId}/messages`,
|
|
204
204
|
{
|
|
@@ -235,14 +235,14 @@ export default defineIntegration({
|
|
|
235
235
|
validateSignature(webhook) {
|
|
236
236
|
const signature = webhook.headers['x-hub-signature-256'];
|
|
237
237
|
const secret = process.env.WHATSAPP_WEBHOOK_SECRET;
|
|
238
|
-
|
|
238
|
+
|
|
239
239
|
if (!signature || !secret) return false;
|
|
240
|
-
|
|
241
|
-
const expectedSignature = 'sha256=' +
|
|
240
|
+
|
|
241
|
+
const expectedSignature = 'sha256=' +
|
|
242
242
|
crypto.createHmac('sha256', secret)
|
|
243
243
|
.update(webhook.rawBody || '')
|
|
244
244
|
.digest('hex');
|
|
245
|
-
|
|
245
|
+
|
|
246
246
|
return SecurityHelpers.secureCompare(signature, expectedSignature);
|
|
247
247
|
},
|
|
248
248
|
|
|
@@ -250,7 +250,7 @@ export default defineIntegration({
|
|
|
250
250
|
verifyWebhook(params) {
|
|
251
251
|
const { mode, token, challenge } = params;
|
|
252
252
|
const verifyToken = process.env.WHATSAPP_VERIFY_TOKEN;
|
|
253
|
-
|
|
253
|
+
|
|
254
254
|
if (mode === 'subscribe' && token === verifyToken) {
|
|
255
255
|
return challenge;
|
|
256
256
|
}
|
|
@@ -268,7 +268,7 @@ export default defineIntegration({
|
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
270
|
);
|
|
271
|
-
|
|
271
|
+
|
|
272
272
|
return {
|
|
273
273
|
status: response.ok ? 'healthy' : 'unhealthy',
|
|
274
274
|
message: response.ok ? 'WhatsApp API accessible' : 'WhatsApp API error',
|
|
@@ -296,13 +296,13 @@ import {WhatsAppDatasource} from './datasources';
|
|
|
296
296
|
|
|
297
297
|
export default defineIntegration({
|
|
298
298
|
name: 'whatsapp',
|
|
299
|
-
|
|
300
|
-
|
|
299
|
+
spec: whatsappIntegration,
|
|
300
|
+
|
|
301
301
|
// Configure per-service datasources
|
|
302
302
|
datasources: {
|
|
303
303
|
whatsapp: WhatsAppDatasource
|
|
304
304
|
},
|
|
305
|
-
|
|
305
|
+
|
|
306
306
|
// Configure per-service cache
|
|
307
307
|
cache: {
|
|
308
308
|
redisUrl: process.env.WHATSAPP_REDIS_URL,
|
|
@@ -321,20 +321,20 @@ export default defineIntegration({
|
|
|
321
321
|
// Use cache for rate limiting
|
|
322
322
|
const rateLimitKey = `rate_limit:${config.credentials.phoneNumberId}`;
|
|
323
323
|
const currentCount = await ctx.cache.get(rateLimitKey) || 0;
|
|
324
|
-
|
|
324
|
+
|
|
325
325
|
if (currentCount >= 1000) {
|
|
326
326
|
return {
|
|
327
327
|
success: false,
|
|
328
328
|
error: new Error('Rate limit exceeded')
|
|
329
329
|
};
|
|
330
330
|
}
|
|
331
|
-
|
|
331
|
+
|
|
332
332
|
// Send message...
|
|
333
333
|
const result = await this.sendToWhatsApp(message, config);
|
|
334
|
-
|
|
334
|
+
|
|
335
335
|
// Update rate limit counter
|
|
336
336
|
await ctx.cache.set(rateLimitKey, currentCount + 1, 60 * 60 * 1000);
|
|
337
|
-
|
|
337
|
+
|
|
338
338
|
return result;
|
|
339
339
|
}
|
|
340
340
|
});
|
|
@@ -345,7 +345,7 @@ export default defineIntegration({
|
|
|
345
345
|
### Inbound Message Flow
|
|
346
346
|
|
|
347
347
|
1. **Webhook Received** → `i_receiveWebhook` action
|
|
348
|
-
2. **Security Validation** → Timestamp, signature, payload checks
|
|
348
|
+
2. **Security Validation** → Timestamp, signature, payload checks
|
|
349
349
|
3. **Message Normalization** → Convert to unified format
|
|
350
350
|
4. **Event Emission** → `integration.message.received` event
|
|
351
351
|
5. **Message Processing** → Application-specific logic
|
|
@@ -391,4 +391,4 @@ Integration services automatically inherit:
|
|
|
391
391
|
- **Health Monitoring**: Automated health checks
|
|
392
392
|
- **Event System**: Pub/sub event broadcasting
|
|
393
393
|
|
|
394
|
-
This architecture provides a robust, scalable foundation for building messaging platform integrations while maintaining consistency, security, and observability across all platforms.
|
|
394
|
+
This architecture provides a robust, scalable foundation for building messaging platform integrations while maintaining consistency, security, and observability across all platforms.
|
package/package.json
CHANGED
|
@@ -91,15 +91,11 @@ export function defineIntegration<
|
|
|
91
91
|
i_receiveWebhook: {
|
|
92
92
|
rest: {
|
|
93
93
|
method: 'POST' as const,
|
|
94
|
-
path: '
|
|
94
|
+
path: '/:tenantId',
|
|
95
95
|
},
|
|
96
96
|
params: {
|
|
97
97
|
tenantId: 'string',
|
|
98
|
-
|
|
99
|
-
integrationId: 'string',
|
|
100
|
-
platform: 'string',
|
|
101
|
-
rawPayload: 'any',
|
|
102
|
-
rawBody: {type: 'string', optional: true},
|
|
98
|
+
payload: 'object',
|
|
103
99
|
headers: 'object',
|
|
104
100
|
timestamp: 'number',
|
|
105
101
|
},
|
|
@@ -181,20 +177,20 @@ export function defineIntegration<
|
|
|
181
177
|
() => config.sendMessage(ctx, message, integrationConfig),
|
|
182
178
|
3,
|
|
183
179
|
1000,
|
|
184
|
-
`Send message via ${config.
|
|
180
|
+
`Send message via ${config.spec.platform}`
|
|
185
181
|
);
|
|
186
182
|
|
|
187
183
|
// Emit event based on result
|
|
188
184
|
if (result.success) {
|
|
189
185
|
ctx.emit('integration.message.sent', {
|
|
190
186
|
messageId: result.messageId,
|
|
191
|
-
platform: config.
|
|
187
|
+
platform: config.spec.platform,
|
|
192
188
|
metadata: result.metadata,
|
|
193
189
|
});
|
|
194
190
|
} else {
|
|
195
191
|
ctx.emit('integration.message.failed', {
|
|
196
192
|
error: result.error,
|
|
197
|
-
platform: config.
|
|
193
|
+
platform: config.spec.platform,
|
|
198
194
|
message,
|
|
199
195
|
});
|
|
200
196
|
}
|
|
@@ -224,12 +220,12 @@ export function defineIntegration<
|
|
|
224
220
|
|
|
225
221
|
return {
|
|
226
222
|
status: 'healthy',
|
|
227
|
-
message: `${config.
|
|
223
|
+
message: `${config.spec.name} integration is running`,
|
|
228
224
|
details: {
|
|
229
|
-
|
|
230
|
-
version: config.
|
|
225
|
+
id: config.spec.id,
|
|
226
|
+
version: config.spec.version,
|
|
231
227
|
timestamp: new Date().toISOString(),
|
|
232
|
-
capabilities: config.
|
|
228
|
+
capabilities: config.spec.capabilities,
|
|
233
229
|
},
|
|
234
230
|
};
|
|
235
231
|
} catch (error: unknown) {
|
|
@@ -238,7 +234,7 @@ export function defineIntegration<
|
|
|
238
234
|
status: 'unhealthy',
|
|
239
235
|
message: err.message,
|
|
240
236
|
details: {
|
|
241
|
-
|
|
237
|
+
id: config.spec.id,
|
|
242
238
|
timestamp: new Date().toISOString(),
|
|
243
239
|
},
|
|
244
240
|
};
|
|
@@ -249,9 +245,10 @@ export function defineIntegration<
|
|
|
249
245
|
i_verifyWebhook: {
|
|
250
246
|
rest: {
|
|
251
247
|
method: 'GET' as const,
|
|
252
|
-
path: '
|
|
248
|
+
path: '/:tenantId',
|
|
253
249
|
},
|
|
254
250
|
params: {
|
|
251
|
+
tenantId: 'string',
|
|
255
252
|
mode: 'string',
|
|
256
253
|
token: 'string',
|
|
257
254
|
challenge: 'string',
|
|
@@ -259,7 +256,7 @@ export function defineIntegration<
|
|
|
259
256
|
handler(
|
|
260
257
|
ctx: AppContext<
|
|
261
258
|
TDatasources,
|
|
262
|
-
{mode: string; token: string; challenge: string}
|
|
259
|
+
{tenantId: string; mode: string; token: string; challenge: string}
|
|
263
260
|
>
|
|
264
261
|
): string {
|
|
265
262
|
if (config.verifyWebhook) {
|
|
@@ -277,12 +274,12 @@ export function defineIntegration<
|
|
|
277
274
|
},
|
|
278
275
|
handler(): object {
|
|
279
276
|
return {
|
|
280
|
-
id: config.
|
|
281
|
-
name: config.
|
|
282
|
-
platform: config.
|
|
283
|
-
version: config.
|
|
284
|
-
status: config.
|
|
285
|
-
capabilities: config.
|
|
277
|
+
id: config.spec.id,
|
|
278
|
+
name: config.spec.name,
|
|
279
|
+
platform: config.spec.platform,
|
|
280
|
+
version: config.spec.version,
|
|
281
|
+
status: config.spec.status,
|
|
282
|
+
capabilities: config.spec.capabilities,
|
|
286
283
|
};
|
|
287
284
|
},
|
|
288
285
|
},
|
|
@@ -316,7 +313,7 @@ export function defineIntegration<
|
|
|
316
313
|
datasources: config.datasources,
|
|
317
314
|
metadata: {
|
|
318
315
|
...config.metadata,
|
|
319
|
-
|
|
316
|
+
spec: config.spec,
|
|
320
317
|
},
|
|
321
318
|
actions,
|
|
322
319
|
events: config.events ?? {},
|
|
@@ -338,7 +335,6 @@ export function defineIntegration<
|
|
|
338
335
|
validateSignature: config.validateSignature,
|
|
339
336
|
}),
|
|
340
337
|
},
|
|
341
|
-
hooks: config.hooks ?? {},
|
|
342
338
|
created: config.created,
|
|
343
339
|
started: config.started,
|
|
344
340
|
stopped: config.stopped,
|
|
@@ -347,7 +343,7 @@ export function defineIntegration<
|
|
|
347
343
|
// Return the service schema - integration methods are available via service methods
|
|
348
344
|
return {
|
|
349
345
|
...baseService,
|
|
350
|
-
// Only add the integration
|
|
351
|
-
|
|
346
|
+
// Only add the integration spec
|
|
347
|
+
spec: config.spec,
|
|
352
348
|
} as IntegrationServiceSchema<TPlatformMessage, TSettings>;
|
|
353
349
|
}
|
package/src/types/integration.ts
CHANGED
package/src/types/message.ts
CHANGED
|
@@ -74,7 +74,7 @@ export interface NormalizedMessage {
|
|
|
74
74
|
|
|
75
75
|
export interface PlatformMessage {
|
|
76
76
|
platform: IntegrationPlatform;
|
|
77
|
-
|
|
77
|
+
payload: any;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
export interface WebhookEvent {
|
|
@@ -82,8 +82,7 @@ export interface WebhookEvent {
|
|
|
82
82
|
channelId: string;
|
|
83
83
|
integrationId: string;
|
|
84
84
|
platform: IntegrationPlatform;
|
|
85
|
-
|
|
86
|
-
rawBody?: string;
|
|
85
|
+
payload: any;
|
|
87
86
|
headers: Record<string, string>;
|
|
88
87
|
timestamp: number;
|
|
89
88
|
}
|
package/src/types/service.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
|
|
10
10
|
import type {DatasourceConstructorRegistry} from '../middlewares/datasource.middleware';
|
|
11
11
|
import type {AppContext} from './context';
|
|
12
|
-
import type {
|
|
12
|
+
import type {BaseSpec, IntegrationConfig} from './integration';
|
|
13
13
|
import type {
|
|
14
14
|
NormalizedMessage,
|
|
15
15
|
PlatformMessage,
|
|
@@ -134,7 +134,7 @@ export interface IntegrationServiceConfig<
|
|
|
134
134
|
TDatasources = unknown
|
|
135
135
|
> extends ServiceConfig<TSettings, TDatasources> {
|
|
136
136
|
name: string;
|
|
137
|
-
|
|
137
|
+
spec: BaseSpec;
|
|
138
138
|
|
|
139
139
|
// Core integration methods
|
|
140
140
|
normalize(webhook: WebhookEvent): Promise<NormalizedMessage[]>;
|
|
@@ -177,7 +177,7 @@ export interface IntegrationServiceSchema<
|
|
|
177
177
|
TPlatformMessage extends PlatformMessage = PlatformMessage,
|
|
178
178
|
TSettings = unknown
|
|
179
179
|
> extends MoleculerServiceSchema<TSettings> {
|
|
180
|
-
|
|
180
|
+
spec: BaseSpec;
|
|
181
181
|
normalize(webhook: WebhookEvent): Promise<NormalizedMessage[]>;
|
|
182
182
|
transform?(
|
|
183
183
|
message: NormalizedMessage,
|