@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Buyer Intent JS SDK
2
2
 
3
- Buyer Intent tracking SDK for partner sites. Provides purpose-built React components for each tracked interaction type, plus low-level `ViewTracker` and `ClickTracker` primitives. Events are sent via `sendBeacon` for best-effort delivery during navigation.
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
- If you plan to use the built-in Kafka connection (via `kafkaBrokers`), install the peer dependency:
11
+ ## Entry Points
12
12
 
13
- ```bash
14
- npm install kafkajs
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
- ## Quick Start (Next.js)
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
- ## Backend (Next.js Route)
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
- ## Barebones Server
77
+ Custom elements and React components use the same underlying event system. You can mix them freely on the same page:
56
78
 
57
- This package ships a tiny server that hosts `POST /activity/events`.
79
+ ```tsx
80
+ import { BuyerIntent } from '@g2crowd/buyer-intent-provider-sdk/react';
58
81
 
59
- ```bash
60
- PARTNER_ID=partner-a KAFKA_BROKERS=broker1:9092,broker2:9092 \
61
- buyer-intent-server
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
- Environment variables:
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
- ## Kafka Setup (Partner Checklist)
102
+ Two elements cover all tracking scenarios. The `tag` or `event-name` attribute determines the interaction type.
75
103
 
76
- Provide these values to your partners so they can publish to their scoped topic:
104
+ ### `<buyer-intent-view>`
77
105
 
78
- - Kafka broker list (host:port)
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
- By default, the SDK derives the topic from `partnerId` using `intent_events_<partnerId>`.
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
- Example using a prebuilt producer:
115
+ Supported `tag` values: `products.show`, `products.pricing`, `products.competitors`, `categories.show`, `comparisons.show`, `reviewers.take_survey`
86
116
 
87
- ```ts
88
- // app/api/activity/events/route.ts
89
- import { Kafka } from 'kafkajs';
90
- import { createNextRouteHandler } from '@g2crowd/buyer-intent-provider-sdk/server';
91
-
92
- const kafka = new Kafka({
93
- clientId: 'partner-app',
94
- brokers: (process.env.KAFKA_BROKERS || '').split(','),
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
- const producer = kafka.producer({
98
- allowAutoTopicCreation: false,
99
- });
128
+ Multiple product IDs (for comparisons):
100
129
 
101
- const POST = createNextRouteHandler({
102
- producer,
103
- partnerId: process.env.PARTNER_ID,
104
- topicPrefix: process.env.TOPIC_PREFIX,
105
- });
130
+ ```html
131
+ <buyer-intent-view tag="comparisons.show" product-ids="[101, 201]">
132
+ ...
133
+ </buyer-intent-view>
106
134
 
107
- export { POST };
135
+ <buyer-intent-view tag="comparisons.show" product-ids="101,201">
136
+ ...
137
+ </buyer-intent-view>
108
138
  ```
109
139
 
110
- ## Dev Adapter (No Kafka)
140
+ ### `<buyer-intent-click>`
111
141
 
112
- If you want to test locally without Kafka, use the dev logger producer.
142
+ Fires an event each time the user clicks anything inside the element.
113
143
 
114
- ```ts
115
- // app/api/activity/events/route.ts
116
- import {
117
- createNextRouteHandler,
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
- const POST = createNextRouteHandler({
122
- producer: createDevLoggerProducer(),
123
- partnerId: 'dev',
124
- });
149
+ Supported `event-name` values: `/ad/clicked`, `/leads/create`
125
150
 
126
- export { POST };
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
- ## Interaction Components
162
+ ### Common Attributes
130
163
 
131
- Purpose-built components for each tracked interaction type. Each sets the correct event name and tag automatically — you just provide the required ID.
164
+ Both elements accept these optional `data-*` attributes:
132
165
 
133
- ### Page Views
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
- These fire a `$view` event when the component appears in the DOM. One event per page navigation.
174
+ ### TypeScript Support
136
175
 
137
- | Component | Required Prop | Tag |
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
- import { BuyerIntent } from '@g2crowd/buyer-intent-provider-sdk';
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
- <BuyerIntent.CategoryView categoryId={45} origin="g2.com" activityEndpoint="/api/activity/events">
154
- <CategoryPage />
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
- ### Click Events
163
-
164
- These fire an event each time the user clicks anything inside the component.
186
+ ## React Components
165
187
 
166
- | Component | Required Prop | Event Name |
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
- <BuyerIntent.AdClick productId={123}>
177
- <button>Sponsored Link</button>
178
- </BuyerIntent.AdClick>
179
- ```
190
+ ### View Components
180
191
 
181
- ### Common Props
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
- All interaction components accept these optional props:
201
+ ### Click Components
184
202
 
185
- | Prop | Description |
186
- | ------------------ | ---------------------------------------- |
187
- | `origin` | Partner hostname |
188
- | `activityEndpoint` | URL to POST events to |
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
- ### Low-Level Primitives
208
+ ### Generic Trackers
196
209
 
197
- For interaction types not covered above, use `ViewTracker` and `ClickTracker` directly with a custom `tag` or `eventName`:
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
- ### Visit Properties
229
+ ### Common Props
218
230
 
219
- ```tsx
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
- ## Server API
258
+ ## Backend
236
259
 
237
- ### createActivityHandler(options)
260
+ ### Next.js Route Handler
238
261
 
239
- Returns a framework-agnostic handler that validates, enriches, and publishes events to Kafka.
262
+ ```ts
263
+ import { createNextRouteHandler } from '@g2crowd/buyer-intent-provider-sdk/server';
240
264
 
241
- ### createNextRouteHandler(options)
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
- Next.js Route Handler wrapper around `createActivityHandler`.
273
+ ### Standalone Server
244
274
 
245
- ### createDevLoggerProducer(options)
275
+ ```bash
276
+ PARTNER_ID=partner-a KAFKA_BROKERS=broker1:9092,broker2:9092 \
277
+ buyer-intent-server
278
+ ```
246
279
 
247
- Dev-only producer that logs messages instead of sending to Kafka.
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
- ### topicName(options)
282
+ ### Custom Kafka Producer
250
283
 
251
- Builds a topic name from a `partnerId` and optional `prefix`.
284
+ ```ts
285
+ import { Kafka } from 'kafkajs';
286
+ import { createNextRouteHandler } from '@g2crowd/buyer-intent-provider-sdk/server';
252
287
 
253
- ## Event Payload
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
- The SDK sends the following JSON body to `activityEndpoint`:
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
+ }