@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/README.md +243 -50
- package/dist/__tests__/index.test.js +38 -18
- package/dist/__tests__/index.test.js.map +1 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +157 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +58 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +139 -72
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +139 -72
- package/dist/index.js.map +1 -1
- package/package.json +8 -3
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
|
package/dist/cli.js.map
ADDED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
|
80
|
-
*
|
|
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;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
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
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
return
|
|
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
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
//
|
|
142
|
-
if (this.
|
|
143
|
-
params.set('
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
211
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
290
|
-
if (this.
|
|
291
|
-
params.set('
|
|
292
|
-
|
|
293
|
-
|
|
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
|
|
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;
|