Skip to content

Commit

Permalink
Merge branch 'main' into fix/force-session-idle
Browse files Browse the repository at this point in the history
  • Loading branch information
pauldambra authored Nov 21, 2024
2 parents db53066 + d71ecd6 commit 2b4515b
Show file tree
Hide file tree
Showing 49 changed files with 1,984 additions and 990 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/es-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:

jobs:
ssr:
name: Cypress
name: Build and check ES5/ES6 support
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -21,3 +21,6 @@ jobs:

- name: Run es-check to check if our ie11 bundle is ES5 compatible
run: npx [email protected] es5 dist/array.full.es5.js

- name: Run es-check to check if our main bundle is ES6 compatible
run: npx [email protected] es6 dist/array.full.js
2 changes: 2 additions & 0 deletions .github/workflows/testcafe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ jobs:
run: pnpm build-rollup

- name: Run ${{ matrix.name }} test
timeout-minutes: 10
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
RUN_ID: ${{ github.run_id }}
BROWSER: ${{ matrix.browser }}
run: pnpm testcafe ${{ matrix.browser }} --stop-on-first-fail

- name: Check ${{ matrix.name }} events
timeout-minutes: 10
run: pnpm check-testcafe-results
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,50 @@
## 1.187.2 - 2024-11-20

- fix: improve ES6 bundling (#1542)

## 1.187.1 - 2024-11-19

- fix: patch angular wrap detection in rrweb (#1543)

## 1.187.0 - 2024-11-19

- feat: allow config of before_send function to edit or reject events (#1515)
- chore: timeout test cafe jobs (#1540)
- chore: specify an explicit browserslist version (#1539)

## 1.186.4 - 2024-11-19

- chore: always transform exponentiation (#1537)
- chore: very small change to IE11 bundling (#1536)

## 1.186.3 - 2024-11-18

- fix: refactor native mutation observer implementation (#1535)
- chore: update dependency versions (#1534)
- chore: remove custom exceptions endpoint (#1513)

## 1.186.2 - 2024-11-18

- fix: angular change detection mutation observer (#1531)
- chore: Added CSP headers to next app for testing what we document (#1528)

## 1.186.1 - 2024-11-15

- fix: XHR req method capture (#1527)

## 1.186.0 - 2024-11-15

- feat: allow triggering sessions when events occur (#1523)

## 1.185.0 - 2024-11-15

- feat: Add customization to add all person profile properties as setPersonPropertiesForFlags (#1517)

## 1.184.2 - 2024-11-13

- fix(flags): support multiple children prop in PostHogFeature (#1516)
- fix: Don't use session storage in memory mode (#1521)

## 1.184.1 - 2024-11-12

- chore: add type to Sentry exception (#1520)
Expand Down
48 changes: 48 additions & 0 deletions cypress/e2e/before_send.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// <reference types="cypress" />

import { start } from '../support/setup'
import { isArray } from '../../src/utils/type-utils'

describe('before_send', () => {
it('can sample and edit with before_send', () => {
start({})

cy.posthog().then((posthog) => {
let counter = 0
const og = posthog.config.before_send
// cypress tests rely on existing before_send function to capture events
// so we have to add it back in here
posthog.config.before_send = [
(cr) => {
if (cr.event === 'custom-event') {
counter++
if (counter === 2) {
return null
}
}
if (cr.event === '$autocapture') {
return {
...cr,
event: 'redacted',
}
}
return cr
},
...(isArray(og) ? og : [og]),
]
})

cy.get('[data-cy-custom-event-button]').click()
cy.get('[data-cy-custom-event-button]').click()

cy.phCaptures().should('deep.equal', [
// before adding the new before sendfn
'$pageview',
'redacted',
'custom-event',
// second button click only has the redacted autocapture event
'redacted',
// because the second custom-event is rejected
])
})
})
58 changes: 57 additions & 1 deletion cypress/e2e/session-recording.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,62 @@ describe('Session recording', () => {
)
})
})

it('it captures XHR method correctly', () => {
cy.get('[data-cy-xhr-call-button]').click()
cy.wait('@example.com')
cy.wait('@session-recording')
cy.phCaptures({ full: true }).then((captures) => {
const snapshots = captures.filter((c) => c.event === '$snapshot')

const capturedRequests: Record<string, any>[] = []
for (const snapshot of snapshots) {
for (const snapshotData of snapshot.properties['$snapshot_data']) {
if (snapshotData.type === 6) {
for (const req of snapshotData.data.payload.requests) {
capturedRequests.push(req)
}
}
}
}

const expectedCaptureds: [RegExp, string][] = [
[/http:\/\/localhost:\d+\/playground\/cypress\//, 'navigation'],
[/http:\/\/localhost:\d+\/static\/array.js/, 'script'],
[
/http:\/\/localhost:\d+\/decide\/\?v=3&ip=1&_=\d+&ver=1\.\d\d\d\.\d+&compression=base64/,
'xmlhttprequest',
],
[/http:\/\/localhost:\d+\/static\/recorder.js\?v=1\.\d\d\d\.\d+/, 'script'],
[/https:\/\/example.com/, 'xmlhttprequest'],
]

// yay, includes expected type 6 network data
expect(capturedRequests.length).to.equal(expectedCaptureds.length)
expectedCaptureds.forEach(([url, initiatorType], index) => {
expect(capturedRequests[index].name).to.match(url)
expect(capturedRequests[index].initiatorType).to.equal(initiatorType)
})

// the HTML file that cypress is operating on (playground/cypress/index.html)
// when the button for this test is click makes a post to https://example.com
const capturedFetchRequest = capturedRequests.find((cr) => cr.name === 'https://example.com/')
expect(capturedFetchRequest).to.not.be.undefined

expect(capturedFetchRequest.fetchStart).to.be.greaterThan(0) // proxy for including network timing info

expect(capturedFetchRequest.initiatorType).to.eql('xmlhttprequest')
expect(capturedFetchRequest.method).to.eql('POST')
expect(capturedFetchRequest.isInitial).to.be.undefined
expect(capturedFetchRequest.requestBody).to.eq('i am the xhr body')

expect(capturedFetchRequest.responseBody).to.eq(
JSON.stringify({
message: 'This is a JSON response',
})
)
})
})
})
})

Expand Down Expand Up @@ -654,7 +710,7 @@ describe('Session recording', () => {
cy.posthog().invoke('capture', 'test_registered_property')
cy.phCaptures({ full: true }).then((captures) => {
expect((captures || []).map((c) => c.event)).to.deep.equal(['$pageview', 'test_registered_property'])
expect(captures[1]['properties']['$session_recording_start_reason']).to.equal('sampling_override')
expect(captures[1]['properties']['$session_recording_start_reason']).to.equal('sampling_overridden')
})

cy.resetPhCaptures()
Expand Down
7 changes: 4 additions & 3 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ Cypress.Commands.add('posthogInit', (options) => {
cy.posthog().invoke('init', 'test_token', {
api_host: location.origin,
debug: true,
_onCapture: (event, eventData) => {
$captures.push(event)
$fullCaptures.push(eventData)
before_send: (event) => {
$captures.push(event.event)
$fullCaptures.push(event)
return event
},
opt_out_useragent_filter: true,
...options,
Expand Down
28 changes: 28 additions & 0 deletions eslint-rules/no-direct-mutation-observer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module.exports = {
meta: {
type: 'problem',
docs: {
description:
'Disallow direct use of MutationObserver and enforce importing NativeMutationObserver from global.ts',
category: 'Best Practices',
recommended: false,
},
schema: [],
messages: {
noDirectMutationObserver:
'Direct use of MutationObserver is not allowed. Use NativeMutationObserver from global.ts instead.',
},
},
create(context) {
return {
NewExpression(node) {
if (node.callee.type === 'Identifier' && node.callee.name === 'MutationObserver') {
context.report({
node,
messageId: 'noDirectMutationObserver',
})
}
},
}
},
}
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posthog-js",
"version": "1.184.1",
"version": "1.187.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 Expand Up @@ -46,6 +46,7 @@
"devDependencies": {
"@babel/core": "7.18.9",
"@babel/plugin-syntax-decorators": "^7.23.3",
"@babel/plugin-transform-exponentiation-operator": "^7.25.9",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.25.8",
"@babel/plugin-transform-react-jsx": "^7.23.4",
"@babel/preset-env": "7.18.9",
Expand Down Expand Up @@ -74,22 +75,22 @@
"@typescript-eslint/eslint-plugin": "^8.2.0",
"@typescript-eslint/parser": "^8.2.0",
"babel-jest": "^26.6.3",
"browserslist": "^4.24.2",
"compare-versions": "^6.1.0",
"cypress": "13.6.3",
"cypress-localstorage-commands": "^2.2.6",
"date-fns": "^3.6.0",
"eslint": "8.57.0",
"eslint-config-posthog-js": "link:eslint-rules",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-compat": "^4.1.4",
"eslint-plugin-compat": "^6.0.1",
"eslint-plugin-jest": "^28.8.3",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-posthog-js": "link:eslint-rules",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"expect": "^29.7.0",
"express": "^4.19.2",
"fast-check": "^2.17.0",
"husky": "^8.0.1",
"jest": "^27.5.1",
Expand Down
45 changes: 39 additions & 6 deletions patches/@[email protected]
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
diff --git a/dist/record.js b/dist/record.js
index 46ec389fefb698243008b39db65470dbdf0a3857..39d9ac821fd84f5a1494ac1e00c8d4e0e58c432a 100644
index 46ec389fefb698243008b39db65470dbdf0a3857..891e1cf6439630d19e9b745ff428db438943a0b2 100644
--- a/dist/record.js
+++ b/dist/record.js
@@ -246,6 +246,9 @@ function isCSSImportRule(rule2) {
@@ -26,6 +26,14 @@ const testableMethods$1 = {
Element: [],
MutationObserver: ["constructor"]
};
+const isFunction = (x) => typeof x === 'function';
+const isAngularZonePatchedFunction = (x) => {
+ if (!isFunction(x)) {
+ return false;
+ }
+ const prototypeKeys = Object.getOwnPropertyNames(x.prototype || {});
+ return prototypeKeys.some((key) => key.indexOf('__zone'));
+}
const untaintedBasePrototype$1 = {};
function getUntaintedPrototype$1(key) {
if (untaintedBasePrototype$1[key])
@@ -54,7 +62,7 @@ function getUntaintedPrototype$1(key) {
}
)
);
- if (isUntaintedAccessors && isUntaintedMethods) {
+ if (isUntaintedAccessors && isUntaintedMethods && !isAngularZonePatchedFunction(defaultObj)) {
untaintedBasePrototype$1[key] = defaultObj.prototype;
return defaultObj.prototype;
}
@@ -246,6 +254,9 @@ function isCSSImportRule(rule2) {
function isCSSStyleRule(rule2) {
return "selectorText" in rule2;
}
Expand All @@ -12,7 +36,7 @@ index 46ec389fefb698243008b39db65470dbdf0a3857..39d9ac821fd84f5a1494ac1e00c8d4e0
class Mirror {
constructor() {
__publicField$1(this, "idNodeMap", /* @__PURE__ */ new Map());
@@ -809,9 +812,14 @@ function serializeElementNode(n2, options) {
@@ -809,9 +820,14 @@ function serializeElementNode(n2, options) {
}
}
if (tagName === "link" && inlineStylesheet) {
Expand All @@ -30,7 +54,7 @@ index 46ec389fefb698243008b39db65470dbdf0a3857..39d9ac821fd84f5a1494ac1e00c8d4e0
let cssText = null;
if (stylesheet) {
cssText = stringifyStylesheet(stylesheet);
@@ -1116,7300 +1124,227 @@ function serializeNodeWithId(n2, options) {
@@ -1116,7300 +1132,227 @@ function serializeNodeWithId(n2, options) {
keepIframeSrcFn
};
if (serializedNode.type === NodeType$2.Element && serializedNode.tagName === "textarea" && serializedNode.attributes.value !== void 0) ;
Expand Down Expand Up @@ -7540,7 +7564,16 @@ index 46ec389fefb698243008b39db65470dbdf0a3857..39d9ac821fd84f5a1494ac1e00c8d4e0
class BaseRRNode {
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
constructor(..._args) {
@@ -11382,11 +4317,19 @@ class CanvasManager {
@@ -8507,7 +1450,7 @@ function getUntaintedPrototype(key) {
}
)
);
- if (isUntaintedAccessors && isUntaintedMethods) {
+ if (isUntaintedAccessors && isUntaintedMethods && !isAngularZonePatchedFunction(defaultObj)) {
untaintedBasePrototype[key] = defaultObj.prototype;
return defaultObj.prototype;
}
@@ -11382,11 +4325,19 @@ class CanvasManager {
let rafId;
const getCanvas = () => {
const matchedCanvas = [];
Expand All @@ -7565,7 +7598,7 @@ index 46ec389fefb698243008b39db65470dbdf0a3857..39d9ac821fd84f5a1494ac1e00c8d4e0
return matchedCanvas;
};
const takeCanvasSnapshots = (timestamp) => {
@@ -11407,13 +4350,20 @@ class CanvasManager {
@@ -11407,13 +4358,20 @@ class CanvasManager {
context.clear(context.COLOR_BUFFER_BIT);
}
}
Expand Down
9 changes: 5 additions & 4 deletions playground/copy-autocapture/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
loaded: function(posthog) {
posthog.identify('test')
},
_onCapture: (event, eventData) => {
if (event === '$copy_autocapture') {
const selectionType = eventData.properties['$copy_type']
const selectionText = eventData.properties['$selected_content']
before_send: (event) => {
if (event.event === '$copy_autocapture') {
const selectionType = event.properties['$copy_type']
const selectionText = event.properties['$selected_content']
document.getElementById('selection-type-outlet').innerText = selectionType
document.getElementById('selection-text-outlet').innerText = selectionText
}
return event
},
})
</script>
Expand Down
Loading

0 comments on commit 2b4515b

Please sign in to comment.