@flipflag/sdk 1.1.0 → 1.1.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/index.min.js +1 -1
- package/dist/provider.js +1 -1
- package/dist/provider.js.map +1 -1
- package/package.json +1 -1
package/dist/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as p from"node:fs/promises";import*as g from"node:path";import*as d from"js-yaml";class y{constructor(e){this.opts=e,this.inited=!1,this.interval=null,this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[],this.options={apiUrl:"https://api.flipflag.dev",...e},this.interval=null}async init(){await this.loadConfigFromYaml(),await this.getFeaturesFlags(),await this.syncFeaturesTimes(),this.interval=setInterval(()=>{this.getFeaturesFlags(),this.syncFeaturesTimes(),this.syncFeaturesUsage()},1e4),this.inited=!0}async loadConfigFromYaml(){var e,t,s,r;const n=(e=this.options.configPath)!=null?e:g.resolve(process.cwd(),".flipflag.yml");let l;try{l=await p.readFile(n,"utf8")}catch(i){if(i?.code==="ENOENT"&&this.options.ignoreMissingConfig)return;throw new Error(`FlipFlag: cannot read config at ${n}: ${(t=i?.message)!=null?t:i}`)}let o;try{o=d.load(l)}catch(i){throw new Error(`FlipFlag: invalid YAML in ${n}: ${(s=i?.message)!=null?s:i}`)}if(!o||typeof o!="object"||Array.isArray(o))
|
|
1
|
+
import*as p from"node:fs/promises";import*as g from"node:path";import*as d from"js-yaml";class y{constructor(e){this.opts=e,this.inited=!1,this.interval=null,this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[],this.options={apiUrl:"https://api.flipflag.dev",...e},this.interval=null}async init(){await this.loadConfigFromYaml(),await this.getFeaturesFlags(),await this.syncFeaturesTimes(),this.interval=setInterval(()=>{this.getFeaturesFlags(),this.syncFeaturesTimes(),this.syncFeaturesUsage()},1e4),this.inited=!0}async loadConfigFromYaml(){var e,t,s,r;const n=(e=this.options.configPath)!=null?e:g.resolve(process.cwd(),".flipflag.yml");let l;try{l=await p.readFile(n,"utf8")}catch(i){if(i?.code==="ENOENT"&&this.options.ignoreMissingConfig)return;throw new Error(`FlipFlag: cannot read config at ${n}: ${(t=i?.message)!=null?t:i}`)}let o;try{o=d.load(l)}catch(i){throw new Error(`FlipFlag: invalid YAML in ${n}: ${(s=i?.message)!=null?s:i}`)}if(!o||typeof o!="object"||Array.isArray(o)){console.warn("FlipFlag: YAML root must be an object (mapping featureName -> config)");return}const u=o;for(const[i,c]of Object.entries(u)){const h=((r=c?.times)!=null?r:[]).map(a=>{var f;return{email:u[i].contributor,start:a.started,end:(f=a.finished)!=null?f:null}});for(const a of h){if(Number.isNaN(Date.parse(a.start)))throw new Error(`FlipFlag: invalid "started" date in ${i}: ${a.start}`);if(a.end!==null&&Number.isNaN(Date.parse(String(a.end))))throw new Error(`FlipFlag: invalid "finished" date in ${i}: ${a.end}`)}this.featuresTimes[i]={times:h}}}destroy(){this.inited=!1,this.interval&&clearInterval(this.interval),this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[]}isEnabled(e){const t=this.getLocalFeatureFlag(e);return t?(this.upsertFeaturesUsage(e),t.enabled):(this.createFeature(e,{times:[]}),!1)}upsertFeaturesUsage(e){const t=this.featuresUsage.find(s=>s.featureName===e);if(t){t.usedAt=new Date;return}this.featuresUsage.push({featureName:e,usedAt:new Date})}async createFeature(e,t){if(!this.options.privateKey)return null;const s=this.getBaseUrl(),r=new URL("/v1/sdk/feature",s);fetch(r.toString(),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({featureName:e,privateKey:this.options.privateKey,...t})}).catch(n=>{console.error("Create Feature:",n)})}getBaseUrl(){if(this.options.apiUrl)return this.options.apiUrl.replace(/\/+$/,"");throw new Error("Base API URL is not configured. Please provide apiUrl in the SDK options.")}async getFeaturesFlags(){if(!this.options.publicKey)throw new Error("Public key is missing. Please provide a valid publicKey in the SDK configuration.");try{const e=this.getBaseUrl(),t=new URL("/v1/sdk/feature/flags",e);t.searchParams.append("publicKey",this.options.publicKey);const s=await fetch(t.toString(),{method:"GET",headers:{"Content-Type":"application/json"}});if(!s.ok&&!this.inited){const r=await s.text();throw new Error(`Failed to get features: ${s.status} - ${r}`)}this.featuresFlags=await s.json()}catch(e){console.error("Get list features flag:",e)}}async syncFeaturesTimes(){if(!this.options.privateKey)return null;Object.entries(this.featuresTimes).forEach(([t,s])=>{this.createFeature(t,s)})}async syncFeaturesUsage(){if(!this.options.publicKey)throw new Error("Public key is missing. Please provide a valid publicKey in the SDK configuration.");const e=this.getBaseUrl(),t=new URL("/v1/sdk/feature/usages",e);fetch(t.toString(),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({publicKey:this.options.publicKey,usages:this.featuresUsage})}).catch(s=>{console.error("Feature Usage Sync:",s)})}getLocalFeatureFlag(e){return this.featuresFlags[e]}}export{y as FlipFlag};
|
package/dist/provider.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import*as p from"node:fs/promises";import*as g from"node:path";import*as d from"js-yaml";class F{constructor(e){this.opts=e,this.inited=!1,this.interval=null,this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[],this.options={apiUrl:"https://api.flipflag.dev",...e},this.interval=null}async init(){await this.loadConfigFromYaml(),await this.getFeaturesFlags(),await this.syncFeaturesTimes(),this.interval=setInterval(()=>{this.getFeaturesFlags(),this.syncFeaturesTimes(),this.syncFeaturesUsage()},1e4),this.inited=!0}async loadConfigFromYaml(){var e,t,
|
|
1
|
+
import*as p from"node:fs/promises";import*as g from"node:path";import*as d from"js-yaml";class F{constructor(e){this.opts=e,this.inited=!1,this.interval=null,this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[],this.options={apiUrl:"https://api.flipflag.dev",...e},this.interval=null}async init(){await this.loadConfigFromYaml(),await this.getFeaturesFlags(),await this.syncFeaturesTimes(),this.interval=setInterval(()=>{this.getFeaturesFlags(),this.syncFeaturesTimes(),this.syncFeaturesUsage()},1e4),this.inited=!0}async loadConfigFromYaml(){var e,t,i,r;const n=(e=this.options.configPath)!=null?e:g.resolve(process.cwd(),".flipflag.yml");let l;try{l=await p.readFile(n,"utf8")}catch(s){if((s==null?void 0:s.code)==="ENOENT"&&this.options.ignoreMissingConfig)return;throw new Error(`FlipFlag: cannot read config at ${n}: ${(t=s==null?void 0:s.message)!=null?t:s}`)}let o;try{o=d.load(l)}catch(s){throw new Error(`FlipFlag: invalid YAML in ${n}: ${(i=s==null?void 0:s.message)!=null?i:s}`)}if(!o||typeof o!="object"||Array.isArray(o)){console.warn("FlipFlag: YAML root must be an object (mapping featureName -> config)");return}const u=o;for(const[s,c]of Object.entries(u)){const h=((r=c==null?void 0:c.times)!=null?r:[]).map(a=>{var f;return{email:u[s].contributor,start:a.started,end:(f=a.finished)!=null?f:null}});for(const a of h){if(Number.isNaN(Date.parse(a.start)))throw new Error(`FlipFlag: invalid "started" date in ${s}: ${a.start}`);if(a.end!==null&&Number.isNaN(Date.parse(String(a.end))))throw new Error(`FlipFlag: invalid "finished" date in ${s}: ${a.end}`)}this.featuresTimes[s]={times:h}}}destroy(){this.inited=!1,this.interval&&clearInterval(this.interval),this.featuresTimes={},this.featuresFlags={},this.featuresUsage=[]}isEnabled(e){const t=this.getLocalFeatureFlag(e);return t?(this.upsertFeaturesUsage(e),t.enabled):(this.createFeature(e,{times:[]}),!1)}upsertFeaturesUsage(e){const t=this.featuresUsage.find(i=>i.featureName===e);if(t){t.usedAt=new Date;return}this.featuresUsage.push({featureName:e,usedAt:new Date})}async createFeature(e,t){if(!this.options.privateKey)return null;const i=this.getBaseUrl(),r=new URL("/v1/sdk/feature",i);fetch(r.toString(),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({featureName:e,privateKey:this.options.privateKey,...t})}).catch(n=>{console.error("Create Feature:",n)})}getBaseUrl(){if(this.options.apiUrl)return this.options.apiUrl.replace(/\/+$/,"");throw new Error("Base API URL is not configured. Please provide apiUrl in the SDK options.")}async getFeaturesFlags(){if(!this.options.publicKey)throw new Error("Public key is missing. Please provide a valid publicKey in the SDK configuration.");try{const e=this.getBaseUrl(),t=new URL("/v1/sdk/feature/flags",e);t.searchParams.append("publicKey",this.options.publicKey);const i=await fetch(t.toString(),{method:"GET",headers:{"Content-Type":"application/json"}});if(!i.ok&&!this.inited){const r=await i.text();throw new Error(`Failed to get features: ${i.status} - ${r}`)}this.featuresFlags=await i.json()}catch(e){console.error("Get list features flag:",e)}}async syncFeaturesTimes(){if(!this.options.privateKey)return null;Object.entries(this.featuresTimes).forEach(([e,t])=>{this.createFeature(e,t)})}async syncFeaturesUsage(){if(!this.options.publicKey)throw new Error("Public key is missing. Please provide a valid publicKey in the SDK configuration.");const e=this.getBaseUrl(),t=new URL("/v1/sdk/feature/usages",e);fetch(t.toString(),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({publicKey:this.options.publicKey,usages:this.featuresUsage})}).catch(i=>{console.error("Feature Usage Sync:",i)})}getLocalFeatureFlag(e){return this.featuresFlags[e]}}export{F as FlipFlag};
|
|
2
2
|
//# sourceMappingURL=provider.js.map
|
package/dist/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sources":["../src/provider.ts"],"sourcesContent":["import {\n FlipFlagYaml,\n IDeclareFeatureOptions,\n IDeclareFeatureTime,\n IFeatureFlag,\n IFeatureFlagUsage,\n IManagerOptions,\n} from \"./types/provider\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport * as yaml from \"js-yaml\";\n\n/**\n * Manager for interacting with FlipFlag.\n * Handles feature declaration, loading remote states, and periodic syncing.\n */\nexport class FlipFlag {\n /** Indicates whether initialization has completed */\n private inited = false;\n /** Interval handler for periodic feature refreshing */\n private interval: NodeJS.Timeout | null = null;\n /** Options passed to the manager (merged with defaults) */\n private options: Partial<IManagerOptions>;\n /**\n * Local cache of declared features times.\n * Keyed by feature name.\n */\n private featuresTimes: Record<string, IDeclareFeatureOptions> = {};\n /**\n * Local cache of declared features flags and their metadata.\n * Keyed by feature name.\n */\n private featuresFlags: Record<string, IFeatureFlag> = {};\n /**\n * Local cache of feature-flag usage events.\n * Each entry represents an interaction with a specific feature flag\n * (read, update, check) along with related metadata.\n */\n private featuresUsage: IFeatureFlagUsage[] = [];\n\n /**\n * @param opts Manager configuration (publicKey, privateKey, apiUrl, etc.)\n */\n constructor(protected readonly opts: IManagerOptions) {\n this.options = { apiUrl: \"https://api.flipflag.dev\", ...opts };\n this.interval = null;\n }\n\n /**\n * Initializes the manager:\n * - Loads the initial feature flags from the server\n * - Starts a 10-second polling loop to:\n * • refresh feature flags\n * • synchronize feature activation times\n */\n public async init() {\n await this.loadConfigFromYaml();\n await this.getFeaturesFlags();\n await this.syncFeaturesTimes();\n\n this.interval = setInterval(() => {\n this.getFeaturesFlags();\n this.syncFeaturesTimes();\n this.syncFeaturesUsage();\n }, 10_000);\n\n this.inited = true;\n }\n\n private async loadConfigFromYaml() {\n const configPath =\n this.options.configPath ?? path.resolve(process.cwd(), \".flipflag.yml\");\n\n let raw: string;\n try {\n raw = await fs.readFile(configPath, \"utf8\");\n } catch (e: any) {\n if (e?.code === \"ENOENT\" && this.options.ignoreMissingConfig) return;\n throw new Error(\n `FlipFlag: cannot read config at ${configPath}: ${e?.message ?? e}`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = yaml.load(raw);\n } catch (e: any) {\n throw new Error(\n `FlipFlag: invalid YAML in ${configPath}: ${e?.message ?? e}`,\n );\n }\n\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\n `FlipFlag: YAML root must be an object (mapping featureName -> config)`,\n );\n }\n\n const doc = parsed as FlipFlagYaml;\n\n for (const [featureName, cfg] of Object.entries(doc)) {\n const times = (cfg?.times ?? []).map((t) => ({\n email: doc.contributor,\n start: t.started,\n end: t.finished ?? null,\n })) as IDeclareFeatureTime[];\n\n for (const t of times) {\n if (Number.isNaN(Date.parse(t.start))) {\n throw new Error(\n `FlipFlag: invalid \"started\" date in ${featureName}: ${t.start}`,\n );\n }\n if (t.end !== null && Number.isNaN(Date.parse(String(t.end)))) {\n throw new Error(\n `FlipFlag: invalid \"finished\" date in ${featureName}: ${t.end}`,\n );\n }\n }\n\n this.featuresTimes[featureName] = { times };\n }\n }\n\n /**\n * Destroys the manager:\n * - Stops the periodic sync\n * - Clears all locally declared features\n */\n public destroy() {\n this.inited = false;\n if (this.interval) clearInterval(this.interval);\n this.featuresTimes = {};\n this.featuresFlags = {};\n this.featuresUsage = [];\n }\n\n /**\n * Checks whether a feature is enabled.\n * If the feature does not exist locally, it will be created with empty options.\n *\n * @param featureName Name of the feature\n * @returns `true` if enabled, otherwise `false`\n */\n isEnabled(featureName: string) {\n const feature = this.getLocalFeatureFlag(featureName);\n\n if (!feature) {\n this.createFeature(featureName, { times: [] });\n\n return false;\n }\n\n this.upsertFeaturesUsage(featureName);\n\n return feature.enabled;\n }\n\n /**\n * Updates or inserts a feature usage record.\n * If the feature was already used before, its timestamp is updated.\n * Otherwise, a new usage entry is created.\n *\n * @param featureName - Name of the feature that was checked/used\n */\n private upsertFeaturesUsage(featureName: string) {\n const existing = this.featuresUsage.find(\n (u) => u.featureName === featureName,\n );\n\n if (existing) {\n existing.usedAt = new Date();\n return;\n }\n\n this.featuresUsage.push({\n featureName,\n usedAt: new Date(),\n });\n }\n\n /**\n * Creates a new feature on the server.\n * Requires a privateKey; otherwise the request is ignored.\n *\n * @param featureName Name of the feature\n * @param options Feature declaration options\n * @returns Created feature data or null if privateKey is missing\n */\n private async createFeature(\n featureName: string,\n options: IDeclareFeatureOptions,\n ) {\n if (!this.options.privateKey) {\n return null;\n }\n\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature\", baseUrl);\n\n fetch(url.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n featureName,\n privateKey: this.options.privateKey,\n ...options,\n }),\n }).catch((error) => {\n console.error(\"Create Feature:\", error);\n });\n }\n\n private getBaseUrl() {\n if (this.options.apiUrl) {\n return this.options.apiUrl.replace(/\\/+$/, \"\");\n }\n\n throw new Error(\n \"Base API URL is not configured. Please provide apiUrl in the SDK options.\",\n );\n }\n\n /**\n * Fetches all features associated with the publicKey.\n * Populates the local `featuresTimes` cache.\n *\n * Throws an error only during initial initialization;\n * later polling failures are ignored.\n */\n private async getFeaturesFlags() {\n if (!this.options.publicKey) {\n throw new Error(\n \"Public key is missing. Please provide a valid publicKey in the SDK configuration.\",\n );\n }\n\n try {\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature/flags\", baseUrl);\n url.searchParams.append(\"publicKey\", this.options.publicKey);\n\n const response = await fetch(url.toString(), {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok && !this.inited) {\n const errorText = await response.text();\n throw new Error(\n `Failed to get features: ${response.status} - ${errorText}`,\n );\n }\n\n this.featuresFlags = await response.json();\n } catch (error) {\n console.error(\"Get list features flag:\", error);\n }\n }\n\n /**\n * Fetches all features associated with the publicKey.\n * Populates the local `featuresTimes` cache.\n *\n * Throws an error only during initial initialization;\n * later polling failures are ignored.\n */\n private async syncFeaturesTimes() {\n if (!this.options.privateKey) {\n return null;\n }\n\n const list = Object.entries(this.featuresTimes);\n list.forEach(([featureName, options]: [string, IDeclareFeatureOptions]) => {\n this.createFeature(featureName, options);\n });\n }\n\n /**\n * Sends the collected feature usage data to the server.\n * Requires a valid `publicKey` provided in the SDK configuration.\n *\n * Throws an error if the `publicKey` is missing;\n * network or server errors during sync are not awaited or propagated.\n */\n private async syncFeaturesUsage() {\n if (!this.options.publicKey) {\n throw new Error(\n \"Public key is missing. Please provide a valid publicKey in the SDK configuration.\",\n );\n }\n\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature/usages\", baseUrl);\n\n fetch(url.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n publicKey: this.options.publicKey,\n usages: this.featuresUsage,\n }),\n }).catch((error) => {\n console.error(\"Feature Usage Sync:\", error);\n });\n }\n\n /**\n * Returns a locally cached feature flag by name.\n *\n * @param featureName Name of the feature\n * @returns The feature or undefined if it does not exist\n */\n private getLocalFeatureFlag(featureName: string) {\n return this.featuresFlags[featureName];\n }\n}\n"],"names":["FlipFlag","opts","_a","_b","_c","_d","configPath","path","raw","fs","e","parsed","yaml","doc","featureName","cfg","times","t","feature","existing","u","options","baseUrl","url","error","response","errorText"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"provider.js","sources":["../src/provider.ts"],"sourcesContent":["import {\n FlipFlagYaml,\n IDeclareFeatureOptions,\n IDeclareFeatureTime,\n IFeatureFlag,\n IFeatureFlagUsage,\n IManagerOptions,\n} from \"./types/provider\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport * as yaml from \"js-yaml\";\n\n/**\n * Manager for interacting with FlipFlag.\n * Handles feature declaration, loading remote states, and periodic syncing.\n */\nexport class FlipFlag {\n /** Indicates whether initialization has completed */\n private inited = false;\n /** Interval handler for periodic feature refreshing */\n private interval: NodeJS.Timeout | null = null;\n /** Options passed to the manager (merged with defaults) */\n private options: Partial<IManagerOptions>;\n /**\n * Local cache of declared features times.\n * Keyed by feature name.\n */\n private featuresTimes: Record<string, IDeclareFeatureOptions> = {};\n /**\n * Local cache of declared features flags and their metadata.\n * Keyed by feature name.\n */\n private featuresFlags: Record<string, IFeatureFlag> = {};\n /**\n * Local cache of feature-flag usage events.\n * Each entry represents an interaction with a specific feature flag\n * (read, update, check) along with related metadata.\n */\n private featuresUsage: IFeatureFlagUsage[] = [];\n\n /**\n * @param opts Manager configuration (publicKey, privateKey, apiUrl, etc.)\n */\n constructor(protected readonly opts: IManagerOptions) {\n this.options = { apiUrl: \"https://api.flipflag.dev\", ...opts };\n this.interval = null;\n }\n\n /**\n * Initializes the manager:\n * - Loads the initial feature flags from the server\n * - Starts a 10-second polling loop to:\n * • refresh feature flags\n * • synchronize feature activation times\n */\n public async init() {\n await this.loadConfigFromYaml();\n await this.getFeaturesFlags();\n await this.syncFeaturesTimes();\n\n this.interval = setInterval(() => {\n this.getFeaturesFlags();\n this.syncFeaturesTimes();\n this.syncFeaturesUsage();\n }, 10_000);\n\n this.inited = true;\n }\n\n private async loadConfigFromYaml() {\n const configPath =\n this.options.configPath ?? path.resolve(process.cwd(), \".flipflag.yml\");\n\n let raw: string;\n try {\n raw = await fs.readFile(configPath, \"utf8\");\n } catch (e: any) {\n if (e?.code === \"ENOENT\" && this.options.ignoreMissingConfig) return;\n throw new Error(\n `FlipFlag: cannot read config at ${configPath}: ${e?.message ?? e}`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = yaml.load(raw);\n } catch (e: any) {\n throw new Error(\n `FlipFlag: invalid YAML in ${configPath}: ${e?.message ?? e}`,\n );\n }\n\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n console.warn(\n \"FlipFlag: YAML root must be an object (mapping featureName -> config)\",\n );\n return;\n }\n\n const doc = parsed as FlipFlagYaml;\n\n for (const [featureName, cfg] of Object.entries(doc)) {\n const times = (cfg?.times ?? []).map((t) => ({\n email: doc[featureName].contributor,\n start: t.started,\n end: t.finished ?? null,\n })) as IDeclareFeatureTime[];\n\n for (const t of times) {\n if (Number.isNaN(Date.parse(t.start))) {\n throw new Error(\n `FlipFlag: invalid \"started\" date in ${featureName}: ${t.start}`,\n );\n }\n if (t.end !== null && Number.isNaN(Date.parse(String(t.end)))) {\n throw new Error(\n `FlipFlag: invalid \"finished\" date in ${featureName}: ${t.end}`,\n );\n }\n }\n\n this.featuresTimes[featureName] = { times };\n }\n }\n\n /**\n * Destroys the manager:\n * - Stops the periodic sync\n * - Clears all locally declared features\n */\n public destroy() {\n this.inited = false;\n if (this.interval) clearInterval(this.interval);\n this.featuresTimes = {};\n this.featuresFlags = {};\n this.featuresUsage = [];\n }\n\n /**\n * Checks whether a feature is enabled.\n * If the feature does not exist locally, it will be created with empty options.\n *\n * @param featureName Name of the feature\n * @returns `true` if enabled, otherwise `false`\n */\n isEnabled(featureName: string) {\n const feature = this.getLocalFeatureFlag(featureName);\n\n if (!feature) {\n this.createFeature(featureName, { times: [] });\n\n return false;\n }\n\n this.upsertFeaturesUsage(featureName);\n\n return feature.enabled;\n }\n\n /**\n * Updates or inserts a feature usage record.\n * If the feature was already used before, its timestamp is updated.\n * Otherwise, a new usage entry is created.\n *\n * @param featureName - Name of the feature that was checked/used\n */\n private upsertFeaturesUsage(featureName: string) {\n const existing = this.featuresUsage.find(\n (u) => u.featureName === featureName,\n );\n\n if (existing) {\n existing.usedAt = new Date();\n return;\n }\n\n this.featuresUsage.push({\n featureName,\n usedAt: new Date(),\n });\n }\n\n /**\n * Creates a new feature on the server.\n * Requires a privateKey; otherwise the request is ignored.\n *\n * @param featureName Name of the feature\n * @param options Feature declaration options\n * @returns Created feature data or null if privateKey is missing\n */\n private async createFeature(\n featureName: string,\n options: IDeclareFeatureOptions,\n ) {\n if (!this.options.privateKey) {\n return null;\n }\n\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature\", baseUrl);\n\n fetch(url.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n featureName,\n privateKey: this.options.privateKey,\n ...options,\n }),\n }).catch((error) => {\n console.error(\"Create Feature:\", error);\n });\n }\n\n private getBaseUrl() {\n if (this.options.apiUrl) {\n return this.options.apiUrl.replace(/\\/+$/, \"\");\n }\n\n throw new Error(\n \"Base API URL is not configured. Please provide apiUrl in the SDK options.\",\n );\n }\n\n /**\n * Fetches all features associated with the publicKey.\n * Populates the local `featuresTimes` cache.\n *\n * Throws an error only during initial initialization;\n * later polling failures are ignored.\n */\n private async getFeaturesFlags() {\n if (!this.options.publicKey) {\n throw new Error(\n \"Public key is missing. Please provide a valid publicKey in the SDK configuration.\",\n );\n }\n\n try {\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature/flags\", baseUrl);\n url.searchParams.append(\"publicKey\", this.options.publicKey);\n\n const response = await fetch(url.toString(), {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok && !this.inited) {\n const errorText = await response.text();\n throw new Error(\n `Failed to get features: ${response.status} - ${errorText}`,\n );\n }\n\n this.featuresFlags = await response.json();\n } catch (error) {\n console.error(\"Get list features flag:\", error);\n }\n }\n\n /**\n * Fetches all features associated with the publicKey.\n * Populates the local `featuresTimes` cache.\n *\n * Throws an error only during initial initialization;\n * later polling failures are ignored.\n */\n private async syncFeaturesTimes() {\n if (!this.options.privateKey) {\n return null;\n }\n\n const list = Object.entries(this.featuresTimes);\n list.forEach(([featureName, options]: [string, IDeclareFeatureOptions]) => {\n this.createFeature(featureName, options);\n });\n }\n\n /**\n * Sends the collected feature usage data to the server.\n * Requires a valid `publicKey` provided in the SDK configuration.\n *\n * Throws an error if the `publicKey` is missing;\n * network or server errors during sync are not awaited or propagated.\n */\n private async syncFeaturesUsage() {\n if (!this.options.publicKey) {\n throw new Error(\n \"Public key is missing. Please provide a valid publicKey in the SDK configuration.\",\n );\n }\n\n const baseUrl = this.getBaseUrl();\n const url = new URL(\"/v1/sdk/feature/usages\", baseUrl);\n\n fetch(url.toString(), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n publicKey: this.options.publicKey,\n usages: this.featuresUsage,\n }),\n }).catch((error) => {\n console.error(\"Feature Usage Sync:\", error);\n });\n }\n\n /**\n * Returns a locally cached feature flag by name.\n *\n * @param featureName Name of the feature\n * @returns The feature or undefined if it does not exist\n */\n private getLocalFeatureFlag(featureName: string) {\n return this.featuresFlags[featureName];\n }\n}\n"],"names":["FlipFlag","opts","_a","_b","_c","_d","configPath","path","raw","fs","e","parsed","yaml","doc","featureName","cfg","times","t","feature","existing","u","options","baseUrl","url","error","response","errorText"],"mappings":"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"}
|