Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Added Beacon API support and replaced @wordpress/api-fetch with native fetch
1 change: 0 additions & 1 deletion projects/packages/woocommerce-analytics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"watch": "pnpm build --watch"
},
"dependencies": {
"@wordpress/api-fetch": "7.31.0",
"debug": "4.4.3"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ public function inject_analytics_data() {
// Set the assets URL for webpack to find the split assets.
wcAnalytics.assets_url = '<?php echo esc_url( plugins_url( '../build/', __DIR__ . '/class-woocommerce-analytics.php' ) ); ?>';

// Set the REST API tracking endpoint URL.
wcAnalytics.trackEndpoint = '<?php echo esc_url( rest_url( 'woocommerce-analytics/v1/track' ) ); ?>';

// Set common properties for all events.
wcAnalytics.commonProps = <?php echo wp_json_encode( $common_properties, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); ?>;

Expand Down
62 changes: 53 additions & 9 deletions projects/packages/woocommerce-analytics/src/client/api-client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
/**
* External dependencies
*/
import apiFetch from '@wordpress/api-fetch';
import debugFactory from 'debug';
/**
* Internal dependencies
*/
import { API_NAMESPACE, API_ENDPOINT, BATCH_SIZE, DEBOUNCE_DELAY } from './constants';
import { BATCH_SIZE, DEBOUNCE_DELAY } from './constants';
import type { ApiEvent, ApiFetchResponse } from './types/shared';

const debug = debugFactory( 'wc-analytics:api-client' );
Expand Down Expand Up @@ -77,6 +76,32 @@ export class ApiClient {
}, DEBOUNCE_DELAY );
};

/**
* Send events using the Beacon API for guaranteed delivery
*
* @param events - The events to send.
* @return True if beacon was successfully queued, false otherwise.
*/
private sendEventsViaBeacon = ( events: ApiEvent[] ): boolean => {
// Check if beacon API is available
if ( typeof navigator === 'undefined' || ! navigator.sendBeacon ) {
debug( 'Beacon API not available' );
return false;
}

try {
// Convert events to JSON and create a Blob with correct content type
const data = JSON.stringify( events );
const blob = new Blob( [ data ], { type: 'application/json' } );

// Send via beacon - returns true if successfully queued
return navigator.sendBeacon( window.wcAnalytics.trackEndpoint, blob );
} catch ( error ) {
debug( 'Beacon API failed: %o', error );
return false;
}
};

/**
* Flush all pending events immediately
*/
Expand All @@ -93,7 +118,20 @@ export class ApiClient {
const eventsToSend = [ ...this.eventQueue ];
this.eventQueue = [];

this.sendEvents( eventsToSend );
if ( ! window.wcAnalytics?.trackEndpoint ) {
debug( 'Track endpoint not available' );
return;
}

// Try sending via Beacon API first for guaranteed delivery
const beaconSuccess = this.sendEventsViaBeacon( eventsToSend );

if ( beaconSuccess ) {
debug( 'Sent %d events via Beacon API', eventsToSend.length );
} else {
debug( 'Failed to send events via Beacon API, falling back to fetch with keepalive' );
this.sendEvents( eventsToSend );
}
};

/**
Expand All @@ -109,16 +147,22 @@ export class ApiClient {
try {
debug( 'Sending %d events to API', events.length );

const response = await apiFetch< ApiFetchResponse >( {
path: `/${ API_NAMESPACE }/${ API_ENDPOINT }`,
const response = await fetch( window.wcAnalytics.trackEndpoint, {
method: 'POST',
data: events,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( events ),
keepalive: true,
credentials: 'same-origin',
} );
debug( 'API response received: %o', response );

if ( ! response.success ) {
debug( 'Some events failed to send: %o', response.results );
if ( ! response.ok ) {
throw new Error( `HTTP error! status: ${ response.status }` );
}

const data: ApiFetchResponse = await response.json();
debug( 'API response received: %o', data );
} catch ( error ) {
debug( 'Failed to send events to API: %o', error );
// Re-add events to queue for potential retry on next event
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,5 @@ export const EVENT_PREFIX = 'woocommerceanalytics_';
export const EVENT_NAME_REGEX = /^[a-z_][a-z0-9_]*$/;

// API Configuration
export const API_NAMESPACE = 'woocommerce-analytics/v1';
export const API_ENDPOINT = 'track';
export const BATCH_SIZE = 10;
export const DEBOUNCE_DELAY = 1000; // 1 second
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
declare global {
interface Window {
wcAnalytics?: {
trackEndpoint: string;
eventQueue: Array< { eventName: string; props?: Record< string, unknown > } >;
commonProps: Record< string, unknown >;
features: Record< string, boolean >;
Expand Down
Loading