flagmint-js-sdk 2.2.2 → 2.2.3-rc.1
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 +1 -1
- package/dist/flagmint.cjs.js +125 -25
- package/dist/flagmint.cjs.js.map +1 -1
- package/dist/flagmint.es.js +125 -25
- package/dist/flagmint.es.js.map +1 -1
- package/dist/flagmint.umd.js +125 -25
- package/dist/flagmint.umd.js.map +1 -1
- package/dist/sdk/core/config-sync/index.d.ts +1 -1
- package/dist/sdk/core/config-sync/piiHash.d.ts +46 -0
- package/dist/sdk/core/config-sync/rulesStore.d.ts +7 -1
- package/dist/sdk/core/config-sync/types.d.ts +5 -0
- package/dist/sdk/core/helpers/cacheHelper.d.ts +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,7 +133,7 @@ After `ready()`:
|
|
|
133
133
|
| `transportMode` | `'auto' \| 'sse' \| 'long-polling'` | `'auto'` | `auto` tries SSE, then long-polling |
|
|
134
134
|
| `EventSourceImpl` | EventSource | browser global | Required in Node |
|
|
135
135
|
| `shareConnection` | boolean | `true` in browsers with `BroadcastChannel`, `false` in Node | One stream per API key across same-origin documents |
|
|
136
|
-
| `enableOfflineCache` | boolean | `true` |
|
|
136
|
+
| `enableOfflineCache` | boolean | `true` | Server-eval: load/save evaluated flags. Config-sync: load/save **rules** only (evaluated maps are not cached — they are per-user and would collide across tabs). |
|
|
137
137
|
| `persistContext` | boolean | `false` | Persist context in the cache adapter |
|
|
138
138
|
| `cacheAdapter` | CacheAdapter | localStorage helpers | Custom cache (Redis, files, …) |
|
|
139
139
|
| `restEndpoint` | string | env default | Override the long-polling / evaluate URL |
|
package/dist/flagmint.cjs.js
CHANGED
|
@@ -38592,12 +38592,27 @@ function parseRulesSnapshot$1(raw) {
|
|
|
38592
38592
|
if (!parsed || typeof parsed.version !== "number" || typeof parsed.expiresAt !== "number" || !Array.isArray(parsed.flags) || typeof parsed.segments !== "object" || parsed.segments === null) {
|
|
38593
38593
|
return null;
|
|
38594
38594
|
}
|
|
38595
|
-
|
|
38595
|
+
const snapshot = {
|
|
38596
38596
|
version: parsed.version,
|
|
38597
38597
|
expiresAt: parsed.expiresAt,
|
|
38598
38598
|
flags: parsed.flags,
|
|
38599
38599
|
segments: parsed.segments || {}
|
|
38600
38600
|
};
|
|
38601
|
+
if (Array.isArray(parsed.piiAttributes)) {
|
|
38602
|
+
snapshot.piiAttributes = parsed.piiAttributes.filter(
|
|
38603
|
+
(attr) => typeof attr === "string"
|
|
38604
|
+
);
|
|
38605
|
+
}
|
|
38606
|
+
if (typeof parsed.piiSalt === "string") {
|
|
38607
|
+
snapshot.piiSalt = parsed.piiSalt;
|
|
38608
|
+
}
|
|
38609
|
+
if (typeof parsed.piiValueEncoding === "string") {
|
|
38610
|
+
snapshot.piiValueEncoding = parsed.piiValueEncoding;
|
|
38611
|
+
}
|
|
38612
|
+
if (parsed.piiWireBroken === true) {
|
|
38613
|
+
snapshot.piiWireBroken = true;
|
|
38614
|
+
}
|
|
38615
|
+
return snapshot;
|
|
38601
38616
|
} catch {
|
|
38602
38617
|
return null;
|
|
38603
38618
|
}
|
|
@@ -38621,6 +38636,12 @@ function saveCachedFlags$1(apiKey, data) {
|
|
|
38621
38636
|
} catch {
|
|
38622
38637
|
}
|
|
38623
38638
|
}
|
|
38639
|
+
function clearCachedFlags(apiKey) {
|
|
38640
|
+
try {
|
|
38641
|
+
storage$1.removeItem?.(getFlagsKey$1(apiKey));
|
|
38642
|
+
} catch {
|
|
38643
|
+
}
|
|
38644
|
+
}
|
|
38624
38645
|
function loadCachedContext$1(apiKey) {
|
|
38625
38646
|
try {
|
|
38626
38647
|
const raw = storage$1.getItem(getContextKey$1(apiKey));
|
|
@@ -38659,6 +38680,7 @@ function clearCachedRulesSnapshot$1(apiKey) {
|
|
|
38659
38680
|
}
|
|
38660
38681
|
const cacheHelper = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
38661
38682
|
__proto__: null,
|
|
38683
|
+
clearCachedFlags,
|
|
38662
38684
|
clearCachedRulesSnapshot: clearCachedRulesSnapshot$1,
|
|
38663
38685
|
loadCachedContext: loadCachedContext$1,
|
|
38664
38686
|
loadCachedFlags: loadCachedFlags$1,
|
|
@@ -39093,7 +39115,7 @@ class SseTransport {
|
|
|
39093
39115
|
this.connectPromise = new Promise((resolve, reject) => {
|
|
39094
39116
|
this.connectResolve = resolve;
|
|
39095
39117
|
this.connectReject = reject;
|
|
39096
|
-
const sdkVersion = "2.2.
|
|
39118
|
+
const sdkVersion = "2.2.3-rc.1";
|
|
39097
39119
|
const platform2 = typeof window !== "undefined" ? "browser" : "nodejs";
|
|
39098
39120
|
const wrapperName = this.configOptions?.wrapper?.name || "native-js";
|
|
39099
39121
|
const wrapperVersion = this.configOptions?.wrapper?.version || "none";
|
|
@@ -41905,22 +41927,80 @@ function payloadHasExplicitPiiFields(payload) {
|
|
|
41905
41927
|
return false;
|
|
41906
41928
|
return payload.piiSalt !== void 0 || payload.piiAttributes !== void 0 || payload.piiValueEncoding !== void 0;
|
|
41907
41929
|
}
|
|
41930
|
+
function piiAttributeKeysFromPayload(payload) {
|
|
41931
|
+
if (!payload || !Array.isArray(payload.piiAttributes))
|
|
41932
|
+
return [];
|
|
41933
|
+
return payload.piiAttributes.filter((k) => typeof k === "string" && k.length > 0);
|
|
41934
|
+
}
|
|
41935
|
+
function hasMalformedPiiAttributes(payload) {
|
|
41936
|
+
if (!payload || payload.piiAttributes === void 0)
|
|
41937
|
+
return false;
|
|
41938
|
+
if (!Array.isArray(payload.piiAttributes))
|
|
41939
|
+
return true;
|
|
41940
|
+
return payload.piiAttributes.some(
|
|
41941
|
+
(entry) => typeof entry !== "string" || entry.length === 0
|
|
41942
|
+
);
|
|
41943
|
+
}
|
|
41944
|
+
function hasExplicitInvalidPiiEncoding(payload) {
|
|
41945
|
+
if (!payload || payload.piiValueEncoding === void 0)
|
|
41946
|
+
return false;
|
|
41947
|
+
return payload.piiValueEncoding !== PII_VALUE_ENCODING;
|
|
41948
|
+
}
|
|
41949
|
+
function payloadRequiresUsablePiiWire(payload) {
|
|
41950
|
+
if (!payload)
|
|
41951
|
+
return false;
|
|
41952
|
+
if (hasExplicitInvalidPiiEncoding(payload))
|
|
41953
|
+
return true;
|
|
41954
|
+
if (hasMalformedPiiAttributes(payload))
|
|
41955
|
+
return true;
|
|
41956
|
+
if (piiAttributeKeysFromPayload(payload).length > 0)
|
|
41957
|
+
return true;
|
|
41958
|
+
return false;
|
|
41959
|
+
}
|
|
41960
|
+
function isExplicitPiiRequirementCleared(payload) {
|
|
41961
|
+
if (!payload || payload.piiAttributes === void 0)
|
|
41962
|
+
return false;
|
|
41963
|
+
if (!Array.isArray(payload.piiAttributes))
|
|
41964
|
+
return false;
|
|
41965
|
+
if (payload.piiAttributes.length !== 0)
|
|
41966
|
+
return false;
|
|
41967
|
+
if (hasExplicitInvalidPiiEncoding(payload))
|
|
41968
|
+
return false;
|
|
41969
|
+
return true;
|
|
41970
|
+
}
|
|
41908
41971
|
function piiWireOptionsFromPayload(payload) {
|
|
41909
41972
|
if (!payload)
|
|
41910
41973
|
return null;
|
|
41974
|
+
if (hasExplicitInvalidPiiEncoding(payload))
|
|
41975
|
+
return null;
|
|
41976
|
+
if (hasMalformedPiiAttributes(payload))
|
|
41977
|
+
return null;
|
|
41911
41978
|
const salt = typeof payload.piiSalt === "string" ? payload.piiSalt : "";
|
|
41912
41979
|
if (!salt)
|
|
41913
41980
|
return null;
|
|
41914
|
-
const
|
|
41915
|
-
if (encoding !== PII_VALUE_ENCODING)
|
|
41916
|
-
return null;
|
|
41917
|
-
const attrs = Array.isArray(payload.piiAttributes) ? payload.piiAttributes.filter((k) => typeof k === "string") : [];
|
|
41981
|
+
const attrs = piiAttributeKeysFromPayload(payload);
|
|
41918
41982
|
return {
|
|
41919
41983
|
piiAttributes: attrs,
|
|
41920
41984
|
piiSalt: salt,
|
|
41921
41985
|
piiValueEncoding: PII_VALUE_ENCODING
|
|
41922
41986
|
};
|
|
41923
41987
|
}
|
|
41988
|
+
function isPiiWireBroken(payload, resolved, previousPriorHadPiiAttrs = false) {
|
|
41989
|
+
if (hasExplicitInvalidPiiEncoding(payload) || hasMalformedPiiAttributes(payload)) {
|
|
41990
|
+
return true;
|
|
41991
|
+
}
|
|
41992
|
+
if (resolved) {
|
|
41993
|
+
return false;
|
|
41994
|
+
}
|
|
41995
|
+
if (isExplicitPiiRequirementCleared(payload))
|
|
41996
|
+
return false;
|
|
41997
|
+
if (payloadRequiresUsablePiiWire(payload))
|
|
41998
|
+
return true;
|
|
41999
|
+
if (previousPriorHadPiiAttrs && payloadHasExplicitPiiFields(payload)) {
|
|
42000
|
+
return true;
|
|
42001
|
+
}
|
|
42002
|
+
return false;
|
|
42003
|
+
}
|
|
41924
42004
|
function resolvePiiWireForDelta(previous, action) {
|
|
41925
42005
|
if (!payloadHasExplicitPiiFields(action)) {
|
|
41926
42006
|
return previous;
|
|
@@ -41973,7 +42053,8 @@ function createEmptyRulesState() {
|
|
|
41973
42053
|
segments: /* @__PURE__ */ new Map(),
|
|
41974
42054
|
ready: false,
|
|
41975
42055
|
needsFullConfig: true,
|
|
41976
|
-
piiWire: null
|
|
42056
|
+
piiWire: null,
|
|
42057
|
+
piiWireBroken: false
|
|
41977
42058
|
};
|
|
41978
42059
|
}
|
|
41979
42060
|
function cloneState(state) {
|
|
@@ -41988,7 +42069,8 @@ function cloneState(state) {
|
|
|
41988
42069
|
piiAttributes: [...state.piiWire.piiAttributes],
|
|
41989
42070
|
piiSalt: state.piiWire.piiSalt,
|
|
41990
42071
|
piiValueEncoding: state.piiWire.piiValueEncoding
|
|
41991
|
-
} : null
|
|
42072
|
+
} : null,
|
|
42073
|
+
piiWireBroken: state.piiWireBroken
|
|
41992
42074
|
};
|
|
41993
42075
|
}
|
|
41994
42076
|
function segmentsFromRecord(record) {
|
|
@@ -42015,14 +42097,18 @@ function hydrateFromFull(action) {
|
|
|
42015
42097
|
if (flag?.key)
|
|
42016
42098
|
flags.set(flag.key, flag);
|
|
42017
42099
|
}
|
|
42100
|
+
const piiWire = piiWireOptionsFromPayload(action);
|
|
42101
|
+
const piiWireBroken = isPiiWireBroken(action, piiWire, false);
|
|
42018
42102
|
return {
|
|
42019
42103
|
version: action.version,
|
|
42020
42104
|
expiresAt: action.expiresAt,
|
|
42021
42105
|
flags,
|
|
42022
42106
|
segments: segmentsFromRecord(action.segments),
|
|
42023
|
-
|
|
42024
|
-
|
|
42025
|
-
|
|
42107
|
+
// Usable rules, but PII metadata must be valid when hashing is required.
|
|
42108
|
+
ready: !piiWireBroken,
|
|
42109
|
+
needsFullConfig: piiWireBroken,
|
|
42110
|
+
piiWire,
|
|
42111
|
+
piiWireBroken
|
|
42026
42112
|
};
|
|
42027
42113
|
}
|
|
42028
42114
|
function applyPatch(state, action) {
|
|
@@ -42035,14 +42121,19 @@ function applyPatch(state, action) {
|
|
|
42035
42121
|
flags.set(flag.key, flag);
|
|
42036
42122
|
}
|
|
42037
42123
|
const nextPii = resolvePiiWireForDelta(state.piiWire, action);
|
|
42124
|
+
const priorHadPiiAttrs = (state.piiWire?.piiAttributes.length ?? 0) > 0 || state.piiWireBroken;
|
|
42125
|
+
const computedBroken = isPiiWireBroken(action, nextPii, priorHadPiiAttrs);
|
|
42126
|
+
const recoveredWithRequiredAttrs = !!nextPii && nextPii.piiAttributes.length > 0;
|
|
42127
|
+
const piiWireBroken = isExplicitPiiRequirementCleared(action) ? false : recoveredWithRequiredAttrs ? false : computedBroken || state.piiWireBroken;
|
|
42038
42128
|
return {
|
|
42039
42129
|
version: action.toVersion,
|
|
42040
42130
|
expiresAt: typeof action.expiresAt === "number" ? action.expiresAt : state.expiresAt,
|
|
42041
42131
|
flags,
|
|
42042
42132
|
segments: applySegmentUpserts(state.segments, action.segments),
|
|
42043
|
-
ready:
|
|
42044
|
-
needsFullConfig:
|
|
42045
|
-
piiWire: nextPii
|
|
42133
|
+
ready: !piiWireBroken,
|
|
42134
|
+
needsFullConfig: piiWireBroken,
|
|
42135
|
+
piiWire: nextPii,
|
|
42136
|
+
piiWireBroken
|
|
42046
42137
|
};
|
|
42047
42138
|
}
|
|
42048
42139
|
function reduceRules(state, action, now = Date.now()) {
|
|
@@ -42062,8 +42153,8 @@ function reduceRules(state, action, now = Date.now()) {
|
|
|
42062
42153
|
...cloneState(state),
|
|
42063
42154
|
version: state.version,
|
|
42064
42155
|
expiresAt: action.expiresAt,
|
|
42065
|
-
ready:
|
|
42066
|
-
needsFullConfig: state.flags.size === 0 || versionMismatch ? true : state.needsFullConfig
|
|
42156
|
+
ready: !state.piiWireBroken,
|
|
42157
|
+
needsFullConfig: state.piiWireBroken || state.flags.size === 0 || versionMismatch ? true : state.needsFullConfig
|
|
42067
42158
|
}
|
|
42068
42159
|
};
|
|
42069
42160
|
}
|
|
@@ -42119,8 +42210,8 @@ function reduceRules(state, action, now = Date.now()) {
|
|
|
42119
42210
|
...current,
|
|
42120
42211
|
version: action.toVersion,
|
|
42121
42212
|
expiresAt: typeof action.expiresAt === "number" ? action.expiresAt : current.expiresAt,
|
|
42122
|
-
ready:
|
|
42123
|
-
needsFullConfig:
|
|
42213
|
+
ready: !current.piiWireBroken,
|
|
42214
|
+
needsFullConfig: current.piiWireBroken
|
|
42124
42215
|
}
|
|
42125
42216
|
};
|
|
42126
42217
|
}
|
|
@@ -42143,9 +42234,12 @@ class RulesStore {
|
|
|
42143
42234
|
return this.state;
|
|
42144
42235
|
}
|
|
42145
42236
|
/**
|
|
42146
|
-
* Fail-closed readiness: lease not expired
|
|
42237
|
+
* Fail-closed readiness: lease not expired, store marked ready, and PII
|
|
42238
|
+
* wire metadata usable when hashing is required.
|
|
42147
42239
|
*/
|
|
42148
42240
|
isReady(now = Date.now()) {
|
|
42241
|
+
if (this.state.piiWireBroken)
|
|
42242
|
+
return false;
|
|
42149
42243
|
if (!this.state.ready)
|
|
42150
42244
|
return false;
|
|
42151
42245
|
if (!this.state.expiresAt)
|
|
@@ -42229,7 +42323,9 @@ class RulesStore {
|
|
|
42229
42323
|
piiAttributes: [...pii.piiAttributes],
|
|
42230
42324
|
piiSalt: pii.piiSalt,
|
|
42231
42325
|
piiValueEncoding: pii.piiValueEncoding
|
|
42232
|
-
} : {}
|
|
42326
|
+
} : {},
|
|
42327
|
+
// Persist fail-closed even when piiWire is null (unusable salt/encoding).
|
|
42328
|
+
...this.state.piiWireBroken ? { piiWireBroken: true } : {}
|
|
42233
42329
|
};
|
|
42234
42330
|
}
|
|
42235
42331
|
hydrateFromSnapshot(snapshot, now = Date.now()) {
|
|
@@ -42239,14 +42335,17 @@ class RulesStore {
|
|
|
42239
42335
|
flags.set(flag.key, flag);
|
|
42240
42336
|
}
|
|
42241
42337
|
const expired = isConfigPayloadExpired(snapshot.expiresAt, now);
|
|
42338
|
+
const piiWire = piiWireOptionsFromPayload(snapshot);
|
|
42339
|
+
const piiWireBroken = snapshot.piiWireBroken === true || isPiiWireBroken(snapshot, piiWire, false);
|
|
42242
42340
|
this.state = {
|
|
42243
42341
|
version: snapshot.version,
|
|
42244
42342
|
expiresAt: snapshot.expiresAt,
|
|
42245
42343
|
flags,
|
|
42246
42344
|
segments: segmentsFromRecord(snapshot.segments),
|
|
42247
|
-
ready: !expired && flags.size > 0,
|
|
42248
|
-
needsFullConfig: expired || flags.size === 0,
|
|
42249
|
-
piiWire
|
|
42345
|
+
ready: !expired && flags.size > 0 && !piiWireBroken,
|
|
42346
|
+
needsFullConfig: expired || flags.size === 0 || piiWireBroken,
|
|
42347
|
+
piiWire,
|
|
42348
|
+
piiWireBroken
|
|
42250
42349
|
};
|
|
42251
42350
|
}
|
|
42252
42351
|
}
|
|
@@ -43217,9 +43316,10 @@ class FlagClient {
|
|
|
43217
43316
|
}
|
|
43218
43317
|
let hydratedRules = false;
|
|
43219
43318
|
if (this.configSync && this.rulesStore && this.enableOfflineCache) {
|
|
43319
|
+
clearCachedFlags(this.apiKey);
|
|
43220
43320
|
hydratedRules = await this.hydrateRulesFromCache();
|
|
43221
43321
|
}
|
|
43222
|
-
if (this.enableOfflineCache && !hydratedRules) {
|
|
43322
|
+
if (this.enableOfflineCache && !this.configSync && !hydratedRules) {
|
|
43223
43323
|
const cached = await Promise.resolve(
|
|
43224
43324
|
this.cacheAdapter.loadFlags(this.apiKey, this.cacheTTL)
|
|
43225
43325
|
);
|
|
@@ -43461,7 +43561,7 @@ class FlagClient {
|
|
|
43461
43561
|
*/
|
|
43462
43562
|
updateFlags(newFlags, fromShare = false) {
|
|
43463
43563
|
this.flags = newFlags;
|
|
43464
|
-
if (this.enableOfflineCache) {
|
|
43564
|
+
if (this.enableOfflineCache && !this.configSync) {
|
|
43465
43565
|
void Promise.resolve(
|
|
43466
43566
|
this.cacheAdapter.saveFlags(this.apiKey, newFlags)
|
|
43467
43567
|
);
|