@g2crowd/buyer-intent-provider-sdk 0.2.0 → 0.4.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 +237 -190
- package/package.json +19 -12
- package/src/browser/components/index.js +29 -0
- package/src/browser/components/tracker.js +105 -0
- package/src/browser/dom.js +184 -0
- package/src/browser/elements/base.js +124 -0
- package/src/browser/elements/index.js +6 -0
- package/src/browser/identity.js +67 -0
- package/src/browser/index.js +8 -0
- package/src/browser/sdk.js +336 -0
- package/src/browser/transport.js +25 -0
- package/src/index.d.ts +126 -0
- package/src/index.js +8 -0
- package/src/react/index.d.ts +77 -0
- package/{browser → src/react}/index.js +20 -10
- package/browser/sdk.js +0 -468
- package/browser/tag_component.js +0 -46
- package/browser/trackers.js +0 -42
- package/index.d.ts +0 -183
- package/index.js +0 -15
- /package/{browser → src/browser}/core.js +0 -0
- /package/{server → src/server}/handlers.js +0 -0
- /package/{server → src/server}/index.js +0 -0
- /package/{server.js → src/server.js} +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
function buildViewAttrs({
|
|
4
|
+
productId,
|
|
5
|
+
productIds,
|
|
6
|
+
categoryId,
|
|
7
|
+
tag,
|
|
8
|
+
sourceLocation,
|
|
9
|
+
context,
|
|
10
|
+
origin,
|
|
11
|
+
activityEndpoint,
|
|
12
|
+
userType,
|
|
13
|
+
distinctId,
|
|
14
|
+
}) {
|
|
15
|
+
const payload = { sourceLocation, context };
|
|
16
|
+
const attrs = {
|
|
17
|
+
'data-buyer-intent': JSON.stringify(payload),
|
|
18
|
+
'data-origin': origin,
|
|
19
|
+
'data-activity-endpoint': activityEndpoint,
|
|
20
|
+
'data-user-type': userType,
|
|
21
|
+
'data-distinct-id': distinctId,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (tag) attrs.tag = tag;
|
|
25
|
+
if (sourceLocation) attrs['source-location'] = sourceLocation;
|
|
26
|
+
if (productId != null) attrs['product-id'] = String(productId);
|
|
27
|
+
if (productIds) attrs['product-ids'] = JSON.stringify(productIds);
|
|
28
|
+
if (categoryId != null) attrs['category-id'] = String(categoryId);
|
|
29
|
+
|
|
30
|
+
return attrs;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildClickAttrs({
|
|
34
|
+
eventName,
|
|
35
|
+
productId,
|
|
36
|
+
productIds,
|
|
37
|
+
categoryId,
|
|
38
|
+
sourceLocation,
|
|
39
|
+
context,
|
|
40
|
+
origin,
|
|
41
|
+
activityEndpoint,
|
|
42
|
+
userType,
|
|
43
|
+
distinctId,
|
|
44
|
+
}) {
|
|
45
|
+
const payload = { sourceLocation, context };
|
|
46
|
+
const attrs = {
|
|
47
|
+
'data-buyer-intent': JSON.stringify(payload),
|
|
48
|
+
'data-origin': origin,
|
|
49
|
+
'data-activity-endpoint': activityEndpoint,
|
|
50
|
+
'data-user-type': userType,
|
|
51
|
+
'data-distinct-id': distinctId,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (eventName) attrs['event-name'] = eventName;
|
|
55
|
+
if (productId != null) attrs['product-id'] = String(productId);
|
|
56
|
+
if (productIds) attrs['product-ids'] = JSON.stringify(productIds);
|
|
57
|
+
if (categoryId != null) attrs['category-id'] = String(categoryId);
|
|
58
|
+
|
|
59
|
+
return attrs;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function SessionProvider({
|
|
63
|
+
origin,
|
|
64
|
+
activityEndpoint,
|
|
65
|
+
userType,
|
|
66
|
+
distinctId,
|
|
67
|
+
children,
|
|
68
|
+
}) {
|
|
69
|
+
const attrs = {};
|
|
70
|
+
if (origin) attrs.origin = origin;
|
|
71
|
+
if (activityEndpoint) attrs['activity-endpoint'] = activityEndpoint;
|
|
72
|
+
if (userType) attrs['user-type'] = userType;
|
|
73
|
+
if (distinctId) attrs['distinct-id'] = distinctId;
|
|
74
|
+
return React.createElement('buyer-intent-session', attrs, children);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function SubjectTracker({
|
|
78
|
+
productId,
|
|
79
|
+
categoryId,
|
|
80
|
+
productIds,
|
|
81
|
+
categoryIds,
|
|
82
|
+
}) {
|
|
83
|
+
const attrs = {};
|
|
84
|
+
if (productId != null) attrs['product-id'] = String(productId);
|
|
85
|
+
if (categoryId != null) attrs['category-id'] = String(categoryId);
|
|
86
|
+
if (productIds) attrs['product-ids'] = JSON.stringify(productIds);
|
|
87
|
+
if (categoryIds) attrs['category-ids'] = JSON.stringify(categoryIds);
|
|
88
|
+
return React.createElement('buyer-intent-subject', attrs);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function ViewTracker({ children, ...rest }) {
|
|
92
|
+
return React.createElement(
|
|
93
|
+
'buyer-intent-view',
|
|
94
|
+
buildViewAttrs(rest),
|
|
95
|
+
children
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function ClickTracker({ children, ...rest }) {
|
|
100
|
+
return React.createElement(
|
|
101
|
+
'buyer-intent-click',
|
|
102
|
+
buildClickAttrs(rest),
|
|
103
|
+
children
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
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
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseIds(raw) {
|
|
101
|
+
if (!raw) return [];
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(raw);
|
|
104
|
+
if (Array.isArray(parsed)) return parsed.map(Number);
|
|
105
|
+
} catch (_e) {}
|
|
106
|
+
return raw.split(',').map((s) => Number(s.trim())).filter((n) => !Number.isNaN(n));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function readSessionConfig(element) {
|
|
110
|
+
if (!element || typeof element.closest !== 'function') {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const session = element.closest('buyer-intent-session');
|
|
115
|
+
if (!session) {
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
origin: session.getAttribute('origin') || undefined,
|
|
121
|
+
activityEndpoint: session.getAttribute('activity-endpoint') || undefined,
|
|
122
|
+
userType: session.getAttribute('user-type') || undefined,
|
|
123
|
+
distinctId: session.getAttribute('distinct-id') || undefined,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function collectSubjectIds(viewElement) {
|
|
128
|
+
if (!viewElement || typeof viewElement.querySelectorAll !== 'function') {
|
|
129
|
+
return { productIds: [], categoryIds: [] };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const subjects = viewElement.querySelectorAll('buyer-intent-subject');
|
|
133
|
+
const productIds = [];
|
|
134
|
+
const categoryIds = [];
|
|
135
|
+
|
|
136
|
+
for (const subject of subjects) {
|
|
137
|
+
const pids = subject.getAttribute('product-ids');
|
|
138
|
+
const pid = subject.getAttribute('product-id');
|
|
139
|
+
if (pids) {
|
|
140
|
+
productIds.push(...parseIds(pids));
|
|
141
|
+
} else if (pid) {
|
|
142
|
+
productIds.push(Number(pid));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const cids = subject.getAttribute('category-ids');
|
|
146
|
+
const cid = subject.getAttribute('category-id');
|
|
147
|
+
if (cids) {
|
|
148
|
+
categoryIds.push(...parseIds(cids));
|
|
149
|
+
} else if (cid) {
|
|
150
|
+
categoryIds.push(Number(cid));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { productIds, categoryIds };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function readViewContext(clickElement) {
|
|
158
|
+
if (!clickElement || typeof clickElement.closest !== 'function') {
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const view = clickElement.closest('buyer-intent-view');
|
|
163
|
+
if (!view) {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const viewData = readTagElement(view) || {};
|
|
168
|
+
const subjectIds = collectSubjectIds(view);
|
|
169
|
+
|
|
170
|
+
const productIds = [
|
|
171
|
+
...(viewData.productIds || []),
|
|
172
|
+
...subjectIds.productIds,
|
|
173
|
+
];
|
|
174
|
+
const categoryIds = [
|
|
175
|
+
...(viewData.categoryIds || []),
|
|
176
|
+
...subjectIds.categoryIds,
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
...viewData,
|
|
181
|
+
productIds: productIds.length ? [...new Set(productIds)] : undefined,
|
|
182
|
+
categoryIds: categoryIds.length ? [...new Set(categoryIds)] : undefined,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { ensureSDK } from '../sdk.js';
|
|
2
|
+
|
|
3
|
+
const BaseHTMLElement =
|
|
4
|
+
typeof HTMLElement !== 'undefined' ? HTMLElement : class {};
|
|
5
|
+
|
|
6
|
+
function parseIds(raw) {
|
|
7
|
+
if (!raw) return undefined;
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(raw);
|
|
10
|
+
if (Array.isArray(parsed)) return parsed;
|
|
11
|
+
} catch (_e) {}
|
|
12
|
+
return raw.split(',').map((s) => Number(s.trim()));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readIntentData(element) {
|
|
16
|
+
const data = {};
|
|
17
|
+
const productId = element.getAttribute('product-id');
|
|
18
|
+
const productIds = element.getAttribute('product-ids');
|
|
19
|
+
const categoryId = element.getAttribute('category-id');
|
|
20
|
+
|
|
21
|
+
if (productIds) {
|
|
22
|
+
data.productIds = parseIds(productIds);
|
|
23
|
+
} else if (productId) {
|
|
24
|
+
data.productIds = [Number(productId)];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (categoryId) {
|
|
28
|
+
data.categoryIds = [Number(categoryId)];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const tag = element.getAttribute('tag');
|
|
32
|
+
if (tag) {
|
|
33
|
+
data.tag = tag;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sourceLocation = element.getAttribute('source-location');
|
|
37
|
+
if (sourceLocation) {
|
|
38
|
+
data.sourceLocation = sourceLocation;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const eventName = element.getAttribute('event-name');
|
|
42
|
+
if (eventName) {
|
|
43
|
+
element.dataset.eventName = eventName;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function mergeIntentData(element, patch) {
|
|
50
|
+
let existing = {};
|
|
51
|
+
try {
|
|
52
|
+
existing = JSON.parse(element.getAttribute('data-buyer-intent') || '{}');
|
|
53
|
+
} catch (_e) {
|
|
54
|
+
existing = {};
|
|
55
|
+
}
|
|
56
|
+
const merged = { ...existing, ...patch };
|
|
57
|
+
element.setAttribute('data-buyer-intent', JSON.stringify(merged));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function defineElement(tagName, ElementClass) {
|
|
61
|
+
if (typeof customElements !== 'undefined' && !customElements.get(tagName)) {
|
|
62
|
+
customElements.define(tagName, ElementClass);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class BuyerIntentSessionElement extends BaseHTMLElement {
|
|
67
|
+
connectedCallback() {
|
|
68
|
+
this.style.display = 'contents';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class BuyerIntentSubjectElement extends BaseHTMLElement {
|
|
73
|
+
connectedCallback() {
|
|
74
|
+
this.style.display = 'none';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class BuyerIntentViewElement extends BaseHTMLElement {
|
|
79
|
+
connectedCallback() {
|
|
80
|
+
ensureSDK();
|
|
81
|
+
mergeIntentData(this, readIntentData(this));
|
|
82
|
+
this.dataset.trigger = 'view';
|
|
83
|
+
queueMicrotask(() => {
|
|
84
|
+
this.dispatchEvent(
|
|
85
|
+
new CustomEvent('buyerintent:view', { bubbles: true })
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class BuyerIntentClickElement extends BaseHTMLElement {
|
|
92
|
+
connectedCallback() {
|
|
93
|
+
ensureSDK();
|
|
94
|
+
mergeIntentData(this, readIntentData(this));
|
|
95
|
+
this.dataset.trigger = 'click';
|
|
96
|
+
this._handleClick = () => {
|
|
97
|
+
this.dispatchEvent(
|
|
98
|
+
new CustomEvent('buyerintent:click', { bubbles: true })
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
this.addEventListener('click', this._handleClick);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
disconnectedCallback() {
|
|
105
|
+
if (this._handleClick) {
|
|
106
|
+
this.removeEventListener('click', this._handleClick);
|
|
107
|
+
this._handleClick = null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
defineElement('buyer-intent-session', BuyerIntentSessionElement);
|
|
113
|
+
defineElement('buyer-intent-subject', BuyerIntentSubjectElement);
|
|
114
|
+
defineElement('buyer-intent-view', BuyerIntentViewElement);
|
|
115
|
+
defineElement('buyer-intent-click', BuyerIntentClickElement);
|
|
116
|
+
|
|
117
|
+
export {
|
|
118
|
+
BuyerIntentSessionElement,
|
|
119
|
+
BuyerIntentSubjectElement,
|
|
120
|
+
BuyerIntentViewElement,
|
|
121
|
+
BuyerIntentClickElement,
|
|
122
|
+
mergeIntentData,
|
|
123
|
+
defineElement,
|
|
124
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const STORAGE_KEY = 'buyer_intent_distinct_id';
|
|
2
|
+
|
|
3
|
+
function generateId() {
|
|
4
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
5
|
+
return crypto.randomUUID();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return `bi-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function read() {
|
|
12
|
+
if (typeof window === 'undefined') {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
return window.localStorage.getItem(STORAGE_KEY);
|
|
18
|
+
} catch (_error) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function store(value) {
|
|
24
|
+
if (typeof window === 'undefined') {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
window.localStorage.setItem(STORAGE_KEY, value);
|
|
30
|
+
} catch (_error) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createIdentityManager() {
|
|
36
|
+
let distinctId = null;
|
|
37
|
+
|
|
38
|
+
function ensure() {
|
|
39
|
+
if (distinctId) {
|
|
40
|
+
return distinctId;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const stored = read();
|
|
44
|
+
if (stored) {
|
|
45
|
+
distinctId = stored;
|
|
46
|
+
return stored;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const generated = generateId();
|
|
50
|
+
distinctId = generated;
|
|
51
|
+
store(generated);
|
|
52
|
+
return generated;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function set(value) {
|
|
56
|
+
if (value) {
|
|
57
|
+
distinctId = value;
|
|
58
|
+
store(value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function get() {
|
|
63
|
+
return distinctId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { ensure, set, get };
|
|
67
|
+
}
|