Skip to content

Commit 35aaf90

Browse files
HazATlobsterkatie
andauthored
feat: Rework how we track sessions (#3224)
* feat: Rework how we track sessions * Apply suggestions from code review Co-authored-by: Katie Byers <[email protected]> * feat: Rework * fix: Remove test code * fix: Linter * meta: Set Version to 6.1.0-beta.0 * Revert "meta: Set Version to 6.1.0-beta.0" This reverts commit f25938f. Co-authored-by: Katie Byers <[email protected]>
1 parent bb83006 commit 35aaf90

File tree

6 files changed

+57
-70
lines changed

6 files changed

+57
-70
lines changed

packages/browser/src/sdk.ts

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -201,73 +201,14 @@ function startSessionTracking(): void {
201201

202202
const hub = getCurrentHub();
203203

204-
/**
205-
* We should be using `Promise.all([windowLoaded, firstContentfulPaint])` here,
206-
* but, as always, it's not available in the IE10-11. Thanks IE.
207-
*/
208-
let loadResolved = document.readyState === 'complete';
209-
let fcpResolved = false;
210-
const possiblyEndSession = (): void => {
211-
if (fcpResolved && loadResolved) {
212-
hub.endSession();
213-
}
214-
};
215-
const resolveWindowLoaded = (): void => {
216-
loadResolved = true;
217-
possiblyEndSession();
218-
window.removeEventListener('load', resolveWindowLoaded);
219-
};
220-
221204
hub.startSession();
222-
223-
if (!loadResolved) {
224-
// IE doesn't support `{ once: true }` for event listeners, so we have to manually
225-
// attach and then detach it once completed.
226-
window.addEventListener('load', resolveWindowLoaded);
227-
}
228-
229-
try {
230-
const po = new PerformanceObserver((entryList, po) => {
231-
entryList.getEntries().forEach(entry => {
232-
if (entry.name === 'first-contentful-paint' && entry.startTime < firstHiddenTime) {
233-
po.disconnect();
234-
fcpResolved = true;
235-
possiblyEndSession();
236-
}
237-
});
238-
});
239-
240-
// There's no need to even attach this listener if `PerformanceObserver` constructor will fail,
241-
// so we do it below here.
242-
let firstHiddenTime = document.visibilityState === 'hidden' ? 0 : Infinity;
243-
document.addEventListener(
244-
'visibilitychange',
245-
event => {
246-
firstHiddenTime = Math.min(firstHiddenTime, event.timeStamp);
247-
},
248-
{ once: true },
249-
);
250-
251-
po.observe({
252-
type: 'paint',
253-
buffered: true,
254-
});
255-
} catch (e) {
256-
fcpResolved = true;
257-
possiblyEndSession();
258-
}
205+
hub.captureSession();
259206

260207
// We want to create a session for every navigation as well
261208
addInstrumentationHandler({
262209
callback: () => {
263-
if (
264-
!getCurrentHub()
265-
.getScope()
266-
?.getSession()
267-
) {
268-
hub.startSession();
269-
hub.endSession();
270-
}
210+
hub.startSession();
211+
hub.captureSession();
271212
},
272213
type: 'history',
273214
});

packages/core/src/baseclient.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ export abstract class BaseClient<B extends Backend, O extends Options> implement
156156
logger.warn('Discarded session because of missing release');
157157
} else {
158158
this._sendSession(session);
159+
// After sending, we set init false to inidcate it's not the first occurence
160+
session.update({ init: false });
159161
}
160162
}
161163

@@ -252,6 +254,7 @@ export abstract class BaseClient<B extends Backend, O extends Options> implement
252254
userAgent,
253255
errors: session.errors + Number(errored || crashed),
254256
});
257+
this.captureSession(session);
255258
}
256259

257260
/** Deliver captured session to Sentry */

packages/hub/src/hub.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
IntegrationClass,
1414
Primitive,
1515
SessionContext,
16+
SessionStatus,
1617
Severity,
1718
Span,
1819
SpanContext,
@@ -360,10 +361,33 @@ export class Hub implements HubInterface {
360361
/**
361362
* @inheritDoc
362363
*/
363-
public startSession(context?: SessionContext): Session {
364-
// End existing session if there's one
365-
this.endSession();
364+
public captureSession(endSession: boolean = false): void {
365+
// both send the update and pull the session from the scope
366+
if (endSession) {
367+
return this.endSession();
368+
}
369+
370+
// only send the update
371+
this._sendSessionUpdate();
372+
}
373+
374+
/**
375+
* @inheritDoc
376+
*/
377+
public endSession(): void {
378+
this.getStackTop()
379+
?.scope?.getSession()
380+
?.close();
381+
this._sendSessionUpdate();
382+
383+
// the session is over; take it off of the scope
384+
this.getStackTop()?.scope?.setSession();
385+
}
366386

387+
/**
388+
* @inheritDoc
389+
*/
390+
public startSession(context?: SessionContext): Session {
367391
const { scope, client } = this.getStackTop();
368392
const { release, environment } = (client && client.getOptions()) || {};
369393
const session = new Session({
@@ -372,26 +396,34 @@ export class Hub implements HubInterface {
372396
...(scope && { user: scope.getUser() }),
373397
...context,
374398
});
399+
375400
if (scope) {
401+
// End existing session if there's one
402+
const currentSession = scope.getSession && scope.getSession();
403+
if (currentSession && currentSession.status === SessionStatus.Ok) {
404+
currentSession.update({ status: SessionStatus.Exited });
405+
}
406+
this.endSession();
407+
408+
// Afterwards we set the new session on the scope
376409
scope.setSession(session);
377410
}
411+
378412
return session;
379413
}
380414

381415
/**
382-
* @inheritDoc
416+
* Sends the current Session on the scope
383417
*/
384-
public endSession(): void {
418+
private _sendSessionUpdate(): void {
385419
const { scope, client } = this.getStackTop();
386420
if (!scope) return;
387421

388422
const session = scope.getSession && scope.getSession();
389423
if (session) {
390-
session.close();
391424
if (client && client.captureSession) {
392425
client.captureSession(session);
393426
}
394-
scope.setSession();
395427
}
396428
}
397429

packages/hub/src/session.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export class Session implements SessionInterface {
1616
public status: SessionStatus = SessionStatus.Ok;
1717
public environment?: string;
1818
public ipAddress?: string;
19+
public init: boolean = true;
1920

2021
constructor(context?: Omit<SessionContext, 'started' | 'status'>) {
2122
if (context) {
@@ -42,6 +43,9 @@ export class Session implements SessionInterface {
4243
// Good enough uuid validation. — Kamil
4344
this.sid = context.sid.length === 32 ? context.sid : uuid4();
4445
}
46+
if (context.init !== undefined) {
47+
this.init = context.init;
48+
}
4549
if (context.did) {
4650
this.did = `${context.did}`;
4751
}
@@ -103,7 +107,7 @@ export class Session implements SessionInterface {
103107
} {
104108
return dropUndefinedKeys({
105109
sid: `${this.sid}`,
106-
init: true,
110+
init: this.init,
107111
started: new Date(this.started).toISOString(),
108112
timestamp: new Date(this.timestamp).toISOString(),
109113
status: this.status,

packages/types/src/hub.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,10 @@ export interface Hub {
222222
* Ends the session that lives on the current scope and sends it to Sentry
223223
*/
224224
endSession(): void;
225+
226+
/**
227+
* Sends the current session on the scope to Sentry
228+
* @param endSession If set the session will be marked as exited and removed from the scope
229+
*/
230+
captureSession(endSession: boolean): void;
225231
}

packages/types/src/session.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface Session extends SessionContext {
3535
export interface SessionContext {
3636
sid?: string;
3737
did?: string;
38+
init?: boolean;
3839
timestamp?: number;
3940
started?: number;
4041
duration?: number;

0 commit comments

Comments
 (0)