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
|
@@ -16,7 +16,8 @@ export interface FlagClientOptions<C extends Record<string, any> = Record<string
|
|
|
16
16
|
* Performs X25519 ECDH on ASL handshake and derives a session MAC key used
|
|
17
17
|
* to verify signed `lease` / `fullConfig` / `delta` payloads.
|
|
18
18
|
* Default false — keeps the legacy evaluated-flags stream.
|
|
19
|
-
* When true
|
|
19
|
+
* When true and `shareConnection` is on, tabs share **rules** only (one SSE);
|
|
20
|
+
* each document keeps its own context and evaluates locally.
|
|
20
21
|
*/
|
|
21
22
|
configSync?: boolean;
|
|
22
23
|
/**
|
|
@@ -68,6 +69,8 @@ export interface FlagClientOptions<C extends Record<string, any> = Record<string
|
|
|
68
69
|
* leader and forward updateContext() to it. Do not enable this when two clients
|
|
69
70
|
* must evaluate different contexts at the same time on one page, or when the
|
|
70
71
|
* origin hosts untrusted documents that should not steer or read this stream.
|
|
72
|
+
*
|
|
73
|
+
* With `configSync`, sharing uses rules-only mode (not evaluated maps / context RPC).
|
|
71
74
|
*/
|
|
72
75
|
shareConnection?: boolean;
|
|
73
76
|
/**
|
|
@@ -133,7 +136,20 @@ export declare class FlagClient<T = unknown, C extends Record<string, any> = Rec
|
|
|
133
136
|
* @returns true when this document is a follower and should skip opening a stream.
|
|
134
137
|
*/
|
|
135
138
|
private joinConnectionShare;
|
|
139
|
+
/**
|
|
140
|
+
* Leader: wire context RPC for evaluated share only.
|
|
141
|
+
* Rules share never forwards follower context onto the stream.
|
|
142
|
+
*/
|
|
136
143
|
private attachShareLeader;
|
|
144
|
+
/**
|
|
145
|
+
* Follower hooks for rules-only share: hydrate rules locally and evaluate
|
|
146
|
+
* with this document's context (never the leader's).
|
|
147
|
+
*/
|
|
148
|
+
private attachRulesShareFollower;
|
|
149
|
+
/**
|
|
150
|
+
* Leader: push the right snapshot type to followers after flags/rules change.
|
|
151
|
+
*/
|
|
152
|
+
private broadcastShareSnapshot;
|
|
137
153
|
/**
|
|
138
154
|
* Creates and initializes the transport layer used to receive feature flags
|
|
139
155
|
* updates from the Flagmint platform.
|
|
@@ -7,7 +7,7 @@ export type { ConfigSyncPayload, DeltaConfigPayload, DeltasCatchUpPayload, FullC
|
|
|
7
7
|
export { performAslHandshake } from './handshake';
|
|
8
8
|
export type { AslHandshakeSuccess } from './handshake';
|
|
9
9
|
export { evaluateSdkFlag, evaluateAllSdkFlags, coerceType, prepareContextForEvaluator, prepareContextForLocalEval, mapToRecord, } from './evaluateSdkFlag';
|
|
10
|
-
export { PII_VALUE_ENCODING, normalizePiiString, hashPiiValue, isPiiAttribute, payloadHasExplicitPiiFields, piiWireOptionsFromPayload, resolvePiiWireForDelta, applyPiiHashToFlatContext, } from './piiHash';
|
|
10
|
+
export { PII_VALUE_ENCODING, normalizePiiString, hashPiiValue, isPiiAttribute, payloadHasExplicitPiiFields, hasExplicitInvalidPiiEncoding, hasMalformedPiiAttributes, payloadRequiresUsablePiiWire, isExplicitPiiRequirementCleared, piiAttributeKeysFromPayload, piiWireOptionsFromPayload, isPiiWireBroken, resolvePiiWireForDelta, applyPiiHashToFlatContext, } from './piiHash';
|
|
11
11
|
export type { PiiWireOptions, ApplyPiiHashMode } from './piiHash';
|
|
12
12
|
export { evaluateRule, evaluateKillSwitch, evaluateOnVariation, evaluateFlagWithTargetingRules, applyRolloutStrategy, applyPercentageRollout, applyVariantRollout, applyGradualRollout, computeCurrentPercentage, defaultHash, getContextAttribute, } from './flagEvaluator';
|
|
13
13
|
export type { ExpectedFlagType, FlagEvaluationResult, Condition, TargetingRule, Rollout, Variation, Segment, EvaluationDeps, } from './flagEvaluator';
|
|
@@ -53,6 +53,40 @@ export type PiiWirePayloadFields = {
|
|
|
53
53
|
* @param payload Signed fullConfig / delta fields
|
|
54
54
|
*/
|
|
55
55
|
export declare function payloadHasExplicitPiiFields(payload: PiiWirePayloadFields | null | undefined): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* String attribute keys listed on a PII wire payload (ignores non-strings).
|
|
58
|
+
*
|
|
59
|
+
* @param payload Signed fullConfig / delta fields
|
|
60
|
+
*/
|
|
61
|
+
export declare function piiAttributeKeysFromPayload(payload: PiiWirePayloadFields | null | undefined): string[];
|
|
62
|
+
/**
|
|
63
|
+
* True when `piiAttributes` is present but not a clean list of non-empty strings.
|
|
64
|
+
* e.g. `[null]`, `['']`, or a non-array — must not count as "remove all PII".
|
|
65
|
+
*
|
|
66
|
+
* @param payload Signed fullConfig / delta fields
|
|
67
|
+
*/
|
|
68
|
+
export declare function hasMalformedPiiAttributes(payload: PiiWirePayloadFields | null | undefined): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* True when `piiValueEncoding` is present but is not usable `hmac-v1`.
|
|
71
|
+
* Distinguishes omitted encoding (OK — default hmac-v1) from e.g. `123` / `hmac-v2`.
|
|
72
|
+
*
|
|
73
|
+
* @param payload Signed fullConfig / delta fields
|
|
74
|
+
*/
|
|
75
|
+
export declare function hasExplicitInvalidPiiEncoding(payload: PiiWirePayloadFields | null | undefined): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* True when the payload claims PII hashing is in play and the client must have
|
|
78
|
+
* usable salt + `hmac-v1` encoding (otherwise local eval must fail closed).
|
|
79
|
+
*
|
|
80
|
+
* @param payload Signed fullConfig / delta fields
|
|
81
|
+
*/
|
|
82
|
+
export declare function payloadRequiresUsablePiiWire(payload: PiiWirePayloadFields | null | undefined): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* True when the payload explicitly clears the PII attribute list.
|
|
85
|
+
* Only a literal empty array `[]` counts — `[null]` / `['']` are malformed, not removal.
|
|
86
|
+
*
|
|
87
|
+
* @param payload Signed fullConfig / delta fields
|
|
88
|
+
*/
|
|
89
|
+
export declare function isExplicitPiiRequirementCleared(payload: PiiWirePayloadFields | null | undefined): boolean;
|
|
56
90
|
/**
|
|
57
91
|
* Parse PII wire options from a config-sync payload body.
|
|
58
92
|
*
|
|
@@ -60,6 +94,18 @@ export declare function payloadHasExplicitPiiFields(payload: PiiWirePayloadField
|
|
|
60
94
|
* @returns Options when salt + encoding are usable; otherwise null
|
|
61
95
|
*/
|
|
62
96
|
export declare function piiWireOptionsFromPayload(payload: PiiWirePayloadFields | null | undefined): PiiWireOptions | null;
|
|
97
|
+
/**
|
|
98
|
+
* Whether resolved PII options are missing while the payload (or prior state)
|
|
99
|
+
* still requires hashing — local eval must use defaults only.
|
|
100
|
+
*
|
|
101
|
+
* Explicit `piiAttributes: []` (literal empty array) clears the requirement
|
|
102
|
+
* even if salt is empty — evaluation may proceed without hashing.
|
|
103
|
+
*
|
|
104
|
+
* @param payload Payload that was just applied (full or merged delta fields)
|
|
105
|
+
* @param resolved Result of {@link piiWireOptionsFromPayload} / delta resolve
|
|
106
|
+
* @param previousPriorHadPiiAttrs Whether the store previously had PII attribute keys
|
|
107
|
+
*/
|
|
108
|
+
export declare function isPiiWireBroken(payload: PiiWirePayloadFields | null | undefined, resolved: PiiWireOptions | null, previousPriorHadPiiAttrs?: boolean): boolean;
|
|
63
109
|
/**
|
|
64
110
|
* Resolve PII wire options for a delta: keep prior when all PII fields are
|
|
65
111
|
* absent; otherwise parse after merging omitted fields from `previous`.
|
|
@@ -11,6 +11,11 @@ export type RulesState = {
|
|
|
11
11
|
needsFullConfig: boolean;
|
|
12
12
|
/** PII hash options from the last fullConfig/delta (null when absent). */
|
|
13
13
|
piiWire: PiiWireOptions | null;
|
|
14
|
+
/**
|
|
15
|
+
* True when hashed PII rules are expected but salt/encoding is unusable.
|
|
16
|
+
* Fail closed (defaults only) until a good fullConfig arrives.
|
|
17
|
+
*/
|
|
18
|
+
piiWireBroken: boolean;
|
|
14
19
|
};
|
|
15
20
|
export type ApplyResult = {
|
|
16
21
|
ok: true;
|
|
@@ -36,7 +41,8 @@ export declare class RulesStore {
|
|
|
36
41
|
getMacKey(): Uint8Array | null;
|
|
37
42
|
getState(): RulesState;
|
|
38
43
|
/**
|
|
39
|
-
* Fail-closed readiness: lease not expired
|
|
44
|
+
* Fail-closed readiness: lease not expired, store marked ready, and PII
|
|
45
|
+
* wire metadata usable when hashing is required.
|
|
40
46
|
*/
|
|
41
47
|
isReady(now?: number): boolean;
|
|
42
48
|
/**
|
|
@@ -79,4 +79,9 @@ export type RulesCacheSnapshot = {
|
|
|
79
79
|
piiAttributes?: string[];
|
|
80
80
|
piiSalt?: string;
|
|
81
81
|
piiValueEncoding?: string;
|
|
82
|
+
/**
|
|
83
|
+
* When true, hydrate must stay fail-closed even if salt/attrs were omitted
|
|
84
|
+
* from the snapshot (broken wire with `piiWire === null`).
|
|
85
|
+
*/
|
|
86
|
+
piiWireBroken?: boolean;
|
|
82
87
|
};
|
|
@@ -8,34 +8,37 @@
|
|
|
8
8
|
*
|
|
9
9
|
* This hub elects a single **leader** per API key:
|
|
10
10
|
*
|
|
11
|
-
* - **Leader** holds the real transport (handshake + EventSource).
|
|
12
|
-
*
|
|
13
|
-
* - **Followers** skip handshake and EventSource. They apply the leader's flags
|
|
14
|
-
* and forward `updateContext()` to the leader, which runs it on the shared
|
|
15
|
-
* stream.
|
|
11
|
+
* - **Leader** holds the real transport (handshake + EventSource).
|
|
12
|
+
* - **Followers** skip handshake and EventSource.
|
|
16
13
|
*
|
|
17
|
-
*
|
|
18
|
-
* short TTL and heartbeat. `destroy()` and a `pagehide` / `beforeunload` listener
|
|
19
|
-
* post `bye` and release the lock so another member can take over immediately.
|
|
20
|
-
* If the document is killed without those events, followers wait until the lock
|
|
21
|
-
* TTL expires.
|
|
14
|
+
* Two modes (separate BroadcastChannel / lock keys so they never mix):
|
|
22
15
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
16
|
+
* - **`evaluated`** (server eval, default): leader broadcasts evaluated flag
|
|
17
|
+
* maps; followers forward `updateContext` onto the leader stream (last write
|
|
18
|
+
* wins). Same behavior as before.
|
|
19
|
+
* - **`rules`** (config sync): leader broadcasts verified **rules snapshots**;
|
|
20
|
+
* each tab keeps its own context and evaluates locally. Followers do **not**
|
|
21
|
+
* forward context to the leader.
|
|
27
22
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
23
|
+
* Leadership is stored in `localStorage` with a short TTL and heartbeat.
|
|
24
|
+
* `destroy()` and a `pagehide` / `beforeunload` listener post `bye` and release
|
|
25
|
+
* the lock so another member can take over immediately.
|
|
26
|
+
*
|
|
27
|
+
* Trust: `BroadcastChannel` and `localStorage` are same-origin. If the origin
|
|
28
|
+
* serves untrusted content, set `shareConnection: false`.
|
|
33
29
|
*
|
|
34
30
|
* @module connectionShare
|
|
35
31
|
*/
|
|
36
32
|
import type { Transport } from '../../core/transports/Transport';
|
|
33
|
+
import type { RulesCacheSnapshot } from '../../core/config-sync/types';
|
|
37
34
|
/** Role this document plays in the share group for a given API key. */
|
|
38
35
|
export type ShareRole = 'leader' | 'follower';
|
|
36
|
+
/**
|
|
37
|
+
* What the leader shares with followers.
|
|
38
|
+
* - `evaluated` — classic server-eval flag maps + context RPC
|
|
39
|
+
* - `rules` — config-sync rules snapshots; local eval per tab
|
|
40
|
+
*/
|
|
41
|
+
export type ConnectionShareMode = 'evaluated' | 'rules';
|
|
39
42
|
/**
|
|
40
43
|
* Persistence used for the leadership lock.
|
|
41
44
|
* Defaults to `localStorage` so sibling iframes on the same origin can compete.
|
|
@@ -61,6 +64,11 @@ export interface ShareChannel {
|
|
|
61
64
|
* `localStorage`, and `setInterval`.
|
|
62
65
|
*/
|
|
63
66
|
export interface ConnectionShareOptions {
|
|
67
|
+
/**
|
|
68
|
+
* `evaluated` (default): server-eval share.
|
|
69
|
+
* `rules`: config-sync share — rules only, per-tab context.
|
|
70
|
+
*/
|
|
71
|
+
mode?: ConnectionShareMode;
|
|
64
72
|
/** Override `new BroadcastChannel(name)` (tests). */
|
|
65
73
|
channelFactory?: (name: string) => ShareChannel;
|
|
66
74
|
/** Override `localStorage` for the leadership lock (tests). */
|
|
@@ -99,11 +107,18 @@ export declare class ConnectionShareHub<C = Record<string, unknown>, T = unknown
|
|
|
99
107
|
private watchId;
|
|
100
108
|
private closed;
|
|
101
109
|
private latestFlags;
|
|
110
|
+
private latestRules;
|
|
102
111
|
private flagsWaiters;
|
|
112
|
+
private rulesWaiters;
|
|
103
113
|
private flagsTimeouts;
|
|
114
|
+
private rulesTimeouts;
|
|
104
115
|
private contextWaiters;
|
|
105
116
|
private onPromoteCallback?;
|
|
106
117
|
private leaderContextHandler?;
|
|
118
|
+
/** Rules mode: apply leader snapshot → return flags evaluated for this tab. */
|
|
119
|
+
private followerRulesApply?;
|
|
120
|
+
/** Rules mode: re-evaluate after local `updateContext` (no leader RPC). */
|
|
121
|
+
private followerLocalEval?;
|
|
107
122
|
private followerFlagsCallback?;
|
|
108
123
|
private followerErrorCallback?;
|
|
109
124
|
private readonly now;
|
|
@@ -115,6 +130,7 @@ export declare class ConnectionShareHub<C = Record<string, unknown>, T = unknown
|
|
|
115
130
|
private unloadHandler?;
|
|
116
131
|
private readonly lockKey;
|
|
117
132
|
private readonly channelName;
|
|
133
|
+
private readonly mode;
|
|
118
134
|
/**
|
|
119
135
|
* @param apiKey Share groups are keyed by API key. Distinct keys never share.
|
|
120
136
|
* @param options Optional test doubles for channel, storage, and timers.
|
|
@@ -122,6 +138,8 @@ export declare class ConnectionShareHub<C = Record<string, unknown>, T = unknown
|
|
|
122
138
|
constructor(apiKey: string, options?: ConnectionShareOptions);
|
|
123
139
|
/** `leader` if this document holds the lock; otherwise `follower`. */
|
|
124
140
|
get currentRole(): ShareRole;
|
|
141
|
+
/** `evaluated` (server eval) or `rules` (config sync). */
|
|
142
|
+
get shareMode(): ConnectionShareMode;
|
|
125
143
|
/**
|
|
126
144
|
* Join the share group: compete for the lock, subscribe to the channel.
|
|
127
145
|
*
|
|
@@ -146,16 +164,43 @@ export declare class ConnectionShareHub<C = Record<string, unknown>, T = unknown
|
|
|
146
164
|
setLeaderContextHandler(handler: (context: C, options?: {
|
|
147
165
|
persist?: boolean;
|
|
148
166
|
}) => Promise<Record<string, T>>): void;
|
|
167
|
+
/**
|
|
168
|
+
* Rules-mode follower: hydrate local rules from the leader snapshot and
|
|
169
|
+
* return flags evaluated for **this** document's context.
|
|
170
|
+
*
|
|
171
|
+
* @param handler Apply snapshot + local eval → flag map
|
|
172
|
+
*/
|
|
173
|
+
setFollowerRulesApply(handler: (payload: {
|
|
174
|
+
snapshot: RulesCacheSnapshot;
|
|
175
|
+
analytics?: Record<string, boolean>;
|
|
176
|
+
}) => Record<string, T>): void;
|
|
177
|
+
/**
|
|
178
|
+
* Rules-mode follower: re-evaluate after local `updateContext` (no RPC).
|
|
179
|
+
*
|
|
180
|
+
* @param handler Context → flag map using this tab's rules store
|
|
181
|
+
*/
|
|
182
|
+
setFollowerLocalEval(handler: (context: C) => Record<string, T>): void;
|
|
149
183
|
/**
|
|
150
184
|
* Leader-only: publish the current evaluated flags to all followers.
|
|
151
|
-
* No-op on followers (avoids echo loops).
|
|
185
|
+
* No-op on followers (avoids echo loops). Evaluated mode only.
|
|
186
|
+
*
|
|
187
|
+
* @param flags Evaluated flag map from the leader stream
|
|
152
188
|
*/
|
|
153
189
|
broadcastFlags(flags: Record<string, T>): void;
|
|
190
|
+
/**
|
|
191
|
+
* Leader-only (rules mode): publish a verified rules snapshot.
|
|
192
|
+
* Followers hydrate and evaluate with their own context.
|
|
193
|
+
*
|
|
194
|
+
* @param snapshot Rules store snapshot (MAC already verified on the leader)
|
|
195
|
+
* @param analytics Optional per-flag analytics map from rules
|
|
196
|
+
*/
|
|
197
|
+
broadcastRules(snapshot: RulesCacheSnapshot, analytics?: Record<string, boolean>): void;
|
|
154
198
|
/** Leader-only: mirror a transport `onError` (auth, quota) to followers. */
|
|
155
199
|
broadcastError(error: Error): void;
|
|
156
200
|
/**
|
|
157
|
-
* Transport used by follower FlagClients.
|
|
158
|
-
*
|
|
201
|
+
* Transport used by follower FlagClients.
|
|
202
|
+
* - Evaluated: `init` waits for flags; `fetchFlags` RPCs through the leader.
|
|
203
|
+
* - Rules: `init` waits for a rules snapshot; `fetchFlags` evaluates locally.
|
|
159
204
|
* `destroy` is a no-op — {@link ConnectionShareHub.destroy} owns teardown.
|
|
160
205
|
*/
|
|
161
206
|
createFollowerTransport(): Transport<C, T>;
|
|
@@ -196,6 +241,12 @@ export declare class ConnectionShareHub<C = Record<string, unknown>, T = unknown
|
|
|
196
241
|
* Used by follower `init()`.
|
|
197
242
|
*/
|
|
198
243
|
private waitForFlags;
|
|
244
|
+
/**
|
|
245
|
+
* Block until the leader has sent a `rules` snapshot, or {@link FOLLOWER_WAIT_MS}.
|
|
246
|
+
*
|
|
247
|
+
* @returns Latest rules payload from the leader
|
|
248
|
+
*/
|
|
249
|
+
private waitForRules;
|
|
199
250
|
/**
|
|
200
251
|
* Follower `fetchFlags`: ask the leader to POST `/context` and wait for
|
|
201
252
|
* `context-result`. Leaders run the handler locally.
|
package/package.json
CHANGED