flagmint-js-sdk 2.2.2 → 2.2.3-rc-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 +20 -6
- package/dist/flagmint.cjs.js +297 -42
- package/dist/flagmint.cjs.js.map +1 -1
- package/dist/flagmint.es.js +297 -42
- package/dist/flagmint.es.js.map +1 -1
- package/dist/flagmint.umd.js +297 -42
- package/dist/flagmint.umd.js.map +1 -1
- package/dist/sdk/core/client.d.ts +17 -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/connectionShare.d.ts +73 -22
- package/package.json +1 -1
package/dist/flagmint.umd.js
CHANGED
|
@@ -39095,7 +39095,7 @@ var __publicField = (obj, key, value) => {
|
|
|
39095
39095
|
this.connectPromise = new Promise((resolve, reject) => {
|
|
39096
39096
|
this.connectResolve = resolve;
|
|
39097
39097
|
this.connectReject = reject;
|
|
39098
|
-
const sdkVersion = "2.2.2";
|
|
39098
|
+
const sdkVersion = "2.2.3-rc-2";
|
|
39099
39099
|
const platform2 = typeof window !== "undefined" ? "browser" : "nodejs";
|
|
39100
39100
|
const wrapperName = this.configOptions?.wrapper?.name || "native-js";
|
|
39101
39101
|
const wrapperVersion = this.configOptions?.wrapper?.version || "none";
|
|
@@ -39364,11 +39364,15 @@ var __publicField = (obj, key, value) => {
|
|
|
39364
39364
|
this.watchId = null;
|
|
39365
39365
|
this.closed = false;
|
|
39366
39366
|
this.latestFlags = null;
|
|
39367
|
+
this.latestRules = null;
|
|
39367
39368
|
this.flagsWaiters = [];
|
|
39369
|
+
this.rulesWaiters = [];
|
|
39368
39370
|
this.flagsTimeouts = [];
|
|
39371
|
+
this.rulesTimeouts = [];
|
|
39369
39372
|
this.contextWaiters = /* @__PURE__ */ new Map();
|
|
39370
|
-
this.
|
|
39371
|
-
this.
|
|
39373
|
+
this.mode = options.mode ?? "evaluated";
|
|
39374
|
+
this.lockKey = this.mode === "rules" ? `flagmint_share_rules_lock:${apiKey}` : `flagmint_share_lock:${apiKey}`;
|
|
39375
|
+
this.channelName = this.mode === "rules" ? `flagmint-share-rules:${apiKey}` : `flagmint-share:${apiKey}`;
|
|
39372
39376
|
this.now = options.now ?? (() => Date.now());
|
|
39373
39377
|
this.storage = options.storage ?? (typeof localStorage !== "undefined" ? localStorage : null);
|
|
39374
39378
|
this.channelFactory = options.channelFactory;
|
|
@@ -39380,6 +39384,10 @@ var __publicField = (obj, key, value) => {
|
|
|
39380
39384
|
get currentRole() {
|
|
39381
39385
|
return this.role;
|
|
39382
39386
|
}
|
|
39387
|
+
/** `evaluated` (server eval) or `rules` (config sync). */
|
|
39388
|
+
get shareMode() {
|
|
39389
|
+
return this.mode;
|
|
39390
|
+
}
|
|
39383
39391
|
/**
|
|
39384
39392
|
* Join the share group: compete for the lock, subscribe to the channel.
|
|
39385
39393
|
*
|
|
@@ -39423,16 +39431,57 @@ var __publicField = (obj, key, value) => {
|
|
|
39423
39431
|
setLeaderContextHandler(handler) {
|
|
39424
39432
|
this.leaderContextHandler = handler;
|
|
39425
39433
|
}
|
|
39434
|
+
/**
|
|
39435
|
+
* Rules-mode follower: hydrate local rules from the leader snapshot and
|
|
39436
|
+
* return flags evaluated for **this** document's context.
|
|
39437
|
+
*
|
|
39438
|
+
* @param handler Apply snapshot + local eval → flag map
|
|
39439
|
+
*/
|
|
39440
|
+
setFollowerRulesApply(handler) {
|
|
39441
|
+
this.followerRulesApply = handler;
|
|
39442
|
+
}
|
|
39443
|
+
/**
|
|
39444
|
+
* Rules-mode follower: re-evaluate after local `updateContext` (no RPC).
|
|
39445
|
+
*
|
|
39446
|
+
* @param handler Context → flag map using this tab's rules store
|
|
39447
|
+
*/
|
|
39448
|
+
setFollowerLocalEval(handler) {
|
|
39449
|
+
this.followerLocalEval = handler;
|
|
39450
|
+
}
|
|
39426
39451
|
/**
|
|
39427
39452
|
* Leader-only: publish the current evaluated flags to all followers.
|
|
39428
|
-
* No-op on followers (avoids echo loops).
|
|
39453
|
+
* No-op on followers (avoids echo loops). Evaluated mode only.
|
|
39454
|
+
*
|
|
39455
|
+
* @param flags Evaluated flag map from the leader stream
|
|
39429
39456
|
*/
|
|
39430
39457
|
broadcastFlags(flags) {
|
|
39458
|
+
if (this.mode !== "evaluated")
|
|
39459
|
+
return;
|
|
39431
39460
|
if (this.role !== "leader" || !this.channel)
|
|
39432
39461
|
return;
|
|
39433
39462
|
this.latestFlags = flags;
|
|
39434
39463
|
this.post({ type: "flags", memberId: this.memberId, flags });
|
|
39435
39464
|
}
|
|
39465
|
+
/**
|
|
39466
|
+
* Leader-only (rules mode): publish a verified rules snapshot.
|
|
39467
|
+
* Followers hydrate and evaluate with their own context.
|
|
39468
|
+
*
|
|
39469
|
+
* @param snapshot Rules store snapshot (MAC already verified on the leader)
|
|
39470
|
+
* @param analytics Optional per-flag analytics map from rules
|
|
39471
|
+
*/
|
|
39472
|
+
broadcastRules(snapshot, analytics) {
|
|
39473
|
+
if (this.mode !== "rules")
|
|
39474
|
+
return;
|
|
39475
|
+
if (this.role !== "leader" || !this.channel)
|
|
39476
|
+
return;
|
|
39477
|
+
this.latestRules = { snapshot, analytics };
|
|
39478
|
+
this.post({
|
|
39479
|
+
type: "rules",
|
|
39480
|
+
memberId: this.memberId,
|
|
39481
|
+
snapshot,
|
|
39482
|
+
analytics
|
|
39483
|
+
});
|
|
39484
|
+
}
|
|
39436
39485
|
/** Leader-only: mirror a transport `onError` (auth, quota) to followers. */
|
|
39437
39486
|
broadcastError(error) {
|
|
39438
39487
|
if (this.role !== "leader" || !this.channel)
|
|
@@ -39446,11 +39495,34 @@ var __publicField = (obj, key, value) => {
|
|
|
39446
39495
|
});
|
|
39447
39496
|
}
|
|
39448
39497
|
/**
|
|
39449
|
-
* Transport used by follower FlagClients.
|
|
39450
|
-
*
|
|
39498
|
+
* Transport used by follower FlagClients.
|
|
39499
|
+
* - Evaluated: `init` waits for flags; `fetchFlags` RPCs through the leader.
|
|
39500
|
+
* - Rules: `init` waits for a rules snapshot; `fetchFlags` evaluates locally.
|
|
39451
39501
|
* `destroy` is a no-op — {@link ConnectionShareHub.destroy} owns teardown.
|
|
39452
39502
|
*/
|
|
39453
39503
|
createFollowerTransport() {
|
|
39504
|
+
if (this.mode === "rules") {
|
|
39505
|
+
return {
|
|
39506
|
+
init: async () => {
|
|
39507
|
+
const payload = await this.waitForRules();
|
|
39508
|
+
const flags = this.followerRulesApply?.(payload) ?? {};
|
|
39509
|
+
this.followerFlagsCallback?.(flags);
|
|
39510
|
+
},
|
|
39511
|
+
fetchFlags: async (context) => {
|
|
39512
|
+
if (!this.followerLocalEval) {
|
|
39513
|
+
throw new Error("Rules-share follower has no local eval handler.");
|
|
39514
|
+
}
|
|
39515
|
+
return this.followerLocalEval(context);
|
|
39516
|
+
},
|
|
39517
|
+
destroy: () => void 0,
|
|
39518
|
+
onFlagsUpdated: (callback) => {
|
|
39519
|
+
this.followerFlagsCallback = callback;
|
|
39520
|
+
},
|
|
39521
|
+
onError: (callback) => {
|
|
39522
|
+
this.followerErrorCallback = callback;
|
|
39523
|
+
}
|
|
39524
|
+
};
|
|
39525
|
+
}
|
|
39454
39526
|
return {
|
|
39455
39527
|
init: async () => {
|
|
39456
39528
|
const flags = await this.waitForFlags();
|
|
@@ -39493,10 +39565,15 @@ var __publicField = (obj, key, value) => {
|
|
|
39493
39565
|
setTimeout(() => channel.close(), 0);
|
|
39494
39566
|
}
|
|
39495
39567
|
this.flagsWaiters = [];
|
|
39568
|
+
this.rulesWaiters = [];
|
|
39496
39569
|
this.flagsTimeouts.splice(0).forEach(({ timeoutId, reject }) => {
|
|
39497
39570
|
clearTimeout(timeoutId);
|
|
39498
39571
|
reject(new Error("Connection share hub closed."));
|
|
39499
39572
|
});
|
|
39573
|
+
this.rulesTimeouts.splice(0).forEach(({ timeoutId, reject }) => {
|
|
39574
|
+
clearTimeout(timeoutId);
|
|
39575
|
+
reject(new Error("Connection share hub closed."));
|
|
39576
|
+
});
|
|
39500
39577
|
this.contextWaiters.forEach(({ reject }) => reject(new Error("Connection share hub closed.")));
|
|
39501
39578
|
this.contextWaiters.clear();
|
|
39502
39579
|
}
|
|
@@ -39593,7 +39670,16 @@ var __publicField = (obj, key, value) => {
|
|
|
39593
39670
|
return;
|
|
39594
39671
|
switch (message.type) {
|
|
39595
39672
|
case "hello":
|
|
39596
|
-
if (this.role
|
|
39673
|
+
if (this.role !== "leader")
|
|
39674
|
+
break;
|
|
39675
|
+
if (this.mode === "rules" && this.latestRules) {
|
|
39676
|
+
this.post({
|
|
39677
|
+
type: "rules",
|
|
39678
|
+
memberId: this.memberId,
|
|
39679
|
+
snapshot: this.latestRules.snapshot,
|
|
39680
|
+
analytics: this.latestRules.analytics
|
|
39681
|
+
});
|
|
39682
|
+
} else if (this.mode === "evaluated" && this.latestFlags) {
|
|
39597
39683
|
this.post({
|
|
39598
39684
|
type: "flags",
|
|
39599
39685
|
memberId: this.memberId,
|
|
@@ -39602,7 +39688,7 @@ var __publicField = (obj, key, value) => {
|
|
|
39602
39688
|
}
|
|
39603
39689
|
break;
|
|
39604
39690
|
case "flags":
|
|
39605
|
-
if (this.role === "leader")
|
|
39691
|
+
if (this.mode !== "evaluated" || this.role === "leader")
|
|
39606
39692
|
return;
|
|
39607
39693
|
if (!message.flags || typeof message.flags !== "object" || Array.isArray(message.flags)) {
|
|
39608
39694
|
return;
|
|
@@ -39611,6 +39697,22 @@ var __publicField = (obj, key, value) => {
|
|
|
39611
39697
|
this.flagsWaiters.splice(0).forEach((resolve) => resolve(message.flags));
|
|
39612
39698
|
this.followerFlagsCallback?.(message.flags);
|
|
39613
39699
|
break;
|
|
39700
|
+
case "rules":
|
|
39701
|
+
if (this.mode !== "rules" || this.role === "leader")
|
|
39702
|
+
return;
|
|
39703
|
+
if (!message.snapshot || typeof message.snapshot !== "object")
|
|
39704
|
+
return;
|
|
39705
|
+
{
|
|
39706
|
+
const payload = {
|
|
39707
|
+
snapshot: message.snapshot,
|
|
39708
|
+
analytics: message.analytics
|
|
39709
|
+
};
|
|
39710
|
+
this.latestRules = payload;
|
|
39711
|
+
this.rulesWaiters.splice(0).forEach((resolve) => resolve(payload));
|
|
39712
|
+
const flags = this.followerRulesApply?.(payload) ?? {};
|
|
39713
|
+
this.followerFlagsCallback?.(flags);
|
|
39714
|
+
}
|
|
39715
|
+
break;
|
|
39614
39716
|
case "error":
|
|
39615
39717
|
if (this.role === "leader")
|
|
39616
39718
|
return;
|
|
@@ -39623,7 +39725,7 @@ var __publicField = (obj, key, value) => {
|
|
|
39623
39725
|
}
|
|
39624
39726
|
break;
|
|
39625
39727
|
case "context-request":
|
|
39626
|
-
if (this.role !== "leader")
|
|
39728
|
+
if (this.mode !== "evaluated" || this.role !== "leader")
|
|
39627
39729
|
return;
|
|
39628
39730
|
void this.handleContextRequest(message);
|
|
39629
39731
|
break;
|
|
@@ -39708,6 +39810,31 @@ var __publicField = (obj, key, value) => {
|
|
|
39708
39810
|
this.flagsTimeouts.push({ timeoutId, reject });
|
|
39709
39811
|
});
|
|
39710
39812
|
}
|
|
39813
|
+
/**
|
|
39814
|
+
* Block until the leader has sent a `rules` snapshot, or {@link FOLLOWER_WAIT_MS}.
|
|
39815
|
+
*
|
|
39816
|
+
* @returns Latest rules payload from the leader
|
|
39817
|
+
*/
|
|
39818
|
+
waitForRules() {
|
|
39819
|
+
if (this.latestRules)
|
|
39820
|
+
return Promise.resolve(this.latestRules);
|
|
39821
|
+
if (this.closed)
|
|
39822
|
+
return Promise.reject(new Error("Connection share hub closed."));
|
|
39823
|
+
return new Promise((resolve, reject) => {
|
|
39824
|
+
const timeoutId = setTimeout(() => {
|
|
39825
|
+
this.rulesWaiters = this.rulesWaiters.filter((waiter) => waiter !== onRules);
|
|
39826
|
+
this.rulesTimeouts = this.rulesTimeouts.filter((entry) => entry.timeoutId !== timeoutId);
|
|
39827
|
+
reject(new Error("Timed out waiting for shared rules snapshot from the leader."));
|
|
39828
|
+
}, FOLLOWER_WAIT_MS);
|
|
39829
|
+
const onRules = (payload) => {
|
|
39830
|
+
clearTimeout(timeoutId);
|
|
39831
|
+
this.rulesTimeouts = this.rulesTimeouts.filter((entry) => entry.timeoutId !== timeoutId);
|
|
39832
|
+
resolve(payload);
|
|
39833
|
+
};
|
|
39834
|
+
this.rulesWaiters.push(onRules);
|
|
39835
|
+
this.rulesTimeouts.push({ timeoutId, reject });
|
|
39836
|
+
});
|
|
39837
|
+
}
|
|
39711
39838
|
/**
|
|
39712
39839
|
* Follower `fetchFlags`: ask the leader to POST `/context` and wait for
|
|
39713
39840
|
* `context-result`. Leaders run the handler locally.
|
|
@@ -41907,22 +42034,80 @@ var __publicField = (obj, key, value) => {
|
|
|
41907
42034
|
return false;
|
|
41908
42035
|
return payload.piiSalt !== void 0 || payload.piiAttributes !== void 0 || payload.piiValueEncoding !== void 0;
|
|
41909
42036
|
}
|
|
42037
|
+
function piiAttributeKeysFromPayload(payload) {
|
|
42038
|
+
if (!payload || !Array.isArray(payload.piiAttributes))
|
|
42039
|
+
return [];
|
|
42040
|
+
return payload.piiAttributes.filter((k) => typeof k === "string" && k.length > 0);
|
|
42041
|
+
}
|
|
42042
|
+
function hasMalformedPiiAttributes(payload) {
|
|
42043
|
+
if (!payload || payload.piiAttributes === void 0)
|
|
42044
|
+
return false;
|
|
42045
|
+
if (!Array.isArray(payload.piiAttributes))
|
|
42046
|
+
return true;
|
|
42047
|
+
return payload.piiAttributes.some(
|
|
42048
|
+
(entry) => typeof entry !== "string" || entry.length === 0
|
|
42049
|
+
);
|
|
42050
|
+
}
|
|
42051
|
+
function hasExplicitInvalidPiiEncoding(payload) {
|
|
42052
|
+
if (!payload || payload.piiValueEncoding === void 0)
|
|
42053
|
+
return false;
|
|
42054
|
+
return payload.piiValueEncoding !== PII_VALUE_ENCODING;
|
|
42055
|
+
}
|
|
42056
|
+
function payloadRequiresUsablePiiWire(payload) {
|
|
42057
|
+
if (!payload)
|
|
42058
|
+
return false;
|
|
42059
|
+
if (hasExplicitInvalidPiiEncoding(payload))
|
|
42060
|
+
return true;
|
|
42061
|
+
if (hasMalformedPiiAttributes(payload))
|
|
42062
|
+
return true;
|
|
42063
|
+
if (piiAttributeKeysFromPayload(payload).length > 0)
|
|
42064
|
+
return true;
|
|
42065
|
+
return false;
|
|
42066
|
+
}
|
|
42067
|
+
function isExplicitPiiRequirementCleared(payload) {
|
|
42068
|
+
if (!payload || payload.piiAttributes === void 0)
|
|
42069
|
+
return false;
|
|
42070
|
+
if (!Array.isArray(payload.piiAttributes))
|
|
42071
|
+
return false;
|
|
42072
|
+
if (payload.piiAttributes.length !== 0)
|
|
42073
|
+
return false;
|
|
42074
|
+
if (hasExplicitInvalidPiiEncoding(payload))
|
|
42075
|
+
return false;
|
|
42076
|
+
return true;
|
|
42077
|
+
}
|
|
41910
42078
|
function piiWireOptionsFromPayload(payload) {
|
|
41911
42079
|
if (!payload)
|
|
41912
42080
|
return null;
|
|
42081
|
+
if (hasExplicitInvalidPiiEncoding(payload))
|
|
42082
|
+
return null;
|
|
42083
|
+
if (hasMalformedPiiAttributes(payload))
|
|
42084
|
+
return null;
|
|
41913
42085
|
const salt = typeof payload.piiSalt === "string" ? payload.piiSalt : "";
|
|
41914
42086
|
if (!salt)
|
|
41915
42087
|
return null;
|
|
41916
|
-
const
|
|
41917
|
-
if (encoding !== PII_VALUE_ENCODING)
|
|
41918
|
-
return null;
|
|
41919
|
-
const attrs = Array.isArray(payload.piiAttributes) ? payload.piiAttributes.filter((k) => typeof k === "string") : [];
|
|
42088
|
+
const attrs = piiAttributeKeysFromPayload(payload);
|
|
41920
42089
|
return {
|
|
41921
42090
|
piiAttributes: attrs,
|
|
41922
42091
|
piiSalt: salt,
|
|
41923
42092
|
piiValueEncoding: PII_VALUE_ENCODING
|
|
41924
42093
|
};
|
|
41925
42094
|
}
|
|
42095
|
+
function isPiiWireBroken(payload, resolved, previousPriorHadPiiAttrs = false) {
|
|
42096
|
+
if (hasExplicitInvalidPiiEncoding(payload) || hasMalformedPiiAttributes(payload)) {
|
|
42097
|
+
return true;
|
|
42098
|
+
}
|
|
42099
|
+
if (resolved) {
|
|
42100
|
+
return false;
|
|
42101
|
+
}
|
|
42102
|
+
if (isExplicitPiiRequirementCleared(payload))
|
|
42103
|
+
return false;
|
|
42104
|
+
if (payloadRequiresUsablePiiWire(payload))
|
|
42105
|
+
return true;
|
|
42106
|
+
if (previousPriorHadPiiAttrs && payloadHasExplicitPiiFields(payload)) {
|
|
42107
|
+
return true;
|
|
42108
|
+
}
|
|
42109
|
+
return false;
|
|
42110
|
+
}
|
|
41926
42111
|
function resolvePiiWireForDelta(previous, action) {
|
|
41927
42112
|
if (!payloadHasExplicitPiiFields(action)) {
|
|
41928
42113
|
return previous;
|
|
@@ -41975,7 +42160,8 @@ var __publicField = (obj, key, value) => {
|
|
|
41975
42160
|
segments: /* @__PURE__ */ new Map(),
|
|
41976
42161
|
ready: false,
|
|
41977
42162
|
needsFullConfig: true,
|
|
41978
|
-
piiWire: null
|
|
42163
|
+
piiWire: null,
|
|
42164
|
+
piiWireBroken: false
|
|
41979
42165
|
};
|
|
41980
42166
|
}
|
|
41981
42167
|
function cloneState(state) {
|
|
@@ -41990,7 +42176,8 @@ var __publicField = (obj, key, value) => {
|
|
|
41990
42176
|
piiAttributes: [...state.piiWire.piiAttributes],
|
|
41991
42177
|
piiSalt: state.piiWire.piiSalt,
|
|
41992
42178
|
piiValueEncoding: state.piiWire.piiValueEncoding
|
|
41993
|
-
} : null
|
|
42179
|
+
} : null,
|
|
42180
|
+
piiWireBroken: state.piiWireBroken
|
|
41994
42181
|
};
|
|
41995
42182
|
}
|
|
41996
42183
|
function segmentsFromRecord(record) {
|
|
@@ -42017,14 +42204,18 @@ var __publicField = (obj, key, value) => {
|
|
|
42017
42204
|
if (flag?.key)
|
|
42018
42205
|
flags.set(flag.key, flag);
|
|
42019
42206
|
}
|
|
42207
|
+
const piiWire = piiWireOptionsFromPayload(action);
|
|
42208
|
+
const piiWireBroken = isPiiWireBroken(action, piiWire, false);
|
|
42020
42209
|
return {
|
|
42021
42210
|
version: action.version,
|
|
42022
42211
|
expiresAt: action.expiresAt,
|
|
42023
42212
|
flags,
|
|
42024
42213
|
segments: segmentsFromRecord(action.segments),
|
|
42025
|
-
|
|
42026
|
-
|
|
42027
|
-
|
|
42214
|
+
// Usable rules, but PII metadata must be valid when hashing is required.
|
|
42215
|
+
ready: !piiWireBroken,
|
|
42216
|
+
needsFullConfig: piiWireBroken,
|
|
42217
|
+
piiWire,
|
|
42218
|
+
piiWireBroken
|
|
42028
42219
|
};
|
|
42029
42220
|
}
|
|
42030
42221
|
function applyPatch(state, action) {
|
|
@@ -42037,14 +42228,19 @@ var __publicField = (obj, key, value) => {
|
|
|
42037
42228
|
flags.set(flag.key, flag);
|
|
42038
42229
|
}
|
|
42039
42230
|
const nextPii = resolvePiiWireForDelta(state.piiWire, action);
|
|
42231
|
+
const priorHadPiiAttrs = (state.piiWire?.piiAttributes.length ?? 0) > 0 || state.piiWireBroken;
|
|
42232
|
+
const computedBroken = isPiiWireBroken(action, nextPii, priorHadPiiAttrs);
|
|
42233
|
+
const recoveredWithRequiredAttrs = !!nextPii && nextPii.piiAttributes.length > 0;
|
|
42234
|
+
const piiWireBroken = isExplicitPiiRequirementCleared(action) ? false : recoveredWithRequiredAttrs ? false : computedBroken || state.piiWireBroken;
|
|
42040
42235
|
return {
|
|
42041
42236
|
version: action.toVersion,
|
|
42042
42237
|
expiresAt: typeof action.expiresAt === "number" ? action.expiresAt : state.expiresAt,
|
|
42043
42238
|
flags,
|
|
42044
42239
|
segments: applySegmentUpserts(state.segments, action.segments),
|
|
42045
|
-
ready:
|
|
42046
|
-
needsFullConfig:
|
|
42047
|
-
piiWire: nextPii
|
|
42240
|
+
ready: !piiWireBroken,
|
|
42241
|
+
needsFullConfig: piiWireBroken,
|
|
42242
|
+
piiWire: nextPii,
|
|
42243
|
+
piiWireBroken
|
|
42048
42244
|
};
|
|
42049
42245
|
}
|
|
42050
42246
|
function reduceRules(state, action, now = Date.now()) {
|
|
@@ -42064,8 +42260,8 @@ var __publicField = (obj, key, value) => {
|
|
|
42064
42260
|
...cloneState(state),
|
|
42065
42261
|
version: state.version,
|
|
42066
42262
|
expiresAt: action.expiresAt,
|
|
42067
|
-
ready:
|
|
42068
|
-
needsFullConfig: state.flags.size === 0 || versionMismatch ? true : state.needsFullConfig
|
|
42263
|
+
ready: !state.piiWireBroken,
|
|
42264
|
+
needsFullConfig: state.piiWireBroken || state.flags.size === 0 || versionMismatch ? true : state.needsFullConfig
|
|
42069
42265
|
}
|
|
42070
42266
|
};
|
|
42071
42267
|
}
|
|
@@ -42121,8 +42317,8 @@ var __publicField = (obj, key, value) => {
|
|
|
42121
42317
|
...current,
|
|
42122
42318
|
version: action.toVersion,
|
|
42123
42319
|
expiresAt: typeof action.expiresAt === "number" ? action.expiresAt : current.expiresAt,
|
|
42124
|
-
ready:
|
|
42125
|
-
needsFullConfig:
|
|
42320
|
+
ready: !current.piiWireBroken,
|
|
42321
|
+
needsFullConfig: current.piiWireBroken
|
|
42126
42322
|
}
|
|
42127
42323
|
};
|
|
42128
42324
|
}
|
|
@@ -42145,9 +42341,12 @@ var __publicField = (obj, key, value) => {
|
|
|
42145
42341
|
return this.state;
|
|
42146
42342
|
}
|
|
42147
42343
|
/**
|
|
42148
|
-
* Fail-closed readiness: lease not expired
|
|
42344
|
+
* Fail-closed readiness: lease not expired, store marked ready, and PII
|
|
42345
|
+
* wire metadata usable when hashing is required.
|
|
42149
42346
|
*/
|
|
42150
42347
|
isReady(now = Date.now()) {
|
|
42348
|
+
if (this.state.piiWireBroken)
|
|
42349
|
+
return false;
|
|
42151
42350
|
if (!this.state.ready)
|
|
42152
42351
|
return false;
|
|
42153
42352
|
if (!this.state.expiresAt)
|
|
@@ -42231,7 +42430,9 @@ var __publicField = (obj, key, value) => {
|
|
|
42231
42430
|
piiAttributes: [...pii.piiAttributes],
|
|
42232
42431
|
piiSalt: pii.piiSalt,
|
|
42233
42432
|
piiValueEncoding: pii.piiValueEncoding
|
|
42234
|
-
} : {}
|
|
42433
|
+
} : {},
|
|
42434
|
+
// Persist fail-closed even when piiWire is null (unusable salt/encoding).
|
|
42435
|
+
...this.state.piiWireBroken ? { piiWireBroken: true } : {}
|
|
42235
42436
|
};
|
|
42236
42437
|
}
|
|
42237
42438
|
hydrateFromSnapshot(snapshot, now = Date.now()) {
|
|
@@ -42241,14 +42442,17 @@ var __publicField = (obj, key, value) => {
|
|
|
42241
42442
|
flags.set(flag.key, flag);
|
|
42242
42443
|
}
|
|
42243
42444
|
const expired = isConfigPayloadExpired(snapshot.expiresAt, now);
|
|
42445
|
+
const piiWire = piiWireOptionsFromPayload(snapshot);
|
|
42446
|
+
const piiWireBroken = snapshot.piiWireBroken === true || isPiiWireBroken(snapshot, piiWire, false);
|
|
42244
42447
|
this.state = {
|
|
42245
42448
|
version: snapshot.version,
|
|
42246
42449
|
expiresAt: snapshot.expiresAt,
|
|
42247
42450
|
flags,
|
|
42248
42451
|
segments: segmentsFromRecord(snapshot.segments),
|
|
42249
|
-
ready: !expired && flags.size > 0,
|
|
42250
|
-
needsFullConfig: expired || flags.size === 0,
|
|
42251
|
-
piiWire
|
|
42452
|
+
ready: !expired && flags.size > 0 && !piiWireBroken,
|
|
42453
|
+
needsFullConfig: expired || flags.size === 0 || piiWireBroken,
|
|
42454
|
+
piiWire,
|
|
42455
|
+
piiWireBroken
|
|
42252
42456
|
};
|
|
42253
42457
|
}
|
|
42254
42458
|
}
|
|
@@ -43168,11 +43372,11 @@ var __publicField = (obj, key, value) => {
|
|
|
43168
43372
|
this.env = options.env;
|
|
43169
43373
|
this.enableFlagmint = options.enableFlagmint ?? true;
|
|
43170
43374
|
this.configSync = options.configSync ?? false;
|
|
43171
|
-
|
|
43375
|
+
logger.setup({ debugLog: options.debugLog });
|
|
43376
|
+
this.shareConnection = options.shareConnection ?? isConnectionSharingAvailable();
|
|
43172
43377
|
if (this.configSync) {
|
|
43173
43378
|
this.rulesStore = new RulesStore();
|
|
43174
43379
|
}
|
|
43175
|
-
logger.setup({ debugLog: options.debugLog });
|
|
43176
43380
|
if (this.previewMode && this.rawFlags && Object.keys(this.rawFlags).length > 0) {
|
|
43177
43381
|
this.flags = this.evaluateLocally(this.rawFlags, this.context);
|
|
43178
43382
|
this.readyPromise = Promise.resolve();
|
|
@@ -43236,7 +43440,7 @@ var __publicField = (obj, key, value) => {
|
|
|
43236
43440
|
return;
|
|
43237
43441
|
}
|
|
43238
43442
|
await this.setupTransport(options);
|
|
43239
|
-
this.
|
|
43443
|
+
this.broadcastShareSnapshot();
|
|
43240
43444
|
this.isInitialized = true;
|
|
43241
43445
|
this.resolveReady();
|
|
43242
43446
|
} catch (err) {
|
|
@@ -43276,19 +43480,26 @@ var __publicField = (obj, key, value) => {
|
|
|
43276
43480
|
* @returns true when this document is a follower and should skip opening a stream.
|
|
43277
43481
|
*/
|
|
43278
43482
|
async joinConnectionShare(options) {
|
|
43279
|
-
if (!this.shareConnection
|
|
43483
|
+
if (!this.shareConnection) {
|
|
43484
|
+
return false;
|
|
43485
|
+
}
|
|
43486
|
+
if (options.transport && !options.share) {
|
|
43280
43487
|
return false;
|
|
43281
43488
|
}
|
|
43282
43489
|
if (!options.share && !isConnectionSharingAvailable()) {
|
|
43283
43490
|
return false;
|
|
43284
43491
|
}
|
|
43285
|
-
|
|
43492
|
+
const shareMode = this.configSync ? "rules" : "evaluated";
|
|
43493
|
+
this.shareHub = new ConnectionShareHub(this.apiKey, {
|
|
43494
|
+
...options.share,
|
|
43495
|
+
mode: shareMode
|
|
43496
|
+
});
|
|
43286
43497
|
this.shareHub.onPromote(async () => {
|
|
43287
43498
|
logger.log("[FlagClient] Share leader departed. Promoting this document to hold the stream.");
|
|
43288
43499
|
try {
|
|
43289
43500
|
await this.setupTransport(options);
|
|
43290
43501
|
this.attachShareLeader();
|
|
43291
|
-
this.
|
|
43502
|
+
this.broadcastShareSnapshot();
|
|
43292
43503
|
} catch (err) {
|
|
43293
43504
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
43294
43505
|
logger.error("[FlagClient] Promotion to share leader failed:", error);
|
|
@@ -43297,7 +43508,12 @@ var __publicField = (obj, key, value) => {
|
|
|
43297
43508
|
});
|
|
43298
43509
|
const role = await this.shareHub.join();
|
|
43299
43510
|
if (role === "follower") {
|
|
43300
|
-
logger.log(
|
|
43511
|
+
logger.log(
|
|
43512
|
+
shareMode === "rules" ? "[FlagClient] Following shared config-sync rules from another same-origin document." : "[FlagClient] Following an existing Flagmint stream in another same-origin document."
|
|
43513
|
+
);
|
|
43514
|
+
if (shareMode === "rules") {
|
|
43515
|
+
this.attachRulesShareFollower();
|
|
43516
|
+
}
|
|
43301
43517
|
this.transport = this.shareHub.createFollowerTransport();
|
|
43302
43518
|
this.bindTransport(this.transport);
|
|
43303
43519
|
await this.transport.init();
|
|
@@ -43306,8 +43522,15 @@ var __publicField = (obj, key, value) => {
|
|
|
43306
43522
|
this.attachShareLeader();
|
|
43307
43523
|
return false;
|
|
43308
43524
|
}
|
|
43525
|
+
/**
|
|
43526
|
+
* Leader: wire context RPC for evaluated share only.
|
|
43527
|
+
* Rules share never forwards follower context onto the stream.
|
|
43528
|
+
*/
|
|
43309
43529
|
attachShareLeader() {
|
|
43310
|
-
this.shareHub
|
|
43530
|
+
if (!this.shareHub || this.shareHub.shareMode === "rules") {
|
|
43531
|
+
return;
|
|
43532
|
+
}
|
|
43533
|
+
this.shareHub.setLeaderContextHandler(async (context, options) => {
|
|
43311
43534
|
const mergedContext = toJsonCloneable({ ...this.context, ...context });
|
|
43312
43535
|
if (options?.persist === true) {
|
|
43313
43536
|
this.context = mergedContext;
|
|
@@ -43316,9 +43539,6 @@ var __publicField = (obj, key, value) => {
|
|
|
43316
43539
|
}
|
|
43317
43540
|
}
|
|
43318
43541
|
if (!this.transport || typeof this.transport.fetchFlags !== "function") {
|
|
43319
|
-
if (this.configSync && this.rulesStore) {
|
|
43320
|
-
return this.evaluateFromRulesStore(mergedContext);
|
|
43321
|
-
}
|
|
43322
43542
|
return this.flags;
|
|
43323
43543
|
}
|
|
43324
43544
|
return this.transport.fetchFlags(mergedContext, {
|
|
@@ -43326,6 +43546,41 @@ var __publicField = (obj, key, value) => {
|
|
|
43326
43546
|
});
|
|
43327
43547
|
});
|
|
43328
43548
|
}
|
|
43549
|
+
/**
|
|
43550
|
+
* Follower hooks for rules-only share: hydrate rules locally and evaluate
|
|
43551
|
+
* with this document's context (never the leader's).
|
|
43552
|
+
*/
|
|
43553
|
+
attachRulesShareFollower() {
|
|
43554
|
+
if (!this.shareHub || !this.rulesStore)
|
|
43555
|
+
return;
|
|
43556
|
+
this.shareHub.setFollowerRulesApply((payload) => {
|
|
43557
|
+
this.rulesStore.hydrateFromSnapshot(payload.snapshot);
|
|
43558
|
+
if (payload.analytics) {
|
|
43559
|
+
this.updateAnalytics(payload.analytics);
|
|
43560
|
+
}
|
|
43561
|
+
const flags = this.evaluateFromRulesStore();
|
|
43562
|
+
this.updateFlags(flags, true);
|
|
43563
|
+
return flags;
|
|
43564
|
+
});
|
|
43565
|
+
this.shareHub.setFollowerLocalEval((context) => {
|
|
43566
|
+
return this.evaluateFromRulesStore(context);
|
|
43567
|
+
});
|
|
43568
|
+
}
|
|
43569
|
+
/**
|
|
43570
|
+
* Leader: push the right snapshot type to followers after flags/rules change.
|
|
43571
|
+
*/
|
|
43572
|
+
broadcastShareSnapshot() {
|
|
43573
|
+
if (!this.shareHub || this.shareHub.currentRole !== "leader")
|
|
43574
|
+
return;
|
|
43575
|
+
if (this.shareHub.shareMode === "rules" && this.rulesStore) {
|
|
43576
|
+
const snapshot = this.rulesStore.toSnapshot();
|
|
43577
|
+
if (snapshot.flags.length === 0)
|
|
43578
|
+
return;
|
|
43579
|
+
this.shareHub.broadcastRules(snapshot, this.analyticsMapFromRulesStore());
|
|
43580
|
+
return;
|
|
43581
|
+
}
|
|
43582
|
+
this.shareHub.broadcastFlags(this.flags);
|
|
43583
|
+
}
|
|
43329
43584
|
/**
|
|
43330
43585
|
* Creates and initializes the transport layer used to receive feature flags
|
|
43331
43586
|
* updates from the Flagmint platform.
|
|
@@ -43470,7 +43725,7 @@ var __publicField = (obj, key, value) => {
|
|
|
43470
43725
|
}
|
|
43471
43726
|
this.notifySubscribers();
|
|
43472
43727
|
if (!fromShare) {
|
|
43473
|
-
this.
|
|
43728
|
+
this.broadcastShareSnapshot();
|
|
43474
43729
|
}
|
|
43475
43730
|
}
|
|
43476
43731
|
/**
|