flagmint-js-sdk 1.2.26 → 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.
@@ -0,0 +1,56 @@
1
+ import type { ConfigSyncPayload, DeltaConfigPayload, DeltasCatchUpPayload, FullConfigPayload, LeasePayload, RulesCacheSnapshot, SdkFlagConfig, SdkSegment } from './types';
2
+ export type RulesState = {
3
+ version: number;
4
+ expiresAt: number;
5
+ flags: Map<string, SdkFlagConfig>;
6
+ segments: Map<string, SdkSegment>;
7
+ /** false when lease expired offline — fail closed (defaults only). */
8
+ ready: boolean;
9
+ /** Set when a delta's fromVersion does not match — reconnect with fullConfig. */
10
+ needsFullConfig: boolean;
11
+ };
12
+ export type ApplyResult = {
13
+ ok: true;
14
+ state: RulesState;
15
+ } | {
16
+ ok: false;
17
+ reason: 'bad_signature' | 'version_gap' | 'stale' | 'expired' | 'invalid_payload';
18
+ state: RulesState;
19
+ };
20
+ export declare function createEmptyRulesState(): RulesState;
21
+ /**
22
+ * Pure reducer for config-sync actions (signatures already verified).
23
+ */
24
+ export declare function reduceRules(state: RulesState, action: ConfigSyncPayload, now?: number): ApplyResult;
25
+ /**
26
+ * In-memory rules store: verify MAC → reduce → optional localCache snapshot.
27
+ */
28
+ export declare class RulesStore {
29
+ private state;
30
+ private macKey;
31
+ constructor(macKey?: Uint8Array | null, initial?: RulesState);
32
+ setMacKey(macKey: Uint8Array | null): void;
33
+ getMacKey(): Uint8Array | null;
34
+ getState(): RulesState;
35
+ /**
36
+ * Fail-closed readiness: lease not expired and store marked ready.
37
+ */
38
+ isReady(now?: number): boolean;
39
+ markExpired(): void;
40
+ getFlag(key: string): SdkFlagConfig | undefined;
41
+ /**
42
+ * Fail-closed default for a flag key (last-known `default_value` only).
43
+ */
44
+ getFailClosedDefault(key: string): unknown;
45
+ wantsFullConfig(now?: number): boolean;
46
+ sinceVersion(now?: number): number | undefined;
47
+ /**
48
+ * Verify signature with the session MAC key, then apply the reducer.
49
+ */
50
+ applySigned(payload: ConfigSyncPayload, now?: number): ApplyResult;
51
+ /** Apply without MAC (unit tests / already-verified payloads). */
52
+ applyTrusted(payload: ConfigSyncPayload, now?: number): ApplyResult;
53
+ toSnapshot(): RulesCacheSnapshot;
54
+ hydrateFromSnapshot(snapshot: RulesCacheSnapshot, now?: number): void;
55
+ }
56
+ export type { LeasePayload, FullConfigPayload, DeltaConfigPayload, DeltasCatchUpPayload };
@@ -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
+ };
@@ -0,0 +1,27 @@
1
+ export declare const MAX_EVENT_BATCH = 20;
2
+ export declare const EVENT_FLUSH_MS = 2000;
3
+ export type ApplicationEventKind = 'error' | 'custom';
4
+ export interface ApplicationEvent {
5
+ flagKey: string;
6
+ kind: ApplicationEventKind;
7
+ eventName?: string;
8
+ variationValue?: unknown;
9
+ userKey?: string;
10
+ timestamp: string;
11
+ extra?: Record<string, unknown>;
12
+ }
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;
21
+ export declare function userKeyFromContext(context: Record<string, unknown> | undefined): string | undefined;
22
+ export declare function extraFromError(error: unknown, extra?: Record<string, unknown>): Record<string, unknown> | undefined;
23
+ /**
24
+ * When the server has sent an analytics map, only flags with analytics on
25
+ * are reported. A null map means an older server — send and let ingest drop.
26
+ */
27
+ export declare function shouldReportApplicationEvent(analyticsByFlag: Record<string, boolean> | null, flagKey: string): boolean;
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Same-origin connection sharing for Flagmint streaming transports.
3
+ *
4
+ * When two or more FlagClient instances run in the same origin — typical cases
5
+ * are a host page plus sibling iframes, or two widgets on one Python-embedded
6
+ * page — each would otherwise open its own ASL handshake and SSE/long-poll
7
+ * stream. That doubles evaluation quota and is almost never intended.
8
+ *
9
+ * This hub elects a single **leader** per API key:
10
+ *
11
+ * - **Leader** holds the real transport (handshake + EventSource). It broadcasts
12
+ * flag snapshots and errors over {@link BroadcastChannel}.
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.
16
+ *
17
+ * Leadership is stored in `localStorage` (`flagmint_share_lock:<apiKey>`) with a
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.
22
+ *
23
+ * Sharing is a **browser** feature. Node cluster workers each need their own
24
+ * stream — {@link isConnectionSharingAvailable} is false there. Do not share
25
+ * when two clients on one page must keep different evaluation contexts at the
26
+ * same time; `/context` is per connection, so the last write wins.
27
+ *
28
+ * Trust: `BroadcastChannel` and `localStorage` are same-origin. Any document
29
+ * on this origin (including a third-party iframe you host there) can join the
30
+ * group, POST a context through the leader's authenticated stream, and read
31
+ * the flags broadcast back. That is expected among trusted siblings. If the
32
+ * origin serves untrusted content, set `shareConnection: false`.
33
+ *
34
+ * @module connectionShare
35
+ */
36
+ import type { Transport } from '../../core/transports/Transport';
37
+ /** Role this document plays in the share group for a given API key. */
38
+ export type ShareRole = 'leader' | 'follower';
39
+ /**
40
+ * Persistence used for the leadership lock.
41
+ * Defaults to `localStorage` so sibling iframes on the same origin can compete.
42
+ */
43
+ export interface ShareStorage {
44
+ getItem(key: string): string | null;
45
+ setItem(key: string, value: string): void;
46
+ removeItem(key: string): void;
47
+ }
48
+ /**
49
+ * Cross-document bus. Production uses {@link BroadcastChannel};
50
+ * tests inject an in-memory implementation.
51
+ */
52
+ export interface ShareChannel {
53
+ postMessage(data: unknown): void;
54
+ close(): void;
55
+ onmessage: ((event: {
56
+ data: unknown;
57
+ }) => void) | null;
58
+ }
59
+ /**
60
+ * Test/runtime hooks. Omit in production — the hub uses `BroadcastChannel`,
61
+ * `localStorage`, and `setInterval`.
62
+ */
63
+ export interface ConnectionShareOptions {
64
+ /** Override `new BroadcastChannel(name)` (tests). */
65
+ channelFactory?: (name: string) => ShareChannel;
66
+ /** Override `localStorage` for the leadership lock (tests). */
67
+ storage?: ShareStorage;
68
+ /** Clock used for lock expiry. */
69
+ now?: () => number;
70
+ setTimer?: (handler: () => void, ms: number) => ReturnType<typeof setInterval>;
71
+ clearTimer?: (id: ReturnType<typeof setInterval>) => void;
72
+ /** Delay used to stagger lock confirmation (tests inject a no-op). */
73
+ delay?: (ms: number) => Promise<void>;
74
+ }
75
+ /**
76
+ * Whether this runtime can share a stream across documents.
77
+ *
78
+ * @returns `true` in a browser with {@link BroadcastChannel} (includes same-origin
79
+ * iframes). `false` in Node, so cluster workers each keep their own connection.
80
+ */
81
+ export declare function isConnectionSharingAvailable(): boolean;
82
+ /**
83
+ * Elects a single streaming leader per API key across same-origin documents.
84
+ *
85
+ * {@link FlagClient} constructs one hub when `shareConnection` is enabled (the
86
+ * browser default). The leader opens SSE; followers use
87
+ * {@link ConnectionShareHub.createFollowerTransport} instead.
88
+ *
89
+ * @template C Evaluation context forwarded from follower `updateContext` calls.
90
+ * @template T Flag value type in snapshots broadcast by the leader.
91
+ */
92
+ export declare class ConnectionShareHub<C = Record<string, unknown>, T = unknown> {
93
+ private readonly apiKey;
94
+ /** Stable id for this document in channel messages and the lock record. */
95
+ readonly memberId: string;
96
+ private role;
97
+ private channel;
98
+ private heartbeatId;
99
+ private watchId;
100
+ private closed;
101
+ private latestFlags;
102
+ private flagsWaiters;
103
+ private flagsTimeouts;
104
+ private contextWaiters;
105
+ private onPromoteCallback?;
106
+ private leaderContextHandler?;
107
+ private followerFlagsCallback?;
108
+ private followerErrorCallback?;
109
+ private readonly now;
110
+ private readonly storage;
111
+ private readonly channelFactory?;
112
+ private readonly setTimer;
113
+ private readonly clearTimer;
114
+ private readonly delay;
115
+ private unloadHandler?;
116
+ private readonly lockKey;
117
+ private readonly channelName;
118
+ /**
119
+ * @param apiKey Share groups are keyed by API key. Distinct keys never share.
120
+ * @param options Optional test doubles for channel, storage, and timers.
121
+ */
122
+ constructor(apiKey: string, options?: ConnectionShareOptions);
123
+ /** `leader` if this document holds the lock; otherwise `follower`. */
124
+ get currentRole(): ShareRole;
125
+ /**
126
+ * Join the share group: compete for the lock, subscribe to the channel.
127
+ *
128
+ * If {@link BroadcastChannel} is missing, this member becomes leader locally
129
+ * and never shares (same as a Node process).
130
+ *
131
+ * @returns The role this member should take. Only the leader should call
132
+ * ASL handshake / `SseTransport.init()`.
133
+ */
134
+ join(): Promise<ShareRole>;
135
+ /**
136
+ * Invoked after a follower wins the lock (leader `bye` or expired heartbeat).
137
+ * The client should open a real transport here, then
138
+ * {@link ConnectionShareHub.broadcastFlags}.
139
+ */
140
+ onPromote(callback: () => void | Promise<void>): void;
141
+ /**
142
+ * Leader-only: run `updateContext` on the real transport when a follower
143
+ * sends `context-request`. The context is whatever that same-origin
144
+ * document posted; the leader does not authenticate the sender beyond origin.
145
+ */
146
+ setLeaderContextHandler(handler: (context: C, options?: {
147
+ persist?: boolean;
148
+ }) => Promise<Record<string, T>>): void;
149
+ /**
150
+ * Leader-only: publish the current evaluated flags to all followers.
151
+ * No-op on followers (avoids echo loops).
152
+ */
153
+ broadcastFlags(flags: Record<string, T>): void;
154
+ /** Leader-only: mirror a transport `onError` (auth, quota) to followers. */
155
+ broadcastError(error: Error): void;
156
+ /**
157
+ * Transport used by follower FlagClients. `init` waits for the leader
158
+ * snapshot; `fetchFlags` RPCs `updateContext` through the leader.
159
+ * `destroy` is a no-op — {@link ConnectionShareHub.destroy} owns teardown.
160
+ */
161
+ createFollowerTransport(): Transport<C, T>;
162
+ /**
163
+ * Leave the share group. Leaders release the lock and post `bye` so another
164
+ * iframe can open the stream. Also invoked from `pagehide` / `beforeunload`.
165
+ * Channel close is deferred one tick so `bye` can flush on native
166
+ * {@link BroadcastChannel}.
167
+ */
168
+ destroy(): void;
169
+ private bindUnloadListener;
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;
177
+ /** Open the BroadcastChannel for this API key, or a test factory channel. */
178
+ private openChannel;
179
+ /** Mark this member leader and start lock heartbeat + `alive` pings. */
180
+ private becomeLeader;
181
+ /**
182
+ * Follower poll: if the lock is free or expired, take it and run
183
+ * {@link ConnectionShareHub.onPromote}.
184
+ */
185
+ private maybeTakeover;
186
+ /** Dispatch a {@link ShareMessage} from another document in the group. */
187
+ private handleMessage;
188
+ /**
189
+ * Leader: evaluate a follower's context on the real transport and reply.
190
+ * Any same-origin document can trigger this; the resulting flags are
191
+ * broadcast to the whole group.
192
+ */
193
+ private handleContextRequest;
194
+ /**
195
+ * Block until the leader has sent a `flags` snapshot, or {@link FOLLOWER_WAIT_MS}.
196
+ * Used by follower `init()`.
197
+ */
198
+ private waitForFlags;
199
+ /**
200
+ * Follower `fetchFlags`: ask the leader to POST `/context` and wait for
201
+ * `context-result`. Leaders run the handler locally.
202
+ *
203
+ * The posted context is not origin-isolated beyond BroadcastChannel itself.
204
+ * Untrusted same-origin frames must not share a connection with a trusted
205
+ * leader — set `shareConnection: false` on those clients.
206
+ */
207
+ private requestContextUpdate;
208
+ /** Read the leadership lock, or `null` if missing/corrupt. */
209
+ private readLock;
210
+ /**
211
+ * Write the lock, wait so a competing write can land, then re-read.
212
+ * localStorage has no compare-and-set; the delay plus confirm is the
213
+ * best we can do so two iframes do not both become leader.
214
+ *
215
+ * @returns `true` if this member still owns the lock after the confirm.
216
+ */
217
+ private tryAcquireLock;
218
+ /** Extend lock expiry; no-op if another member stole the lock. */
219
+ private refreshLock;
220
+ /** Drop the lock only if this member still owns it. */
221
+ private releaseLock;
222
+ }
@@ -0,0 +1,2 @@
1
+ export declare const ERR_CONTEXT_NOT_CLONEABLE = "ERR_CONTEXT_NOT_CLONEABLE";
2
+ export declare function toJsonCloneable<T>(value: T): T;
@@ -0,0 +1,142 @@
1
+ import type { Transport } from './Transport';
2
+ export interface SseTransportConfig<C = unknown, T = unknown> {
3
+ endpoint: string;
4
+ apiKey: string;
5
+ context: any;
6
+ wrapper?: {
7
+ name: string;
8
+ version: string;
9
+ };
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;
42
+ }
43
+ /**
44
+ * A Server-Sent Events (SSE) transport implementation responsible for
45
+ * maintaining a persistent streaming connection to the Flagmint platform.
46
+ *
47
+ * Handshake: `POST /auth/asl-handshake` issues a single-use sessionId.
48
+ * Stream: `GET {endpoint}/stream?sessionId&context&sdkVersion&platform&wrapper*`
49
+ * emits `connected` then `flags`. Heartbeats are SSE comments (`: heartbeat`)
50
+ * and are not visible to EventSource — they only keep the TCP connection alive.
51
+ * Context: `POST {contextEndpoint|endpoint}/context` with `x-api-key` returns 202 and the
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`).
54
+ *
55
+ * @template C The shape of the evaluation context sent to the Flagmint server.
56
+ * @template T The feature flag value type returned by the platform.
57
+ *
58
+ * @implements {Transport<C, T>}
59
+ */
60
+ export declare class SseTransport<C, T> implements Transport<C, T> {
61
+ private endpoint;
62
+ private sessionId;
63
+ private refreshTokenCallback?;
64
+ private configOptions?;
65
+ private eventSource;
66
+ private flags;
67
+ private context;
68
+ private lastSentContext;
69
+ private connectionId;
70
+ private onFlagsUpdatedCallback?;
71
+ private onAnalyticsUpdatedCallback?;
72
+ private onErrorCallback?;
73
+ private initialFlagsReceived;
74
+ private initialFlagsResolve;
75
+ private initialFlagsReject;
76
+ private nextFlagsResolve;
77
+ private connectPromise;
78
+ private connectResolve;
79
+ private connectReject;
80
+ private connectionListener?;
81
+ private flagsListener?;
82
+ private leaseListener?;
83
+ private fullConfigListener?;
84
+ private deltaListener?;
85
+ private deltasListener?;
86
+ private quotaListener?;
87
+ private errorListener?;
88
+ private reconnectTimeoutId;
89
+ private reconnectAttempts;
90
+ private terminalClose;
91
+ private contextUpdateQueue;
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();
96
+ init(): Promise<void>;
97
+ /**
98
+ * POST /v2/flags/context. FF-EU always returns 202 and pushes the next
99
+ * `flags` event on the open SSE stream (after a 400ms debounce). The HTTP
100
+ * body does not contain evaluated flags — browser and Node share this path.
101
+ */
102
+ fetchFlags(context: C, options?: {
103
+ persist?: boolean;
104
+ }): Promise<Record<string, T>>;
105
+ onFlagsUpdated(callback: (flags: Record<string, T>) => void): void;
106
+ onAnalyticsUpdated(callback: (analytics: Record<string, boolean>) => void): void;
107
+ onError(callback: (error: Error) => void): void;
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;
116
+ private get apiKey();
117
+ /** Host for POST /context — API when stream host is SSE-only. */
118
+ private get contextBase();
119
+ private applyFlags;
120
+ private emitError;
121
+ private performContextUpdate;
122
+ /** Fire-and-forget observed-context telemetry for config-sync mode. */
123
+ private postContextTelemetry;
124
+ private safeReadJson;
125
+ private quotaExtra;
126
+ private handleQuotaPayload;
127
+ private cleanupEventSource;
128
+ private resolveEventSourceImpl;
129
+ private connect;
130
+ private scheduleActiveReconnection;
131
+ private createConnectionListener;
132
+ private createFlagsListener;
133
+ private createConfigSyncListener;
134
+ private createQuotaListener;
135
+ /**
136
+ * Named `error` events from FF-EU (`event: error`) after the stream is hijacked.
137
+ * Distinct from EventSource.onerror, which fires on network/connection failure.
138
+ */
139
+ private createStreamErrorListener;
140
+ private mapStreamErrorCode;
141
+ private resetInitialResolvers;
142
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flagmint-js-sdk",
3
- "version": "1.2.26",
3
+ "version": "2.2.0",
4
4
  "description": "Framework-agnostic JavaScript SDK for managing feature flags in Flagmint applications.",
5
5
  "author": "Flagmint Team",
6
6
  "license": "BSD 3-Clause",
@@ -22,7 +22,10 @@
22
22
  "scripts": {
23
23
  "clean": "rimraf dist",
24
24
  "build": "npm run clean && vite build",
25
- "test": "jest --runInBand --detectOpenHandles"
25
+ "test": "jest --runInBand --detectOpenHandles",
26
+ "simulate:unreachable": "npm run build && node ./scripts/simulate-unreachable-server.js",
27
+ "simulate:cache-fallback": "npm run build && node ./scripts/simulate-cache-adapter-fallback.js",
28
+ "simulate:asl-gen": "npm run build && node ./scripts/test-asl-handshake.mjs"
26
29
  },
27
30
  "peerDependencies": {
28
31
  "crypto": "*"
@@ -42,7 +45,11 @@
42
45
  "vite-plugin-dts": "^2.0.0"
43
46
  },
44
47
  "dependencies": {
45
- "buffer": "^6.0.3"
48
+ "@noble/ciphers": "^2.2.0",
49
+ "@noble/curves": "^2.2.0",
50
+ "@noble/hashes": "^2.2.0",
51
+ "buffer": "^6.0.3",
52
+ "node-fetch": "^3.3.2"
46
53
  },
47
54
  "optionalDependencies": {
48
55
  "ws": "^8.0.0"
@@ -1,33 +0,0 @@
1
- import type { Transport } from './Transport';
2
- type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'failed';
3
- export declare class WebSocketTransport<C, T> implements Transport<C, T> {
4
- private wsUrl;
5
- private apiKey;
6
- private maxRetries;
7
- private initialBackoffMs;
8
- private socket;
9
- private flags;
10
- private context;
11
- private isReady;
12
- private initialFlagsReceived;
13
- private initialFlagsPromise;
14
- private initialFlagsResolve;
15
- private initialFlagsReject;
16
- private onFlagsUpdatedCallback?;
17
- private onConnectionStateCallback?;
18
- private retries;
19
- private reconnectTimeoutId;
20
- constructor(wsUrl: string, apiKey: string, maxRetries?: number, initialBackoffMs?: number);
21
- init(): Promise<void>;
22
- private waitForInitialFlags;
23
- private connectWithRetry;
24
- private cleanupSocket;
25
- private setConnectionState;
26
- private getWebSocketImplementation;
27
- fetchFlags(context: C): Promise<Record<string, T>>;
28
- onFlagsUpdated(callback: (flags: Record<string, T>) => void): void;
29
- onConnectionStateChanged(callback: (state: ConnectionState) => void): void;
30
- destroy(): void;
31
- private sendContext;
32
- }
33
- export {};