visitory-sdk
Version:
Feature flags and analytics SDK for Visitory
283 lines (282 loc) • 9.76 kB
JavaScript
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;