@togglely/sdk-core 1.1.11 → 1.2.2

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/cli.js ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Togglely CLI - Build-time JSON generator
4
+ *
5
+ * Usage:
6
+ * togglely-pull --apiKey=xxx --project=xxx --environment=xxx --output=./toggles.json
7
+ * togglely-pull --config=./togglely.config.js
8
+ */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ function parseArgs() {
12
+ const args = process.argv.slice(2);
13
+ const config = {};
14
+ for (const arg of args) {
15
+ if (arg.startsWith('--apiKey=')) {
16
+ config.apiKey = arg.split('=')[1];
17
+ }
18
+ else if (arg.startsWith('--project=')) {
19
+ config.project = arg.split('=')[1];
20
+ }
21
+ else if (arg.startsWith('--environment=')) {
22
+ config.environment = arg.split('=')[1];
23
+ }
24
+ else if (arg.startsWith('--baseUrl=')) {
25
+ config.baseUrl = arg.split('=')[1];
26
+ }
27
+ else if (arg.startsWith('--tenantId=')) {
28
+ config.tenantId = arg.split('=')[1];
29
+ }
30
+ else if (arg.startsWith('--output=')) {
31
+ config.output = arg.split('=')[1];
32
+ }
33
+ else if (arg.startsWith('--format=')) {
34
+ config.format = arg.split('=')[1];
35
+ }
36
+ else if (arg.startsWith('--config=')) {
37
+ const configPath = arg.split('=')[1];
38
+ if (fs.existsSync(configPath)) {
39
+ const fileConfig = require(path.resolve(configPath));
40
+ Object.assign(config, fileConfig);
41
+ }
42
+ else {
43
+ console.error(`Config file not found: ${configPath}`);
44
+ process.exit(1);
45
+ }
46
+ }
47
+ }
48
+ return config;
49
+ }
50
+ function loadEnvFile() {
51
+ const envConfig = {};
52
+ // Try to load from .env file
53
+ const envPaths = ['.env', '.env.local', '.env.production', '.env.development'];
54
+ for (const envPath of envPaths) {
55
+ if (fs.existsSync(envPath)) {
56
+ const content = fs.readFileSync(envPath, 'utf-8');
57
+ const lines = content.split('\n');
58
+ for (const line of lines) {
59
+ const match = line.match(/^TOGGLELY_(\w+)=(.+)$/);
60
+ if (match) {
61
+ const key = match[1].toLowerCase();
62
+ const value = match[2].replace(/^["']|["']$/g, '');
63
+ if (key === 'apikey')
64
+ envConfig.apiKey = value;
65
+ if (key === 'project')
66
+ envConfig.project = value;
67
+ if (key === 'environment')
68
+ envConfig.environment = value;
69
+ if (key === 'baseurl')
70
+ envConfig.baseUrl = value;
71
+ if (key === 'tenantid')
72
+ envConfig.tenantId = value;
73
+ }
74
+ }
75
+ }
76
+ }
77
+ return envConfig;
78
+ }
79
+ async function fetchToggles(config) {
80
+ const params = new URLSearchParams();
81
+ params.set('apiKey', config.apiKey);
82
+ if (config.tenantId)
83
+ params.set('tenantId', config.tenantId);
84
+ const url = `${config.baseUrl}/sdk/flags/${config.project}/${config.environment}?${params.toString()}`;
85
+ console.log(`Fetching toggles from: ${url.replace(config.apiKey, '***')}`);
86
+ const response = await fetch(url);
87
+ if (!response.ok) {
88
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
89
+ }
90
+ return response.json();
91
+ }
92
+ function generateOutput(data, format) {
93
+ switch (format) {
94
+ case 'env':
95
+ return Object.entries(data)
96
+ .map(([key, value]) => {
97
+ const envKey = `TOGGLELY_${key.toUpperCase().replace(/-/g, '_')}`;
98
+ const envValue = typeof value === 'object' ? JSON.stringify(value) : String(value);
99
+ return `${envKey}=${envValue}`;
100
+ })
101
+ .join('\n');
102
+ case 'js':
103
+ return `// Auto-generated by Togglely CLI
104
+ module.exports = ${JSON.stringify(data, null, 2)};
105
+ `;
106
+ case 'json':
107
+ default:
108
+ return JSON.stringify(data, null, 2);
109
+ }
110
+ }
111
+ async function main() {
112
+ console.log('🚀 Togglely CLI - Build-time JSON Generator\n');
113
+ const argsConfig = parseArgs();
114
+ const envConfig = loadEnvFile();
115
+ // Merge configs (args > env > defaults)
116
+ const config = {
117
+ baseUrl: 'https://togglely.de',
118
+ output: './togglely-toggles.json',
119
+ format: 'json',
120
+ ...envConfig,
121
+ ...argsConfig,
122
+ };
123
+ // Validate required fields
124
+ if (!config.apiKey || !config.project || !config.environment) {
125
+ console.error('❌ Missing required parameters:');
126
+ console.error(' --apiKey=<key>');
127
+ console.error(' --project=<project-key>');
128
+ console.error(' --environment=<environment-key>');
129
+ console.error('\nOr use environment variables:');
130
+ console.error(' TOGGLELY_APIKEY, TOGGLELY_PROJECT, TOGGLELY_ENVIRONMENT');
131
+ console.error('\nOr use a config file:');
132
+ console.error(' --config=./togglely.config.js');
133
+ process.exit(1);
134
+ }
135
+ try {
136
+ const data = await fetchToggles(config);
137
+ const output = generateOutput(data, config.format);
138
+ // Ensure directory exists
139
+ const dir = path.dirname(config.output);
140
+ if (!fs.existsSync(dir)) {
141
+ fs.mkdirSync(dir, { recursive: true });
142
+ }
143
+ fs.writeFileSync(config.output, output);
144
+ console.log(`✅ Successfully saved ${Object.keys(data).length} toggles to: ${config.output}`);
145
+ console.log(`\nToggles found:`);
146
+ Object.entries(data).forEach(([key, value]) => {
147
+ const status = value.enabled ? '✅' : '❌';
148
+ console.log(` ${status} ${key}: ${JSON.stringify(value.value)}`);
149
+ });
150
+ }
151
+ catch (error) {
152
+ console.error(`❌ Error: ${error.message}`);
153
+ process.exit(1);
154
+ }
155
+ }
156
+ main();
157
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  /**
2
- * Togglely Core SDK - Framework agnostic
2
+ * Togglely Core SDK - Framework agnostic feature flag management
3
3
  *
4
- * Supports offline fallback via environment variables
4
+ * Features:
5
+ * - Real-time feature flag evaluation
6
+ * - Offline fallback via JSON file, environment variables, or window object
7
+ * - Multi-brand/tenant support
8
+ * - Type-safe flag access
9
+ * - Build-time JSON generation for offline-first deployment
5
10
  */
6
11
  export interface TogglelyConfig {
7
12
  /** API Key from your Togglely dashboard */
@@ -14,8 +19,12 @@ export interface TogglelyConfig {
14
19
  baseUrl: string;
15
20
  /** Request timeout in milliseconds (default: 5000) */
16
21
  timeout?: number;
17
- /** Enable offline mode fallback via environment variables (default: true) */
22
+ /** Enable offline fallback (default: true) */
18
23
  offlineFallback?: boolean;
24
+ /** Path to offline JSON file for fallback (optional) */
25
+ offlineJsonPath?: string;
26
+ /** Inline offline toggles data (optional) */
27
+ offlineToggles?: Record<string, ToggleValue>;
19
28
  /** Prefix for environment variables (default: 'TOGGLELY_') */
20
29
  envPrefix?: string;
21
30
  /** Auto-fetch toggles on init (default: true) */
@@ -37,6 +46,7 @@ export interface ToggleContext {
37
46
  export interface ToggleValue {
38
47
  value: any;
39
48
  enabled: boolean;
49
+ flagType?: 'BOOLEAN' | 'STRING' | 'NUMBER' | 'JSON';
40
50
  }
41
51
  export interface AllTogglesResponse {
42
52
  [key: string]: ToggleValue;
@@ -49,9 +59,6 @@ export interface TogglelyState {
49
59
  }
50
60
  export type TogglelyEventType = 'ready' | 'update' | 'error' | 'offline' | 'online';
51
61
  export type TogglelyEventHandler = (state: TogglelyState) => void;
52
- /**
53
- * Core Togglely Client
54
- */
55
62
  export declare class TogglelyClient {
56
63
  private config;
57
64
  private toggles;
@@ -69,19 +76,62 @@ export declare class TogglelyClient {
69
76
  getState(): TogglelyState;
70
77
  isReady(): boolean;
71
78
  isOffline(): boolean;
79
+ /**
80
+ * Check if a boolean feature flag is enabled
81
+ * @param key - The flag key
82
+ * @param defaultValue - Default value if flag not found
83
+ * @returns Promise<boolean>
84
+ */
72
85
  isEnabled(key: string, defaultValue?: boolean): Promise<boolean>;
86
+ /**
87
+ * Get a string feature flag value
88
+ * @param key - The flag key
89
+ * @param defaultValue - Default value if flag not found or disabled
90
+ * @returns Promise<string>
91
+ */
73
92
  getString(key: string, defaultValue?: string): Promise<string>;
93
+ /**
94
+ * Get a number feature flag value
95
+ * @param key - The flag key
96
+ * @param defaultValue - Default value if flag not found or disabled
97
+ * @returns Promise<number>
98
+ */
74
99
  getNumber(key: string, defaultValue?: number): Promise<number>;
100
+ /**
101
+ * Get a JSON feature flag value
102
+ * @param key - The flag key
103
+ * @param defaultValue - Default value if flag not found or disabled
104
+ * @returns Promise<T>
105
+ */
75
106
  getJSON<T = any>(key: string, defaultValue?: T): Promise<T>;
107
+ /**
108
+ * Get raw toggle value
109
+ * @param key - The flag key
110
+ * @returns Promise<ToggleValue | null>
111
+ */
76
112
  getValue(key: string): Promise<ToggleValue | null>;
113
+ /**
114
+ * Get all toggles
115
+ * @returns Record<string, ToggleValue>
116
+ */
77
117
  getAllToggles(): Record<string, ToggleValue>;
78
118
  /**
79
- * Load toggles from environment variables
80
- * Format: TOGGLELY_<TOGGLE_KEY>=<value> or TOGGLELY_<TOGGLE_KEY>_ENABLED=true
119
+ * Load offline toggles from multiple sources (in priority order):
120
+ * 1. Inline offlineToggles from config
121
+ * 2. JSON file (if offlineJsonPath is set)
122
+ * 3. window.__TOGGLELY_TOGGLES (browser)
123
+ * 4. Environment variables (Node.js)
81
124
  */
82
125
  private loadOfflineToggles;
126
+ /**
127
+ * Load offline toggles from JSON file (async)
128
+ */
129
+ private loadOfflineJsonFile;
83
130
  private getOfflineToggle;
84
131
  private parseOfflineValue;
132
+ /**
133
+ * Refresh all toggles from the server
134
+ */
85
135
  refresh(): Promise<void>;
86
136
  forceOfflineMode(): void;
87
137
  forceOnlineMode(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.esm.js CHANGED
@@ -1,11 +1,14 @@
1
1
  /**
2
- * Togglely Core SDK - Framework agnostic
2
+ * Togglely Core SDK - Framework agnostic feature flag management
3
3
  *
4
- * Supports offline fallback via environment variables
5
- */
6
- /**
7
- * Core Togglely Client
4
+ * Features:
5
+ * - Real-time feature flag evaluation
6
+ * - Offline fallback via JSON file, environment variables, or window object
7
+ * - Multi-brand/tenant support
8
+ * - Type-safe flag access
9
+ * - Build-time JSON generation for offline-first deployment
8
10
  */
11
+ // ==================== Togglely Client ====================
9
12
  class TogglelyClient {
10
13
  constructor(config) {
11
14
  this.toggles = new Map();
@@ -23,6 +26,7 @@ class TogglelyClient {
23
26
  offlineFallback: true,
24
27
  envPrefix: 'TOGGLELY_',
25
28
  autoFetch: true,
29
+ offlineJsonPath: undefined,
26
30
  ...config
27
31
  };
28
32
  // Initialize event handlers
@@ -88,30 +92,56 @@ class TogglelyClient {
88
92
  return this.state.isOffline;
89
93
  }
90
94
  // ==================== Toggle Accessors ====================
95
+ /**
96
+ * Check if a boolean feature flag is enabled
97
+ * @param key - The flag key
98
+ * @param defaultValue - Default value if flag not found
99
+ * @returns Promise<boolean>
100
+ */
91
101
  async isEnabled(key, defaultValue = false) {
92
- console.log(`[Togglely SDK] isEnabled("${key}", default=${defaultValue})`);
93
102
  const value = await this.getValue(key);
94
- console.log(`[Togglely SDK] isEnabled result for "${key}":`, value);
95
103
  if (value === null) {
96
- console.log(`[Togglely SDK] "${key}" not found, returning default: ${defaultValue}`);
97
104
  return defaultValue;
98
105
  }
99
- const result = value.enabled && value.value === true;
100
- console.log(`[Togglely SDK] "${key}" enabled=${value.enabled}, value=${value.value} returning ${result}`);
101
- return result;
106
+ // enabled is the primary on/off switch for a flag.
107
+ // value.value holds the flag's configured value (used for non-boolean use-cases).
108
+ // For boolean flags: if enabled, return the boolean value itself.
109
+ // If the value is not a boolean (misconfigured), fall back to just enabled.
110
+ if (typeof value.value === 'boolean') {
111
+ return value.enabled && value.value;
112
+ }
113
+ return value.enabled;
102
114
  }
115
+ /**
116
+ * Get a string feature flag value
117
+ * @param key - The flag key
118
+ * @param defaultValue - Default value if flag not found or disabled
119
+ * @returns Promise<string>
120
+ */
103
121
  async getString(key, defaultValue = '') {
104
122
  const value = await this.getValue(key);
105
123
  if (value === null || !value.enabled)
106
124
  return defaultValue;
107
125
  return String(value.value);
108
126
  }
127
+ /**
128
+ * Get a number feature flag value
129
+ * @param key - The flag key
130
+ * @param defaultValue - Default value if flag not found or disabled
131
+ * @returns Promise<number>
132
+ */
109
133
  async getNumber(key, defaultValue = 0) {
110
134
  const value = await this.getValue(key);
111
135
  if (value === null || !value.enabled)
112
136
  return defaultValue;
113
137
  return Number(value.value);
114
138
  }
139
+ /**
140
+ * Get a JSON feature flag value
141
+ * @param key - The flag key
142
+ * @param defaultValue - Default value if flag not found or disabled
143
+ * @returns Promise<T>
144
+ */
115
145
  async getJSON(key, defaultValue = {}) {
116
146
  const value = await this.getValue(key);
117
147
  if (value === null || !value.enabled)
@@ -126,57 +156,43 @@ class TogglelyClient {
126
156
  }
127
157
  return value.value;
128
158
  }
159
+ /**
160
+ * Get raw toggle value
161
+ * @param key - The flag key
162
+ * @returns Promise<ToggleValue | null>
163
+ */
129
164
  async getValue(key) {
130
- // Try cache first - but only if no context is set
131
- if (Object.keys(this.context).length === 0) {
132
- const cached = this.toggles.get(key);
133
- if (cached) {
134
- console.log(`[Togglely SDK] Cache hit for "${key}":`, cached);
135
- return cached;
136
- }
137
- }
165
+ // Note: We intentionally do NOT use cache here to always get fresh values
166
+ // The cache is only used for offline fallback and getAllToggles()
167
+ // If you need cached values, use getAllToggles() instead
138
168
  // Fetch from server
139
169
  try {
140
170
  const params = new URLSearchParams();
141
- // Send API key as query parameter (required by backend)
142
- if (this.config.apiKey)
143
- params.set('apiKey', this.config.apiKey);
144
- const brandKey = this.context.tenantId || this.context.brandKey;
145
- if (brandKey)
146
- params.set('tenantId', String(brandKey));
147
- // Always send context if available (needed for targeting rules)
171
+ // Support both brandKey and tenantId for maximum compatibility
172
+ if (this.context.brandKey)
173
+ params.set('brandKey', String(this.context.brandKey));
174
+ if (this.context.tenantId)
175
+ params.set('tenantId', String(this.context.tenantId));
148
176
  if (Object.keys(this.context).length > 0) {
149
177
  params.set('context', JSON.stringify(this.context));
150
178
  }
151
179
  const query = params.toString() ? `?${params.toString()}` : '';
152
180
  const url = `${this.config.baseUrl}/sdk/flags/${encodeURIComponent(this.config.project)}/${encodeURIComponent(this.config.environment)}/${encodeURIComponent(key)}${query}`;
153
- console.log(`[Togglely SDK] Fetching: ${url}`);
154
- const response = await this.fetchWithTimeout(url, { headers: { 'Content-Type': 'application/json' } });
155
- console.log(`[Togglely SDK] Response status: ${response.status}`);
181
+ const headers = {
182
+ 'Content-Type': 'application/json'
183
+ };
184
+ if (this.config.apiKey) {
185
+ headers['Authorization'] = `Bearer ${this.config.apiKey}`;
186
+ }
187
+ const response = await this.fetchWithTimeout(url, { headers });
156
188
  if (!response.ok) {
157
- // Try to get error details from response
158
- let errorBody = '';
159
- try {
160
- errorBody = await response.text();
161
- console.error(`[Togglely SDK] Error response body:`, errorBody);
162
- }
163
- catch { }
164
189
  if (response.status === 404) {
165
- console.log(`[Togglely SDK] Flag "${key}" not found (404)`);
166
190
  return null;
167
191
  }
168
- if (response.status === 401) {
169
- console.error(`[Togglely SDK] Authentication failed (401) - Check your API key`);
170
- }
171
- if (response.status === 403) {
172
- console.error(`[Togglely SDK] Forbidden (403) - Origin not allowed`);
173
- }
174
- throw new Error(`HTTP ${response.status}: ${errorBody || response.statusText}`);
192
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
175
193
  }
176
194
  const data = await response.json();
177
- console.log(`[Togglely SDK] Received data for "${key}":`, data);
178
195
  this.toggles.set(key, data);
179
- // Update state if we were offline
180
196
  if (this.state.isOffline) {
181
197
  this.state.isOffline = false;
182
198
  this.emit('online');
@@ -184,20 +200,21 @@ class TogglelyClient {
184
200
  return data;
185
201
  }
186
202
  catch (error) {
187
- console.error(`[Togglely SDK] Network/Error for "${key}":`, error.message);
188
- // Try offline fallback only if enabled
203
+ // Try offline fallback
189
204
  if (this.config.offlineFallback) {
190
205
  const offlineValue = this.getOfflineToggle(key);
191
206
  if (offlineValue !== null) {
192
- console.log(`[Togglely SDK] Using offline fallback for "${key}":`, offlineValue);
193
207
  return offlineValue;
194
208
  }
195
209
  }
196
- // Return safe default instead of null
197
- console.warn(`[Togglely SDK] Returning default (disabled) for "${key}" due to error`);
210
+ // Return safe default
198
211
  return { value: false, enabled: false };
199
212
  }
200
213
  }
214
+ /**
215
+ * Get all toggles
216
+ * @returns Record<string, ToggleValue>
217
+ */
201
218
  getAllToggles() {
202
219
  const result = {};
203
220
  this.toggles.forEach((value, key) => {
@@ -207,12 +224,28 @@ class TogglelyClient {
207
224
  }
208
225
  // ==================== Offline Fallback ====================
209
226
  /**
210
- * Load toggles from environment variables
211
- * Format: TOGGLELY_<TOGGLE_KEY>=<value> or TOGGLELY_<TOGGLE_KEY>_ENABLED=true
227
+ * Load offline toggles from multiple sources (in priority order):
228
+ * 1. Inline offlineToggles from config
229
+ * 2. JSON file (if offlineJsonPath is set)
230
+ * 3. window.__TOGGLELY_TOGGLES (browser)
231
+ * 4. Environment variables (Node.js)
212
232
  */
213
233
  loadOfflineToggles() {
214
234
  try {
215
- // Browser environment - check window.__TOGGLELY_TOGGLES
235
+ // Priority 1: Inline offline toggles from config
236
+ if (this.config.offlineToggles && Object.keys(this.config.offlineToggles).length > 0) {
237
+ for (const [key, value] of Object.entries(this.config.offlineToggles)) {
238
+ this.toggles.set(key, value);
239
+ }
240
+ this.offlineTogglesLoaded = true;
241
+ console.log('[Togglely] Loaded offline toggles from config');
242
+ return;
243
+ }
244
+ // Priority 2: JSON file (browser only - fetch synchronously not possible, will try async)
245
+ if (this.config.offlineJsonPath && typeof window !== 'undefined') {
246
+ this.loadOfflineJsonFile(this.config.offlineJsonPath);
247
+ }
248
+ // Priority 3: Browser environment - check window.__TOGGLELY_TOGGLES
216
249
  if (typeof window !== 'undefined' && window.__TOGGLELY_TOGGLES) {
217
250
  const offlineToggles = window.__TOGGLELY_TOGGLES;
218
251
  for (const [key, value] of Object.entries(offlineToggles)) {
@@ -222,12 +255,11 @@ class TogglelyClient {
222
255
  console.log('[Togglely] Loaded offline toggles from window.__TOGGLELY_TOGGLES');
223
256
  return;
224
257
  }
225
- // Node.js / Bun / Deno environment - check process.env
258
+ // Priority 4: Node.js / Bun / Deno environment - check process.env
226
259
  if (typeof process !== 'undefined' && process.env) {
227
260
  const prefix = this.config.envPrefix;
228
261
  for (const [envKey, envValue] of Object.entries(process.env)) {
229
262
  if (envKey?.startsWith(prefix) && envValue !== undefined) {
230
- // Parse toggle key: TOGGLELY_MY_FEATURE -> my-feature
231
263
  const toggleKey = envKey
232
264
  .slice(prefix.length)
233
265
  .toLowerCase()
@@ -243,13 +275,47 @@ class TogglelyClient {
243
275
  console.warn('[Togglely] Failed to load offline toggles:', error);
244
276
  }
245
277
  }
278
+ /**
279
+ * Load offline toggles from JSON file (async)
280
+ */
281
+ async loadOfflineJsonFile(path) {
282
+ try {
283
+ if (typeof window !== 'undefined') {
284
+ // Browser - fetch the JSON file
285
+ const response = await fetch(path);
286
+ if (response.ok) {
287
+ const data = await response.json();
288
+ for (const [key, value] of Object.entries(data)) {
289
+ this.toggles.set(key, this.parseOfflineValue(value));
290
+ }
291
+ this.offlineTogglesLoaded = true;
292
+ console.log('[Togglely] Loaded offline toggles from JSON file:', path);
293
+ }
294
+ }
295
+ else if (typeof require !== 'undefined') {
296
+ // Node.js - require the JSON file
297
+ const fs = require('fs');
298
+ const pathModule = require('path');
299
+ const fullPath = pathModule.resolve(path);
300
+ if (fs.existsSync(fullPath)) {
301
+ const data = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
302
+ for (const [key, value] of Object.entries(data)) {
303
+ this.toggles.set(key, this.parseOfflineValue(value));
304
+ }
305
+ this.offlineTogglesLoaded = true;
306
+ console.log('[Togglely] Loaded offline toggles from JSON file:', fullPath);
307
+ }
308
+ }
309
+ }
310
+ catch (error) {
311
+ console.warn('[Togglely] Failed to load offline JSON file:', error);
312
+ }
313
+ }
246
314
  getOfflineToggle(key) {
247
315
  if (!this.config.offlineFallback)
248
316
  return null;
249
- // Try to get from already loaded offline toggles
250
317
  const cached = this.toggles.get(key);
251
318
  if (cached) {
252
- // If we haven't emitted offline event yet, do it now
253
319
  if (!this.state.isOffline) {
254
320
  this.state.isOffline = true;
255
321
  this.emit('offline');
@@ -259,44 +325,47 @@ class TogglelyClient {
259
325
  return null;
260
326
  }
261
327
  parseOfflineValue(value) {
262
- // Handle boolean strings
263
328
  if (typeof value === 'string') {
264
329
  const lower = value.toLowerCase();
265
330
  if (lower === 'true')
266
331
  return { value: true, enabled: true };
267
332
  if (lower === 'false')
268
333
  return { value: false, enabled: true };
269
- // Try to parse as number
270
334
  if (!isNaN(Number(value))) {
271
335
  return { value: Number(value), enabled: true };
272
336
  }
273
- // Try to parse as JSON
274
337
  try {
275
338
  const parsed = JSON.parse(value);
276
339
  return { value: parsed, enabled: true };
277
340
  }
278
341
  catch {
279
- // Return as string
280
342
  return { value, enabled: true };
281
343
  }
282
344
  }
283
345
  return { value, enabled: true };
284
346
  }
285
347
  // ==================== Refresh / Polling ====================
348
+ /**
349
+ * Refresh all toggles from the server
350
+ */
286
351
  async refresh() {
287
352
  try {
288
353
  const params = new URLSearchParams();
289
- // Send API key as query parameter (required by backend)
290
- if (this.config.apiKey)
291
- params.set('apiKey', this.config.apiKey);
292
- const brandKey = this.context.tenantId || this.context.brandKey;
293
- if (brandKey)
294
- params.set('tenantId', String(brandKey));
295
- // Always send context if available (needed for targeting rules)
354
+ // Support both brandKey and tenantId for maximum compatibility
355
+ if (this.context.brandKey)
356
+ params.set('brandKey', String(this.context.brandKey));
357
+ if (this.context.tenantId)
358
+ params.set('tenantId', String(this.context.tenantId));
296
359
  if (Object.keys(this.context).length > 0) {
297
360
  params.set('context', JSON.stringify(this.context));
298
361
  }
299
- const response = await this.fetchWithTimeout(`${this.config.baseUrl}/sdk/flags/${encodeURIComponent(this.config.project)}/${encodeURIComponent(this.config.environment)}?${params.toString()}`, { headers: { 'Content-Type': 'application/json' } });
362
+ const headers = {
363
+ 'Content-Type': 'application/json'
364
+ };
365
+ if (this.config.apiKey) {
366
+ headers['Authorization'] = `Bearer ${this.config.apiKey}`;
367
+ }
368
+ const response = await this.fetchWithTimeout(`${this.config.baseUrl}/sdk/flags/${encodeURIComponent(this.config.project)}/${encodeURIComponent(this.config.environment)}?${params.toString()}`, { headers });
300
369
  if (!response.ok) {
301
370
  throw new Error(`HTTP ${response.status}`);
302
371
  }
@@ -311,7 +380,6 @@ class TogglelyClient {
311
380
  this.state.isReady = true;
312
381
  this.emit('ready');
313
382
  }
314
- // If we were offline, go online
315
383
  if (this.state.isOffline) {
316
384
  this.state.isOffline = false;
317
385
  this.emit('online');
@@ -320,7 +388,6 @@ class TogglelyClient {
320
388
  }
321
389
  catch (error) {
322
390
  this.state.lastError = error;
323
- // If we have offline toggles, switch to offline mode
324
391
  if (this.config.offlineFallback && this.offlineTogglesLoaded) {
325
392
  if (!this.state.isOffline) {
326
393
  this.state.isOffline = true;