@g2crowd/buyer-intent-provider-sdk 0.4.0 → 0.5.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 +105 -9
- package/package.json +5 -4
- package/src/browser/components/tracker.js +4 -0
- package/src/browser/dom.js +4 -4
- package/src/browser/elements/base.js +3 -3
- package/src/browser/index.js +1 -1
- package/src/browser/sdk.js +1 -1
- package/src/index.d.ts +4 -2
- package/src/index.js +1 -0
- package/src/react/index.d.ts +17 -15
- package/src/server/handlers.js +102 -28
- package/src/server/index.js +2 -0
- package/src/server.js +3 -0
package/README.md
CHANGED
|
@@ -270,21 +270,64 @@ PARTNER_ID=partner-a KAFKA_BROKERS=broker1:9092,broker2:9092 \
|
|
|
270
270
|
buyer-intent-server
|
|
271
271
|
```
|
|
272
272
|
|
|
273
|
-
Environment variables: `PORT` (default 3000), `PARTNER_ID`, `KAFKA_BROKERS` (comma-separated), `TOPIC_PREFIX`, `KAFKA_TOPIC`, `KAFKA_CLIENT_ID`, `USE_DEV_LOGGER=true`.
|
|
273
|
+
Environment variables: `PORT` (default 3000), `PARTNER_ID`, `KAFKA_BROKERS` (comma-separated), `KAFKA_BRIDGE_URL` (Strimzi HTTP bridge), `KAFKA_BRIDGE_USERNAME`, `KAFKA_BRIDGE_PASSWORD`, `TOPIC_PREFIX`, `KAFKA_TOPIC`, `KAFKA_CLIENT_ID`, `USE_DEV_LOGGER=true`.
|
|
274
274
|
|
|
275
275
|
### Custom Kafka Producer
|
|
276
276
|
|
|
277
277
|
```ts
|
|
278
|
-
import {
|
|
278
|
+
import {
|
|
279
|
+
createNextRouteHandler,
|
|
280
|
+
createKafkaProducer,
|
|
281
|
+
} from '@g2crowd/buyer-intent-provider-sdk/server';
|
|
282
|
+
|
|
283
|
+
const POST = createNextRouteHandler({
|
|
284
|
+
producer: createKafkaProducer({
|
|
285
|
+
brokers: (process.env.KAFKA_BROKERS || '').split(','),
|
|
286
|
+
clientId: 'partner-app',
|
|
287
|
+
}),
|
|
288
|
+
partnerId: process.env.PARTNER_ID,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
export { POST };
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
You can also pass a raw `kafkajs` producer or any object with a `send({ topic, messages })` method.
|
|
295
|
+
|
|
296
|
+
### HTTP Bridge (Strimzi)
|
|
297
|
+
|
|
298
|
+
Use `KAFKA_BRIDGE_URL` to produce via an HTTP bridge instead of connecting to Kafka directly. No `kafkajs` dependency required.
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
PARTNER_ID=partner-a KAFKA_BRIDGE_URL=http://my-bridge:8080 \
|
|
302
|
+
buyer-intent-server
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Or pass it programmatically:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
279
308
|
import { createNextRouteHandler } from '@g2crowd/buyer-intent-provider-sdk/server';
|
|
280
309
|
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
310
|
+
const POST = createNextRouteHandler({
|
|
311
|
+
kafkaBridgeUrl: process.env.KAFKA_BRIDGE_URL,
|
|
312
|
+
partnerId: process.env.PARTNER_ID,
|
|
284
313
|
});
|
|
285
314
|
|
|
315
|
+
export { POST };
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
For custom headers (e.g. auth tokens):
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
import {
|
|
322
|
+
createNextRouteHandler,
|
|
323
|
+
createHttpProducer,
|
|
324
|
+
} from '@g2crowd/buyer-intent-provider-sdk/server';
|
|
325
|
+
|
|
286
326
|
const POST = createNextRouteHandler({
|
|
287
|
-
producer:
|
|
327
|
+
producer: createHttpProducer({
|
|
328
|
+
bridgeUrl: process.env.KAFKA_BRIDGE_URL,
|
|
329
|
+
headers: { Authorization: `Bearer ${process.env.BRIDGE_TOKEN}` },
|
|
330
|
+
}),
|
|
288
331
|
partnerId: process.env.PARTNER_ID,
|
|
289
332
|
});
|
|
290
333
|
|
|
@@ -309,6 +352,10 @@ export { POST };
|
|
|
309
352
|
|
|
310
353
|
## Event Payload
|
|
311
354
|
|
|
355
|
+
The client sends a beacon to the activity endpoint. The server handler wraps it into a composite event and writes it to Kafka.
|
|
356
|
+
|
|
357
|
+
### Client → Server (sendBeacon body)
|
|
358
|
+
|
|
312
359
|
```json
|
|
313
360
|
{
|
|
314
361
|
"name": "$view",
|
|
@@ -320,15 +367,64 @@ export { POST };
|
|
|
320
367
|
"user_type": "standard",
|
|
321
368
|
"distinct_id": "visitor-uuid",
|
|
322
369
|
"origin": "yoursite.com",
|
|
323
|
-
"source_location": "ProductsController#show"
|
|
370
|
+
"source_location": "ProductsController#show",
|
|
371
|
+
"context": {}
|
|
324
372
|
},
|
|
325
373
|
"visit": {
|
|
326
374
|
"properties": {
|
|
327
375
|
"landing_page": "https://yoursite.com/products/acme-crm",
|
|
328
376
|
"referrer": "https://google.com/",
|
|
329
|
-
"user_agent": "Mozilla/5.0",
|
|
330
|
-
"utm_source": "newsletter"
|
|
377
|
+
"user_agent": "Mozilla/5.0 ...",
|
|
378
|
+
"utm_source": "newsletter",
|
|
379
|
+
"utm_medium": "email",
|
|
380
|
+
"utm_campaign": "spring-2026",
|
|
381
|
+
"utm_term": "crm+software",
|
|
382
|
+
"utm_content": "hero-cta"
|
|
331
383
|
}
|
|
332
384
|
}
|
|
333
385
|
}
|
|
334
386
|
```
|
|
387
|
+
|
|
388
|
+
### Server → Kafka (composite event)
|
|
389
|
+
|
|
390
|
+
The server enriches the client payload with server-side tokens, timestamps, and IP before writing to Kafka. This is the shape written to the `intent_events_{partnerId}` topic:
|
|
391
|
+
|
|
392
|
+
```json
|
|
393
|
+
{
|
|
394
|
+
"event": {
|
|
395
|
+
"id": "e0c1f2a3-...",
|
|
396
|
+
"name": "$view",
|
|
397
|
+
"time": "2026-02-17T14:20:00.000Z",
|
|
398
|
+
"properties": {
|
|
399
|
+
"product_ids": [123],
|
|
400
|
+
"category_ids": [45],
|
|
401
|
+
"tag": "products.show",
|
|
402
|
+
"url": "https://yoursite.com/products/acme-crm",
|
|
403
|
+
"user_type": "standard",
|
|
404
|
+
"distinct_id": "visitor-uuid",
|
|
405
|
+
"origin": "yoursite.com",
|
|
406
|
+
"source_location": "ProductsController#show",
|
|
407
|
+
"context": {}
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
"visit": {
|
|
411
|
+
"visit_token": "a1b2c3d4-...",
|
|
412
|
+
"visitor_token": "f5e6d7c8-...",
|
|
413
|
+
"started_at": "2026-02-17T14:20:00.000Z",
|
|
414
|
+
"created_at": "2026-02-17T14:20:00.000Z",
|
|
415
|
+
"properties": {
|
|
416
|
+
"landing_page": "https://yoursite.com/products/acme-crm",
|
|
417
|
+
"referrer": "https://google.com/",
|
|
418
|
+
"user_agent": "Mozilla/5.0 ...",
|
|
419
|
+
"ip": "203.0.113.42",
|
|
420
|
+
"utm_source": "newsletter",
|
|
421
|
+
"utm_medium": "email",
|
|
422
|
+
"utm_campaign": "spring-2026",
|
|
423
|
+
"utm_term": "crm+software",
|
|
424
|
+
"utm_content": "hero-cta"
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
The server sets `visit_token` and `visitor_token` as `httpOnly` cookies so repeat visits from the same browser share stable tokens.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@g2crowd/buyer-intent-provider-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Buyer intent tracking SDK with pageview defaults",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -26,17 +26,18 @@
|
|
|
26
26
|
"url": "https://github.com/g2crowd/buyer-intent-provider-js"
|
|
27
27
|
},
|
|
28
28
|
"publishConfig": {
|
|
29
|
-
"access": "
|
|
29
|
+
"access": "restricted",
|
|
30
|
+
"registry": "https://npm.pkg.github.com"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"kafkajs": "^2.2.4",
|
|
33
34
|
"react": ">=17"
|
|
34
35
|
},
|
|
35
36
|
"peerDependenciesMeta": {
|
|
36
|
-
"
|
|
37
|
+
"kafkajs": {
|
|
37
38
|
"optional": true
|
|
38
39
|
},
|
|
39
|
-
"
|
|
40
|
+
"react": {
|
|
40
41
|
"optional": true
|
|
41
42
|
}
|
|
42
43
|
},
|
|
@@ -4,6 +4,7 @@ function buildViewAttrs({
|
|
|
4
4
|
productId,
|
|
5
5
|
productIds,
|
|
6
6
|
categoryId,
|
|
7
|
+
categoryIds,
|
|
7
8
|
tag,
|
|
8
9
|
sourceLocation,
|
|
9
10
|
context,
|
|
@@ -26,6 +27,7 @@ function buildViewAttrs({
|
|
|
26
27
|
if (productId != null) attrs['product-id'] = String(productId);
|
|
27
28
|
if (productIds) attrs['product-ids'] = JSON.stringify(productIds);
|
|
28
29
|
if (categoryId != null) attrs['category-id'] = String(categoryId);
|
|
30
|
+
if (categoryIds) attrs['category-ids'] = JSON.stringify(categoryIds);
|
|
29
31
|
|
|
30
32
|
return attrs;
|
|
31
33
|
}
|
|
@@ -35,6 +37,7 @@ function buildClickAttrs({
|
|
|
35
37
|
productId,
|
|
36
38
|
productIds,
|
|
37
39
|
categoryId,
|
|
40
|
+
categoryIds,
|
|
38
41
|
sourceLocation,
|
|
39
42
|
context,
|
|
40
43
|
origin,
|
|
@@ -55,6 +58,7 @@ function buildClickAttrs({
|
|
|
55
58
|
if (productId != null) attrs['product-id'] = String(productId);
|
|
56
59
|
if (productIds) attrs['product-ids'] = JSON.stringify(productIds);
|
|
57
60
|
if (categoryId != null) attrs['category-id'] = String(categoryId);
|
|
61
|
+
if (categoryIds) attrs['category-ids'] = JSON.stringify(categoryIds);
|
|
58
62
|
|
|
59
63
|
return attrs;
|
|
60
64
|
}
|
package/src/browser/dom.js
CHANGED
|
@@ -101,9 +101,9 @@ function parseIds(raw) {
|
|
|
101
101
|
if (!raw) return [];
|
|
102
102
|
try {
|
|
103
103
|
const parsed = JSON.parse(raw);
|
|
104
|
-
if (Array.isArray(parsed)) return parsed
|
|
104
|
+
if (Array.isArray(parsed)) return parsed;
|
|
105
105
|
} catch (_e) {}
|
|
106
|
-
return raw.split(',').map((s) =>
|
|
106
|
+
return raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
export function readSessionConfig(element) {
|
|
@@ -139,7 +139,7 @@ export function collectSubjectIds(viewElement) {
|
|
|
139
139
|
if (pids) {
|
|
140
140
|
productIds.push(...parseIds(pids));
|
|
141
141
|
} else if (pid) {
|
|
142
|
-
productIds.push(
|
|
142
|
+
productIds.push(pid);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
const cids = subject.getAttribute('category-ids');
|
|
@@ -147,7 +147,7 @@ export function collectSubjectIds(viewElement) {
|
|
|
147
147
|
if (cids) {
|
|
148
148
|
categoryIds.push(...parseIds(cids));
|
|
149
149
|
} else if (cid) {
|
|
150
|
-
categoryIds.push(
|
|
150
|
+
categoryIds.push(cid);
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -9,7 +9,7 @@ function parseIds(raw) {
|
|
|
9
9
|
const parsed = JSON.parse(raw);
|
|
10
10
|
if (Array.isArray(parsed)) return parsed;
|
|
11
11
|
} catch (_e) {}
|
|
12
|
-
return raw.split(',').map((s) =>
|
|
12
|
+
return raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function readIntentData(element) {
|
|
@@ -21,11 +21,11 @@ function readIntentData(element) {
|
|
|
21
21
|
if (productIds) {
|
|
22
22
|
data.productIds = parseIds(productIds);
|
|
23
23
|
} else if (productId) {
|
|
24
|
-
data.productIds = [
|
|
24
|
+
data.productIds = [productId];
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (categoryId) {
|
|
28
|
-
data.categoryIds = [
|
|
28
|
+
data.categoryIds = [categoryId];
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const tag = element.getAttribute('tag');
|
package/src/browser/index.js
CHANGED
package/src/browser/sdk.js
CHANGED
|
@@ -150,7 +150,7 @@ export function createBuyerIntentSDK() {
|
|
|
150
150
|
function init(options = {}) {
|
|
151
151
|
activityEndpoint = options.activityEndpoint || activityEndpoint;
|
|
152
152
|
origin = options.origin || origin;
|
|
153
|
-
viewName = options.viewName ||
|
|
153
|
+
viewName = options.viewName || viewName;
|
|
154
154
|
userType = options.userType || userType;
|
|
155
155
|
|
|
156
156
|
if (options.distinctId) {
|
package/src/index.d.ts
CHANGED
|
@@ -78,9 +78,10 @@ export type BuyerIntentInitOptions = {
|
|
|
78
78
|
userType?: UserType;
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
+
export type SubjectId = number | string;
|
|
81
82
|
export type BuyerIntentTagOptions = {
|
|
82
|
-
productIds?:
|
|
83
|
-
categoryIds?:
|
|
83
|
+
productIds?: SubjectId[];
|
|
84
|
+
categoryIds?: SubjectId[];
|
|
84
85
|
tag?: string;
|
|
85
86
|
sourceLocation?: string;
|
|
86
87
|
context?: Record<string, unknown>;
|
|
@@ -113,6 +114,7 @@ export type BuyerIntentSDK = {
|
|
|
113
114
|
setBaseProperties: (options?: BuyerIntentBaseOptions) => void;
|
|
114
115
|
setVisitProperties: (options?: BuyerIntentVisitProperties) => void;
|
|
115
116
|
setActivityEndpoint: (endpoint: string) => void;
|
|
117
|
+
listenForElements: () => void;
|
|
116
118
|
trackNextRouter: (nextRouter: {
|
|
117
119
|
events?: {
|
|
118
120
|
on: (event: string, handler: () => void) => void;
|
package/src/index.js
CHANGED
package/src/react/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as React from 'react';
|
|
2
|
-
import type { UserType } from '../index';
|
|
2
|
+
import type { UserType, SubjectId } from '../index';
|
|
3
3
|
|
|
4
4
|
type CommonProps = {
|
|
5
5
|
sourceLocation?: string;
|
|
@@ -20,30 +20,32 @@ export type SessionProviderProps = {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
export type SubjectTrackerProps = {
|
|
23
|
-
productId?:
|
|
24
|
-
categoryId?:
|
|
25
|
-
productIds?:
|
|
26
|
-
categoryIds?:
|
|
23
|
+
productId?: SubjectId;
|
|
24
|
+
categoryId?: SubjectId;
|
|
25
|
+
productIds?: SubjectId[];
|
|
26
|
+
categoryIds?: SubjectId[];
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
export type ViewTrackerProps = CommonProps & {
|
|
30
30
|
tag?: string;
|
|
31
|
-
productId?:
|
|
32
|
-
productIds?:
|
|
33
|
-
categoryId?:
|
|
31
|
+
productId?: SubjectId;
|
|
32
|
+
productIds?: SubjectId[];
|
|
33
|
+
categoryId?: SubjectId;
|
|
34
|
+
categoryIds?: SubjectId[];
|
|
34
35
|
};
|
|
35
36
|
|
|
36
37
|
export type ClickTrackerProps = CommonProps & {
|
|
37
38
|
eventName?: string;
|
|
38
|
-
productId?:
|
|
39
|
-
productIds?:
|
|
40
|
-
categoryId?:
|
|
39
|
+
productId?: SubjectId;
|
|
40
|
+
productIds?: SubjectId[];
|
|
41
|
+
categoryId?: SubjectId;
|
|
42
|
+
categoryIds?: SubjectId[];
|
|
41
43
|
};
|
|
42
44
|
|
|
43
|
-
type ProductIdViewProps = CommonProps & { productId:
|
|
44
|
-
type CategoryViewProps = CommonProps & { categoryId:
|
|
45
|
-
type ProductIdsViewProps = CommonProps & { productIds:
|
|
46
|
-
type ProductIdClickProps = CommonProps & { productId:
|
|
45
|
+
type ProductIdViewProps = CommonProps & { productId: SubjectId };
|
|
46
|
+
type CategoryViewProps = CommonProps & { categoryId: SubjectId };
|
|
47
|
+
type ProductIdsViewProps = CommonProps & { productIds: SubjectId[] };
|
|
48
|
+
type ProductIdClickProps = CommonProps & { productId: SubjectId };
|
|
47
49
|
|
|
48
50
|
export const SessionProvider: React.FC<SessionProviderProps>;
|
|
49
51
|
export const SubjectTracker: React.FC<SubjectTrackerProps>;
|
package/src/server/handlers.js
CHANGED
|
@@ -71,38 +71,38 @@ function defaultGetIp({ headers, request }) {
|
|
|
71
71
|
return undefined;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
function
|
|
74
|
+
function resolveProducer({
|
|
75
|
+
producer,
|
|
76
|
+
kafkaBrokers,
|
|
77
|
+
kafkaClientId,
|
|
78
|
+
kafkaBridgeUrl,
|
|
79
|
+
kafkaBridgeUsername,
|
|
80
|
+
kafkaBridgePassword,
|
|
81
|
+
kafkaBridgeHeaders
|
|
82
|
+
}) {
|
|
75
83
|
if (producer) {
|
|
76
84
|
return producer;
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
if (
|
|
80
|
-
|
|
87
|
+
if (kafkaBridgeUrl) {
|
|
88
|
+
return createHttpProducer({
|
|
89
|
+
bridgeUrl: kafkaBridgeUrl,
|
|
90
|
+
bridgeUsername: kafkaBridgeUsername,
|
|
91
|
+
bridgePassword: kafkaBridgePassword,
|
|
92
|
+
headers: kafkaBridgeHeaders
|
|
93
|
+
});
|
|
81
94
|
}
|
|
82
95
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this._producer = kafka.producer();
|
|
94
|
-
await this._producer.connect();
|
|
95
|
-
},
|
|
96
|
-
async send(payload) {
|
|
97
|
-
await this.connect();
|
|
98
|
-
return this._producer.send(payload);
|
|
99
|
-
},
|
|
100
|
-
async disconnect() {
|
|
101
|
-
if (this._producer) {
|
|
102
|
-
await this._producer.disconnect();
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
};
|
|
96
|
+
if (kafkaBrokers && kafkaBrokers.length) {
|
|
97
|
+
return createKafkaProducer({
|
|
98
|
+
brokers: kafkaBrokers,
|
|
99
|
+
clientId: kafkaClientId
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw new Error(
|
|
104
|
+
'producer, kafkaBridgeUrl, or kafkaBrokers is required'
|
|
105
|
+
);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
function buildCompositeEvent({
|
|
@@ -154,10 +154,14 @@ export function createActivityHandler(options = {}) {
|
|
|
154
154
|
const nowFn = options.nowFn || (() => new Date().toISOString());
|
|
155
155
|
const uuidFn = options.uuidFn || generateId;
|
|
156
156
|
const getIp = options.getIp || defaultGetIp;
|
|
157
|
-
const producer =
|
|
157
|
+
const producer = resolveProducer({
|
|
158
158
|
producer: options.producer,
|
|
159
159
|
kafkaBrokers: options.kafkaBrokers,
|
|
160
|
-
kafkaClientId: options.kafkaClientId
|
|
160
|
+
kafkaClientId: options.kafkaClientId,
|
|
161
|
+
kafkaBridgeUrl: options.kafkaBridgeUrl,
|
|
162
|
+
kafkaBridgeUsername: options.kafkaBridgeUsername,
|
|
163
|
+
kafkaBridgePassword: options.kafkaBridgePassword,
|
|
164
|
+
kafkaBridgeHeaders: options.kafkaBridgeHeaders
|
|
161
165
|
});
|
|
162
166
|
|
|
163
167
|
return async function handle({ body, headers, cookies, request }) {
|
|
@@ -257,6 +261,76 @@ export function createNextRouteHandler(options = {}) {
|
|
|
257
261
|
};
|
|
258
262
|
}
|
|
259
263
|
|
|
264
|
+
export function createKafkaProducer(options = {}) {
|
|
265
|
+
const brokers = options.brokers;
|
|
266
|
+
if (!brokers || !brokers.length) {
|
|
267
|
+
throw new Error('brokers is required for createKafkaProducer');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const clientId = options.clientId || 'buyer-intent-sdk';
|
|
271
|
+
let _producer;
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
async connect() {
|
|
275
|
+
if (_producer) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const module = await import('kafkajs');
|
|
279
|
+
const kafka = new module.Kafka({ clientId, brokers });
|
|
280
|
+
_producer = kafka.producer();
|
|
281
|
+
await _producer.connect();
|
|
282
|
+
},
|
|
283
|
+
async send(payload) {
|
|
284
|
+
await this.connect();
|
|
285
|
+
return _producer.send(payload);
|
|
286
|
+
},
|
|
287
|
+
async disconnect() {
|
|
288
|
+
if (_producer) {
|
|
289
|
+
await _producer.disconnect();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function createHttpProducer(options = {}) {
|
|
296
|
+
const bridgeUrl = options.bridgeUrl;
|
|
297
|
+
if (!bridgeUrl) {
|
|
298
|
+
throw new Error('bridgeUrl is required for createHttpProducer');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const headers = {
|
|
302
|
+
'Content-Type': 'application/vnd.kafka.json.v2+json',
|
|
303
|
+
...(options.bridgeUsername && {
|
|
304
|
+
'Authorization': `Basic ${Buffer.from(`${options.bridgeUsername}:${options.bridgePassword || ''}`).toString('base64')}`
|
|
305
|
+
}),
|
|
306
|
+
...options.headers
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
async send(payload) {
|
|
311
|
+
const url = `${bridgeUrl.replace(/\/+$/, '')}/topics/${encodeURIComponent(payload.topic)}`;
|
|
312
|
+
const records = payload.messages.map((msg) => ({
|
|
313
|
+
value: typeof msg.value === 'string' ? JSON.parse(msg.value) : msg.value
|
|
314
|
+
}));
|
|
315
|
+
|
|
316
|
+
const response = await fetch(url, {
|
|
317
|
+
method: 'POST',
|
|
318
|
+
headers,
|
|
319
|
+
body: JSON.stringify({ records })
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
if (!response.ok) {
|
|
323
|
+
const body = await response.text();
|
|
324
|
+
throw new Error(
|
|
325
|
+
`Kafka bridge error ${response.status}: ${body}`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return response.json();
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
260
334
|
export function createDevLoggerProducer(options = {}) {
|
|
261
335
|
const log = options.log || console.log;
|
|
262
336
|
|
package/src/server/index.js
CHANGED
package/src/server.js
CHANGED
|
@@ -42,6 +42,9 @@ function buildHandlerOptions() {
|
|
|
42
42
|
return {
|
|
43
43
|
kafkaBrokers: brokers,
|
|
44
44
|
kafkaClientId: process.env.KAFKA_CLIENT_ID,
|
|
45
|
+
kafkaBridgeUrl: process.env.KAFKA_BRIDGE_URL,
|
|
46
|
+
kafkaBridgeUsername: process.env.KAFKA_BRIDGE_USERNAME,
|
|
47
|
+
kafkaBridgePassword: process.env.KAFKA_BRIDGE_PASSWORD,
|
|
45
48
|
partnerId: process.env.PARTNER_ID,
|
|
46
49
|
topicPrefix: process.env.TOPIC_PREFIX,
|
|
47
50
|
topic: process.env.KAFKA_TOPIC,
|