fragment-headless-sdk 2.2.1 → 2.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/dist/utils/attribution.d.ts +22 -0
- package/dist/utils/attribution.js +62 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/metrics.d.ts +4 -0
- package/dist/utils/metrics.js +34 -3
- package/package.json +1 -1
- package/readme.md +11 -2
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface FragmentAttribution {
|
|
2
|
+
sectionId: string;
|
|
3
|
+
sectionType: 'announcement' | 'hero_banner';
|
|
4
|
+
timestamp: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Store attribution data for a section click
|
|
8
|
+
* Uses sessionStorage for session-scoped attribution (last-click wins)
|
|
9
|
+
* @param sectionId - The UUID of the section
|
|
10
|
+
* @param sectionType - The type of section (announcement or hero_banner)
|
|
11
|
+
*/
|
|
12
|
+
export declare function setAttribution(sectionId: string, sectionType: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Retrieve stored attribution data
|
|
15
|
+
* @returns The stored attribution or null if none exists
|
|
16
|
+
*/
|
|
17
|
+
export declare function getAttribution(): FragmentAttribution | null;
|
|
18
|
+
/**
|
|
19
|
+
* Clear stored attribution data
|
|
20
|
+
* Called after successful conversion tracking
|
|
21
|
+
*/
|
|
22
|
+
export declare function clearAttribution(): void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const STORAGE_KEY = 'fragment_attribution';
|
|
2
|
+
/**
|
|
3
|
+
* Store attribution data for a section click
|
|
4
|
+
* Uses sessionStorage for session-scoped attribution (last-click wins)
|
|
5
|
+
* @param sectionId - The UUID of the section
|
|
6
|
+
* @param sectionType - The type of section (announcement or hero_banner)
|
|
7
|
+
*/
|
|
8
|
+
export function setAttribution(sectionId, sectionType) {
|
|
9
|
+
if (typeof sessionStorage === 'undefined')
|
|
10
|
+
return;
|
|
11
|
+
const attribution = {
|
|
12
|
+
sectionId,
|
|
13
|
+
sectionType: sectionType,
|
|
14
|
+
timestamp: Date.now()
|
|
15
|
+
};
|
|
16
|
+
try {
|
|
17
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(attribution));
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
// Handle quota exceeded or other storage errors silently
|
|
21
|
+
console.warn('Fragment: Failed to set attribution', e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Retrieve stored attribution data
|
|
26
|
+
* @returns The stored attribution or null if none exists
|
|
27
|
+
*/
|
|
28
|
+
export function getAttribution() {
|
|
29
|
+
if (typeof sessionStorage === 'undefined')
|
|
30
|
+
return null;
|
|
31
|
+
try {
|
|
32
|
+
const stored = sessionStorage.getItem(STORAGE_KEY);
|
|
33
|
+
return stored ? JSON.parse(stored) : null;
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
// Handle parse errors or other issues
|
|
37
|
+
console.warn('Fragment: Failed to get attribution', e);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Clear stored attribution data
|
|
43
|
+
* Called after successful conversion tracking
|
|
44
|
+
*/
|
|
45
|
+
export function clearAttribution() {
|
|
46
|
+
if (typeof sessionStorage === 'undefined')
|
|
47
|
+
return;
|
|
48
|
+
try {
|
|
49
|
+
sessionStorage.removeItem(STORAGE_KEY);
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
console.warn('Fragment: Failed to clear attribution', e);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Make globally accessible for Shopify theme integrations
|
|
56
|
+
if (typeof window !== 'undefined') {
|
|
57
|
+
window.fragmentAttribution = {
|
|
58
|
+
get: getAttribution,
|
|
59
|
+
set: setAttribution,
|
|
60
|
+
clear: clearAttribution
|
|
61
|
+
};
|
|
62
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
package/dist/utils/metrics.d.ts
CHANGED
|
@@ -8,4 +8,8 @@ declare global {
|
|
|
8
8
|
dataLayer?: unknown[];
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Fire a scroll past metric when user scrolls past a section
|
|
13
|
+
*/
|
|
14
|
+
export declare function fireScrollPastMetric(measurementId: string | undefined, sectionType: SectionType, sectionId: string): void;
|
|
11
15
|
export declare function fireImpressionWhenVisible(el: HTMLElement, pixelUrl: string, measurementId?: string, sectionType?: SectionType, sectionId?: string): void;
|
package/dist/utils/metrics.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SectionType } from "../constants";
|
|
2
|
+
import { setAttribution } from "./attribution";
|
|
2
3
|
// --- Unicode-safe Base64URL ---
|
|
3
4
|
export function toBase64Url(input) {
|
|
4
5
|
// Handles emojis & non-ASCII reliably:
|
|
@@ -22,6 +23,10 @@ export function fireClickMetric(clickUrl, measurementId, sectionType, sectionId)
|
|
|
22
23
|
return;
|
|
23
24
|
if (!clickUrl)
|
|
24
25
|
return;
|
|
26
|
+
// Store attribution for potential purchase/add-to-cart tracking
|
|
27
|
+
if (sectionType && sectionId) {
|
|
28
|
+
setAttribution(sectionId, sectionType);
|
|
29
|
+
}
|
|
25
30
|
// Send to GA4 first (if available)
|
|
26
31
|
if (measurementId && sectionType && sectionId) {
|
|
27
32
|
sendGA4Event("click", measurementId, sectionType, sectionId);
|
|
@@ -55,7 +60,9 @@ export function fireClickMetric(clickUrl, measurementId, sectionType, sectionId)
|
|
|
55
60
|
* This allows filtering by event name in GA4 without requiring custom dimensions.
|
|
56
61
|
*/
|
|
57
62
|
function getSectionEventName(baseEventName, sectionType) {
|
|
58
|
-
const sectionPrefix = sectionType === SectionType.Announcement
|
|
63
|
+
const sectionPrefix = sectionType === SectionType.Announcement
|
|
64
|
+
? "fragment_announcement"
|
|
65
|
+
: "fragment_hero_banner";
|
|
59
66
|
return `${sectionPrefix}_${baseEventName}`;
|
|
60
67
|
}
|
|
61
68
|
/**
|
|
@@ -65,7 +72,7 @@ function getSectionEventName(baseEventName, sectionType) {
|
|
|
65
72
|
* Only sends section_id parameter - event name already indicates section type.
|
|
66
73
|
* This is a fire-and-forget operation that won't throw errors.
|
|
67
74
|
*/
|
|
68
|
-
function sendGA4Event(baseEventName, measurementId, sectionType, sectionId) {
|
|
75
|
+
function sendGA4Event(baseEventName, measurementId, sectionType, sectionId, additionalParams) {
|
|
69
76
|
if (typeof window === "undefined")
|
|
70
77
|
return;
|
|
71
78
|
if (!measurementId)
|
|
@@ -76,12 +83,25 @@ function sendGA4Event(baseEventName, measurementId, sectionType, sectionId) {
|
|
|
76
83
|
const eventName = getSectionEventName(baseEventName, sectionType);
|
|
77
84
|
window.gtag("event", eventName, {
|
|
78
85
|
section_id: sectionId,
|
|
86
|
+
...additionalParams,
|
|
79
87
|
});
|
|
80
88
|
}
|
|
81
89
|
catch {
|
|
82
90
|
// Silently fail - don't break tracking if GA4 fails
|
|
83
91
|
}
|
|
84
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Fire a scroll past metric when user scrolls past a section
|
|
95
|
+
*/
|
|
96
|
+
export function fireScrollPastMetric(measurementId, sectionType, sectionId) {
|
|
97
|
+
if (typeof window === "undefined")
|
|
98
|
+
return;
|
|
99
|
+
if (!measurementId || !sectionType || !sectionId)
|
|
100
|
+
return;
|
|
101
|
+
sendGA4Event("scroll_past", measurementId, sectionType, sectionId, {
|
|
102
|
+
engagement_type: "scroll_past",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
85
105
|
// --- View tracking (once per element) ---
|
|
86
106
|
const seenEls = typeof WeakSet !== "undefined" ? new WeakSet() : null;
|
|
87
107
|
export function fireImpressionWhenVisible(el, pixelUrl, measurementId, sectionType, sectionId) {
|
|
@@ -92,6 +112,7 @@ export function fireImpressionWhenVisible(el, pixelUrl, measurementId, sectionTy
|
|
|
92
112
|
if (seenEls && seenEls.has(el))
|
|
93
113
|
return; // de-dupe by element
|
|
94
114
|
let fired = false;
|
|
115
|
+
let hasScrolledPast = false;
|
|
95
116
|
const img = new Image();
|
|
96
117
|
const fire = () => {
|
|
97
118
|
if (fired)
|
|
@@ -119,8 +140,18 @@ export function fireImpressionWhenVisible(el, pixelUrl, measurementId, sectionTy
|
|
|
119
140
|
for (const e of entries) {
|
|
120
141
|
if (e.isIntersecting && e.intersectionRatio >= 0.3) {
|
|
121
142
|
fire();
|
|
143
|
+
// Don't disconnect - keep observing for scroll past
|
|
144
|
+
}
|
|
145
|
+
else if (!e.isIntersecting &&
|
|
146
|
+
!hasScrolledPast &&
|
|
147
|
+
e.boundingClientRect.top < 0 &&
|
|
148
|
+
fired) {
|
|
149
|
+
// User has scrolled past the section (it was visible, now it's above viewport)
|
|
150
|
+
hasScrolledPast = true;
|
|
151
|
+
if (measurementId && sectionType && sectionId) {
|
|
152
|
+
fireScrollPastMetric(measurementId, sectionType, sectionId);
|
|
153
|
+
}
|
|
122
154
|
io.disconnect();
|
|
123
|
-
break;
|
|
124
155
|
}
|
|
125
156
|
}
|
|
126
157
|
}, { threshold: [0, 0.3] });
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -4,7 +4,7 @@ The official SDK for integrating with Fragment-Shopify CMS. Provides React compo
|
|
|
4
4
|
|
|
5
5
|
## ✨ What's New
|
|
6
6
|
|
|
7
|
-
**v2.
|
|
7
|
+
**v2.3.0** - Attribution tracking system and scroll-past engagement metrics
|
|
8
8
|
|
|
9
9
|
> See [CHANGELOG.md](./docs/CHANGELOG.md) for full release history
|
|
10
10
|
|
|
@@ -73,12 +73,21 @@ Fragment-Shopify App (CMS) → API Endpoint → fragment-headless-sdk (Consumer)
|
|
|
73
73
|
|
|
74
74
|
### Google Analytics 4 (GA4) Integration (v2.2.0+)
|
|
75
75
|
|
|
76
|
-
- 📊 **Automatic Event Tracking**: Automatic
|
|
76
|
+
- 📊 **Automatic Event Tracking**: Automatic section-specific events (`fragment_announcement_view`, `fragment_hero_banner_click`, etc.)
|
|
77
77
|
- 🎯 **Type-Safe Section Types**: `SectionType` enum for consistent section identification
|
|
78
78
|
- 🔧 **Configurable Tracking**: Control tracking via `measurementId`, `sectionId`, and `sectionType` fields
|
|
79
79
|
- ⚡ **Dual Tracking**: Maintains existing pixel tracking while adding GA4 support
|
|
80
80
|
- 🛡️ **Graceful Fallback**: Works even if GA4 is not configured (doesn't break functionality)
|
|
81
81
|
- 📦 **Exported Types**: `SectionType` enum available for consumer use
|
|
82
|
+
- 📈 **Scroll Past Tracking**: Automatic `scroll_past` events when users scroll past sections (v2.3.0+)
|
|
83
|
+
|
|
84
|
+
### Attribution Tracking System (v2.3.0+)
|
|
85
|
+
|
|
86
|
+
- 🎯 **Last-Click Attribution**: Session-scoped attribution tracking for section interactions
|
|
87
|
+
- 💾 **SessionStorage Integration**: Stores attribution data in browser sessionStorage
|
|
88
|
+
- 🔗 **Shopify Theme Integration**: Accessible via `window.fragmentAttribution` for purchase tracking
|
|
89
|
+
- 📊 **Conversion Tracking**: Enables tracking which sections lead to conversions
|
|
90
|
+
- 🧹 **Automatic Cleanup**: Attribution data can be cleared after successful conversion tracking
|
|
82
91
|
|
|
83
92
|
---
|
|
84
93
|
|