flagmint-js-sdk 2.0.0 → 2.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/README.md +16 -16
- package/dist/flagmint.cjs.js +656 -1615
- package/dist/flagmint.cjs.js.map +1 -1
- package/dist/flagmint.es.js +656 -1615
- package/dist/flagmint.es.js.map +1 -1
- package/dist/flagmint.umd.js +656 -1615
- package/dist/flagmint.umd.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/sdk/core/client.d.ts +1 -53
- package/dist/sdk/core/config-sync/aslEcdh.d.ts +32 -0
- package/dist/sdk/core/config-sync/evaluateSdkFlag.d.ts +26 -0
- package/dist/sdk/core/config-sync/flagEvaluator.d.ts +373 -0
- package/dist/sdk/core/config-sync/flattenContext.d.ts +28 -0
- package/dist/sdk/core/config-sync/handshake.d.ts +21 -0
- package/dist/sdk/core/config-sync/index.d.ts +13 -0
- package/dist/sdk/core/config-sync/rulesStore.d.ts +56 -0
- package/dist/sdk/core/config-sync/signPayload.d.ts +17 -0
- package/dist/sdk/core/config-sync/stringHash.d.ts +7 -0
- package/dist/sdk/core/config-sync/types.d.ts +72 -0
- package/dist/sdk/core/helpers/applicationEvents.d.ts +7 -0
- package/dist/sdk/core/helpers/connectionShare.d.ts +7 -0
- package/dist/sdk/core/helpers/jsonCloneable.d.ts +2 -0
- package/dist/sdk/core/transports/LongPollingTransport.d.ts +1 -5
- package/dist/sdk/core/transports/SSETransport.d.ts +55 -3
- package/dist/sdk/core/transports/Transport.d.ts +1 -5
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SignableConfigBody } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Stable JSON for HMAC: sort object keys recursively so field order cannot
|
|
4
|
+
* invalidate signatures across language runtimes.
|
|
5
|
+
* Must match FF-EU `canonicalizeForSigning`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function canonicalizeForSigning(value: unknown): string;
|
|
8
|
+
/**
|
|
9
|
+
* HMAC-SHA256 hex digest over the payload with `signature` omitted.
|
|
10
|
+
*/
|
|
11
|
+
export declare function signConfigPayload(body: SignableConfigBody, secret: Uint8Array | string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Verify HMAC-SHA256 hex `signature` on a config-sync payload.
|
|
14
|
+
* Prefer the ASL ECDH-derived session MAC key.
|
|
15
|
+
*/
|
|
16
|
+
export declare function verifyConfigPayloadSignature(body: SignableConfigBody, secret: Uint8Array | string): boolean;
|
|
17
|
+
export declare function isConfigPayloadExpired(expiresAt: number, now?: number): boolean;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* djb2 variant matching npm `string-hash` (and Go `evaluate.StringHash`).
|
|
3
|
+
* Iterates right-to-left with XOR — required for rollout bucket parity.
|
|
4
|
+
*/
|
|
5
|
+
export declare function stringHash(input: string): number;
|
|
6
|
+
/** Stable bucket in [0, 99] for percentage / variant rollouts. */
|
|
7
|
+
export declare function hashPercent(input: string): number;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types for SSE config-sync (aligned with FF-EU `modules/evaluator/config-sync.ts`).
|
|
3
|
+
*/
|
|
4
|
+
export type SdkFlagConfig = {
|
|
5
|
+
key: string;
|
|
6
|
+
type: 'boolean' | 'string' | 'number' | 'json';
|
|
7
|
+
is_active: boolean;
|
|
8
|
+
default_value: unknown;
|
|
9
|
+
off_variation_id?: string | null;
|
|
10
|
+
targeting_rules: unknown[];
|
|
11
|
+
variations: Array<{
|
|
12
|
+
id: string;
|
|
13
|
+
value: unknown;
|
|
14
|
+
key?: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
}>;
|
|
17
|
+
rollouts: Record<string, unknown>;
|
|
18
|
+
analytics_enabled: boolean;
|
|
19
|
+
};
|
|
20
|
+
export type SdkSegment = {
|
|
21
|
+
id: string;
|
|
22
|
+
rules: unknown[];
|
|
23
|
+
logical_op?: string;
|
|
24
|
+
force?: boolean;
|
|
25
|
+
};
|
|
26
|
+
export type SignableConfigBody = Record<string, unknown> & {
|
|
27
|
+
signature?: string;
|
|
28
|
+
};
|
|
29
|
+
export type LeasePayload = {
|
|
30
|
+
type: 'lease';
|
|
31
|
+
version: number;
|
|
32
|
+
serverNow: number;
|
|
33
|
+
expiresAt: number;
|
|
34
|
+
signature: string;
|
|
35
|
+
};
|
|
36
|
+
export type FullConfigPayload = {
|
|
37
|
+
type: 'fullConfig';
|
|
38
|
+
version: number;
|
|
39
|
+
compiledAt: number;
|
|
40
|
+
expiresAt: number;
|
|
41
|
+
flags: SdkFlagConfig[];
|
|
42
|
+
segments: Record<string, SdkSegment>;
|
|
43
|
+
warnings?: string[];
|
|
44
|
+
signature: string;
|
|
45
|
+
};
|
|
46
|
+
export type DeltaConfigPayload = {
|
|
47
|
+
type: 'delta';
|
|
48
|
+
fromVersion: number;
|
|
49
|
+
toVersion: number;
|
|
50
|
+
compiledAt: number;
|
|
51
|
+
expiresAt: number;
|
|
52
|
+
upserts: SdkFlagConfig[];
|
|
53
|
+
deletes: string[];
|
|
54
|
+
segments: Record<string, SdkSegment>;
|
|
55
|
+
signature: string;
|
|
56
|
+
};
|
|
57
|
+
export type DeltasCatchUpPayload = {
|
|
58
|
+
type: 'deltas';
|
|
59
|
+
fromVersion: number;
|
|
60
|
+
toVersion: number;
|
|
61
|
+
expiresAt: number;
|
|
62
|
+
items: DeltaConfigPayload[];
|
|
63
|
+
signature: string;
|
|
64
|
+
};
|
|
65
|
+
export type ConfigSyncPayload = LeasePayload | FullConfigPayload | DeltaConfigPayload | DeltasCatchUpPayload;
|
|
66
|
+
/** Persisted cache for reconnect (`fullConfig` vs `sinceVersion`). */
|
|
67
|
+
export type RulesCacheSnapshot = {
|
|
68
|
+
version: number;
|
|
69
|
+
expiresAt: number;
|
|
70
|
+
flags: SdkFlagConfig[];
|
|
71
|
+
segments: Record<string, SdkSegment>;
|
|
72
|
+
};
|
|
@@ -11,6 +11,13 @@ export interface ApplicationEvent {
|
|
|
11
11
|
extra?: Record<string, unknown>;
|
|
12
12
|
}
|
|
13
13
|
export declare function eventsUrlFromRestEndpoint(restEndpoint: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* API base for `POST …/context` (and similar) from the REST evaluate URL.
|
|
16
|
+
* Stream hosts may be SSE-only (`STREAM_HOSTS`); context must stay on the API host.
|
|
17
|
+
*
|
|
18
|
+
* `https://api…/evaluator/evaluate` → `https://api…/evaluator/v2/flags`
|
|
19
|
+
*/
|
|
20
|
+
export declare function flagsBaseFromRestEndpoint(restEndpoint: string): string;
|
|
14
21
|
export declare function userKeyFromContext(context: Record<string, unknown> | undefined): string | undefined;
|
|
15
22
|
export declare function extraFromError(error: unknown, extra?: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
16
23
|
/**
|
|
@@ -100,6 +100,7 @@ export declare class ConnectionShareHub<C = Record<string, unknown>, T = unknown
|
|
|
100
100
|
private closed;
|
|
101
101
|
private latestFlags;
|
|
102
102
|
private flagsWaiters;
|
|
103
|
+
private flagsTimeouts;
|
|
103
104
|
private contextWaiters;
|
|
104
105
|
private onPromoteCallback?;
|
|
105
106
|
private leaderContextHandler?;
|
|
@@ -167,6 +168,12 @@ export declare class ConnectionShareHub<C = Record<string, unknown>, T = unknown
|
|
|
167
168
|
destroy(): void;
|
|
168
169
|
private bindUnloadListener;
|
|
169
170
|
private unbindUnloadListener;
|
|
171
|
+
/**
|
|
172
|
+
* JSON-clone then post. Native `BroadcastChannel` structured-clones the
|
|
173
|
+
* payload; Vue reactive Proxies and similar host objects throw
|
|
174
|
+
* `DataCloneError`. JSON.stringify can walk those Proxies.
|
|
175
|
+
*/
|
|
176
|
+
private post;
|
|
170
177
|
/** Open the BroadcastChannel for this API key, or a test factory channel. */
|
|
171
178
|
private openChannel;
|
|
172
179
|
/** Mark this member leader and start lock heartbeat + `alive` pings. */
|
|
@@ -13,7 +13,6 @@ export declare class LongPollingTransport<C, T> implements Transport<C, T> {
|
|
|
13
13
|
private backoffMultiplier;
|
|
14
14
|
private pollTimeoutId;
|
|
15
15
|
private onUpdateCallback?;
|
|
16
|
-
private onAnalyticsUpdatedCallback?;
|
|
17
16
|
private currentContext;
|
|
18
17
|
private currentFlags;
|
|
19
18
|
private consecutiveErrors;
|
|
@@ -24,10 +23,7 @@ export declare class LongPollingTransport<C, T> implements Transport<C, T> {
|
|
|
24
23
|
private poll;
|
|
25
24
|
private applyBackoff;
|
|
26
25
|
private flagsChanged;
|
|
27
|
-
fetchFlags(context: C,
|
|
28
|
-
persist?: boolean;
|
|
29
|
-
}): Promise<Record<string, T>>;
|
|
26
|
+
fetchFlags(context: C): Promise<Record<string, T>>;
|
|
30
27
|
onFlagsUpdated(callback: (flags: Record<string, T>) => void): void;
|
|
31
|
-
onAnalyticsUpdated(callback: (analytics: Record<string, boolean>) => void): void;
|
|
32
28
|
destroy(): void;
|
|
33
29
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Transport } from './Transport';
|
|
2
|
-
export interface SseTransportConfig {
|
|
2
|
+
export interface SseTransportConfig<C = unknown, T = unknown> {
|
|
3
3
|
endpoint: string;
|
|
4
4
|
apiKey: string;
|
|
5
5
|
context: any;
|
|
@@ -8,6 +8,37 @@ export interface SseTransportConfig {
|
|
|
8
8
|
version: string;
|
|
9
9
|
};
|
|
10
10
|
EventSourceImpl?: any;
|
|
11
|
+
/**
|
|
12
|
+
* When true, stream uses config-sync (`fullConfig` / `sinceVersion`) and
|
|
13
|
+
* listens for lease / fullConfig / delta / deltas instead of evaluated `flags`.
|
|
14
|
+
*/
|
|
15
|
+
configSync?: boolean;
|
|
16
|
+
/** Build config-mode query params for each connect/reconnect. */
|
|
17
|
+
getConfigSyncParams?: () => {
|
|
18
|
+
wantFullConfig: boolean;
|
|
19
|
+
sinceVersion?: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Apply a signed config-sync payload (verify + reduce). Return whether the
|
|
23
|
+
* rules store is ready enough to publish evaluated flags.
|
|
24
|
+
*/
|
|
25
|
+
onConfigSyncEvent?: (eventName: 'lease' | 'fullConfig' | 'delta' | 'deltas', payload: Record<string, unknown>) => {
|
|
26
|
+
publish: boolean;
|
|
27
|
+
};
|
|
28
|
+
/** Evaluate current rules store against the given (or last) context. */
|
|
29
|
+
getEvaluatedFlags?: (context: C) => Record<string, T>;
|
|
30
|
+
/** Analytics map from the rules store (flag key → analytics_enabled). */
|
|
31
|
+
getAnalyticsMap?: () => Record<string, boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* When true (config sync), POST /context is fire-and-forget telemetry —
|
|
34
|
+
* flags are re-evaluated locally and returned immediately.
|
|
35
|
+
*/
|
|
36
|
+
contextAsTelemetry?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* API base for `POST …/context` (defaults to `endpoint`). Use the API host
|
|
39
|
+
* when `endpoint` is a stream-only hostname.
|
|
40
|
+
*/
|
|
41
|
+
contextEndpoint?: string;
|
|
11
42
|
}
|
|
12
43
|
/**
|
|
13
44
|
* A Server-Sent Events (SSE) transport implementation responsible for
|
|
@@ -17,8 +48,9 @@ export interface SseTransportConfig {
|
|
|
17
48
|
* Stream: `GET {endpoint}/stream?sessionId&context&sdkVersion&platform&wrapper*`
|
|
18
49
|
* emits `connected` then `flags`. Heartbeats are SSE comments (`: heartbeat`)
|
|
19
50
|
* and are not visible to EventSource — they only keep the TCP connection alive.
|
|
20
|
-
* Context: `POST {endpoint}/context` with `x-api-key` returns 202 and the
|
|
51
|
+
* Context: `POST {contextEndpoint|endpoint}/context` with `x-api-key` returns 202 and the
|
|
21
52
|
* re-evaluated flags arrive on the existing stream after a 400ms debounce.
|
|
53
|
+
* Prefer the API host when `endpoint` is stream-only (`STREAM_HOSTS`).
|
|
22
54
|
*
|
|
23
55
|
* @template C The shape of the evaluation context sent to the Flagmint server.
|
|
24
56
|
* @template T The feature flag value type returned by the platform.
|
|
@@ -33,6 +65,7 @@ export declare class SseTransport<C, T> implements Transport<C, T> {
|
|
|
33
65
|
private eventSource;
|
|
34
66
|
private flags;
|
|
35
67
|
private context;
|
|
68
|
+
private lastSentContext;
|
|
36
69
|
private connectionId;
|
|
37
70
|
private onFlagsUpdatedCallback?;
|
|
38
71
|
private onAnalyticsUpdatedCallback?;
|
|
@@ -46,13 +79,20 @@ export declare class SseTransport<C, T> implements Transport<C, T> {
|
|
|
46
79
|
private connectReject;
|
|
47
80
|
private connectionListener?;
|
|
48
81
|
private flagsListener?;
|
|
82
|
+
private leaseListener?;
|
|
83
|
+
private fullConfigListener?;
|
|
84
|
+
private deltaListener?;
|
|
85
|
+
private deltasListener?;
|
|
49
86
|
private quotaListener?;
|
|
50
87
|
private errorListener?;
|
|
51
88
|
private reconnectTimeoutId;
|
|
52
89
|
private reconnectAttempts;
|
|
53
90
|
private terminalClose;
|
|
54
91
|
private contextUpdateQueue;
|
|
55
|
-
|
|
92
|
+
/** Wall time when the current SSE `connected` event was received (debug lifecycle). */
|
|
93
|
+
private connectedAtMs;
|
|
94
|
+
constructor(endpoint: string, sessionId: string, initialContext: C, refreshTokenCallback?: (() => Promise<string>) | undefined, configOptions?: Partial<SseTransportConfig<C, T>> | undefined);
|
|
95
|
+
private get configSyncEnabled();
|
|
56
96
|
init(): Promise<void>;
|
|
57
97
|
/**
|
|
58
98
|
* POST /v2/flags/context. FF-EU always returns 202 and pushes the next
|
|
@@ -66,10 +106,21 @@ export declare class SseTransport<C, T> implements Transport<C, T> {
|
|
|
66
106
|
onAnalyticsUpdated(callback: (analytics: Record<string, boolean>) => void): void;
|
|
67
107
|
onError(callback: (error: Error) => void): void;
|
|
68
108
|
destroy(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Opt-in SSE lifecycle lines (`FlagClient` `debugLog: true`).
|
|
111
|
+
* Example: `[SseTransport] disconnected connectionId=… upMs=900012 reason=network_error retryInMs=1500`
|
|
112
|
+
*/
|
|
113
|
+
private logLifecycle;
|
|
114
|
+
private logLifecycleConnected;
|
|
115
|
+
private logLifecycleDisconnected;
|
|
69
116
|
private get apiKey();
|
|
117
|
+
/** Host for POST /context — API when stream host is SSE-only. */
|
|
118
|
+
private get contextBase();
|
|
70
119
|
private applyFlags;
|
|
71
120
|
private emitError;
|
|
72
121
|
private performContextUpdate;
|
|
122
|
+
/** Fire-and-forget observed-context telemetry for config-sync mode. */
|
|
123
|
+
private postContextTelemetry;
|
|
73
124
|
private safeReadJson;
|
|
74
125
|
private quotaExtra;
|
|
75
126
|
private handleQuotaPayload;
|
|
@@ -79,6 +130,7 @@ export declare class SseTransport<C, T> implements Transport<C, T> {
|
|
|
79
130
|
private scheduleActiveReconnection;
|
|
80
131
|
private createConnectionListener;
|
|
81
132
|
private createFlagsListener;
|
|
133
|
+
private createConfigSyncListener;
|
|
82
134
|
private createQuotaListener;
|
|
83
135
|
/**
|
|
84
136
|
* Named `error` events from FF-EU (`event: error`) after the stream is hijacked.
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
export interface Transport<C, T> {
|
|
2
2
|
init(onUpdate?: (flags: Record<string, T>) => void): Promise<void>;
|
|
3
|
-
fetchFlags(context: C,
|
|
4
|
-
persist?: boolean;
|
|
5
|
-
}): Promise<Record<string, T>>;
|
|
3
|
+
fetchFlags(context: C): Promise<Record<string, T>>;
|
|
6
4
|
destroy(): void;
|
|
7
5
|
onFlagsUpdated?: (callback: (flags: Record<string, T>) => void) => void;
|
|
8
|
-
onAnalyticsUpdated?: (callback: (analytics: Record<string, boolean>) => void) => void;
|
|
9
6
|
onContextUpdated?: (callback: (context: C) => void) => void;
|
|
10
|
-
onError?: (callback: (error: Error) => void) => void;
|
|
11
7
|
}
|
package/package.json
CHANGED