@g2crowd/buyer-intent-provider-sdk 0.2.0 → 0.3.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 +222 -146
- package/browser/components/index.js +24 -0
- package/browser/components/tracker.js +40 -0
- package/browser/dom.js +98 -0
- package/browser/elements/base.js +99 -0
- package/browser/elements/index.js +1 -0
- package/browser/identity.js +67 -0
- package/browser/index.js +4 -35
- package/browser/sdk.js +53 -259
- package/browser/transport.js +25 -0
- package/index.d.ts +43 -129
- package/index.js +2 -11
- package/package.json +14 -2
- package/react/index.d.ts +55 -0
- package/react/index.js +39 -0
- package/browser/tag_component.js +0 -46
- package/browser/trackers.js +0 -42
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Buyer Intent JS SDK
|
|
2
2
|
|
|
3
|
-
Buyer
|
|
3
|
+
Buyer intent tracking SDK for partner sites. Works in any frontend — drop in custom HTML elements, or use the React convenience layer. Events are sent via `sendBeacon` for best-effort delivery during navigation. React is never loaded unless you import from the `/react` subpath.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,18 +8,52 @@ Buyer Intent tracking SDK for partner sites. Provides purpose-built React compon
|
|
|
8
8
|
npm install @g2crowd/buyer-intent-provider-sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Entry Points
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
| Import Path | Contains | React Required |
|
|
14
|
+
| ------------------------------------------- | -------------------- | -------------- |
|
|
15
|
+
| `@g2crowd/buyer-intent-provider-sdk` | SDK, custom elements | No |
|
|
16
|
+
| `@g2crowd/buyer-intent-provider-sdk/react` | React components | Yes |
|
|
17
|
+
| `@g2crowd/buyer-intent-provider-sdk/server` | Server handlers | No |
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### HTML Custom Elements (any framework)
|
|
22
|
+
|
|
23
|
+
The SDK registers two custom elements — `<buyer-intent-view>` and `<buyer-intent-click>` — that work in any HTML page. No React, no build step required.
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<script type="module">
|
|
27
|
+
import '@g2crowd/buyer-intent-provider-sdk';
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<buyer-intent-view
|
|
31
|
+
tag="products.show"
|
|
32
|
+
product-id="123"
|
|
33
|
+
data-origin="g2.com"
|
|
34
|
+
data-activity-endpoint="/api/activity/events"
|
|
35
|
+
>
|
|
36
|
+
<h1>Acme CRM</h1>
|
|
37
|
+
<p>Product details here.</p>
|
|
38
|
+
|
|
39
|
+
<buyer-intent-click
|
|
40
|
+
event-name="/leads/create"
|
|
41
|
+
product-id="123"
|
|
42
|
+
data-origin="g2.com"
|
|
43
|
+
data-activity-endpoint="/api/activity/events"
|
|
44
|
+
>
|
|
45
|
+
<button>Get a Demo</button>
|
|
46
|
+
</buyer-intent-click>
|
|
47
|
+
</buyer-intent-view>
|
|
15
48
|
```
|
|
16
49
|
|
|
17
|
-
|
|
50
|
+
### React Components
|
|
51
|
+
|
|
52
|
+
For React apps, import from `/react` to get named components with typed props. Each component hardcodes its `tag` or `event-name`, so typos are caught at compile time.
|
|
18
53
|
|
|
19
54
|
```tsx
|
|
20
|
-
import { BuyerIntent } from '@g2crowd/buyer-intent-provider-sdk';
|
|
55
|
+
import { BuyerIntent } from '@g2crowd/buyer-intent-provider-sdk/react';
|
|
21
56
|
|
|
22
|
-
// Product profile page — fires a $view with tag "products.show"
|
|
23
57
|
export default function ProductPage() {
|
|
24
58
|
return (
|
|
25
59
|
<BuyerIntent.ProfileView
|
|
@@ -38,165 +72,146 @@ export default function ProductPage() {
|
|
|
38
72
|
}
|
|
39
73
|
```
|
|
40
74
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
// app/api/activity/events/route.ts
|
|
45
|
-
import { createNextRouteHandler } from '@g2crowd/buyer-intent-provider-sdk/server';
|
|
46
|
-
|
|
47
|
-
const POST = createNextRouteHandler({
|
|
48
|
-
kafkaBrokers: [process.env.KAFKA_BROKER || 'localhost:9092'],
|
|
49
|
-
partnerId: process.env.PARTNER_ID,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
export { POST };
|
|
53
|
-
```
|
|
75
|
+
### Mixing Both
|
|
54
76
|
|
|
55
|
-
|
|
77
|
+
Custom elements and React components use the same underlying event system. You can mix them freely on the same page:
|
|
56
78
|
|
|
57
|
-
|
|
79
|
+
```tsx
|
|
80
|
+
import { BuyerIntent } from '@g2crowd/buyer-intent-provider-sdk/react';
|
|
58
81
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
82
|
+
<BuyerIntent.PricingView
|
|
83
|
+
productId={123}
|
|
84
|
+
origin="g2.com"
|
|
85
|
+
activityEndpoint="/api/activity/events"
|
|
86
|
+
>
|
|
87
|
+
<h1>Pricing</h1>
|
|
88
|
+
|
|
89
|
+
<buyer-intent-click
|
|
90
|
+
event-name="/leads/create"
|
|
91
|
+
product-id="123"
|
|
92
|
+
data-origin="g2.com"
|
|
93
|
+
data-activity-endpoint="/api/activity/events"
|
|
94
|
+
>
|
|
95
|
+
<button>Start Free Trial</button>
|
|
96
|
+
</buyer-intent-click>
|
|
97
|
+
</BuyerIntent.PricingView>;
|
|
62
98
|
```
|
|
63
99
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- `PORT` (default: 3000)
|
|
67
|
-
- `PARTNER_ID`
|
|
68
|
-
- `TOPIC_PREFIX` (optional)
|
|
69
|
-
- `KAFKA_BROKERS` (comma-separated)
|
|
70
|
-
- `KAFKA_TOPIC` (optional override)
|
|
71
|
-
- `KAFKA_CLIENT_ID` (optional)
|
|
72
|
-
- `USE_DEV_LOGGER=true` (logs instead of Kafka)
|
|
100
|
+
## Custom Elements
|
|
73
101
|
|
|
74
|
-
|
|
102
|
+
Two elements cover all tracking scenarios. The `tag` or `event-name` attribute determines the interaction type.
|
|
75
103
|
|
|
76
|
-
|
|
104
|
+
### `<buyer-intent-view>`
|
|
77
105
|
|
|
78
|
-
|
|
79
|
-
- Topic name (scoped to their tenant)
|
|
80
|
-
- Auth mechanism (SASL user/pass or mTLS)
|
|
81
|
-
- TLS requirements (on/off, CA certs if needed)
|
|
106
|
+
Fires a `$view` event when the element connects to the DOM. One event per page navigation.
|
|
82
107
|
|
|
83
|
-
|
|
108
|
+
| Attribute | Required | Description |
|
|
109
|
+
| ------------- | -------- | ---------------------------------------------------- |
|
|
110
|
+
| `tag` | Yes | View type (see supported tags below) |
|
|
111
|
+
| `product-id` | - | Single product ID |
|
|
112
|
+
| `product-ids` | - | Multiple product IDs (JSON array or comma-separated) |
|
|
113
|
+
| `category-id` | - | Category ID |
|
|
84
114
|
|
|
85
|
-
|
|
115
|
+
Supported `tag` values: `products.show`, `products.pricing`, `products.competitors`, `categories.show`, `comparisons.show`, `reviewers.take_survey`
|
|
86
116
|
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
117
|
+
```html
|
|
118
|
+
<buyer-intent-view
|
|
119
|
+
tag="categories.show"
|
|
120
|
+
category-id="45"
|
|
121
|
+
data-origin="g2.com"
|
|
122
|
+
data-activity-endpoint="/api/activity/events"
|
|
123
|
+
>
|
|
124
|
+
<h1>CRM Software</h1>
|
|
125
|
+
</buyer-intent-view>
|
|
126
|
+
```
|
|
96
127
|
|
|
97
|
-
|
|
98
|
-
allowAutoTopicCreation: false,
|
|
99
|
-
});
|
|
128
|
+
Multiple product IDs (for comparisons):
|
|
100
129
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
});
|
|
130
|
+
```html
|
|
131
|
+
<buyer-intent-view tag="comparisons.show" product-ids="[101, 201]">
|
|
132
|
+
...
|
|
133
|
+
</buyer-intent-view>
|
|
106
134
|
|
|
107
|
-
|
|
135
|
+
<buyer-intent-view tag="comparisons.show" product-ids="101,201">
|
|
136
|
+
...
|
|
137
|
+
</buyer-intent-view>
|
|
108
138
|
```
|
|
109
139
|
|
|
110
|
-
|
|
140
|
+
### `<buyer-intent-click>`
|
|
111
141
|
|
|
112
|
-
|
|
142
|
+
Fires an event each time the user clicks anything inside the element.
|
|
113
143
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
createDevLoggerProducer,
|
|
119
|
-
} from '@g2crowd/buyer-intent-provider-sdk/server';
|
|
144
|
+
| Attribute | Required | Description |
|
|
145
|
+
| ------------ | -------- | ----------------------------------- |
|
|
146
|
+
| `event-name` | Yes | Click event type (see values below) |
|
|
147
|
+
| `product-id` | - | Product ID |
|
|
120
148
|
|
|
121
|
-
|
|
122
|
-
producer: createDevLoggerProducer(),
|
|
123
|
-
partnerId: 'dev',
|
|
124
|
-
});
|
|
149
|
+
Supported `event-name` values: `/ad/clicked`, `/leads/create`
|
|
125
150
|
|
|
126
|
-
|
|
151
|
+
```html
|
|
152
|
+
<buyer-intent-click
|
|
153
|
+
event-name="/leads/create"
|
|
154
|
+
product-id="123"
|
|
155
|
+
data-origin="g2.com"
|
|
156
|
+
data-activity-endpoint="/api/activity/events"
|
|
157
|
+
>
|
|
158
|
+
<button>Get a Demo</button>
|
|
159
|
+
</buyer-intent-click>
|
|
127
160
|
```
|
|
128
161
|
|
|
129
|
-
|
|
162
|
+
### Common Attributes
|
|
130
163
|
|
|
131
|
-
|
|
164
|
+
Both elements accept these optional `data-*` attributes:
|
|
132
165
|
|
|
133
|
-
|
|
166
|
+
| Attribute | Description |
|
|
167
|
+
| ------------------------ | ------------------------------------------------------ |
|
|
168
|
+
| `data-origin` | Partner hostname |
|
|
169
|
+
| `data-activity-endpoint` | URL to POST events to |
|
|
170
|
+
| `data-user-type` | User type string (default: `"standard"`) |
|
|
171
|
+
| `data-distinct-id` | Override the auto-generated visitor ID |
|
|
172
|
+
| `data-buyer-intent` | JSON object with `sourceLocation` and `context` fields |
|
|
134
173
|
|
|
135
|
-
|
|
174
|
+
### TypeScript Support
|
|
136
175
|
|
|
137
|
-
|
|
138
|
-
| ----------------- | ----------------- | ----------------------- |
|
|
139
|
-
| `ProfileView` | `productId` | `products.show` |
|
|
140
|
-
| `PricingView` | `productId` | `products.pricing` |
|
|
141
|
-
| `CompetitorsView` | `productId` | `products.competitors` |
|
|
142
|
-
| `CategoryView` | `categoryId` | `categories.show` |
|
|
143
|
-
| `CompareView` | `productIds` (2+) | `comparisons.show` |
|
|
144
|
-
| `WriteReviewView` | `productId` | `reviewers.take_survey` |
|
|
176
|
+
The package augments `JSX.IntrinsicElements` with typed attributes. The `tag` and `event-name` attributes are union types, so invalid values are caught at compile time:
|
|
145
177
|
|
|
146
178
|
```tsx
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
<BuyerIntent.ProfileView productId={123} origin="g2.com" activityEndpoint="/api/activity/events">
|
|
150
|
-
<ProductPage />
|
|
151
|
-
</BuyerIntent.ProfileView>
|
|
179
|
+
// ✅ Type-safe
|
|
180
|
+
<buyer-intent-view tag="products.show" product-id="123">
|
|
152
181
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
</BuyerIntent.CategoryView>
|
|
156
|
-
|
|
157
|
-
<BuyerIntent.CompareView productIds={[123, 456]} origin="g2.com" activityEndpoint="/api/activity/events">
|
|
158
|
-
<ComparisonPage />
|
|
159
|
-
</BuyerIntent.CompareView>
|
|
182
|
+
// ❌ Type error: '"invalid.tag"' is not assignable to type 'ViewTag'
|
|
183
|
+
<buyer-intent-view tag="invalid.tag" product-id="123">
|
|
160
184
|
```
|
|
161
185
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
These fire an event each time the user clicks anything inside the component.
|
|
186
|
+
## React Components
|
|
165
187
|
|
|
166
|
-
|
|
167
|
-
| ----------------- | ------------- | --------------- |
|
|
168
|
-
| `AdClick` | `productId` | `/ad/clicked` |
|
|
169
|
-
| `LeadCreateClick` | `productId` | `/leads/create` |
|
|
170
|
-
|
|
171
|
-
```tsx
|
|
172
|
-
<BuyerIntent.LeadCreateClick productId={123}>
|
|
173
|
-
<button>Get a Demo</button>
|
|
174
|
-
</BuyerIntent.LeadCreateClick>
|
|
188
|
+
Named React components wrap the custom elements with hardcoded `tag`/`event-name` values. They accept camelCase props instead of HTML attributes.
|
|
175
189
|
|
|
176
|
-
|
|
177
|
-
<button>Sponsored Link</button>
|
|
178
|
-
</BuyerIntent.AdClick>
|
|
179
|
-
```
|
|
190
|
+
### View Components
|
|
180
191
|
|
|
181
|
-
|
|
192
|
+
| Component | Tag | Required Prop |
|
|
193
|
+
| ----------------- | ----------------------- | ------------- |
|
|
194
|
+
| `ProfileView` | `products.show` | `productId` |
|
|
195
|
+
| `PricingView` | `products.pricing` | `productId` |
|
|
196
|
+
| `CompetitorsView` | `products.competitors` | `productId` |
|
|
197
|
+
| `CategoryView` | `categories.show` | `categoryId` |
|
|
198
|
+
| `CompareView` | `comparisons.show` | `productIds` |
|
|
199
|
+
| `WriteReviewView` | `reviewers.take_survey` | `productId` |
|
|
182
200
|
|
|
183
|
-
|
|
201
|
+
### Click Components
|
|
184
202
|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
189
|
-
| `userType` | User type string (default: `"standard"`) |
|
|
190
|
-
| `distinctId` | Override the auto-generated visitor ID |
|
|
191
|
-
| `sourceLocation` | Controller/action identifier |
|
|
192
|
-
| `context` | Arbitrary metadata object |
|
|
193
|
-
| `eventName` | Override the default event name |
|
|
203
|
+
| Component | Event Name | Required Prop |
|
|
204
|
+
| ----------------- | --------------- | ------------- |
|
|
205
|
+
| `AdClick` | `/ad/clicked` | `productId` |
|
|
206
|
+
| `LeadCreateClick` | `/leads/create` | `productId` |
|
|
194
207
|
|
|
195
|
-
###
|
|
208
|
+
### Generic Trackers
|
|
196
209
|
|
|
197
|
-
For interaction types
|
|
210
|
+
For custom interaction types, use `ViewTracker` or `ClickTracker` directly:
|
|
198
211
|
|
|
199
212
|
```tsx
|
|
213
|
+
import { BuyerIntent } from '@g2crowd/buyer-intent-provider-sdk/react';
|
|
214
|
+
|
|
200
215
|
<BuyerIntent.ViewTracker
|
|
201
216
|
tag="custom.page_type"
|
|
202
217
|
productIds={[123]}
|
|
@@ -206,17 +221,25 @@ For interaction types not covered above, use `ViewTracker` and `ClickTracker` di
|
|
|
206
221
|
<CustomPage />
|
|
207
222
|
</BuyerIntent.ViewTracker>
|
|
208
223
|
|
|
209
|
-
<BuyerIntent.ClickTracker
|
|
210
|
-
eventName="$custom_action"
|
|
211
|
-
productIds={[123]}
|
|
212
|
-
>
|
|
224
|
+
<BuyerIntent.ClickTracker eventName="$custom_action" productIds={[123]}>
|
|
213
225
|
<button>Custom Action</button>
|
|
214
226
|
</BuyerIntent.ClickTracker>
|
|
215
227
|
```
|
|
216
228
|
|
|
217
|
-
###
|
|
229
|
+
### Common Props
|
|
218
230
|
|
|
219
|
-
|
|
231
|
+
| Prop | Description |
|
|
232
|
+
| ------------------ | ---------------------------------------- |
|
|
233
|
+
| `origin` | Partner hostname |
|
|
234
|
+
| `activityEndpoint` | URL to POST events to |
|
|
235
|
+
| `userType` | User type string (default: `"standard"`) |
|
|
236
|
+
| `distinctId` | Override the auto-generated visitor ID |
|
|
237
|
+
| `sourceLocation` | Controller/action identifier |
|
|
238
|
+
| `context` | Arbitrary metadata object |
|
|
239
|
+
|
|
240
|
+
## Visit Properties
|
|
241
|
+
|
|
242
|
+
```js
|
|
220
243
|
import { buyerIntent } from '@g2crowd/buyer-intent-provider-sdk';
|
|
221
244
|
|
|
222
245
|
buyerIntent.setVisitProperties({
|
|
@@ -232,27 +255,82 @@ buyerIntent.setVisitProperties({
|
|
|
232
255
|
});
|
|
233
256
|
```
|
|
234
257
|
|
|
235
|
-
##
|
|
258
|
+
## Backend
|
|
236
259
|
|
|
237
|
-
###
|
|
260
|
+
### Next.js Route Handler
|
|
238
261
|
|
|
239
|
-
|
|
262
|
+
```ts
|
|
263
|
+
import { createNextRouteHandler } from '@g2crowd/buyer-intent-provider-sdk/server';
|
|
240
264
|
|
|
241
|
-
|
|
265
|
+
const POST = createNextRouteHandler({
|
|
266
|
+
kafkaBrokers: [process.env.KAFKA_BROKER || 'localhost:9092'],
|
|
267
|
+
partnerId: process.env.PARTNER_ID,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
export { POST };
|
|
271
|
+
```
|
|
242
272
|
|
|
243
|
-
|
|
273
|
+
### Standalone Server
|
|
244
274
|
|
|
245
|
-
|
|
275
|
+
```bash
|
|
276
|
+
PARTNER_ID=partner-a KAFKA_BROKERS=broker1:9092,broker2:9092 \
|
|
277
|
+
buyer-intent-server
|
|
278
|
+
```
|
|
246
279
|
|
|
247
|
-
|
|
280
|
+
Environment variables: `PORT` (default 3000), `PARTNER_ID`, `KAFKA_BROKERS` (comma-separated), `TOPIC_PREFIX`, `KAFKA_TOPIC`, `KAFKA_CLIENT_ID`, `USE_DEV_LOGGER=true`.
|
|
248
281
|
|
|
249
|
-
###
|
|
282
|
+
### Custom Kafka Producer
|
|
250
283
|
|
|
251
|
-
|
|
284
|
+
```ts
|
|
285
|
+
import { Kafka } from 'kafkajs';
|
|
286
|
+
import { createNextRouteHandler } from '@g2crowd/buyer-intent-provider-sdk/server';
|
|
252
287
|
|
|
253
|
-
|
|
288
|
+
const kafka = new Kafka({
|
|
289
|
+
clientId: 'partner-app',
|
|
290
|
+
brokers: (process.env.KAFKA_BROKERS || '').split(','),
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const POST = createNextRouteHandler({
|
|
294
|
+
producer: kafka.producer({ allowAutoTopicCreation: false }),
|
|
295
|
+
partnerId: process.env.PARTNER_ID,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
export { POST };
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Dev Adapter (No Kafka)
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
import {
|
|
305
|
+
createNextRouteHandler,
|
|
306
|
+
createDevLoggerProducer,
|
|
307
|
+
} from '@g2crowd/buyer-intent-provider-sdk/server';
|
|
308
|
+
|
|
309
|
+
const POST = createNextRouteHandler({
|
|
310
|
+
producer: createDevLoggerProducer(),
|
|
311
|
+
partnerId: 'dev',
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
export { POST };
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Server API
|
|
254
318
|
|
|
255
|
-
|
|
319
|
+
| Function | Purpose |
|
|
320
|
+
| ---------------------------------- | ---------------------------------------------------------------------------------- |
|
|
321
|
+
| `createActivityHandler(options)` | Framework-agnostic handler that validates, enriches, and publishes events to Kafka |
|
|
322
|
+
| `createNextRouteHandler(options)` | Next.js Route Handler wrapper around `createActivityHandler` |
|
|
323
|
+
| `createDevLoggerProducer(options)` | Dev-only producer that logs messages instead of sending to Kafka |
|
|
324
|
+
| `topicName(options)` | Builds a topic name from `partnerId` and optional `prefix` |
|
|
325
|
+
|
|
326
|
+
## Kafka Setup (Partner Checklist)
|
|
327
|
+
|
|
328
|
+
- Kafka broker list (host:port)
|
|
329
|
+
- Topic name (scoped to their tenant, default: `intent_events_<partnerId>`)
|
|
330
|
+
- Auth mechanism (SASL user/pass or mTLS)
|
|
331
|
+
- TLS requirements (on/off, CA certs if needed)
|
|
332
|
+
|
|
333
|
+
## Event Payload
|
|
256
334
|
|
|
257
335
|
```json
|
|
258
336
|
{
|
|
@@ -266,9 +344,7 @@ The SDK sends the following JSON body to `activityEndpoint`:
|
|
|
266
344
|
"distinct_id": "visitor-uuid",
|
|
267
345
|
"origin": "g2.com",
|
|
268
346
|
"source_location": "ProductsController#show",
|
|
269
|
-
"context": {
|
|
270
|
-
"page": "product"
|
|
271
|
-
}
|
|
347
|
+
"context": { "page": "product" }
|
|
272
348
|
},
|
|
273
349
|
"visit": {
|
|
274
350
|
"properties": {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { ViewTracker, ClickTracker } from './tracker.js';
|
|
2
|
+
import { ViewTracker, ClickTracker } from './tracker.js';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
function view(tag) {
|
|
6
|
+
return function ViewComponent({ children, ...rest }) {
|
|
7
|
+
return React.createElement(ViewTracker, { ...rest, tag }, children);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function click(eventName) {
|
|
12
|
+
return function ClickComponent({ children, ...rest }) {
|
|
13
|
+
return React.createElement(ClickTracker, { ...rest, eventName }, children);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ProfileView = view('products.show');
|
|
18
|
+
export const PricingView = view('products.pricing');
|
|
19
|
+
export const CompetitorsView = view('products.competitors');
|
|
20
|
+
export const CategoryView = view('categories.show');
|
|
21
|
+
export const CompareView = view('comparisons.show');
|
|
22
|
+
export const WriteReviewView = view('reviewers.take_survey');
|
|
23
|
+
export const AdClick = click('/ad/clicked');
|
|
24
|
+
export const LeadCreateClick = click('/leads/create');
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
function buildAttrs({
|
|
4
|
+
productId,
|
|
5
|
+
productIds,
|
|
6
|
+
categoryId,
|
|
7
|
+
tag,
|
|
8
|
+
eventName,
|
|
9
|
+
sourceLocation,
|
|
10
|
+
context,
|
|
11
|
+
origin,
|
|
12
|
+
activityEndpoint,
|
|
13
|
+
userType,
|
|
14
|
+
distinctId,
|
|
15
|
+
}) {
|
|
16
|
+
const payload = { sourceLocation, context };
|
|
17
|
+
const attrs = {
|
|
18
|
+
'data-buyer-intent': JSON.stringify(payload),
|
|
19
|
+
'data-origin': origin,
|
|
20
|
+
'data-activity-endpoint': activityEndpoint,
|
|
21
|
+
'data-user-type': userType,
|
|
22
|
+
'data-distinct-id': distinctId,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (tag) attrs.tag = tag;
|
|
26
|
+
if (eventName) attrs['event-name'] = eventName;
|
|
27
|
+
if (productId != null) attrs['product-id'] = String(productId);
|
|
28
|
+
if (productIds) attrs['product-ids'] = JSON.stringify(productIds);
|
|
29
|
+
if (categoryId != null) attrs['category-id'] = String(categoryId);
|
|
30
|
+
|
|
31
|
+
return attrs;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function ViewTracker({ children, ...rest }) {
|
|
35
|
+
return React.createElement('buyer-intent-view', buildAttrs(rest), children);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function ClickTracker({ children, ...rest }) {
|
|
39
|
+
return React.createElement('buyer-intent-click', buildAttrs(rest), children);
|
|
40
|
+
}
|
package/browser/dom.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
function parseJson(value) {
|
|
2
|
+
if (!value) {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(value);
|
|
8
|
+
} catch (_error) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function parseArray(value) {
|
|
14
|
+
if (!value) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const parsed = parseJson(value);
|
|
19
|
+
if (Array.isArray(parsed)) {
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof value === 'string') {
|
|
24
|
+
return value
|
|
25
|
+
.split(',')
|
|
26
|
+
.map((entry) => entry.trim())
|
|
27
|
+
.filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readTagElement(element) {
|
|
34
|
+
if (!element) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (element.tagName === 'SCRIPT') {
|
|
39
|
+
const parsed =
|
|
40
|
+
parseJson(element.textContent || '') ||
|
|
41
|
+
parseJson(element.getAttribute('data-buyer-intent'));
|
|
42
|
+
if (parsed && typeof parsed === 'object') {
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const dataJson = parseJson(element.getAttribute('data-buyer-intent'));
|
|
48
|
+
if (dataJson && typeof dataJson === 'object') {
|
|
49
|
+
return dataJson;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const dataset = element.dataset || {};
|
|
53
|
+
return {
|
|
54
|
+
productIds: parseArray(dataset.productIds),
|
|
55
|
+
categoryIds: parseArray(dataset.categoryIds),
|
|
56
|
+
tag: dataset.tag,
|
|
57
|
+
sourceLocation: dataset.sourceLocation,
|
|
58
|
+
context: parseJson(dataset.context),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function readTags(elements) {
|
|
63
|
+
if (typeof document === 'undefined') {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const found = elements || new Set();
|
|
68
|
+
const legacy = document.getElementById('buyer-intent-tags');
|
|
69
|
+
if (legacy) {
|
|
70
|
+
found.add(legacy);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
document.querySelectorAll('[data-buyer-intent]').forEach((element) => {
|
|
74
|
+
found.add(element);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return Array.from(found)
|
|
78
|
+
.map((element) => ({ element, tag: readTagElement(element) }))
|
|
79
|
+
.filter((entry) => entry.tag && typeof entry.tag === 'object');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function readElementConfig(element) {
|
|
83
|
+
if (!element || !element.dataset) {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const { origin, userType, distinctId, activityEndpoint } = element.dataset;
|
|
88
|
+
|
|
89
|
+
return { origin, userType, distinctId, activityEndpoint };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function resolveEventName(element, fallback) {
|
|
93
|
+
if (element && element.dataset && element.dataset.eventName) {
|
|
94
|
+
return element.dataset.eventName;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return fallback;
|
|
98
|
+
}
|