posthog-js-lite 4.1.0 → 4.1.1
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/LICENSE +245 -0
- package/README.md +2 -1
- package/dist/index.cjs +459 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.mjs +454 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +26 -9
- package/CHANGELOG.md +0 -187
- package/index.ts +0 -5
- package/lib/index.cjs +0 -2641
- package/lib/index.cjs.map +0 -1
- package/lib/index.d.ts +0 -681
- package/lib/index.mjs +0 -2636
- package/lib/index.mjs.map +0 -1
- package/src/context.ts +0 -170
- package/src/patch.ts +0 -50
- package/src/posthog-web.ts +0 -141
- package/src/storage.ts +0 -168
- package/src/types.ts +0 -8
- package/test/posthog-web.spec.ts +0 -289
- package/tsconfig.json +0 -7
package/lib/index.mjs
DELETED
|
@@ -1,2636 +0,0 @@
|
|
|
1
|
-
var version = "4.1.0";
|
|
2
|
-
|
|
3
|
-
var PostHogPersistedProperty;
|
|
4
|
-
(function (PostHogPersistedProperty) {
|
|
5
|
-
PostHogPersistedProperty["AnonymousId"] = "anonymous_id";
|
|
6
|
-
PostHogPersistedProperty["DistinctId"] = "distinct_id";
|
|
7
|
-
PostHogPersistedProperty["Props"] = "props";
|
|
8
|
-
PostHogPersistedProperty["FeatureFlagDetails"] = "feature_flag_details";
|
|
9
|
-
PostHogPersistedProperty["FeatureFlags"] = "feature_flags";
|
|
10
|
-
PostHogPersistedProperty["FeatureFlagPayloads"] = "feature_flag_payloads";
|
|
11
|
-
PostHogPersistedProperty["BootstrapFeatureFlagDetails"] = "bootstrap_feature_flag_details";
|
|
12
|
-
PostHogPersistedProperty["BootstrapFeatureFlags"] = "bootstrap_feature_flags";
|
|
13
|
-
PostHogPersistedProperty["BootstrapFeatureFlagPayloads"] = "bootstrap_feature_flag_payloads";
|
|
14
|
-
PostHogPersistedProperty["OverrideFeatureFlags"] = "override_feature_flags";
|
|
15
|
-
PostHogPersistedProperty["Queue"] = "queue";
|
|
16
|
-
PostHogPersistedProperty["OptedOut"] = "opted_out";
|
|
17
|
-
PostHogPersistedProperty["SessionId"] = "session_id";
|
|
18
|
-
PostHogPersistedProperty["SessionStartTimestamp"] = "session_start_timestamp";
|
|
19
|
-
PostHogPersistedProperty["SessionLastTimestamp"] = "session_timestamp";
|
|
20
|
-
PostHogPersistedProperty["PersonProperties"] = "person_properties";
|
|
21
|
-
PostHogPersistedProperty["GroupProperties"] = "group_properties";
|
|
22
|
-
PostHogPersistedProperty["InstalledAppBuild"] = "installed_app_build";
|
|
23
|
-
PostHogPersistedProperty["InstalledAppVersion"] = "installed_app_version";
|
|
24
|
-
PostHogPersistedProperty["SessionReplay"] = "session_replay";
|
|
25
|
-
PostHogPersistedProperty["SurveyLastSeenDate"] = "survey_last_seen_date";
|
|
26
|
-
PostHogPersistedProperty["SurveysSeen"] = "surveys_seen";
|
|
27
|
-
PostHogPersistedProperty["Surveys"] = "surveys";
|
|
28
|
-
PostHogPersistedProperty["RemoteConfig"] = "remote_config";
|
|
29
|
-
PostHogPersistedProperty["FlagsEndpointWasHit"] = "flags_endpoint_was_hit";
|
|
30
|
-
})(PostHogPersistedProperty || (PostHogPersistedProperty = {}));
|
|
31
|
-
// Any key prefixed with `attr__` can be added
|
|
32
|
-
var Compression;
|
|
33
|
-
(function (Compression) {
|
|
34
|
-
Compression["GZipJS"] = "gzip-js";
|
|
35
|
-
Compression["Base64"] = "base64";
|
|
36
|
-
})(Compression || (Compression = {}));
|
|
37
|
-
var SurveyPosition;
|
|
38
|
-
(function (SurveyPosition) {
|
|
39
|
-
SurveyPosition["Left"] = "left";
|
|
40
|
-
SurveyPosition["Right"] = "right";
|
|
41
|
-
SurveyPosition["Center"] = "center";
|
|
42
|
-
})(SurveyPosition || (SurveyPosition = {}));
|
|
43
|
-
var SurveyWidgetType;
|
|
44
|
-
(function (SurveyWidgetType) {
|
|
45
|
-
SurveyWidgetType["Button"] = "button";
|
|
46
|
-
SurveyWidgetType["Tab"] = "tab";
|
|
47
|
-
SurveyWidgetType["Selector"] = "selector";
|
|
48
|
-
})(SurveyWidgetType || (SurveyWidgetType = {}));
|
|
49
|
-
var SurveyType;
|
|
50
|
-
(function (SurveyType) {
|
|
51
|
-
SurveyType["Popover"] = "popover";
|
|
52
|
-
SurveyType["API"] = "api";
|
|
53
|
-
SurveyType["Widget"] = "widget";
|
|
54
|
-
})(SurveyType || (SurveyType = {}));
|
|
55
|
-
var SurveyQuestionDescriptionContentType;
|
|
56
|
-
(function (SurveyQuestionDescriptionContentType) {
|
|
57
|
-
SurveyQuestionDescriptionContentType["Html"] = "html";
|
|
58
|
-
SurveyQuestionDescriptionContentType["Text"] = "text";
|
|
59
|
-
})(SurveyQuestionDescriptionContentType || (SurveyQuestionDescriptionContentType = {}));
|
|
60
|
-
var SurveyRatingDisplay;
|
|
61
|
-
(function (SurveyRatingDisplay) {
|
|
62
|
-
SurveyRatingDisplay["Number"] = "number";
|
|
63
|
-
SurveyRatingDisplay["Emoji"] = "emoji";
|
|
64
|
-
})(SurveyRatingDisplay || (SurveyRatingDisplay = {}));
|
|
65
|
-
var SurveyQuestionType;
|
|
66
|
-
(function (SurveyQuestionType) {
|
|
67
|
-
SurveyQuestionType["Open"] = "open";
|
|
68
|
-
SurveyQuestionType["MultipleChoice"] = "multiple_choice";
|
|
69
|
-
SurveyQuestionType["SingleChoice"] = "single_choice";
|
|
70
|
-
SurveyQuestionType["Rating"] = "rating";
|
|
71
|
-
SurveyQuestionType["Link"] = "link";
|
|
72
|
-
})(SurveyQuestionType || (SurveyQuestionType = {}));
|
|
73
|
-
var SurveyQuestionBranchingType;
|
|
74
|
-
(function (SurveyQuestionBranchingType) {
|
|
75
|
-
SurveyQuestionBranchingType["NextQuestion"] = "next_question";
|
|
76
|
-
SurveyQuestionBranchingType["End"] = "end";
|
|
77
|
-
SurveyQuestionBranchingType["ResponseBased"] = "response_based";
|
|
78
|
-
SurveyQuestionBranchingType["SpecificQuestion"] = "specific_question";
|
|
79
|
-
})(SurveyQuestionBranchingType || (SurveyQuestionBranchingType = {}));
|
|
80
|
-
var SurveyMatchType;
|
|
81
|
-
(function (SurveyMatchType) {
|
|
82
|
-
SurveyMatchType["Regex"] = "regex";
|
|
83
|
-
SurveyMatchType["NotRegex"] = "not_regex";
|
|
84
|
-
SurveyMatchType["Exact"] = "exact";
|
|
85
|
-
SurveyMatchType["IsNot"] = "is_not";
|
|
86
|
-
SurveyMatchType["Icontains"] = "icontains";
|
|
87
|
-
SurveyMatchType["NotIcontains"] = "not_icontains";
|
|
88
|
-
})(SurveyMatchType || (SurveyMatchType = {}));
|
|
89
|
-
/** Sync with plugin-server/src/types.ts */
|
|
90
|
-
var ActionStepStringMatching;
|
|
91
|
-
(function (ActionStepStringMatching) {
|
|
92
|
-
ActionStepStringMatching["Contains"] = "contains";
|
|
93
|
-
ActionStepStringMatching["Exact"] = "exact";
|
|
94
|
-
ActionStepStringMatching["Regex"] = "regex";
|
|
95
|
-
})(ActionStepStringMatching || (ActionStepStringMatching = {}));
|
|
96
|
-
|
|
97
|
-
const normalizeFlagsResponse = (flagsResponse) => {
|
|
98
|
-
if ('flags' in flagsResponse) {
|
|
99
|
-
// Convert v2 format to v1 format
|
|
100
|
-
const featureFlags = getFlagValuesFromFlags(flagsResponse.flags);
|
|
101
|
-
const featureFlagPayloads = getPayloadsFromFlags(flagsResponse.flags);
|
|
102
|
-
return {
|
|
103
|
-
...flagsResponse,
|
|
104
|
-
featureFlags,
|
|
105
|
-
featureFlagPayloads,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
// Convert v1 format to v2 format
|
|
110
|
-
const featureFlags = flagsResponse.featureFlags ?? {};
|
|
111
|
-
const featureFlagPayloads = Object.fromEntries(Object.entries(flagsResponse.featureFlagPayloads || {}).map(([k, v]) => [k, parsePayload(v)]));
|
|
112
|
-
const flags = Object.fromEntries(Object.entries(featureFlags).map(([key, value]) => [
|
|
113
|
-
key,
|
|
114
|
-
getFlagDetailFromFlagAndPayload(key, value, featureFlagPayloads[key]),
|
|
115
|
-
]));
|
|
116
|
-
return {
|
|
117
|
-
...flagsResponse,
|
|
118
|
-
featureFlags,
|
|
119
|
-
featureFlagPayloads,
|
|
120
|
-
flags,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
function getFlagDetailFromFlagAndPayload(key, value, payload) {
|
|
125
|
-
return {
|
|
126
|
-
key: key,
|
|
127
|
-
enabled: typeof value === 'string' ? true : value,
|
|
128
|
-
variant: typeof value === 'string' ? value : undefined,
|
|
129
|
-
reason: undefined,
|
|
130
|
-
metadata: {
|
|
131
|
-
id: undefined,
|
|
132
|
-
version: undefined,
|
|
133
|
-
payload: payload ? JSON.stringify(payload) : undefined,
|
|
134
|
-
description: undefined,
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Get the flag values from the flags v4 response.
|
|
140
|
-
* @param flags - The flags
|
|
141
|
-
* @returns The flag values
|
|
142
|
-
*/
|
|
143
|
-
const getFlagValuesFromFlags = (flags) => {
|
|
144
|
-
return Object.fromEntries(Object.entries(flags ?? {})
|
|
145
|
-
.map(([key, detail]) => [key, getFeatureFlagValue(detail)])
|
|
146
|
-
.filter(([, value]) => value !== undefined));
|
|
147
|
-
};
|
|
148
|
-
/**
|
|
149
|
-
* Get the payloads from the flags v4 response.
|
|
150
|
-
* @param flags - The flags
|
|
151
|
-
* @returns The payloads
|
|
152
|
-
*/
|
|
153
|
-
const getPayloadsFromFlags = (flags) => {
|
|
154
|
-
const safeFlags = flags ?? {};
|
|
155
|
-
return Object.fromEntries(Object.keys(safeFlags)
|
|
156
|
-
.filter((flag) => {
|
|
157
|
-
const details = safeFlags[flag];
|
|
158
|
-
return details.enabled && details.metadata && details.metadata.payload !== undefined;
|
|
159
|
-
})
|
|
160
|
-
.map((flag) => {
|
|
161
|
-
const payload = safeFlags[flag].metadata?.payload;
|
|
162
|
-
return [flag, payload ? parsePayload(payload) : undefined];
|
|
163
|
-
}));
|
|
164
|
-
};
|
|
165
|
-
const getFeatureFlagValue = (detail) => {
|
|
166
|
-
return detail === undefined ? undefined : detail.variant ?? detail.enabled;
|
|
167
|
-
};
|
|
168
|
-
const parsePayload = (response) => {
|
|
169
|
-
if (typeof response !== 'string') {
|
|
170
|
-
return response;
|
|
171
|
-
}
|
|
172
|
-
try {
|
|
173
|
-
return JSON.parse(response);
|
|
174
|
-
}
|
|
175
|
-
catch {
|
|
176
|
-
return response;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
/**
|
|
180
|
-
* Get the normalized flag details from the flags and payloads.
|
|
181
|
-
* This is used to convert things like boostrap and stored feature flags and payloads to the v4 format.
|
|
182
|
-
* This helps us ensure backwards compatibility.
|
|
183
|
-
* If a key exists in the featureFlagPayloads that is not in the featureFlags, we treat it as a true feature flag.
|
|
184
|
-
*
|
|
185
|
-
* @param featureFlags - The feature flags
|
|
186
|
-
* @param featureFlagPayloads - The feature flag payloads
|
|
187
|
-
* @returns The normalized flag details
|
|
188
|
-
*/
|
|
189
|
-
const createFlagsResponseFromFlagsAndPayloads = (featureFlags, featureFlagPayloads) => {
|
|
190
|
-
// If a feature flag payload key is not in the feature flags, we treat it as true feature flag.
|
|
191
|
-
const allKeys = [...new Set([...Object.keys(featureFlags ?? {}), ...Object.keys(featureFlagPayloads ?? {})])];
|
|
192
|
-
const enabledFlags = allKeys
|
|
193
|
-
.filter((flag) => !!featureFlags[flag] || !!featureFlagPayloads[flag])
|
|
194
|
-
.reduce((res, key) => ((res[key] = featureFlags[key] ?? true), res), {});
|
|
195
|
-
const flagDetails = {
|
|
196
|
-
featureFlags: enabledFlags,
|
|
197
|
-
featureFlagPayloads: featureFlagPayloads ?? {},
|
|
198
|
-
};
|
|
199
|
-
return normalizeFlagsResponse(flagDetails);
|
|
200
|
-
};
|
|
201
|
-
const updateFlagValue = (flag, value) => {
|
|
202
|
-
return {
|
|
203
|
-
...flag,
|
|
204
|
-
enabled: getEnabledFromValue(value),
|
|
205
|
-
variant: getVariantFromValue(value),
|
|
206
|
-
};
|
|
207
|
-
};
|
|
208
|
-
function getEnabledFromValue(value) {
|
|
209
|
-
return typeof value === 'string' ? true : value;
|
|
210
|
-
}
|
|
211
|
-
function getVariantFromValue(value) {
|
|
212
|
-
return typeof value === 'string' ? value : undefined;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const STRING_FORMAT = 'utf8';
|
|
216
|
-
function assert(truthyValue, message) {
|
|
217
|
-
if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
|
|
218
|
-
throw new Error(message);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
function isEmpty(truthyValue) {
|
|
222
|
-
if (truthyValue.trim().length === 0) {
|
|
223
|
-
return true;
|
|
224
|
-
}
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
function removeTrailingSlash(url) {
|
|
228
|
-
return url?.replace(/\/+$/, '');
|
|
229
|
-
}
|
|
230
|
-
async function retriable(fn, props) {
|
|
231
|
-
let lastError = null;
|
|
232
|
-
for (let i = 0; i < props.retryCount + 1; i++) {
|
|
233
|
-
if (i > 0) {
|
|
234
|
-
// don't wait when it's the last try
|
|
235
|
-
await new Promise((r) => setTimeout(r, props.retryDelay));
|
|
236
|
-
}
|
|
237
|
-
try {
|
|
238
|
-
const res = await fn();
|
|
239
|
-
return res;
|
|
240
|
-
}
|
|
241
|
-
catch (e) {
|
|
242
|
-
lastError = e;
|
|
243
|
-
if (!props.retryCheck(e)) {
|
|
244
|
-
throw e;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
throw lastError;
|
|
249
|
-
}
|
|
250
|
-
function currentTimestamp() {
|
|
251
|
-
return new Date().getTime();
|
|
252
|
-
}
|
|
253
|
-
function currentISOTime() {
|
|
254
|
-
return new Date().toISOString();
|
|
255
|
-
}
|
|
256
|
-
function safeSetTimeout(fn, timeout) {
|
|
257
|
-
// NOTE: we use this so rarely that it is totally fine to do `safeSetTimeout(fn, 0)``
|
|
258
|
-
// rather than setImmediate.
|
|
259
|
-
const t = setTimeout(fn, timeout);
|
|
260
|
-
// We unref if available to prevent Node.js hanging on exit
|
|
261
|
-
t?.unref && t?.unref();
|
|
262
|
-
return t;
|
|
263
|
-
}
|
|
264
|
-
const isError = (x) => {
|
|
265
|
-
return x instanceof Error;
|
|
266
|
-
};
|
|
267
|
-
function getFetch() {
|
|
268
|
-
return typeof fetch !== 'undefined' ? fetch : typeof globalThis.fetch !== 'undefined' ? globalThis.fetch : undefined;
|
|
269
|
-
}
|
|
270
|
-
function allSettled(promises) {
|
|
271
|
-
return Promise.all(promises.map((p) => (p ?? Promise.resolve()).then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }))));
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Older browsers and some runtimes don't support this yet
|
|
276
|
-
* This API (as of 2025-05-07) is not available on React Native.
|
|
277
|
-
*/
|
|
278
|
-
function isGzipSupported() {
|
|
279
|
-
return 'CompressionStream' in globalThis;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Gzip a string using Compression Streams API if it's available
|
|
283
|
-
*/
|
|
284
|
-
async function gzipCompress(input, isDebug = true) {
|
|
285
|
-
try {
|
|
286
|
-
// Turn the string into a stream using a Blob, and then compress it
|
|
287
|
-
const dataStream = new Blob([input], {
|
|
288
|
-
type: 'text/plain',
|
|
289
|
-
}).stream();
|
|
290
|
-
const compressedStream = dataStream.pipeThrough(new CompressionStream('gzip'));
|
|
291
|
-
// Using a Response to easily extract the readablestream value. Decoding into a string for fetch
|
|
292
|
-
return await new Response(compressedStream).blob();
|
|
293
|
-
}
|
|
294
|
-
catch (error) {
|
|
295
|
-
if (isDebug) {
|
|
296
|
-
console.error('Failed to gzip compress data', error);
|
|
297
|
-
}
|
|
298
|
-
return null;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
class SimpleEventEmitter {
|
|
303
|
-
constructor() {
|
|
304
|
-
this.events = {};
|
|
305
|
-
this.events = {};
|
|
306
|
-
}
|
|
307
|
-
on(event, listener) {
|
|
308
|
-
if (!this.events[event]) {
|
|
309
|
-
this.events[event] = [];
|
|
310
|
-
}
|
|
311
|
-
this.events[event].push(listener);
|
|
312
|
-
return () => {
|
|
313
|
-
this.events[event] = this.events[event].filter((x) => x !== listener);
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
emit(event, payload) {
|
|
317
|
-
for (const listener of this.events[event] || []) {
|
|
318
|
-
listener(payload);
|
|
319
|
-
}
|
|
320
|
-
for (const listener of this.events['*'] || []) {
|
|
321
|
-
listener(event, payload);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// vendor from: https://github.com/LiosK/uuidv7/blob/f30b7a7faff73afbce0b27a46c638310f96912ba/src/index.ts
|
|
327
|
-
// https://github.com/LiosK/uuidv7#license
|
|
328
|
-
/**
|
|
329
|
-
* uuidv7: An experimental implementation of the proposed UUID Version 7
|
|
330
|
-
*
|
|
331
|
-
* @license Apache-2.0
|
|
332
|
-
* @copyright 2021-2023 LiosK
|
|
333
|
-
* @packageDocumentation
|
|
334
|
-
*/
|
|
335
|
-
const DIGITS = "0123456789abcdef";
|
|
336
|
-
/** Represents a UUID as a 16-byte byte array. */
|
|
337
|
-
class UUID {
|
|
338
|
-
/** @param bytes - The 16-byte byte array representation. */
|
|
339
|
-
constructor(bytes) {
|
|
340
|
-
this.bytes = bytes;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Creates an object from the internal representation, a 16-byte byte array
|
|
344
|
-
* containing the binary UUID representation in the big-endian byte order.
|
|
345
|
-
*
|
|
346
|
-
* This method does NOT shallow-copy the argument, and thus the created object
|
|
347
|
-
* holds the reference to the underlying buffer.
|
|
348
|
-
*
|
|
349
|
-
* @throws TypeError if the length of the argument is not 16.
|
|
350
|
-
*/
|
|
351
|
-
static ofInner(bytes) {
|
|
352
|
-
if (bytes.length !== 16) {
|
|
353
|
-
throw new TypeError("not 128-bit length");
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
return new UUID(bytes);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Builds a byte array from UUIDv7 field values.
|
|
361
|
-
*
|
|
362
|
-
* @param unixTsMs - A 48-bit `unix_ts_ms` field value.
|
|
363
|
-
* @param randA - A 12-bit `rand_a` field value.
|
|
364
|
-
* @param randBHi - The higher 30 bits of 62-bit `rand_b` field value.
|
|
365
|
-
* @param randBLo - The lower 32 bits of 62-bit `rand_b` field value.
|
|
366
|
-
* @throws RangeError if any field value is out of the specified range.
|
|
367
|
-
*/
|
|
368
|
-
static fromFieldsV7(unixTsMs, randA, randBHi, randBLo) {
|
|
369
|
-
if (!Number.isInteger(unixTsMs) ||
|
|
370
|
-
!Number.isInteger(randA) ||
|
|
371
|
-
!Number.isInteger(randBHi) ||
|
|
372
|
-
!Number.isInteger(randBLo) ||
|
|
373
|
-
unixTsMs < 0 ||
|
|
374
|
-
randA < 0 ||
|
|
375
|
-
randBHi < 0 ||
|
|
376
|
-
randBLo < 0 ||
|
|
377
|
-
unixTsMs > 281474976710655 ||
|
|
378
|
-
randA > 0xfff ||
|
|
379
|
-
randBHi > 1073741823 ||
|
|
380
|
-
randBLo > 4294967295) {
|
|
381
|
-
throw new RangeError("invalid field value");
|
|
382
|
-
}
|
|
383
|
-
const bytes = new Uint8Array(16);
|
|
384
|
-
bytes[0] = unixTsMs / 2 ** 40;
|
|
385
|
-
bytes[1] = unixTsMs / 2 ** 32;
|
|
386
|
-
bytes[2] = unixTsMs / 2 ** 24;
|
|
387
|
-
bytes[3] = unixTsMs / 2 ** 16;
|
|
388
|
-
bytes[4] = unixTsMs / 2 ** 8;
|
|
389
|
-
bytes[5] = unixTsMs;
|
|
390
|
-
bytes[6] = 0x70 | (randA >>> 8);
|
|
391
|
-
bytes[7] = randA;
|
|
392
|
-
bytes[8] = 0x80 | (randBHi >>> 24);
|
|
393
|
-
bytes[9] = randBHi >>> 16;
|
|
394
|
-
bytes[10] = randBHi >>> 8;
|
|
395
|
-
bytes[11] = randBHi;
|
|
396
|
-
bytes[12] = randBLo >>> 24;
|
|
397
|
-
bytes[13] = randBLo >>> 16;
|
|
398
|
-
bytes[14] = randBLo >>> 8;
|
|
399
|
-
bytes[15] = randBLo;
|
|
400
|
-
return new UUID(bytes);
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Builds a byte array from a string representation.
|
|
404
|
-
*
|
|
405
|
-
* This method accepts the following formats:
|
|
406
|
-
*
|
|
407
|
-
* - 32-digit hexadecimal format without hyphens: `0189dcd553117d408db09496a2eef37b`
|
|
408
|
-
* - 8-4-4-4-12 hyphenated format: `0189dcd5-5311-7d40-8db0-9496a2eef37b`
|
|
409
|
-
* - Hyphenated format with surrounding braces: `{0189dcd5-5311-7d40-8db0-9496a2eef37b}`
|
|
410
|
-
* - RFC 4122 URN format: `urn:uuid:0189dcd5-5311-7d40-8db0-9496a2eef37b`
|
|
411
|
-
*
|
|
412
|
-
* Leading and trailing whitespaces represents an error.
|
|
413
|
-
*
|
|
414
|
-
* @throws SyntaxError if the argument could not parse as a valid UUID string.
|
|
415
|
-
*/
|
|
416
|
-
static parse(uuid) {
|
|
417
|
-
let hex = undefined;
|
|
418
|
-
switch (uuid.length) {
|
|
419
|
-
case 32:
|
|
420
|
-
hex = /^[0-9a-f]{32}$/i.exec(uuid)?.[0];
|
|
421
|
-
break;
|
|
422
|
-
case 36:
|
|
423
|
-
hex =
|
|
424
|
-
/^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i
|
|
425
|
-
.exec(uuid)
|
|
426
|
-
?.slice(1, 6)
|
|
427
|
-
.join("");
|
|
428
|
-
break;
|
|
429
|
-
case 38:
|
|
430
|
-
hex =
|
|
431
|
-
/^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i
|
|
432
|
-
.exec(uuid)
|
|
433
|
-
?.slice(1, 6)
|
|
434
|
-
.join("");
|
|
435
|
-
break;
|
|
436
|
-
case 45:
|
|
437
|
-
hex =
|
|
438
|
-
/^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i
|
|
439
|
-
.exec(uuid)
|
|
440
|
-
?.slice(1, 6)
|
|
441
|
-
.join("");
|
|
442
|
-
break;
|
|
443
|
-
}
|
|
444
|
-
if (hex) {
|
|
445
|
-
const inner = new Uint8Array(16);
|
|
446
|
-
for (let i = 0; i < 16; i += 4) {
|
|
447
|
-
const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16);
|
|
448
|
-
inner[i + 0] = n >>> 24;
|
|
449
|
-
inner[i + 1] = n >>> 16;
|
|
450
|
-
inner[i + 2] = n >>> 8;
|
|
451
|
-
inner[i + 3] = n;
|
|
452
|
-
}
|
|
453
|
-
return new UUID(inner);
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
throw new SyntaxError("could not parse UUID string");
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* @returns The 8-4-4-4-12 canonical hexadecimal string representation
|
|
461
|
-
* (`0189dcd5-5311-7d40-8db0-9496a2eef37b`).
|
|
462
|
-
*/
|
|
463
|
-
toString() {
|
|
464
|
-
let text = "";
|
|
465
|
-
for (let i = 0; i < this.bytes.length; i++) {
|
|
466
|
-
text += DIGITS.charAt(this.bytes[i] >>> 4);
|
|
467
|
-
text += DIGITS.charAt(this.bytes[i] & 0xf);
|
|
468
|
-
if (i === 3 || i === 5 || i === 7 || i === 9) {
|
|
469
|
-
text += "-";
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
return text;
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* @returns The 32-digit hexadecimal representation without hyphens
|
|
476
|
-
* (`0189dcd553117d408db09496a2eef37b`).
|
|
477
|
-
*/
|
|
478
|
-
toHex() {
|
|
479
|
-
let text = "";
|
|
480
|
-
for (let i = 0; i < this.bytes.length; i++) {
|
|
481
|
-
text += DIGITS.charAt(this.bytes[i] >>> 4);
|
|
482
|
-
text += DIGITS.charAt(this.bytes[i] & 0xf);
|
|
483
|
-
}
|
|
484
|
-
return text;
|
|
485
|
-
}
|
|
486
|
-
/** @returns The 8-4-4-4-12 canonical hexadecimal string representation. */
|
|
487
|
-
toJSON() {
|
|
488
|
-
return this.toString();
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Reports the variant field value of the UUID or, if appropriate, "NIL" or
|
|
492
|
-
* "MAX".
|
|
493
|
-
*
|
|
494
|
-
* For convenience, this method reports "NIL" or "MAX" if `this` represents
|
|
495
|
-
* the Nil or Max UUID, although the Nil and Max UUIDs are technically
|
|
496
|
-
* subsumed under the variants `0b0` and `0b111`, respectively.
|
|
497
|
-
*/
|
|
498
|
-
getVariant() {
|
|
499
|
-
const n = this.bytes[8] >>> 4;
|
|
500
|
-
if (n < 0) {
|
|
501
|
-
throw new Error("unreachable");
|
|
502
|
-
}
|
|
503
|
-
else if (n <= 0b0111) {
|
|
504
|
-
return this.bytes.every((e) => e === 0) ? "NIL" : "VAR_0";
|
|
505
|
-
}
|
|
506
|
-
else if (n <= 0b1011) {
|
|
507
|
-
return "VAR_10";
|
|
508
|
-
}
|
|
509
|
-
else if (n <= 0b1101) {
|
|
510
|
-
return "VAR_110";
|
|
511
|
-
}
|
|
512
|
-
else if (n <= 0b1111) {
|
|
513
|
-
return this.bytes.every((e) => e === 0xff) ? "MAX" : "VAR_RESERVED";
|
|
514
|
-
}
|
|
515
|
-
else {
|
|
516
|
-
throw new Error("unreachable");
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* Returns the version field value of the UUID or `undefined` if the UUID does
|
|
521
|
-
* not have the variant field value of `0b10`.
|
|
522
|
-
*/
|
|
523
|
-
getVersion() {
|
|
524
|
-
return this.getVariant() === "VAR_10" ? this.bytes[6] >>> 4 : undefined;
|
|
525
|
-
}
|
|
526
|
-
/** Creates an object from `this`. */
|
|
527
|
-
clone() {
|
|
528
|
-
return new UUID(this.bytes.slice(0));
|
|
529
|
-
}
|
|
530
|
-
/** Returns true if `this` is equivalent to `other`. */
|
|
531
|
-
equals(other) {
|
|
532
|
-
return this.compareTo(other) === 0;
|
|
533
|
-
}
|
|
534
|
-
/**
|
|
535
|
-
* Returns a negative integer, zero, or positive integer if `this` is less
|
|
536
|
-
* than, equal to, or greater than `other`, respectively.
|
|
537
|
-
*/
|
|
538
|
-
compareTo(other) {
|
|
539
|
-
for (let i = 0; i < 16; i++) {
|
|
540
|
-
const diff = this.bytes[i] - other.bytes[i];
|
|
541
|
-
if (diff !== 0) {
|
|
542
|
-
return Math.sign(diff);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
return 0;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Encapsulates the monotonic counter state.
|
|
550
|
-
*
|
|
551
|
-
* This class provides APIs to utilize a separate counter state from that of the
|
|
552
|
-
* global generator used by {@link uuidv7} and {@link uuidv7obj}. In addition to
|
|
553
|
-
* the default {@link generate} method, this class has {@link generateOrAbort}
|
|
554
|
-
* that is useful to absolutely guarantee the monotonically increasing order of
|
|
555
|
-
* generated UUIDs. See their respective documentation for details.
|
|
556
|
-
*/
|
|
557
|
-
class V7Generator {
|
|
558
|
-
/**
|
|
559
|
-
* Creates a generator object with the default random number generator, or
|
|
560
|
-
* with the specified one if passed as an argument. The specified random
|
|
561
|
-
* number generator should be cryptographically strong and securely seeded.
|
|
562
|
-
*/
|
|
563
|
-
constructor(randomNumberGenerator) {
|
|
564
|
-
this.timestamp = 0;
|
|
565
|
-
this.counter = 0;
|
|
566
|
-
this.random = randomNumberGenerator ?? getDefaultRandom();
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Generates a new UUIDv7 object from the current timestamp, or resets the
|
|
570
|
-
* generator upon significant timestamp rollback.
|
|
571
|
-
*
|
|
572
|
-
* This method returns a monotonically increasing UUID by reusing the previous
|
|
573
|
-
* timestamp even if the up-to-date timestamp is smaller than the immediately
|
|
574
|
-
* preceding UUID's. However, when such a clock rollback is considered
|
|
575
|
-
* significant (i.e., by more than ten seconds), this method resets the
|
|
576
|
-
* generator and returns a new UUID based on the given timestamp, breaking the
|
|
577
|
-
* increasing order of UUIDs.
|
|
578
|
-
*
|
|
579
|
-
* See {@link generateOrAbort} for the other mode of generation and
|
|
580
|
-
* {@link generateOrResetCore} for the low-level primitive.
|
|
581
|
-
*/
|
|
582
|
-
generate() {
|
|
583
|
-
return this.generateOrResetCore(Date.now(), 10000);
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Generates a new UUIDv7 object from the current timestamp, or returns
|
|
587
|
-
* `undefined` upon significant timestamp rollback.
|
|
588
|
-
*
|
|
589
|
-
* This method returns a monotonically increasing UUID by reusing the previous
|
|
590
|
-
* timestamp even if the up-to-date timestamp is smaller than the immediately
|
|
591
|
-
* preceding UUID's. However, when such a clock rollback is considered
|
|
592
|
-
* significant (i.e., by more than ten seconds), this method aborts and
|
|
593
|
-
* returns `undefined` immediately.
|
|
594
|
-
*
|
|
595
|
-
* See {@link generate} for the other mode of generation and
|
|
596
|
-
* {@link generateOrAbortCore} for the low-level primitive.
|
|
597
|
-
*/
|
|
598
|
-
generateOrAbort() {
|
|
599
|
-
return this.generateOrAbortCore(Date.now(), 10000);
|
|
600
|
-
}
|
|
601
|
-
/**
|
|
602
|
-
* Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the
|
|
603
|
-
* generator upon significant timestamp rollback.
|
|
604
|
-
*
|
|
605
|
-
* This method is equivalent to {@link generate} except that it takes a custom
|
|
606
|
-
* timestamp and clock rollback allowance.
|
|
607
|
-
*
|
|
608
|
-
* @param rollbackAllowance - The amount of `unixTsMs` rollback that is
|
|
609
|
-
* considered significant. A suggested value is `10_000` (milliseconds).
|
|
610
|
-
* @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
|
|
611
|
-
*/
|
|
612
|
-
generateOrResetCore(unixTsMs, rollbackAllowance) {
|
|
613
|
-
let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
|
|
614
|
-
if (value === undefined) {
|
|
615
|
-
// reset state and resume
|
|
616
|
-
this.timestamp = 0;
|
|
617
|
-
value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
|
|
618
|
-
}
|
|
619
|
-
return value;
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Generates a new UUIDv7 object from the `unixTsMs` passed, or returns
|
|
623
|
-
* `undefined` upon significant timestamp rollback.
|
|
624
|
-
*
|
|
625
|
-
* This method is equivalent to {@link generateOrAbort} except that it takes a
|
|
626
|
-
* custom timestamp and clock rollback allowance.
|
|
627
|
-
*
|
|
628
|
-
* @param rollbackAllowance - The amount of `unixTsMs` rollback that is
|
|
629
|
-
* considered significant. A suggested value is `10_000` (milliseconds).
|
|
630
|
-
* @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
|
|
631
|
-
*/
|
|
632
|
-
generateOrAbortCore(unixTsMs, rollbackAllowance) {
|
|
633
|
-
const MAX_COUNTER = 4398046511103;
|
|
634
|
-
if (!Number.isInteger(unixTsMs) ||
|
|
635
|
-
unixTsMs < 1 ||
|
|
636
|
-
unixTsMs > 281474976710655) {
|
|
637
|
-
throw new RangeError("`unixTsMs` must be a 48-bit positive integer");
|
|
638
|
-
}
|
|
639
|
-
else if (rollbackAllowance < 0 || rollbackAllowance > 281474976710655) {
|
|
640
|
-
throw new RangeError("`rollbackAllowance` out of reasonable range");
|
|
641
|
-
}
|
|
642
|
-
if (unixTsMs > this.timestamp) {
|
|
643
|
-
this.timestamp = unixTsMs;
|
|
644
|
-
this.resetCounter();
|
|
645
|
-
}
|
|
646
|
-
else if (unixTsMs + rollbackAllowance >= this.timestamp) {
|
|
647
|
-
// go on with previous timestamp if new one is not much smaller
|
|
648
|
-
this.counter++;
|
|
649
|
-
if (this.counter > MAX_COUNTER) {
|
|
650
|
-
// increment timestamp at counter overflow
|
|
651
|
-
this.timestamp++;
|
|
652
|
-
this.resetCounter();
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
else {
|
|
656
|
-
// abort if clock went backwards to unbearable extent
|
|
657
|
-
return undefined;
|
|
658
|
-
}
|
|
659
|
-
return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & (2 ** 30 - 1), this.random.nextUint32());
|
|
660
|
-
}
|
|
661
|
-
/** Initializes the counter at a 42-bit random integer. */
|
|
662
|
-
resetCounter() {
|
|
663
|
-
this.counter =
|
|
664
|
-
this.random.nextUint32() * 0x400 + (this.random.nextUint32() & 0x3ff);
|
|
665
|
-
}
|
|
666
|
-
/**
|
|
667
|
-
* Generates a new UUIDv4 object utilizing the random number generator inside.
|
|
668
|
-
*
|
|
669
|
-
* @internal
|
|
670
|
-
*/
|
|
671
|
-
generateV4() {
|
|
672
|
-
const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer);
|
|
673
|
-
bytes[6] = 0x40 | (bytes[6] >>> 4);
|
|
674
|
-
bytes[8] = 0x80 | (bytes[8] >>> 2);
|
|
675
|
-
return UUID.ofInner(bytes);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
/** A global flag to force use of cryptographically strong RNG. */
|
|
679
|
-
// declare const UUIDV7_DENY_WEAK_RNG: boolean;
|
|
680
|
-
/** Returns the default random number generator available in the environment. */
|
|
681
|
-
const getDefaultRandom = () => {
|
|
682
|
-
// fix: crypto isn't available in react-native, always use Math.random
|
|
683
|
-
// // detect Web Crypto API
|
|
684
|
-
// if (
|
|
685
|
-
// typeof crypto !== "undefined" &&
|
|
686
|
-
// typeof crypto.getRandomValues !== "undefined"
|
|
687
|
-
// ) {
|
|
688
|
-
// return new BufferedCryptoRandom();
|
|
689
|
-
// } else {
|
|
690
|
-
// // fall back on Math.random() unless the flag is set to true
|
|
691
|
-
// if (typeof UUIDV7_DENY_WEAK_RNG !== "undefined" && UUIDV7_DENY_WEAK_RNG) {
|
|
692
|
-
// throw new Error("no cryptographically strong RNG available");
|
|
693
|
-
// }
|
|
694
|
-
// return {
|
|
695
|
-
// nextUint32: (): number =>
|
|
696
|
-
// Math.trunc(Math.random() * 0x1_0000) * 0x1_0000 +
|
|
697
|
-
// Math.trunc(Math.random() * 0x1_0000),
|
|
698
|
-
// };
|
|
699
|
-
// }
|
|
700
|
-
return {
|
|
701
|
-
nextUint32: () => Math.trunc(Math.random() * 65536) * 65536 +
|
|
702
|
-
Math.trunc(Math.random() * 65536),
|
|
703
|
-
};
|
|
704
|
-
};
|
|
705
|
-
// /**
|
|
706
|
-
// * Wraps `crypto.getRandomValues()` to enable buffering; this uses a small
|
|
707
|
-
// * buffer by default to avoid both unbearable throughput decline in some
|
|
708
|
-
// * environments and the waste of time and space for unused values.
|
|
709
|
-
// */
|
|
710
|
-
// class BufferedCryptoRandom {
|
|
711
|
-
// private readonly buffer = new Uint32Array(8);
|
|
712
|
-
// private cursor = 0xffff;
|
|
713
|
-
// nextUint32(): number {
|
|
714
|
-
// if (this.cursor >= this.buffer.length) {
|
|
715
|
-
// crypto.getRandomValues(this.buffer);
|
|
716
|
-
// this.cursor = 0;
|
|
717
|
-
// }
|
|
718
|
-
// return this.buffer[this.cursor++];
|
|
719
|
-
// }
|
|
720
|
-
// }
|
|
721
|
-
let defaultGenerator;
|
|
722
|
-
/**
|
|
723
|
-
* Generates a UUIDv7 string.
|
|
724
|
-
*
|
|
725
|
-
* @returns The 8-4-4-4-12 canonical hexadecimal string representation
|
|
726
|
-
* ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").
|
|
727
|
-
*/
|
|
728
|
-
const uuidv7 = () => uuidv7obj().toString();
|
|
729
|
-
/** Generates a UUIDv7 object. */
|
|
730
|
-
const uuidv7obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generate();
|
|
731
|
-
|
|
732
|
-
class PostHogFetchHttpError extends Error {
|
|
733
|
-
constructor(response, reqByteLength) {
|
|
734
|
-
super('HTTP error while fetching PostHog: status=' + response.status + ', reqByteLength=' + reqByteLength);
|
|
735
|
-
this.response = response;
|
|
736
|
-
this.reqByteLength = reqByteLength;
|
|
737
|
-
this.name = 'PostHogFetchHttpError';
|
|
738
|
-
}
|
|
739
|
-
get status() {
|
|
740
|
-
return this.response.status;
|
|
741
|
-
}
|
|
742
|
-
get text() {
|
|
743
|
-
return this.response.text();
|
|
744
|
-
}
|
|
745
|
-
get json() {
|
|
746
|
-
return this.response.json();
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
class PostHogFetchNetworkError extends Error {
|
|
750
|
-
constructor(error) {
|
|
751
|
-
// TRICKY: "cause" is a newer property but is just ignored otherwise. Cast to any to ignore the type issue.
|
|
752
|
-
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
|
753
|
-
// @ts-ignore
|
|
754
|
-
super('Network error while fetching PostHog', error instanceof Error ? { cause: error } : {});
|
|
755
|
-
this.error = error;
|
|
756
|
-
this.name = 'PostHogFetchNetworkError';
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
const maybeAdd = (key, value) => value !== undefined ? { [key]: value } : {};
|
|
760
|
-
async function logFlushError(err) {
|
|
761
|
-
if (err instanceof PostHogFetchHttpError) {
|
|
762
|
-
let text = '';
|
|
763
|
-
try {
|
|
764
|
-
text = await err.text;
|
|
765
|
-
}
|
|
766
|
-
catch { }
|
|
767
|
-
console.error(`Error while flushing PostHog: message=${err.message}, response body=${text}`, err);
|
|
768
|
-
}
|
|
769
|
-
else {
|
|
770
|
-
console.error('Error while flushing PostHog', err);
|
|
771
|
-
}
|
|
772
|
-
return Promise.resolve();
|
|
773
|
-
}
|
|
774
|
-
function isPostHogFetchError(err) {
|
|
775
|
-
return typeof err === 'object' && (err instanceof PostHogFetchHttpError || err instanceof PostHogFetchNetworkError);
|
|
776
|
-
}
|
|
777
|
-
function isPostHogFetchContentTooLargeError(err) {
|
|
778
|
-
return typeof err === 'object' && err instanceof PostHogFetchHttpError && err.status === 413;
|
|
779
|
-
}
|
|
780
|
-
var QuotaLimitedFeature;
|
|
781
|
-
(function (QuotaLimitedFeature) {
|
|
782
|
-
QuotaLimitedFeature["FeatureFlags"] = "feature_flags";
|
|
783
|
-
QuotaLimitedFeature["Recordings"] = "recordings";
|
|
784
|
-
})(QuotaLimitedFeature || (QuotaLimitedFeature = {}));
|
|
785
|
-
class PostHogCoreStateless {
|
|
786
|
-
constructor(apiKey, options) {
|
|
787
|
-
this.flushPromise = null;
|
|
788
|
-
this.shutdownPromise = null;
|
|
789
|
-
this.pendingPromises = {};
|
|
790
|
-
// internal
|
|
791
|
-
this._events = new SimpleEventEmitter();
|
|
792
|
-
this._isInitialized = false;
|
|
793
|
-
assert(apiKey, "You must pass your PostHog project's api key.");
|
|
794
|
-
this.apiKey = apiKey;
|
|
795
|
-
this.host = removeTrailingSlash(options?.host || 'https://us.i.posthog.com');
|
|
796
|
-
this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 20;
|
|
797
|
-
this.maxBatchSize = Math.max(this.flushAt, options?.maxBatchSize ?? 100);
|
|
798
|
-
this.maxQueueSize = Math.max(this.flushAt, options?.maxQueueSize ?? 1000);
|
|
799
|
-
this.flushInterval = options?.flushInterval ?? 10000;
|
|
800
|
-
this.preloadFeatureFlags = options?.preloadFeatureFlags ?? true;
|
|
801
|
-
// If enable is explicitly set to false we override the optout
|
|
802
|
-
this.defaultOptIn = options?.defaultOptIn ?? true;
|
|
803
|
-
this.disableSurveys = options?.disableSurveys ?? false;
|
|
804
|
-
this._retryOptions = {
|
|
805
|
-
retryCount: options?.fetchRetryCount ?? 3,
|
|
806
|
-
retryDelay: options?.fetchRetryDelay ?? 3000,
|
|
807
|
-
retryCheck: isPostHogFetchError,
|
|
808
|
-
};
|
|
809
|
-
this.requestTimeout = options?.requestTimeout ?? 10000; // 10 seconds
|
|
810
|
-
this.featureFlagsRequestTimeoutMs = options?.featureFlagsRequestTimeoutMs ?? 3000; // 3 seconds
|
|
811
|
-
this.remoteConfigRequestTimeoutMs = options?.remoteConfigRequestTimeoutMs ?? 3000; // 3 seconds
|
|
812
|
-
this.disableGeoip = options?.disableGeoip ?? true;
|
|
813
|
-
this.disabled = options?.disabled ?? false;
|
|
814
|
-
this.historicalMigration = options?.historicalMigration ?? false;
|
|
815
|
-
// Init promise allows the derived class to block calls until it is ready
|
|
816
|
-
this._initPromise = Promise.resolve();
|
|
817
|
-
this._isInitialized = true;
|
|
818
|
-
this.disableCompression = !isGzipSupported() || (options?.disableCompression ?? false);
|
|
819
|
-
}
|
|
820
|
-
logMsgIfDebug(fn) {
|
|
821
|
-
if (this.isDebug) {
|
|
822
|
-
fn();
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
wrap(fn) {
|
|
826
|
-
if (this.disabled) {
|
|
827
|
-
this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
if (this._isInitialized) {
|
|
831
|
-
// NOTE: We could also check for the "opt in" status here...
|
|
832
|
-
return fn();
|
|
833
|
-
}
|
|
834
|
-
this._initPromise.then(() => fn());
|
|
835
|
-
}
|
|
836
|
-
getCommonEventProperties() {
|
|
837
|
-
return {
|
|
838
|
-
$lib: this.getLibraryId(),
|
|
839
|
-
$lib_version: this.getLibraryVersion(),
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
get optedOut() {
|
|
843
|
-
return this.getPersistedProperty(PostHogPersistedProperty.OptedOut) ?? !this.defaultOptIn;
|
|
844
|
-
}
|
|
845
|
-
async optIn() {
|
|
846
|
-
this.wrap(() => {
|
|
847
|
-
this.setPersistedProperty(PostHogPersistedProperty.OptedOut, false);
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
async optOut() {
|
|
851
|
-
this.wrap(() => {
|
|
852
|
-
this.setPersistedProperty(PostHogPersistedProperty.OptedOut, true);
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
on(event, cb) {
|
|
856
|
-
return this._events.on(event, cb);
|
|
857
|
-
}
|
|
858
|
-
debug(enabled = true) {
|
|
859
|
-
this.removeDebugCallback?.();
|
|
860
|
-
if (enabled) {
|
|
861
|
-
const removeDebugCallback = this.on('*', (event, payload) => console.log('PostHog Debug', event, payload));
|
|
862
|
-
this.removeDebugCallback = () => {
|
|
863
|
-
removeDebugCallback();
|
|
864
|
-
this.removeDebugCallback = undefined;
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
get isDebug() {
|
|
869
|
-
return !!this.removeDebugCallback;
|
|
870
|
-
}
|
|
871
|
-
get isDisabled() {
|
|
872
|
-
return this.disabled;
|
|
873
|
-
}
|
|
874
|
-
buildPayload(payload) {
|
|
875
|
-
return {
|
|
876
|
-
distinct_id: payload.distinct_id,
|
|
877
|
-
event: payload.event,
|
|
878
|
-
properties: {
|
|
879
|
-
...(payload.properties || {}),
|
|
880
|
-
...this.getCommonEventProperties(), // Common PH props
|
|
881
|
-
},
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
addPendingPromise(promise) {
|
|
885
|
-
const promiseUUID = uuidv7();
|
|
886
|
-
this.pendingPromises[promiseUUID] = promise;
|
|
887
|
-
promise
|
|
888
|
-
.catch(() => { })
|
|
889
|
-
.finally(() => {
|
|
890
|
-
delete this.pendingPromises[promiseUUID];
|
|
891
|
-
});
|
|
892
|
-
return promise;
|
|
893
|
-
}
|
|
894
|
-
/***
|
|
895
|
-
*** TRACKING
|
|
896
|
-
***/
|
|
897
|
-
identifyStateless(distinctId, properties, options) {
|
|
898
|
-
this.wrap(() => {
|
|
899
|
-
// The properties passed to identifyStateless are event properties.
|
|
900
|
-
// To add person properties, pass in all person properties to the `$set` and `$set_once` keys.
|
|
901
|
-
const payload = {
|
|
902
|
-
...this.buildPayload({
|
|
903
|
-
distinct_id: distinctId,
|
|
904
|
-
event: '$identify',
|
|
905
|
-
properties,
|
|
906
|
-
}),
|
|
907
|
-
};
|
|
908
|
-
this.enqueue('identify', payload, options);
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
async identifyStatelessImmediate(distinctId, properties, options) {
|
|
912
|
-
const payload = {
|
|
913
|
-
...this.buildPayload({
|
|
914
|
-
distinct_id: distinctId,
|
|
915
|
-
event: '$identify',
|
|
916
|
-
properties,
|
|
917
|
-
}),
|
|
918
|
-
};
|
|
919
|
-
await this.sendImmediate('identify', payload, options);
|
|
920
|
-
}
|
|
921
|
-
captureStateless(distinctId, event, properties, options) {
|
|
922
|
-
this.wrap(() => {
|
|
923
|
-
const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
|
|
924
|
-
this.enqueue('capture', payload, options);
|
|
925
|
-
});
|
|
926
|
-
}
|
|
927
|
-
async captureStatelessImmediate(distinctId, event, properties, options) {
|
|
928
|
-
const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
|
|
929
|
-
await this.sendImmediate('capture', payload, options);
|
|
930
|
-
}
|
|
931
|
-
aliasStateless(alias, distinctId, properties, options) {
|
|
932
|
-
this.wrap(() => {
|
|
933
|
-
const payload = this.buildPayload({
|
|
934
|
-
event: '$create_alias',
|
|
935
|
-
distinct_id: distinctId,
|
|
936
|
-
properties: {
|
|
937
|
-
...(properties || {}),
|
|
938
|
-
distinct_id: distinctId,
|
|
939
|
-
alias,
|
|
940
|
-
},
|
|
941
|
-
});
|
|
942
|
-
this.enqueue('alias', payload, options);
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
async aliasStatelessImmediate(alias, distinctId, properties, options) {
|
|
946
|
-
const payload = this.buildPayload({
|
|
947
|
-
event: '$create_alias',
|
|
948
|
-
distinct_id: distinctId,
|
|
949
|
-
properties: {
|
|
950
|
-
...(properties || {}),
|
|
951
|
-
distinct_id: distinctId,
|
|
952
|
-
alias,
|
|
953
|
-
},
|
|
954
|
-
});
|
|
955
|
-
await this.sendImmediate('alias', payload, options);
|
|
956
|
-
}
|
|
957
|
-
/***
|
|
958
|
-
*** GROUPS
|
|
959
|
-
***/
|
|
960
|
-
groupIdentifyStateless(groupType, groupKey, groupProperties, options, distinctId, eventProperties) {
|
|
961
|
-
this.wrap(() => {
|
|
962
|
-
const payload = this.buildPayload({
|
|
963
|
-
distinct_id: distinctId || `$${groupType}_${groupKey}`,
|
|
964
|
-
event: '$groupidentify',
|
|
965
|
-
properties: {
|
|
966
|
-
$group_type: groupType,
|
|
967
|
-
$group_key: groupKey,
|
|
968
|
-
$group_set: groupProperties || {},
|
|
969
|
-
...(eventProperties || {}),
|
|
970
|
-
},
|
|
971
|
-
});
|
|
972
|
-
this.enqueue('capture', payload, options);
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
|
-
async getRemoteConfig() {
|
|
976
|
-
await this._initPromise;
|
|
977
|
-
let host = this.host;
|
|
978
|
-
if (host === 'https://us.i.posthog.com') {
|
|
979
|
-
host = 'https://us-assets.i.posthog.com';
|
|
980
|
-
}
|
|
981
|
-
else if (host === 'https://eu.i.posthog.com') {
|
|
982
|
-
host = 'https://eu-assets.i.posthog.com';
|
|
983
|
-
}
|
|
984
|
-
const url = `${host}/array/${this.apiKey}/config`;
|
|
985
|
-
const fetchOptions = {
|
|
986
|
-
method: 'GET',
|
|
987
|
-
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
988
|
-
};
|
|
989
|
-
// Don't retry remote config API calls
|
|
990
|
-
return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.remoteConfigRequestTimeoutMs)
|
|
991
|
-
.then((response) => response.json())
|
|
992
|
-
.catch((error) => {
|
|
993
|
-
this.logMsgIfDebug(() => console.error('Remote config could not be loaded', error));
|
|
994
|
-
this._events.emit('error', error);
|
|
995
|
-
return undefined;
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
/***
|
|
999
|
-
*** FEATURE FLAGS
|
|
1000
|
-
***/
|
|
1001
|
-
async getFlags(distinctId, groups = {}, personProperties = {}, groupProperties = {}, extraPayload = {}) {
|
|
1002
|
-
await this._initPromise;
|
|
1003
|
-
const url = `${this.host}/flags/?v=2&config=true`;
|
|
1004
|
-
const fetchOptions = {
|
|
1005
|
-
method: 'POST',
|
|
1006
|
-
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
1007
|
-
body: JSON.stringify({
|
|
1008
|
-
token: this.apiKey,
|
|
1009
|
-
distinct_id: distinctId,
|
|
1010
|
-
groups,
|
|
1011
|
-
person_properties: personProperties,
|
|
1012
|
-
group_properties: groupProperties,
|
|
1013
|
-
...extraPayload,
|
|
1014
|
-
}),
|
|
1015
|
-
};
|
|
1016
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Flags URL', url));
|
|
1017
|
-
// Don't retry /flags API calls
|
|
1018
|
-
return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
|
|
1019
|
-
.then((response) => response.json())
|
|
1020
|
-
.then((response) => normalizeFlagsResponse(response))
|
|
1021
|
-
.catch((error) => {
|
|
1022
|
-
this._events.emit('error', error);
|
|
1023
|
-
return undefined;
|
|
1024
|
-
});
|
|
1025
|
-
}
|
|
1026
|
-
async getFeatureFlagStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
|
|
1027
|
-
await this._initPromise;
|
|
1028
|
-
const flagDetailResponse = await this.getFeatureFlagDetailStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
|
|
1029
|
-
if (flagDetailResponse === undefined) {
|
|
1030
|
-
// If we haven't loaded flags yet, or errored out, we respond with undefined
|
|
1031
|
-
return {
|
|
1032
|
-
response: undefined,
|
|
1033
|
-
requestId: undefined,
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
let response = getFeatureFlagValue(flagDetailResponse.response);
|
|
1037
|
-
if (response === undefined) {
|
|
1038
|
-
// For cases where the flag is unknown, return false
|
|
1039
|
-
response = false;
|
|
1040
|
-
}
|
|
1041
|
-
// If we have flags we either return the value (true or string) or false
|
|
1042
|
-
return {
|
|
1043
|
-
response,
|
|
1044
|
-
requestId: flagDetailResponse.requestId,
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
async getFeatureFlagDetailStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
|
|
1048
|
-
await this._initPromise;
|
|
1049
|
-
const flagsResponse = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
|
|
1050
|
-
if (flagsResponse === undefined) {
|
|
1051
|
-
return undefined;
|
|
1052
|
-
}
|
|
1053
|
-
const featureFlags = flagsResponse.flags;
|
|
1054
|
-
const flagDetail = featureFlags[key];
|
|
1055
|
-
return {
|
|
1056
|
-
response: flagDetail,
|
|
1057
|
-
requestId: flagsResponse.requestId,
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
async getFeatureFlagPayloadStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
|
|
1061
|
-
await this._initPromise;
|
|
1062
|
-
const payloads = await this.getFeatureFlagPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
|
|
1063
|
-
if (!payloads) {
|
|
1064
|
-
return undefined;
|
|
1065
|
-
}
|
|
1066
|
-
const response = payloads[key];
|
|
1067
|
-
// Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
|
|
1068
|
-
if (response === undefined) {
|
|
1069
|
-
return null;
|
|
1070
|
-
}
|
|
1071
|
-
return response;
|
|
1072
|
-
}
|
|
1073
|
-
async getFeatureFlagPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
|
|
1074
|
-
await this._initPromise;
|
|
1075
|
-
const payloads = (await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate)).payloads;
|
|
1076
|
-
return payloads;
|
|
1077
|
-
}
|
|
1078
|
-
async getFeatureFlagsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
|
|
1079
|
-
await this._initPromise;
|
|
1080
|
-
return await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
|
|
1081
|
-
}
|
|
1082
|
-
async getFeatureFlagsAndPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
|
|
1083
|
-
await this._initPromise;
|
|
1084
|
-
const featureFlagDetails = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
|
|
1085
|
-
if (!featureFlagDetails) {
|
|
1086
|
-
return {
|
|
1087
|
-
flags: undefined,
|
|
1088
|
-
payloads: undefined,
|
|
1089
|
-
requestId: undefined,
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
return {
|
|
1093
|
-
flags: featureFlagDetails.featureFlags,
|
|
1094
|
-
payloads: featureFlagDetails.featureFlagPayloads,
|
|
1095
|
-
requestId: featureFlagDetails.requestId,
|
|
1096
|
-
};
|
|
1097
|
-
}
|
|
1098
|
-
async getFeatureFlagDetailsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
|
|
1099
|
-
await this._initPromise;
|
|
1100
|
-
const extraPayload = {};
|
|
1101
|
-
if (disableGeoip ?? this.disableGeoip) {
|
|
1102
|
-
extraPayload['geoip_disable'] = true;
|
|
1103
|
-
}
|
|
1104
|
-
if (flagKeysToEvaluate) {
|
|
1105
|
-
extraPayload['flag_keys_to_evaluate'] = flagKeysToEvaluate;
|
|
1106
|
-
}
|
|
1107
|
-
const flagsResponse = await this.getFlags(distinctId, groups, personProperties, groupProperties, extraPayload);
|
|
1108
|
-
if (flagsResponse === undefined) {
|
|
1109
|
-
// We probably errored out, so return undefined
|
|
1110
|
-
return undefined;
|
|
1111
|
-
}
|
|
1112
|
-
// if there's an error on the flagsResponse, log a console error, but don't throw an error
|
|
1113
|
-
if (flagsResponse.errorsWhileComputingFlags) {
|
|
1114
|
-
console.error('[FEATURE FLAGS] Error while computing feature flags, some flags may be missing or incorrect. Learn more at https://posthog.com/docs/feature-flags/best-practices');
|
|
1115
|
-
}
|
|
1116
|
-
// Add check for quota limitation on feature flags
|
|
1117
|
-
if (flagsResponse.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
|
|
1118
|
-
console.warn('[FEATURE FLAGS] Feature flags quota limit exceeded - feature flags unavailable. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts');
|
|
1119
|
-
return {
|
|
1120
|
-
flags: {},
|
|
1121
|
-
featureFlags: {},
|
|
1122
|
-
featureFlagPayloads: {},
|
|
1123
|
-
requestId: flagsResponse?.requestId,
|
|
1124
|
-
};
|
|
1125
|
-
}
|
|
1126
|
-
return flagsResponse;
|
|
1127
|
-
}
|
|
1128
|
-
/***
|
|
1129
|
-
*** SURVEYS
|
|
1130
|
-
***/
|
|
1131
|
-
async getSurveysStateless() {
|
|
1132
|
-
await this._initPromise;
|
|
1133
|
-
if (this.disableSurveys === true) {
|
|
1134
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Loading surveys is disabled.'));
|
|
1135
|
-
return [];
|
|
1136
|
-
}
|
|
1137
|
-
const url = `${this.host}/api/surveys/?token=${this.apiKey}`;
|
|
1138
|
-
const fetchOptions = {
|
|
1139
|
-
method: 'GET',
|
|
1140
|
-
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
1141
|
-
};
|
|
1142
|
-
const response = await this.fetchWithRetry(url, fetchOptions)
|
|
1143
|
-
.then((response) => {
|
|
1144
|
-
if (response.status !== 200 || !response.json) {
|
|
1145
|
-
const msg = `Surveys API could not be loaded: ${response.status}`;
|
|
1146
|
-
const error = new Error(msg);
|
|
1147
|
-
this.logMsgIfDebug(() => console.error(error));
|
|
1148
|
-
this._events.emit('error', new Error(msg));
|
|
1149
|
-
return undefined;
|
|
1150
|
-
}
|
|
1151
|
-
return response.json();
|
|
1152
|
-
})
|
|
1153
|
-
.catch((error) => {
|
|
1154
|
-
this.logMsgIfDebug(() => console.error('Surveys API could not be loaded', error));
|
|
1155
|
-
this._events.emit('error', error);
|
|
1156
|
-
return undefined;
|
|
1157
|
-
});
|
|
1158
|
-
const newSurveys = response?.surveys;
|
|
1159
|
-
if (newSurveys) {
|
|
1160
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from API: ', JSON.stringify(newSurveys)));
|
|
1161
|
-
}
|
|
1162
|
-
return newSurveys ?? [];
|
|
1163
|
-
}
|
|
1164
|
-
get props() {
|
|
1165
|
-
if (!this._props) {
|
|
1166
|
-
this._props = this.getPersistedProperty(PostHogPersistedProperty.Props);
|
|
1167
|
-
}
|
|
1168
|
-
return this._props || {};
|
|
1169
|
-
}
|
|
1170
|
-
set props(val) {
|
|
1171
|
-
this._props = val;
|
|
1172
|
-
}
|
|
1173
|
-
async register(properties) {
|
|
1174
|
-
this.wrap(() => {
|
|
1175
|
-
this.props = {
|
|
1176
|
-
...this.props,
|
|
1177
|
-
...properties,
|
|
1178
|
-
};
|
|
1179
|
-
this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
|
-
async unregister(property) {
|
|
1183
|
-
this.wrap(() => {
|
|
1184
|
-
delete this.props[property];
|
|
1185
|
-
this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
|
|
1186
|
-
});
|
|
1187
|
-
}
|
|
1188
|
-
/***
|
|
1189
|
-
*** QUEUEING AND FLUSHING
|
|
1190
|
-
***/
|
|
1191
|
-
enqueue(type, _message, options) {
|
|
1192
|
-
this.wrap(() => {
|
|
1193
|
-
if (this.optedOut) {
|
|
1194
|
-
this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
|
|
1195
|
-
return;
|
|
1196
|
-
}
|
|
1197
|
-
const message = this.prepareMessage(type, _message, options);
|
|
1198
|
-
const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1199
|
-
if (queue.length >= this.maxQueueSize) {
|
|
1200
|
-
queue.shift();
|
|
1201
|
-
this.logMsgIfDebug(() => console.info('Queue is full, the oldest event is dropped.'));
|
|
1202
|
-
}
|
|
1203
|
-
queue.push({ message });
|
|
1204
|
-
this.setPersistedProperty(PostHogPersistedProperty.Queue, queue);
|
|
1205
|
-
this._events.emit(type, message);
|
|
1206
|
-
// Flush queued events if we meet the flushAt length
|
|
1207
|
-
if (queue.length >= this.flushAt) {
|
|
1208
|
-
this.flushBackground();
|
|
1209
|
-
}
|
|
1210
|
-
if (this.flushInterval && !this._flushTimer) {
|
|
1211
|
-
this._flushTimer = safeSetTimeout(() => this.flushBackground(), this.flushInterval);
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
}
|
|
1215
|
-
async sendImmediate(type, _message, options) {
|
|
1216
|
-
if (this.disabled) {
|
|
1217
|
-
this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
if (!this._isInitialized) {
|
|
1221
|
-
await this._initPromise;
|
|
1222
|
-
}
|
|
1223
|
-
if (this.optedOut) {
|
|
1224
|
-
this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
|
-
const data = {
|
|
1228
|
-
api_key: this.apiKey,
|
|
1229
|
-
batch: [this.prepareMessage(type, _message, options)],
|
|
1230
|
-
sent_at: currentISOTime(),
|
|
1231
|
-
};
|
|
1232
|
-
if (this.historicalMigration) {
|
|
1233
|
-
data.historical_migration = true;
|
|
1234
|
-
}
|
|
1235
|
-
const payload = JSON.stringify(data);
|
|
1236
|
-
const url = `${this.host}/batch/`;
|
|
1237
|
-
const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
|
|
1238
|
-
const fetchOptions = {
|
|
1239
|
-
method: 'POST',
|
|
1240
|
-
headers: {
|
|
1241
|
-
...this.getCustomHeaders(),
|
|
1242
|
-
'Content-Type': 'application/json',
|
|
1243
|
-
...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
|
|
1244
|
-
},
|
|
1245
|
-
body: gzippedPayload || payload,
|
|
1246
|
-
};
|
|
1247
|
-
try {
|
|
1248
|
-
await this.fetchWithRetry(url, fetchOptions);
|
|
1249
|
-
}
|
|
1250
|
-
catch (err) {
|
|
1251
|
-
this._events.emit('error', err);
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
prepareMessage(type, _message, options) {
|
|
1255
|
-
const message = {
|
|
1256
|
-
..._message,
|
|
1257
|
-
type: type,
|
|
1258
|
-
library: this.getLibraryId(),
|
|
1259
|
-
library_version: this.getLibraryVersion(),
|
|
1260
|
-
timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
|
|
1261
|
-
uuid: options?.uuid ? options.uuid : uuidv7(),
|
|
1262
|
-
};
|
|
1263
|
-
const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
|
|
1264
|
-
if (addGeoipDisableProperty) {
|
|
1265
|
-
if (!message.properties) {
|
|
1266
|
-
message.properties = {};
|
|
1267
|
-
}
|
|
1268
|
-
message['properties']['$geoip_disable'] = true;
|
|
1269
|
-
}
|
|
1270
|
-
if (message.distinctId) {
|
|
1271
|
-
message.distinct_id = message.distinctId;
|
|
1272
|
-
delete message.distinctId;
|
|
1273
|
-
}
|
|
1274
|
-
return message;
|
|
1275
|
-
}
|
|
1276
|
-
clearFlushTimer() {
|
|
1277
|
-
if (this._flushTimer) {
|
|
1278
|
-
clearTimeout(this._flushTimer);
|
|
1279
|
-
this._flushTimer = undefined;
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
/**
|
|
1283
|
-
* Helper for flushing the queue in the background
|
|
1284
|
-
* Avoids unnecessary promise errors
|
|
1285
|
-
*/
|
|
1286
|
-
flushBackground() {
|
|
1287
|
-
void this.flush().catch(async (err) => {
|
|
1288
|
-
await logFlushError(err);
|
|
1289
|
-
});
|
|
1290
|
-
}
|
|
1291
|
-
/**
|
|
1292
|
-
* Flushes the queue
|
|
1293
|
-
*
|
|
1294
|
-
* This function will return a promise that will resolve when the flush is complete,
|
|
1295
|
-
* or reject if there was an error (for example if the server or network is down).
|
|
1296
|
-
*
|
|
1297
|
-
* If there is already a flush in progress, this function will wait for that flush to complete.
|
|
1298
|
-
*
|
|
1299
|
-
* It's recommended to do error handling in the callback of the promise.
|
|
1300
|
-
*
|
|
1301
|
-
* @example
|
|
1302
|
-
* posthog.flush().then(() => {
|
|
1303
|
-
* console.log('Flush complete')
|
|
1304
|
-
* }).catch((err) => {
|
|
1305
|
-
* console.error('Flush failed', err)
|
|
1306
|
-
* })
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
1309
|
-
* @throws PostHogFetchHttpError
|
|
1310
|
-
* @throws PostHogFetchNetworkError
|
|
1311
|
-
* @throws Error
|
|
1312
|
-
*/
|
|
1313
|
-
async flush() {
|
|
1314
|
-
// Wait for the current flush operation to finish (regardless of success or failure), then try to flush again.
|
|
1315
|
-
// Use allSettled instead of finally to be defensive around flush throwing errors immediately rather than rejecting.
|
|
1316
|
-
// Use a custom allSettled implementation to avoid issues with patching Promise on RN
|
|
1317
|
-
const nextFlushPromise = allSettled([this.flushPromise]).then(() => {
|
|
1318
|
-
return this._flush();
|
|
1319
|
-
});
|
|
1320
|
-
this.flushPromise = nextFlushPromise;
|
|
1321
|
-
void this.addPendingPromise(nextFlushPromise);
|
|
1322
|
-
allSettled([nextFlushPromise]).then(() => {
|
|
1323
|
-
// If there are no others waiting to flush, clear the promise.
|
|
1324
|
-
// We don't strictly need to do this, but it could make debugging easier
|
|
1325
|
-
if (this.flushPromise === nextFlushPromise) {
|
|
1326
|
-
this.flushPromise = null;
|
|
1327
|
-
}
|
|
1328
|
-
});
|
|
1329
|
-
return nextFlushPromise;
|
|
1330
|
-
}
|
|
1331
|
-
getCustomHeaders() {
|
|
1332
|
-
// Don't set the user agent if we're not on a browser. The latest spec allows
|
|
1333
|
-
// the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
|
|
1334
|
-
// and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
|
|
1335
|
-
// but browsers such as Chrome and Safari have not caught up.
|
|
1336
|
-
const customUserAgent = this.getCustomUserAgent();
|
|
1337
|
-
const headers = {};
|
|
1338
|
-
if (customUserAgent && customUserAgent !== '') {
|
|
1339
|
-
headers['User-Agent'] = customUserAgent;
|
|
1340
|
-
}
|
|
1341
|
-
return headers;
|
|
1342
|
-
}
|
|
1343
|
-
async _flush() {
|
|
1344
|
-
this.clearFlushTimer();
|
|
1345
|
-
await this._initPromise;
|
|
1346
|
-
let queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1347
|
-
if (!queue.length) {
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
|
-
const sentMessages = [];
|
|
1351
|
-
const originalQueueLength = queue.length;
|
|
1352
|
-
while (queue.length > 0 && sentMessages.length < originalQueueLength) {
|
|
1353
|
-
const batchItems = queue.slice(0, this.maxBatchSize);
|
|
1354
|
-
const batchMessages = batchItems.map((item) => item.message);
|
|
1355
|
-
const persistQueueChange = () => {
|
|
1356
|
-
const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1357
|
-
const newQueue = refreshedQueue.slice(batchItems.length);
|
|
1358
|
-
this.setPersistedProperty(PostHogPersistedProperty.Queue, newQueue);
|
|
1359
|
-
queue = newQueue;
|
|
1360
|
-
};
|
|
1361
|
-
const data = {
|
|
1362
|
-
api_key: this.apiKey,
|
|
1363
|
-
batch: batchMessages,
|
|
1364
|
-
sent_at: currentISOTime(),
|
|
1365
|
-
};
|
|
1366
|
-
if (this.historicalMigration) {
|
|
1367
|
-
data.historical_migration = true;
|
|
1368
|
-
}
|
|
1369
|
-
const payload = JSON.stringify(data);
|
|
1370
|
-
const url = `${this.host}/batch/`;
|
|
1371
|
-
const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
|
|
1372
|
-
const fetchOptions = {
|
|
1373
|
-
method: 'POST',
|
|
1374
|
-
headers: {
|
|
1375
|
-
...this.getCustomHeaders(),
|
|
1376
|
-
'Content-Type': 'application/json',
|
|
1377
|
-
...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
|
|
1378
|
-
},
|
|
1379
|
-
body: gzippedPayload || payload,
|
|
1380
|
-
};
|
|
1381
|
-
const retryOptions = {
|
|
1382
|
-
retryCheck: (err) => {
|
|
1383
|
-
// don't automatically retry on 413 errors, we want to reduce the batch size first
|
|
1384
|
-
if (isPostHogFetchContentTooLargeError(err)) {
|
|
1385
|
-
return false;
|
|
1386
|
-
}
|
|
1387
|
-
// otherwise, retry on network errors
|
|
1388
|
-
return isPostHogFetchError(err);
|
|
1389
|
-
},
|
|
1390
|
-
};
|
|
1391
|
-
try {
|
|
1392
|
-
await this.fetchWithRetry(url, fetchOptions, retryOptions);
|
|
1393
|
-
}
|
|
1394
|
-
catch (err) {
|
|
1395
|
-
if (isPostHogFetchContentTooLargeError(err) && batchMessages.length > 1) {
|
|
1396
|
-
// if we get a 413 error, we want to reduce the batch size and try again
|
|
1397
|
-
this.maxBatchSize = Math.max(1, Math.floor(batchMessages.length / 2));
|
|
1398
|
-
this.logMsgIfDebug(() => console.warn(`Received 413 when sending batch of size ${batchMessages.length}, reducing batch size to ${this.maxBatchSize}`));
|
|
1399
|
-
// do not persist the queue change, we want to retry the same batch
|
|
1400
|
-
continue;
|
|
1401
|
-
}
|
|
1402
|
-
// depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
|
|
1403
|
-
// and this will be an endless loop, in this case, if the error isn't a network issue, we always remove the items from the queue
|
|
1404
|
-
if (!(err instanceof PostHogFetchNetworkError)) {
|
|
1405
|
-
persistQueueChange();
|
|
1406
|
-
}
|
|
1407
|
-
this._events.emit('error', err);
|
|
1408
|
-
throw err;
|
|
1409
|
-
}
|
|
1410
|
-
persistQueueChange();
|
|
1411
|
-
sentMessages.push(...batchMessages);
|
|
1412
|
-
}
|
|
1413
|
-
this._events.emit('flush', sentMessages);
|
|
1414
|
-
}
|
|
1415
|
-
async fetchWithRetry(url, options, retryOptions, requestTimeout) {
|
|
1416
|
-
var _a;
|
|
1417
|
-
(_a = AbortSignal).timeout ?? (_a.timeout = function timeout(ms) {
|
|
1418
|
-
const ctrl = new AbortController();
|
|
1419
|
-
setTimeout(() => ctrl.abort(), ms);
|
|
1420
|
-
return ctrl.signal;
|
|
1421
|
-
});
|
|
1422
|
-
const body = options.body ? options.body : '';
|
|
1423
|
-
let reqByteLength = -1;
|
|
1424
|
-
try {
|
|
1425
|
-
if (body instanceof Blob) {
|
|
1426
|
-
reqByteLength = body.size;
|
|
1427
|
-
}
|
|
1428
|
-
else {
|
|
1429
|
-
reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
catch {
|
|
1433
|
-
if (body instanceof Blob) {
|
|
1434
|
-
reqByteLength = body.size;
|
|
1435
|
-
}
|
|
1436
|
-
else {
|
|
1437
|
-
const encoded = new TextEncoder().encode(body);
|
|
1438
|
-
reqByteLength = encoded.length;
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
return await retriable(async () => {
|
|
1442
|
-
let res = null;
|
|
1443
|
-
try {
|
|
1444
|
-
res = await this.fetch(url, {
|
|
1445
|
-
signal: AbortSignal.timeout(requestTimeout ?? this.requestTimeout),
|
|
1446
|
-
...options,
|
|
1447
|
-
});
|
|
1448
|
-
}
|
|
1449
|
-
catch (e) {
|
|
1450
|
-
// fetch will only throw on network errors or on timeouts
|
|
1451
|
-
throw new PostHogFetchNetworkError(e);
|
|
1452
|
-
}
|
|
1453
|
-
// If we're in no-cors mode, we can't access the response status
|
|
1454
|
-
// We only throw on HTTP errors if we're not in no-cors mode
|
|
1455
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Request/mode#no-cors
|
|
1456
|
-
const isNoCors = options.mode === 'no-cors';
|
|
1457
|
-
if (!isNoCors && (res.status < 200 || res.status >= 400)) {
|
|
1458
|
-
throw new PostHogFetchHttpError(res, reqByteLength);
|
|
1459
|
-
}
|
|
1460
|
-
return res;
|
|
1461
|
-
}, { ...this._retryOptions, ...retryOptions });
|
|
1462
|
-
}
|
|
1463
|
-
async _shutdown(shutdownTimeoutMs = 30000) {
|
|
1464
|
-
// A little tricky - we want to have a max shutdown time and enforce it, even if that means we have some
|
|
1465
|
-
// dangling promises. We'll keep track of the timeout and resolve/reject based on that.
|
|
1466
|
-
await this._initPromise;
|
|
1467
|
-
let hasTimedOut = false;
|
|
1468
|
-
this.clearFlushTimer();
|
|
1469
|
-
const doShutdown = async () => {
|
|
1470
|
-
try {
|
|
1471
|
-
await Promise.all(Object.values(this.pendingPromises));
|
|
1472
|
-
while (true) {
|
|
1473
|
-
const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1474
|
-
if (queue.length === 0) {
|
|
1475
|
-
break;
|
|
1476
|
-
}
|
|
1477
|
-
// flush again to make sure we send all events, some of which might've been added
|
|
1478
|
-
// while we were waiting for the pending promises to resolve
|
|
1479
|
-
// For example, see sendFeatureFlags in posthog-node/src/posthog-node.ts::capture
|
|
1480
|
-
await this.flush();
|
|
1481
|
-
if (hasTimedOut) {
|
|
1482
|
-
break;
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
catch (e) {
|
|
1487
|
-
if (!isPostHogFetchError(e)) {
|
|
1488
|
-
throw e;
|
|
1489
|
-
}
|
|
1490
|
-
await logFlushError(e);
|
|
1491
|
-
}
|
|
1492
|
-
};
|
|
1493
|
-
return Promise.race([
|
|
1494
|
-
new Promise((_, reject) => {
|
|
1495
|
-
safeSetTimeout(() => {
|
|
1496
|
-
this.logMsgIfDebug(() => console.error('Timed out while shutting down PostHog'));
|
|
1497
|
-
hasTimedOut = true;
|
|
1498
|
-
reject('Timeout while shutting down PostHog. Some events may not have been sent.');
|
|
1499
|
-
}, shutdownTimeoutMs);
|
|
1500
|
-
}),
|
|
1501
|
-
doShutdown(),
|
|
1502
|
-
]);
|
|
1503
|
-
}
|
|
1504
|
-
/**
|
|
1505
|
-
* Call shutdown() once before the node process exits, so ensure that all events have been sent and all promises
|
|
1506
|
-
* have resolved. Do not use this function if you intend to keep using this PostHog instance after calling it.
|
|
1507
|
-
* @param shutdownTimeoutMs
|
|
1508
|
-
*/
|
|
1509
|
-
async shutdown(shutdownTimeoutMs = 30000) {
|
|
1510
|
-
if (this.shutdownPromise) {
|
|
1511
|
-
this.logMsgIfDebug(() => console.warn('shutdown() called while already shutting down. shutdown() is meant to be called once before process exit - use flush() for per-request cleanup'));
|
|
1512
|
-
}
|
|
1513
|
-
else {
|
|
1514
|
-
this.shutdownPromise = this._shutdown(shutdownTimeoutMs).finally(() => {
|
|
1515
|
-
this.shutdownPromise = null;
|
|
1516
|
-
});
|
|
1517
|
-
}
|
|
1518
|
-
return this.shutdownPromise;
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
class PostHogCore extends PostHogCoreStateless {
|
|
1522
|
-
constructor(apiKey, options) {
|
|
1523
|
-
// Default for stateful mode is to not disable geoip. Only override if explicitly set
|
|
1524
|
-
const disableGeoipOption = options?.disableGeoip ?? false;
|
|
1525
|
-
// Default for stateful mode is to timeout at 10s. Only override if explicitly set
|
|
1526
|
-
const featureFlagsRequestTimeoutMs = options?.featureFlagsRequestTimeoutMs ?? 10000; // 10 seconds
|
|
1527
|
-
super(apiKey, { ...options, disableGeoip: disableGeoipOption, featureFlagsRequestTimeoutMs });
|
|
1528
|
-
this.flagCallReported = {};
|
|
1529
|
-
this._sessionMaxLengthSeconds = 24 * 60 * 60; // 24 hours
|
|
1530
|
-
this.sessionProps = {};
|
|
1531
|
-
this.sendFeatureFlagEvent = options?.sendFeatureFlagEvent ?? true;
|
|
1532
|
-
this._sessionExpirationTimeSeconds = options?.sessionExpirationTimeSeconds ?? 1800; // 30 minutes
|
|
1533
|
-
}
|
|
1534
|
-
setupBootstrap(options) {
|
|
1535
|
-
const bootstrap = options?.bootstrap;
|
|
1536
|
-
if (!bootstrap) {
|
|
1537
|
-
return;
|
|
1538
|
-
}
|
|
1539
|
-
// bootstrap options are only set if no persisted values are found
|
|
1540
|
-
// this is to prevent overwriting existing values
|
|
1541
|
-
if (bootstrap.distinctId) {
|
|
1542
|
-
if (bootstrap.isIdentifiedId) {
|
|
1543
|
-
const distinctId = this.getPersistedProperty(PostHogPersistedProperty.DistinctId);
|
|
1544
|
-
if (!distinctId) {
|
|
1545
|
-
this.setPersistedProperty(PostHogPersistedProperty.DistinctId, bootstrap.distinctId);
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
else {
|
|
1549
|
-
const anonymousId = this.getPersistedProperty(PostHogPersistedProperty.AnonymousId);
|
|
1550
|
-
if (!anonymousId) {
|
|
1551
|
-
this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, bootstrap.distinctId);
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
const bootstrapFeatureFlags = bootstrap.featureFlags;
|
|
1556
|
-
const bootstrapFeatureFlagPayloads = bootstrap.featureFlagPayloads ?? {};
|
|
1557
|
-
if (bootstrapFeatureFlags && Object.keys(bootstrapFeatureFlags).length) {
|
|
1558
|
-
const normalizedBootstrapFeatureFlagDetails = createFlagsResponseFromFlagsAndPayloads(bootstrapFeatureFlags, bootstrapFeatureFlagPayloads);
|
|
1559
|
-
if (Object.keys(normalizedBootstrapFeatureFlagDetails.flags).length > 0) {
|
|
1560
|
-
this.setBootstrappedFeatureFlagDetails(normalizedBootstrapFeatureFlagDetails);
|
|
1561
|
-
const currentFeatureFlagDetails = this.getKnownFeatureFlagDetails() || { flags: {}, requestId: undefined };
|
|
1562
|
-
const newFeatureFlagDetails = {
|
|
1563
|
-
flags: {
|
|
1564
|
-
...normalizedBootstrapFeatureFlagDetails.flags,
|
|
1565
|
-
...currentFeatureFlagDetails.flags,
|
|
1566
|
-
},
|
|
1567
|
-
requestId: normalizedBootstrapFeatureFlagDetails.requestId,
|
|
1568
|
-
};
|
|
1569
|
-
this.setKnownFeatureFlagDetails(newFeatureFlagDetails);
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
clearProps() {
|
|
1574
|
-
this.props = undefined;
|
|
1575
|
-
this.sessionProps = {};
|
|
1576
|
-
this.flagCallReported = {};
|
|
1577
|
-
}
|
|
1578
|
-
on(event, cb) {
|
|
1579
|
-
return this._events.on(event, cb);
|
|
1580
|
-
}
|
|
1581
|
-
reset(propertiesToKeep) {
|
|
1582
|
-
this.wrap(() => {
|
|
1583
|
-
const allPropertiesToKeep = [PostHogPersistedProperty.Queue, ...(propertiesToKeep || [])];
|
|
1584
|
-
// clean up props
|
|
1585
|
-
this.clearProps();
|
|
1586
|
-
for (const key of Object.keys(PostHogPersistedProperty)) {
|
|
1587
|
-
if (!allPropertiesToKeep.includes(PostHogPersistedProperty[key])) {
|
|
1588
|
-
this.setPersistedProperty(PostHogPersistedProperty[key], null);
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
this.reloadFeatureFlags();
|
|
1592
|
-
});
|
|
1593
|
-
}
|
|
1594
|
-
getCommonEventProperties() {
|
|
1595
|
-
const featureFlags = this.getFeatureFlags();
|
|
1596
|
-
const featureVariantProperties = {};
|
|
1597
|
-
if (featureFlags) {
|
|
1598
|
-
for (const [feature, variant] of Object.entries(featureFlags)) {
|
|
1599
|
-
featureVariantProperties[`$feature/${feature}`] = variant;
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
return {
|
|
1603
|
-
...maybeAdd('$active_feature_flags', featureFlags ? Object.keys(featureFlags) : undefined),
|
|
1604
|
-
...featureVariantProperties,
|
|
1605
|
-
...super.getCommonEventProperties(),
|
|
1606
|
-
};
|
|
1607
|
-
}
|
|
1608
|
-
enrichProperties(properties) {
|
|
1609
|
-
return {
|
|
1610
|
-
...this.props,
|
|
1611
|
-
...this.sessionProps,
|
|
1612
|
-
...(properties || {}),
|
|
1613
|
-
...this.getCommonEventProperties(),
|
|
1614
|
-
$session_id: this.getSessionId(),
|
|
1615
|
-
};
|
|
1616
|
-
}
|
|
1617
|
-
/**
|
|
1618
|
-
* * @returns {string} The stored session ID for the current session. This may be an empty string if the client is not yet fully initialized.
|
|
1619
|
-
*/
|
|
1620
|
-
getSessionId() {
|
|
1621
|
-
if (!this._isInitialized) {
|
|
1622
|
-
return '';
|
|
1623
|
-
}
|
|
1624
|
-
let sessionId = this.getPersistedProperty(PostHogPersistedProperty.SessionId);
|
|
1625
|
-
const sessionLastTimestamp = this.getPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp) || 0;
|
|
1626
|
-
const sessionStartTimestamp = this.getPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp) || 0;
|
|
1627
|
-
const now = Date.now();
|
|
1628
|
-
const sessionLastDif = now - sessionLastTimestamp;
|
|
1629
|
-
const sessionStartDif = now - sessionStartTimestamp;
|
|
1630
|
-
if (!sessionId ||
|
|
1631
|
-
sessionLastDif > this._sessionExpirationTimeSeconds * 1000 ||
|
|
1632
|
-
sessionStartDif > this._sessionMaxLengthSeconds * 1000) {
|
|
1633
|
-
sessionId = uuidv7();
|
|
1634
|
-
this.setPersistedProperty(PostHogPersistedProperty.SessionId, sessionId);
|
|
1635
|
-
this.setPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp, now);
|
|
1636
|
-
}
|
|
1637
|
-
this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, now);
|
|
1638
|
-
return sessionId;
|
|
1639
|
-
}
|
|
1640
|
-
resetSessionId() {
|
|
1641
|
-
this.wrap(() => {
|
|
1642
|
-
this.setPersistedProperty(PostHogPersistedProperty.SessionId, null);
|
|
1643
|
-
this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, null);
|
|
1644
|
-
this.setPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp, null);
|
|
1645
|
-
});
|
|
1646
|
-
}
|
|
1647
|
-
/**
|
|
1648
|
-
* * @returns {string} The stored anonymous ID. This may be an empty string if the client is not yet fully initialized.
|
|
1649
|
-
*/
|
|
1650
|
-
getAnonymousId() {
|
|
1651
|
-
if (!this._isInitialized) {
|
|
1652
|
-
return '';
|
|
1653
|
-
}
|
|
1654
|
-
let anonId = this.getPersistedProperty(PostHogPersistedProperty.AnonymousId);
|
|
1655
|
-
if (!anonId) {
|
|
1656
|
-
anonId = uuidv7();
|
|
1657
|
-
this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, anonId);
|
|
1658
|
-
}
|
|
1659
|
-
return anonId;
|
|
1660
|
-
}
|
|
1661
|
-
/**
|
|
1662
|
-
* * @returns {string} The stored distinct ID. This may be an empty string if the client is not yet fully initialized.
|
|
1663
|
-
*/
|
|
1664
|
-
getDistinctId() {
|
|
1665
|
-
if (!this._isInitialized) {
|
|
1666
|
-
return '';
|
|
1667
|
-
}
|
|
1668
|
-
return this.getPersistedProperty(PostHogPersistedProperty.DistinctId) || this.getAnonymousId();
|
|
1669
|
-
}
|
|
1670
|
-
registerForSession(properties) {
|
|
1671
|
-
this.sessionProps = {
|
|
1672
|
-
...this.sessionProps,
|
|
1673
|
-
...properties,
|
|
1674
|
-
};
|
|
1675
|
-
}
|
|
1676
|
-
unregisterForSession(property) {
|
|
1677
|
-
delete this.sessionProps[property];
|
|
1678
|
-
}
|
|
1679
|
-
/***
|
|
1680
|
-
*** TRACKING
|
|
1681
|
-
***/
|
|
1682
|
-
identify(distinctId, properties, options) {
|
|
1683
|
-
this.wrap(() => {
|
|
1684
|
-
const previousDistinctId = this.getDistinctId();
|
|
1685
|
-
distinctId = distinctId || previousDistinctId;
|
|
1686
|
-
if (properties?.$groups) {
|
|
1687
|
-
this.groups(properties.$groups);
|
|
1688
|
-
}
|
|
1689
|
-
// promote $set and $set_once to top level
|
|
1690
|
-
const userPropsOnce = properties?.$set_once;
|
|
1691
|
-
delete properties?.$set_once;
|
|
1692
|
-
// if no $set is provided we assume all properties are $set
|
|
1693
|
-
const userProps = properties?.$set || properties;
|
|
1694
|
-
const allProperties = this.enrichProperties({
|
|
1695
|
-
$anon_distinct_id: this.getAnonymousId(),
|
|
1696
|
-
...maybeAdd('$set', userProps),
|
|
1697
|
-
...maybeAdd('$set_once', userPropsOnce),
|
|
1698
|
-
});
|
|
1699
|
-
if (distinctId !== previousDistinctId) {
|
|
1700
|
-
// We keep the AnonymousId to be used by flags calls and identify to link the previousId
|
|
1701
|
-
this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, previousDistinctId);
|
|
1702
|
-
this.setPersistedProperty(PostHogPersistedProperty.DistinctId, distinctId);
|
|
1703
|
-
this.reloadFeatureFlags();
|
|
1704
|
-
}
|
|
1705
|
-
super.identifyStateless(distinctId, allProperties, options);
|
|
1706
|
-
});
|
|
1707
|
-
}
|
|
1708
|
-
capture(event, properties, options) {
|
|
1709
|
-
this.wrap(() => {
|
|
1710
|
-
const distinctId = this.getDistinctId();
|
|
1711
|
-
if (properties?.$groups) {
|
|
1712
|
-
this.groups(properties.$groups);
|
|
1713
|
-
}
|
|
1714
|
-
const allProperties = this.enrichProperties(properties);
|
|
1715
|
-
super.captureStateless(distinctId, event, allProperties, options);
|
|
1716
|
-
});
|
|
1717
|
-
}
|
|
1718
|
-
alias(alias) {
|
|
1719
|
-
this.wrap(() => {
|
|
1720
|
-
const distinctId = this.getDistinctId();
|
|
1721
|
-
const allProperties = this.enrichProperties({});
|
|
1722
|
-
super.aliasStateless(alias, distinctId, allProperties);
|
|
1723
|
-
});
|
|
1724
|
-
}
|
|
1725
|
-
autocapture(eventType, elements, properties = {}, options) {
|
|
1726
|
-
this.wrap(() => {
|
|
1727
|
-
const distinctId = this.getDistinctId();
|
|
1728
|
-
const payload = {
|
|
1729
|
-
distinct_id: distinctId,
|
|
1730
|
-
event: '$autocapture',
|
|
1731
|
-
properties: {
|
|
1732
|
-
...this.enrichProperties(properties),
|
|
1733
|
-
$event_type: eventType,
|
|
1734
|
-
$elements: elements,
|
|
1735
|
-
},
|
|
1736
|
-
};
|
|
1737
|
-
this.enqueue('autocapture', payload, options);
|
|
1738
|
-
});
|
|
1739
|
-
}
|
|
1740
|
-
/***
|
|
1741
|
-
*** GROUPS
|
|
1742
|
-
***/
|
|
1743
|
-
groups(groups) {
|
|
1744
|
-
this.wrap(() => {
|
|
1745
|
-
// Get persisted groups
|
|
1746
|
-
const existingGroups = this.props.$groups || {};
|
|
1747
|
-
this.register({
|
|
1748
|
-
$groups: {
|
|
1749
|
-
...existingGroups,
|
|
1750
|
-
...groups,
|
|
1751
|
-
},
|
|
1752
|
-
});
|
|
1753
|
-
if (Object.keys(groups).find((type) => existingGroups[type] !== groups[type])) {
|
|
1754
|
-
this.reloadFeatureFlags();
|
|
1755
|
-
}
|
|
1756
|
-
});
|
|
1757
|
-
}
|
|
1758
|
-
group(groupType, groupKey, groupProperties, options) {
|
|
1759
|
-
this.wrap(() => {
|
|
1760
|
-
this.groups({
|
|
1761
|
-
[groupType]: groupKey,
|
|
1762
|
-
});
|
|
1763
|
-
if (groupProperties) {
|
|
1764
|
-
this.groupIdentify(groupType, groupKey, groupProperties, options);
|
|
1765
|
-
}
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
1768
|
-
groupIdentify(groupType, groupKey, groupProperties, options) {
|
|
1769
|
-
this.wrap(() => {
|
|
1770
|
-
const distinctId = this.getDistinctId();
|
|
1771
|
-
const eventProperties = this.enrichProperties({});
|
|
1772
|
-
super.groupIdentifyStateless(groupType, groupKey, groupProperties, options, distinctId, eventProperties);
|
|
1773
|
-
});
|
|
1774
|
-
}
|
|
1775
|
-
/***
|
|
1776
|
-
* PROPERTIES
|
|
1777
|
-
***/
|
|
1778
|
-
setPersonPropertiesForFlags(properties) {
|
|
1779
|
-
this.wrap(() => {
|
|
1780
|
-
// Get persisted person properties
|
|
1781
|
-
const existingProperties = this.getPersistedProperty(PostHogPersistedProperty.PersonProperties) || {};
|
|
1782
|
-
this.setPersistedProperty(PostHogPersistedProperty.PersonProperties, {
|
|
1783
|
-
...existingProperties,
|
|
1784
|
-
...properties,
|
|
1785
|
-
});
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
resetPersonPropertiesForFlags() {
|
|
1789
|
-
this.wrap(() => {
|
|
1790
|
-
this.setPersistedProperty(PostHogPersistedProperty.PersonProperties, null);
|
|
1791
|
-
});
|
|
1792
|
-
}
|
|
1793
|
-
setGroupPropertiesForFlags(properties) {
|
|
1794
|
-
this.wrap(() => {
|
|
1795
|
-
// Get persisted group properties
|
|
1796
|
-
const existingProperties = this.getPersistedProperty(PostHogPersistedProperty.GroupProperties) ||
|
|
1797
|
-
{};
|
|
1798
|
-
if (Object.keys(existingProperties).length !== 0) {
|
|
1799
|
-
Object.keys(existingProperties).forEach((groupType) => {
|
|
1800
|
-
existingProperties[groupType] = {
|
|
1801
|
-
...existingProperties[groupType],
|
|
1802
|
-
...properties[groupType],
|
|
1803
|
-
};
|
|
1804
|
-
delete properties[groupType];
|
|
1805
|
-
});
|
|
1806
|
-
}
|
|
1807
|
-
this.setPersistedProperty(PostHogPersistedProperty.GroupProperties, {
|
|
1808
|
-
...existingProperties,
|
|
1809
|
-
...properties,
|
|
1810
|
-
});
|
|
1811
|
-
});
|
|
1812
|
-
}
|
|
1813
|
-
resetGroupPropertiesForFlags() {
|
|
1814
|
-
this.wrap(() => {
|
|
1815
|
-
this.setPersistedProperty(PostHogPersistedProperty.GroupProperties, null);
|
|
1816
|
-
});
|
|
1817
|
-
}
|
|
1818
|
-
async remoteConfigAsync() {
|
|
1819
|
-
await this._initPromise;
|
|
1820
|
-
if (this._remoteConfigResponsePromise) {
|
|
1821
|
-
return this._remoteConfigResponsePromise;
|
|
1822
|
-
}
|
|
1823
|
-
return this._remoteConfigAsync();
|
|
1824
|
-
}
|
|
1825
|
-
/***
|
|
1826
|
-
*** FEATURE FLAGS
|
|
1827
|
-
***/
|
|
1828
|
-
async flagsAsync(sendAnonDistinctId = true) {
|
|
1829
|
-
await this._initPromise;
|
|
1830
|
-
if (this._flagsResponsePromise) {
|
|
1831
|
-
return this._flagsResponsePromise;
|
|
1832
|
-
}
|
|
1833
|
-
return this._flagsAsync(sendAnonDistinctId);
|
|
1834
|
-
}
|
|
1835
|
-
cacheSessionReplay(source, response) {
|
|
1836
|
-
const sessionReplay = response?.sessionRecording;
|
|
1837
|
-
if (sessionReplay) {
|
|
1838
|
-
this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, sessionReplay);
|
|
1839
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', `Session replay config from ${source}: `, JSON.stringify(sessionReplay)));
|
|
1840
|
-
}
|
|
1841
|
-
else if (typeof sessionReplay === 'boolean' && sessionReplay === false) {
|
|
1842
|
-
// if session replay is disabled, we don't need to cache it
|
|
1843
|
-
// we need to check for this because the response might be undefined (/flags does not return sessionRecording yet)
|
|
1844
|
-
this.logMsgIfDebug(() => console.info('PostHog Debug', `Session replay config from ${source} disabled.`));
|
|
1845
|
-
this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, null);
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
async _remoteConfigAsync() {
|
|
1849
|
-
this._remoteConfigResponsePromise = this._initPromise
|
|
1850
|
-
.then(() => {
|
|
1851
|
-
let remoteConfig = this.getPersistedProperty(PostHogPersistedProperty.RemoteConfig);
|
|
1852
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Cached remote config: ', JSON.stringify(remoteConfig)));
|
|
1853
|
-
return super.getRemoteConfig().then((response) => {
|
|
1854
|
-
if (response) {
|
|
1855
|
-
const remoteConfigWithoutSurveys = { ...response };
|
|
1856
|
-
delete remoteConfigWithoutSurveys.surveys;
|
|
1857
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Fetched remote config: ', JSON.stringify(remoteConfigWithoutSurveys)));
|
|
1858
|
-
if (this.disableSurveys === false) {
|
|
1859
|
-
const surveys = response.surveys;
|
|
1860
|
-
let hasSurveys = true;
|
|
1861
|
-
if (!Array.isArray(surveys)) {
|
|
1862
|
-
// If surveys is not an array, it means there are no surveys (its a boolean instead)
|
|
1863
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'There are no surveys.'));
|
|
1864
|
-
hasSurveys = false;
|
|
1865
|
-
}
|
|
1866
|
-
else {
|
|
1867
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from remote config: ', JSON.stringify(surveys)));
|
|
1868
|
-
}
|
|
1869
|
-
if (hasSurveys) {
|
|
1870
|
-
this.setPersistedProperty(PostHogPersistedProperty.Surveys, surveys);
|
|
1871
|
-
}
|
|
1872
|
-
else {
|
|
1873
|
-
this.setPersistedProperty(PostHogPersistedProperty.Surveys, null);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
else {
|
|
1877
|
-
this.setPersistedProperty(PostHogPersistedProperty.Surveys, null);
|
|
1878
|
-
}
|
|
1879
|
-
// we cache the surveys in its own storage key
|
|
1880
|
-
this.setPersistedProperty(PostHogPersistedProperty.RemoteConfig, remoteConfigWithoutSurveys);
|
|
1881
|
-
this.cacheSessionReplay('remote config', response);
|
|
1882
|
-
// we only dont load flags if the remote config has no feature flags
|
|
1883
|
-
if (response.hasFeatureFlags === false) {
|
|
1884
|
-
// resetting flags to empty object
|
|
1885
|
-
this.setKnownFeatureFlagDetails({ flags: {} });
|
|
1886
|
-
this.logMsgIfDebug(() => console.warn('Remote config has no feature flags, will not load feature flags.'));
|
|
1887
|
-
}
|
|
1888
|
-
else if (this.preloadFeatureFlags !== false) {
|
|
1889
|
-
this.reloadFeatureFlags();
|
|
1890
|
-
}
|
|
1891
|
-
if (!response.supportedCompression?.includes(Compression.GZipJS)) {
|
|
1892
|
-
this.disableCompression = true;
|
|
1893
|
-
}
|
|
1894
|
-
remoteConfig = response;
|
|
1895
|
-
}
|
|
1896
|
-
return remoteConfig;
|
|
1897
|
-
});
|
|
1898
|
-
})
|
|
1899
|
-
.finally(() => {
|
|
1900
|
-
this._remoteConfigResponsePromise = undefined;
|
|
1901
|
-
});
|
|
1902
|
-
return this._remoteConfigResponsePromise;
|
|
1903
|
-
}
|
|
1904
|
-
async _flagsAsync(sendAnonDistinctId = true) {
|
|
1905
|
-
this._flagsResponsePromise = this._initPromise
|
|
1906
|
-
.then(async () => {
|
|
1907
|
-
const distinctId = this.getDistinctId();
|
|
1908
|
-
const groups = this.props.$groups || {};
|
|
1909
|
-
const personProperties = this.getPersistedProperty(PostHogPersistedProperty.PersonProperties) || {};
|
|
1910
|
-
const groupProperties = this.getPersistedProperty(PostHogPersistedProperty.GroupProperties) ||
|
|
1911
|
-
{};
|
|
1912
|
-
const extraProperties = {
|
|
1913
|
-
$anon_distinct_id: sendAnonDistinctId ? this.getAnonymousId() : undefined,
|
|
1914
|
-
};
|
|
1915
|
-
const res = await super.getFlags(distinctId, groups, personProperties, groupProperties, extraProperties);
|
|
1916
|
-
// Add check for quota limitation on feature flags
|
|
1917
|
-
if (res?.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
|
|
1918
|
-
// Unset all feature flags by setting to null
|
|
1919
|
-
this.setKnownFeatureFlagDetails(null);
|
|
1920
|
-
console.warn('[FEATURE FLAGS] Feature flags quota limit exceeded - unsetting all flags. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts');
|
|
1921
|
-
return res;
|
|
1922
|
-
}
|
|
1923
|
-
if (res?.featureFlags) {
|
|
1924
|
-
// clear flag call reported if we have new flags since they might have changed
|
|
1925
|
-
if (this.sendFeatureFlagEvent) {
|
|
1926
|
-
this.flagCallReported = {};
|
|
1927
|
-
}
|
|
1928
|
-
let newFeatureFlagDetails = res;
|
|
1929
|
-
if (res.errorsWhileComputingFlags) {
|
|
1930
|
-
// if not all flags were computed, we upsert flags instead of replacing them
|
|
1931
|
-
const currentFlagDetails = this.getKnownFeatureFlagDetails();
|
|
1932
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Cached feature flags: ', JSON.stringify(currentFlagDetails)));
|
|
1933
|
-
newFeatureFlagDetails = {
|
|
1934
|
-
...res,
|
|
1935
|
-
flags: { ...currentFlagDetails?.flags, ...res.flags },
|
|
1936
|
-
};
|
|
1937
|
-
}
|
|
1938
|
-
this.setKnownFeatureFlagDetails(newFeatureFlagDetails);
|
|
1939
|
-
// Mark that we hit the /flags endpoint so we can capture this in the $feature_flag_called event
|
|
1940
|
-
this.setPersistedProperty(PostHogPersistedProperty.FlagsEndpointWasHit, true);
|
|
1941
|
-
this.cacheSessionReplay('flags', res);
|
|
1942
|
-
}
|
|
1943
|
-
return res;
|
|
1944
|
-
})
|
|
1945
|
-
.finally(() => {
|
|
1946
|
-
this._flagsResponsePromise = undefined;
|
|
1947
|
-
});
|
|
1948
|
-
return this._flagsResponsePromise;
|
|
1949
|
-
}
|
|
1950
|
-
// We only store the flags and request id in the feature flag details storage key
|
|
1951
|
-
setKnownFeatureFlagDetails(flagsResponse) {
|
|
1952
|
-
this.wrap(() => {
|
|
1953
|
-
this.setPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails, flagsResponse);
|
|
1954
|
-
this._events.emit('featureflags', getFlagValuesFromFlags(flagsResponse?.flags ?? {}));
|
|
1955
|
-
});
|
|
1956
|
-
}
|
|
1957
|
-
getKnownFeatureFlagDetails() {
|
|
1958
|
-
const storedDetails = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails);
|
|
1959
|
-
if (!storedDetails) {
|
|
1960
|
-
// Rebuild from the stored feature flags and feature flag payloads
|
|
1961
|
-
const featureFlags = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlags);
|
|
1962
|
-
const featureFlagPayloads = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlagPayloads);
|
|
1963
|
-
if (featureFlags === undefined && featureFlagPayloads === undefined) {
|
|
1964
|
-
return undefined;
|
|
1965
|
-
}
|
|
1966
|
-
return createFlagsResponseFromFlagsAndPayloads(featureFlags ?? {}, featureFlagPayloads ?? {});
|
|
1967
|
-
}
|
|
1968
|
-
return normalizeFlagsResponse(storedDetails);
|
|
1969
|
-
}
|
|
1970
|
-
getKnownFeatureFlags() {
|
|
1971
|
-
const featureFlagDetails = this.getKnownFeatureFlagDetails();
|
|
1972
|
-
if (!featureFlagDetails) {
|
|
1973
|
-
return undefined;
|
|
1974
|
-
}
|
|
1975
|
-
return getFlagValuesFromFlags(featureFlagDetails.flags);
|
|
1976
|
-
}
|
|
1977
|
-
getKnownFeatureFlagPayloads() {
|
|
1978
|
-
const featureFlagDetails = this.getKnownFeatureFlagDetails();
|
|
1979
|
-
if (!featureFlagDetails) {
|
|
1980
|
-
return undefined;
|
|
1981
|
-
}
|
|
1982
|
-
return getPayloadsFromFlags(featureFlagDetails.flags);
|
|
1983
|
-
}
|
|
1984
|
-
getBootstrappedFeatureFlagDetails() {
|
|
1985
|
-
const details = this.getPersistedProperty(PostHogPersistedProperty.BootstrapFeatureFlagDetails);
|
|
1986
|
-
if (!details) {
|
|
1987
|
-
return undefined;
|
|
1988
|
-
}
|
|
1989
|
-
return details;
|
|
1990
|
-
}
|
|
1991
|
-
setBootstrappedFeatureFlagDetails(details) {
|
|
1992
|
-
this.setPersistedProperty(PostHogPersistedProperty.BootstrapFeatureFlagDetails, details);
|
|
1993
|
-
}
|
|
1994
|
-
getBootstrappedFeatureFlags() {
|
|
1995
|
-
const details = this.getBootstrappedFeatureFlagDetails();
|
|
1996
|
-
if (!details) {
|
|
1997
|
-
return undefined;
|
|
1998
|
-
}
|
|
1999
|
-
return getFlagValuesFromFlags(details.flags);
|
|
2000
|
-
}
|
|
2001
|
-
getBootstrappedFeatureFlagPayloads() {
|
|
2002
|
-
const details = this.getBootstrappedFeatureFlagDetails();
|
|
2003
|
-
if (!details) {
|
|
2004
|
-
return undefined;
|
|
2005
|
-
}
|
|
2006
|
-
return getPayloadsFromFlags(details.flags);
|
|
2007
|
-
}
|
|
2008
|
-
getFeatureFlag(key) {
|
|
2009
|
-
const details = this.getFeatureFlagDetails();
|
|
2010
|
-
if (!details) {
|
|
2011
|
-
// If we haven't loaded flags yet, or errored out, we respond with undefined
|
|
2012
|
-
return undefined;
|
|
2013
|
-
}
|
|
2014
|
-
const featureFlag = details.flags[key];
|
|
2015
|
-
let response = getFeatureFlagValue(featureFlag);
|
|
2016
|
-
if (response === undefined) {
|
|
2017
|
-
// For cases where the flag is unknown, return false
|
|
2018
|
-
response = false;
|
|
2019
|
-
}
|
|
2020
|
-
if (this.sendFeatureFlagEvent && !this.flagCallReported[key]) {
|
|
2021
|
-
const bootstrappedResponse = this.getBootstrappedFeatureFlags()?.[key];
|
|
2022
|
-
const bootstrappedPayload = this.getBootstrappedFeatureFlagPayloads()?.[key];
|
|
2023
|
-
this.flagCallReported[key] = true;
|
|
2024
|
-
this.capture('$feature_flag_called', {
|
|
2025
|
-
$feature_flag: key,
|
|
2026
|
-
$feature_flag_response: response,
|
|
2027
|
-
...maybeAdd('$feature_flag_id', featureFlag?.metadata?.id),
|
|
2028
|
-
...maybeAdd('$feature_flag_version', featureFlag?.metadata?.version),
|
|
2029
|
-
...maybeAdd('$feature_flag_reason', featureFlag?.reason?.description ?? featureFlag?.reason?.code),
|
|
2030
|
-
...maybeAdd('$feature_flag_bootstrapped_response', bootstrappedResponse),
|
|
2031
|
-
...maybeAdd('$feature_flag_bootstrapped_payload', bootstrappedPayload),
|
|
2032
|
-
// If we haven't yet received a response from the /flags endpoint, we must have used the bootstrapped value
|
|
2033
|
-
$used_bootstrap_value: !this.getPersistedProperty(PostHogPersistedProperty.FlagsEndpointWasHit),
|
|
2034
|
-
...maybeAdd('$feature_flag_request_id', details.requestId),
|
|
2035
|
-
});
|
|
2036
|
-
}
|
|
2037
|
-
// If we have flags we either return the value (true or string) or false
|
|
2038
|
-
return response;
|
|
2039
|
-
}
|
|
2040
|
-
getFeatureFlagPayload(key) {
|
|
2041
|
-
const payloads = this.getFeatureFlagPayloads();
|
|
2042
|
-
if (!payloads) {
|
|
2043
|
-
return undefined;
|
|
2044
|
-
}
|
|
2045
|
-
const response = payloads[key];
|
|
2046
|
-
// Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
|
|
2047
|
-
if (response === undefined) {
|
|
2048
|
-
return null;
|
|
2049
|
-
}
|
|
2050
|
-
return response;
|
|
2051
|
-
}
|
|
2052
|
-
getFeatureFlagPayloads() {
|
|
2053
|
-
return this.getFeatureFlagDetails()?.featureFlagPayloads;
|
|
2054
|
-
}
|
|
2055
|
-
getFeatureFlags() {
|
|
2056
|
-
// NOTE: We don't check for _initPromise here as the function is designed to be
|
|
2057
|
-
// callable before the state being loaded anyways
|
|
2058
|
-
return this.getFeatureFlagDetails()?.featureFlags;
|
|
2059
|
-
}
|
|
2060
|
-
getFeatureFlagDetails() {
|
|
2061
|
-
// NOTE: We don't check for _initPromise here as the function is designed to be
|
|
2062
|
-
// callable before the state being loaded anyways
|
|
2063
|
-
let details = this.getKnownFeatureFlagDetails();
|
|
2064
|
-
const overriddenFlags = this.getPersistedProperty(PostHogPersistedProperty.OverrideFeatureFlags);
|
|
2065
|
-
if (!overriddenFlags) {
|
|
2066
|
-
return details;
|
|
2067
|
-
}
|
|
2068
|
-
details = details ?? { featureFlags: {}, featureFlagPayloads: {}, flags: {} };
|
|
2069
|
-
const flags = details.flags ?? {};
|
|
2070
|
-
for (const key in overriddenFlags) {
|
|
2071
|
-
if (!overriddenFlags[key]) {
|
|
2072
|
-
delete flags[key];
|
|
2073
|
-
}
|
|
2074
|
-
else {
|
|
2075
|
-
flags[key] = updateFlagValue(flags[key], overriddenFlags[key]);
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
const result = {
|
|
2079
|
-
...details,
|
|
2080
|
-
flags,
|
|
2081
|
-
};
|
|
2082
|
-
return normalizeFlagsResponse(result);
|
|
2083
|
-
}
|
|
2084
|
-
getFeatureFlagsAndPayloads() {
|
|
2085
|
-
const flags = this.getFeatureFlags();
|
|
2086
|
-
const payloads = this.getFeatureFlagPayloads();
|
|
2087
|
-
return {
|
|
2088
|
-
flags,
|
|
2089
|
-
payloads,
|
|
2090
|
-
};
|
|
2091
|
-
}
|
|
2092
|
-
isFeatureEnabled(key) {
|
|
2093
|
-
const response = this.getFeatureFlag(key);
|
|
2094
|
-
if (response === undefined) {
|
|
2095
|
-
return undefined;
|
|
2096
|
-
}
|
|
2097
|
-
return !!response;
|
|
2098
|
-
}
|
|
2099
|
-
// Used when we want to trigger the reload but we don't care about the result
|
|
2100
|
-
reloadFeatureFlags(options) {
|
|
2101
|
-
this.flagsAsync(true)
|
|
2102
|
-
.then((res) => {
|
|
2103
|
-
options?.cb?.(undefined, res?.featureFlags);
|
|
2104
|
-
})
|
|
2105
|
-
.catch((e) => {
|
|
2106
|
-
options?.cb?.(e, undefined);
|
|
2107
|
-
if (!options?.cb) {
|
|
2108
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Error reloading feature flags', e));
|
|
2109
|
-
}
|
|
2110
|
-
});
|
|
2111
|
-
}
|
|
2112
|
-
async reloadRemoteConfigAsync() {
|
|
2113
|
-
return await this.remoteConfigAsync();
|
|
2114
|
-
}
|
|
2115
|
-
async reloadFeatureFlagsAsync(sendAnonDistinctId) {
|
|
2116
|
-
return (await this.flagsAsync(sendAnonDistinctId ?? true))?.featureFlags;
|
|
2117
|
-
}
|
|
2118
|
-
onFeatureFlags(cb) {
|
|
2119
|
-
return this.on('featureflags', async () => {
|
|
2120
|
-
const flags = this.getFeatureFlags();
|
|
2121
|
-
if (flags) {
|
|
2122
|
-
cb(flags);
|
|
2123
|
-
}
|
|
2124
|
-
});
|
|
2125
|
-
}
|
|
2126
|
-
onFeatureFlag(key, cb) {
|
|
2127
|
-
return this.on('featureflags', async () => {
|
|
2128
|
-
const flagResponse = this.getFeatureFlag(key);
|
|
2129
|
-
if (flagResponse !== undefined) {
|
|
2130
|
-
cb(flagResponse);
|
|
2131
|
-
}
|
|
2132
|
-
});
|
|
2133
|
-
}
|
|
2134
|
-
async overrideFeatureFlag(flags) {
|
|
2135
|
-
this.wrap(() => {
|
|
2136
|
-
if (flags === null) {
|
|
2137
|
-
return this.setPersistedProperty(PostHogPersistedProperty.OverrideFeatureFlags, null);
|
|
2138
|
-
}
|
|
2139
|
-
return this.setPersistedProperty(PostHogPersistedProperty.OverrideFeatureFlags, flags);
|
|
2140
|
-
});
|
|
2141
|
-
}
|
|
2142
|
-
/***
|
|
2143
|
-
*** ERROR TRACKING
|
|
2144
|
-
***/
|
|
2145
|
-
captureException(error, additionalProperties) {
|
|
2146
|
-
const properties = {
|
|
2147
|
-
$exception_level: 'error',
|
|
2148
|
-
$exception_list: [
|
|
2149
|
-
{
|
|
2150
|
-
type: isError(error) ? error.name : 'Error',
|
|
2151
|
-
value: isError(error) ? error.message : error,
|
|
2152
|
-
mechanism: {
|
|
2153
|
-
handled: true,
|
|
2154
|
-
synthetic: false,
|
|
2155
|
-
},
|
|
2156
|
-
},
|
|
2157
|
-
],
|
|
2158
|
-
...additionalProperties,
|
|
2159
|
-
};
|
|
2160
|
-
properties.$exception_personURL = new URL(`/project/${this.apiKey}/person/${this.getDistinctId()}`, this.host).toString();
|
|
2161
|
-
this.capture('$exception', properties);
|
|
2162
|
-
}
|
|
2163
|
-
/**
|
|
2164
|
-
* Capture written user feedback for a LLM trace. Numeric values are converted to strings.
|
|
2165
|
-
* @param traceId The trace ID to capture feedback for.
|
|
2166
|
-
* @param userFeedback The feedback to capture.
|
|
2167
|
-
*/
|
|
2168
|
-
captureTraceFeedback(traceId, userFeedback) {
|
|
2169
|
-
this.capture('$ai_feedback', {
|
|
2170
|
-
$ai_feedback_text: userFeedback,
|
|
2171
|
-
$ai_trace_id: String(traceId),
|
|
2172
|
-
});
|
|
2173
|
-
}
|
|
2174
|
-
/**
|
|
2175
|
-
* Capture a metric for a LLM trace. Numeric values are converted to strings.
|
|
2176
|
-
* @param traceId The trace ID to capture the metric for.
|
|
2177
|
-
* @param metricName The name of the metric to capture.
|
|
2178
|
-
* @param metricValue The value of the metric to capture.
|
|
2179
|
-
*/
|
|
2180
|
-
captureTraceMetric(traceId, metricName, metricValue) {
|
|
2181
|
-
this.capture('$ai_metric', {
|
|
2182
|
-
$ai_metric_name: metricName,
|
|
2183
|
-
$ai_metric_value: String(metricValue),
|
|
2184
|
-
$ai_trace_id: String(traceId),
|
|
2185
|
-
});
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
|
-
function getContext(window) {
|
|
2190
|
-
let context = {};
|
|
2191
|
-
if (window?.navigator) {
|
|
2192
|
-
const userAgent = window.navigator.userAgent;
|
|
2193
|
-
const osValue = os(window);
|
|
2194
|
-
context = {
|
|
2195
|
-
...context,
|
|
2196
|
-
...(osValue !== undefined && {
|
|
2197
|
-
$os: osValue
|
|
2198
|
-
}),
|
|
2199
|
-
$browser: browser(userAgent, window.navigator.vendor, !!window.opera),
|
|
2200
|
-
$referrer: window.document.referrer,
|
|
2201
|
-
$referring_domain: referringDomain(window.document.referrer),
|
|
2202
|
-
$device: device(userAgent),
|
|
2203
|
-
$current_url: window.location.href,
|
|
2204
|
-
$host: window.location.host,
|
|
2205
|
-
$pathname: window.location.pathname,
|
|
2206
|
-
$browser_version: browserVersion(userAgent, window.navigator.vendor, !!window.opera),
|
|
2207
|
-
$screen_height: window.screen.height,
|
|
2208
|
-
$screen_width: window.screen.width,
|
|
2209
|
-
$screen_dpr: window.devicePixelRatio
|
|
2210
|
-
};
|
|
2211
|
-
}
|
|
2212
|
-
context = {
|
|
2213
|
-
...context,
|
|
2214
|
-
$lib: 'js',
|
|
2215
|
-
$lib_version: version,
|
|
2216
|
-
$insert_id: Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10),
|
|
2217
|
-
$time: currentTimestamp() / 1000 // epoch time in seconds
|
|
2218
|
-
};
|
|
2219
|
-
return context; // TODO: strip empty props?
|
|
2220
|
-
}
|
|
2221
|
-
function includes(haystack, needle) {
|
|
2222
|
-
return haystack.indexOf(needle) >= 0;
|
|
2223
|
-
}
|
|
2224
|
-
function browser(userAgent, vendor, opera) {
|
|
2225
|
-
vendor = vendor || ''; // vendor is undefined for at least IE9
|
|
2226
|
-
if (opera || includes(userAgent, ' OPR/')) {
|
|
2227
|
-
if (includes(userAgent, 'Mini')) {
|
|
2228
|
-
return 'Opera Mini';
|
|
2229
|
-
}
|
|
2230
|
-
return 'Opera';
|
|
2231
|
-
} else if (/(BlackBerry|PlayBook|BB10)/i.test(userAgent)) {
|
|
2232
|
-
return 'BlackBerry';
|
|
2233
|
-
} else if (includes(userAgent, 'IEMobile') || includes(userAgent, 'WPDesktop')) {
|
|
2234
|
-
return 'Internet Explorer Mobile';
|
|
2235
|
-
} else if (includes(userAgent, 'SamsungBrowser/')) {
|
|
2236
|
-
// https://developer.samsung.com/internet/user-agent-string-format
|
|
2237
|
-
return 'Samsung Internet';
|
|
2238
|
-
} else if (includes(userAgent, 'Edge') || includes(userAgent, 'Edg/')) {
|
|
2239
|
-
return 'Microsoft Edge';
|
|
2240
|
-
} else if (includes(userAgent, 'FBIOS')) {
|
|
2241
|
-
return 'Facebook Mobile';
|
|
2242
|
-
} else if (includes(userAgent, 'Chrome')) {
|
|
2243
|
-
return 'Chrome';
|
|
2244
|
-
} else if (includes(userAgent, 'CriOS')) {
|
|
2245
|
-
return 'Chrome iOS';
|
|
2246
|
-
} else if (includes(userAgent, 'UCWEB') || includes(userAgent, 'UCBrowser')) {
|
|
2247
|
-
return 'UC Browser';
|
|
2248
|
-
} else if (includes(userAgent, 'FxiOS')) {
|
|
2249
|
-
return 'Firefox iOS';
|
|
2250
|
-
} else if (includes(vendor, 'Apple')) {
|
|
2251
|
-
if (includes(userAgent, 'Mobile')) {
|
|
2252
|
-
return 'Mobile Safari';
|
|
2253
|
-
}
|
|
2254
|
-
return 'Safari';
|
|
2255
|
-
} else if (includes(userAgent, 'Android')) {
|
|
2256
|
-
return 'Android Mobile';
|
|
2257
|
-
} else if (includes(userAgent, 'Konqueror')) {
|
|
2258
|
-
return 'Konqueror';
|
|
2259
|
-
} else if (includes(userAgent, 'Firefox')) {
|
|
2260
|
-
return 'Firefox';
|
|
2261
|
-
} else if (includes(userAgent, 'MSIE') || includes(userAgent, 'Trident/')) {
|
|
2262
|
-
return 'Internet Explorer';
|
|
2263
|
-
} else if (includes(userAgent, 'Gecko')) {
|
|
2264
|
-
return 'Mozilla';
|
|
2265
|
-
} else {
|
|
2266
|
-
return '';
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
function browserVersion(userAgent, vendor, opera) {
|
|
2270
|
-
const regexList = {
|
|
2271
|
-
'Internet Explorer Mobile': /rv:(\d+(\.\d+)?)/,
|
|
2272
|
-
'Microsoft Edge': /Edge?\/(\d+(\.\d+)?)/,
|
|
2273
|
-
Chrome: /Chrome\/(\d+(\.\d+)?)/,
|
|
2274
|
-
'Chrome iOS': /CriOS\/(\d+(\.\d+)?)/,
|
|
2275
|
-
'UC Browser': /(UCBrowser|UCWEB)\/(\d+(\.\d+)?)/,
|
|
2276
|
-
Safari: /Version\/(\d+(\.\d+)?)/,
|
|
2277
|
-
'Mobile Safari': /Version\/(\d+(\.\d+)?)/,
|
|
2278
|
-
Opera: /(Opera|OPR)\/(\d+(\.\d+)?)/,
|
|
2279
|
-
Firefox: /Firefox\/(\d+(\.\d+)?)/,
|
|
2280
|
-
'Firefox iOS': /FxiOS\/(\d+(\.\d+)?)/,
|
|
2281
|
-
Konqueror: /Konqueror:(\d+(\.\d+)?)/,
|
|
2282
|
-
BlackBerry: /BlackBerry (\d+(\.\d+)?)/,
|
|
2283
|
-
'Android Mobile': /android\s(\d+(\.\d+)?)/,
|
|
2284
|
-
'Samsung Internet': /SamsungBrowser\/(\d+(\.\d+)?)/,
|
|
2285
|
-
'Internet Explorer': /(rv:|MSIE )(\d+(\.\d+)?)/,
|
|
2286
|
-
Mozilla: /rv:(\d+(\.\d+)?)/
|
|
2287
|
-
};
|
|
2288
|
-
const browserString = browser(userAgent, vendor, opera);
|
|
2289
|
-
const regex = regexList[browserString] || undefined;
|
|
2290
|
-
if (regex === undefined) {
|
|
2291
|
-
return null;
|
|
2292
|
-
}
|
|
2293
|
-
const matches = userAgent.match(regex);
|
|
2294
|
-
if (!matches) {
|
|
2295
|
-
return null;
|
|
2296
|
-
}
|
|
2297
|
-
return parseFloat(matches[matches.length - 2]);
|
|
2298
|
-
}
|
|
2299
|
-
function os(window) {
|
|
2300
|
-
if (!window?.navigator) {
|
|
2301
|
-
return undefined;
|
|
2302
|
-
}
|
|
2303
|
-
const a = window.navigator.userAgent;
|
|
2304
|
-
if (/Windows/i.test(a)) {
|
|
2305
|
-
if (/Phone/.test(a) || /WPDesktop/.test(a)) {
|
|
2306
|
-
return 'Windows Phone';
|
|
2307
|
-
}
|
|
2308
|
-
return 'Windows';
|
|
2309
|
-
} else if (/(iPhone|iPad|iPod)/.test(a)) {
|
|
2310
|
-
return 'iOS';
|
|
2311
|
-
} else if (/Android/.test(a)) {
|
|
2312
|
-
return 'Android';
|
|
2313
|
-
} else if (/(BlackBerry|PlayBook|BB10)/i.test(a)) {
|
|
2314
|
-
return 'BlackBerry';
|
|
2315
|
-
} else if (/Mac/i.test(a)) {
|
|
2316
|
-
return 'Mac OS X';
|
|
2317
|
-
} else if (/Linux/.test(a)) {
|
|
2318
|
-
return 'Linux';
|
|
2319
|
-
} else if (/CrOS/.test(a)) {
|
|
2320
|
-
return 'Chrome OS';
|
|
2321
|
-
} else {
|
|
2322
|
-
return undefined;
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
function device(userAgent) {
|
|
2326
|
-
if (/Windows Phone/i.test(userAgent) || /WPDesktop/.test(userAgent)) {
|
|
2327
|
-
return 'Windows Phone';
|
|
2328
|
-
} else if (/iPad/.test(userAgent)) {
|
|
2329
|
-
return 'iPad';
|
|
2330
|
-
} else if (/iPod/.test(userAgent)) {
|
|
2331
|
-
return 'iPod Touch';
|
|
2332
|
-
} else if (/iPhone/.test(userAgent)) {
|
|
2333
|
-
return 'iPhone';
|
|
2334
|
-
} else if (/(BlackBerry|PlayBook|BB10)/i.test(userAgent)) {
|
|
2335
|
-
return 'BlackBerry';
|
|
2336
|
-
} else if (/Android/.test(userAgent)) {
|
|
2337
|
-
return 'Android';
|
|
2338
|
-
} else {
|
|
2339
|
-
return '';
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
function referringDomain(referrer) {
|
|
2343
|
-
const split = referrer.split('/');
|
|
2344
|
-
if (split.length >= 3) {
|
|
2345
|
-
return split[2];
|
|
2346
|
-
}
|
|
2347
|
-
return '';
|
|
2348
|
-
}
|
|
2349
|
-
|
|
2350
|
-
// Methods partially borrowed from quirksmode.org/js/cookies.html
|
|
2351
|
-
const cookieStore = {
|
|
2352
|
-
getItem(key) {
|
|
2353
|
-
try {
|
|
2354
|
-
const nameEQ = key + '=';
|
|
2355
|
-
const ca = document.cookie.split(';');
|
|
2356
|
-
for (let i = 0; i < ca.length; i++) {
|
|
2357
|
-
let c = ca[i];
|
|
2358
|
-
while (c.charAt(0) == ' ') {
|
|
2359
|
-
c = c.substring(1, c.length);
|
|
2360
|
-
}
|
|
2361
|
-
if (c.indexOf(nameEQ) === 0) {
|
|
2362
|
-
return decodeURIComponent(c.substring(nameEQ.length, c.length));
|
|
2363
|
-
}
|
|
2364
|
-
}
|
|
2365
|
-
} catch (err) {}
|
|
2366
|
-
return null;
|
|
2367
|
-
},
|
|
2368
|
-
setItem(key, value) {
|
|
2369
|
-
try {
|
|
2370
|
-
const cdomain = '',
|
|
2371
|
-
expires = '',
|
|
2372
|
-
secure = '';
|
|
2373
|
-
const new_cookie_val = key + '=' + encodeURIComponent(value) + expires + '; path=/' + cdomain + secure;
|
|
2374
|
-
document.cookie = new_cookie_val;
|
|
2375
|
-
} catch (err) {
|
|
2376
|
-
return;
|
|
2377
|
-
}
|
|
2378
|
-
},
|
|
2379
|
-
removeItem(name) {
|
|
2380
|
-
try {
|
|
2381
|
-
cookieStore.setItem(name, '');
|
|
2382
|
-
} catch (err) {
|
|
2383
|
-
return;
|
|
2384
|
-
}
|
|
2385
|
-
},
|
|
2386
|
-
clear() {
|
|
2387
|
-
document.cookie = '';
|
|
2388
|
-
},
|
|
2389
|
-
getAllKeys() {
|
|
2390
|
-
const ca = document.cookie.split(';');
|
|
2391
|
-
const keys = [];
|
|
2392
|
-
for (let i = 0; i < ca.length; i++) {
|
|
2393
|
-
let c = ca[i];
|
|
2394
|
-
while (c.charAt(0) == ' ') {
|
|
2395
|
-
c = c.substring(1, c.length);
|
|
2396
|
-
}
|
|
2397
|
-
keys.push(c.split('=')[0]);
|
|
2398
|
-
}
|
|
2399
|
-
return keys;
|
|
2400
|
-
}
|
|
2401
|
-
};
|
|
2402
|
-
const createStorageLike = store => {
|
|
2403
|
-
return {
|
|
2404
|
-
getItem(key) {
|
|
2405
|
-
return store.getItem(key);
|
|
2406
|
-
},
|
|
2407
|
-
setItem(key, value) {
|
|
2408
|
-
store.setItem(key, value);
|
|
2409
|
-
},
|
|
2410
|
-
removeItem(key) {
|
|
2411
|
-
store.removeItem(key);
|
|
2412
|
-
},
|
|
2413
|
-
clear() {
|
|
2414
|
-
store.clear();
|
|
2415
|
-
},
|
|
2416
|
-
getAllKeys() {
|
|
2417
|
-
const keys = [];
|
|
2418
|
-
for (const key in localStorage) {
|
|
2419
|
-
keys.push(key);
|
|
2420
|
-
}
|
|
2421
|
-
return keys;
|
|
2422
|
-
}
|
|
2423
|
-
};
|
|
2424
|
-
};
|
|
2425
|
-
const checkStoreIsSupported = (storage, key = '__mplssupport__') => {
|
|
2426
|
-
try {
|
|
2427
|
-
const val = 'xyz';
|
|
2428
|
-
storage.setItem(key, val);
|
|
2429
|
-
if (storage.getItem(key) !== val) {
|
|
2430
|
-
return false;
|
|
2431
|
-
}
|
|
2432
|
-
storage.removeItem(key);
|
|
2433
|
-
return true;
|
|
2434
|
-
} catch (err) {
|
|
2435
|
-
return false;
|
|
2436
|
-
}
|
|
2437
|
-
};
|
|
2438
|
-
let localStore = undefined;
|
|
2439
|
-
let sessionStore = undefined;
|
|
2440
|
-
const createMemoryStorage = () => {
|
|
2441
|
-
const _cache = {};
|
|
2442
|
-
const store = {
|
|
2443
|
-
getItem(key) {
|
|
2444
|
-
return _cache[key];
|
|
2445
|
-
},
|
|
2446
|
-
setItem(key, value) {
|
|
2447
|
-
_cache[key] = value !== null ? value : undefined;
|
|
2448
|
-
},
|
|
2449
|
-
removeItem(key) {
|
|
2450
|
-
delete _cache[key];
|
|
2451
|
-
},
|
|
2452
|
-
clear() {
|
|
2453
|
-
for (const key in _cache) {
|
|
2454
|
-
delete _cache[key];
|
|
2455
|
-
}
|
|
2456
|
-
},
|
|
2457
|
-
getAllKeys() {
|
|
2458
|
-
const keys = [];
|
|
2459
|
-
for (const key in _cache) {
|
|
2460
|
-
keys.push(key);
|
|
2461
|
-
}
|
|
2462
|
-
return keys;
|
|
2463
|
-
}
|
|
2464
|
-
};
|
|
2465
|
-
return store;
|
|
2466
|
-
};
|
|
2467
|
-
const getStorage = (type, window) => {
|
|
2468
|
-
if (window) {
|
|
2469
|
-
if (!localStore) {
|
|
2470
|
-
const _localStore = createStorageLike(window.localStorage);
|
|
2471
|
-
localStore = checkStoreIsSupported(_localStore) ? _localStore : undefined;
|
|
2472
|
-
}
|
|
2473
|
-
if (!sessionStore) {
|
|
2474
|
-
const _sessionStore = createStorageLike(window.sessionStorage);
|
|
2475
|
-
sessionStore = checkStoreIsSupported(_sessionStore) ? _sessionStore : undefined;
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
switch (type) {
|
|
2479
|
-
case 'cookie':
|
|
2480
|
-
return cookieStore || localStore || sessionStore || createMemoryStorage();
|
|
2481
|
-
case 'localStorage':
|
|
2482
|
-
return localStore || sessionStore || createMemoryStorage();
|
|
2483
|
-
case 'sessionStorage':
|
|
2484
|
-
return sessionStore || createMemoryStorage();
|
|
2485
|
-
case 'memory':
|
|
2486
|
-
return createMemoryStorage();
|
|
2487
|
-
default:
|
|
2488
|
-
return createMemoryStorage();
|
|
2489
|
-
}
|
|
2490
|
-
};
|
|
2491
|
-
|
|
2492
|
-
// import { patch } from 'rrweb/typings/utils'
|
|
2493
|
-
// copied from: https://github.com/PostHog/posthog-js/blob/main/src/extensions/replay/rrweb-plugins/patch.ts
|
|
2494
|
-
// which was copied from https://github.com/rrweb-io/rrweb/blob/8aea5b00a4dfe5a6f59bd2ae72bb624f45e51e81/packages/rrweb/src/utils.ts#L129
|
|
2495
|
-
// which was copied from https://github.com/getsentry/sentry-javascript/blob/b2109071975af8bf0316d3b5b38f519bdaf5dc15/packages/utils/src/object.ts
|
|
2496
|
-
// copied from: https://github.com/PostHog/posthog-js/blob/main/react/src/utils/type-utils.ts#L4
|
|
2497
|
-
const isFunction = function (f) {
|
|
2498
|
-
return typeof f === 'function';
|
|
2499
|
-
};
|
|
2500
|
-
function patch(source, name, replacement) {
|
|
2501
|
-
try {
|
|
2502
|
-
if (!(name in source)) {
|
|
2503
|
-
return () => {
|
|
2504
|
-
//
|
|
2505
|
-
};
|
|
2506
|
-
}
|
|
2507
|
-
const original = source[name];
|
|
2508
|
-
const wrapped = replacement(original);
|
|
2509
|
-
// Make sure it's a function first, as we need to attach an empty prototype for `defineProperties` to work
|
|
2510
|
-
// otherwise it'll throw "TypeError: Object.defineProperties called on non-object"
|
|
2511
|
-
if (isFunction(wrapped)) {
|
|
2512
|
-
wrapped.prototype = wrapped.prototype || {};
|
|
2513
|
-
Object.defineProperties(wrapped, {
|
|
2514
|
-
__posthog_wrapped__: {
|
|
2515
|
-
enumerable: false,
|
|
2516
|
-
value: true
|
|
2517
|
-
}
|
|
2518
|
-
});
|
|
2519
|
-
}
|
|
2520
|
-
source[name] = wrapped;
|
|
2521
|
-
return () => {
|
|
2522
|
-
source[name] = original;
|
|
2523
|
-
};
|
|
2524
|
-
} catch {
|
|
2525
|
-
return () => {
|
|
2526
|
-
//
|
|
2527
|
-
};
|
|
2528
|
-
// This can throw if multiple fill happens on a global object like XMLHttpRequest
|
|
2529
|
-
// Fixes https://github.com/getsentry/sentry-javascript/issues/2043
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
|
-
class PostHog extends PostHogCore {
|
|
2534
|
-
constructor(apiKey, options) {
|
|
2535
|
-
super(apiKey, options);
|
|
2536
|
-
this._lastPathname = '';
|
|
2537
|
-
// posthog-js stores options in one object on
|
|
2538
|
-
this._storageKey = options?.persistence_name ? `ph_${options.persistence_name}` : `ph_${apiKey}_posthog`;
|
|
2539
|
-
this._storage = getStorage(options?.persistence || 'localStorage', this.getWindow());
|
|
2540
|
-
this.setupBootstrap(options);
|
|
2541
|
-
if (options?.preloadFeatureFlags !== false) {
|
|
2542
|
-
this.reloadFeatureFlags();
|
|
2543
|
-
}
|
|
2544
|
-
if (options?.captureHistoryEvents && typeof window !== 'undefined') {
|
|
2545
|
-
this._lastPathname = window?.location?.pathname || '';
|
|
2546
|
-
this.setupHistoryEventTracking();
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
getWindow() {
|
|
2550
|
-
return typeof window !== 'undefined' ? window : undefined;
|
|
2551
|
-
}
|
|
2552
|
-
getPersistedProperty(key) {
|
|
2553
|
-
if (!this._storageCache) {
|
|
2554
|
-
this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || '{}') || {};
|
|
2555
|
-
}
|
|
2556
|
-
return this._storageCache[key];
|
|
2557
|
-
}
|
|
2558
|
-
setPersistedProperty(key, value) {
|
|
2559
|
-
if (!this._storageCache) {
|
|
2560
|
-
this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || '{}') || {};
|
|
2561
|
-
}
|
|
2562
|
-
if (value === null) {
|
|
2563
|
-
delete this._storageCache[key];
|
|
2564
|
-
} else {
|
|
2565
|
-
this._storageCache[key] = value;
|
|
2566
|
-
}
|
|
2567
|
-
this._storage.setItem(this._storageKey, JSON.stringify(this._storageCache));
|
|
2568
|
-
}
|
|
2569
|
-
fetch(url, options) {
|
|
2570
|
-
const fetchFn = getFetch();
|
|
2571
|
-
if (!fetchFn) {
|
|
2572
|
-
// error will be handled by the caller (fetchWithRetry)
|
|
2573
|
-
return Promise.reject(new Error('Fetch API is not available in this environment.'));
|
|
2574
|
-
}
|
|
2575
|
-
return fetchFn(url, options);
|
|
2576
|
-
}
|
|
2577
|
-
getLibraryId() {
|
|
2578
|
-
return 'posthog-js-lite';
|
|
2579
|
-
}
|
|
2580
|
-
getLibraryVersion() {
|
|
2581
|
-
return version;
|
|
2582
|
-
}
|
|
2583
|
-
getCustomUserAgent() {
|
|
2584
|
-
return;
|
|
2585
|
-
}
|
|
2586
|
-
getCommonEventProperties() {
|
|
2587
|
-
return {
|
|
2588
|
-
...super.getCommonEventProperties(),
|
|
2589
|
-
...getContext(this.getWindow())
|
|
2590
|
-
};
|
|
2591
|
-
}
|
|
2592
|
-
setupHistoryEventTracking() {
|
|
2593
|
-
const window = this.getWindow();
|
|
2594
|
-
if (!window) {
|
|
2595
|
-
return;
|
|
2596
|
-
}
|
|
2597
|
-
// Old fashioned, we could also use arrow functions but I think the closure for a patch is more reliable
|
|
2598
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
2599
|
-
const self = this;
|
|
2600
|
-
patch(window.history, 'pushState', originalPushState => {
|
|
2601
|
-
return function patchedPushState(state, title, url) {
|
|
2602
|
-
;
|
|
2603
|
-
originalPushState.call(this, state, title, url);
|
|
2604
|
-
self.captureNavigationEvent('pushState');
|
|
2605
|
-
};
|
|
2606
|
-
});
|
|
2607
|
-
patch(window.history, 'replaceState', originalReplaceState => {
|
|
2608
|
-
return function patchedReplaceState(state, title, url) {
|
|
2609
|
-
;
|
|
2610
|
-
originalReplaceState.call(this, state, title, url);
|
|
2611
|
-
self.captureNavigationEvent('replaceState');
|
|
2612
|
-
};
|
|
2613
|
-
});
|
|
2614
|
-
// For popstate we need to listen to the event instead of overriding a method
|
|
2615
|
-
window.addEventListener('popstate', () => {
|
|
2616
|
-
this.captureNavigationEvent('popstate');
|
|
2617
|
-
});
|
|
2618
|
-
}
|
|
2619
|
-
captureNavigationEvent(navigationType) {
|
|
2620
|
-
const window = this.getWindow();
|
|
2621
|
-
if (!window) {
|
|
2622
|
-
return;
|
|
2623
|
-
}
|
|
2624
|
-
const currentPathname = window.location.pathname;
|
|
2625
|
-
// Only capture pageview if the pathname has changed
|
|
2626
|
-
if (currentPathname !== this._lastPathname) {
|
|
2627
|
-
this.capture('$pageview', {
|
|
2628
|
-
navigation_type: navigationType
|
|
2629
|
-
});
|
|
2630
|
-
this._lastPathname = currentPathname;
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
|
|
2635
|
-
export { PostHog, PostHog as default };
|
|
2636
|
-
//# sourceMappingURL=index.mjs.map
|