@posthog/core 1.28.6 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Minimal cookie-reading interface compatible with Next.js `cookies()`,
3
+ * `request.cookies`, and plain objects.
4
+ */
5
+ export interface CookieStore {
6
+ get(name: string): {
7
+ value: string;
8
+ } | undefined;
9
+ }
10
+ /**
11
+ * Adapts a raw `Cookie` header string into a {@link CookieStore}.
12
+ */
13
+ export declare function cookieStoreFromHeader(cookieHeader: string): CookieStore;
14
+ export interface PostHogCookieState {
15
+ distinctId: string;
16
+ isIdentified: boolean;
17
+ sessionId?: string;
18
+ deviceId?: string;
19
+ }
20
+ /**
21
+ * Returns the PostHog cookie name for the given API key.
22
+ *
23
+ * PostHog-js stores state in a cookie named `ph_<sanitized_token>_posthog`.
24
+ * The token is sanitized by replacing `+` with `PL`, `/` with `SL`, `=` with `EQ`.
25
+ *
26
+ * @param apiKey - The PostHog project API key
27
+ * @returns The cookie name string
28
+ */
29
+ export declare function getPostHogCookieName(apiKey: string): string;
30
+ /**
31
+ * Serializes an anonymous ID into the JSON format posthog-js expects.
32
+ *
33
+ * When `distinct_id === $device_id`, posthog-js treats the user as anonymous.
34
+ *
35
+ * @param anonymousId - The anonymous distinct ID to serialize
36
+ * @returns JSON string suitable for the PostHog cookie value
37
+ */
38
+ export declare function serializePostHogCookie(anonymousId: string): string;
39
+ /**
40
+ * Reads and parses the PostHog cookie from a cookie store.
41
+ *
42
+ * Compatible with Next.js `cookies()`, `request.cookies`, and any object
43
+ * with a `get(name)` method that returns `{ value: string } | undefined`.
44
+ */
45
+ export declare function readPostHogCookie(cookies: CookieStore, apiKey: string): PostHogCookieState | null;
46
+ /**
47
+ * Converts cookie state into PostHog properties (e.g. `$session_id`, `$device_id`).
48
+ */
49
+ export declare function cookieStateToProperties(state: PostHogCookieState | null): Record<string, string> | undefined;
50
+ /**
51
+ * Parses a PostHog cookie value and extracts identity information.
52
+ *
53
+ * The cookie value is a JSON object containing `distinct_id` and `$user_state`.
54
+ * A user is considered identified if `$user_state` is `'identified'`.
55
+ *
56
+ * @param cookieValue - The raw cookie string value
57
+ * @returns Parsed identity state, or null if the cookie is missing/invalid
58
+ */
59
+ export declare function parsePostHogCookie(cookieValue: string): PostHogCookieState | null;
60
+ export interface ConsentCookieConfig {
61
+ consent_persistence_name?: string | null;
62
+ opt_out_capturing_cookie_prefix?: string | null;
63
+ }
64
+ export declare function getConsentCookieName(apiKey: string, config?: ConsentCookieConfig): string;
65
+ export interface ConsentConfig extends ConsentCookieConfig {
66
+ opt_out_capturing_by_default?: boolean;
67
+ }
68
+ export declare function isOptedOut(cookies: CookieStore, apiKey: string, config?: ConsentConfig): boolean;
69
+ //# sourceMappingURL=cookie.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAA;CACjD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,CAmBvE;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,OAAO,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CASlE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAIjG;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAY5G;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAuBjF;AAED,MAAM,WAAW,mBAAmB;IAClC,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxC,+BAA+B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAChD;AAID,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAQzF;AAED,MAAM,WAAW,aAAc,SAAQ,mBAAmB;IACxD,4BAA4B,CAAC,EAAE,OAAO,CAAA;CACvC;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAUhG"}
package/dist/cookie.js ADDED
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ getConsentCookieName: ()=>getConsentCookieName,
28
+ parsePostHogCookie: ()=>parsePostHogCookie,
29
+ isOptedOut: ()=>isOptedOut,
30
+ readPostHogCookie: ()=>readPostHogCookie,
31
+ serializePostHogCookie: ()=>serializePostHogCookie,
32
+ cookieStateToProperties: ()=>cookieStateToProperties,
33
+ getPostHogCookieName: ()=>getPostHogCookieName,
34
+ cookieStoreFromHeader: ()=>cookieStoreFromHeader
35
+ });
36
+ const index_js_namespaceObject = require("./utils/index.js");
37
+ const uuidv7_js_namespaceObject = require("./vendor/uuidv7.js");
38
+ const COOKIE_PREFIX = 'ph_';
39
+ const COOKIE_SUFFIX = '_posthog';
40
+ function cookieStoreFromHeader(cookieHeader) {
41
+ const cookies = {};
42
+ if (cookieHeader) for (const pair of cookieHeader.split(';')){
43
+ const [key, ...valueParts] = pair.trim().split('=');
44
+ if (key) {
45
+ const raw = valueParts.join('=').trim();
46
+ try {
47
+ cookies[key.trim()] = decodeURIComponent(raw);
48
+ } catch {
49
+ cookies[key.trim()] = raw;
50
+ }
51
+ }
52
+ }
53
+ return {
54
+ get: (name)=>name in cookies ? {
55
+ value: cookies[name]
56
+ } : void 0
57
+ };
58
+ }
59
+ function getPostHogCookieName(apiKey) {
60
+ const sanitized = apiKey.replace(/\+/g, 'PL').replace(/\//g, 'SL').replace(/=/g, 'EQ');
61
+ return `${COOKIE_PREFIX}${sanitized}${COOKIE_SUFFIX}`;
62
+ }
63
+ function serializePostHogCookie(anonymousId) {
64
+ const now = Date.now();
65
+ const sessionId = (0, uuidv7_js_namespaceObject.uuidv7)();
66
+ return JSON.stringify({
67
+ distinct_id: anonymousId,
68
+ $device_id: anonymousId,
69
+ $user_state: 'anonymous',
70
+ $sesid: [
71
+ now,
72
+ sessionId,
73
+ now
74
+ ]
75
+ });
76
+ }
77
+ function readPostHogCookie(cookies, apiKey) {
78
+ const cookieName = getPostHogCookieName(apiKey);
79
+ const cookie = cookies.get(cookieName);
80
+ return cookie ? parsePostHogCookie(cookie.value) : null;
81
+ }
82
+ function cookieStateToProperties(state) {
83
+ if (!state) return;
84
+ const props = {};
85
+ if (state.sessionId) props.$session_id = state.sessionId;
86
+ if (state.deviceId) props.$device_id = state.deviceId;
87
+ return Object.keys(props).length > 0 ? props : void 0;
88
+ }
89
+ function parsePostHogCookie(cookieValue) {
90
+ if (!cookieValue) return null;
91
+ try {
92
+ const parsed = JSON.parse(cookieValue);
93
+ if (!parsed || 'object' != typeof parsed || !parsed.distinct_id) return null;
94
+ const sesid = (0, index_js_namespaceObject.isArray)(parsed.$sesid) ? parsed.$sesid[1] : void 0;
95
+ return {
96
+ distinctId: String(parsed.distinct_id),
97
+ isIdentified: 'identified' === parsed.$user_state,
98
+ sessionId: 'string' == typeof sesid ? sesid : void 0,
99
+ deviceId: 'string' == typeof parsed.$device_id ? parsed.$device_id : void 0
100
+ };
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+ const CONSENT_PREFIX = '__ph_opt_in_out_';
106
+ function getConsentCookieName(apiKey, config) {
107
+ if (config?.consent_persistence_name) return config.consent_persistence_name;
108
+ if (config?.opt_out_capturing_cookie_prefix) return config.opt_out_capturing_cookie_prefix + apiKey;
109
+ return CONSENT_PREFIX + apiKey;
110
+ }
111
+ function isOptedOut(cookies, apiKey, config) {
112
+ const cookieName = getConsentCookieName(apiKey, config);
113
+ const cookie = cookies.get(cookieName);
114
+ if (cookie) return (0, index_js_namespaceObject.isNoLike)(cookie.value);
115
+ return config?.opt_out_capturing_by_default ?? false;
116
+ }
117
+ exports.cookieStateToProperties = __webpack_exports__.cookieStateToProperties;
118
+ exports.cookieStoreFromHeader = __webpack_exports__.cookieStoreFromHeader;
119
+ exports.getConsentCookieName = __webpack_exports__.getConsentCookieName;
120
+ exports.getPostHogCookieName = __webpack_exports__.getPostHogCookieName;
121
+ exports.isOptedOut = __webpack_exports__.isOptedOut;
122
+ exports.parsePostHogCookie = __webpack_exports__.parsePostHogCookie;
123
+ exports.readPostHogCookie = __webpack_exports__.readPostHogCookie;
124
+ exports.serializePostHogCookie = __webpack_exports__.serializePostHogCookie;
125
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
126
+ "cookieStateToProperties",
127
+ "cookieStoreFromHeader",
128
+ "getConsentCookieName",
129
+ "getPostHogCookieName",
130
+ "isOptedOut",
131
+ "parsePostHogCookie",
132
+ "readPostHogCookie",
133
+ "serializePostHogCookie"
134
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
135
+ Object.defineProperty(exports, '__esModule', {
136
+ value: true
137
+ });
@@ -0,0 +1,82 @@
1
+ import { isArray, isNoLike } from "./utils/index.mjs";
2
+ import { uuidv7 } from "./vendor/uuidv7.mjs";
3
+ const COOKIE_PREFIX = 'ph_';
4
+ const COOKIE_SUFFIX = '_posthog';
5
+ function cookieStoreFromHeader(cookieHeader) {
6
+ const cookies = {};
7
+ if (cookieHeader) for (const pair of cookieHeader.split(';')){
8
+ const [key, ...valueParts] = pair.trim().split('=');
9
+ if (key) {
10
+ const raw = valueParts.join('=').trim();
11
+ try {
12
+ cookies[key.trim()] = decodeURIComponent(raw);
13
+ } catch {
14
+ cookies[key.trim()] = raw;
15
+ }
16
+ }
17
+ }
18
+ return {
19
+ get: (name)=>name in cookies ? {
20
+ value: cookies[name]
21
+ } : void 0
22
+ };
23
+ }
24
+ function getPostHogCookieName(apiKey) {
25
+ const sanitized = apiKey.replace(/\+/g, 'PL').replace(/\//g, 'SL').replace(/=/g, 'EQ');
26
+ return `${COOKIE_PREFIX}${sanitized}${COOKIE_SUFFIX}`;
27
+ }
28
+ function serializePostHogCookie(anonymousId) {
29
+ const now = Date.now();
30
+ const sessionId = uuidv7();
31
+ return JSON.stringify({
32
+ distinct_id: anonymousId,
33
+ $device_id: anonymousId,
34
+ $user_state: 'anonymous',
35
+ $sesid: [
36
+ now,
37
+ sessionId,
38
+ now
39
+ ]
40
+ });
41
+ }
42
+ function readPostHogCookie(cookies, apiKey) {
43
+ const cookieName = getPostHogCookieName(apiKey);
44
+ const cookie = cookies.get(cookieName);
45
+ return cookie ? parsePostHogCookie(cookie.value) : null;
46
+ }
47
+ function cookieStateToProperties(state) {
48
+ if (!state) return;
49
+ const props = {};
50
+ if (state.sessionId) props.$session_id = state.sessionId;
51
+ if (state.deviceId) props.$device_id = state.deviceId;
52
+ return Object.keys(props).length > 0 ? props : void 0;
53
+ }
54
+ function parsePostHogCookie(cookieValue) {
55
+ if (!cookieValue) return null;
56
+ try {
57
+ const parsed = JSON.parse(cookieValue);
58
+ if (!parsed || 'object' != typeof parsed || !parsed.distinct_id) return null;
59
+ const sesid = isArray(parsed.$sesid) ? parsed.$sesid[1] : void 0;
60
+ return {
61
+ distinctId: String(parsed.distinct_id),
62
+ isIdentified: 'identified' === parsed.$user_state,
63
+ sessionId: 'string' == typeof sesid ? sesid : void 0,
64
+ deviceId: 'string' == typeof parsed.$device_id ? parsed.$device_id : void 0
65
+ };
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+ const CONSENT_PREFIX = '__ph_opt_in_out_';
71
+ function getConsentCookieName(apiKey, config) {
72
+ if (config?.consent_persistence_name) return config.consent_persistence_name;
73
+ if (config?.opt_out_capturing_cookie_prefix) return config.opt_out_capturing_cookie_prefix + apiKey;
74
+ return CONSENT_PREFIX + apiKey;
75
+ }
76
+ function isOptedOut(cookies, apiKey, config) {
77
+ const cookieName = getConsentCookieName(apiKey, config);
78
+ const cookie = cookies.get(cookieName);
79
+ if (cookie) return isNoLike(cookie.value);
80
+ return config?.opt_out_capturing_by_default ?? false;
81
+ }
82
+ export { cookieStateToProperties, cookieStoreFromHeader, getConsentCookieName, getPostHogCookieName, isOptedOut, parsePostHogCookie, readPostHogCookie, serializePostHogCookie };
package/dist/gzip.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  export declare function isGzipSupported(): boolean;
6
6
  export declare const isNativeAsyncGzipReadError: (error: unknown) => boolean;
7
+ export declare const isNativeAsyncGzipError: (error: unknown) => boolean;
7
8
  export type GzipCompressOptions = {
8
9
  /**
9
10
  * By default this helper swallows compression errors and returns null.
@@ -1 +1 @@
1
- {"version":3,"file":"gzip.d.ts","sourceRoot":"","sources":["../src/gzip.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAOzC;AAED,eAAO,MAAM,0BAA0B,GAAI,OAAO,OAAO,KAAG,OAQ3D,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,UAAO,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CA8BrH"}
1
+ {"version":3,"file":"gzip.d.ts","sourceRoot":"","sources":["../src/gzip.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAOzC;AAID,eAAO,MAAM,0BAA0B,GAAI,OAAO,OAAO,KAAG,OAQ3D,CAAA;AAED,eAAO,MAAM,sBAAsB,GAAI,OAAO,OAAO,KAAG,OAQvD,CAAA;AA0DD,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,UAAO,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAgCrH"}
package/dist/gzip.js CHANGED
@@ -26,21 +26,60 @@ __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  gzipCompress: ()=>gzipCompress,
28
28
  isGzipSupported: ()=>isGzipSupported,
29
+ isNativeAsyncGzipError: ()=>isNativeAsyncGzipError,
29
30
  isNativeAsyncGzipReadError: ()=>isNativeAsyncGzipReadError
30
31
  });
31
32
  function isGzipSupported() {
32
33
  return 'CompressionStream' in globalThis && 'TextEncoder' in globalThis && 'Response' in globalThis && 'function' == typeof Response.prototype.blob;
33
34
  }
35
+ const NATIVE_GZIP_VALIDATION_ERROR = 'NativeGzipValidationError';
34
36
  const isNativeAsyncGzipReadError = (error)=>{
35
37
  if (!error || 'object' != typeof error) return false;
36
38
  const name = 'name' in error ? String(error.name) : '';
37
39
  return 'NotReadableError' === name;
38
40
  };
41
+ const isNativeAsyncGzipError = (error)=>{
42
+ if (!error || 'object' != typeof error) return false;
43
+ const name = 'name' in error ? String(error.name) : '';
44
+ return isNativeAsyncGzipReadError(error) || name === NATIVE_GZIP_VALIDATION_ERROR;
45
+ };
46
+ let crc32Table;
47
+ const getCrc32Table = ()=>{
48
+ if (crc32Table) return crc32Table;
49
+ crc32Table = [];
50
+ for(let i = 0; i < 256; i++){
51
+ let crc = i;
52
+ for(let j = 0; j < 8; j++)crc = 1 & crc ? 0xedb88320 ^ crc >>> 1 : crc >>> 1;
53
+ crc32Table[i] = crc >>> 0;
54
+ }
55
+ return crc32Table;
56
+ };
57
+ const crc32 = (bytes)=>{
58
+ const table = getCrc32Table();
59
+ let crc = 0xffffffff;
60
+ for(let i = 0; i < bytes.length; i++)crc = table[(crc ^ bytes[i]) & 0xff] ^ crc >>> 8;
61
+ return (0xffffffff ^ crc) >>> 0;
62
+ };
63
+ const throwNativeGzipValidationError = (reason)=>{
64
+ const error = new Error(`Native gzip produced invalid output: ${reason}`);
65
+ error.name = NATIVE_GZIP_VALIDATION_ERROR;
66
+ throw error;
67
+ };
68
+ const validateNativeGzip = async (compressed, inputBytes)=>{
69
+ if (compressed.size < 18) throwNativeGzipValidationError('too-short');
70
+ const header = new Uint8Array(await compressed.slice(0, 10).arrayBuffer());
71
+ if (0x1f !== header[0] || 0x8b !== header[1] || 0x08 !== header[2]) throwNativeGzipValidationError('invalid-header');
72
+ const trailer = new DataView(await compressed.slice(compressed.size - 8).arrayBuffer());
73
+ if (trailer.getUint32(0, true) !== crc32(inputBytes)) throwNativeGzipValidationError('invalid-crc');
74
+ const inputSize = inputBytes.length >>> 0;
75
+ if (trailer.getUint32(4, true) !== inputSize) throwNativeGzipValidationError('invalid-size');
76
+ };
39
77
  async function gzipCompress(input, isDebug = true, options) {
40
78
  try {
79
+ const inputBytes = new TextEncoder().encode(input);
41
80
  const compressedStream = new CompressionStream('gzip');
42
81
  const writer = compressedStream.writable.getWriter();
43
- const writePromise = writer.write(new TextEncoder().encode(input)).then(()=>writer.close()).catch(async (err)=>{
82
+ const writePromise = writer.write(inputBytes).then(()=>writer.close()).catch(async (err)=>{
44
83
  try {
45
84
  await writer.abort(err);
46
85
  } catch {}
@@ -51,6 +90,7 @@ async function gzipCompress(input, isDebug = true, options) {
51
90
  responsePromise,
52
91
  writePromise
53
92
  ]);
93
+ await validateNativeGzip(compressed, inputBytes);
54
94
  return compressed;
55
95
  } catch (error) {
56
96
  if (options?.rethrow) throw error;
@@ -60,10 +100,12 @@ async function gzipCompress(input, isDebug = true, options) {
60
100
  }
61
101
  exports.gzipCompress = __webpack_exports__.gzipCompress;
62
102
  exports.isGzipSupported = __webpack_exports__.isGzipSupported;
103
+ exports.isNativeAsyncGzipError = __webpack_exports__.isNativeAsyncGzipError;
63
104
  exports.isNativeAsyncGzipReadError = __webpack_exports__.isNativeAsyncGzipReadError;
64
105
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
65
106
  "gzipCompress",
66
107
  "isGzipSupported",
108
+ "isNativeAsyncGzipError",
67
109
  "isNativeAsyncGzipReadError"
68
110
  ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
69
111
  Object.defineProperty(exports, '__esModule', {
package/dist/gzip.mjs CHANGED
@@ -1,16 +1,54 @@
1
1
  function isGzipSupported() {
2
2
  return 'CompressionStream' in globalThis && 'TextEncoder' in globalThis && 'Response' in globalThis && 'function' == typeof Response.prototype.blob;
3
3
  }
4
+ const NATIVE_GZIP_VALIDATION_ERROR = 'NativeGzipValidationError';
4
5
  const isNativeAsyncGzipReadError = (error)=>{
5
6
  if (!error || 'object' != typeof error) return false;
6
7
  const name = 'name' in error ? String(error.name) : '';
7
8
  return 'NotReadableError' === name;
8
9
  };
10
+ const isNativeAsyncGzipError = (error)=>{
11
+ if (!error || 'object' != typeof error) return false;
12
+ const name = 'name' in error ? String(error.name) : '';
13
+ return isNativeAsyncGzipReadError(error) || name === NATIVE_GZIP_VALIDATION_ERROR;
14
+ };
15
+ let crc32Table;
16
+ const getCrc32Table = ()=>{
17
+ if (crc32Table) return crc32Table;
18
+ crc32Table = [];
19
+ for(let i = 0; i < 256; i++){
20
+ let crc = i;
21
+ for(let j = 0; j < 8; j++)crc = 1 & crc ? 0xedb88320 ^ crc >>> 1 : crc >>> 1;
22
+ crc32Table[i] = crc >>> 0;
23
+ }
24
+ return crc32Table;
25
+ };
26
+ const crc32 = (bytes)=>{
27
+ const table = getCrc32Table();
28
+ let crc = 0xffffffff;
29
+ for(let i = 0; i < bytes.length; i++)crc = table[(crc ^ bytes[i]) & 0xff] ^ crc >>> 8;
30
+ return (0xffffffff ^ crc) >>> 0;
31
+ };
32
+ const throwNativeGzipValidationError = (reason)=>{
33
+ const error = new Error(`Native gzip produced invalid output: ${reason}`);
34
+ error.name = NATIVE_GZIP_VALIDATION_ERROR;
35
+ throw error;
36
+ };
37
+ const validateNativeGzip = async (compressed, inputBytes)=>{
38
+ if (compressed.size < 18) throwNativeGzipValidationError('too-short');
39
+ const header = new Uint8Array(await compressed.slice(0, 10).arrayBuffer());
40
+ if (0x1f !== header[0] || 0x8b !== header[1] || 0x08 !== header[2]) throwNativeGzipValidationError('invalid-header');
41
+ const trailer = new DataView(await compressed.slice(compressed.size - 8).arrayBuffer());
42
+ if (trailer.getUint32(0, true) !== crc32(inputBytes)) throwNativeGzipValidationError('invalid-crc');
43
+ const inputSize = inputBytes.length >>> 0;
44
+ if (trailer.getUint32(4, true) !== inputSize) throwNativeGzipValidationError('invalid-size');
45
+ };
9
46
  async function gzipCompress(input, isDebug = true, options) {
10
47
  try {
48
+ const inputBytes = new TextEncoder().encode(input);
11
49
  const compressedStream = new CompressionStream('gzip');
12
50
  const writer = compressedStream.writable.getWriter();
13
- const writePromise = writer.write(new TextEncoder().encode(input)).then(()=>writer.close()).catch(async (err)=>{
51
+ const writePromise = writer.write(inputBytes).then(()=>writer.close()).catch(async (err)=>{
14
52
  try {
15
53
  await writer.abort(err);
16
54
  } catch {}
@@ -21,6 +59,7 @@ async function gzipCompress(input, isDebug = true, options) {
21
59
  responsePromise,
22
60
  writePromise
23
61
  ]);
62
+ await validateNativeGzip(compressed, inputBytes);
24
63
  return compressed;
25
64
  } catch (error) {
26
65
  if (options?.rethrow) throw error;
@@ -28,4 +67,4 @@ async function gzipCompress(input, isDebug = true, options) {
28
67
  return null;
29
68
  }
30
69
  }
31
- export { gzipCompress, isGzipSupported, isNativeAsyncGzipReadError };
70
+ export { gzipCompress, isGzipSupported, isNativeAsyncGzipError, isNativeAsyncGzipReadError };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { getFeatureFlagValue } from './featureFlagUtils';
2
- export { gzipCompress, isNativeAsyncGzipReadError } from './gzip';
2
+ export { gzipCompress, isNativeAsyncGzipError, isNativeAsyncGzipReadError } from './gzip';
3
3
  export * from './utils';
4
4
  export * as ErrorTracking from './error-tracking';
5
5
  export { buildOtlpLogRecord, buildOtlpLogsPayload, getOtlpSeverityNumber, getOtlpSeverityText, toOtlpAnyValue, toOtlpKeyValueList, } from './logs/logs-utils';
@@ -7,6 +7,7 @@ export { PostHogLogs } from './logs';
7
7
  export type { BeforeSendLogFn, BufferedLogEntry, CaptureLogger, LogSdkContext, PostHogLogsConfig, ResolvedPostHogLogsConfig, } from './logs/types';
8
8
  export type { CaptureLogOptions, LogAttributeValue, LogAttributes, LogSeverityLevel } from './logs/types';
9
9
  export { uuidv7 } from './vendor/uuidv7';
10
+ export * from './cookie';
10
11
  export * from './posthog-core';
11
12
  export * from './posthog-core-stateless';
12
13
  export * from './tracing-headers';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,QAAQ,CAAA;AACjE,cAAc,SAAS,CAAA;AACvB,OAAO,KAAK,aAAa,MAAM,kBAAkB,CAAA;AACjD,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AACpC,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,cAAc,CAAA;AAIrB,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACzG,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,0BAA0B,CAAA;AACxC,cAAc,mBAAmB,CAAA;AACjC,cAAc,SAAS,CAAA;AACvB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,QAAQ,CAAA;AACzF,cAAc,SAAS,CAAA;AACvB,OAAO,KAAK,aAAa,MAAM,kBAAkB,CAAA;AACjD,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AACpC,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,cAAc,CAAA;AAIrB,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACzG,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,0BAA0B,CAAA;AACxC,cAAc,mBAAmB,CAAA;AACjC,cAAc,SAAS,CAAA;AACvB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA"}
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  var __webpack_modules__ = {
3
+ "./cookie": function(module) {
4
+ module.exports = require("./cookie.js");
5
+ },
3
6
  "./error-tracking": function(module) {
4
7
  module.exports = require("./error-tracking/index.js");
5
8
  },
@@ -86,12 +89,13 @@ var __webpack_exports__ = {};
86
89
  buildOtlpLogRecord: ()=>_logs_logs_utils__WEBPACK_IMPORTED_MODULE_4__.buildOtlpLogRecord,
87
90
  buildOtlpLogsPayload: ()=>_logs_logs_utils__WEBPACK_IMPORTED_MODULE_4__.buildOtlpLogsPayload,
88
91
  getFeatureFlagValue: ()=>_featureFlagUtils__WEBPACK_IMPORTED_MODULE_0__.getFeatureFlagValue,
89
- getLengthFromRules: ()=>_surveys_validation__WEBPACK_IMPORTED_MODULE_11__.getLengthFromRules,
92
+ getLengthFromRules: ()=>_surveys_validation__WEBPACK_IMPORTED_MODULE_12__.getLengthFromRules,
90
93
  getOtlpSeverityNumber: ()=>_logs_logs_utils__WEBPACK_IMPORTED_MODULE_4__.getOtlpSeverityNumber,
91
94
  getOtlpSeverityText: ()=>_logs_logs_utils__WEBPACK_IMPORTED_MODULE_4__.getOtlpSeverityText,
92
- getRequirementsHint: ()=>_surveys_validation__WEBPACK_IMPORTED_MODULE_11__.getRequirementsHint,
93
- getValidationError: ()=>_surveys_validation__WEBPACK_IMPORTED_MODULE_11__.getValidationError,
95
+ getRequirementsHint: ()=>_surveys_validation__WEBPACK_IMPORTED_MODULE_12__.getRequirementsHint,
96
+ getValidationError: ()=>_surveys_validation__WEBPACK_IMPORTED_MODULE_12__.getValidationError,
94
97
  gzipCompress: ()=>_gzip__WEBPACK_IMPORTED_MODULE_1__.gzipCompress,
98
+ isNativeAsyncGzipError: ()=>_gzip__WEBPACK_IMPORTED_MODULE_1__.isNativeAsyncGzipError,
95
99
  isNativeAsyncGzipReadError: ()=>_gzip__WEBPACK_IMPORTED_MODULE_1__.isNativeAsyncGzipReadError,
96
100
  toOtlpAnyValue: ()=>_logs_logs_utils__WEBPACK_IMPORTED_MODULE_4__.toOtlpAnyValue,
97
101
  toOtlpKeyValueList: ()=>_logs_logs_utils__WEBPACK_IMPORTED_MODULE_4__.toOtlpKeyValueList,
@@ -104,6 +108,7 @@ var __webpack_exports__ = {};
104
108
  for(var __WEBPACK_IMPORT_KEY__ in _utils__WEBPACK_IMPORTED_MODULE_2__)if ([
105
109
  "toOtlpKeyValueList",
106
110
  "PostHogLogs",
111
+ "isNativeAsyncGzipError",
107
112
  "getOtlpSeverityText",
108
113
  "isNativeAsyncGzipReadError",
109
114
  "getFeatureFlagValue",
@@ -126,11 +131,36 @@ var __webpack_exports__ = {};
126
131
  var _logs_logs_utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__("./logs/logs-utils");
127
132
  var _logs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__("./logs");
128
133
  var _vendor_uuidv7__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__("./vendor/uuidv7");
129
- var _posthog_core__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__("./posthog-core");
134
+ var _cookie__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__("./cookie");
135
+ var __WEBPACK_REEXPORT_OBJECT__ = {};
136
+ for(var __WEBPACK_IMPORT_KEY__ in _cookie__WEBPACK_IMPORTED_MODULE_7__)if ([
137
+ "toOtlpKeyValueList",
138
+ "PostHogLogs",
139
+ "isNativeAsyncGzipError",
140
+ "getOtlpSeverityText",
141
+ "isNativeAsyncGzipReadError",
142
+ "getFeatureFlagValue",
143
+ "uuidv7",
144
+ "getValidationError",
145
+ "ErrorTracking",
146
+ "gzipCompress",
147
+ "default",
148
+ "getRequirementsHint",
149
+ "getOtlpSeverityNumber",
150
+ "getLengthFromRules",
151
+ "buildOtlpLogsPayload",
152
+ "buildOtlpLogRecord",
153
+ "toOtlpAnyValue"
154
+ ].indexOf(__WEBPACK_IMPORT_KEY__) < 0) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
155
+ return _cookie__WEBPACK_IMPORTED_MODULE_7__[key];
156
+ }).bind(0, __WEBPACK_IMPORT_KEY__);
157
+ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
158
+ var _posthog_core__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__("./posthog-core");
130
159
  var __WEBPACK_REEXPORT_OBJECT__ = {};
131
- for(var __WEBPACK_IMPORT_KEY__ in _posthog_core__WEBPACK_IMPORTED_MODULE_7__)if ([
160
+ for(var __WEBPACK_IMPORT_KEY__ in _posthog_core__WEBPACK_IMPORTED_MODULE_8__)if ([
132
161
  "toOtlpKeyValueList",
133
162
  "PostHogLogs",
163
+ "isNativeAsyncGzipError",
134
164
  "getOtlpSeverityText",
135
165
  "isNativeAsyncGzipReadError",
136
166
  "getFeatureFlagValue",
@@ -146,14 +176,15 @@ var __webpack_exports__ = {};
146
176
  "buildOtlpLogRecord",
147
177
  "toOtlpAnyValue"
148
178
  ].indexOf(__WEBPACK_IMPORT_KEY__) < 0) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
149
- return _posthog_core__WEBPACK_IMPORTED_MODULE_7__[key];
179
+ return _posthog_core__WEBPACK_IMPORTED_MODULE_8__[key];
150
180
  }).bind(0, __WEBPACK_IMPORT_KEY__);
151
181
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
152
- var _posthog_core_stateless__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__("./posthog-core-stateless");
182
+ var _posthog_core_stateless__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__("./posthog-core-stateless");
153
183
  var __WEBPACK_REEXPORT_OBJECT__ = {};
154
- for(var __WEBPACK_IMPORT_KEY__ in _posthog_core_stateless__WEBPACK_IMPORTED_MODULE_8__)if ([
184
+ for(var __WEBPACK_IMPORT_KEY__ in _posthog_core_stateless__WEBPACK_IMPORTED_MODULE_9__)if ([
155
185
  "toOtlpKeyValueList",
156
186
  "PostHogLogs",
187
+ "isNativeAsyncGzipError",
157
188
  "getOtlpSeverityText",
158
189
  "isNativeAsyncGzipReadError",
159
190
  "getFeatureFlagValue",
@@ -169,14 +200,15 @@ var __webpack_exports__ = {};
169
200
  "buildOtlpLogRecord",
170
201
  "toOtlpAnyValue"
171
202
  ].indexOf(__WEBPACK_IMPORT_KEY__) < 0) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
172
- return _posthog_core_stateless__WEBPACK_IMPORTED_MODULE_8__[key];
203
+ return _posthog_core_stateless__WEBPACK_IMPORTED_MODULE_9__[key];
173
204
  }).bind(0, __WEBPACK_IMPORT_KEY__);
174
205
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
175
- var _tracing_headers__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__("./tracing-headers");
206
+ var _tracing_headers__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__("./tracing-headers");
176
207
  var __WEBPACK_REEXPORT_OBJECT__ = {};
177
- for(var __WEBPACK_IMPORT_KEY__ in _tracing_headers__WEBPACK_IMPORTED_MODULE_9__)if ([
208
+ for(var __WEBPACK_IMPORT_KEY__ in _tracing_headers__WEBPACK_IMPORTED_MODULE_10__)if ([
178
209
  "toOtlpKeyValueList",
179
210
  "PostHogLogs",
211
+ "isNativeAsyncGzipError",
180
212
  "getOtlpSeverityText",
181
213
  "isNativeAsyncGzipReadError",
182
214
  "getFeatureFlagValue",
@@ -192,14 +224,15 @@ var __webpack_exports__ = {};
192
224
  "buildOtlpLogRecord",
193
225
  "toOtlpAnyValue"
194
226
  ].indexOf(__WEBPACK_IMPORT_KEY__) < 0) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
195
- return _tracing_headers__WEBPACK_IMPORTED_MODULE_9__[key];
227
+ return _tracing_headers__WEBPACK_IMPORTED_MODULE_10__[key];
196
228
  }).bind(0, __WEBPACK_IMPORT_KEY__);
197
229
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
198
- var _types__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__("./types");
230
+ var _types__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__("./types");
199
231
  var __WEBPACK_REEXPORT_OBJECT__ = {};
200
- for(var __WEBPACK_IMPORT_KEY__ in _types__WEBPACK_IMPORTED_MODULE_10__)if ([
232
+ for(var __WEBPACK_IMPORT_KEY__ in _types__WEBPACK_IMPORTED_MODULE_11__)if ([
201
233
  "toOtlpKeyValueList",
202
234
  "PostHogLogs",
235
+ "isNativeAsyncGzipError",
203
236
  "getOtlpSeverityText",
204
237
  "isNativeAsyncGzipReadError",
205
238
  "getFeatureFlagValue",
@@ -215,10 +248,10 @@ var __webpack_exports__ = {};
215
248
  "buildOtlpLogRecord",
216
249
  "toOtlpAnyValue"
217
250
  ].indexOf(__WEBPACK_IMPORT_KEY__) < 0) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
218
- return _types__WEBPACK_IMPORTED_MODULE_10__[key];
251
+ return _types__WEBPACK_IMPORTED_MODULE_11__[key];
219
252
  }).bind(0, __WEBPACK_IMPORT_KEY__);
220
253
  __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
221
- var _surveys_validation__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__("./surveys/validation");
254
+ var _surveys_validation__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__("./surveys/validation");
222
255
  })();
223
256
  exports.ErrorTracking = __webpack_exports__.ErrorTracking;
224
257
  exports.PostHogLogs = __webpack_exports__.PostHogLogs;
@@ -231,6 +264,7 @@ exports.getOtlpSeverityText = __webpack_exports__.getOtlpSeverityText;
231
264
  exports.getRequirementsHint = __webpack_exports__.getRequirementsHint;
232
265
  exports.getValidationError = __webpack_exports__.getValidationError;
233
266
  exports.gzipCompress = __webpack_exports__.gzipCompress;
267
+ exports.isNativeAsyncGzipError = __webpack_exports__.isNativeAsyncGzipError;
234
268
  exports.isNativeAsyncGzipReadError = __webpack_exports__.isNativeAsyncGzipReadError;
235
269
  exports.toOtlpAnyValue = __webpack_exports__.toOtlpAnyValue;
236
270
  exports.toOtlpKeyValueList = __webpack_exports__.toOtlpKeyValueList;
@@ -247,6 +281,7 @@ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
247
281
  "getRequirementsHint",
248
282
  "getValidationError",
249
283
  "gzipCompress",
284
+ "isNativeAsyncGzipError",
250
285
  "isNativeAsyncGzipReadError",
251
286
  "toOtlpAnyValue",
252
287
  "toOtlpKeyValueList",
package/dist/index.mjs CHANGED
@@ -1,13 +1,14 @@
1
1
  import { getFeatureFlagValue } from "./featureFlagUtils.mjs";
2
- import { gzipCompress, isNativeAsyncGzipReadError } from "./gzip.mjs";
2
+ import { gzipCompress, isNativeAsyncGzipError, isNativeAsyncGzipReadError } from "./gzip.mjs";
3
3
  import { buildOtlpLogRecord, buildOtlpLogsPayload, getOtlpSeverityNumber, getOtlpSeverityText, toOtlpAnyValue, toOtlpKeyValueList } from "./logs/logs-utils.mjs";
4
4
  import { PostHogLogs } from "./logs/index.mjs";
5
5
  import { uuidv7 } from "./vendor/uuidv7.mjs";
6
6
  import { getLengthFromRules, getRequirementsHint, getValidationError } from "./surveys/validation.mjs";
7
7
  export * from "./utils/index.mjs";
8
+ export * from "./cookie.mjs";
8
9
  export * from "./posthog-core.mjs";
9
10
  export * from "./posthog-core-stateless.mjs";
10
11
  export * from "./tracing-headers.mjs";
11
12
  export * from "./types.mjs";
12
13
  import * as __WEBPACK_EXTERNAL_MODULE__error_tracking_index_mjs_b3406d6f__ from "./error-tracking/index.mjs";
13
- export { __WEBPACK_EXTERNAL_MODULE__error_tracking_index_mjs_b3406d6f__ as ErrorTracking, PostHogLogs, buildOtlpLogRecord, buildOtlpLogsPayload, getFeatureFlagValue, getLengthFromRules, getOtlpSeverityNumber, getOtlpSeverityText, getRequirementsHint, getValidationError, gzipCompress, isNativeAsyncGzipReadError, toOtlpAnyValue, toOtlpKeyValueList, uuidv7 };
14
+ export { __WEBPACK_EXTERNAL_MODULE__error_tracking_index_mjs_b3406d6f__ as ErrorTracking, PostHogLogs, buildOtlpLogRecord, buildOtlpLogsPayload, getFeatureFlagValue, getLengthFromRules, getOtlpSeverityNumber, getOtlpSeverityText, getRequirementsHint, getValidationError, gzipCompress, isNativeAsyncGzipError, isNativeAsyncGzipReadError, toOtlpAnyValue, toOtlpKeyValueList, uuidv7 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/core",
3
- "version": "1.28.6",
3
+ "version": "1.29.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -67,7 +67,7 @@
67
67
  }
68
68
  },
69
69
  "dependencies": {
70
- "@posthog/types": "1.373.1"
70
+ "@posthog/types": "1.373.3"
71
71
  },
72
72
  "devDependencies": {
73
73
  "@rslib/core": "0.10.6",
package/src/cookie.ts ADDED
@@ -0,0 +1,173 @@
1
+ import { isArray, isNoLike } from './utils'
2
+ import { uuidv7 } from './vendor/uuidv7'
3
+
4
+ const COOKIE_PREFIX = 'ph_'
5
+ const COOKIE_SUFFIX = '_posthog'
6
+
7
+ /**
8
+ * Minimal cookie-reading interface compatible with Next.js `cookies()`,
9
+ * `request.cookies`, and plain objects.
10
+ */
11
+ export interface CookieStore {
12
+ get(name: string): { value: string } | undefined
13
+ }
14
+
15
+ /**
16
+ * Adapts a raw `Cookie` header string into a {@link CookieStore}.
17
+ */
18
+ export function cookieStoreFromHeader(cookieHeader: string): CookieStore {
19
+ const cookies: Record<string, string> = {}
20
+ if (cookieHeader) {
21
+ for (const pair of cookieHeader.split(';')) {
22
+ const [key, ...valueParts] = pair.trim().split('=')
23
+ if (key) {
24
+ const raw = valueParts.join('=').trim()
25
+ // `decodeURIComponent` throws `URIError` on malformed sequences (e.g. `%gg`).
26
+ // The Cookie header is externally controlled, so fall back to the raw value
27
+ // rather than crashing the request handler.
28
+ try {
29
+ cookies[key.trim()] = decodeURIComponent(raw)
30
+ } catch {
31
+ cookies[key.trim()] = raw
32
+ }
33
+ }
34
+ }
35
+ }
36
+ return { get: (name: string) => (name in cookies ? { value: cookies[name] } : undefined) }
37
+ }
38
+
39
+ export interface PostHogCookieState {
40
+ distinctId: string
41
+ isIdentified: boolean
42
+ sessionId?: string
43
+ deviceId?: string
44
+ }
45
+
46
+ /**
47
+ * Returns the PostHog cookie name for the given API key.
48
+ *
49
+ * PostHog-js stores state in a cookie named `ph_<sanitized_token>_posthog`.
50
+ * The token is sanitized by replacing `+` with `PL`, `/` with `SL`, `=` with `EQ`.
51
+ *
52
+ * @param apiKey - The PostHog project API key
53
+ * @returns The cookie name string
54
+ */
55
+ export function getPostHogCookieName(apiKey: string): string {
56
+ const sanitized = apiKey.replace(/\+/g, 'PL').replace(/\//g, 'SL').replace(/=/g, 'EQ')
57
+ return `${COOKIE_PREFIX}${sanitized}${COOKIE_SUFFIX}`
58
+ }
59
+
60
+ /**
61
+ * Serializes an anonymous ID into the JSON format posthog-js expects.
62
+ *
63
+ * When `distinct_id === $device_id`, posthog-js treats the user as anonymous.
64
+ *
65
+ * @param anonymousId - The anonymous distinct ID to serialize
66
+ * @returns JSON string suitable for the PostHog cookie value
67
+ */
68
+ export function serializePostHogCookie(anonymousId: string): string {
69
+ const now = Date.now()
70
+ const sessionId = uuidv7()
71
+ return JSON.stringify({
72
+ distinct_id: anonymousId,
73
+ $device_id: anonymousId,
74
+ $user_state: 'anonymous',
75
+ $sesid: [now, sessionId, now],
76
+ })
77
+ }
78
+
79
+ /**
80
+ * Reads and parses the PostHog cookie from a cookie store.
81
+ *
82
+ * Compatible with Next.js `cookies()`, `request.cookies`, and any object
83
+ * with a `get(name)` method that returns `{ value: string } | undefined`.
84
+ */
85
+ export function readPostHogCookie(cookies: CookieStore, apiKey: string): PostHogCookieState | null {
86
+ const cookieName = getPostHogCookieName(apiKey)
87
+ const cookie = cookies.get(cookieName)
88
+ return cookie ? parsePostHogCookie(cookie.value) : null
89
+ }
90
+
91
+ /**
92
+ * Converts cookie state into PostHog properties (e.g. `$session_id`, `$device_id`).
93
+ */
94
+ export function cookieStateToProperties(state: PostHogCookieState | null): Record<string, string> | undefined {
95
+ if (!state) {
96
+ return undefined
97
+ }
98
+ const props: Record<string, string> = {}
99
+ if (state.sessionId) {
100
+ props.$session_id = state.sessionId
101
+ }
102
+ if (state.deviceId) {
103
+ props.$device_id = state.deviceId
104
+ }
105
+ return Object.keys(props).length > 0 ? props : undefined
106
+ }
107
+
108
+ /**
109
+ * Parses a PostHog cookie value and extracts identity information.
110
+ *
111
+ * The cookie value is a JSON object containing `distinct_id` and `$user_state`.
112
+ * A user is considered identified if `$user_state` is `'identified'`.
113
+ *
114
+ * @param cookieValue - The raw cookie string value
115
+ * @returns Parsed identity state, or null if the cookie is missing/invalid
116
+ */
117
+ export function parsePostHogCookie(cookieValue: string): PostHogCookieState | null {
118
+ if (!cookieValue) {
119
+ return null
120
+ }
121
+
122
+ try {
123
+ const parsed = JSON.parse(cookieValue)
124
+ if (!parsed || typeof parsed !== 'object' || !parsed.distinct_id) {
125
+ return null
126
+ }
127
+
128
+ // $sesid is stored as [lastActivityTimestamp, sessionId, sessionStartTimestamp]
129
+ const sesid = isArray(parsed.$sesid) ? parsed.$sesid[1] : undefined
130
+
131
+ return {
132
+ distinctId: String(parsed.distinct_id),
133
+ isIdentified: parsed.$user_state === 'identified',
134
+ sessionId: typeof sesid === 'string' ? sesid : undefined,
135
+ deviceId: typeof parsed.$device_id === 'string' ? parsed.$device_id : undefined,
136
+ }
137
+ } catch {
138
+ return null
139
+ }
140
+ }
141
+
142
+ export interface ConsentCookieConfig {
143
+ consent_persistence_name?: string | null
144
+ opt_out_capturing_cookie_prefix?: string | null
145
+ }
146
+
147
+ const CONSENT_PREFIX = '__ph_opt_in_out_'
148
+
149
+ export function getConsentCookieName(apiKey: string, config?: ConsentCookieConfig): string {
150
+ if (config?.consent_persistence_name) {
151
+ return config.consent_persistence_name
152
+ }
153
+ if (config?.opt_out_capturing_cookie_prefix) {
154
+ return config.opt_out_capturing_cookie_prefix + apiKey
155
+ }
156
+ return CONSENT_PREFIX + apiKey
157
+ }
158
+
159
+ export interface ConsentConfig extends ConsentCookieConfig {
160
+ opt_out_capturing_by_default?: boolean
161
+ }
162
+
163
+ export function isOptedOut(cookies: CookieStore, apiKey: string, config?: ConsentConfig): boolean {
164
+ const cookieName = getConsentCookieName(apiKey, config)
165
+ const cookie = cookies.get(cookieName)
166
+
167
+ if (cookie) {
168
+ return isNoLike(cookie.value)
169
+ }
170
+
171
+ // No consent cookie means pending — defer to config
172
+ return config?.opt_out_capturing_by_default ?? false
173
+ }
package/src/gzip.ts CHANGED
@@ -11,6 +11,8 @@ export function isGzipSupported(): boolean {
11
11
  )
12
12
  }
13
13
 
14
+ const NATIVE_GZIP_VALIDATION_ERROR = 'NativeGzipValidationError'
15
+
14
16
  export const isNativeAsyncGzipReadError = (error: unknown): boolean => {
15
17
  if (!error || typeof error !== 'object') {
16
18
  return false
@@ -21,6 +23,72 @@ export const isNativeAsyncGzipReadError = (error: unknown): boolean => {
21
23
  return name === 'NotReadableError'
22
24
  }
23
25
 
26
+ export const isNativeAsyncGzipError = (error: unknown): boolean => {
27
+ if (!error || typeof error !== 'object') {
28
+ return false
29
+ }
30
+
31
+ const name = 'name' in error ? String(error.name) : ''
32
+
33
+ return isNativeAsyncGzipReadError(error) || name === NATIVE_GZIP_VALIDATION_ERROR
34
+ }
35
+
36
+ type NativeGzipValidationReason = 'too-short' | 'invalid-header' | 'invalid-crc' | 'invalid-size'
37
+
38
+ let crc32Table: number[] | undefined
39
+
40
+ const getCrc32Table = (): number[] => {
41
+ if (crc32Table) {
42
+ return crc32Table
43
+ }
44
+
45
+ crc32Table = []
46
+ for (let i = 0; i < 256; i++) {
47
+ let crc = i
48
+ for (let j = 0; j < 8; j++) {
49
+ crc = crc & 1 ? 0xedb88320 ^ (crc >>> 1) : crc >>> 1
50
+ }
51
+ crc32Table[i] = crc >>> 0
52
+ }
53
+ return crc32Table
54
+ }
55
+
56
+ const crc32 = (bytes: Uint8Array): number => {
57
+ const table = getCrc32Table()
58
+ let crc = 0xffffffff
59
+ for (let i = 0; i < bytes.length; i++) {
60
+ crc = table[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8)
61
+ }
62
+ return (crc ^ 0xffffffff) >>> 0
63
+ }
64
+
65
+ const throwNativeGzipValidationError = (reason: NativeGzipValidationReason): never => {
66
+ const error = new Error(`Native gzip produced invalid output: ${reason}`)
67
+ error.name = NATIVE_GZIP_VALIDATION_ERROR
68
+ throw error
69
+ }
70
+
71
+ const validateNativeGzip = async (compressed: Blob, inputBytes: Uint8Array): Promise<void> => {
72
+ if (compressed.size < 18) {
73
+ throwNativeGzipValidationError('too-short')
74
+ }
75
+
76
+ const header = new Uint8Array(await compressed.slice(0, 10).arrayBuffer())
77
+ if (header[0] !== 0x1f || header[1] !== 0x8b || header[2] !== 0x08) {
78
+ throwNativeGzipValidationError('invalid-header')
79
+ }
80
+
81
+ const trailer = new DataView(await compressed.slice(compressed.size - 8).arrayBuffer())
82
+ if (trailer.getUint32(0, true) !== crc32(inputBytes)) {
83
+ throwNativeGzipValidationError('invalid-crc')
84
+ }
85
+
86
+ const inputSize = inputBytes.length >>> 0
87
+ if (trailer.getUint32(4, true) !== inputSize) {
88
+ throwNativeGzipValidationError('invalid-size')
89
+ }
90
+ }
91
+
24
92
  export type GzipCompressOptions = {
25
93
  /**
26
94
  * By default this helper swallows compression errors and returns null.
@@ -36,11 +104,12 @@ export type GzipCompressOptions = {
36
104
  */
37
105
  export async function gzipCompress(input: string, isDebug = true, options?: GzipCompressOptions): Promise<Blob | null> {
38
106
  try {
107
+ const inputBytes = new TextEncoder().encode(input)
39
108
  const compressedStream = new CompressionStream('gzip')
40
109
  const writer = compressedStream.writable.getWriter()
41
110
 
42
111
  const writePromise = writer
43
- .write(new TextEncoder().encode(input))
112
+ .write(inputBytes)
44
113
  .then(() => writer.close())
45
114
  .catch(async (err) => {
46
115
  try {
@@ -53,6 +122,7 @@ export async function gzipCompress(input: string, isDebug = true, options?: Gzip
53
122
  const responsePromise = new Response(compressedStream.readable).blob()
54
123
 
55
124
  const [compressed] = await Promise.all([responsePromise, writePromise])
125
+ await validateNativeGzip(compressed, inputBytes)
56
126
  return compressed
57
127
  } catch (error) {
58
128
  if (options?.rethrow) {
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { getFeatureFlagValue } from './featureFlagUtils'
2
- export { gzipCompress, isNativeAsyncGzipReadError } from './gzip'
2
+ export { gzipCompress, isNativeAsyncGzipError, isNativeAsyncGzipReadError } from './gzip'
3
3
  export * from './utils'
4
4
  export * as ErrorTracking from './error-tracking'
5
5
  export {
@@ -24,6 +24,7 @@ export type {
24
24
  // packages to type their `captureLog` calls.
25
25
  export type { CaptureLogOptions, LogAttributeValue, LogAttributes, LogSeverityLevel } from './logs/types'
26
26
  export { uuidv7 } from './vendor/uuidv7'
27
+ export * from './cookie'
27
28
  export * from './posthog-core'
28
29
  export * from './posthog-core-stateless'
29
30
  export * from './tracing-headers'