@rolloutly/core 0.1.7 → 0.1.9

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.
package/dist/index.cjs CHANGED
@@ -193,7 +193,11 @@ var RolloutlyClient = class {
193
193
  try {
194
194
  await this.fetchFlags();
195
195
  if (this.config.realtimeEnabled) {
196
- await this.setupRealtime();
196
+ try {
197
+ await this.setupRealtime();
198
+ } catch (err) {
199
+ this.log("Warning: Realtime setup failed:", err instanceof Error ? err.message : String(err));
200
+ }
197
201
  }
198
202
  this.status = "ready";
199
203
  this.initResolve();
@@ -208,24 +212,35 @@ var RolloutlyClient = class {
208
212
  async fetchFlags() {
209
213
  const url = `${this.config.baseUrl}/api/sdk/flags`;
210
214
  const hasUserContext = this.config.user && Object.keys(this.config.user).length > 0;
211
- const response = await fetch(url, {
212
- method: hasUserContext ? "POST" : "GET",
213
- headers: {
214
- Authorization: `Bearer ${this.config.token}`,
215
- ...hasUserContext && { "Content-Type": "application/json" }
216
- },
217
- ...hasUserContext && {
218
- body: JSON.stringify({ user: this.config.user })
215
+ try {
216
+ const response = await fetch(url, {
217
+ method: hasUserContext ? "POST" : "GET",
218
+ headers: {
219
+ Authorization: `Bearer ${this.config.token}`,
220
+ ...hasUserContext && { "Content-Type": "application/json" }
221
+ },
222
+ ...hasUserContext && {
223
+ body: JSON.stringify({ user: this.config.user })
224
+ }
225
+ });
226
+ if (!response.ok) {
227
+ const errorMessage = `Failed to fetch flags: ${response.status}`;
228
+ this.log("Warning:", errorMessage, "- using cached/default values");
229
+ this.updateFlagValuesCache();
230
+ this.notifyListeners();
231
+ return;
219
232
  }
220
- });
221
- if (!response.ok) {
222
- throw new Error(`Failed to fetch flags: ${response.status}`);
233
+ const data = await response.json();
234
+ this.flags = data.flags;
235
+ this.cacheFlags();
236
+ this.notifyListeners();
237
+ this.log("Flags fetched:", Object.keys(this.flags).length, hasUserContext ? "(with user context)" : "");
238
+ } catch (err) {
239
+ const errorMessage = err instanceof Error ? err.message : String(err);
240
+ this.log("Warning: Network error fetching flags:", errorMessage, "- using cached/default values");
241
+ this.updateFlagValuesCache();
242
+ this.notifyListeners();
223
243
  }
224
- const data = await response.json();
225
- this.flags = data.flags;
226
- this.cacheFlags();
227
- this.notifyListeners();
228
- this.log("Flags fetched:", Object.keys(this.flags).length, hasUserContext ? "(with user context)" : "");
229
244
  }
230
245
  async setupRealtime() {
231
246
  const databaseURL = await this.getDatabaseUrl();
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"names":["ref","off","initializeApp","getDatabase","onValue"],"mappings":"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","file":"index.cjs","sourcesContent":["import { initializeApp, type FirebaseApp } from 'firebase/app';\nimport {\n getDatabase,\n off,\n onValue,\n ref,\n type Database,\n type Unsubscribe,\n} from 'firebase/database';\n\nimport type {\n ClientStatus,\n Flag,\n FlagChangeListener,\n FlagMap,\n FlagValue,\n ParsedToken,\n RolloutlyConfig,\n UserContext,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://rolloutly.com';\nconst CACHE_KEY = 'rolloutly_flags';\n\n/**\n * Convert a string to camelCase\n * 'instagram-integration' -> 'instagramIntegration'\n * 'my_feature_flag' -> 'myFeatureFlag'\n */\nconst toCamelCase = (str: string): string => {\n return str.replace(/[-_](.)/g, (_, char: string) => char.toUpperCase());\n};\n\n/**\n * Check if a key needs camelCase conversion (has hyphens or underscores)\n */\nconst needsCamelCase = (key: string): boolean => {\n return key.includes('-') || key.includes('_');\n};\n\nexport class RolloutlyClient {\n private config: Required<\n Omit<RolloutlyConfig, 'defaultFlags' | 'user'> & {\n defaultFlags: Record<string, FlagValue>;\n user: UserContext | undefined;\n }\n >;\n private flags: FlagMap = {};\n private flagValuesCache: Record<string, FlagValue | undefined> = {};\n private status: ClientStatus = 'initializing';\n private error: Error | null = null;\n private listeners: Set<FlagChangeListener> = new Set();\n private parsedToken: ParsedToken;\n private firebaseApp: FirebaseApp | null = null;\n private database: Database | null = null;\n private realtimeUnsubscribe: Unsubscribe | null = null;\n private initPromise: Promise<void>;\n private initResolve!: () => void;\n private initReject!: (error: Error) => void;\n\n constructor(config: RolloutlyConfig) {\n this.config = {\n token: config.token,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n realtimeEnabled: config.realtimeEnabled ?? true,\n defaultFlags: config.defaultFlags ?? {},\n user: config.user,\n debug: config.debug ?? false,\n };\n\n // Parse the token\n const parsed = this.parseToken(config.token);\n\n if (!parsed) {\n throw new Error('Invalid SDK token format');\n }\n\n this.parsedToken = parsed;\n\n // Create init promise\n this.initPromise = new Promise((resolve, reject) => {\n this.initResolve = resolve;\n this.initReject = reject;\n });\n\n // Load cached flags first\n this.loadCachedFlags();\n\n // Start initialization\n void this.initialize();\n }\n\n /**\n * Wait for the client to be initialized\n */\n async waitForInit(): Promise<void> {\n return this.initPromise;\n }\n\n /**\n * Get a single flag value\n * Supports both original keys ('instagram-integration') and camelCase ('instagramIntegration')\n */\n getFlag<T extends FlagValue = FlagValue>(key: string): T | undefined {\n // Try direct lookup first\n let flag = this.flags[key];\n\n // If not found and key is camelCase, try to find original key\n if (!flag) {\n const originalKey = this.findOriginalKey(key);\n\n if (originalKey) {\n flag = this.flags[originalKey];\n }\n }\n\n if (flag) {\n const defaultKey = this.findOriginalKey(key) || key;\n\n return (\n flag.enabled ? flag.value : this.config.defaultFlags[defaultKey]\n ) as T;\n }\n\n return this.config.defaultFlags[key] as T;\n }\n\n /**\n * Get all flags\n */\n getFlags(): FlagMap {\n return { ...this.flags };\n }\n\n /**\n * Get all flag values as a stable object (for React hooks)\n * Returns a cached object that only changes when flags change\n */\n getFlagValues(): Record<string, FlagValue | undefined> {\n return this.flagValuesCache;\n }\n\n /**\n * Check if a boolean flag is enabled\n * Supports both original keys ('instagram-integration') and camelCase ('instagramIntegration')\n */\n isEnabled(key: string): boolean {\n // Try direct lookup first\n let flag = this.flags[key];\n let lookupKey = key;\n\n // If not found and key is camelCase, try to find original key\n if (!flag) {\n const originalKey = this.findOriginalKey(key);\n\n if (originalKey) {\n flag = this.flags[originalKey];\n lookupKey = originalKey;\n }\n }\n\n if (!flag) {\n return Boolean(this.config.defaultFlags[lookupKey]);\n }\n\n if (!flag.enabled) {\n return Boolean(this.config.defaultFlags[lookupKey]);\n }\n\n return flag.type === 'boolean' ? Boolean(flag.value) : true;\n }\n\n /**\n * Get current client status\n */\n getStatus(): ClientStatus {\n return this.status;\n }\n\n /**\n * Get the last error if any\n */\n getError(): Error | null {\n return this.error;\n }\n\n /**\n * Subscribe to flag changes (for React useSyncExternalStore)\n */\n subscribe(listener: FlagChangeListener): () => void {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Update the user context and re-fetch flags\n * Call this when the user logs in or their attributes change\n */\n async identify(user: UserContext): Promise<void> {\n this.config.user = user;\n this.log('User identified:', user.userId || user.email || 'anonymous');\n\n // Re-fetch flags with new user context\n await this.fetchFlags();\n }\n\n /**\n * Clear the user context (e.g., on logout)\n */\n async reset(): Promise<void> {\n this.config.user = undefined;\n this.log('User context reset');\n\n // Re-fetch flags without user context\n await this.fetchFlags();\n }\n\n /**\n * Get the current user context\n */\n getUser(): UserContext | undefined {\n return this.config.user;\n }\n\n /**\n * Cleanup and disconnect\n */\n close(): void {\n if (this.realtimeUnsubscribe) {\n this.realtimeUnsubscribe();\n this.realtimeUnsubscribe = null;\n }\n\n if (this.database) {\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n off(flagsRef);\n }\n\n this.listeners.clear();\n this.log('Client closed');\n }\n\n // ==================== Private Methods ====================\n\n /**\n * Find the original flag key from a camelCase version\n * 'instagramIntegration' -> 'instagram-integration' (if it exists in flags)\n */\n private findOriginalKey(camelKey: string): string | null {\n // If the key exists directly, return null (no conversion needed)\n if (this.flags[camelKey]) {\n return null;\n }\n\n // Search for a flag whose camelCase version matches\n for (const key of Object.keys(this.flags)) {\n if (needsCamelCase(key) && toCamelCase(key) === camelKey) {\n return key;\n }\n }\n\n return null;\n }\n\n private parseToken(token: string): ParsedToken | null {\n const parts = token.split('_');\n\n // Format: rly_{projectId}_{environmentKey}_{randomString}\n if (parts.length < 4 || parts[0] !== 'rly') {\n return null;\n }\n\n return {\n projectId: parts[1],\n environmentKey: parts[2],\n };\n }\n\n private async initialize(): Promise<void> {\n try {\n // Fetch initial flags from API\n await this.fetchFlags();\n\n // Set up real-time updates if enabled\n if (this.config.realtimeEnabled) {\n await this.setupRealtime();\n }\n\n this.status = 'ready';\n this.initResolve();\n this.log('Client initialized');\n } catch (err) {\n this.status = 'error';\n this.error = err instanceof Error ? err : new Error(String(err));\n this.initReject(this.error);\n this.log('Initialization failed:', this.error.message);\n }\n }\n\n private async fetchFlags(): Promise<void> {\n const url = `${this.config.baseUrl}/api/sdk/flags`;\n\n // Use POST with user context if available, otherwise GET\n const hasUserContext = this.config.user && Object.keys(this.config.user).length > 0;\n\n const response = await fetch(url, {\n method: hasUserContext ? 'POST' : 'GET',\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n ...(hasUserContext && { 'Content-Type': 'application/json' }),\n },\n ...(hasUserContext && {\n body: JSON.stringify({ user: this.config.user }),\n }),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch flags: ${response.status}`);\n }\n\n const data = (await response.json()) as { flags: FlagMap };\n this.flags = data.flags;\n this.cacheFlags();\n this.notifyListeners();\n this.log('Flags fetched:', Object.keys(this.flags).length, hasUserContext ? '(with user context)' : '');\n }\n\n private async setupRealtime(): Promise<void> {\n // Initialize Firebase with minimal config for Realtime DB\n // The database URL is derived from the API response or configured\n const databaseURL = await this.getDatabaseUrl();\n\n if (!databaseURL) {\n this.log('Realtime DB URL not available, skipping real-time updates');\n\n return;\n }\n\n this.firebaseApp = initializeApp(\n {\n databaseURL,\n },\n `rolloutly-${Date.now()}`,\n );\n\n this.database = getDatabase(this.firebaseApp);\n\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n\n this.realtimeUnsubscribe = onValue(\n flagsRef,\n (snapshot) => {\n const data = snapshot.val() as Record<string, Flag> | null;\n\n if (data) {\n const hasUserContext = this.config.user && Object.keys(this.config.user).length > 0;\n\n if (hasUserContext) {\n // When user context is provided, re-fetch from API to get\n // server-evaluated targeting rules instead of using raw values\n this.log('Realtime change detected, re-fetching with user context...');\n void this.fetchFlags();\n } else {\n // No user context, use realtime values directly\n this.flags = Object.entries(data).reduce<FlagMap>(\n (acc, [key, flag]) => {\n acc[key] = { ...flag, key };\n\n return acc;\n },\n {},\n );\n this.cacheFlags();\n this.notifyListeners();\n this.log('Realtime update received');\n }\n }\n },\n (error) => {\n this.log('Realtime error:', error.message);\n },\n );\n }\n\n private async getDatabaseUrl(): Promise<string | null> {\n // Try to get the database URL from the API\n try {\n const url = `${this.config.baseUrl}/api/sdk/config`;\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (response.ok) {\n const data = (await response.json()) as { databaseUrl?: string };\n\n return data.databaseUrl ?? null;\n }\n } catch {\n // Ignore - we'll try without realtime\n }\n\n return null;\n }\n\n private loadCachedFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n const cached = localStorage.getItem(CACHE_KEY);\n\n if (cached) {\n this.flags = JSON.parse(cached) as FlagMap;\n this.updateFlagValuesCache();\n this.log('Loaded cached flags');\n }\n } catch {\n // Ignore cache errors\n }\n }\n\n private cacheFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify(this.flags));\n } catch {\n // Ignore cache errors\n }\n }\n\n private updateFlagValuesCache(): void {\n // Create a new cache object only when flags change\n // Include both original keys and camelCase versions for easy destructuring\n this.flagValuesCache = Object.entries(this.flags).reduce<\n Record<string, FlagValue | undefined>\n >((acc, [key, flag]) => {\n // Always include original key\n acc[key] = flag.value;\n\n // If key has hyphens or underscores, also add camelCase version\n if (needsCamelCase(key)) {\n const camelKey = toCamelCase(key);\n acc[camelKey] = flag.value;\n }\n\n return acc;\n }, {});\n }\n\n private notifyListeners(): void {\n this.updateFlagValuesCache();\n this.listeners.forEach((listener) => listener());\n }\n\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[Rolloutly]', ...args);\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/client.ts"],"names":["ref","off","initializeApp","getDatabase","onValue"],"mappings":"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","file":"index.cjs","sourcesContent":["import { initializeApp, type FirebaseApp } from 'firebase/app';\nimport {\n getDatabase,\n off,\n onValue,\n ref,\n type Database,\n type Unsubscribe,\n} from 'firebase/database';\n\nimport type {\n ClientStatus,\n Flag,\n FlagChangeListener,\n FlagMap,\n FlagValue,\n ParsedToken,\n RolloutlyConfig,\n UserContext,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://rolloutly.com';\nconst CACHE_KEY = 'rolloutly_flags';\n\n/**\n * Convert a string to camelCase\n * 'instagram-integration' -> 'instagramIntegration'\n * 'my_feature_flag' -> 'myFeatureFlag'\n */\nconst toCamelCase = (str: string): string => {\n return str.replace(/[-_](.)/g, (_, char: string) => char.toUpperCase());\n};\n\n/**\n * Check if a key needs camelCase conversion (has hyphens or underscores)\n */\nconst needsCamelCase = (key: string): boolean => {\n return key.includes('-') || key.includes('_');\n};\n\nexport class RolloutlyClient {\n private config: Required<\n Omit<RolloutlyConfig, 'defaultFlags' | 'user'> & {\n defaultFlags: Record<string, FlagValue>;\n user: UserContext | undefined;\n }\n >;\n private flags: FlagMap = {};\n private flagValuesCache: Record<string, FlagValue | undefined> = {};\n private status: ClientStatus = 'initializing';\n private error: Error | null = null;\n private listeners: Set<FlagChangeListener> = new Set();\n private parsedToken: ParsedToken;\n private firebaseApp: FirebaseApp | null = null;\n private database: Database | null = null;\n private realtimeUnsubscribe: Unsubscribe | null = null;\n private initPromise: Promise<void>;\n private initResolve!: () => void;\n private initReject!: (error: Error) => void;\n\n constructor(config: RolloutlyConfig) {\n this.config = {\n token: config.token,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n realtimeEnabled: config.realtimeEnabled ?? true,\n defaultFlags: config.defaultFlags ?? {},\n user: config.user,\n debug: config.debug ?? false,\n };\n\n // Parse the token\n const parsed = this.parseToken(config.token);\n\n if (!parsed) {\n throw new Error('Invalid SDK token format');\n }\n\n this.parsedToken = parsed;\n\n // Create init promise\n this.initPromise = new Promise((resolve, reject) => {\n this.initResolve = resolve;\n this.initReject = reject;\n });\n\n // Load cached flags first\n this.loadCachedFlags();\n\n // Start initialization\n void this.initialize();\n }\n\n /**\n * Wait for the client to be initialized\n */\n async waitForInit(): Promise<void> {\n return this.initPromise;\n }\n\n /**\n * Get a single flag value\n * Supports both original keys ('instagram-integration') and camelCase ('instagramIntegration')\n */\n getFlag<T extends FlagValue = FlagValue>(key: string): T | undefined {\n // Try direct lookup first\n let flag = this.flags[key];\n\n // If not found and key is camelCase, try to find original key\n if (!flag) {\n const originalKey = this.findOriginalKey(key);\n\n if (originalKey) {\n flag = this.flags[originalKey];\n }\n }\n\n if (flag) {\n const defaultKey = this.findOriginalKey(key) || key;\n\n return (\n flag.enabled ? flag.value : this.config.defaultFlags[defaultKey]\n ) as T;\n }\n\n return this.config.defaultFlags[key] as T;\n }\n\n /**\n * Get all flags\n */\n getFlags(): FlagMap {\n return { ...this.flags };\n }\n\n /**\n * Get all flag values as a stable object (for React hooks)\n * Returns a cached object that only changes when flags change\n */\n getFlagValues(): Record<string, FlagValue | undefined> {\n return this.flagValuesCache;\n }\n\n /**\n * Check if a boolean flag is enabled\n * Supports both original keys ('instagram-integration') and camelCase ('instagramIntegration')\n */\n isEnabled(key: string): boolean {\n // Try direct lookup first\n let flag = this.flags[key];\n let lookupKey = key;\n\n // If not found and key is camelCase, try to find original key\n if (!flag) {\n const originalKey = this.findOriginalKey(key);\n\n if (originalKey) {\n flag = this.flags[originalKey];\n lookupKey = originalKey;\n }\n }\n\n if (!flag) {\n return Boolean(this.config.defaultFlags[lookupKey]);\n }\n\n if (!flag.enabled) {\n return Boolean(this.config.defaultFlags[lookupKey]);\n }\n\n return flag.type === 'boolean' ? Boolean(flag.value) : true;\n }\n\n /**\n * Get current client status\n */\n getStatus(): ClientStatus {\n return this.status;\n }\n\n /**\n * Get the last error if any\n */\n getError(): Error | null {\n return this.error;\n }\n\n /**\n * Subscribe to flag changes (for React useSyncExternalStore)\n */\n subscribe(listener: FlagChangeListener): () => void {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Update the user context and re-fetch flags\n * Call this when the user logs in or their attributes change\n */\n async identify(user: UserContext): Promise<void> {\n this.config.user = user;\n this.log('User identified:', user.userId || user.email || 'anonymous');\n\n // Re-fetch flags with new user context\n await this.fetchFlags();\n }\n\n /**\n * Clear the user context (e.g., on logout)\n */\n async reset(): Promise<void> {\n this.config.user = undefined;\n this.log('User context reset');\n\n // Re-fetch flags without user context\n await this.fetchFlags();\n }\n\n /**\n * Get the current user context\n */\n getUser(): UserContext | undefined {\n return this.config.user;\n }\n\n /**\n * Cleanup and disconnect\n */\n close(): void {\n if (this.realtimeUnsubscribe) {\n this.realtimeUnsubscribe();\n this.realtimeUnsubscribe = null;\n }\n\n if (this.database) {\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n off(flagsRef);\n }\n\n this.listeners.clear();\n this.log('Client closed');\n }\n\n // ==================== Private Methods ====================\n\n /**\n * Find the original flag key from a camelCase version\n * 'instagramIntegration' -> 'instagram-integration' (if it exists in flags)\n */\n private findOriginalKey(camelKey: string): string | null {\n // If the key exists directly, return null (no conversion needed)\n if (this.flags[camelKey]) {\n return null;\n }\n\n // Search for a flag whose camelCase version matches\n for (const key of Object.keys(this.flags)) {\n if (needsCamelCase(key) && toCamelCase(key) === camelKey) {\n return key;\n }\n }\n\n return null;\n }\n\n private parseToken(token: string): ParsedToken | null {\n const parts = token.split('_');\n\n // Format: rly_{projectId}_{environmentKey}_{randomString}\n if (parts.length < 4 || parts[0] !== 'rly') {\n return null;\n }\n\n return {\n projectId: parts[1],\n environmentKey: parts[2],\n };\n }\n\n private async initialize(): Promise<void> {\n try {\n // Fetch initial flags from API (will gracefully handle errors)\n await this.fetchFlags();\n\n // Set up real-time updates if enabled (will gracefully handle errors)\n if (this.config.realtimeEnabled) {\n try {\n await this.setupRealtime();\n } catch (err) {\n // Realtime setup failure is not critical - log and continue\n this.log('Warning: Realtime setup failed:', err instanceof Error ? err.message : String(err));\n }\n }\n\n // Always resolve successfully, even if fetch failed (we have cached/default values)\n this.status = 'ready';\n this.initResolve();\n this.log('Client initialized');\n } catch (err) {\n // Only reject for critical errors (like invalid token format)\n this.status = 'error';\n this.error = err instanceof Error ? err : new Error(String(err));\n this.initReject(this.error);\n this.log('Initialization failed:', this.error.message);\n }\n }\n\n private async fetchFlags(): Promise<void> {\n const url = `${this.config.baseUrl}/api/sdk/flags`;\n\n // Use POST with user context if available, otherwise GET\n const hasUserContext = this.config.user && Object.keys(this.config.user).length > 0;\n\n try {\n const response = await fetch(url, {\n method: hasUserContext ? 'POST' : 'GET',\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n ...(hasUserContext && { 'Content-Type': 'application/json' }),\n },\n ...(hasUserContext && {\n body: JSON.stringify({ user: this.config.user }),\n }),\n });\n\n if (!response.ok) {\n // Log error but don't throw - use cached/default values instead\n const errorMessage = `Failed to fetch flags: ${response.status}`;\n this.log('Warning:', errorMessage, '- using cached/default values');\n \n // If we have cached flags, keep using them\n // Otherwise, flags will fall back to defaultFlags via getFlag/isEnabled\n this.updateFlagValuesCache();\n this.notifyListeners();\n \n return;\n }\n\n const data = (await response.json()) as { flags: FlagMap };\n this.flags = data.flags;\n this.cacheFlags();\n this.notifyListeners();\n this.log('Flags fetched:', Object.keys(this.flags).length, hasUserContext ? '(with user context)' : '');\n } catch (err) {\n // Network errors or other fetch failures - log but don't throw\n const errorMessage = err instanceof Error ? err.message : String(err);\n this.log('Warning: Network error fetching flags:', errorMessage, '- using cached/default values');\n \n // If we have cached flags, keep using them\n // Otherwise, flags will fall back to defaultFlags via getFlag/isEnabled\n this.updateFlagValuesCache();\n this.notifyListeners();\n }\n }\n\n private async setupRealtime(): Promise<void> {\n // Initialize Firebase with minimal config for Realtime DB\n // The database URL is derived from the API response or configured\n const databaseURL = await this.getDatabaseUrl();\n\n if (!databaseURL) {\n this.log('Realtime DB URL not available, skipping real-time updates');\n\n return;\n }\n\n this.firebaseApp = initializeApp(\n {\n databaseURL,\n },\n `rolloutly-${Date.now()}`,\n );\n\n this.database = getDatabase(this.firebaseApp);\n\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n\n this.realtimeUnsubscribe = onValue(\n flagsRef,\n (snapshot) => {\n const data = snapshot.val() as Record<string, Flag> | null;\n\n if (data) {\n const hasUserContext = this.config.user && Object.keys(this.config.user).length > 0;\n\n if (hasUserContext) {\n // When user context is provided, re-fetch from API to get\n // server-evaluated targeting rules instead of using raw values\n this.log('Realtime change detected, re-fetching with user context...');\n void this.fetchFlags();\n } else {\n // No user context, use realtime values directly\n this.flags = Object.entries(data).reduce<FlagMap>(\n (acc, [key, flag]) => {\n acc[key] = { ...flag, key };\n\n return acc;\n },\n {},\n );\n this.cacheFlags();\n this.notifyListeners();\n this.log('Realtime update received');\n }\n }\n },\n (error) => {\n this.log('Realtime error:', error.message);\n },\n );\n }\n\n private async getDatabaseUrl(): Promise<string | null> {\n // Try to get the database URL from the API\n try {\n const url = `${this.config.baseUrl}/api/sdk/config`;\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (response.ok) {\n const data = (await response.json()) as { databaseUrl?: string };\n\n return data.databaseUrl ?? null;\n }\n } catch {\n // Ignore - we'll try without realtime\n }\n\n return null;\n }\n\n private loadCachedFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n const cached = localStorage.getItem(CACHE_KEY);\n\n if (cached) {\n this.flags = JSON.parse(cached) as FlagMap;\n this.updateFlagValuesCache();\n this.log('Loaded cached flags');\n }\n } catch {\n // Ignore cache errors\n }\n }\n\n private cacheFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify(this.flags));\n } catch {\n // Ignore cache errors\n }\n }\n\n private updateFlagValuesCache(): void {\n // Create a new cache object only when flags change\n // Include both original keys and camelCase versions for easy destructuring\n this.flagValuesCache = Object.entries(this.flags).reduce<\n Record<string, FlagValue | undefined>\n >((acc, [key, flag]) => {\n // Always include original key\n acc[key] = flag.value;\n\n // If key has hyphens or underscores, also add camelCase version\n if (needsCamelCase(key)) {\n const camelKey = toCamelCase(key);\n acc[camelKey] = flag.value;\n }\n\n return acc;\n }, {});\n }\n\n private notifyListeners(): void {\n this.updateFlagValuesCache();\n this.listeners.forEach((listener) => listener());\n }\n\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[Rolloutly]', ...args);\n }\n }\n}\n"]}
package/dist/index.js CHANGED
@@ -191,7 +191,11 @@ var RolloutlyClient = class {
191
191
  try {
192
192
  await this.fetchFlags();
193
193
  if (this.config.realtimeEnabled) {
194
- await this.setupRealtime();
194
+ try {
195
+ await this.setupRealtime();
196
+ } catch (err) {
197
+ this.log("Warning: Realtime setup failed:", err instanceof Error ? err.message : String(err));
198
+ }
195
199
  }
196
200
  this.status = "ready";
197
201
  this.initResolve();
@@ -206,24 +210,35 @@ var RolloutlyClient = class {
206
210
  async fetchFlags() {
207
211
  const url = `${this.config.baseUrl}/api/sdk/flags`;
208
212
  const hasUserContext = this.config.user && Object.keys(this.config.user).length > 0;
209
- const response = await fetch(url, {
210
- method: hasUserContext ? "POST" : "GET",
211
- headers: {
212
- Authorization: `Bearer ${this.config.token}`,
213
- ...hasUserContext && { "Content-Type": "application/json" }
214
- },
215
- ...hasUserContext && {
216
- body: JSON.stringify({ user: this.config.user })
213
+ try {
214
+ const response = await fetch(url, {
215
+ method: hasUserContext ? "POST" : "GET",
216
+ headers: {
217
+ Authorization: `Bearer ${this.config.token}`,
218
+ ...hasUserContext && { "Content-Type": "application/json" }
219
+ },
220
+ ...hasUserContext && {
221
+ body: JSON.stringify({ user: this.config.user })
222
+ }
223
+ });
224
+ if (!response.ok) {
225
+ const errorMessage = `Failed to fetch flags: ${response.status}`;
226
+ this.log("Warning:", errorMessage, "- using cached/default values");
227
+ this.updateFlagValuesCache();
228
+ this.notifyListeners();
229
+ return;
217
230
  }
218
- });
219
- if (!response.ok) {
220
- throw new Error(`Failed to fetch flags: ${response.status}`);
231
+ const data = await response.json();
232
+ this.flags = data.flags;
233
+ this.cacheFlags();
234
+ this.notifyListeners();
235
+ this.log("Flags fetched:", Object.keys(this.flags).length, hasUserContext ? "(with user context)" : "");
236
+ } catch (err) {
237
+ const errorMessage = err instanceof Error ? err.message : String(err);
238
+ this.log("Warning: Network error fetching flags:", errorMessage, "- using cached/default values");
239
+ this.updateFlagValuesCache();
240
+ this.notifyListeners();
221
241
  }
222
- const data = await response.json();
223
- this.flags = data.flags;
224
- this.cacheFlags();
225
- this.notifyListeners();
226
- this.log("Flags fetched:", Object.keys(this.flags).length, hasUserContext ? "(with user context)" : "");
227
242
  }
228
243
  async setupRealtime() {
229
244
  const databaseURL = await this.getDatabaseUrl();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { initializeApp, type FirebaseApp } from 'firebase/app';\nimport {\n getDatabase,\n off,\n onValue,\n ref,\n type Database,\n type Unsubscribe,\n} from 'firebase/database';\n\nimport type {\n ClientStatus,\n Flag,\n FlagChangeListener,\n FlagMap,\n FlagValue,\n ParsedToken,\n RolloutlyConfig,\n UserContext,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://rolloutly.com';\nconst CACHE_KEY = 'rolloutly_flags';\n\n/**\n * Convert a string to camelCase\n * 'instagram-integration' -> 'instagramIntegration'\n * 'my_feature_flag' -> 'myFeatureFlag'\n */\nconst toCamelCase = (str: string): string => {\n return str.replace(/[-_](.)/g, (_, char: string) => char.toUpperCase());\n};\n\n/**\n * Check if a key needs camelCase conversion (has hyphens or underscores)\n */\nconst needsCamelCase = (key: string): boolean => {\n return key.includes('-') || key.includes('_');\n};\n\nexport class RolloutlyClient {\n private config: Required<\n Omit<RolloutlyConfig, 'defaultFlags' | 'user'> & {\n defaultFlags: Record<string, FlagValue>;\n user: UserContext | undefined;\n }\n >;\n private flags: FlagMap = {};\n private flagValuesCache: Record<string, FlagValue | undefined> = {};\n private status: ClientStatus = 'initializing';\n private error: Error | null = null;\n private listeners: Set<FlagChangeListener> = new Set();\n private parsedToken: ParsedToken;\n private firebaseApp: FirebaseApp | null = null;\n private database: Database | null = null;\n private realtimeUnsubscribe: Unsubscribe | null = null;\n private initPromise: Promise<void>;\n private initResolve!: () => void;\n private initReject!: (error: Error) => void;\n\n constructor(config: RolloutlyConfig) {\n this.config = {\n token: config.token,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n realtimeEnabled: config.realtimeEnabled ?? true,\n defaultFlags: config.defaultFlags ?? {},\n user: config.user,\n debug: config.debug ?? false,\n };\n\n // Parse the token\n const parsed = this.parseToken(config.token);\n\n if (!parsed) {\n throw new Error('Invalid SDK token format');\n }\n\n this.parsedToken = parsed;\n\n // Create init promise\n this.initPromise = new Promise((resolve, reject) => {\n this.initResolve = resolve;\n this.initReject = reject;\n });\n\n // Load cached flags first\n this.loadCachedFlags();\n\n // Start initialization\n void this.initialize();\n }\n\n /**\n * Wait for the client to be initialized\n */\n async waitForInit(): Promise<void> {\n return this.initPromise;\n }\n\n /**\n * Get a single flag value\n * Supports both original keys ('instagram-integration') and camelCase ('instagramIntegration')\n */\n getFlag<T extends FlagValue = FlagValue>(key: string): T | undefined {\n // Try direct lookup first\n let flag = this.flags[key];\n\n // If not found and key is camelCase, try to find original key\n if (!flag) {\n const originalKey = this.findOriginalKey(key);\n\n if (originalKey) {\n flag = this.flags[originalKey];\n }\n }\n\n if (flag) {\n const defaultKey = this.findOriginalKey(key) || key;\n\n return (\n flag.enabled ? flag.value : this.config.defaultFlags[defaultKey]\n ) as T;\n }\n\n return this.config.defaultFlags[key] as T;\n }\n\n /**\n * Get all flags\n */\n getFlags(): FlagMap {\n return { ...this.flags };\n }\n\n /**\n * Get all flag values as a stable object (for React hooks)\n * Returns a cached object that only changes when flags change\n */\n getFlagValues(): Record<string, FlagValue | undefined> {\n return this.flagValuesCache;\n }\n\n /**\n * Check if a boolean flag is enabled\n * Supports both original keys ('instagram-integration') and camelCase ('instagramIntegration')\n */\n isEnabled(key: string): boolean {\n // Try direct lookup first\n let flag = this.flags[key];\n let lookupKey = key;\n\n // If not found and key is camelCase, try to find original key\n if (!flag) {\n const originalKey = this.findOriginalKey(key);\n\n if (originalKey) {\n flag = this.flags[originalKey];\n lookupKey = originalKey;\n }\n }\n\n if (!flag) {\n return Boolean(this.config.defaultFlags[lookupKey]);\n }\n\n if (!flag.enabled) {\n return Boolean(this.config.defaultFlags[lookupKey]);\n }\n\n return flag.type === 'boolean' ? Boolean(flag.value) : true;\n }\n\n /**\n * Get current client status\n */\n getStatus(): ClientStatus {\n return this.status;\n }\n\n /**\n * Get the last error if any\n */\n getError(): Error | null {\n return this.error;\n }\n\n /**\n * Subscribe to flag changes (for React useSyncExternalStore)\n */\n subscribe(listener: FlagChangeListener): () => void {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Update the user context and re-fetch flags\n * Call this when the user logs in or their attributes change\n */\n async identify(user: UserContext): Promise<void> {\n this.config.user = user;\n this.log('User identified:', user.userId || user.email || 'anonymous');\n\n // Re-fetch flags with new user context\n await this.fetchFlags();\n }\n\n /**\n * Clear the user context (e.g., on logout)\n */\n async reset(): Promise<void> {\n this.config.user = undefined;\n this.log('User context reset');\n\n // Re-fetch flags without user context\n await this.fetchFlags();\n }\n\n /**\n * Get the current user context\n */\n getUser(): UserContext | undefined {\n return this.config.user;\n }\n\n /**\n * Cleanup and disconnect\n */\n close(): void {\n if (this.realtimeUnsubscribe) {\n this.realtimeUnsubscribe();\n this.realtimeUnsubscribe = null;\n }\n\n if (this.database) {\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n off(flagsRef);\n }\n\n this.listeners.clear();\n this.log('Client closed');\n }\n\n // ==================== Private Methods ====================\n\n /**\n * Find the original flag key from a camelCase version\n * 'instagramIntegration' -> 'instagram-integration' (if it exists in flags)\n */\n private findOriginalKey(camelKey: string): string | null {\n // If the key exists directly, return null (no conversion needed)\n if (this.flags[camelKey]) {\n return null;\n }\n\n // Search for a flag whose camelCase version matches\n for (const key of Object.keys(this.flags)) {\n if (needsCamelCase(key) && toCamelCase(key) === camelKey) {\n return key;\n }\n }\n\n return null;\n }\n\n private parseToken(token: string): ParsedToken | null {\n const parts = token.split('_');\n\n // Format: rly_{projectId}_{environmentKey}_{randomString}\n if (parts.length < 4 || parts[0] !== 'rly') {\n return null;\n }\n\n return {\n projectId: parts[1],\n environmentKey: parts[2],\n };\n }\n\n private async initialize(): Promise<void> {\n try {\n // Fetch initial flags from API\n await this.fetchFlags();\n\n // Set up real-time updates if enabled\n if (this.config.realtimeEnabled) {\n await this.setupRealtime();\n }\n\n this.status = 'ready';\n this.initResolve();\n this.log('Client initialized');\n } catch (err) {\n this.status = 'error';\n this.error = err instanceof Error ? err : new Error(String(err));\n this.initReject(this.error);\n this.log('Initialization failed:', this.error.message);\n }\n }\n\n private async fetchFlags(): Promise<void> {\n const url = `${this.config.baseUrl}/api/sdk/flags`;\n\n // Use POST with user context if available, otherwise GET\n const hasUserContext = this.config.user && Object.keys(this.config.user).length > 0;\n\n const response = await fetch(url, {\n method: hasUserContext ? 'POST' : 'GET',\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n ...(hasUserContext && { 'Content-Type': 'application/json' }),\n },\n ...(hasUserContext && {\n body: JSON.stringify({ user: this.config.user }),\n }),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch flags: ${response.status}`);\n }\n\n const data = (await response.json()) as { flags: FlagMap };\n this.flags = data.flags;\n this.cacheFlags();\n this.notifyListeners();\n this.log('Flags fetched:', Object.keys(this.flags).length, hasUserContext ? '(with user context)' : '');\n }\n\n private async setupRealtime(): Promise<void> {\n // Initialize Firebase with minimal config for Realtime DB\n // The database URL is derived from the API response or configured\n const databaseURL = await this.getDatabaseUrl();\n\n if (!databaseURL) {\n this.log('Realtime DB URL not available, skipping real-time updates');\n\n return;\n }\n\n this.firebaseApp = initializeApp(\n {\n databaseURL,\n },\n `rolloutly-${Date.now()}`,\n );\n\n this.database = getDatabase(this.firebaseApp);\n\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n\n this.realtimeUnsubscribe = onValue(\n flagsRef,\n (snapshot) => {\n const data = snapshot.val() as Record<string, Flag> | null;\n\n if (data) {\n const hasUserContext = this.config.user && Object.keys(this.config.user).length > 0;\n\n if (hasUserContext) {\n // When user context is provided, re-fetch from API to get\n // server-evaluated targeting rules instead of using raw values\n this.log('Realtime change detected, re-fetching with user context...');\n void this.fetchFlags();\n } else {\n // No user context, use realtime values directly\n this.flags = Object.entries(data).reduce<FlagMap>(\n (acc, [key, flag]) => {\n acc[key] = { ...flag, key };\n\n return acc;\n },\n {},\n );\n this.cacheFlags();\n this.notifyListeners();\n this.log('Realtime update received');\n }\n }\n },\n (error) => {\n this.log('Realtime error:', error.message);\n },\n );\n }\n\n private async getDatabaseUrl(): Promise<string | null> {\n // Try to get the database URL from the API\n try {\n const url = `${this.config.baseUrl}/api/sdk/config`;\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (response.ok) {\n const data = (await response.json()) as { databaseUrl?: string };\n\n return data.databaseUrl ?? null;\n }\n } catch {\n // Ignore - we'll try without realtime\n }\n\n return null;\n }\n\n private loadCachedFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n const cached = localStorage.getItem(CACHE_KEY);\n\n if (cached) {\n this.flags = JSON.parse(cached) as FlagMap;\n this.updateFlagValuesCache();\n this.log('Loaded cached flags');\n }\n } catch {\n // Ignore cache errors\n }\n }\n\n private cacheFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify(this.flags));\n } catch {\n // Ignore cache errors\n }\n }\n\n private updateFlagValuesCache(): void {\n // Create a new cache object only when flags change\n // Include both original keys and camelCase versions for easy destructuring\n this.flagValuesCache = Object.entries(this.flags).reduce<\n Record<string, FlagValue | undefined>\n >((acc, [key, flag]) => {\n // Always include original key\n acc[key] = flag.value;\n\n // If key has hyphens or underscores, also add camelCase version\n if (needsCamelCase(key)) {\n const camelKey = toCamelCase(key);\n acc[camelKey] = flag.value;\n }\n\n return acc;\n }, {});\n }\n\n private notifyListeners(): void {\n this.updateFlagValuesCache();\n this.listeners.forEach((listener) => listener());\n }\n\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[Rolloutly]', ...args);\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/client.ts"],"names":[],"mappings":";;;;AAqBA,IAAM,gBAAA,GAAmB,uBAAA;AACzB,IAAM,SAAA,GAAY,iBAAA;AAOlB,IAAM,WAAA,GAAc,CAAC,GAAA,KAAwB;AAC3C,EAAA,OAAO,GAAA,CAAI,QAAQ,UAAA,EAAY,CAAC,GAAG,IAAA,KAAiB,IAAA,CAAK,aAAa,CAAA;AACxE,CAAA;AAKA,IAAM,cAAA,GAAiB,CAAC,GAAA,KAAyB;AAC/C,EAAA,OAAO,IAAI,QAAA,CAAS,GAAG,CAAA,IAAK,GAAA,CAAI,SAAS,GAAG,CAAA;AAC9C,CAAA;AAEO,IAAM,kBAAN,MAAsB;AAAA,EAoB3B,YAAY,MAAA,EAAyB;AAbrC,IAAA,IAAA,CAAQ,QAAiB,EAAC;AAC1B,IAAA,IAAA,CAAQ,kBAAyD,EAAC;AAClE,IAAA,IAAA,CAAQ,MAAA,GAAuB,cAAA;AAC/B,IAAA,IAAA,CAAQ,KAAA,GAAsB,IAAA;AAC9B,IAAA,IAAA,CAAQ,SAAA,uBAAyC,GAAA,EAAI;AAErD,IAAA,IAAA,CAAQ,WAAA,GAAkC,IAAA;AAC1C,IAAA,IAAA,CAAQ,QAAA,GAA4B,IAAA;AACpC,IAAA,IAAA,CAAQ,mBAAA,GAA0C,IAAA;AAMhD,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,OAAO,MAAA,CAAO,KAAA;AAAA,MACd,OAAA,EAAS,OAAO,OAAA,IAAW,gBAAA;AAAA,MAC3B,eAAA,EAAiB,OAAO,eAAA,IAAmB,IAAA;AAAA,MAC3C,YAAA,EAAc,MAAA,CAAO,YAAA,IAAgB,EAAC;AAAA,MACtC,MAAM,MAAA,CAAO,IAAA;AAAA,MACb,KAAA,EAAO,OAAO,KAAA,IAAS;AAAA,KACzB;AAGA,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,UAAA,CAAW,MAAA,CAAO,KAAK,CAAA;AAE3C,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,MAAM,IAAI,MAAM,0BAA0B,CAAA;AAAA,IAC5C;AAEA,IAAA,IAAA,CAAK,WAAA,GAAc,MAAA;AAGnB,IAAA,IAAA,CAAK,WAAA,GAAc,IAAI,OAAA,CAAQ,CAAC,SAAS,MAAA,KAAW;AAClD,MAAA,IAAA,CAAK,WAAA,GAAc,OAAA;AACnB,MAAA,IAAA,CAAK,UAAA,GAAa,MAAA;AAAA,IACpB,CAAC,CAAA;AAGD,IAAA,IAAA,CAAK,eAAA,EAAgB;AAGrB,IAAA,KAAK,KAAK,UAAA,EAAW;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAA,GAA6B;AACjC,IAAA,OAAO,IAAA,CAAK,WAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAyC,GAAA,EAA4B;AAEnE,IAAA,IAAI,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAGzB,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA,MAAM,WAAA,GAAc,IAAA,CAAK,eAAA,CAAgB,GAAG,CAAA;AAE5C,MAAA,IAAI,WAAA,EAAa;AACf,QAAA,IAAA,GAAO,IAAA,CAAK,MAAM,WAAW,CAAA;AAAA,MAC/B;AAAA,IACF;AAEA,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,MAAM,UAAA,GAAa,IAAA,CAAK,eAAA,CAAgB,GAAG,CAAA,IAAK,GAAA;AAEhD,MAAA,OACE,KAAK,OAAA,GAAU,IAAA,CAAK,QAAQ,IAAA,CAAK,MAAA,CAAO,aAAa,UAAU,CAAA;AAAA,IAEnE;AAEA,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,GAAG,CAAA;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAAoB;AAClB,IAAA,OAAO,EAAE,GAAG,IAAA,CAAK,KAAA,EAAM;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAA,GAAuD;AACrD,IAAA,OAAO,IAAA,CAAK,eAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,GAAA,EAAsB;AAE9B,IAAA,IAAI,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACzB,IAAA,IAAI,SAAA,GAAY,GAAA;AAGhB,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA,MAAM,WAAA,GAAc,IAAA,CAAK,eAAA,CAAgB,GAAG,CAAA;AAE5C,MAAA,IAAI,WAAA,EAAa;AACf,QAAA,IAAA,GAAO,IAAA,CAAK,MAAM,WAAW,CAAA;AAC7B,QAAA,SAAA,GAAY,WAAA;AAAA,MACd;AAAA,IACF;AAEA,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,SAAS,CAAC,CAAA;AAAA,IACpD;AAEA,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,SAAS,CAAC,CAAA;AAAA,IACpD;AAEA,IAAA,OAAO,KAAK,IAAA,KAAS,SAAA,GAAY,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAA,GAAI,IAAA;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,GAA0B;AACxB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAAyB;AACvB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAA,EAA0C;AAClD,IAAA,IAAA,CAAK,SAAA,CAAU,IAAI,QAAQ,CAAA;AAE3B,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,CAAK,SAAA,CAAU,OAAO,QAAQ,CAAA;AAAA,IAChC,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,SAAS,IAAA,EAAkC;AAC/C,IAAA,IAAA,CAAK,OAAO,IAAA,GAAO,IAAA;AACnB,IAAA,IAAA,CAAK,IAAI,kBAAA,EAAoB,IAAA,CAAK,MAAA,IAAU,IAAA,CAAK,SAAS,WAAW,CAAA;AAGrE,IAAA,MAAM,KAAK,UAAA,EAAW;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAA,GAAuB;AAC3B,IAAA,IAAA,CAAK,OAAO,IAAA,GAAO,MAAA;AACnB,IAAA,IAAA,CAAK,IAAI,oBAAoB,CAAA;AAG7B,IAAA,MAAM,KAAK,UAAA,EAAW;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAA,GAAmC;AACjC,IAAA,OAAO,KAAK,MAAA,CAAO,IAAA;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAc;AACZ,IAAA,IAAI,KAAK,mBAAA,EAAqB;AAC5B,MAAA,IAAA,CAAK,mBAAA,EAAoB;AACzB,MAAA,IAAA,CAAK,mBAAA,GAAsB,IAAA;AAAA,IAC7B;AAEA,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,MAAM,QAAA,GAAW,GAAA;AAAA,QACf,IAAA,CAAK,QAAA;AAAA,QACL,SAAS,IAAA,CAAK,WAAA,CAAY,SAAS,CAAA,CAAA,EAAI,IAAA,CAAK,YAAY,cAAc,CAAA;AAAA,OACxE;AACA,MAAA,GAAA,CAAI,QAAQ,CAAA;AAAA,IACd;AAEA,IAAA,IAAA,CAAK,UAAU,KAAA,EAAM;AACrB,IAAA,IAAA,CAAK,IAAI,eAAe,CAAA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,gBAAgB,QAAA,EAAiC;AAEvD,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,QAAQ,CAAA,EAAG;AACxB,MAAA,OAAO,IAAA;AAAA,IACT;AAGA,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA,EAAG;AACzC,MAAA,IAAI,eAAe,GAAG,CAAA,IAAK,WAAA,CAAY,GAAG,MAAM,QAAA,EAAU;AACxD,QAAA,OAAO,GAAA;AAAA,MACT;AAAA,IACF;AAEA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEQ,WAAW,KAAA,EAAmC;AACpD,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA;AAG7B,IAAA,IAAI,MAAM,MAAA,GAAS,CAAA,IAAK,KAAA,CAAM,CAAC,MAAM,KAAA,EAAO;AAC1C,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,OAAO;AAAA,MACL,SAAA,EAAW,MAAM,CAAC,CAAA;AAAA,MAClB,cAAA,EAAgB,MAAM,CAAC;AAAA,KACzB;AAAA,EACF;AAAA,EAEA,MAAc,UAAA,GAA4B;AACxC,IAAA,IAAI;AAEF,MAAA,MAAM,KAAK,UAAA,EAAW;AAGtB,MAAA,IAAI,IAAA,CAAK,OAAO,eAAA,EAAiB;AAC/B,QAAA,IAAI;AACF,UAAA,MAAM,KAAK,aAAA,EAAc;AAAA,QAC3B,SAAS,GAAA,EAAK;AAEZ,UAAA,IAAA,CAAK,GAAA,CAAI,mCAAmC,GAAA,YAAe,KAAA,GAAQ,IAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,QAC9F;AAAA,MACF;AAGA,MAAA,IAAA,CAAK,MAAA,GAAS,OAAA;AACd,MAAA,IAAA,CAAK,WAAA,EAAY;AACjB,MAAA,IAAA,CAAK,IAAI,oBAAoB,CAAA;AAAA,IAC/B,SAAS,GAAA,EAAK;AAEZ,MAAA,IAAA,CAAK,MAAA,GAAS,OAAA;AACd,MAAA,IAAA,CAAK,KAAA,GAAQ,eAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA;AAC/D,MAAA,IAAA,CAAK,UAAA,CAAW,KAAK,KAAK,CAAA;AAC1B,MAAA,IAAA,CAAK,GAAA,CAAI,wBAAA,EAA0B,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAAA,IACvD;AAAA,EACF;AAAA,EAEA,MAAc,UAAA,GAA4B;AACxC,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,MAAA,CAAO,OAAO,CAAA,cAAA,CAAA;AAGlC,IAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,MAAA,CAAO,IAAA,IAAQ,MAAA,CAAO,KAAK,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,CAAE,MAAA,GAAS,CAAA;AAElF,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,MAAA,EAAQ,iBAAiB,MAAA,GAAS,KAAA;AAAA,QAClC,OAAA,EAAS;AAAA,UACP,aAAA,EAAe,CAAA,OAAA,EAAU,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,CAAA;AAAA,UAC1C,GAAI,cAAA,IAAkB,EAAE,cAAA,EAAgB,kBAAA;AAAmB,SAC7D;AAAA,QACA,GAAI,cAAA,IAAkB;AAAA,UACpB,IAAA,EAAM,KAAK,SAAA,CAAU,EAAE,MAAM,IAAA,CAAK,MAAA,CAAO,MAAM;AAAA;AACjD,OACD,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAEhB,QAAA,MAAM,YAAA,GAAe,CAAA,uBAAA,EAA0B,QAAA,CAAS,MAAM,CAAA,CAAA;AAC9D,QAAA,IAAA,CAAK,GAAA,CAAI,UAAA,EAAY,YAAA,EAAc,+BAA+B,CAAA;AAIlE,QAAA,IAAA,CAAK,qBAAA,EAAsB;AAC3B,QAAA,IAAA,CAAK,eAAA,EAAgB;AAErB,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAClC,MAAA,IAAA,CAAK,QAAQ,IAAA,CAAK,KAAA;AAClB,MAAA,IAAA,CAAK,UAAA,EAAW;AAChB,MAAA,IAAA,CAAK,eAAA,EAAgB;AACrB,MAAA,IAAA,CAAK,GAAA,CAAI,gBAAA,EAAkB,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA,CAAE,MAAA,EAAQ,cAAA,GAAiB,qBAAA,GAAwB,EAAE,CAAA;AAAA,IACxG,SAAS,GAAA,EAAK;AAEZ,MAAA,MAAM,eAAe,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AACpE,MAAA,IAAA,CAAK,GAAA,CAAI,wCAAA,EAA0C,YAAA,EAAc,+BAA+B,CAAA;AAIhG,MAAA,IAAA,CAAK,qBAAA,EAAsB;AAC3B,MAAA,IAAA,CAAK,eAAA,EAAgB;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAc,aAAA,GAA+B;AAG3C,IAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,cAAA,EAAe;AAE9C,IAAA,IAAI,CAAC,WAAA,EAAa;AAChB,MAAA,IAAA,CAAK,IAAI,2DAA2D,CAAA;AAEpE,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,WAAA,GAAc,aAAA;AAAA,MACjB;AAAA,QACE;AAAA,OACF;AAAA,MACA,CAAA,UAAA,EAAa,IAAA,CAAK,GAAA,EAAK,CAAA;AAAA,KACzB;AAEA,IAAA,IAAA,CAAK,QAAA,GAAW,WAAA,CAAY,IAAA,CAAK,WAAW,CAAA;AAE5C,IAAA,MAAM,QAAA,GAAW,GAAA;AAAA,MACf,IAAA,CAAK,QAAA;AAAA,MACL,SAAS,IAAA,CAAK,WAAA,CAAY,SAAS,CAAA,CAAA,EAAI,IAAA,CAAK,YAAY,cAAc,CAAA;AAAA,KACxE;AAEA,IAAA,IAAA,CAAK,mBAAA,GAAsB,OAAA;AAAA,MACzB,QAAA;AAAA,MACA,CAAC,QAAA,KAAa;AACZ,QAAA,MAAM,IAAA,GAAO,SAAS,GAAA,EAAI;AAE1B,QAAA,IAAI,IAAA,EAAM;AACR,UAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,MAAA,CAAO,IAAA,IAAQ,MAAA,CAAO,KAAK,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,CAAE,MAAA,GAAS,CAAA;AAElF,UAAA,IAAI,cAAA,EAAgB;AAGlB,YAAA,IAAA,CAAK,IAAI,4DAA4D,CAAA;AACrE,YAAA,KAAK,KAAK,UAAA,EAAW;AAAA,UACvB,CAAA,MAAO;AAEL,YAAA,IAAA,CAAK,KAAA,GAAQ,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA,CAAE,MAAA;AAAA,cAChC,CAAC,GAAA,EAAK,CAAC,GAAA,EAAK,IAAI,CAAA,KAAM;AACpB,gBAAA,GAAA,CAAI,GAAG,CAAA,GAAI,EAAE,GAAG,MAAM,GAAA,EAAI;AAE1B,gBAAA,OAAO,GAAA;AAAA,cACT,CAAA;AAAA,cACA;AAAC,aACH;AACA,YAAA,IAAA,CAAK,UAAA,EAAW;AAChB,YAAA,IAAA,CAAK,eAAA,EAAgB;AACrB,YAAA,IAAA,CAAK,IAAI,0BAA0B,CAAA;AAAA,UACrC;AAAA,QACF;AAAA,MACF,CAAA;AAAA,MACA,CAAC,KAAA,KAAU;AACT,QAAA,IAAA,CAAK,GAAA,CAAI,iBAAA,EAAmB,KAAA,CAAM,OAAO,CAAA;AAAA,MAC3C;AAAA,KACF;AAAA,EACF;AAAA,EAEA,MAAc,cAAA,GAAyC;AAErD,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,MAAA,CAAO,OAAO,CAAA,eAAA,CAAA;AAClC,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,OAAA,EAAS;AAAA,UACP,aAAA,EAAe,CAAA,OAAA,EAAU,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA;AAAA;AAC5C,OACD,CAAA;AAED,MAAA,IAAI,SAAS,EAAA,EAAI;AACf,QAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAElC,QAAA,OAAO,KAAK,WAAA,IAAe,IAAA;AAAA,MAC7B;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAEA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEQ,eAAA,GAAwB;AAC9B,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,SAAS,CAAA;AAE7C,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,IAAA,CAAK,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA;AAC9B,QAAA,IAAA,CAAK,qBAAA,EAAsB;AAC3B,QAAA,IAAA,CAAK,IAAI,qBAAqB,CAAA;AAAA,MAChC;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,UAAA,GAAmB;AACzB,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,IAAA,IAAI;AACF,MAAA,YAAA,CAAa,QAAQ,SAAA,EAAW,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,KAAK,CAAC,CAAA;AAAA,IAC5D,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,qBAAA,GAA8B;AAGpC,IAAA,IAAA,CAAK,eAAA,GAAkB,MAAA,CAAO,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAA,CAAE,MAAA,CAEhD,CAAC,GAAA,EAAK,CAAC,GAAA,EAAK,IAAI,CAAA,KAAM;AAEtB,MAAA,GAAA,CAAI,GAAG,IAAI,IAAA,CAAK,KAAA;AAGhB,MAAA,IAAI,cAAA,CAAe,GAAG,CAAA,EAAG;AACvB,QAAA,MAAM,QAAA,GAAW,YAAY,GAAG,CAAA;AAChC,QAAA,GAAA,CAAI,QAAQ,IAAI,IAAA,CAAK,KAAA;AAAA,MACvB;AAEA,MAAA,OAAO,GAAA;AAAA,IACT,CAAA,EAAG,EAAE,CAAA;AAAA,EACP;AAAA,EAEQ,eAAA,GAAwB;AAC9B,IAAA,IAAA,CAAK,qBAAA,EAAsB;AAC3B,IAAA,IAAA,CAAK,SAAA,CAAU,OAAA,CAAQ,CAAC,QAAA,KAAa,UAAU,CAAA;AAAA,EACjD;AAAA,EAEQ,OAAO,IAAA,EAAuB;AACpC,IAAA,IAAI,IAAA,CAAK,OAAO,KAAA,EAAO;AACrB,MAAA,OAAA,CAAQ,GAAA,CAAI,aAAA,EAAe,GAAG,IAAI,CAAA;AAAA,IACpC;AAAA,EACF;AACF","file":"index.js","sourcesContent":["import { initializeApp, type FirebaseApp } from 'firebase/app';\nimport {\n getDatabase,\n off,\n onValue,\n ref,\n type Database,\n type Unsubscribe,\n} from 'firebase/database';\n\nimport type {\n ClientStatus,\n Flag,\n FlagChangeListener,\n FlagMap,\n FlagValue,\n ParsedToken,\n RolloutlyConfig,\n UserContext,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://rolloutly.com';\nconst CACHE_KEY = 'rolloutly_flags';\n\n/**\n * Convert a string to camelCase\n * 'instagram-integration' -> 'instagramIntegration'\n * 'my_feature_flag' -> 'myFeatureFlag'\n */\nconst toCamelCase = (str: string): string => {\n return str.replace(/[-_](.)/g, (_, char: string) => char.toUpperCase());\n};\n\n/**\n * Check if a key needs camelCase conversion (has hyphens or underscores)\n */\nconst needsCamelCase = (key: string): boolean => {\n return key.includes('-') || key.includes('_');\n};\n\nexport class RolloutlyClient {\n private config: Required<\n Omit<RolloutlyConfig, 'defaultFlags' | 'user'> & {\n defaultFlags: Record<string, FlagValue>;\n user: UserContext | undefined;\n }\n >;\n private flags: FlagMap = {};\n private flagValuesCache: Record<string, FlagValue | undefined> = {};\n private status: ClientStatus = 'initializing';\n private error: Error | null = null;\n private listeners: Set<FlagChangeListener> = new Set();\n private parsedToken: ParsedToken;\n private firebaseApp: FirebaseApp | null = null;\n private database: Database | null = null;\n private realtimeUnsubscribe: Unsubscribe | null = null;\n private initPromise: Promise<void>;\n private initResolve!: () => void;\n private initReject!: (error: Error) => void;\n\n constructor(config: RolloutlyConfig) {\n this.config = {\n token: config.token,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n realtimeEnabled: config.realtimeEnabled ?? true,\n defaultFlags: config.defaultFlags ?? {},\n user: config.user,\n debug: config.debug ?? false,\n };\n\n // Parse the token\n const parsed = this.parseToken(config.token);\n\n if (!parsed) {\n throw new Error('Invalid SDK token format');\n }\n\n this.parsedToken = parsed;\n\n // Create init promise\n this.initPromise = new Promise((resolve, reject) => {\n this.initResolve = resolve;\n this.initReject = reject;\n });\n\n // Load cached flags first\n this.loadCachedFlags();\n\n // Start initialization\n void this.initialize();\n }\n\n /**\n * Wait for the client to be initialized\n */\n async waitForInit(): Promise<void> {\n return this.initPromise;\n }\n\n /**\n * Get a single flag value\n * Supports both original keys ('instagram-integration') and camelCase ('instagramIntegration')\n */\n getFlag<T extends FlagValue = FlagValue>(key: string): T | undefined {\n // Try direct lookup first\n let flag = this.flags[key];\n\n // If not found and key is camelCase, try to find original key\n if (!flag) {\n const originalKey = this.findOriginalKey(key);\n\n if (originalKey) {\n flag = this.flags[originalKey];\n }\n }\n\n if (flag) {\n const defaultKey = this.findOriginalKey(key) || key;\n\n return (\n flag.enabled ? flag.value : this.config.defaultFlags[defaultKey]\n ) as T;\n }\n\n return this.config.defaultFlags[key] as T;\n }\n\n /**\n * Get all flags\n */\n getFlags(): FlagMap {\n return { ...this.flags };\n }\n\n /**\n * Get all flag values as a stable object (for React hooks)\n * Returns a cached object that only changes when flags change\n */\n getFlagValues(): Record<string, FlagValue | undefined> {\n return this.flagValuesCache;\n }\n\n /**\n * Check if a boolean flag is enabled\n * Supports both original keys ('instagram-integration') and camelCase ('instagramIntegration')\n */\n isEnabled(key: string): boolean {\n // Try direct lookup first\n let flag = this.flags[key];\n let lookupKey = key;\n\n // If not found and key is camelCase, try to find original key\n if (!flag) {\n const originalKey = this.findOriginalKey(key);\n\n if (originalKey) {\n flag = this.flags[originalKey];\n lookupKey = originalKey;\n }\n }\n\n if (!flag) {\n return Boolean(this.config.defaultFlags[lookupKey]);\n }\n\n if (!flag.enabled) {\n return Boolean(this.config.defaultFlags[lookupKey]);\n }\n\n return flag.type === 'boolean' ? Boolean(flag.value) : true;\n }\n\n /**\n * Get current client status\n */\n getStatus(): ClientStatus {\n return this.status;\n }\n\n /**\n * Get the last error if any\n */\n getError(): Error | null {\n return this.error;\n }\n\n /**\n * Subscribe to flag changes (for React useSyncExternalStore)\n */\n subscribe(listener: FlagChangeListener): () => void {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Update the user context and re-fetch flags\n * Call this when the user logs in or their attributes change\n */\n async identify(user: UserContext): Promise<void> {\n this.config.user = user;\n this.log('User identified:', user.userId || user.email || 'anonymous');\n\n // Re-fetch flags with new user context\n await this.fetchFlags();\n }\n\n /**\n * Clear the user context (e.g., on logout)\n */\n async reset(): Promise<void> {\n this.config.user = undefined;\n this.log('User context reset');\n\n // Re-fetch flags without user context\n await this.fetchFlags();\n }\n\n /**\n * Get the current user context\n */\n getUser(): UserContext | undefined {\n return this.config.user;\n }\n\n /**\n * Cleanup and disconnect\n */\n close(): void {\n if (this.realtimeUnsubscribe) {\n this.realtimeUnsubscribe();\n this.realtimeUnsubscribe = null;\n }\n\n if (this.database) {\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n off(flagsRef);\n }\n\n this.listeners.clear();\n this.log('Client closed');\n }\n\n // ==================== Private Methods ====================\n\n /**\n * Find the original flag key from a camelCase version\n * 'instagramIntegration' -> 'instagram-integration' (if it exists in flags)\n */\n private findOriginalKey(camelKey: string): string | null {\n // If the key exists directly, return null (no conversion needed)\n if (this.flags[camelKey]) {\n return null;\n }\n\n // Search for a flag whose camelCase version matches\n for (const key of Object.keys(this.flags)) {\n if (needsCamelCase(key) && toCamelCase(key) === camelKey) {\n return key;\n }\n }\n\n return null;\n }\n\n private parseToken(token: string): ParsedToken | null {\n const parts = token.split('_');\n\n // Format: rly_{projectId}_{environmentKey}_{randomString}\n if (parts.length < 4 || parts[0] !== 'rly') {\n return null;\n }\n\n return {\n projectId: parts[1],\n environmentKey: parts[2],\n };\n }\n\n private async initialize(): Promise<void> {\n try {\n // Fetch initial flags from API (will gracefully handle errors)\n await this.fetchFlags();\n\n // Set up real-time updates if enabled (will gracefully handle errors)\n if (this.config.realtimeEnabled) {\n try {\n await this.setupRealtime();\n } catch (err) {\n // Realtime setup failure is not critical - log and continue\n this.log('Warning: Realtime setup failed:', err instanceof Error ? err.message : String(err));\n }\n }\n\n // Always resolve successfully, even if fetch failed (we have cached/default values)\n this.status = 'ready';\n this.initResolve();\n this.log('Client initialized');\n } catch (err) {\n // Only reject for critical errors (like invalid token format)\n this.status = 'error';\n this.error = err instanceof Error ? err : new Error(String(err));\n this.initReject(this.error);\n this.log('Initialization failed:', this.error.message);\n }\n }\n\n private async fetchFlags(): Promise<void> {\n const url = `${this.config.baseUrl}/api/sdk/flags`;\n\n // Use POST with user context if available, otherwise GET\n const hasUserContext = this.config.user && Object.keys(this.config.user).length > 0;\n\n try {\n const response = await fetch(url, {\n method: hasUserContext ? 'POST' : 'GET',\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n ...(hasUserContext && { 'Content-Type': 'application/json' }),\n },\n ...(hasUserContext && {\n body: JSON.stringify({ user: this.config.user }),\n }),\n });\n\n if (!response.ok) {\n // Log error but don't throw - use cached/default values instead\n const errorMessage = `Failed to fetch flags: ${response.status}`;\n this.log('Warning:', errorMessage, '- using cached/default values');\n \n // If we have cached flags, keep using them\n // Otherwise, flags will fall back to defaultFlags via getFlag/isEnabled\n this.updateFlagValuesCache();\n this.notifyListeners();\n \n return;\n }\n\n const data = (await response.json()) as { flags: FlagMap };\n this.flags = data.flags;\n this.cacheFlags();\n this.notifyListeners();\n this.log('Flags fetched:', Object.keys(this.flags).length, hasUserContext ? '(with user context)' : '');\n } catch (err) {\n // Network errors or other fetch failures - log but don't throw\n const errorMessage = err instanceof Error ? err.message : String(err);\n this.log('Warning: Network error fetching flags:', errorMessage, '- using cached/default values');\n \n // If we have cached flags, keep using them\n // Otherwise, flags will fall back to defaultFlags via getFlag/isEnabled\n this.updateFlagValuesCache();\n this.notifyListeners();\n }\n }\n\n private async setupRealtime(): Promise<void> {\n // Initialize Firebase with minimal config for Realtime DB\n // The database URL is derived from the API response or configured\n const databaseURL = await this.getDatabaseUrl();\n\n if (!databaseURL) {\n this.log('Realtime DB URL not available, skipping real-time updates');\n\n return;\n }\n\n this.firebaseApp = initializeApp(\n {\n databaseURL,\n },\n `rolloutly-${Date.now()}`,\n );\n\n this.database = getDatabase(this.firebaseApp);\n\n const flagsRef = ref(\n this.database,\n `flags/${this.parsedToken.projectId}/${this.parsedToken.environmentKey}`,\n );\n\n this.realtimeUnsubscribe = onValue(\n flagsRef,\n (snapshot) => {\n const data = snapshot.val() as Record<string, Flag> | null;\n\n if (data) {\n const hasUserContext = this.config.user && Object.keys(this.config.user).length > 0;\n\n if (hasUserContext) {\n // When user context is provided, re-fetch from API to get\n // server-evaluated targeting rules instead of using raw values\n this.log('Realtime change detected, re-fetching with user context...');\n void this.fetchFlags();\n } else {\n // No user context, use realtime values directly\n this.flags = Object.entries(data).reduce<FlagMap>(\n (acc, [key, flag]) => {\n acc[key] = { ...flag, key };\n\n return acc;\n },\n {},\n );\n this.cacheFlags();\n this.notifyListeners();\n this.log('Realtime update received');\n }\n }\n },\n (error) => {\n this.log('Realtime error:', error.message);\n },\n );\n }\n\n private async getDatabaseUrl(): Promise<string | null> {\n // Try to get the database URL from the API\n try {\n const url = `${this.config.baseUrl}/api/sdk/config`;\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n },\n });\n\n if (response.ok) {\n const data = (await response.json()) as { databaseUrl?: string };\n\n return data.databaseUrl ?? null;\n }\n } catch {\n // Ignore - we'll try without realtime\n }\n\n return null;\n }\n\n private loadCachedFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n const cached = localStorage.getItem(CACHE_KEY);\n\n if (cached) {\n this.flags = JSON.parse(cached) as FlagMap;\n this.updateFlagValuesCache();\n this.log('Loaded cached flags');\n }\n } catch {\n // Ignore cache errors\n }\n }\n\n private cacheFlags(): void {\n if (typeof window === 'undefined') return;\n\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify(this.flags));\n } catch {\n // Ignore cache errors\n }\n }\n\n private updateFlagValuesCache(): void {\n // Create a new cache object only when flags change\n // Include both original keys and camelCase versions for easy destructuring\n this.flagValuesCache = Object.entries(this.flags).reduce<\n Record<string, FlagValue | undefined>\n >((acc, [key, flag]) => {\n // Always include original key\n acc[key] = flag.value;\n\n // If key has hyphens or underscores, also add camelCase version\n if (needsCamelCase(key)) {\n const camelKey = toCamelCase(key);\n acc[camelKey] = flag.value;\n }\n\n return acc;\n }, {});\n }\n\n private notifyListeners(): void {\n this.updateFlagValuesCache();\n this.listeners.forEach((listener) => listener());\n }\n\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[Rolloutly]', ...args);\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rolloutly/core",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Rolloutly feature flags SDK - Core JavaScript client",
5
5
  "author": "Kevin Beltrão",
6
6
  "license": "MIT",
@@ -35,17 +35,17 @@
35
35
  "dist",
36
36
  "README.md"
37
37
  ],
38
- "scripts": {
39
- "build": "tsup",
40
- "dev": "tsup --watch",
41
- "typecheck": "tsc --noEmit",
42
- "clean": "rm -rf dist"
43
- },
44
38
  "dependencies": {
45
39
  "firebase": "^12.0.0"
46
40
  },
47
41
  "devDependencies": {
48
42
  "tsup": "^8.0.1",
49
43
  "typescript": "^5.3.3"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup",
47
+ "dev": "tsup --watch",
48
+ "typecheck": "tsc --noEmit",
49
+ "clean": "rm -rf dist"
50
50
  }
51
- }
51
+ }