@posthog/browser-common 0.10.4 → 0.10.6
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/dist/client.d.ts +8 -2
- package/dist/config.js +1 -1
- package/dist/config.mjs +1 -1
- package/dist/feature-flags.d.ts +8 -0
- package/dist/feature-flags.js +70 -8
- package/dist/feature-flags.mjs +70 -8
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
|
@@ -108,8 +108,14 @@ export interface Client {
|
|
|
108
108
|
readonly isOptedOut: boolean;
|
|
109
109
|
/** Whether the host currently permits data capture. */
|
|
110
110
|
readonly canCapture: boolean;
|
|
111
|
-
/**
|
|
112
|
-
|
|
111
|
+
/** Whether the client rate limit drops an event captured now. Reading it must not use a token. */
|
|
112
|
+
readonly isRateLimited?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Synchronously admits an analytics event to the client's normal pipeline.
|
|
115
|
+
* Returns `false` when the client rate limit dropped the event, so the caller can send it again later.
|
|
116
|
+
* An event that is dropped for another reason, for example by `before_send`, returns `true`.
|
|
117
|
+
*/
|
|
118
|
+
capture(event: string, properties?: Properties | null, options?: CaptureOptions): boolean;
|
|
113
119
|
/** Registers a synchronous producer of properties merged into every captured event. */
|
|
114
120
|
registerDynamicEventProperties(producer: () => Record<string, unknown>): Disposable;
|
|
115
121
|
/** Returns the extension registered under a typed stable name, or `undefined` when it is not installed. */
|
package/dist/config.js
CHANGED
package/dist/config.mjs
CHANGED
package/dist/feature-flags.d.ts
CHANGED
|
@@ -76,6 +76,8 @@ export declare class PostHogFeatureFlags implements Extension {
|
|
|
76
76
|
private _additionalReloadRequested;
|
|
77
77
|
private _reloadDebouncer;
|
|
78
78
|
private _firingFeatureFlagsCallbacks;
|
|
79
|
+
private _flagCallsPendingCapture;
|
|
80
|
+
private _sendingFlagCallsPendingCapture;
|
|
79
81
|
private _flagsLoadedFromRemote;
|
|
80
82
|
private _staleCacheRefreshTriggered;
|
|
81
83
|
private _consecutiveStatusZeroFailures;
|
|
@@ -153,6 +155,8 @@ export declare class PostHogFeatureFlags implements Extension {
|
|
|
153
155
|
setAnonymousDistinctId(anon_distinct_id: string | undefined): void;
|
|
154
156
|
setReloadingPaused(isPaused: boolean): void;
|
|
155
157
|
resetFlagCallReported(): void;
|
|
158
|
+
/** Sends `$feature_flag_called` for the flags read while capture was off, once the client can capture. */
|
|
159
|
+
sendFlagCallsPendingCapture(): void;
|
|
156
160
|
_callFlagsEndpoint(options?: {
|
|
157
161
|
disableFlags?: boolean;
|
|
158
162
|
}): void;
|
|
@@ -224,7 +228,11 @@ export declare class PostHogFeatureFlags implements Extension {
|
|
|
224
228
|
* @returns {FeatureFlagResult | undefined} The feature flag result including key, enabled, variant, and payload.
|
|
225
229
|
*/
|
|
226
230
|
getFeatureFlagResult(key: string, options?: FeatureFlagOptions): FeatureFlagResult | undefined;
|
|
231
|
+
/** Returns `false` when the client rate limit dropped the event. */
|
|
227
232
|
private _captureFeatureFlagCalled;
|
|
233
|
+
private _holdFlagCall;
|
|
234
|
+
private _flagCallContext;
|
|
235
|
+
private _unmarkFlagCallReported;
|
|
228
236
|
getRemoteConfigPayload(key: string, callback: RemoteConfigFeatureFlagCallback): void;
|
|
229
237
|
private _getRemoteConfigPayload;
|
|
230
238
|
/**
|
package/dist/feature-flags.js
CHANGED
|
@@ -190,6 +190,11 @@ const QuotaLimitedResource = {
|
|
|
190
190
|
FeatureFlags: 'feature_flags',
|
|
191
191
|
Recordings: 'recordings'
|
|
192
192
|
};
|
|
193
|
+
const haveSameGroups = (a, b)=>{
|
|
194
|
+
const keys = Object.keys(a);
|
|
195
|
+
return keys.length === Object.keys(b).length && keys.every((key)=>a[key] === b[key]);
|
|
196
|
+
};
|
|
197
|
+
const isSameFlagCallContext = (a, b)=>a.distinctId === b.distinctId && a.sessionId === b.sessionId && haveSameGroups(a.groups, b.groups);
|
|
193
198
|
class PostHogFeatureFlags {
|
|
194
199
|
constructor(configSource){
|
|
195
200
|
this.name = 'featureFlags';
|
|
@@ -207,6 +212,8 @@ class PostHogFeatureFlags {
|
|
|
207
212
|
this._reloadingDisabled = false;
|
|
208
213
|
this._additionalReloadRequested = false;
|
|
209
214
|
this._firingFeatureFlagsCallbacks = false;
|
|
215
|
+
this._flagCallsPendingCapture = new Map();
|
|
216
|
+
this._sendingFlagCallsPendingCapture = false;
|
|
210
217
|
this._flagsLoadedFromRemote = false;
|
|
211
218
|
this._staleCacheRefreshTriggered = false;
|
|
212
219
|
this._consecutiveStatusZeroFailures = 0;
|
|
@@ -650,6 +657,27 @@ class PostHogFeatureFlags {
|
|
|
650
657
|
}
|
|
651
658
|
resetFlagCallReported() {
|
|
652
659
|
this._remove(external_constants_js_namespaceObject.FLAG_CALL_REPORTED);
|
|
660
|
+
this._flagCallsPendingCapture = new Map();
|
|
661
|
+
}
|
|
662
|
+
sendFlagCallsPendingCapture() {
|
|
663
|
+
if (0 === this._flagCallsPendingCapture.size || this._sendingFlagCallsPendingCapture || !this._client?.canCapture || this._client.isRateLimited) return;
|
|
664
|
+
this._sendingFlagCallsPendingCapture = true;
|
|
665
|
+
try {
|
|
666
|
+
const flagVariants = this.getFlagVariants();
|
|
667
|
+
for (const key of Array.from(this._flagCallsPendingCapture.keys())){
|
|
668
|
+
const pending = this._flagCallsPendingCapture.get(key);
|
|
669
|
+
if (pending) {
|
|
670
|
+
if (pending.value !== String(flagVariants[key]) || !isSameFlagCallContext(pending.context, this._flagCallContext())) {
|
|
671
|
+
this._flagCallsPendingCapture.delete(key);
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
this.getFeatureFlagResult(key);
|
|
675
|
+
if (this._flagCallsPendingCapture.has(key)) break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
} finally{
|
|
679
|
+
this._sendingFlagCallsPendingCapture = false;
|
|
680
|
+
}
|
|
653
681
|
}
|
|
654
682
|
_callFlagsEndpoint(options) {
|
|
655
683
|
this._clearDebouncer();
|
|
@@ -812,12 +840,17 @@ class PostHogFeatureFlags {
|
|
|
812
840
|
[external_constants_js_namespaceObject.FLAG_CALL_REPORTED_SESSION_ID]: sessionIdToPersist
|
|
813
841
|
});
|
|
814
842
|
} else {
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
843
|
+
const captureOff = !this._client?.canCapture;
|
|
844
|
+
const canCapture = !captureOff && !this._client?.isRateLimited;
|
|
845
|
+
if (canCapture) this._flagCallsPendingCapture.delete(key);
|
|
846
|
+
else {
|
|
847
|
+
if (captureOff && Object.keys(flagCallReported).length > 0) {
|
|
848
|
+
flagCallReported = {};
|
|
849
|
+
this._set({
|
|
850
|
+
[external_constants_js_namespaceObject.FLAG_CALL_REPORTED]: flagCallReported
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
if (!flagCallReported[key]?.includes(flagReportValue)) this._holdFlagCall(key, flagReportValue);
|
|
821
854
|
}
|
|
822
855
|
if (canCapture && !flagCallReported[key]?.includes(flagReportValue)) {
|
|
823
856
|
if ((0, core_namespaceObject.isArray)(flagCallReported[key])) flagCallReported[key].push(flagReportValue);
|
|
@@ -853,7 +886,10 @@ class PostHogFeatureFlags {
|
|
|
853
886
|
if (!(0, core_namespaceObject.isUndefined)(flagDetails?.original_variant) || !(0, core_namespaceObject.isUndefined)(flagDetails?.original_enabled)) properties.$feature_flag_original_response = (0, core_namespaceObject.isUndefined)(flagDetails.original_variant) ? flagDetails.original_enabled : flagDetails.original_variant;
|
|
854
887
|
if (flagDetails?.metadata?.original_payload) properties.$feature_flag_original_payload = flagDetails?.metadata?.original_payload;
|
|
855
888
|
if (errors.length) properties.$feature_flag_error = errors.join(',');
|
|
856
|
-
this._captureFeatureFlagCalled(properties)
|
|
889
|
+
if (!this._captureFeatureFlagCalled(properties)) {
|
|
890
|
+
this._unmarkFlagCallReported(key, flagReportValue);
|
|
891
|
+
this._holdFlagCall(key, flagReportValue);
|
|
892
|
+
}
|
|
857
893
|
} else if (sessionIdToPersist) this._set({
|
|
858
894
|
[external_constants_js_namespaceObject.FLAG_CALL_REPORTED]: flagCallReported,
|
|
859
895
|
[external_constants_js_namespaceObject.FLAG_CALL_REPORTED_SESSION_ID]: sessionIdToPersist
|
|
@@ -869,11 +905,36 @@ class PostHogFeatureFlags {
|
|
|
869
905
|
}
|
|
870
906
|
_captureFeatureFlagCalled(properties) {
|
|
871
907
|
try {
|
|
872
|
-
this._client?.capture('$feature_flag_called', properties);
|
|
908
|
+
return this._client?.capture('$feature_flag_called', properties) !== false;
|
|
873
909
|
} catch (error) {
|
|
874
910
|
this._logger.error('Failed to capture feature flag call', error);
|
|
911
|
+
return true;
|
|
875
912
|
}
|
|
876
913
|
}
|
|
914
|
+
_holdFlagCall(key, value) {
|
|
915
|
+
this._flagCallsPendingCapture.set(key, {
|
|
916
|
+
value,
|
|
917
|
+
context: this._flagCallContext()
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
_flagCallContext() {
|
|
921
|
+
return {
|
|
922
|
+
distinctId: this._client?.distinctId,
|
|
923
|
+
groups: {
|
|
924
|
+
...this._client?.groups
|
|
925
|
+
},
|
|
926
|
+
sessionId: this._config.deduplicateCallsPerSession ? this._client?.session?.sessionId : void 0
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
_unmarkFlagCallReported(key, flagReportValue) {
|
|
930
|
+
const flagCallReported = this._prop(external_constants_js_namespaceObject.FLAG_CALL_REPORTED) || {};
|
|
931
|
+
const reportedValues = flagCallReported[key];
|
|
932
|
+
if (!(0, core_namespaceObject.isArray)(reportedValues) || !reportedValues.includes(flagReportValue)) return;
|
|
933
|
+
flagCallReported[key] = reportedValues.filter((value)=>value !== flagReportValue);
|
|
934
|
+
this._set({
|
|
935
|
+
[external_constants_js_namespaceObject.FLAG_CALL_REPORTED]: flagCallReported
|
|
936
|
+
});
|
|
937
|
+
}
|
|
877
938
|
getRemoteConfigPayload(key, callback) {
|
|
878
939
|
this._getRemoteConfigPayload(key, callback);
|
|
879
940
|
}
|
|
@@ -1215,6 +1276,7 @@ class PostHogFeatureFlags {
|
|
|
1215
1276
|
this._clearDebouncer();
|
|
1216
1277
|
this._override_warning = false;
|
|
1217
1278
|
this._consecutiveStatusZeroFailures = 0;
|
|
1279
|
+
this._flagCallsPendingCapture = new Map();
|
|
1218
1280
|
}
|
|
1219
1281
|
}
|
|
1220
1282
|
exports.FeatureFlagError = __webpack_exports__.FeatureFlagError;
|
package/dist/feature-flags.mjs
CHANGED
|
@@ -154,6 +154,11 @@ const QuotaLimitedResource = {
|
|
|
154
154
|
FeatureFlags: 'feature_flags',
|
|
155
155
|
Recordings: 'recordings'
|
|
156
156
|
};
|
|
157
|
+
const haveSameGroups = (a, b)=>{
|
|
158
|
+
const keys = Object.keys(a);
|
|
159
|
+
return keys.length === Object.keys(b).length && keys.every((key)=>a[key] === b[key]);
|
|
160
|
+
};
|
|
161
|
+
const isSameFlagCallContext = (a, b)=>a.distinctId === b.distinctId && a.sessionId === b.sessionId && haveSameGroups(a.groups, b.groups);
|
|
157
162
|
class PostHogFeatureFlags {
|
|
158
163
|
constructor(configSource){
|
|
159
164
|
this.name = 'featureFlags';
|
|
@@ -171,6 +176,8 @@ class PostHogFeatureFlags {
|
|
|
171
176
|
this._reloadingDisabled = false;
|
|
172
177
|
this._additionalReloadRequested = false;
|
|
173
178
|
this._firingFeatureFlagsCallbacks = false;
|
|
179
|
+
this._flagCallsPendingCapture = new Map();
|
|
180
|
+
this._sendingFlagCallsPendingCapture = false;
|
|
174
181
|
this._flagsLoadedFromRemote = false;
|
|
175
182
|
this._staleCacheRefreshTriggered = false;
|
|
176
183
|
this._consecutiveStatusZeroFailures = 0;
|
|
@@ -614,6 +621,27 @@ class PostHogFeatureFlags {
|
|
|
614
621
|
}
|
|
615
622
|
resetFlagCallReported() {
|
|
616
623
|
this._remove(FLAG_CALL_REPORTED);
|
|
624
|
+
this._flagCallsPendingCapture = new Map();
|
|
625
|
+
}
|
|
626
|
+
sendFlagCallsPendingCapture() {
|
|
627
|
+
if (0 === this._flagCallsPendingCapture.size || this._sendingFlagCallsPendingCapture || !this._client?.canCapture || this._client.isRateLimited) return;
|
|
628
|
+
this._sendingFlagCallsPendingCapture = true;
|
|
629
|
+
try {
|
|
630
|
+
const flagVariants = this.getFlagVariants();
|
|
631
|
+
for (const key of Array.from(this._flagCallsPendingCapture.keys())){
|
|
632
|
+
const pending = this._flagCallsPendingCapture.get(key);
|
|
633
|
+
if (pending) {
|
|
634
|
+
if (pending.value !== String(flagVariants[key]) || !isSameFlagCallContext(pending.context, this._flagCallContext())) {
|
|
635
|
+
this._flagCallsPendingCapture.delete(key);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
this.getFeatureFlagResult(key);
|
|
639
|
+
if (this._flagCallsPendingCapture.has(key)) break;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
} finally{
|
|
643
|
+
this._sendingFlagCallsPendingCapture = false;
|
|
644
|
+
}
|
|
617
645
|
}
|
|
618
646
|
_callFlagsEndpoint(options) {
|
|
619
647
|
this._clearDebouncer();
|
|
@@ -776,12 +804,17 @@ class PostHogFeatureFlags {
|
|
|
776
804
|
[FLAG_CALL_REPORTED_SESSION_ID]: sessionIdToPersist
|
|
777
805
|
});
|
|
778
806
|
} else {
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
807
|
+
const captureOff = !this._client?.canCapture;
|
|
808
|
+
const canCapture = !captureOff && !this._client?.isRateLimited;
|
|
809
|
+
if (canCapture) this._flagCallsPendingCapture.delete(key);
|
|
810
|
+
else {
|
|
811
|
+
if (captureOff && Object.keys(flagCallReported).length > 0) {
|
|
812
|
+
flagCallReported = {};
|
|
813
|
+
this._set({
|
|
814
|
+
[FLAG_CALL_REPORTED]: flagCallReported
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
if (!flagCallReported[key]?.includes(flagReportValue)) this._holdFlagCall(key, flagReportValue);
|
|
785
818
|
}
|
|
786
819
|
if (canCapture && !flagCallReported[key]?.includes(flagReportValue)) {
|
|
787
820
|
if (isArray(flagCallReported[key])) flagCallReported[key].push(flagReportValue);
|
|
@@ -817,7 +850,10 @@ class PostHogFeatureFlags {
|
|
|
817
850
|
if (!isUndefined(flagDetails?.original_variant) || !isUndefined(flagDetails?.original_enabled)) properties.$feature_flag_original_response = isUndefined(flagDetails.original_variant) ? flagDetails.original_enabled : flagDetails.original_variant;
|
|
818
851
|
if (flagDetails?.metadata?.original_payload) properties.$feature_flag_original_payload = flagDetails?.metadata?.original_payload;
|
|
819
852
|
if (errors.length) properties.$feature_flag_error = errors.join(',');
|
|
820
|
-
this._captureFeatureFlagCalled(properties)
|
|
853
|
+
if (!this._captureFeatureFlagCalled(properties)) {
|
|
854
|
+
this._unmarkFlagCallReported(key, flagReportValue);
|
|
855
|
+
this._holdFlagCall(key, flagReportValue);
|
|
856
|
+
}
|
|
821
857
|
} else if (sessionIdToPersist) this._set({
|
|
822
858
|
[FLAG_CALL_REPORTED]: flagCallReported,
|
|
823
859
|
[FLAG_CALL_REPORTED_SESSION_ID]: sessionIdToPersist
|
|
@@ -833,11 +869,36 @@ class PostHogFeatureFlags {
|
|
|
833
869
|
}
|
|
834
870
|
_captureFeatureFlagCalled(properties) {
|
|
835
871
|
try {
|
|
836
|
-
this._client?.capture('$feature_flag_called', properties);
|
|
872
|
+
return this._client?.capture('$feature_flag_called', properties) !== false;
|
|
837
873
|
} catch (error) {
|
|
838
874
|
this._logger.error('Failed to capture feature flag call', error);
|
|
875
|
+
return true;
|
|
839
876
|
}
|
|
840
877
|
}
|
|
878
|
+
_holdFlagCall(key, value) {
|
|
879
|
+
this._flagCallsPendingCapture.set(key, {
|
|
880
|
+
value,
|
|
881
|
+
context: this._flagCallContext()
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
_flagCallContext() {
|
|
885
|
+
return {
|
|
886
|
+
distinctId: this._client?.distinctId,
|
|
887
|
+
groups: {
|
|
888
|
+
...this._client?.groups
|
|
889
|
+
},
|
|
890
|
+
sessionId: this._config.deduplicateCallsPerSession ? this._client?.session?.sessionId : void 0
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
_unmarkFlagCallReported(key, flagReportValue) {
|
|
894
|
+
const flagCallReported = this._prop(FLAG_CALL_REPORTED) || {};
|
|
895
|
+
const reportedValues = flagCallReported[key];
|
|
896
|
+
if (!isArray(reportedValues) || !reportedValues.includes(flagReportValue)) return;
|
|
897
|
+
flagCallReported[key] = reportedValues.filter((value)=>value !== flagReportValue);
|
|
898
|
+
this._set({
|
|
899
|
+
[FLAG_CALL_REPORTED]: flagCallReported
|
|
900
|
+
});
|
|
901
|
+
}
|
|
841
902
|
getRemoteConfigPayload(key, callback) {
|
|
842
903
|
this._getRemoteConfigPayload(key, callback);
|
|
843
904
|
}
|
|
@@ -1179,6 +1240,7 @@ class PostHogFeatureFlags {
|
|
|
1179
1240
|
this._clearDebouncer();
|
|
1180
1241
|
this._override_warning = false;
|
|
1181
1242
|
this._consecutiveStatusZeroFailures = 0;
|
|
1243
|
+
this._flagCallsPendingCapture = new Map();
|
|
1182
1244
|
}
|
|
1183
1245
|
}
|
|
1184
1246
|
export { FeatureFlagError, PostHogFeatureFlags, QuotaLimitedResource, filterActiveFeatureFlags, parseFlagsResponse };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/browser-common",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.6",
|
|
4
4
|
"description": "Internal shared browser utilities and extension primitives for PostHog Browser SDKs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -241,8 +241,8 @@
|
|
|
241
241
|
"dependencies": {
|
|
242
242
|
"dompurify": "^3.4.13",
|
|
243
243
|
"preact": "^10.29.3",
|
|
244
|
-
"@posthog/
|
|
245
|
-
"@posthog/
|
|
244
|
+
"@posthog/core": "^1.63.1",
|
|
245
|
+
"@posthog/types": "^1.419.0"
|
|
246
246
|
},
|
|
247
247
|
"devDependencies": {
|
|
248
248
|
"@rsbuild/plugin-react": "^2.1.0",
|