We refactored the Lytics transport to leverage SDK Kit's transport plugin instead of reimplementing HTTP logic.
- Retry Logic - Exponential backoff with configurable max retries (default: 3)
- Multiple Transport Methods:
fetch- Modern, default for most requestsbeacon- For page unload scenarios (guaranteed delivery)xhr- Fallback for older environmentspixel- Ultimate fallback (GET only)
- Automatic Transport Selection - Chooses best transport based on:
- User preference
- Page unload state
- Browser support
- Timeout Handling - Configurable timeouts (default: 10s)
- Event System - Emits
transport:send,transport:success,transport:error - Request/Response Hooks -
beforeSend,afterSendfor middleware
Our lyticsTransportPlugin wraps SDK Kit's transport to handle:
- Query Param Auth - Automatically adds
?key=xxxto all requests - URL Building - Combines
baseUrl+path+ params - Response Envelope Unwrapping - Extracts
datafrom{ data, status, request_id } - Error Enrichment - Adds
request_idto errors for debugging - Simplified API - Just
get(path, params)andpost(path, body, params)
┌─────────────────────────────────────────────────┐
│ lio-client API (workflows, content, schema) │
│ └─ await lio.workflows.list() │
└────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ lyticsTransportPlugin │
│ • Add ?key=xxx auth │
│ • Build full URL │
│ • Unwrap { data, status, request_id } │
│ • Simplify to get()/post() │
└────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ SDK Kit transportPlugin │
│ • Retry with exponential backoff │
│ • Select best transport (fetch/beacon/xhr) │
│ • Handle timeouts │
│ • Emit events │
└────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Actual HTTP (fetch/sendBeacon/XMLHttpRequest) │
└─────────────────────────────────────────────────┘// ❌ Manual retry logic
// ❌ Manual timeout handling
// ❌ Only fetch (no beacon/xhr fallbacks)
// ❌ ~170 lines of transport code
async get<T>(path: string, params?: Record<string, any>): Promise<T> {
const url = buildUrl(path, params);
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(url, {
method: 'GET',
signal: controller.signal,
headers: { 'Accept': 'application/json' },
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: response.statusText }));
handleError({ response: { ...errorData, status: response.status } });
}
const data = await response.json() as ApiResponse<T>;
return unwrapResponse(data);
} catch (error: any) {
if (error.name === 'AbortError') {
const timeoutError = new Error(`Request timeout after ${timeout}ms`);
(timeoutError as any).code = 'TIMEOUT';
handleError(timeoutError);
}
handleError(error);
}
}// ✅ Retry logic from SDK Kit
// ✅ Timeout handling from SDK Kit
// ✅ Multiple transports (fetch/beacon/xhr/pixel)
// ✅ ~40 lines of Lytics-specific logic
async get<T>(path: string, params?: Record<string, any>): Promise<T> {
const url = buildUrl(path, params);
const request: TransportRequest = {
url,
method: 'GET',
headers: { 'Accept': 'application/json' },
};
const response = await sdkTransport.send(request); // SDK Kit handles everything!
return unwrapResponse<T>(response);
}Users can configure SDK Kit's transport behavior:
const lio = createLioClient({
apiKey: 'xxx',
transport: {
baseUrl: 'https://api.lytics.io',
defaultTimeout: 15000, // 15 seconds
defaultRetries: 5, // Retry 5 times
defaultTransport: 'fetch', // Or 'beacon', 'xhr'
}
});- 70% less code in our transport layer
- More robust - battle-tested retry/timeout logic
- More flexible - multiple transport methods
- Better UX - beacon transport for reliable page unload tracking
- Easier to maintain - SDK Kit handles the hard parts