@ops-ai/react-router-toggly 1.0.1 → 1.2.0
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/CHANGELOG.md +30 -0
- package/README.md +50 -0
- package/dist/client.cjs +431 -82
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +45 -3
- package/dist/client.d.ts +45 -3
- package/dist/client.js +432 -83
- package/dist/client.js.map +1 -1
- package/dist/server.cjs +212 -64
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +57 -5
- package/dist/server.d.ts +57 -5
- package/dist/server.js +212 -64
- package/dist/server.js.map +1 -1
- package/dist/telemetry/grpc.cjs +1 -1
- package/dist/telemetry/grpc.cjs.map +1 -1
- package/dist/telemetry/grpc.js +1 -1
- package/dist/telemetry/grpc.js.map +1 -1
- package/package.json +3 -2
package/dist/server.cjs
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
var togglyHooksTypes = require('@ops-ai/toggly-hooks-types');
|
|
4
4
|
var togglyEval = require('@ops-ai/toggly-eval');
|
|
5
|
+
var togglySignedDefs = require('@ops-ai/toggly-signed-defs');
|
|
5
6
|
var async_hooks = require('async_hooks');
|
|
6
7
|
var togglyLocalGates = require('@ops-ai/toggly-local-gates');
|
|
7
8
|
var WebSocket = require('ws');
|
|
8
|
-
var togglySignedDefs = require('@ops-ai/toggly-signed-defs');
|
|
9
9
|
var module$1 = require('module');
|
|
10
10
|
var fs = require('fs');
|
|
11
11
|
var path = require('path');
|
|
@@ -62,28 +62,49 @@ function mergeConfig(config) {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
function buildDefinitionsUrl(config, context) {
|
|
65
|
-
const { baseUrl, appKey, environment, groups, claims, evaluationMode } = mergeConfig(config);
|
|
65
|
+
const { baseUrl, appKey, environment, groups, claims, evaluationMode, enableVariants } = mergeConfig(config);
|
|
66
66
|
if (!appKey) {
|
|
67
67
|
throw new Error("appKey is required");
|
|
68
68
|
}
|
|
69
69
|
const mode = evaluationMode ?? "remote";
|
|
70
|
-
const
|
|
70
|
+
const useVariants = mode !== "local" && enableVariants === true;
|
|
71
|
+
let pathSegment = "evaluated-signed";
|
|
72
|
+
if (mode === "local") {
|
|
73
|
+
pathSegment = "definitions-signed";
|
|
74
|
+
} else if (useVariants) {
|
|
75
|
+
pathSegment = "evaluated-variants-signed";
|
|
76
|
+
}
|
|
71
77
|
const url = new URL(`${baseUrl}/${pathSegment}/${appKey}/${environment}`);
|
|
72
78
|
if (mode === "local") {
|
|
73
79
|
return url.toString();
|
|
74
80
|
}
|
|
75
|
-
const fromParam = context;
|
|
81
|
+
const fromParam = typeof context === "string" ? { identity: context } : context;
|
|
76
82
|
togglyHooksTypes.appendEvaluationContext(
|
|
77
83
|
url,
|
|
78
84
|
{
|
|
79
85
|
identity: fromParam?.identity,
|
|
80
|
-
groups: groups,
|
|
81
|
-
claims: claims
|
|
86
|
+
groups: fromParam?.groups ?? groups,
|
|
87
|
+
claims: fromParam?.claims ?? claims
|
|
82
88
|
},
|
|
83
|
-
"evaluated"
|
|
89
|
+
useVariants ? "variants" : "evaluated"
|
|
84
90
|
);
|
|
85
91
|
return url.toString();
|
|
86
92
|
}
|
|
93
|
+
function parseVariantDefsPayload(parsed) {
|
|
94
|
+
return togglySignedDefs.asVariantDefsRecord(togglySignedDefs.unwrapDefsPayload(parsed));
|
|
95
|
+
}
|
|
96
|
+
function variantDefsToFlags(defs) {
|
|
97
|
+
const out = {};
|
|
98
|
+
for (const key of Object.keys(defs)) {
|
|
99
|
+
out[key] = defs[key]?.enabled === true;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
function decodeVariantValue(value, isT) {
|
|
104
|
+
if (value === null || value === void 0) return null;
|
|
105
|
+
if (isT) return isT(value) ? value : null;
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
87
108
|
function isFeatureEnabledLocal(defsByKey, featureKey, evalCtx = {}, defaultValue = false) {
|
|
88
109
|
if (!defsByKey || defsByKey.size === 0) {
|
|
89
110
|
return defaultValue;
|
|
@@ -566,7 +587,7 @@ var MetricsBatcher = class {
|
|
|
566
587
|
|
|
567
588
|
// src/core/sdk-identity.ts
|
|
568
589
|
var SDK_ID = "react-router";
|
|
569
|
-
var SDK_VERSION = "1.0
|
|
590
|
+
var SDK_VERSION = "1.2.0";
|
|
570
591
|
function sdkUserAgent() {
|
|
571
592
|
return `toggly-${SDK_ID}/${SDK_VERSION}`;
|
|
572
593
|
}
|
|
@@ -1086,7 +1107,7 @@ async function extractEvalContext(request, providers = {}) {
|
|
|
1086
1107
|
|
|
1087
1108
|
// src/server/sdk-identity.ts
|
|
1088
1109
|
var SDK_ID2 = "react-router";
|
|
1089
|
-
var SDK_VERSION2 = "1.0
|
|
1110
|
+
var SDK_VERSION2 = "1.2.0";
|
|
1090
1111
|
var SDK_HEADER_ID = "X-Toggly-Sdk";
|
|
1091
1112
|
var SDK_HEADER_VERSION = "X-Toggly-Sdk-Version";
|
|
1092
1113
|
function sdkUserAgent2() {
|
|
@@ -1378,12 +1399,19 @@ var TogglyServerClient = class {
|
|
|
1378
1399
|
this.flags = {};
|
|
1379
1400
|
/** Raw definitions-signed models keyed by featureKey. */
|
|
1380
1401
|
this.definitions = /* @__PURE__ */ new Map();
|
|
1402
|
+
/** Evaluated-variants-signed defs when `enableVariants`; otherwise null. */
|
|
1403
|
+
this.variants = null;
|
|
1381
1404
|
this.hooks = [];
|
|
1382
1405
|
this.initialized = false;
|
|
1383
1406
|
/** Dedupes concurrent cold-start definition fetches. */
|
|
1384
1407
|
this.definitionsLoadPromise = null;
|
|
1385
1408
|
/** Single-flight for definition GET so concurrent joiners are not double-counted. */
|
|
1386
1409
|
this.definitionsFetchInFlight = null;
|
|
1410
|
+
/**
|
|
1411
|
+
* Serializes variants-mode init so concurrent identities cannot interleave
|
|
1412
|
+
* shared `this.variants` / `this.flags` mutations.
|
|
1413
|
+
*/
|
|
1414
|
+
this.variantsInitChain = Promise.resolve();
|
|
1387
1415
|
// WebSocket live updates
|
|
1388
1416
|
this.ws = null;
|
|
1389
1417
|
this.wsConnected = false;
|
|
@@ -1403,7 +1431,7 @@ var TogglyServerClient = class {
|
|
|
1403
1431
|
...config,
|
|
1404
1432
|
...telemetryFlags,
|
|
1405
1433
|
...grpcClients,
|
|
1406
|
-
evaluationMode: "local",
|
|
1434
|
+
evaluationMode: config.enableVariants ? "remote" : "local",
|
|
1407
1435
|
telemetryTransport: config.telemetryTransport ?? "grpc",
|
|
1408
1436
|
telemetryAttachProcessHandlers: config.telemetryAttachProcessHandlers ?? (config.telemetryTransport ?? "grpc") === "grpc"
|
|
1409
1437
|
});
|
|
@@ -1447,12 +1475,12 @@ var TogglyServerClient = class {
|
|
|
1447
1475
|
});
|
|
1448
1476
|
this.telemetry.start();
|
|
1449
1477
|
}
|
|
1450
|
-
recordCheckForKey(featureKey, enabled, identityOverride) {
|
|
1478
|
+
recordCheckForKey(featureKey, enabled, identityOverride, variant) {
|
|
1451
1479
|
if (!this.telemetry?.usageEnabled) {
|
|
1452
1480
|
return;
|
|
1453
1481
|
}
|
|
1454
1482
|
const identity = identityOverride?.identity ?? this.identity;
|
|
1455
|
-
this.telemetry.recordCheck(featureKey, enabled, identity);
|
|
1483
|
+
this.telemetry.recordCheck(featureKey, enabled, identity, variant);
|
|
1456
1484
|
}
|
|
1457
1485
|
/** Soft-fail wrapper — definition refresh must not throw from telemetry. */
|
|
1458
1486
|
noteDefinitionCacheHit() {
|
|
@@ -1564,6 +1592,9 @@ var TogglyServerClient = class {
|
|
|
1564
1592
|
* shared mutable `this.flags` / `this.identity` (safe under concurrency).
|
|
1565
1593
|
*/
|
|
1566
1594
|
snapshotFlags(identityOverride) {
|
|
1595
|
+
if (this.config.enableVariants) {
|
|
1596
|
+
return { ...this.flags };
|
|
1597
|
+
}
|
|
1567
1598
|
return {
|
|
1568
1599
|
...this.config.featureDefaults ?? {},
|
|
1569
1600
|
...togglyEval.snapshotEvaluatedBooleans(
|
|
@@ -1573,12 +1604,18 @@ var TogglyServerClient = class {
|
|
|
1573
1604
|
};
|
|
1574
1605
|
}
|
|
1575
1606
|
getEffectiveFlag(featureKey, defaultValue = false, entityContext, identityOverride) {
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
featureKey
|
|
1579
|
-
this.
|
|
1580
|
-
|
|
1581
|
-
|
|
1607
|
+
let evaluated;
|
|
1608
|
+
if (this.config.enableVariants) {
|
|
1609
|
+
const raw = this.flags[featureKey];
|
|
1610
|
+
evaluated = typeof raw === "boolean" ? raw : this.config.featureDefaults?.[featureKey] ?? defaultValue;
|
|
1611
|
+
} else {
|
|
1612
|
+
evaluated = isFeatureEnabledLocal(
|
|
1613
|
+
this.definitions,
|
|
1614
|
+
featureKey,
|
|
1615
|
+
this.buildEvalContext(identityOverride, entityContext),
|
|
1616
|
+
this.config.featureDefaults?.[featureKey] ?? defaultValue
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1582
1619
|
return togglyLocalGates.applyLocalGate(evaluated, featureKey, this.localGates, this.localGateIndex);
|
|
1583
1620
|
}
|
|
1584
1621
|
getDefinitionsRevision() {
|
|
@@ -1655,13 +1692,56 @@ var TogglyServerClient = class {
|
|
|
1655
1692
|
return this.wsConnected;
|
|
1656
1693
|
}
|
|
1657
1694
|
/**
|
|
1658
|
-
* Initialize the client
|
|
1659
|
-
*
|
|
1660
|
-
*
|
|
1661
|
-
*
|
|
1662
|
-
*
|
|
1695
|
+
* Initialize the client and optionally bind identity.
|
|
1696
|
+
* Local mode: concurrent cold starts share one definitions fetch; each caller
|
|
1697
|
+
* still receives a request-local snapshot for its identity.
|
|
1698
|
+
* Variants mode: inits are serialized so concurrent identities cannot
|
|
1699
|
+
* interleave remote variant assignments on the shared client.
|
|
1663
1700
|
*/
|
|
1664
1701
|
async init(identity) {
|
|
1702
|
+
if (this.config.enableVariants) {
|
|
1703
|
+
return this.initWithVariants(identity);
|
|
1704
|
+
}
|
|
1705
|
+
return this.initWithLocalDefinitions(identity);
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Variants are remote-evaluated; identity must be bound before fetch so
|
|
1709
|
+
* `/evaluated-variants-signed` receives the correct `userId`.
|
|
1710
|
+
*/
|
|
1711
|
+
async initWithVariants(identity) {
|
|
1712
|
+
const run = async () => {
|
|
1713
|
+
const previousIdentity = this.identity;
|
|
1714
|
+
const wasWarm = this.initialized && this.variants !== null;
|
|
1715
|
+
const identityChanged = previousIdentity !== identity;
|
|
1716
|
+
this.identity = identity;
|
|
1717
|
+
if (!wasWarm || identityChanged) {
|
|
1718
|
+
if (identityChanged && this.definitionsFetchInFlight) {
|
|
1719
|
+
await this.definitionsFetchInFlight.catch(() => void 0);
|
|
1720
|
+
}
|
|
1721
|
+
await this.fetchFlags();
|
|
1722
|
+
this.initialized = true;
|
|
1723
|
+
if (!wasWarm) {
|
|
1724
|
+
this.startWebSocket();
|
|
1725
|
+
if (identity) {
|
|
1726
|
+
await this.executeBeforeIdentify(identity);
|
|
1727
|
+
await this.executeAfterIdentify(identity);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
this.flags = this.snapshotFlags({ identity });
|
|
1732
|
+
if (wasWarm && !identityChanged) {
|
|
1733
|
+
this.logger.debug("Client already initialized; re-snapshotted for identity.");
|
|
1734
|
+
}
|
|
1735
|
+
return { ...this.flags };
|
|
1736
|
+
};
|
|
1737
|
+
const result = this.variantsInitChain.then(run, run);
|
|
1738
|
+
this.variantsInitChain = result.then(
|
|
1739
|
+
() => void 0,
|
|
1740
|
+
() => void 0
|
|
1741
|
+
);
|
|
1742
|
+
return result;
|
|
1743
|
+
}
|
|
1744
|
+
async initWithLocalDefinitions(identity) {
|
|
1665
1745
|
const wasWarm = this.initialized && this.definitions.size > 0;
|
|
1666
1746
|
if (!wasWarm) {
|
|
1667
1747
|
if (!this.definitionsLoadPromise) {
|
|
@@ -1705,38 +1785,98 @@ var TogglyServerClient = class {
|
|
|
1705
1785
|
this.definitionsFetchInFlight = null;
|
|
1706
1786
|
}
|
|
1707
1787
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1788
|
+
applyNoAppKeyDefaults() {
|
|
1789
|
+
this.logger.debug("No appKey, using featureDefaults.");
|
|
1790
|
+
this.definitions = /* @__PURE__ */ new Map();
|
|
1791
|
+
this.variants = this.config.enableVariants ? {} : null;
|
|
1792
|
+
this.flags = this.config.featureDefaults ?? {};
|
|
1793
|
+
return this.flags;
|
|
1794
|
+
}
|
|
1795
|
+
applyParsedDefinitionsBody(parsed, useVariants) {
|
|
1796
|
+
if (useVariants) {
|
|
1797
|
+
this.variants = parseVariantDefsPayload(parsed);
|
|
1711
1798
|
this.definitions = /* @__PURE__ */ new Map();
|
|
1799
|
+
this.flags = {
|
|
1800
|
+
...this.config.featureDefaults,
|
|
1801
|
+
...variantDefsToFlags(this.variants)
|
|
1802
|
+
};
|
|
1803
|
+
this.logger.debug(`Fetched ${Object.keys(this.variants).length} variant defs.`);
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1806
|
+
this.variants = null;
|
|
1807
|
+
this.definitions = togglyEval.parseDefinitionsPayload(parsed);
|
|
1808
|
+
this.flags = {
|
|
1809
|
+
...this.config.featureDefaults,
|
|
1810
|
+
...togglyEval.snapshotEvaluatedBooleans(this.definitions, this.buildEvalContext())
|
|
1811
|
+
};
|
|
1812
|
+
this.logger.debug(`Fetched ${this.definitions.size} definitions.`);
|
|
1813
|
+
}
|
|
1814
|
+
hasLastKnownGoodDefinitions(useVariants) {
|
|
1815
|
+
if (this.definitions.size > 0) {
|
|
1816
|
+
return true;
|
|
1817
|
+
}
|
|
1818
|
+
return Boolean(useVariants && this.variants && Object.keys(this.variants).length > 0);
|
|
1819
|
+
}
|
|
1820
|
+
buildDefinitionsFetchUrl(useVariants, pin) {
|
|
1821
|
+
const identityContext = useVariants && this.identity ? { identity: this.identity } : void 0;
|
|
1822
|
+
return appendDefinitionsRevisionParam(
|
|
1823
|
+
buildDefinitionsUrl(this.config, identityContext),
|
|
1824
|
+
pin
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1827
|
+
noteNotModifiedResponse(responseRevision) {
|
|
1828
|
+
if (responseRevision) {
|
|
1829
|
+
this.cacheDefinitionsRevision(responseRevision);
|
|
1830
|
+
}
|
|
1831
|
+
this.noteDefinitionCacheHit();
|
|
1832
|
+
this.logger.debug("Definitions unchanged (304)");
|
|
1833
|
+
return this.flags;
|
|
1834
|
+
}
|
|
1835
|
+
noteAppliedDefinitionsRevision(previousRevision, responseRevision) {
|
|
1836
|
+
if (responseRevision) {
|
|
1837
|
+
this.cacheDefinitionsRevision(responseRevision);
|
|
1838
|
+
}
|
|
1839
|
+
if (this.definitionsRevisionsMatch(previousRevision, responseRevision)) {
|
|
1840
|
+
this.noteDefinitionCacheHit();
|
|
1841
|
+
} else {
|
|
1842
|
+
this.noteDefinitionCacheMiss();
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
recoverDefinitionsFetchError(error, outcomeRecorded, useVariants) {
|
|
1846
|
+
this.logger.warn(
|
|
1847
|
+
"Failed to fetch flags, preserving last-known-good flags when available.",
|
|
1848
|
+
error
|
|
1849
|
+
);
|
|
1850
|
+
this.config.onError?.("Error fetching feature flags", error);
|
|
1851
|
+
if (!outcomeRecorded && this.hasLastKnownGoodDefinitions(useVariants)) {
|
|
1852
|
+
this.noteDefinitionCacheHit();
|
|
1853
|
+
}
|
|
1854
|
+
if (Object.keys(this.flags).length === 0) {
|
|
1712
1855
|
this.flags = this.config.featureDefaults ?? {};
|
|
1713
|
-
|
|
1856
|
+
}
|
|
1857
|
+
return this.flags;
|
|
1858
|
+
}
|
|
1859
|
+
async performDefinitionsFetch() {
|
|
1860
|
+
if (!this.config.appKey) {
|
|
1861
|
+
return this.applyNoAppKeyDefaults();
|
|
1714
1862
|
}
|
|
1715
1863
|
let outcomeRecorded = false;
|
|
1864
|
+
const useVariants = this.config.enableVariants === true;
|
|
1716
1865
|
try {
|
|
1717
1866
|
const pin = this.pendingDefinitionsPin;
|
|
1718
1867
|
this.pendingDefinitionsPin = null;
|
|
1719
|
-
const url =
|
|
1720
|
-
buildDefinitionsUrl(this.config),
|
|
1721
|
-
pin
|
|
1722
|
-
);
|
|
1868
|
+
const url = this.buildDefinitionsFetchUrl(useVariants, pin);
|
|
1723
1869
|
this.logger.debug(`Fetching definitions from: ${url}`);
|
|
1724
1870
|
const previousRevision = pin ? null : this.getDefinitionsRevision();
|
|
1725
|
-
const
|
|
1726
|
-
|
|
1727
|
-
);
|
|
1871
|
+
const conditionalHeaders = previousRevision ? { "If-None-Match": previousRevision } : void 0;
|
|
1872
|
+
const headers = buildDefinitionFetchHeaders(conditionalHeaders);
|
|
1728
1873
|
const response = await fetchWithTimeout(url, { headers }, this.config.timeout);
|
|
1729
1874
|
const responseRevision = this.normalizeDefinitionsRevision(
|
|
1730
1875
|
extractDefinitionsRevision(response)
|
|
1731
1876
|
);
|
|
1732
1877
|
if (response.status === 304) {
|
|
1733
|
-
if (responseRevision) {
|
|
1734
|
-
this.cacheDefinitionsRevision(responseRevision);
|
|
1735
|
-
}
|
|
1736
|
-
this.noteDefinitionCacheHit();
|
|
1737
1878
|
outcomeRecorded = true;
|
|
1738
|
-
this.
|
|
1739
|
-
return this.flags;
|
|
1879
|
+
return this.noteNotModifiedResponse(responseRevision);
|
|
1740
1880
|
}
|
|
1741
1881
|
if (!response.ok) {
|
|
1742
1882
|
throw new TogglyNetworkError(
|
|
@@ -1749,35 +1889,15 @@ var TogglyServerClient = class {
|
|
|
1749
1889
|
baseUrl: this.config.baseUrl ?? "https://definitions.toggly.io",
|
|
1750
1890
|
allowedKeyIds: this.config.allowedKeyIds,
|
|
1751
1891
|
maxSignatureAgeSeconds: this.config.maxSignatureAgeSeconds,
|
|
1752
|
-
headers: buildDefinitionFetchHeaders(
|
|
1892
|
+
headers: buildDefinitionFetchHeaders()
|
|
1753
1893
|
});
|
|
1754
|
-
this.
|
|
1755
|
-
this.
|
|
1756
|
-
...this.config.featureDefaults ?? {},
|
|
1757
|
-
...togglyEval.snapshotEvaluatedBooleans(this.definitions, this.buildEvalContext())
|
|
1758
|
-
};
|
|
1759
|
-
if (responseRevision) {
|
|
1760
|
-
this.cacheDefinitionsRevision(responseRevision);
|
|
1761
|
-
}
|
|
1762
|
-
this.logger.debug(`Fetched ${this.definitions.size} definitions.`);
|
|
1763
|
-
if (this.definitionsRevisionsMatch(previousRevision, responseRevision)) {
|
|
1764
|
-
this.noteDefinitionCacheHit();
|
|
1765
|
-
} else {
|
|
1766
|
-
this.noteDefinitionCacheMiss();
|
|
1767
|
-
}
|
|
1894
|
+
this.applyParsedDefinitionsBody(parsed, useVariants);
|
|
1895
|
+
this.noteAppliedDefinitionsRevision(previousRevision, responseRevision);
|
|
1768
1896
|
outcomeRecorded = true;
|
|
1769
1897
|
await this.executeAfterRefresh(this.flags);
|
|
1770
1898
|
return this.flags;
|
|
1771
1899
|
} catch (error) {
|
|
1772
|
-
this.
|
|
1773
|
-
this.config.onError?.("Error fetching feature flags", error);
|
|
1774
|
-
if (!outcomeRecorded && this.definitions.size > 0) {
|
|
1775
|
-
this.noteDefinitionCacheHit();
|
|
1776
|
-
}
|
|
1777
|
-
if (Object.keys(this.flags).length === 0) {
|
|
1778
|
-
this.flags = this.config.featureDefaults ?? {};
|
|
1779
|
-
}
|
|
1780
|
-
return this.flags;
|
|
1900
|
+
return this.recoverDefinitionsFetchError(error, outcomeRecorded, useVariants);
|
|
1781
1901
|
}
|
|
1782
1902
|
}
|
|
1783
1903
|
/**
|
|
@@ -1786,6 +1906,34 @@ var TogglyServerClient = class {
|
|
|
1786
1906
|
getFlags() {
|
|
1787
1907
|
return { ...this.flags };
|
|
1788
1908
|
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Current variant assignment for a feature (requires `enableVariants`).
|
|
1911
|
+
* Soft-null when disabled, the feature is off/local-gated, or no variant is assigned.
|
|
1912
|
+
*/
|
|
1913
|
+
getVariant(featureKey) {
|
|
1914
|
+
if (!this.config.enableVariants) return null;
|
|
1915
|
+
const entry = this.variants?.[featureKey];
|
|
1916
|
+
const enabled = togglyLocalGates.applyLocalGate(
|
|
1917
|
+
entry?.enabled === true,
|
|
1918
|
+
featureKey,
|
|
1919
|
+
this.localGates,
|
|
1920
|
+
this.localGateIndex
|
|
1921
|
+
);
|
|
1922
|
+
this.recordCheckForKey(
|
|
1923
|
+
featureKey,
|
|
1924
|
+
enabled,
|
|
1925
|
+
void 0,
|
|
1926
|
+
enabled && entry?.variant ? entry.variant : "disabled"
|
|
1927
|
+
);
|
|
1928
|
+
if (!enabled || !entry?.variant) return null;
|
|
1929
|
+
return { name: entry.variant, configurationValue: entry.configurationValue };
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Configuration payload for the assigned variant, if any.
|
|
1933
|
+
*/
|
|
1934
|
+
getVariantValue(featureKey, isT) {
|
|
1935
|
+
return decodeVariantValue(this.getVariant(featureKey)?.configurationValue, isT);
|
|
1936
|
+
}
|
|
1789
1937
|
/**
|
|
1790
1938
|
* Check if a feature is enabled
|
|
1791
1939
|
*/
|