diff --git a/packages/integration-tests/suites/replay/sessionExpiry/init.js b/packages/integration-tests/suites/replay/sessionExpiry/init.js new file mode 100644 index 000000000000..3e685021e1fe --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/init.js @@ -0,0 +1,22 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = new Sentry.Replay({ + flushMinDelay: 500, + flushMaxDelay: 500, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + debug: true, + + integrations: [window.Replay], +}); + +window.Replay._replay.timeouts = { + sessionIdle: 2000, // this is usually 5min, but we want to test this with shorter times + maxSessionLife: 3600000, // default: 60min +}; diff --git a/packages/integration-tests/suites/replay/sessionExpiry/template.html b/packages/integration-tests/suites/replay/sessionExpiry/template.html new file mode 100644 index 000000000000..7223a20f82ba --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/template.html @@ -0,0 +1,10 @@ + + +
+ + + + + + + diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts b/packages/integration-tests/suites/replay/sessionExpiry/test.ts new file mode 100644 index 000000000000..38927478dea6 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts @@ -0,0 +1,98 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates'; +import { + getFullRecordingSnapshots, + getIncrementalRecordingSnapshots, + getReplayEvent, + getReplaySnapshot, + normalize, + shouldSkipReplayTest, + waitForReplayRequest, +} from '../../../utils/replayHelpers'; + +// Session should expire after 2s - keep in sync with init.js +const SESSION_TIMEOUT = 2000; + +sentryTest('handles an expired session RUN', async ({ getLocalTestPath, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, 1); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + const req0 = await reqPromise0; + + const replayEvent0 = getReplayEvent(req0); + expect(replayEvent0).toEqual(getExpectedReplayEvent({})); + + const fullSnapshots0 = getFullRecordingSnapshots(req0); + expect(fullSnapshots0.length).toEqual(1); + const stringifiedSnapshot = normalize(fullSnapshots0[0]); + expect(stringifiedSnapshot).toMatchSnapshot('snapshot-0.json'); + + // We wait for another segment 0 + const reqPromise2 = waitForReplayRequest(page, 0); + + await page.click('#button1'); + const req1 = await reqPromise1; + + const replayEvent1 = getReplayEvent(req1); + expect(replayEvent1).toEqual(getExpectedReplayEvent({ replay_start_timestamp: undefined, segment_id: 1, urls: [] })); + + const fullSnapshots1 = getFullRecordingSnapshots(req1); + expect(fullSnapshots1.length).toEqual(0); + + const incrementalSnapshots1 = getIncrementalRecordingSnapshots(req1); + // The number of incremental snapshots depends on the browser + expect(incrementalSnapshots1.length).toBeGreaterThanOrEqual(4); + + expect(incrementalSnapshots1).toEqual( + expect.arrayContaining([ + { + source: 1, + positions: [ + { + id: 9, + timeOffset: expect.any(Number), + x: expect.any(Number), + y: expect.any(Number), + }, + ], + }, + ]), + ); + + const replay = await getReplaySnapshot(page); + const oldSessionId = replay.session?.id; + + await new Promise(resolve => setTimeout(resolve, SESSION_TIMEOUT)); + + await page.click('#button2'); + const req2 = await reqPromise2; + + const replay2 = await getReplaySnapshot(page); + + expect(replay2.session?.id).not.toEqual(oldSessionId); + + const replayEvent2 = getReplayEvent(req2); + expect(replayEvent2).toEqual(getExpectedReplayEvent({})); + + const fullSnapshots2 = getFullRecordingSnapshots(req2); + expect(fullSnapshots2.length).toEqual(1); + const stringifiedSnapshot2 = normalize(fullSnapshots2[0]); + expect(stringifiedSnapshot2).toMatchSnapshot('snapshot-2.json'); +}); diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-chromium.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-chromium.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-chromium.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-firefox.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-firefox.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-firefox.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-chromium.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-chromium.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-chromium.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-firefox.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-firefox.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-firefox.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2.json b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/init.js b/packages/integration-tests/suites/replay/sessionInactive/init.js new file mode 100644 index 000000000000..f1b0345e71d7 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/init.js @@ -0,0 +1,22 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = new Sentry.Replay({ + flushMinDelay: 500, + flushMaxDelay: 500, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + debug: true, + + integrations: [window.Replay], +}); + +window.Replay._replay.timeouts = { + sessionIdle: 300000, // default: 5min + maxSessionLife: 2000, // this is usually 60min, but we want to test this with shorter times +}; diff --git a/packages/integration-tests/suites/replay/sessionInactive/template.html b/packages/integration-tests/suites/replay/sessionInactive/template.html new file mode 100644 index 000000000000..7223a20f82ba --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/template.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts b/packages/integration-tests/suites/replay/sessionInactive/test.ts new file mode 100644 index 000000000000..e4a1e6d8b243 --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts @@ -0,0 +1,88 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates'; +import { + getFullRecordingSnapshots, + getReplayEvent, + getReplaySnapshot, + normalize, + shouldSkipReplayTest, + waitForReplayRequest, +} from '../../../utils/replayHelpers'; + +// Session should expire after 2s - keep in sync with init.js +const SESSION_TIMEOUT = 2000; + +sentryTest('handles an inactive session', async ({ getLocalTestPath, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + const req0 = await reqPromise0; + + const replayEvent0 = getReplayEvent(req0); + expect(replayEvent0).toEqual(getExpectedReplayEvent({})); + + const fullSnapshots0 = getFullRecordingSnapshots(req0); + expect(fullSnapshots0.length).toEqual(1); + const stringifiedSnapshot = normalize(fullSnapshots0[0]); + expect(stringifiedSnapshot).toMatchSnapshot('snapshot-0.json'); + + await page.click('#button1'); + + // We wait for another segment 0 + const reqPromise1 = waitForReplayRequest(page, 0); + + // Now we wait for the session timeout, nothing should be sent in the meanwhile + await new Promise(resolve => setTimeout(resolve, SESSION_TIMEOUT)); + + // nothing happened because no activity/inactivity was detected + const replay = await getReplaySnapshot(page); + // @ts-ignore private api + expect(replay._isEnabled).toEqual(true); + // @ts-ignore private api + expect(replay._isPaused).toEqual(false); + + // Now we trigger a blur event, which should move the session to paused mode + await page.evaluate(() => { + window.dispatchEvent(new Event('blur')); + }); + + const replay2 = await getReplaySnapshot(page); + // @ts-ignore private api + expect(replay2._isEnabled).toEqual(true); + // @ts-ignore private api + expect(replay2._isPaused).toEqual(true); + + // Trigger an action, should re-start the recording + await page.click('#button2'); + const req1 = await reqPromise1; + + const replay3 = await getReplaySnapshot(page); + // @ts-ignore private api + expect(replay3._isEnabled).toEqual(true); + // @ts-ignore private api + expect(replay3._isPaused).toEqual(false); + + const replayEvent1 = getReplayEvent(req1); + expect(replayEvent1).toEqual(getExpectedReplayEvent({})); + + const fullSnapshots1 = getFullRecordingSnapshots(req1); + expect(fullSnapshots1.length).toEqual(1); + const stringifiedSnapshot1 = normalize(fullSnapshots1[0]); + expect(stringifiedSnapshot1).toMatchSnapshot('snapshot-1.json'); +}); diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-chromium.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-chromium.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-chromium.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-firefox.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-firefox.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-firefox.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-webkit.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-webkit.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0-webkit.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-0.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-chromium.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-chromium.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-chromium.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-firefox.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-firefox.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-firefox.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-webkit.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-webkit.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1-webkit.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1.json b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1.json new file mode 100644 index 000000000000..a4ad4ac4d60a --- /dev/null +++ b/packages/integration-tests/suites/replay/sessionInactive/test.ts-snapshots/snapshot-1.json @@ -0,0 +1,109 @@ +{ + "node": { + "type": 0, + "childNodes": [ + { + "type": 1, + "name": "html", + "publicId": "", + "systemId": "", + "id": 2 + }, + { + "type": 2, + "tagName": "html", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "head", + "attributes": {}, + "childNodes": [ + { + "type": 2, + "tagName": "meta", + "attributes": { + "charset": "utf-8" + }, + "childNodes": [], + "id": 5 + } + ], + "id": 4 + }, + { + "type": 3, + "textContent": "\n ", + "id": 6 + }, + { + "type": 2, + "tagName": "body", + "attributes": {}, + "childNodes": [ + { + "type": 3, + "textContent": "\n ", + "id": 8 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 1')", + "id": "button1" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 10 + } + ], + "id": 9 + }, + { + "type": 3, + "textContent": "\n ", + "id": 11 + }, + { + "type": 2, + "tagName": "button", + "attributes": { + "onclick": "console.log('Test log 2')", + "id": "button2" + }, + "childNodes": [ + { + "type": 3, + "textContent": "***** **", + "id": 13 + } + ], + "id": 12 + }, + { + "type": 3, + "textContent": "\n ", + "id": 14 + }, + { + "type": 3, + "textContent": "\n\n", + "id": 15 + } + ], + "id": 7 + } + ], + "id": 3 + } + ], + "id": 1 + }, + "initialOffset": { + "left": 0, + "top": 0 + } +} \ No newline at end of file diff --git a/packages/integration-tests/utils/replayHelpers.ts b/packages/integration-tests/utils/replayHelpers.ts index 236431a75ee6..b68e74708916 100644 --- a/packages/integration-tests/utils/replayHelpers.ts +++ b/packages/integration-tests/utils/replayHelpers.ts @@ -138,7 +138,7 @@ export function getFullRecordingSnapshots(resOrReq: Request | Response): Recordi return events.filter(event => event.type === 2).map(event => event.data as RecordingSnapshot); } -function getIncrementalRecordingSnapshots(resOrReq: Request | Response): RecordingSnapshot[] { +export function getIncrementalRecordingSnapshots(resOrReq: Request | Response): RecordingSnapshot[] { const replayRequest = getRequest(resOrReq); const events = getDecompressedRecordingEvents(replayRequest) as RecordingEvent[]; return events.filter(event => event.type === 3).map(event => event.data as RecordingSnapshot); diff --git a/packages/replay/package.json b/packages/replay/package.json index dc607ff7b0f1..c1a783a0e763 100644 --- a/packages/replay/package.json +++ b/packages/replay/package.json @@ -25,7 +25,7 @@ "fix:prettier": "prettier --write \"{src,test,scripts}/**/*.ts\"", "lint": "run-s lint:prettier lint:eslint", "lint:eslint": "eslint . --format stylish", - "lint:prettier": "prettier --check \"{src,test,scripts,worker}/**/*.ts\"", + "lint:prettier": "prettier --check \"{src,test,scripts}/**/*.ts\"", "test": "jest", "test:watch": "jest --watch", "bootstrap:demo": "cd demo && yarn", diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index 505d53410d1f..544052172017 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -21,6 +21,7 @@ import type { ReplayContainer as ReplayContainerInterface, ReplayPluginOptions, Session, + Timeouts, } from './types'; import { addEvent } from './util/addEvent'; import { addGlobalListeners } from './util/addGlobalListeners'; @@ -54,6 +55,15 @@ export class ReplayContainer implements ReplayContainerInterface { */ public recordingMode: ReplayRecordingMode = 'session'; + /** + * These are here so we can overwrite them in tests etc. + * @hidden + */ + public readonly timeouts: Timeouts = { + sessionIdle: SESSION_IDLE_DURATION, + maxSessionLife: MAX_SESSION_LIFE, + } as const; + /** * Options to pass to `rrweb.record()` */ @@ -368,7 +378,7 @@ export class ReplayContainer implements ReplayContainerInterface { // MAX_SESSION_LIFE. Otherwise non-user activity can trigger a new // session+recording. This creates noisy replays that do not have much // content in them. - if (this._lastActivity && isExpired(this._lastActivity, MAX_SESSION_LIFE)) { + if (this._lastActivity && isExpired(this._lastActivity, this.timeouts.maxSessionLife)) { // Pause recording this.pause(); return; @@ -408,7 +418,7 @@ export class ReplayContainer implements ReplayContainerInterface { */ private _loadAndCheckSession(): boolean { const { type, session } = getSession({ - expiry: SESSION_IDLE_DURATION, + timeouts: this.timeouts, stickySession: Boolean(this._options.stickySession), currentSession: this.session, sessionSampleRate: this._options.sessionSampleRate, @@ -517,7 +527,7 @@ export class ReplayContainer implements ReplayContainerInterface { ) => { // If this is false, it means session is expired, create and a new session and wait for checkout if (!this.checkAndHandleExpiredSession()) { - __DEBUG_BUILD__ && logger.error('[Replay] Received replay event after session expired.'); + __DEBUG_BUILD__ && logger.warn('[Replay] Received replay event after session expired.'); return; } @@ -620,7 +630,7 @@ export class ReplayContainer implements ReplayContainerInterface { return; } - const expired = isSessionExpired(this.session, SESSION_IDLE_DURATION); + const expired = isSessionExpired(this.session, this.timeouts); if (breadcrumb && !expired) { this._createCustomBreadcrumb(breadcrumb); diff --git a/packages/replay/src/session/getSession.ts b/packages/replay/src/session/getSession.ts index 54b16d9a5414..150fbe12c871 100644 --- a/packages/replay/src/session/getSession.ts +++ b/packages/replay/src/session/getSession.ts @@ -1,16 +1,13 @@ import { logger } from '@sentry/utils'; -import type { Session, SessionOptions } from '../types'; +import type { Session, SessionOptions, Timeouts } from '../types'; import { isSessionExpired } from '../util/isSessionExpired'; import { createSession } from './createSession'; import { fetchSession } from './fetchSession'; import { makeSession } from './Session'; interface GetSessionParams extends SessionOptions { - /** - * The length of time (in ms) which we will consider the session to be expired. - */ - expiry: number; + timeouts: Timeouts; /** * The current session (e.g. if stickySession is off) @@ -22,7 +19,7 @@ interface GetSessionParams extends SessionOptions { * Get or create a session */ export function getSession({ - expiry, + timeouts, currentSession, stickySession, sessionSampleRate, @@ -35,7 +32,7 @@ export function getSession({ // If there is a session, check if it is valid (e.g. "last activity" time // should be within the "session idle time", and "session started" time is // within "max session time"). - const isExpired = isSessionExpired(session, expiry); + const isExpired = isSessionExpired(session, timeouts); if (!isExpired) { return { type: 'saved', session }; diff --git a/packages/replay/src/types.ts b/packages/replay/src/types.ts index 174f1da240f8..b91e8be36129 100644 --- a/packages/replay/src/types.ts +++ b/packages/replay/src/types.ts @@ -18,6 +18,11 @@ export interface SendReplayData { options: ReplayPluginOptions; } +export interface Timeouts { + sessionIdle: number; + maxSessionLife: number; +} + /** * The request payload to worker */ @@ -289,6 +294,10 @@ export interface ReplayContainer { performanceEvents: AllPerformanceEntry[]; session: Session | undefined; recordingMode: ReplayRecordingMode; + timeouts: { + sessionIdle: number; + maxSessionLife: number; + }; isEnabled(): boolean; isPaused(): boolean; getContext(): InternalEventContext; diff --git a/packages/replay/src/util/addEvent.ts b/packages/replay/src/util/addEvent.ts index 5cf351fd6f9c..8b2918656518 100644 --- a/packages/replay/src/util/addEvent.ts +++ b/packages/replay/src/util/addEvent.ts @@ -1,7 +1,6 @@ import { getCurrentHub } from '@sentry/core'; import { logger } from '@sentry/utils'; -import { SESSION_IDLE_DURATION } from '../constants'; import type { AddEventResult, RecordingEvent, ReplayContainer } from '../types'; /** @@ -31,7 +30,7 @@ export async function addEvent( // page has been left open and idle for a long period of time and user // comes back to trigger a new session. The performance entries rely on // `performance.timeOrigin`, which is when the page first opened. - if (timestampInMs + SESSION_IDLE_DURATION < new Date().getTime()) { + if (timestampInMs + replay.timeouts.sessionIdle < new Date().getTime()) { return null; } diff --git a/packages/replay/src/util/isSessionExpired.ts b/packages/replay/src/util/isSessionExpired.ts index a9d529f0986a..b7025a19cbf6 100644 --- a/packages/replay/src/util/isSessionExpired.ts +++ b/packages/replay/src/util/isSessionExpired.ts @@ -1,16 +1,15 @@ -import { MAX_SESSION_LIFE } from '../constants'; -import type { Session } from '../types'; +import type { Session, Timeouts } from '../types'; import { isExpired } from './isExpired'; /** * Checks to see if session is expired */ -export function isSessionExpired(session: Session, idleTimeout: number, targetTime: number = +new Date()): boolean { +export function isSessionExpired(session: Session, timeouts: Timeouts, targetTime: number = +new Date()): boolean { return ( // First, check that maximum session length has not been exceeded - isExpired(session.started, MAX_SESSION_LIFE, targetTime) || + isExpired(session.started, timeouts.maxSessionLife, targetTime) || // check that the idle timeout has not been exceeded (i.e. user has // performed an action within the last `idleTimeout` ms) - isExpired(session.lastActivity, idleTimeout, targetTime) + isExpired(session.lastActivity, timeouts.sessionIdle, targetTime) ); } diff --git a/packages/replay/test/unit/session/getSession.test.ts b/packages/replay/test/unit/session/getSession.test.ts index 54c259fb7772..4ac92b148e75 100644 --- a/packages/replay/test/unit/session/getSession.test.ts +++ b/packages/replay/test/unit/session/getSession.test.ts @@ -1,4 +1,4 @@ -import { WINDOW } from '../../../src/constants'; +import { MAX_SESSION_LIFE, SESSION_IDLE_DURATION, WINDOW } from '../../../src/constants'; import * as CreateSession from '../../../src/session/createSession'; import * as FetchSession from '../../../src/session/fetchSession'; import { getSession } from '../../../src/session/getSession'; @@ -42,7 +42,10 @@ describe('Unit | session | getSession', () => { it('creates a non-sticky session when one does not exist', function () { const { session } = getSession({ - expiry: 900000, + timeouts: { + sessionIdle: SESSION_IDLE_DURATION, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: false, ...SAMPLE_RATES, }); @@ -66,7 +69,10 @@ describe('Unit | session | getSession', () => { saveSession(createMockSession(new Date().getTime() - 10000)); const { session } = getSession({ - expiry: 1000, + timeouts: { + sessionIdle: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: false, ...SAMPLE_RATES, }); @@ -79,7 +85,10 @@ describe('Unit | session | getSession', () => { it('creates a non-sticky session, when one is expired', function () { const { session } = getSession({ - expiry: 1000, + timeouts: { + sessionIdle: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: false, ...SAMPLE_RATES, currentSession: makeSession({ @@ -102,7 +111,10 @@ describe('Unit | session | getSession', () => { expect(FetchSession.fetchSession()).toBe(null); const { session } = getSession({ - expiry: 900000, + timeouts: { + sessionIdle: SESSION_IDLE_DURATION, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: true, sessionSampleRate: 1.0, errorSampleRate: 0.0, @@ -134,7 +146,10 @@ describe('Unit | session | getSession', () => { saveSession(createMockSession(now)); const { session } = getSession({ - expiry: 1000, + timeouts: { + sessionIdle: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: true, sessionSampleRate: 1.0, errorSampleRate: 0.0, @@ -157,7 +172,10 @@ describe('Unit | session | getSession', () => { saveSession(createMockSession(new Date().getTime() - 2000)); const { session } = getSession({ - expiry: 1000, + timeouts: { + sessionIdle: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: true, ...SAMPLE_RATES, }); @@ -173,7 +191,10 @@ describe('Unit | session | getSession', () => { it('fetches a non-expired non-sticky session', function () { const { session } = getSession({ - expiry: 1000, + timeouts: { + sessionIdle: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, stickySession: false, ...SAMPLE_RATES, currentSession: makeSession({ diff --git a/packages/replay/test/unit/util/isSessionExpired.test.ts b/packages/replay/test/unit/util/isSessionExpired.test.ts index 381b8ffe6428..627105d322f0 100644 --- a/packages/replay/test/unit/util/isSessionExpired.test.ts +++ b/packages/replay/test/unit/util/isSessionExpired.test.ts @@ -1,3 +1,4 @@ +import { MAX_SESSION_LIFE } from '../../../src/constants'; import { makeSession } from '../../../src/session/Session'; import { isSessionExpired } from '../../../src/util/isSessionExpired'; @@ -14,18 +15,28 @@ function createSession(extra?: Record