flagmint-js-sdk 2.2.1 → 2.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 +1 -1
- package/dist/flagmint.cjs.js +140 -21
- package/dist/flagmint.cjs.js.map +1 -1
- package/dist/flagmint.es.js +140 -21
- package/dist/flagmint.es.js.map +1 -1
- package/dist/flagmint.umd.js +140 -21
- package/dist/flagmint.umd.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/sdk/core/config-sync/evaluateSdkFlag.d.ts +24 -1
- package/dist/sdk/core/config-sync/index.d.ts +4 -2
- package/dist/sdk/core/config-sync/piiHash.d.ts +87 -0
- package/dist/sdk/core/config-sync/rulesStore.d.ts +3 -0
- package/dist/sdk/core/config-sync/types.d.ts +12 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ transportMode: 'long-polling' // POST evaluate on an interval
|
|
|
162
162
|
|
|
163
163
|
# Connection sharing
|
|
164
164
|
|
|
165
|
-
Used when the same app is embedded twice on one origin (for example two
|
|
165
|
+
Used when the same app is embedded twice on one origin (for example two iframes in a host page).
|
|
166
166
|
|
|
167
167
|
- **Leader** performs handshake and holds EventSource.
|
|
168
168
|
- **Followers** receive flag snapshots over `BroadcastChannel` (`flagmint-share:<apiKey>`). Leadership is a `localStorage` lock (`flagmint_share_lock:<apiKey>`).
|
package/dist/flagmint.cjs.js
CHANGED
|
@@ -39093,7 +39093,7 @@ class SseTransport {
|
|
|
39093
39093
|
this.connectPromise = new Promise((resolve, reject) => {
|
|
39094
39094
|
this.connectResolve = resolve;
|
|
39095
39095
|
this.connectReject = reject;
|
|
39096
|
-
const sdkVersion = "2.2.
|
|
39096
|
+
const sdkVersion = "2.2.2";
|
|
39097
39097
|
const platform2 = typeof window !== "undefined" ? "browser" : "nodejs";
|
|
39098
39098
|
const wrapperName = this.configOptions?.wrapper?.name || "native-js";
|
|
39099
39099
|
const wrapperVersion = this.configOptions?.wrapper?.version || "none";
|
|
@@ -41779,7 +41779,7 @@ function expand2(hash3, prk, info, length = 32) {
|
|
|
41779
41779
|
const hkdf = (hash3, ikm, salt, info, length) => expand2(hash3, extract(hash3, ikm, salt), info, length);
|
|
41780
41780
|
const ASL_HKDF_INFO = "flagmint-asl-config-sync-mac-v1";
|
|
41781
41781
|
const ASL_KEY_AGREEMENT = "x25519-hkdf-sha256";
|
|
41782
|
-
const textEncoder$
|
|
41782
|
+
const textEncoder$2 = new TextEncoder();
|
|
41783
41783
|
function generateAslClientKeyPair() {
|
|
41784
41784
|
const privateKey = x25519.utils.randomSecretKey();
|
|
41785
41785
|
const publicKey = x25519.getPublicKey(privateKey);
|
|
@@ -41810,7 +41810,7 @@ function deriveAslMacKey(input) {
|
|
|
41810
41810
|
sha256,
|
|
41811
41811
|
shared,
|
|
41812
41812
|
salt,
|
|
41813
|
-
textEncoder$
|
|
41813
|
+
textEncoder$2.encode(ASL_HKDF_INFO),
|
|
41814
41814
|
32
|
|
41815
41815
|
);
|
|
41816
41816
|
return { configMacKey };
|
|
@@ -41820,7 +41820,7 @@ function wipeKeyMaterial(key2) {
|
|
|
41820
41820
|
return;
|
|
41821
41821
|
key2.fill(0);
|
|
41822
41822
|
}
|
|
41823
|
-
const textEncoder = new TextEncoder();
|
|
41823
|
+
const textEncoder$1 = new TextEncoder();
|
|
41824
41824
|
function canonicalizeForSigning(value) {
|
|
41825
41825
|
return JSON.stringify(sortValue(value));
|
|
41826
41826
|
}
|
|
@@ -41837,12 +41837,12 @@ function sortValue(value) {
|
|
|
41837
41837
|
return sorted;
|
|
41838
41838
|
}
|
|
41839
41839
|
function normalizeMacKey(secret2) {
|
|
41840
|
-
return typeof secret2 === "string" ? textEncoder.encode(secret2) : secret2;
|
|
41840
|
+
return typeof secret2 === "string" ? textEncoder$1.encode(secret2) : secret2;
|
|
41841
41841
|
}
|
|
41842
41842
|
function signConfigPayload(body, secret2) {
|
|
41843
41843
|
const { signature: _ignored, ...unsigned } = body;
|
|
41844
41844
|
const canonical = canonicalizeForSigning(unsigned);
|
|
41845
|
-
const mac = hmac2(sha256, normalizeMacKey(secret2), textEncoder.encode(canonical));
|
|
41845
|
+
const mac = hmac2(sha256, normalizeMacKey(secret2), textEncoder$1.encode(canonical));
|
|
41846
41846
|
return bytesToHex$1(mac);
|
|
41847
41847
|
}
|
|
41848
41848
|
function timingSafeEqualBytes(a, b) {
|
|
@@ -41864,13 +41864,107 @@ function verifyConfigPayloadSignature(body, secret2) {
|
|
|
41864
41864
|
} catch {
|
|
41865
41865
|
return false;
|
|
41866
41866
|
}
|
|
41867
|
-
const a = textEncoder.encode(body.signature);
|
|
41868
|
-
const b = textEncoder.encode(expected);
|
|
41867
|
+
const a = textEncoder$1.encode(body.signature);
|
|
41868
|
+
const b = textEncoder$1.encode(expected);
|
|
41869
41869
|
return timingSafeEqualBytes(a, b);
|
|
41870
41870
|
}
|
|
41871
41871
|
function isConfigPayloadExpired(expiresAt, now = Date.now()) {
|
|
41872
41872
|
return !Number.isFinite(expiresAt) || now >= expiresAt;
|
|
41873
41873
|
}
|
|
41874
|
+
const PII_VALUE_ENCODING = "hmac-v1";
|
|
41875
|
+
const textEncoder = new TextEncoder();
|
|
41876
|
+
function normalizePiiString(value) {
|
|
41877
|
+
return value.trim().toLowerCase();
|
|
41878
|
+
}
|
|
41879
|
+
function hashPiiValue(value, salt) {
|
|
41880
|
+
const mac = hmac2(
|
|
41881
|
+
sha256,
|
|
41882
|
+
textEncoder.encode(salt),
|
|
41883
|
+
textEncoder.encode(normalizePiiString(value))
|
|
41884
|
+
);
|
|
41885
|
+
return bytesToHex$1(mac);
|
|
41886
|
+
}
|
|
41887
|
+
function isPiiAttribute(attribute, piiAttributes) {
|
|
41888
|
+
if (!attribute || piiAttributes.length === 0)
|
|
41889
|
+
return false;
|
|
41890
|
+
const attr = attribute.trim();
|
|
41891
|
+
if (!attr)
|
|
41892
|
+
return false;
|
|
41893
|
+
for (const key2 of piiAttributes) {
|
|
41894
|
+
if (!key2)
|
|
41895
|
+
continue;
|
|
41896
|
+
if (attr === key2)
|
|
41897
|
+
return true;
|
|
41898
|
+
if (attr.endsWith(`.${key2}`))
|
|
41899
|
+
return true;
|
|
41900
|
+
}
|
|
41901
|
+
return false;
|
|
41902
|
+
}
|
|
41903
|
+
function payloadHasExplicitPiiFields(payload) {
|
|
41904
|
+
if (!payload)
|
|
41905
|
+
return false;
|
|
41906
|
+
return payload.piiSalt !== void 0 || payload.piiAttributes !== void 0 || payload.piiValueEncoding !== void 0;
|
|
41907
|
+
}
|
|
41908
|
+
function piiWireOptionsFromPayload(payload) {
|
|
41909
|
+
if (!payload)
|
|
41910
|
+
return null;
|
|
41911
|
+
const salt = typeof payload.piiSalt === "string" ? payload.piiSalt : "";
|
|
41912
|
+
if (!salt)
|
|
41913
|
+
return null;
|
|
41914
|
+
const encoding = typeof payload.piiValueEncoding === "string" ? payload.piiValueEncoding : PII_VALUE_ENCODING;
|
|
41915
|
+
if (encoding !== PII_VALUE_ENCODING)
|
|
41916
|
+
return null;
|
|
41917
|
+
const attrs = Array.isArray(payload.piiAttributes) ? payload.piiAttributes.filter((k) => typeof k === "string") : [];
|
|
41918
|
+
return {
|
|
41919
|
+
piiAttributes: attrs,
|
|
41920
|
+
piiSalt: salt,
|
|
41921
|
+
piiValueEncoding: PII_VALUE_ENCODING
|
|
41922
|
+
};
|
|
41923
|
+
}
|
|
41924
|
+
function resolvePiiWireForDelta(previous, action) {
|
|
41925
|
+
if (!payloadHasExplicitPiiFields(action)) {
|
|
41926
|
+
return previous;
|
|
41927
|
+
}
|
|
41928
|
+
const merged = {
|
|
41929
|
+
piiSalt: action.piiSalt !== void 0 ? action.piiSalt : previous?.piiSalt,
|
|
41930
|
+
piiAttributes: action.piiAttributes !== void 0 ? action.piiAttributes : previous?.piiAttributes,
|
|
41931
|
+
piiValueEncoding: action.piiValueEncoding !== void 0 ? action.piiValueEncoding : previous?.piiValueEncoding
|
|
41932
|
+
};
|
|
41933
|
+
return piiWireOptionsFromPayload(merged);
|
|
41934
|
+
}
|
|
41935
|
+
function applyPiiHashToFlatContext(context, options, mode) {
|
|
41936
|
+
if (!options || !options.piiSalt || !options.piiAttributes.length || options.piiValueEncoding !== PII_VALUE_ENCODING) {
|
|
41937
|
+
return context;
|
|
41938
|
+
}
|
|
41939
|
+
if (mode?.alreadyHashed) {
|
|
41940
|
+
return context;
|
|
41941
|
+
}
|
|
41942
|
+
let changed = false;
|
|
41943
|
+
const out = { ...context };
|
|
41944
|
+
for (const [key2, value] of Object.entries(context)) {
|
|
41945
|
+
if (!isPiiAttribute(key2, options.piiAttributes))
|
|
41946
|
+
continue;
|
|
41947
|
+
if (typeof value === "string") {
|
|
41948
|
+
out[key2] = hashPiiValue(value, options.piiSalt);
|
|
41949
|
+
changed = true;
|
|
41950
|
+
continue;
|
|
41951
|
+
}
|
|
41952
|
+
if (Array.isArray(value)) {
|
|
41953
|
+
let arrayChanged = false;
|
|
41954
|
+
const next = value.map((entry) => {
|
|
41955
|
+
if (typeof entry !== "string")
|
|
41956
|
+
return entry;
|
|
41957
|
+
arrayChanged = true;
|
|
41958
|
+
return hashPiiValue(entry, options.piiSalt);
|
|
41959
|
+
});
|
|
41960
|
+
if (arrayChanged) {
|
|
41961
|
+
out[key2] = next;
|
|
41962
|
+
changed = true;
|
|
41963
|
+
}
|
|
41964
|
+
}
|
|
41965
|
+
}
|
|
41966
|
+
return changed ? out : context;
|
|
41967
|
+
}
|
|
41874
41968
|
function createEmptyRulesState() {
|
|
41875
41969
|
return {
|
|
41876
41970
|
version: 0,
|
|
@@ -41878,7 +41972,8 @@ function createEmptyRulesState() {
|
|
|
41878
41972
|
flags: /* @__PURE__ */ new Map(),
|
|
41879
41973
|
segments: /* @__PURE__ */ new Map(),
|
|
41880
41974
|
ready: false,
|
|
41881
|
-
needsFullConfig: true
|
|
41975
|
+
needsFullConfig: true,
|
|
41976
|
+
piiWire: null
|
|
41882
41977
|
};
|
|
41883
41978
|
}
|
|
41884
41979
|
function cloneState(state) {
|
|
@@ -41888,7 +41983,12 @@ function cloneState(state) {
|
|
|
41888
41983
|
flags: new Map(state.flags),
|
|
41889
41984
|
segments: new Map(state.segments),
|
|
41890
41985
|
ready: state.ready,
|
|
41891
|
-
needsFullConfig: state.needsFullConfig
|
|
41986
|
+
needsFullConfig: state.needsFullConfig,
|
|
41987
|
+
piiWire: state.piiWire ? {
|
|
41988
|
+
piiAttributes: [...state.piiWire.piiAttributes],
|
|
41989
|
+
piiSalt: state.piiWire.piiSalt,
|
|
41990
|
+
piiValueEncoding: state.piiWire.piiValueEncoding
|
|
41991
|
+
} : null
|
|
41892
41992
|
};
|
|
41893
41993
|
}
|
|
41894
41994
|
function segmentsFromRecord(record) {
|
|
@@ -41921,7 +42021,8 @@ function hydrateFromFull(action) {
|
|
|
41921
42021
|
flags,
|
|
41922
42022
|
segments: segmentsFromRecord(action.segments),
|
|
41923
42023
|
ready: true,
|
|
41924
|
-
needsFullConfig: false
|
|
42024
|
+
needsFullConfig: false,
|
|
42025
|
+
piiWire: piiWireOptionsFromPayload(action)
|
|
41925
42026
|
};
|
|
41926
42027
|
}
|
|
41927
42028
|
function applyPatch(state, action) {
|
|
@@ -41933,13 +42034,15 @@ function applyPatch(state, action) {
|
|
|
41933
42034
|
if (flag?.key)
|
|
41934
42035
|
flags.set(flag.key, flag);
|
|
41935
42036
|
}
|
|
42037
|
+
const nextPii = resolvePiiWireForDelta(state.piiWire, action);
|
|
41936
42038
|
return {
|
|
41937
42039
|
version: action.toVersion,
|
|
41938
42040
|
expiresAt: typeof action.expiresAt === "number" ? action.expiresAt : state.expiresAt,
|
|
41939
42041
|
flags,
|
|
41940
42042
|
segments: applySegmentUpserts(state.segments, action.segments),
|
|
41941
42043
|
ready: true,
|
|
41942
|
-
needsFullConfig: false
|
|
42044
|
+
needsFullConfig: false,
|
|
42045
|
+
piiWire: nextPii
|
|
41943
42046
|
};
|
|
41944
42047
|
}
|
|
41945
42048
|
function reduceRules(state, action, now = Date.now()) {
|
|
@@ -42116,11 +42219,17 @@ class RulesStore {
|
|
|
42116
42219
|
this.state.segments.forEach((segment, id) => {
|
|
42117
42220
|
segments[id] = segment;
|
|
42118
42221
|
});
|
|
42222
|
+
const pii = this.state.piiWire;
|
|
42119
42223
|
return {
|
|
42120
42224
|
version: this.state.version,
|
|
42121
42225
|
expiresAt: this.state.expiresAt,
|
|
42122
42226
|
flags: Array.from(this.state.flags.values()),
|
|
42123
|
-
segments
|
|
42227
|
+
segments,
|
|
42228
|
+
...pii ? {
|
|
42229
|
+
piiAttributes: [...pii.piiAttributes],
|
|
42230
|
+
piiSalt: pii.piiSalt,
|
|
42231
|
+
piiValueEncoding: pii.piiValueEncoding
|
|
42232
|
+
} : {}
|
|
42124
42233
|
};
|
|
42125
42234
|
}
|
|
42126
42235
|
hydrateFromSnapshot(snapshot, now = Date.now()) {
|
|
@@ -42136,7 +42245,8 @@ class RulesStore {
|
|
|
42136
42245
|
flags,
|
|
42137
42246
|
segments: segmentsFromRecord(snapshot.segments),
|
|
42138
42247
|
ready: !expired && flags.size > 0,
|
|
42139
|
-
needsFullConfig: expired || flags.size === 0
|
|
42248
|
+
needsFullConfig: expired || flags.size === 0,
|
|
42249
|
+
piiWire: piiWireOptionsFromPayload(snapshot)
|
|
42140
42250
|
};
|
|
42141
42251
|
}
|
|
42142
42252
|
}
|
|
@@ -42338,7 +42448,7 @@ function flattenEvaluationContext(context) {
|
|
|
42338
42448
|
function toComparableString(value) {
|
|
42339
42449
|
if (value === null || value === void 0)
|
|
42340
42450
|
return "";
|
|
42341
|
-
return String(value);
|
|
42451
|
+
return String(value).trim().toLowerCase();
|
|
42342
42452
|
}
|
|
42343
42453
|
function toComparableArray(value) {
|
|
42344
42454
|
if (Array.isArray(value)) {
|
|
@@ -42900,8 +43010,13 @@ function prepareContextForEvaluator(context) {
|
|
|
42900
43010
|
}
|
|
42901
43011
|
return context;
|
|
42902
43012
|
}
|
|
42903
|
-
function
|
|
42904
|
-
const
|
|
43013
|
+
function prepareContextForLocalEval(context, pii) {
|
|
43014
|
+
const prepared = prepareContextForEvaluator(context);
|
|
43015
|
+
const flat = flattenEvaluationContext(prepared);
|
|
43016
|
+
return applyPiiHashToFlatContext(flat, pii);
|
|
43017
|
+
}
|
|
43018
|
+
function evaluateSdkFlag(flag, context, segments, pii) {
|
|
43019
|
+
const evalContext = prepareContextForLocalEval(context, pii);
|
|
42905
43020
|
const variationsById = {};
|
|
42906
43021
|
for (const v of flag.variations || []) {
|
|
42907
43022
|
if (v?.id)
|
|
@@ -42961,7 +43076,7 @@ function evaluateSdkFlag(flag, context, segments) {
|
|
|
42961
43076
|
}
|
|
42962
43077
|
function evaluateAllSdkFlags(flags, context, segments, options) {
|
|
42963
43078
|
const list = flags instanceof Map ? Array.from(flags.values()) : flags;
|
|
42964
|
-
const prepared =
|
|
43079
|
+
const prepared = prepareContextForLocalEval(context, options?.pii);
|
|
42965
43080
|
const out = {};
|
|
42966
43081
|
for (const flag of list) {
|
|
42967
43082
|
if (!flag?.key)
|
|
@@ -42970,7 +43085,7 @@ function evaluateAllSdkFlags(flags, context, segments, options) {
|
|
|
42970
43085
|
out[flag.key] = coerceType(flag.default_value, flag.type);
|
|
42971
43086
|
continue;
|
|
42972
43087
|
}
|
|
42973
|
-
out[flag.key] = evaluateSdkFlag(flag, prepared, segments);
|
|
43088
|
+
out[flag.key] = evaluateSdkFlag(flag, prepared, segments, null);
|
|
42974
43089
|
}
|
|
42975
43090
|
return out;
|
|
42976
43091
|
}
|
|
@@ -43415,7 +43530,8 @@ class FlagClient {
|
|
|
43415
43530
|
value = evaluateSdkFlag(
|
|
43416
43531
|
flag,
|
|
43417
43532
|
this.context,
|
|
43418
|
-
this.rulesStore.getState().segments
|
|
43533
|
+
this.rulesStore.getState().segments,
|
|
43534
|
+
this.rulesStore.getState().piiWire
|
|
43419
43535
|
) ?? fallback;
|
|
43420
43536
|
}
|
|
43421
43537
|
} else {
|
|
@@ -43895,7 +44011,7 @@ class FlagClient {
|
|
|
43895
44011
|
state.flags,
|
|
43896
44012
|
context ?? this.context,
|
|
43897
44013
|
state.segments,
|
|
43898
|
-
{ failClosedDefaultsOnly: failClosed }
|
|
44014
|
+
{ failClosedDefaultsOnly: failClosed, pii: state.piiWire }
|
|
43899
44015
|
);
|
|
43900
44016
|
}
|
|
43901
44017
|
analyticsMapFromRulesStore() {
|
|
@@ -44024,6 +44140,7 @@ exports.FlagClient = FlagClient;
|
|
|
44024
44140
|
exports.LongPollingTransport = LongPollingTransport;
|
|
44025
44141
|
exports.RulesStore = RulesStore;
|
|
44026
44142
|
exports.SseTransport = SseTransport;
|
|
44143
|
+
exports.applyPiiHashToFlatContext = applyPiiHashToFlatContext;
|
|
44027
44144
|
exports.applyRolloutStrategy = applyRolloutStrategy;
|
|
44028
44145
|
exports.asyncCache = cacheHelper_async;
|
|
44029
44146
|
exports.canonicalizeForSigning = canonicalizeForSigning;
|
|
@@ -44041,11 +44158,13 @@ exports.evaluateSdkFlag = evaluateSdkFlag;
|
|
|
44041
44158
|
exports.flattenEvaluationContext = flattenEvaluationContext;
|
|
44042
44159
|
exports.generateAslClientKeyPair = generateAslClientKeyPair;
|
|
44043
44160
|
exports.hashPercent = hashPercent;
|
|
44161
|
+
exports.hashPiiValue = hashPiiValue;
|
|
44044
44162
|
exports.hashToPercentage = hashToPercentage;
|
|
44045
44163
|
exports.isConfigPayloadExpired = isConfigPayloadExpired;
|
|
44046
44164
|
exports.isInSegment = isInSegment;
|
|
44047
44165
|
exports.performAslHandshake = performAslHandshake;
|
|
44048
44166
|
exports.pickVariant = pickVariant;
|
|
44167
|
+
exports.prepareContextForLocalEval = prepareContextForLocalEval;
|
|
44049
44168
|
exports.reduceRules = reduceRules;
|
|
44050
44169
|
exports.signConfigPayload = signConfigPayload;
|
|
44051
44170
|
exports.stringHash = stringHash;
|