UNPKG

visitory-sdk

Version:

Feature flags and analytics SDK for Visitory

283 lines (282 loc) 9.76 kB
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime"; // src/statsig.tsx import { createContext, useContext, useEffect, useState, } from 'react'; // Core Client Class export class StatsigClient { constructor(config) { this.config = config; this.baseUrl = config.baseUrl || 'https://visitory-psi.vercel.app'; if (!this.config.sessionId) { this.config.sessionId = this.generateSessionId(); } } generateSessionId() { return ('session_' + Math.random().toString(36).substring(2) + Date.now().toString(36)); } async checkFeatureFlag(flagKey, userAttributes) { try { const response = await fetch(`${this.baseUrl}/api/flags/evaluate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ apiKey: this.config.apiKey, flagKey, userAttributes: userAttributes || this.config.userAttributes, }), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('Error checking feature flag:', error); return { enabled: false, reason: 'error' }; } } async trackEvent(data) { try { const response = await fetch(`${this.baseUrl}/api/analytics/track`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ apiKey: this.config.apiKey, eventName: data.eventName, userAttributes: data.userAttributes || this.config.userAttributes, properties: data.properties, sessionId: data.sessionId || this.config.sessionId, }), }); return response.ok; } catch (error) { console.error('Error tracking event:', error); return false; } } async trackPageView(page, properties) { return this.trackEvent({ eventName: 'page_view', properties: { page: page || (typeof window !== 'undefined' ? window.location.pathname : '/'), url: typeof window !== 'undefined' ? window.location.href : '', referrer: typeof window !== 'undefined' ? document.referrer : '', ...properties, }, }); } async trackClick(element, properties) { return this.trackEvent({ eventName: 'click', properties: { element, page: typeof window !== 'undefined' ? window.location.pathname : '/', ...properties, }, }); } async trackCustomEvent(eventName, properties) { return this.trackEvent({ eventName, properties, }); } setUserAttributes(userAttributes) { this.config.userAttributes = userAttributes; } setSessionId(sessionId) { this.config.sessionId = sessionId; } getUserAttributes() { return this.config.userAttributes; } } // Singleton instance for standalone functions let statsigInstance = null; export const initialize = (config) => { statsigInstance = new StatsigClient(config); return statsigInstance; }; export const getInstance = () => { return statsigInstance; }; export const checkFeatureFlag = async (flagKey, userAttributes) => { if (!statsigInstance) { throw new Error('Statsig not initialized. Call initialize() first.'); } return statsigInstance.checkFeatureFlag(flagKey, userAttributes); }; export const trackEvent = async (data) => { if (!statsigInstance) { throw new Error('Statsig not initialized. Call initialize() first.'); } return statsigInstance.trackEvent(data); }; export const trackPageView = async (page, properties) => { if (!statsigInstance) { throw new Error('Statsig not initialized. Call initialize() first.'); } return statsigInstance.trackPageView(page, properties); }; export const trackClick = async (element, properties) => { if (!statsigInstance) { throw new Error('Statsig not initialized. Call initialize() first.'); } return statsigInstance.trackClick(element, properties); }; export const trackCustomEvent = async (eventName, properties) => { if (!statsigInstance) { throw new Error('Statsig not initialized. Call initialize() first.'); } return statsigInstance.trackCustomEvent(eventName, properties); }; export const setUserAttributes = (userAttributes) => { if (!statsigInstance) { throw new Error('Statsig not initialized. Call initialize() first.'); } statsigInstance.setUserAttributes(userAttributes); }; export const getUserAttributes = () => { if (!statsigInstance) { throw new Error('Statsig not initialized. Call initialize() first.'); } return statsigInstance.getUserAttributes(); }; // React Context const StatsigContext = createContext(null); export const StatsigProvider = ({ config, children, }) => { const [client] = useState(() => new StatsigClient(config)); useEffect(() => { client.trackPageView(); }, [client]); return (_jsx(StatsigContext.Provider, { value: client, children: children })); }; // React Hooks export const useStatsig = () => { const client = useContext(StatsigContext); if (!client) { throw new Error('useStatsig must be used within a StatsigProvider'); } return client; }; export const useFeatureFlag = (flagKey, userAttributes) => { const client = useStatsig(); const [result, setResult] = useState({ enabled: false }); const [loading, setLoading] = useState(true); useEffect(() => { let cancelled = false; const checkFlag = async () => { setLoading(true); try { const flagResult = await client.checkFeatureFlag(flagKey, userAttributes); if (!cancelled) { setResult(flagResult); } } catch (error) { console.error('Error checking feature flag:', error); if (!cancelled) { setResult({ enabled: false }); } } finally { if (!cancelled) { setLoading(false); } } }; checkFlag(); return () => { cancelled = true; }; }, [client, flagKey, JSON.stringify(userAttributes)]); return { enabled: result.enabled, loading, matchedSegments: result.matchedSegments, }; }; export const useTracking = () => { const client = useStatsig(); const trackEvent = async (data) => { return client.trackEvent(data); }; const trackPageView = async (page, properties) => { return client.trackPageView(page, properties); }; const trackClick = async (element, properties) => { return client.trackClick(element, properties); }; const trackCustomEvent = async (eventName, properties) => { return client.trackCustomEvent(eventName, properties); }; return { trackEvent, trackPageView, trackClick, trackCustomEvent, }; }; export const useUserAttributes = () => { const client = useStatsig(); const updateAttributes = (attributes) => { client.setUserAttributes(attributes); }; const getAttributes = () => { return client.getUserAttributes(); }; return { updateAttributes, getAttributes, }; }; export const useAutoPageTracking = (enabled = true) => { const client = useStatsig(); useEffect(() => { if (!enabled) return; client.trackPageView(); const handleLocationChange = () => { client.trackPageView(); }; window.addEventListener('popstate', handleLocationChange); return () => { window.removeEventListener('popstate', handleLocationChange); }; }, [client, enabled]); }; export const FeatureFlag = ({ flagKey, userAttributes, children, fallback = null, loading = null, }) => { const { enabled, loading: isLoading } = useFeatureFlag(flagKey, userAttributes); if (isLoading && loading) { return _jsx(_Fragment, { children: loading }); } return enabled ? _jsx(_Fragment, { children: children }) : _jsx(_Fragment, { children: fallback }); }; export const withFeatureFlag = (flagKey, userAttributes) => { return (Component) => { const WrappedComponent = (props) => { const { enabled, loading } = useFeatureFlag(flagKey, userAttributes); if (loading) { return null; } return enabled ? _jsx(Component, { ...props }) : null; }; WrappedComponent.displayName = `withFeatureFlag(${Component.displayName || Component.name})`; return WrappedComponent; }; }; export const useVariant = (flagKey, variants, userAttributes) => { const { enabled, loading, matchedSegments } = useFeatureFlag(flagKey, userAttributes); const variant = enabled && matchedSegments && matchedSegments.length > 0 ? variants[0] || null : null; return { variant, loading }; }; export default StatsigClient;