|
16 | 16 |
|
17 | 17 | import IClientContext from '../contracts/IClientContext'; |
18 | 18 | import { LogLevel } from '../contracts/IDBSQLLogger'; |
| 19 | +import fetch from 'node-fetch'; |
19 | 20 |
|
20 | 21 | /** |
21 | 22 | * Context holding feature flag state for a specific host. |
@@ -104,17 +105,95 @@ export default class FeatureFlagCache { |
104 | 105 | } |
105 | 106 |
|
106 | 107 | /** |
107 | | - * Fetches feature flag from server. |
108 | | - * This is a placeholder implementation that returns false. |
109 | | - * Real implementation would fetch from server using connection provider. |
110 | | - * @param _host The host to fetch feature flag for (unused in placeholder implementation) |
| 108 | + * Fetches feature flag from server using connector-service API. |
| 109 | + * Calls GET /api/2.0/connector-service/feature-flags/OSS_NODEJS/{version} |
| 110 | + * |
| 111 | + * @param host The host to fetch feature flag for |
| 112 | + * @returns true if feature flag is enabled, false otherwise |
111 | 113 | */ |
112 | | - // eslint-disable-next-line @typescript-eslint/no-unused-vars |
113 | | - private async fetchFeatureFlag(_host: string): Promise<boolean> { |
114 | | - // Placeholder implementation |
115 | | - // Real implementation would use: |
116 | | - // const connectionProvider = await this.context.getConnectionProvider(); |
117 | | - // and make an API call to fetch the feature flag |
118 | | - return false; |
| 114 | + private async fetchFeatureFlag(host: string): Promise<boolean> { |
| 115 | + const logger = this.context.getLogger(); |
| 116 | + |
| 117 | + try { |
| 118 | + // Get driver version for endpoint |
| 119 | + const driverVersion = this.getDriverVersion(); |
| 120 | + |
| 121 | + // Build feature flags endpoint |
| 122 | + const endpoint = `https://${host}/api/2.0/connector-service/feature-flags/OSS_NODEJS/${driverVersion}`; |
| 123 | + |
| 124 | + // Get authentication headers |
| 125 | + const authHeaders = await this.context.getAuthHeaders(); |
| 126 | + |
| 127 | + logger.log(LogLevel.debug, `Fetching feature flags from ${endpoint}`); |
| 128 | + |
| 129 | + // Make HTTP GET request with authentication |
| 130 | + const response = await fetch(endpoint, { |
| 131 | + method: 'GET', |
| 132 | + headers: { |
| 133 | + ...authHeaders, |
| 134 | + 'Content-Type': 'application/json', |
| 135 | + 'User-Agent': `databricks-sql-nodejs/${driverVersion}`, |
| 136 | + }, |
| 137 | + }); |
| 138 | + |
| 139 | + if (!response.ok) { |
| 140 | + logger.log( |
| 141 | + LogLevel.debug, |
| 142 | + `Feature flag fetch failed: ${response.status} ${response.statusText}` |
| 143 | + ); |
| 144 | + return false; |
| 145 | + } |
| 146 | + |
| 147 | + // Parse response JSON |
| 148 | + const data: any = await response.json(); |
| 149 | + |
| 150 | + // Response format: { flags: [{ name: string, value: string }], ttl_seconds?: number } |
| 151 | + if (data && data.flags && Array.isArray(data.flags)) { |
| 152 | + // Update cache duration if TTL provided |
| 153 | + const ctx = this.contexts.get(host); |
| 154 | + if (ctx && data.ttl_seconds) { |
| 155 | + ctx.cacheDuration = data.ttl_seconds * 1000; // Convert to milliseconds |
| 156 | + logger.log(LogLevel.debug, `Updated cache duration to ${data.ttl_seconds} seconds`); |
| 157 | + } |
| 158 | + |
| 159 | + // Look for our specific feature flag |
| 160 | + const flag = data.flags.find((f: any) => f.name === this.FEATURE_FLAG_NAME); |
| 161 | + |
| 162 | + if (flag) { |
| 163 | + // Parse boolean value (can be string "true"/"false") |
| 164 | + const value = String(flag.value).toLowerCase(); |
| 165 | + const enabled = value === 'true'; |
| 166 | + logger.log( |
| 167 | + LogLevel.debug, |
| 168 | + `Feature flag ${this.FEATURE_FLAG_NAME}: ${enabled}` |
| 169 | + ); |
| 170 | + return enabled; |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + // Feature flag not found in response, default to false |
| 175 | + logger.log(LogLevel.debug, `Feature flag ${this.FEATURE_FLAG_NAME} not found in response`); |
| 176 | + return false; |
| 177 | + } catch (error: any) { |
| 178 | + // Log at debug level only, never propagate exceptions |
| 179 | + logger.log(LogLevel.debug, `Error fetching feature flag from ${host}: ${error.message}`); |
| 180 | + return false; |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + /** |
| 185 | + * Gets the driver version without -oss suffix for API calls. |
| 186 | + * Format: "1.12.0" from "1.12.0-oss" |
| 187 | + */ |
| 188 | + private getDriverVersion(): string { |
| 189 | + try { |
| 190 | + // Import version from lib/version.ts |
| 191 | + const version = require('../version').default; |
| 192 | + // Remove -oss suffix if present |
| 193 | + return version.replace(/-oss$/, ''); |
| 194 | + } catch (error) { |
| 195 | + // Fallback to a default version if import fails |
| 196 | + return '1.0.0'; |
| 197 | + } |
119 | 198 | } |
120 | 199 | } |
0 commit comments