Skip to content

Commit

Permalink
Merge branch 'main' into feat/serverless-decide
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite committed Dec 3, 2024
2 parents 277ed18 + b3b6ef6 commit 9b94afd
Show file tree
Hide file tree
Showing 17 changed files with 112 additions and 43 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
## 1.194.2 - 2024-12-02

- fix: use previous site-app variables (#1574)

## 1.194.1 - 2024-11-30

- fix: Don't crash on bigints (#1573)
- feat: Add customization to print events drop them (#1572)

## 1.194.0 - 2024-11-29

- feat: add $recording_status property (#1571)

## 1.193.1 - 2024-11-28

- fix: zone detection (#1570)

## 1.193.0 - 2024-11-28

- feat: allow decide to provide script name for recorder (#1509)

## 1.192.1 - 2024-11-28


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posthog-js",
"version": "1.192.1",
"version": "1.194.2",
"description": "Posthog-js allows you to automatically capture usage and send events to PostHog.",
"repository": "https://github.com/PostHog/posthog-js",
"author": "[email protected]",
Expand Down
37 changes: 16 additions & 21 deletions patches/@[email protected]
Original file line number Diff line number Diff line change
@@ -1,37 +1,32 @@
diff --git a/dist/record.js b/dist/record.js
index 46ec389fefb698243008b39db65470dbdf0a3857..70db907755d68b08232e25e1b255a974f56f3c65 100644
index 46ec389fefb698243008b39db65470dbdf0a3857..a18724d8b6ba43a30935daf257127fbb0c898541 100644
--- a/dist/record.js
+++ b/dist/record.js
@@ -26,6 +26,19 @@ const testableMethods$1 = {
@@ -26,6 +26,14 @@ const testableMethods$1 = {
Element: [],
MutationObserver: ["constructor"]
};
+const isFunction = (x) => typeof x === 'function';
+const isAngularZonePatchedFunction = (x) => {
+
+const isAngularZonePresent = () => {
+ try {
+ if (!isFunction(x)) {
+ return false;
+ }
+ const prototypeKeys = Object.getOwnPropertyNames(x.prototype || {});
+ return prototypeKeys.some((key) => key.indexOf('__zone'));
+ return !!globalThis.Zone
+ } catch {
+ // we've seen some intermittent problems in Safari since introducing this check
+ return false
+ }
+}
const untaintedBasePrototype$1 = {};
function getUntaintedPrototype$1(key) {
if (untaintedBasePrototype$1[key])
@@ -54,7 +67,7 @@ function getUntaintedPrototype$1(key) {
@@ -54,7 +62,7 @@ function getUntaintedPrototype$1(key) {
}
)
);
- if (isUntaintedAccessors && isUntaintedMethods) {
+ if (isUntaintedAccessors && isUntaintedMethods && !isAngularZonePatchedFunction(defaultObj)) {
+ if (isUntaintedAccessors && isUntaintedMethods && !isAngularZonePresent()) {
untaintedBasePrototype$1[key] = defaultObj.prototype;
return defaultObj.prototype;
}
@@ -65,10 +78,10 @@ function getUntaintedPrototype$1(key) {
@@ -65,10 +73,10 @@ function getUntaintedPrototype$1(key) {
if (!win) return defaultObj.prototype;
const untaintedObject = win[key].prototype;
document.body.removeChild(iframeEl);
Expand All @@ -44,7 +39,7 @@ index 46ec389fefb698243008b39db65470dbdf0a3857..70db907755d68b08232e25e1b255a974
}
}
const untaintedAccessorCache$1 = {};
@@ -246,6 +259,9 @@ function isCSSImportRule(rule2) {
@@ -246,6 +254,9 @@ function isCSSImportRule(rule2) {
function isCSSStyleRule(rule2) {
return "selectorText" in rule2;
}
Expand All @@ -54,7 +49,7 @@ index 46ec389fefb698243008b39db65470dbdf0a3857..70db907755d68b08232e25e1b255a974
class Mirror {
constructor() {
__publicField$1(this, "idNodeMap", /* @__PURE__ */ new Map());
@@ -809,9 +825,14 @@ function serializeElementNode(n2, options) {
@@ -809,9 +820,14 @@ function serializeElementNode(n2, options) {
}
}
if (tagName === "link" && inlineStylesheet) {
Expand All @@ -72,7 +67,7 @@ index 46ec389fefb698243008b39db65470dbdf0a3857..70db907755d68b08232e25e1b255a974
let cssText = null;
if (stylesheet) {
cssText = stringifyStylesheet(stylesheet);
@@ -855,7 +876,15 @@ function serializeElementNode(n2, options) {
@@ -855,7 +871,15 @@ function serializeElementNode(n2, options) {
}
}
if (tagName === "dialog" && n2.open) {
Expand All @@ -89,7 +84,7 @@ index 46ec389fefb698243008b39db65470dbdf0a3857..70db907755d68b08232e25e1b255a974
}
if (tagName === "canvas" && recordCanvas) {
if (n2.__context === "2d") {
@@ -1116,7300 +1145,227 @@ function serializeNodeWithId(n2, options) {
@@ -1116,7300 +1140,227 @@ function serializeNodeWithId(n2, options) {
keepIframeSrcFn
};
if (serializedNode.type === NodeType$2.Element && serializedNode.tagName === "textarea" && serializedNode.attributes.value !== void 0) ;
Expand Down Expand Up @@ -7599,16 +7594,16 @@ index 46ec389fefb698243008b39db65470dbdf0a3857..70db907755d68b08232e25e1b255a974
class BaseRRNode {
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
constructor(..._args) {
@@ -8507,7 +1463,7 @@ function getUntaintedPrototype(key) {
@@ -8507,7 +1458,7 @@ function getUntaintedPrototype(key) {
}
)
);
- if (isUntaintedAccessors && isUntaintedMethods) {
+ if (isUntaintedAccessors && isUntaintedMethods && !isAngularZonePatchedFunction(defaultObj)) {
+ if (isUntaintedAccessors && isUntaintedMethods && !isAngularZonePresent()) {
untaintedBasePrototype[key] = defaultObj.prototype;
return defaultObj.prototype;
}
@@ -11382,11 +4338,19 @@ class CanvasManager {
@@ -11382,11 +4333,19 @@ class CanvasManager {
let rafId;
const getCanvas = () => {
const matchedCanvas = [];
Expand All @@ -7633,7 +7628,7 @@ index 46ec389fefb698243008b39db65470dbdf0a3857..70db907755d68b08232e25e1b255a974
return matchedCanvas;
};
const takeCanvasSnapshots = (timestamp) => {
@@ -11407,13 +4371,20 @@ class CanvasManager {
@@ -11407,13 +4366,20 @@ class CanvasManager {
context.clear(context.COLOR_BUFFER_BIT);
}
}
Expand Down
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/__tests__/extensions/replay/sessionrecording.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,15 @@ describe('SessionRecording', () => {
jest.spyOn(sessionRecording, 'startIfEnabledOrStop')
})

it('loads script based on script config', () => {
sessionRecording.afterDecideResponse(
makeDecideResponse({
sessionRecording: { endpoint: '/s/', scriptConfig: { script: 'experimental-recorder' } },
})
)
expect(loadScriptMock).toHaveBeenCalledWith(posthog, 'experimental-recorder', expect.any(Function))
})

it('when the first event is a meta it does not take a manual full snapshot', () => {
sessionRecording.startIfEnabledOrStop()
expect(loadScriptMock).toHaveBeenCalled()
Expand Down
2 changes: 2 additions & 0 deletions src/__tests__/posthog-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ describe('posthog core', () => {
$session_id: 'sessionId',
$is_identified: false,
$process_person_profile: false,
$recording_status: 'buffering',
})
})

Expand All @@ -459,6 +460,7 @@ describe('posthog core', () => {
$lib_custom_api_host: 'https://custom.posthog.com',
$is_identified: false,
$process_person_profile: false,
$recording_status: 'buffering',
})
})

Expand Down
15 changes: 15 additions & 0 deletions src/__tests__/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,21 @@ describe('request', () => {
'application/x-www-form-urlencoded'
)
})
it('converts bigint properties to string without throwing', () => {
request(
createRequest({
url: 'https://any.posthog-instance.com/',
method: 'POST',
compression: Compression.Base64,
data: { foo: BigInt('999999999999999999999') },
})
)
expect(mockedXHR.send.mock.calls[0][0]).toMatchInlineSnapshot(
`"data=eyJmb28iOiI5OTk5OTk5OTk5OTk5OTk5OTk5OTkifQ%3D%3D"`
)
expect(mockedXHR.setRequestHeader).toHaveBeenCalledWith('Content-Type', 'application/x-www-form-urlencoded')
})
})
describe('sendBeacon', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/site-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ describe('SiteApps', () => {

siteAppsInstance.afterDecideResponse(response)

expect(assignableWindow['__$$ph_site_app_1_posthog']).toBe(posthog)
expect(assignableWindow['__$$ph_site_app_1']).toBe(posthog)
expect(typeof assignableWindow['__$$ph_site_app_1_missed_invocations']).toBe('function')
expect(typeof assignableWindow['__$$ph_site_app_1_callback']).toBe('function')
expect(assignableWindow.__PosthogExtensions__?.loadSiteApp).toHaveBeenCalledWith(
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const SESSION_RECORDING_NETWORK_PAYLOAD_CAPTURE = '$session_recording_net
export const SESSION_RECORDING_CANVAS_RECORDING = '$session_recording_canvas_recording'
export const SESSION_RECORDING_SAMPLE_RATE = '$replay_sample_rate'
export const SESSION_RECORDING_MINIMUM_DURATION = '$replay_minimum_duration'
export const SESSION_RECORDING_SCRIPT_CONFIG = '$replay_script_config'
export const SESSION_ID = '$sesid'
export const SESSION_RECORDING_IS_SAMPLED = '$session_is_sampled'
export const SESSION_RECORDING_URL_TRIGGER_ACTIVATED_SESSION = '$session_recording_url_trigger_activated_session'
Expand Down
6 changes: 6 additions & 0 deletions src/customizations/before-send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,9 @@ export function sampleByEvent(eventNames: KnownEventName[], percent: number): Be
: null
}
}

export const printAndDropEverything: BeforeSendFn = (result) => {
// eslint-disable-next-line no-console
console.log('Would have sent event:', result)
return null
}
15 changes: 12 additions & 3 deletions src/extensions/replay/sessionrecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
SESSION_RECORDING_MINIMUM_DURATION,
SESSION_RECORDING_NETWORK_PAYLOAD_CAPTURE,
SESSION_RECORDING_SAMPLE_RATE,
SESSION_RECORDING_SCRIPT_CONFIG,
SESSION_RECORDING_URL_TRIGGER_ACTIVATED_SESSION,
} from '../../constants'
import {
Expand Down Expand Up @@ -38,7 +39,7 @@ import {

import { isBoolean, isFunction, isNullish, isNumber, isObject, isString, isUndefined } from '../../utils/type-utils'
import { logger } from '../../utils/logger'
import { assignableWindow, document, window } from '../../utils/globals'
import { assignableWindow, document, PostHogExtensionKind, window } from '../../utils/globals'
import { buildNetworkRequestOptions } from './config'
import { isLocalhost } from '../../utils/request-utils'
import { MutationRateLimiter } from './mutation-rate-limiter'
Expand Down Expand Up @@ -383,7 +384,7 @@ export class SessionRecording {
* defaults to buffering mode until a decide response is received
* once a decide response is received status can be disabled, active or sampled
*/
private get status(): SessionRecordingStatus {
get status(): SessionRecordingStatus {
if (!this.receivedDecide) {
return 'buffering'
}
Expand Down Expand Up @@ -696,6 +697,7 @@ export class SessionRecording {
[SESSION_RECORDING_MINIMUM_DURATION]: isUndefined(receivedMinimumDuration)
? null
: receivedMinimumDuration,
[SESSION_RECORDING_SCRIPT_CONFIG]: response.sessionRecording?.scriptConfig,
})
}

Expand Down Expand Up @@ -752,7 +754,7 @@ export class SessionRecording {
// If recorder.js is already loaded (if array.full.js snippet is used or posthog-js/dist/recorder is
// imported), don't load script. Otherwise, remotely import recorder.js from cdn since it hasn't been loaded.
if (!this.rrwebRecord) {
assignableWindow.__PosthogExtensions__?.loadExternalDependency?.(this.instance, 'recorder', (err) => {
assignableWindow.__PosthogExtensions__?.loadExternalDependency?.(this.instance, this.scriptName, (err) => {
if (err) {
return logger.error(LOGGER_PREFIX + ` could not load recorder`, err)
}
Expand All @@ -769,6 +771,13 @@ export class SessionRecording {
}
}

private get scriptName(): PostHogExtensionKind {
return (
(this.instance?.persistence?.get_property(SESSION_RECORDING_SCRIPT_CONFIG)
?.script as PostHogExtensionKind) || 'recorder'
)
}

private isInteractiveEvent(event: eventWithTime) {
return (
event.type === INCREMENTAL_SNAPSHOT_EVENT_TYPE &&
Expand Down
4 changes: 4 additions & 0 deletions src/posthog-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,10 @@ export class PostHog {
properties['$window_id'] = windowId
}

if (this.sessionRecording) {
properties['$recording_status'] = this.sessionRecording.status
}

if (this.requestRouter.region === RequestRouterRegion.CUSTOM) {
properties['$lib_custom_api_host'] = this.config.api_host
}
Expand Down
17 changes: 13 additions & 4 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,17 @@ export const extendURLParams = (url: string, params: Record<string, any>): strin
return `${baseUrl}?${newSearch}`
}

export const jsonStringify = (data: any, space?: string | number): string => {
// With plain JSON.stringify, we get an exception when a property is a BigInt. This has caused problems for some users,
// see https://github.com/PostHog/posthog-js/issues/1440
// To work around this, we convert BigInts to strings before stringifying the data. This is not ideal, as we lose
// information that this was originally a number, but given ClickHouse doesn't support BigInts, the customer
// would not be able to operate on these numerically anyway.
return JSON.stringify(data, (_, value) => (typeof value === 'bigint' ? value.toString() : value), space)
}

const encodeToDataString = (data: string | Record<string, any>): string => {
return 'data=' + encodeURIComponent(typeof data === 'string' ? data : JSON.stringify(data))
return 'data=' + encodeURIComponent(typeof data === 'string' ? data : jsonStringify(data))
}

const encodePostData = ({ data, compression }: RequestOptions): EncodedBody | undefined => {
Expand All @@ -51,7 +60,7 @@ const encodePostData = ({ data, compression }: RequestOptions): EncodedBody | un
}

if (compression === Compression.GZipJS) {
const gzipData = gzipSync(strToU8(JSON.stringify(data)), { mtime: 0 })
const gzipData = gzipSync(strToU8(jsonStringify(data)), { mtime: 0 })
const blob = new Blob([gzipData], { type: CONTENT_TYPE_PLAIN })
return {
contentType: CONTENT_TYPE_PLAIN,
Expand All @@ -61,7 +70,7 @@ const encodePostData = ({ data, compression }: RequestOptions): EncodedBody | un
}

if (compression === Compression.Base64) {
const b64data = _base64Encode(JSON.stringify(data))
const b64data = _base64Encode(jsonStringify(data))
const encodedBody = encodeToDataString(b64data)

return {
Expand All @@ -71,7 +80,7 @@ const encodePostData = ({ data, compression }: RequestOptions): EncodedBody | un
}
}

const jsonBody = JSON.stringify(data)
const jsonBody = jsonStringify(data)
return {
contentType: CONTENT_TYPE_JSON,
body: jsonBody,
Expand Down
2 changes: 1 addition & 1 deletion src/site-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class SiteApps {
for (const { id, url } of response['siteApps']) {
// TODO: if we have opted out and "type" is "site_destination", ignore it... but do include "site_app" types
this.appsLoading.add(id)
assignableWindow[`__$$ph_site_app_${id}_posthog`] = this.instance
assignableWindow[`__$$ph_site_app_${id}`] = this.instance
assignableWindow[`__$$ph_site_app_${id}_missed_invocations`] = () => this.missedInvocations
assignableWindow[`__$$ph_site_app_${id}_callback`] = () => {
this.appsLoading.delete(id)
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ export interface DecideResponse {
linkedFlag?: string | FlagVariant | null
networkPayloadCapture?: Pick<NetworkRecordOptions, 'recordBody' | 'recordHeaders'>
urlTriggers?: SessionRecordingUrlTrigger[]
scriptConfig?: { script?: string | undefined }
urlBlocklist?: SessionRecordingUrlTrigger[]
eventTriggers?: string[]
}
Expand Down
Loading

0 comments on commit 9b94afd

Please sign in to comment.