Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Rework how we track sessions #3224

Merged
merged 7 commits into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 3 additions & 62 deletions packages/browser/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,73 +201,14 @@ function startSessionTracking(): void {

const hub = getCurrentHub();

/**
* We should be using `Promise.all([windowLoaded, firstContentfulPaint])` here,
* but, as always, it's not available in the IE10-11. Thanks IE.
*/
let loadResolved = document.readyState === 'complete';
let fcpResolved = false;
const possiblyEndSession = (): void => {
if (fcpResolved && loadResolved) {
hub.endSession();
}
};
const resolveWindowLoaded = (): void => {
loadResolved = true;
possiblyEndSession();
window.removeEventListener('load', resolveWindowLoaded);
};

hub.startSession();

if (!loadResolved) {
// IE doesn't support `{ once: true }` for event listeners, so we have to manually
// attach and then detach it once completed.
window.addEventListener('load', resolveWindowLoaded);
}

try {
const po = new PerformanceObserver((entryList, po) => {
entryList.getEntries().forEach(entry => {
if (entry.name === 'first-contentful-paint' && entry.startTime < firstHiddenTime) {
po.disconnect();
fcpResolved = true;
possiblyEndSession();
}
});
});

// There's no need to even attach this listener if `PerformanceObserver` constructor will fail,
// so we do it below here.
let firstHiddenTime = document.visibilityState === 'hidden' ? 0 : Infinity;
document.addEventListener(
'visibilitychange',
event => {
firstHiddenTime = Math.min(firstHiddenTime, event.timeStamp);
},
{ once: true },
);

po.observe({
type: 'paint',
buffered: true,
});
} catch (e) {
fcpResolved = true;
possiblyEndSession();
}
hub.captureSession();

// We want to create a session for every navigation as well
addInstrumentationHandler({
callback: () => {
if (
!getCurrentHub()
.getScope()
?.getSession()
) {
hub.startSession();
hub.endSession();
}
hub.startSession();
hub.captureSession();
},
type: 'history',
});
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ export abstract class BaseClient<B extends Backend, O extends Options> implement
logger.warn('Discarded session because of missing release');
} else {
this._sendSession(session);
// After sending, we set init false to inidcate it's not the first occurence
session.update({ init: false });
}
}

Expand Down Expand Up @@ -252,6 +254,7 @@ export abstract class BaseClient<B extends Backend, O extends Options> implement
userAgent,
errors: session.errors + Number(errored || crashed),
});
this.captureSession(session);
}

/** Deliver captured session to Sentry */
Expand Down
46 changes: 39 additions & 7 deletions packages/hub/src/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
IntegrationClass,
Primitive,
SessionContext,
SessionStatus,
Severity,
Span,
SpanContext,
Expand Down Expand Up @@ -360,10 +361,33 @@ export class Hub implements HubInterface {
/**
* @inheritDoc
*/
public startSession(context?: SessionContext): Session {
// End existing session if there's one
this.endSession();
public captureSession(endSession: boolean = false): void {
// both send the update and pull the session from the scope
if (endSession) {
return this.endSession();
}

// only send the update
this._sendSessionUpdate();
}

/**
* @inheritDoc
*/
public endSession(): void {
this.getStackTop()
?.scope?.getSession()
?.close();
this._sendSessionUpdate();

// the session is over; take it off of the scope
this.getStackTop()?.scope?.setSession();
}

/**
* @inheritDoc
*/
public startSession(context?: SessionContext): Session {
const { scope, client } = this.getStackTop();
const { release, environment } = (client && client.getOptions()) || {};
const session = new Session({
Expand All @@ -372,26 +396,34 @@ export class Hub implements HubInterface {
...(scope && { user: scope.getUser() }),
...context,
});

if (scope) {
// End existing session if there's one
const currentSession = scope.getSession && scope.getSession();
if (currentSession && currentSession.status === SessionStatus.Ok) {
currentSession.update({ status: SessionStatus.Exited });
}
this.endSession();

// Afterwards we set the new session on the scope
scope.setSession(session);
}

return session;
}

/**
* @inheritDoc
* Sends the current Session on the scope
*/
public endSession(): void {
private _sendSessionUpdate(): void {
const { scope, client } = this.getStackTop();
if (!scope) return;

const session = scope.getSession && scope.getSession();
if (session) {
session.close();
if (client && client.captureSession) {
client.captureSession(session);
}
scope.setSession();
}
}

Expand Down
6 changes: 5 additions & 1 deletion packages/hub/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class Session implements SessionInterface {
public status: SessionStatus = SessionStatus.Ok;
public environment?: string;
public ipAddress?: string;
public init: boolean = true;

constructor(context?: Omit<SessionContext, 'started' | 'status'>) {
if (context) {
Expand All @@ -42,6 +43,9 @@ export class Session implements SessionInterface {
// Good enough uuid validation. — Kamil
this.sid = context.sid.length === 32 ? context.sid : uuid4();
}
if (context.init !== undefined) {
this.init = context.init;
}
if (context.did) {
this.did = `${context.did}`;
}
Expand Down Expand Up @@ -103,7 +107,7 @@ export class Session implements SessionInterface {
} {
return dropUndefinedKeys({
sid: `${this.sid}`,
init: true,
init: this.init,
started: new Date(this.started).toISOString(),
timestamp: new Date(this.timestamp).toISOString(),
status: this.status,
Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,10 @@ export interface Hub {
* Ends the session that lives on the current scope and sends it to Sentry
*/
endSession(): void;

/**
* Sends the current session on the scope to Sentry
* @param endSession If set the session will be marked as exited and removed from the scope
*/
captureSession(endSession: boolean): void;
}
1 change: 1 addition & 0 deletions packages/types/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface Session extends SessionContext {
export interface SessionContext {
sid?: string;
did?: string;
init?: boolean;
timestamp?: number;
started?: number;
duration?: number;
Expand Down