fragment-headless-sdk 2.2.1 → 2.3.1

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.
@@ -0,0 +1,32 @@
1
+ import { SectionType } from '../constants';
2
+ export interface FragmentAttribution {
3
+ sectionId: string;
4
+ sectionType: SectionType;
5
+ timestamp: number;
6
+ }
7
+ /**
8
+ * Store attribution data for a section click
9
+ * Uses sessionStorage for session-scoped attribution (last-click wins)
10
+ * @param sectionId - The UUID of the section
11
+ * @param sectionType - The type of section (announcement or hero_banner)
12
+ */
13
+ export declare function setAttribution(sectionId: string, sectionType: SectionType): void;
14
+ /**
15
+ * Retrieve stored attribution data
16
+ * @returns The stored attribution or null if none exists or data is invalid
17
+ */
18
+ export declare function getAttribution(): FragmentAttribution | null;
19
+ /**
20
+ * Clear stored attribution data
21
+ * Called after successful conversion tracking
22
+ */
23
+ export declare function clearAttribution(): void;
24
+ declare global {
25
+ interface Window {
26
+ fragmentAttribution?: {
27
+ get: () => FragmentAttribution | null;
28
+ set: (sectionId: string, sectionType: SectionType) => void;
29
+ clear: () => void;
30
+ };
31
+ }
32
+ }
@@ -0,0 +1,89 @@
1
+ import { SectionType } from '../constants';
2
+ const STORAGE_KEY = 'fragment_attribution';
3
+ /**
4
+ * Store attribution data for a section click
5
+ * Uses sessionStorage for session-scoped attribution (last-click wins)
6
+ * @param sectionId - The UUID of the section
7
+ * @param sectionType - The type of section (announcement or hero_banner)
8
+ */
9
+ export function setAttribution(sectionId, sectionType) {
10
+ if (typeof sessionStorage === 'undefined')
11
+ return;
12
+ const attribution = {
13
+ sectionId,
14
+ sectionType,
15
+ timestamp: Date.now()
16
+ };
17
+ try {
18
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(attribution));
19
+ }
20
+ catch (e) {
21
+ // Handle quota exceeded or other storage errors silently
22
+ console.warn('Fragment: Failed to set attribution', e);
23
+ }
24
+ }
25
+ /**
26
+ * Validates that parsed data matches the FragmentAttribution interface
27
+ */
28
+ function isValidAttribution(data) {
29
+ if (!data || typeof data !== 'object')
30
+ return false;
31
+ const obj = data;
32
+ return (typeof obj.sectionId === 'string' &&
33
+ (obj.sectionType === SectionType.Announcement || obj.sectionType === SectionType.HeroBanner) &&
34
+ typeof obj.timestamp === 'number');
35
+ }
36
+ /**
37
+ * Retrieve stored attribution data
38
+ * @returns The stored attribution or null if none exists or data is invalid
39
+ */
40
+ export function getAttribution() {
41
+ if (typeof sessionStorage === 'undefined')
42
+ return null;
43
+ try {
44
+ const stored = sessionStorage.getItem(STORAGE_KEY);
45
+ if (!stored)
46
+ return null;
47
+ const parsed = JSON.parse(stored);
48
+ if (!isValidAttribution(parsed)) {
49
+ // Invalid data - clear it and return null
50
+ sessionStorage.removeItem(STORAGE_KEY);
51
+ return null;
52
+ }
53
+ return parsed;
54
+ }
55
+ catch (e) {
56
+ // Handle parse errors or other issues
57
+ console.warn('Fragment: Failed to get attribution', e);
58
+ // Clear potentially corrupted data
59
+ try {
60
+ sessionStorage.removeItem(STORAGE_KEY);
61
+ }
62
+ catch {
63
+ // Ignore errors when clearing
64
+ }
65
+ return null;
66
+ }
67
+ }
68
+ /**
69
+ * Clear stored attribution data
70
+ * Called after successful conversion tracking
71
+ */
72
+ export function clearAttribution() {
73
+ if (typeof sessionStorage === 'undefined')
74
+ return;
75
+ try {
76
+ sessionStorage.removeItem(STORAGE_KEY);
77
+ }
78
+ catch (e) {
79
+ console.warn('Fragment: Failed to clear attribution', e);
80
+ }
81
+ }
82
+ // Make globally accessible for Shopify theme integrations
83
+ if (typeof window !== 'undefined') {
84
+ window.fragmentAttribution = {
85
+ get: getAttribution,
86
+ set: setAttribution,
87
+ clear: clearAttribution
88
+ };
89
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./announcement-resolvers";
2
+ export * from "./attribution";
2
3
  export * from "./cache";
3
4
  export * from "./fetch-resource";
4
5
  export * from "./hero-resolvers";
@@ -1,4 +1,5 @@
1
1
  export * from "./announcement-resolvers";
2
+ export * from "./attribution";
2
3
  export * from "./cache";
3
4
  export * from "./fetch-resource";
4
5
  export * from "./hero-resolvers";
@@ -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;
@@ -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
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);
@@ -65,7 +70,7 @@ function getSectionEventName(baseEventName, sectionType) {
65
70
  * Only sends section_id parameter - event name already indicates section type.
66
71
  * This is a fire-and-forget operation that won't throw errors.
67
72
  */
68
- function sendGA4Event(baseEventName, measurementId, sectionType, sectionId) {
73
+ function sendGA4Event(baseEventName, measurementId, sectionType, sectionId, additionalParams) {
69
74
  if (typeof window === "undefined")
70
75
  return;
71
76
  if (!measurementId)
@@ -76,12 +81,25 @@ function sendGA4Event(baseEventName, measurementId, sectionType, sectionId) {
76
81
  const eventName = getSectionEventName(baseEventName, sectionType);
77
82
  window.gtag("event", eventName, {
78
83
  section_id: sectionId,
84
+ ...additionalParams,
79
85
  });
80
86
  }
81
87
  catch {
82
88
  // Silently fail - don't break tracking if GA4 fails
83
89
  }
84
90
  }
91
+ /**
92
+ * Fire a scroll past metric when user scrolls past a section
93
+ */
94
+ export function fireScrollPastMetric(measurementId, sectionType, sectionId) {
95
+ if (typeof window === "undefined")
96
+ return;
97
+ if (!measurementId || !sectionType || !sectionId)
98
+ return;
99
+ sendGA4Event("scroll_past", measurementId, sectionType, sectionId, {
100
+ engagement_type: "scroll_past",
101
+ });
102
+ }
85
103
  // --- View tracking (once per element) ---
86
104
  const seenEls = typeof WeakSet !== "undefined" ? new WeakSet() : null;
87
105
  export function fireImpressionWhenVisible(el, pixelUrl, measurementId, sectionType, sectionId) {
@@ -92,6 +110,7 @@ export function fireImpressionWhenVisible(el, pixelUrl, measurementId, sectionTy
92
110
  if (seenEls && seenEls.has(el))
93
111
  return; // de-dupe by element
94
112
  let fired = false;
113
+ let hasScrolledPast = false;
95
114
  const img = new Image();
96
115
  const fire = () => {
97
116
  if (fired)
@@ -119,6 +138,16 @@ export function fireImpressionWhenVisible(el, pixelUrl, measurementId, sectionTy
119
138
  for (const e of entries) {
120
139
  if (e.isIntersecting && e.intersectionRatio >= 0.3) {
121
140
  fire();
141
+ }
142
+ else if (!e.isIntersecting &&
143
+ !hasScrolledPast &&
144
+ e.boundingClientRect.top < 0 &&
145
+ fired) {
146
+ // User scrolled past the section (it's above viewport and was previously visible)
147
+ hasScrolledPast = true;
148
+ if (measurementId && sectionType && sectionId) {
149
+ fireScrollPastMetric(measurementId, sectionType, sectionId);
150
+ }
122
151
  io.disconnect();
123
152
  break;
124
153
  }
@@ -0,0 +1,542 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ### [2.3.1] - 2026-01-25
9
+
10
+ ### 🔧 Technical Improvements
11
+
12
+ - **Enhanced Error Handling** – Improved error handling in attribution functions
13
+ - Better error messages and logging
14
+ - Automatic cleanup of corrupted data
15
+ - Graceful degradation when sessionStorage is unavailable
16
+
17
+ ## [2.3.0] - 2026-01-25
18
+
19
+ ### ✨ Attribution Tracking System
20
+
21
+ - **Session-Based Attribution** – New attribution tracking system for purchase/add-to-cart attribution
22
+ - Automatically stores attribution data when users click section buttons
23
+ - Uses sessionStorage for session-scoped attribution (last-click wins)
24
+ - Tracks section ID, section type, and timestamp for each click
25
+
26
+ ### 📚 Documentation
27
+
28
+ - **New Attribution Section** – Added comprehensive documentation for attribution tracking
29
+ - Usage examples for Shopify theme integrations
30
+ - Programmatic access examples
31
+ - Use cases for purchase and add-to-cart attribution
32
+
33
+ ## [2.2.0] - 2025-01-18
34
+
35
+ ### ✨ Google Analytics 4 (GA4) Integration
36
+
37
+ - **GA4 Event Tracking** – Added comprehensive Google Analytics 4 integration for section tracking
38
+ - Automatic `section_view` events when sections become visible
39
+ - Automatic `section_click` events when users interact with buttons
40
+ - Configurable via `measurementId`, `sectionId`, and `sectionType` fields
41
+ - Graceful fallback if GA4 is not available (doesn't break functionality)
42
+
43
+ ## [2.1.9] - 2025-12-26
44
+
45
+ ### ✨ Announcement Resolvers System
46
+
47
+ - **New Announcement Resolvers Utility** – Added centralized color resolution system for Announcement components
48
+ - `resolveAnnouncementColors()` – Intelligent color resolution with guaranteed fallback values
49
+ - `resolveCountdownColors()` – Dedicated color resolver for countdown timer styling
50
+ - Supports both new format (`colors.background`) and legacy format (`bgColor`) for backward compatibility
51
+
52
+ ### 🔧 Component Refactoring
53
+
54
+ - **Centralized Color Resolution** – Refactored all Announcement components to use the new resolver system
55
+ - Improved code maintainability and consistency across all announcement types
56
+
57
+ ### 🎨 Countdown Timer Styling Improvements
58
+
59
+ - **Enhanced Layout** – Improved visual spacing and sizing
60
+ - Added `gap-1` between countdown digits
61
+ - Reduced digit font size from `text-xl` to `text-lg`
62
+ - Changed line height from `leading-none` to `leading-tight`
63
+
64
+ ### 🔧 Type System Updates
65
+
66
+ - **Enhanced Type Definitions** – Updated `IAnnouncement` and `IHero` interfaces
67
+ - Added `active_duration_seconds`, `last_activated_at`, and `last_deactivated_at` fields
68
+
69
+ ### 📚 Documentation
70
+
71
+ - **New Announcement Resolvers Documentation** – Added comprehensive guide for the new resolver system
72
+
73
+ ## [2.1.8] - 2025-12-25
74
+
75
+ ### ✨ Countdown Timer Styling Enhancements
76
+
77
+ - **Enhanced Countdown Timer Color Tokens** – Added comprehensive color customization for countdown timers
78
+ - `counterDigitColor` – Customize the color of countdown digits (default: `#FFFFFF`)
79
+ - `counterDigitBackgroundColor` – Customize the background color of countdown digits (default: `#000000`)
80
+ - `counterTextColor` – Customize the color of labels and separators (default: uses `counterDigitColor`)
81
+ - **Fixed Token Resolution** – Corrected `counterTextColor` token resolution to use proper fallback handling
82
+ - **Documentation Updates** – Added comprehensive countdown timer styling documentation to README
83
+
84
+ ## [2.1.5] - 2025-12-05
85
+
86
+ ### 🔧 Type System Updates
87
+
88
+ - **Enhanced Type Definitions** – Updated `IAnnouncement` and `IHero` interfaces to match Supabase schema
89
+ - Added `active_start_date: string | null` field
90
+ - Added `active_end_date: string | null` field
91
+ - Made `created_at` and `updated_at` nullable
92
+ - Made `page` field nullable in `IHero`
93
+
94
+ ### 🗑️ Breaking Changes
95
+
96
+ - **Removed Theme & Variant System** – Simplified styling system by removing unused theme/variant abstractions
97
+ - Removed `theme` property from `IFragmentStyling`
98
+ - Removed `variant` property from `IFragmentStyling`
99
+ - Focus now exclusively on design tokens, slots, responsive design, and state-based styling
100
+ - Updated all documentation to reflect the simplified system
101
+
102
+ ## [2.1.4] - 2025-11-18
103
+
104
+ ### ✨ UI/UX Improvements
105
+
106
+ - **Countdown Timer Enhancements** – Improved countdown timer visual design
107
+ - Removed background color from countdown digits for cleaner appearance
108
+ - Increased digit font size for better visibility
109
+ - Shortened label text: "Minutes" → "Mins", "Seconds" → "Secs"
110
+
111
+ - **Announcement Component Updates** – Enhanced announcement functionality
112
+ - Countdown timers can now display alongside buttons when both are enabled
113
+ - Improved layout flexibility for countdown announcements with CTAs
114
+
115
+ ## [2.1.2] - 2025-10-30
116
+
117
+ ### 📚 Documentation Updates
118
+
119
+ - **Enhanced README** – Updated documentation to highlight new click tracking features
120
+ - **Feature Highlights** – Added comprehensive documentation for the enhanced click tracking system
121
+ - **Usage Examples** – Improved examples showing the separation of button destinations and tracking
122
+
123
+ ## [2.1.1] - 2025-10-30
124
+
125
+ ### 🎯 Enhanced Click Tracking System
126
+
127
+ - **Improved Click Tracking Architecture** – Separated button destinations from click tracking for better user experience
128
+ - `buttonHref` now contains the actual destination URL (no redirect)
129
+ - `clickHref` contains the tracking URL for metrics collection
130
+ - Users go directly to intended destinations instead of through redirects
131
+ - **New `fireClickMetric()` Function** – Advanced click tracking without relying on redirects
132
+ - Uses `fetch()` with `no-cors` mode and `keepalive` for reliable tracking
133
+ - Falls back to Image pixel tracking for maximum compatibility
134
+ - Handles server-side rendering gracefully
135
+ - **Removed Automatic New Tab Behavior** – Links no longer force `target="_blank"`
136
+ - Provides more natural user experience
137
+ - Allows developers to control link behavior explicitly
138
+ - Maintains accessibility with proper ARIA labels
139
+
140
+ ### 🛠 Technical Improvements
141
+
142
+ - **Enhanced Component Props** – Both Hero and Announcement components now accept separate tracking parameters
143
+ - `buttonHref` for the actual destination
144
+ - `clickHref` for tracking metrics
145
+ - **Better Error Handling** – Click tracking fails gracefully without affecting user experience
146
+ - **Performance Optimized** – Non-blocking click tracking that doesn't delay navigation
147
+ - **Cross-Browser Compatible** – Works across all modern browsers with appropriate fallbacks
148
+
149
+ ### 📝 Usage Examples
150
+
151
+ ```typescript
152
+ // The SDK automatically handles the separation of concerns
153
+ const heroContent = {
154
+ title: "Shop Now",
155
+ buttonText: "Get Started",
156
+ buttonLink: "https://example.com/products", // Direct destination
157
+ clickUrlBase: "https://tracking.example.com/click", // Tracking base
158
+ // SDK automatically creates:
159
+ // - buttonHref: "https://example.com/products" (direct link)
160
+ // - clickHref: "https://tracking.example.com/click?u=..." (tracking)
161
+ };
162
+
163
+ <Hero content={heroContent} />;
164
+ ```
165
+
166
+ ### ⚡ Performance Benefits
167
+
168
+ - **Faster Navigation** – Users go directly to destinations without redirect delays
169
+ - **Reliable Tracking** – Click metrics are captured even if users navigate away quickly
170
+ - **Better SEO** – Direct links improve search engine crawling and indexing
171
+ - **Enhanced UX** – More predictable link behavior for better user experience
172
+
173
+ ### 🔄 Backward Compatibility
174
+
175
+ - **No Breaking Changes** – All existing implementations continue to work
176
+ - **Automatic Upgrade** – New tracking system activates automatically when `clickUrlBase` is present
177
+ - **Legacy Support** – Existing tracking URLs continue to function as before
178
+
179
+ ## [2.1.0] - 2025-10-27
180
+
181
+ ### 🎨 Enhanced Hero Styling System
182
+
183
+ - **New Hero Resolvers Utility** – Comprehensive utility system for advanced Hero component customization
184
+ - `resolveHeroColors()` – Intelligent color resolution with fallback handling
185
+ - `resolveHeroTypography()` – Typography settings with font family, size, and line height control
186
+ - `resolveContentWidthClass()` – Dynamic content width management
187
+ - `resolvePosition()` – Content positioning (left, center, right alignment)
188
+ - `resolveHeight()` – Flexible height configuration
189
+ - `renderText()` – Unified text rendering with typography and styling support
190
+
191
+ ### ✨ Advanced Typography Features
192
+
193
+ - **Font Family Support** – Built-in support for popular font families:
194
+ - Roboto, Open Sans, Lato, Montserrat, Poppins, Inter, Nunito Sans, Source Sans Pro
195
+ - Custom font family support through `FontKey` type system
196
+ - **Responsive Typography** – Granular control over font sizes and line heights
197
+ - Separate title and description typography settings
198
+ - Tailwind CSS class integration for responsive design
199
+ - **Typography Tokens** – New styling tokens for enhanced typography control:
200
+ - `titleFontSize`, `titleLineHeight`, `titleFont`
201
+ - `descriptionFontSize`, `descriptionLineHeight`, `descriptionFont`
202
+
203
+ ### 🏗️ Layout & Positioning Enhancements
204
+
205
+ - **Content Positioning** – New positioning system for Hero content alignment
206
+ - Left, center, and right alignment options
207
+ - Responsive positioning with proper text alignment
208
+ - **Content Width Control** – Dynamic content width management
209
+ - Configurable content container widths
210
+ - Responsive design integration
211
+ - **Height Management** – Flexible height configuration system
212
+ - Custom height classes support
213
+ - Default height fallbacks
214
+
215
+ ### 🎯 Developer Experience Improvements
216
+
217
+ - **Type Safety** – Enhanced TypeScript interfaces for all new features
218
+ - `HeroResolvedColors` interface for color resolution
219
+ - `HeroTypographySettings` interface for typography configuration
220
+ - `FontKey` type for font family validation
221
+ - **Utility Functions** – New helper functions for common operations
222
+ - `joinClassNames()` – Safe CSS class concatenation
223
+ - `fallbackColor()` – Color value validation with fallbacks
224
+ - **Better Defaults** – Comprehensive default values for all styling options
225
+ - `DEFAULT_COLORS` for color fallbacks
226
+ - `DEFAULT_TYPOGRAPHY` for typography defaults
227
+ - `FONT_FAMILY_MAP` for font family mappings
228
+
229
+ ### 🔄 Backward Compatibility
230
+
231
+ - **Seamless Migration** – All existing Hero components continue to work without changes
232
+ - **Progressive Enhancement** – New features are opt-in and don't affect existing implementations
233
+ - **Legacy Support** – Existing styling approaches remain fully supported
234
+
235
+ ### 📝 Usage Examples
236
+
237
+ ```typescript
238
+ // Enhanced Hero with new typography and positioning
239
+ const heroContent = {
240
+ title: "Welcome to Our Store",
241
+ description: "Discover amazing products",
242
+ buttonText: "Shop Now",
243
+ buttonLink: "/products",
244
+ imageUrl: "https://example.com/hero.jpg",
245
+
246
+ styling: {
247
+ tokens: {
248
+ colors: {
249
+ title: "#ffffff",
250
+ text: "#f0f0f0",
251
+ button: "#007bff",
252
+ buttonText: "#ffffff",
253
+ background: "#1a1a1a",
254
+ },
255
+ typography: {
256
+ titleFont: "montserrat",
257
+ titleFontSize: "text-6xl",
258
+ titleLineHeight: "leading-tight",
259
+ descriptionFont: "inter",
260
+ descriptionFontSize: "text-xl",
261
+ descriptionLineHeight: "leading-relaxed",
262
+ },
263
+ layout: {
264
+ contentWidth: "max-w-4xl",
265
+ position: "center",
266
+ height: "min-h-screen",
267
+ },
268
+ },
269
+ },
270
+ };
271
+ ```
272
+
273
+ ### 🛠 Technical Improvements
274
+
275
+ - **Performance Optimized** – Efficient color and typography resolution
276
+ - **Memory Efficient** – Optimized utility functions with minimal overhead
277
+ - **Tree Shakeable** – Individual utility functions can be imported separately
278
+ - **CSS-in-JS Ready** – Full compatibility with styled-components and emotion
279
+
280
+ ## [1.0.6] - 2025-10-16
281
+
282
+ ### 🚀 Next.js Caching Fix
283
+
284
+ - **Fixed Vercel/Next.js Caching Issues** – Resolved aggressive caching that prevented fresh data from appearing in production deployments
285
+ - Added `cache: 'no-store'` by default for all `fetchResource()` calls
286
+ - Added Next.js-specific `revalidate: 0` configuration
287
+ - Added cache-busting headers (`Cache-Control`, `Pragma`) to prevent CDN caching
288
+ - Smart environment detection for Next.js vs other frameworks
289
+
290
+ ### ✨ New Cache Management Features
291
+
292
+ - **Cache Configuration Options** – Added optional `cacheOptions` parameter to `fetchResource()`
293
+ - `cache`: Control request cache mode (default: 'no-store' for fresh data)
294
+ - `revalidate`: Next.js revalidation time in seconds (default: 0)
295
+ - `tags`: Next.js cache tags for selective invalidation
296
+ - **Cache Invalidation Utilities** – New helper functions for cache management
297
+ - `revalidateFragmentCache()` – Invalidate all or specific Fragment caches
298
+ - `revalidateResourceType()` – Invalidate cache for specific resource type
299
+ - `revalidateAllFragmentCaches()` – Clear all Fragment-related caches
300
+ - `createCacheTag()` / `createCacheTags()` – Generate cache tags
301
+
302
+ ### 🛠 Technical Improvements
303
+
304
+ - **Environment Detection** – Automatic Next.js environment detection for optimal cache settings
305
+ - **Backward Compatibility** – All existing code continues to work without changes
306
+ - **TypeScript Support** – Full type definitions for new cache options
307
+
308
+ ### 📝 Usage Examples
309
+
310
+ ```typescript
311
+ // Default behavior - always fresh data (recommended)
312
+ const announcements = await fetchResource({
313
+ baseUrl: process.env.FRAGMENT_BASE_URL,
314
+ apiKey: process.env.FRAGMENT_API_KEY,
315
+ type: ResourceType.Announcements,
316
+ });
317
+
318
+ // Optional: Enable caching for performance
319
+ const cachedHeroes = await fetchResource({
320
+ baseUrl: process.env.FRAGMENT_BASE_URL,
321
+ apiKey: process.env.FRAGMENT_API_KEY,
322
+ type: ResourceType.HeroBanners,
323
+ cacheOptions: {
324
+ cache: "default",
325
+ revalidate: 300, // 5 minutes
326
+ },
327
+ });
328
+
329
+ // Cache invalidation (server-side only)
330
+ import { revalidateResourceType } from "fragment-headless-sdk";
331
+ await revalidateResourceType(ResourceType.Announcements);
332
+ ```
333
+
334
+ ### ⚠️ Migration Notes
335
+
336
+ - **No breaking changes** – Existing code works without modification
337
+ - **Fresh data by default** – Your database updates will now appear immediately in production
338
+ - **Opt-in caching** – Use `cacheOptions` if you want to enable caching for performance
339
+
340
+ ## [1.0.5] - 2025-09-28
341
+
342
+ ### ✨ New Features
343
+
344
+ - **Metrics Tracking** – Added built-in view and click tracking for both **Hero** and **Announcement** sections.
345
+ - Each resource’s `content` object now includes two server-generated fields:
346
+ - `impressionUrl` – 1×1 pixel URL automatically fired when the component enters the viewport.
347
+ - `clickUrlBase` – base redirect URL used to record button clicks before sending the user to the final destination.
348
+ - The SDK’s `<Hero>` and `<Announcement>` components now automatically:
349
+ - trigger a view pixel when visible, and
350
+ - wrap their CTA buttons with a signed click-tracking redirect.
351
+
352
+ ### 🛠 Technical Notes
353
+
354
+ - The `makeSignedMetricUrls` helper was refactored to attach `impressionUrl` and `clickUrlBase` **inside the `content` object** for each item returned by the API.
355
+ - New client-side utilities exported from `utils`:
356
+ - `buildClickUrl()` – safely appends the final destination (`&u=...`) to a signed `clickUrlBase`.
357
+ - `fireImpressionWhenVisible()` – fires a pixel only once when an element is at least 30 % visible.
358
+
359
+ ### ⚠️ Migration Notes
360
+
361
+ - **No breaking changes.**
362
+ Existing components continue to work; the new tracking is automatic when you upgrade to v1.0.5.
363
+ - If you build custom CTAs outside the provided components, use the new helpers to track clicks and views manually.
364
+
365
+ ## [1.0.4] - 2025-09-27
366
+
367
+ - **Types:** `IHero` now includes `views_count: number` and `clicks_count: number`.
368
+
369
+ ### 📝 Notes
370
+
371
+ - No breaking changes..
372
+
373
+ ## [1.0.3] - 2025-09-21
374
+
375
+ ### 🎨 UI/UX Improvements
376
+
377
+ - **Announcement Type Rename** - Changed `AnnouncementType.Announcement` to `AnnouncementType.Static` for better clarity
378
+ - **Countdown Timer Styling** - Removed white background from countdown timer for cleaner appearance
379
+ - **Layout Optimization** - Improved announcement banner layout with:
380
+ - Removed top/bottom padding (`py-3`) for more compact design
381
+ - Added 50px minimum height for consistent banner sizing
382
+ - Enhanced vertical centering of all content elements
383
+ - **Timer Digit Sizing** - Made countdown timer digits smaller and more compact:
384
+ - Reduced digit size from 24×28px to 20×24px
385
+ - Changed font size from `text-xl` to `text-base`
386
+
387
+ ### 🔧 Technical Changes
388
+
389
+ - Updated `announcementTypes` array to reflect new "Static" label
390
+ - Improved flexbox layout for better vertical alignment
391
+ - Maintained responsive design across all screen sizes
392
+
393
+ ## [1.0.2] - 2025-09-20
394
+
395
+ ### 🔄 Breaking Changes
396
+
397
+ - **Component Naming** - Renamed `Banner` component and all related types to `Announcement`
398
+ - `Banner` → `Announcement`
399
+ - `IBannerContent` → `IAnnouncementContent`
400
+ - `IBanner` → `IAnnouncement`
401
+ - `BannerType` → `AnnouncementType`
402
+ - `BannerStatus` → `AnnouncementStatus`
403
+ - `BannerButton` → `AnnouncementButton`
404
+ - `BannerStyles` → `AnnouncementStyles`
405
+ - `bannerHtml` property → `announcementHtml`
406
+
407
+ - **Resource Type Updates** - Updated resource type enums for consistency
408
+ - `ResourceType.HeroSections` → `ResourceType.HeroBanners`
409
+ - `ResourceType.Banners` → `ResourceType.Announcements`
410
+
411
+ ### 🔗 API Endpoint Changes
412
+
413
+ - Updated API endpoints to match new naming:
414
+ - `/api/v1/hero-sections` → `/api/v1/hero-banners`
415
+ - `/api/v1/banners` → `/api/v1/announcements`
416
+
417
+ ### 📚 Documentation
418
+
419
+ - Updated all documentation to reflect new component and type names
420
+ - Updated README.md examples with new ResourceType values
421
+ - Updated code examples throughout
422
+
423
+ ### 🛠️ Migration Guide
424
+
425
+ To update your existing code:
426
+
427
+ ```typescript
428
+ // Before (v1.0.1)
429
+ import {
430
+ Banner,
431
+ BannerType,
432
+ IBannerContent,
433
+ ResourceType,
434
+ } from "fragment-headless-sdk";
435
+
436
+ const banners = await fetchResource({
437
+ type: ResourceType.Banners,
438
+ });
439
+
440
+ <Banner content={bannerContent} type={BannerType.Standard} />;
441
+
442
+ // After (v1.0.2)
443
+ import {
444
+ Announcement,
445
+ AnnouncementType,
446
+ IAnnouncementContent,
447
+ ResourceType,
448
+ } from "fragment-headless-sdk";
449
+
450
+ const announcements = await fetchResource({
451
+ type: ResourceType.Announcements,
452
+ });
453
+
454
+ <Announcement
455
+ content={announcementContent}
456
+ type={AnnouncementType.Announcement}
457
+ />;
458
+ ```
459
+
460
+ ## [1.0.1] - 2025-09-07
461
+
462
+ ### 🎉 Initial Release
463
+
464
+ The official SDK for integrating with fragment-shopify CMS. Production-ready with full API key authentication support.
465
+
466
+ ### Features
467
+
468
+ - **Complete TypeScript Support** - Full type definitions for all components and API responses
469
+ - **React Components** - Pre-built Hero and Announcement components with responsive design
470
+ - **API Integration** - Built-in utilities for fetching sections from fragment-shopify app
471
+ - **Production Ready** - Full API key authentication with v1 endpoints
472
+ - **Tailwind CSS** - Styled components with customizable design system
473
+
474
+ ### Components
475
+
476
+ - **Hero Component** - Responsive hero sections with desktop/mobile layouts
477
+ - Support for images, videos, and call-to-action buttons
478
+ - Customizable content and styling
479
+ - **Announcement Component** - Flexible announcement bars with multiple display types
480
+ - Standard, marquee, and countdown announcement types
481
+ - `AnnouncementButton` and `CountdownTimer` sub-components
482
+
483
+ ### API Integration
484
+
485
+ - **`fetchResource()` Function** - Simple API for fetching sections
486
+ - **API Key Authentication** - Secure authentication using `keyId:secret` format
487
+ - **v1 Endpoints** - Production endpoints (`/api/v1/announcements`, `/api/v1/hero-banners`)
488
+ - **Error Handling** - Comprehensive error handling and logging
489
+ - **Type Safety** - Full TypeScript support for all API responses
490
+
491
+ ### Usage
492
+
493
+ ```tsx
494
+ import {
495
+ fetchResource,
496
+ ResourceType,
497
+ Hero,
498
+ Announcement,
499
+ } from "fragment-headless-sdk";
500
+
501
+ // Fetch data
502
+ const heroes = await fetchResource({
503
+ baseUrl: process.env.EXTERNAL_API_URL,
504
+ apiKey: process.env.FRAGMENT_API_KEY,
505
+ type: ResourceType.HeroBanners,
506
+ });
507
+
508
+ // Render components
509
+ <Hero content={heroes[0]?.content} />;
510
+ ```
511
+
512
+ ### Environment Variables
513
+
514
+ ```bash
515
+ EXTERNAL_API_URL=https://your-fragment-app.vercel.app
516
+ FRAGMENT_API_KEY=bh_a1b2c3d4e5f6:your-64-char-secret
517
+ ```
518
+
519
+ ---
520
+
521
+ ## Upcoming Changes
522
+
523
+ ### v1.1.0 (Planned)
524
+
525
+ - Enhanced error handling with specific error types
526
+ - Support for additional section types
527
+ - Caching and performance optimizations
528
+
529
+ ### v1.2.0 (Planned)
530
+
531
+ - Real-time updates via webhooks
532
+ - Advanced filtering and sorting options
533
+ - Batch operations support
534
+ - TypeScript strict mode compatibility
535
+
536
+ ---
537
+
538
+ ## Support
539
+
540
+ - **Documentation**: [README.md](./README.md)
541
+ - **NPM Package**: https://www.npmjs.com/package/fragment-shopify-sdk
542
+ - **Issues**: Please report issues in the GitHub repository
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fragment-headless-sdk",
3
- "version": "2.2.1",
3
+ "version": "2.3.1",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,8 @@
12
12
  "./styles": "./dist/styles/fragment-sdk.css"
13
13
  },
14
14
  "files": [
15
- "dist"
15
+ "dist",
16
+ "docs/CHANGELOG.md"
16
17
  ],
17
18
  "scripts": {
18
19
  "build": "tsc"
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.2.0** - Google Analytics 4 (GA4) integration with automatic section tracking
7
+ **v2.3.1** - Enhanced attribution tracking with session storage and global API
8
8
 
9
9
  > See [CHANGELOG.md](./docs/CHANGELOG.md) for full release history
10
10
 
@@ -80,6 +80,15 @@ Fragment-Shopify App (CMS) → API Endpoint → fragment-headless-sdk (Consumer)
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
82
 
83
+ ### Attribution Tracking System (v2.4.0+)
84
+
85
+ - 🎯 **Session-Based Attribution**: Tracks which section was clicked for purchase/add-to-cart attribution
86
+ - 💾 **SessionStorage Integration**: Uses sessionStorage for session-scoped attribution (last-click wins)
87
+ - 🌐 **Global API Access**: Exposed as `window.fragmentAttribution` for Shopify theme integrations
88
+ - 🔒 **Type-Safe**: Full TypeScript support with proper type definitions
89
+ - ✅ **Data Validation**: Automatic validation and cleanup of corrupted attribution data
90
+ - 🧹 **Auto-Cleanup**: Invalid or corrupted data is automatically cleared from storage
91
+
83
92
  ---
84
93
 
85
94
  ## 📦 Installation
@@ -117,7 +126,7 @@ module.exports = {
117
126
  "./components/**/*.{js,ts,jsx,tsx,mdx}",
118
127
  path.join(
119
128
  __dirname,
120
- "node_modules/fragment-headless-sdk/dist/**/*.{js,ts,jsx,tsx}"
129
+ "node_modules/fragment-headless-sdk/dist/**/*.{js,ts,jsx,tsx}",
121
130
  ),
122
131
  ],
123
132
  theme: {
@@ -481,6 +490,105 @@ const announcementContent = {
481
490
  };
482
491
  ```
483
492
 
493
+ ## 🎯 Attribution Tracking
494
+
495
+ The SDK automatically tracks which section was clicked for attribution purposes. This is useful for tracking conversions (purchases, add-to-cart) back to the original section interaction.
496
+
497
+ ### Automatic Attribution
498
+
499
+ When a user clicks a button in a Hero or Announcement section, the SDK automatically stores attribution data in `sessionStorage`:
500
+
501
+ ```typescript
502
+ // Attribution is automatically set when clicks occur
503
+ // No additional code needed - it happens automatically!
504
+ ```
505
+
506
+ ### Global API (Shopify Theme Integration)
507
+
508
+ For Shopify theme integrations, attribution data is exposed globally via `window.fragmentAttribution`:
509
+
510
+ ```javascript
511
+ // Get current attribution data
512
+ const attribution = window.fragmentAttribution.get();
513
+ // Returns: { sectionId: "uuid", sectionType: "announcement" | "hero_banner", timestamp: 1234567890 }
514
+ // Or: null if no attribution exists
515
+
516
+ // Manually set attribution (usually not needed - SDK handles this automatically)
517
+ window.fragmentAttribution.set("section-uuid", "announcement");
518
+
519
+ // Clear attribution (e.g., after successful conversion tracking)
520
+ window.fragmentAttribution.clear();
521
+ ```
522
+
523
+ ### Programmatic Access
524
+
525
+ You can also import and use attribution functions directly:
526
+
527
+ ```typescript
528
+ import { getAttribution, clearAttribution } from "fragment-headless-sdk";
529
+
530
+ // Get attribution data
531
+ const attribution = getAttribution();
532
+ if (attribution) {
533
+ console.log(`User clicked section: ${attribution.sectionId}`);
534
+ console.log(`Section type: ${attribution.sectionType}`);
535
+ console.log(`Clicked at: ${new Date(attribution.timestamp)}`);
536
+ }
537
+
538
+ // Clear attribution after tracking conversion
539
+ clearAttribution();
540
+ ```
541
+
542
+ ### Use Cases
543
+
544
+ **Purchase Attribution:**
545
+
546
+ ```javascript
547
+ // In your Shopify checkout success page
548
+ if (window.fragmentAttribution) {
549
+ const attribution = window.fragmentAttribution.get();
550
+ if (attribution) {
551
+ // Track purchase back to the section click
552
+ // e.g., send to analytics, update database, etc.
553
+ trackPurchase(attribution.sectionId, attribution.sectionType);
554
+
555
+ // Clear attribution after tracking
556
+ window.fragmentAttribution.clear();
557
+ }
558
+ }
559
+ ```
560
+
561
+ **Add-to-Cart Attribution:**
562
+
563
+ ```javascript
564
+ // In your add-to-cart handler
565
+ if (window.fragmentAttribution) {
566
+ const attribution = window.fragmentAttribution.get();
567
+ if (attribution) {
568
+ // Track add-to-cart back to the section click
569
+ trackAddToCart(attribution.sectionId, attribution.sectionType);
570
+ }
571
+ }
572
+ ```
573
+
574
+ ### Attribution Data Structure
575
+
576
+ ```typescript
577
+ interface FragmentAttribution {
578
+ sectionId: string; // UUID of the section that was clicked
579
+ sectionType: SectionType; // "announcement" | "hero_banner"
580
+ timestamp: number; // Unix timestamp of when the click occurred
581
+ }
582
+ ```
583
+
584
+ ### Features
585
+
586
+ - ✅ **Session-Scoped**: Attribution data persists for the browser session only
587
+ - ✅ **Last-Click Wins**: New clicks overwrite previous attribution data
588
+ - ✅ **Type-Safe**: Full TypeScript support with proper type definitions
589
+ - ✅ **Data Validation**: Automatically validates and cleans corrupted data
590
+ - ✅ **Graceful Degradation**: Works even if sessionStorage is unavailable
591
+
484
592
  ## 🔑 API Key Setup
485
593
 
486
594
  Before using the SDK, you need to generate an API key from your Fragment-Shopify app: