@flagsmith/flagsmith 11.0.0-internal.7 → 12.0.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.
@@ -9,8 +9,6 @@ import {
9
9
  IFlagsmithResponse,
10
10
  IFlagsmithTrait,
11
11
  IInitConfig,
12
- IPipelineEvent,
13
- IPipelineEventBatch,
14
12
  ISentryClient,
15
13
  IState,
16
14
  ITraits,
@@ -36,11 +34,6 @@ export enum FlagSource {
36
34
  "SERVER" = "SERVER",
37
35
  }
38
36
 
39
- export enum PipelineEventType {
40
- FLAG_EVALUATION = 'flag_evaluation',
41
- CUSTOM_EVENT = 'custom_event',
42
- }
43
-
44
37
  export type LikeFetch = (input: Partial<RequestInfo>, init?: Partial<RequestInit>) => Promise<Partial<Response>>
45
38
  let _fetch: LikeFetch;
46
39
 
@@ -71,7 +64,6 @@ type Config = {
71
64
  const FLAGSMITH_CONFIG_ANALYTICS_KEY = "flagsmith_value_";
72
65
  const FLAGSMITH_FLAG_ANALYTICS_KEY = "flagsmith_enabled_";
73
66
  const FLAGSMITH_TRAIT_ANALYTICS_KEY = "flagsmith_trait_";
74
- const DEFAULT_PIPELINE_FLUSH_INTERVAL = 10000;
75
67
 
76
68
  const Flagsmith = class {
77
69
  _trigger?:(()=>void)|null= null
@@ -273,46 +265,6 @@ const Flagsmith = class {
273
265
  }
274
266
  };
275
267
 
276
- flushPipelineAnalytics = async () => {
277
- const isEvaluationEnabled = this.evaluationAnalyticsUrl && this.evaluationContext.environment;
278
- const isReadyToFlush = this.pipelineEvents.length > 0 && (!this.isPipelineFlushing || this.pipelineFlushInterval === 0);
279
- if (!isEvaluationEnabled || !isReadyToFlush) {
280
- return;
281
- }
282
-
283
- const environmentKey = this.evaluationContext.environment!.apiKey;
284
- this.isPipelineFlushing = true;
285
- const eventsToSend = this.pipelineEvents.slice(0, this.evaluationAnalyticsMaxBuffer);
286
- this.pipelineEvents = this.pipelineEvents.slice(this.evaluationAnalyticsMaxBuffer);
287
- this.pipelineRecordedKeys.clear();
288
-
289
- const batch: IPipelineEventBatch = {
290
- events: eventsToSend,
291
- environment_key: environmentKey,
292
- };
293
-
294
- try {
295
- const res = await _fetch(this.evaluationAnalyticsUrl + 'v1/analytics/batch', {
296
- method: 'POST',
297
- body: JSON.stringify(batch),
298
- headers: {
299
- 'Content-Type': 'application/json; charset=utf-8',
300
- 'X-Environment-Key': environmentKey,
301
- ...(SDK_VERSION ? { 'Flagsmith-SDK-User-Agent': `flagsmith-js-sdk/${SDK_VERSION}` } : {}),
302
- },
303
- });
304
- if (!res.status || res.status < 200 || res.status >= 300) {
305
- throw new Error(`Pipeline analytics: unexpected status ${res.status}`);
306
- }
307
- this.log('Pipeline analytics: flush successful');
308
- } catch (err) {
309
- this.pipelineEvents = eventsToSend.concat(this.pipelineEvents);
310
- this.log('Pipeline analytics: flush failed, events re-queued', err);
311
- } finally {
312
- this.isPipelineFlushing = false;
313
- }
314
- };
315
-
316
268
  datadogRum: IDatadogRum | null = null;
317
269
  loadingState: LoadingState = {isLoading: true, isFetching: true, error: null, source: FlagSource.NONE}
318
270
  canUseStorage = false
@@ -338,12 +290,6 @@ const Flagsmith = class {
338
290
  sentryClient: ISentryClient | null = null
339
291
  withTraits?: ITraits|null= null
340
292
  cacheOptions = {ttl:0, skipAPI: false, loadStale: false, storageKey: undefined as string|undefined}
341
- private evaluationAnalyticsUrl: string | null = null
342
- private evaluationAnalyticsMaxBuffer: number = 1000
343
- private pipelineEvents: IPipelineEvent[] = []
344
- private pipelineAnalyticsInterval: ReturnType<typeof setInterval> | null = null
345
- private isPipelineFlushing = false
346
- private pipelineRecordedKeys: Map<string, string> = new Map()
347
293
  async init(config: IInitConfig) {
348
294
  const evaluationContext = toEvaluationContext(config.evaluationContext || this.evaluationContext);
349
295
  try {
@@ -362,7 +308,6 @@ const Flagsmith = class {
362
308
  enableDynatrace,
363
309
  enableLogs,
364
310
  environmentID,
365
- evaluationAnalyticsConfig,
366
311
  eventSourceUrl= "https://realtime.flagsmith.com/",
367
312
  fetch: fetchImplementation,
368
313
  headers,
@@ -496,12 +441,6 @@ const Flagsmith = class {
496
441
  }
497
442
  }
498
443
 
499
- if (evaluationAnalyticsConfig) {
500
- this.initPipelineAnalytics(evaluationAnalyticsConfig);
501
- } else {
502
- this.stopPipelineAnalytics();
503
- }
504
-
505
444
  //If the user specified default flags emit a changed event immediately
506
445
  if (cacheFlags) {
507
446
  if (AsyncStorage && this.canUseStorage) {
@@ -684,6 +623,13 @@ const Flagsmith = class {
684
623
  this.evaluationEvent = state.evaluationEvent || this.evaluationEvent;
685
624
  this.identity = this.getContext()?.identity?.identifier
686
625
  this.log("setState called", this)
626
+ // Hydration from server state (e.g. <FlagsmithProvider serverState={...}>
627
+ // without an options prop) must flip loadingState to loaded. Guarded on
628
+ // source===NONE so we never clobber a loaded state already set by
629
+ // init()/getFlags()/cache paths that also call setState internally.
630
+ if (this.loadingState.source === FlagSource.NONE && this.flags && Object.keys(this.flags).length > 0) {
631
+ this.setLoadingState(this._loadedState(null, FlagSource.SERVER));
632
+ }
687
633
  }
688
634
  }
689
635
 
@@ -977,118 +923,9 @@ const Flagsmith = class {
977
923
  }
978
924
  this.evaluationEvent[this.evaluationContext.environment.apiKey][key] += 1;
979
925
  }
980
-
981
- if (this.evaluationAnalyticsUrl) {
982
- this.recordPipelineEvent(key);
983
- }
984
-
985
926
  this.updateEventStorage();
986
927
  };
987
928
 
988
- private pipelineFlushInterval: number = DEFAULT_PIPELINE_FLUSH_INTERVAL;
989
-
990
- private initPipelineAnalytics(config: NonNullable<IInitConfig['evaluationAnalyticsConfig']>) {
991
- this.stopPipelineAnalytics();
992
- this.evaluationAnalyticsUrl = ensureTrailingSlash(config.analyticsServerUrl);
993
- this.evaluationAnalyticsMaxBuffer = config.maxBuffer ?? 1000;
994
- this.pipelineFlushInterval = config.flushInterval ?? DEFAULT_PIPELINE_FLUSH_INTERVAL;
995
- this.pipelineEvents = [];
996
- if (this.pipelineFlushInterval > 0) {
997
- this.pipelineAnalyticsInterval = setInterval(
998
- this.flushPipelineAnalytics,
999
- this.pipelineFlushInterval,
1000
- );
1001
- this.pipelineAnalyticsInterval?.unref?.();
1002
- }
1003
- }
1004
-
1005
- private stopPipelineAnalytics() {
1006
- if (this.pipelineAnalyticsInterval) {
1007
- clearInterval(this.pipelineAnalyticsInterval);
1008
- this.pipelineAnalyticsInterval = null;
1009
- }
1010
- this.evaluationAnalyticsUrl = null;
1011
- this.pipelineEvents = [];
1012
- this.pipelineRecordedKeys.clear();
1013
- }
1014
-
1015
- private currentTraitsSnapshot() {
1016
- return this.evaluationContext.identity?.traits
1017
- ? { ...this.evaluationContext.identity.traits }
1018
- : null;
1019
- }
1020
-
1021
- private getPageUrl(): string | null {
1022
- return typeof window !== 'undefined' && window.location ? window.location.href : null;
1023
- }
1024
-
1025
- private getEventMetadata(extra?: Record<string, unknown>): Record<string, unknown> {
1026
- const pageUrl = this.getPageUrl();
1027
- return {
1028
- ...(extra || {}),
1029
- ...(pageUrl ? { page_url: pageUrl } : {}),
1030
- ...(SDK_VERSION ? { sdk_version: SDK_VERSION } : {}),
1031
- };
1032
- }
1033
-
1034
- // Pipeline event schema — must match the pipeline server's Event struct.
1035
- // To update: 1) IPipelineEvent in types.d.ts 2) event object below 3) tests in test/analytics-pipeline.test.ts
1036
- private buildAnalyticEvent(
1037
- eventType: PipelineEventType,
1038
- eventId: string,
1039
- options?: {
1040
- enabled?: boolean | null;
1041
- value?: any;
1042
- extraMetadata?: Record<string, unknown>;
1043
- timestamp?: number;
1044
- },
1045
- ): IPipelineEvent {
1046
- return {
1047
- event_id: eventId,
1048
- event_type: eventType,
1049
- evaluated_at: options?.timestamp ?? Date.now(),
1050
- identity_identifier: this.evaluationContext.identity?.identifier ?? null,
1051
- enabled: options?.enabled ?? null,
1052
- value: options?.value ?? null,
1053
- traits: this.currentTraitsSnapshot(),
1054
- metadata: this.getEventMetadata(options?.extraMetadata),
1055
- };
1056
- }
1057
-
1058
- private recordPipelineEvent(key: string) {
1059
- const flagKey = key.toLowerCase().replace(/ /g, '_');
1060
- const flag = this.flags && this.flags[flagKey];
1061
- const fingerprint = `${this.evaluationContext.identity?.identifier ?? 'none'}|${flag?.enabled ?? false}|${flag?.value ?? 'null'}`;
1062
- if (this.pipelineRecordedKeys.get(flagKey) === fingerprint) {
1063
- return;
1064
- }
1065
- this.pipelineRecordedKeys.set(flagKey, fingerprint);
1066
- const event = this.buildAnalyticEvent(PipelineEventType.FLAG_EVALUATION, flagKey, {
1067
- enabled: flag ? flag.enabled : null,
1068
- value: flag ? flag.value : null,
1069
- extraMetadata: flag ? { id: flag.id } : undefined,
1070
- });
1071
- this.pipelineEvents.push(event);
1072
-
1073
- if (this.pipelineFlushInterval === 0 || this.pipelineEvents.length >= this.evaluationAnalyticsMaxBuffer) {
1074
- this.flushPipelineAnalytics();
1075
- }
1076
- }
1077
-
1078
- trackEvent = (eventName: string, metadata?: Record<string, unknown>) => {
1079
- if (!this.evaluationAnalyticsUrl || !eventName) {
1080
- return;
1081
- }
1082
- const event = this.buildAnalyticEvent(PipelineEventType.CUSTOM_EVENT, eventName, {
1083
- extraMetadata: metadata,
1084
- });
1085
- this.pipelineEvents.push(event);
1086
-
1087
- if (this.pipelineFlushInterval === 0 || this.pipelineEvents.length >= this.evaluationAnalyticsMaxBuffer) {
1088
- this.flushPipelineAnalytics();
1089
- }
1090
- }
1091
-
1092
929
  private setLoadingState(loadingState: LoadingState) {
1093
930
  if (!deepEqual(loadingState, this.loadingState)) {
1094
931
  this.loadingState = { ...loadingState };
package/src/index.ts CHANGED
@@ -19,4 +19,4 @@ export default flagsmith;
19
19
  export const createFlagsmithInstance = ():IFlagsmith=>{
20
20
  return core({ AsyncStorage, fetch:_fetch, eventSource:_EventSource})
21
21
  }
22
- export { FlagSource, PipelineEventType } from './flagsmith-core';
22
+ export { FlagSource } from './flagsmith-core';
package/src/react.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { createContext, FC, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
1
+ import React, { createContext, FC, useContext, useEffect, useMemo, useRef, useState } from 'react'
2
2
  import Emitter from './utils/emitter'
3
3
  const events = new Emitter()
4
4
 
@@ -87,28 +87,17 @@ const getRenderKey = (flagsmith: IFlagsmith, flags: string[], traits: string[] =
87
87
  export function useFlagsmithLoading() {
88
88
  const flagsmith = useContext(FlagsmithContext)
89
89
  const [loadingState, setLoadingState] = useState(flagsmith?.loadingState)
90
- const [subscribed, setSubscribed] = useState(false)
91
- const refSubscribed = useRef(subscribed)
92
-
93
- const eventListener = useCallback(() => {
94
- setLoadingState(flagsmith?.loadingState)
95
- }, [flagsmith])
96
- if (!refSubscribed.current) {
97
- events.on('loading_event', eventListener)
98
- refSubscribed.current = true
99
- }
100
90
 
101
91
  useEffect(() => {
102
- if (!subscribed && flagsmith?.initialised) {
103
- events.on('loading_event', eventListener)
104
- setSubscribed(true)
105
- }
92
+ if (!flagsmith) return
93
+ setLoadingState(flagsmith.loadingState)
94
+ const unsubscribe = events.on('loading_event', () => {
95
+ setLoadingState(flagsmith.loadingState)
96
+ })
106
97
  return () => {
107
- if (subscribed) {
108
- events.off('loading_event', eventListener)
109
- }
98
+ unsubscribe()
110
99
  }
111
- }, [flagsmith, subscribed, eventListener])
100
+ }, [flagsmith])
112
101
 
113
102
  return loadingState
114
103
  }
@@ -142,32 +131,27 @@ export function useFlags<F extends string | Record<string, any>, T extends strin
142
131
  _flags: readonly (F | keyof F)[],
143
132
  _traits: readonly T[] = []
144
133
  ) {
145
- const firstRender = useRef(true)
146
134
  const flags = useConstant<string[]>(flagsAsArray(_flags))
147
135
  const traits = useConstant<string[]>(flagsAsArray(_traits))
148
136
  const flagsmith = useContext(FlagsmithContext)
149
137
  const [renderRef, setRenderRef] = useState(getRenderKey(flagsmith as IFlagsmith, flags, traits))
150
- const eventListener = useCallback(() => {
151
- const newRenderKey = getRenderKey(flagsmith as IFlagsmith, flags, traits)
152
- if (newRenderKey !== renderRef) {
153
- // @ts-expect-error using internal function, consumers would never call this
154
- flagsmith?.log('React - useFlags flags and traits have changed')
155
- setRenderRef(newRenderKey)
156
- }
157
- }, [renderRef])
158
- const emitterRef = useRef(events.once('event', eventListener))
159
-
160
- if (firstRender.current) {
161
- firstRender.current = false
162
- // @ts-expect-error using internal function, consumers would never call this
163
- flagsmith?.log('React - Initialising event listeners')
164
- }
165
138
 
166
139
  useEffect(() => {
140
+ if (!flagsmith) return
141
+ setRenderRef(getRenderKey(flagsmith, flags, traits))
142
+ const unsubscribe = events.on('event', () => {
143
+ setRenderRef((prev) => {
144
+ const next = getRenderKey(flagsmith, flags, traits)
145
+ if (prev === next) return prev
146
+ // @ts-expect-error using internal function, consumers would never call this
147
+ flagsmith?.log('React - useFlags flags and traits have changed')
148
+ return next
149
+ })
150
+ })
167
151
  return () => {
168
- emitterRef.current?.()
152
+ unsubscribe()
169
153
  }
170
- }, [])
154
+ }, [flagsmith, flags, traits])
171
155
 
172
156
  const res = useMemo(() => {
173
157
  const res: any = {}
package/src/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EvaluationContext, IdentityEvaluationContext, TraitEvaluationContext } from "./evaluation-context";
2
- import { FlagSource, PipelineEventType } from "./flagsmith-core";
2
+ import { FlagSource } from "./flagsmith-core";
3
3
 
4
4
  type IFlagsmithValue<T = string | number | boolean | null> = T
5
5
 
@@ -85,7 +85,7 @@ export type ISentryClient = {
85
85
  } | undefined;
86
86
 
87
87
 
88
- export { FlagSource, PipelineEventType };
88
+ export { FlagSource };
89
89
 
90
90
  export declare type LoadingState = {
91
91
  error: Error | null, // Current error, resets on next attempt to fetch flags
@@ -131,34 +131,6 @@ export interface IInitConfig<F extends string | Record<string, any> = string, T
131
131
  * Customer application metadata
132
132
  */
133
133
  applicationMetadata?: ApplicationMetadata;
134
- /**
135
- * @experimental Internal use only — API may change without notice.
136
- * Configuration for the evaluation analytics pipeline. When provided,
137
- * individual flag evaluation events are buffered and sent to the pipeline endpoint.
138
- * @hidden
139
- */
140
- /** @internal */
141
- evaluationAnalyticsConfig?: {
142
- analyticsServerUrl: string;
143
- maxBuffer?: number;
144
- flushInterval?: number;
145
- };
146
- }
147
-
148
- export interface IPipelineEvent {
149
- event_id: string; // flag_name or event_name
150
- event_type: PipelineEventType;
151
- evaluated_at: number;
152
- identity_identifier: string | null;
153
- enabled?: boolean | null;
154
- value: IFlagsmithValue;
155
- traits?: { [key: string]: null | TraitEvaluationContext } | null;
156
- metadata?: Record<string, any> | null;
157
- }
158
-
159
- export interface IPipelineEventBatch {
160
- events: IPipelineEvent[];
161
- environment_key: string;
162
134
  }
163
135
 
164
136
  export interface IFlagsmithResponse {
@@ -299,15 +271,6 @@ T extends string = string
299
271
  * Set a key value set of traits for a given user, triggers a call to get flags
300
272
  */
301
273
  setTraits: (traits: ITraits) => Promise<void>;
302
- /**
303
- * Track a custom event through the evaluation analytics pipeline.
304
- * Requires `evaluationAnalyticsConfig` to be set; no-op otherwise.
305
- * Events are sent with the current identity (or null if anonymous).
306
- * @experimental Internal use only — API may change without notice.
307
- * @internal
308
- * @hidden
309
- */
310
- trackEvent: (eventName: string, metadata?: Record<string, unknown>) => void;
311
274
  /**
312
275
  * The stored identity of the user
313
276
  */
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by write-version.js
2
- export const SDK_VERSION = "11.0.0-internal.7";
2
+ export const SDK_VERSION = "12.0.0";
package/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EvaluationContext, IdentityEvaluationContext, TraitEvaluationContext } from "./evaluation-context";
2
- import { FlagSource, PipelineEventType } from "./flagsmith-core";
2
+ import { FlagSource } from "./flagsmith-core";
3
3
 
4
4
  type IFlagsmithValue<T = string | number | boolean | null> = T
5
5
 
@@ -85,7 +85,7 @@ export type ISentryClient = {
85
85
  } | undefined;
86
86
 
87
87
 
88
- export { FlagSource, PipelineEventType };
88
+ export { FlagSource };
89
89
 
90
90
  export declare type LoadingState = {
91
91
  error: Error | null, // Current error, resets on next attempt to fetch flags
@@ -131,34 +131,6 @@ export interface IInitConfig<F extends string | Record<string, any> = string, T
131
131
  * Customer application metadata
132
132
  */
133
133
  applicationMetadata?: ApplicationMetadata;
134
- /**
135
- * @experimental Internal use only — API may change without notice.
136
- * Configuration for the evaluation analytics pipeline. When provided,
137
- * individual flag evaluation events are buffered and sent to the pipeline endpoint.
138
- * @hidden
139
- */
140
- /** @internal */
141
- evaluationAnalyticsConfig?: {
142
- analyticsServerUrl: string;
143
- maxBuffer?: number;
144
- flushInterval?: number;
145
- };
146
- }
147
-
148
- export interface IPipelineEvent {
149
- event_id: string; // flag_name or event_name
150
- event_type: PipelineEventType;
151
- evaluated_at: number;
152
- identity_identifier: string | null;
153
- enabled?: boolean | null;
154
- value: IFlagsmithValue;
155
- traits?: { [key: string]: null | TraitEvaluationContext } | null;
156
- metadata?: Record<string, any> | null;
157
- }
158
-
159
- export interface IPipelineEventBatch {
160
- events: IPipelineEvent[];
161
- environment_key: string;
162
134
  }
163
135
 
164
136
  export interface IFlagsmithResponse {
@@ -299,15 +271,6 @@ T extends string = string
299
271
  * Set a key value set of traits for a given user, triggers a call to get flags
300
272
  */
301
273
  setTraits: (traits: ITraits) => Promise<void>;
302
- /**
303
- * Track a custom event through the evaluation analytics pipeline.
304
- * Requires `evaluationAnalyticsConfig` to be set; no-op otherwise.
305
- * Events are sent with the current identity (or null if anonymous).
306
- * @experimental Internal use only — API may change without notice.
307
- * @internal
308
- * @hidden
309
- */
310
- trackEvent: (eventName: string, metadata?: Record<string, unknown>) => void;
311
274
  /**
312
275
  * The stored identity of the user
313
276
  */
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "11.0.0-internal.7";
1
+ export declare const SDK_VERSION = "12.0.0";