Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions examples/sdk/browser/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ <h1 class="card-header" style="text-align: left">Welcome to the Backtrace demo</
<div class="action-button center">
<a class="text" id="send-message" target="_blank">Send a message</a>
</div>
<div class="action-button center">
<a class="text" id="generate-metric" target="_blank">Generate metric</a>
</div>
<div class="action-button center">
<a class="text" id="send-metrics" target="_blank">Send metrics</a>
</div>
</div>
<div class="summary-information">
<p>If you have any questions or concerns, please contact us at</p>
Expand Down
22 changes: 22 additions & 0 deletions examples/sdk/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ function parseNotExistingDomElement(): string {

const sendErrorButton = document.getElementById('send-error') as HTMLElement;
const sendMessageButton = document.getElementById('send-message') as HTMLElement;
const generateMetricButton = document.getElementById('generate-metric') as HTMLElement;
const sendMetricsButton = document.getElementById('send-metrics') as HTMLElement;

async function sendHandledException() {
try {
Expand All @@ -40,5 +42,25 @@ async function sendMessage() {
]);
}

function generateMetric() {
console.log('generate-metric click');
if (!client.metrics) {
console.log('metrics are unavailable');
return;
}
client.metrics.addSummedEvent('click');
}

function sendMetrics() {
console.log('send-metrics click');
if (!client.metrics) {
console.log('metrics are unavailable');
return;
}
client.metrics.send();
}

sendErrorButton.onclick = sendHandledException;
sendMessageButton.onclick = sendMessage;
generateMetricButton.onclick = generateMetric;
sendMetricsButton.onclick = sendMetrics;
24 changes: 24 additions & 0 deletions examples/sdk/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,27 @@ async function sendMessage(message: string, attributes: Record<string, number>)
await client.send(message, attributes);
}

function addEvent(name: string, attributes: Record<string, number>) {
if (!client.metrics) {
console.log('metrics are unavailable');
return;
}
client.metrics.addSummedEvent(name, attributes);
}
function sendMetrics() {
if (!client.metrics) {
console.log('metrics are unavailable');
return;
}
client.metrics.send();
}
function showMenu() {
const menu =
`Please pick one of available options:\n` +
`1. Send an exception\n` +
`2. Send a message\n` +
`3. Add a new summed event\n` +
`4. Send all metrics\n` +
`0. Exit\n` +
`Type the option number:`;
reader.question(menu, async function executeUserOption(optionString: string) {
Expand All @@ -59,6 +75,14 @@ function showMenu() {
await sendMessage('test message', attributes);
break;
}
case 3: {
addEvent('Option clicked', attributes);
break;
}
case 4: {
sendMetrics();
break;
}
case 0: {
reader.close();
return exit(0);
Expand Down
68 changes: 68 additions & 0 deletions packages/browser/src/BacktraceBrowserSessionProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { BacktraceSessionProvider, IdGenerator } from '@backtrace/sdk-core';
import { TimeHelper } from '@backtrace/sdk-core/lib/common/TimeHelper';

export class BacktraceBrowserSessionProvider implements BacktraceSessionProvider {
/**
* Session persistence interval. If no event was send in the persistence interval
* the session is treaten as an old session.
*/
public static readonly PERSISTENCE_INTERVAL = TimeHelper.convertSecondsToMilliseconds(30 * 60);
private readonly SESSION_LAST_ACTIVE = 'backtrace-last-active';
private readonly SESSION_GUID = 'backtrace-guid';

get lastActive(): number {
return this._lastActive;
}

public readonly newSession: boolean = true;

public readonly sessionId: string = IdGenerator.uuid();

private _lastActive = 0;

constructor() {
if (!window.localStorage) {
return;
}

const lastActive = this.readLastActiveTimestamp();
if (!lastActive || TimeHelper.now() - lastActive > BacktraceBrowserSessionProvider.PERSISTENCE_INTERVAL) {
this.updateLastActiveTimestamp();
localStorage.setItem(this.SESSION_GUID, this.sessionId);
return;
}
this._lastActive = lastActive;
this.newSession = false;
this.sessionId = localStorage.getItem(this.SESSION_GUID) as string;
}

public afterMetricsSubmission(): void {
this.updateLastActiveTimestamp();
}

public shouldSend(): boolean {
// if the document is hidden, we shouldn't send metrics, because the open document
// is the one who is being used by the user. This condition makes sure two or more web
// browser tabs of the same app won't report the same metrics or false positive metrics.
return document.hidden === false;
}

private readLastActiveTimestamp(): number | undefined {
const lastActiveStringTimestamp = localStorage.getItem(this.SESSION_LAST_ACTIVE);
if (!lastActiveStringTimestamp) {
return undefined;
}

const lastActive = parseInt(lastActiveStringTimestamp, 10);
if (isNaN(lastActive)) {
return undefined;
}

return lastActive;
}

public updateLastActiveTimestamp() {
this._lastActive = TimeHelper.now();
localStorage.setItem(this.SESSION_LAST_ACTIVE, this._lastActive.toString(10));
}
}
3 changes: 2 additions & 1 deletion packages/browser/src/BacktraceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
BacktraceStackTraceConverter,
} from '@backtrace/sdk-core';
import { AGENT } from './agentDefinition';
import { BacktraceBrowserSessionProvider } from './BacktraceBrowserSessionProvider';
import { BacktraceConfiguration } from './BacktraceConfiguration';
import { BacktraceClientBuilder } from './builder/BacktraceClientBuilder';

Expand All @@ -15,7 +16,7 @@ export class BacktraceClient extends BacktraceCoreClient {
attributeProviders: BacktraceAttributeProvider[],
stackTraceConverter: BacktraceStackTraceConverter,
) {
super(options, AGENT, handler, attributeProviders, stackTraceConverter);
super(options, AGENT, handler, attributeProviders, stackTraceConverter, new BacktraceBrowserSessionProvider());
}

public static builder(options: BacktraceConfiguration): BacktraceClientBuilder {
Expand Down
36 changes: 13 additions & 23 deletions packages/browser/tests/client/clientTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,25 @@ describe('Client tests', () => {
postError: jest.fn().mockResolvedValue(Promise.resolve()),
};

const defaultClientOptions = {
name: 'test',
version: '1.0.0',
url: 'https://submit.backtrace.io/foo/bar/baz',
metrics: {
enable: false,
},
};

let client: BacktraceClient;
it('Should create a client', () => {
client = BacktraceClient.builder({
name: 'test',
version: '1.0.0',
url: 'https://submit.backtrace.io/foo/bar/baz',
}).build();
client = BacktraceClient.builder(defaultClientOptions).build();

expect(client).toBeDefined();
});

describe('Send tests', () => {
beforeEach(() => {
client = BacktraceClient.builder({
name: 'test',
version: '1.0.0',
url: 'https://submit.backtrace.io/foo/bar/baz',
})
.useRequestHandler(requestHandler)
.build();
client = BacktraceClient.builder(defaultClientOptions).useRequestHandler(requestHandler).build();
});
it(`Should not throw an error when sending a message`, async () => {
expect(async () => await client.send('test')).not.toThrow();
Expand All @@ -48,9 +47,7 @@ describe('Client tests', () => {
it(`Should generate an attachment list based on the client options`, async () => {
const testedAttachment = new BacktraceUint8ArrayAttachment('client-add-test', new Uint8Array(0));
client = BacktraceClient.builder({
name: 'test',
version: '1.0.0',
url: 'https://submit.backtrace.io/foo/bar/baz',
...defaultClientOptions,
attachments: [testedAttachment],
})
.useRequestHandler(requestHandler)
Expand All @@ -63,14 +60,7 @@ describe('Client tests', () => {

it(`Should allow to add more attachments`, async () => {
const testedAttachment = new BacktraceUint8ArrayAttachment('client-add-test', new Uint8Array(0));
client = BacktraceClient.builder({
name: 'test',
version: '1.0.0',
url: 'https://submit.backtrace.io/foo/bar/baz',
attachments: [],
})
.useRequestHandler(requestHandler)
.build();
client = BacktraceClient.builder(defaultClientOptions).useRequestHandler(requestHandler).build();

client.attachments.push(testedAttachment);
expect(client.attachments).toBeDefined();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { TimeHelper } from '@backtrace/sdk-core/lib/common/TimeHelper';
import { BacktraceBrowserSessionProvider } from '../../src/BacktraceBrowserSessionProvider';
describe('Session provider tests', () => {
it('Should generate a new uuid on new session', () => {
const sessionProvider = new BacktraceBrowserSessionProvider();

expect(sessionProvider.sessionId).toBeDefined();
});

it('Should reuse the same sessionId', () => {
const sessionProvider1 = new BacktraceBrowserSessionProvider();
const sessionProvider2 = new BacktraceBrowserSessionProvider();
expect(sessionProvider1.sessionId).toEqual(sessionProvider2.sessionId);
});

it('Should generate a new sessionId if the lastActive timestamp is greater than persistence interval time', () => {
const fakeId = 'test';
const lastSessionActiveDate = new Date(Date.now() - BacktraceBrowserSessionProvider.PERSISTENCE_INTERVAL - 1);
localStorage.setItem('backtrace-last-active', lastSessionActiveDate.getTime().toString(10));
localStorage.setItem('backtrace-guid', fakeId);

const sessionProvider = new BacktraceBrowserSessionProvider();
expect(sessionProvider.sessionId).toBeDefined();
expect(sessionProvider.sessionId).not.toEqual(fakeId);
});

it('Should not generate a new sessionId if the lastActive timestamp is lower than persistence interval time', () => {
const fakeId = 'test';
const lastSessionActiveDate = new Date(Date.now() - BacktraceBrowserSessionProvider.PERSISTENCE_INTERVAL + 1);
localStorage.setItem('backtrace-last-active', lastSessionActiveDate.getTime().toString(10));
localStorage.setItem('backtrace-guid', fakeId);

const sessionProvider = new BacktraceBrowserSessionProvider();
expect(sessionProvider.sessionId).toBeDefined();
expect(sessionProvider.sessionId).toEqual(fakeId);
});

it('Should update timestamp', () => {
const timestamp = Date.now();
jest.spyOn(TimeHelper, 'now').mockImplementation(() => {
return timestamp;
});

localStorage.setItem('backtrace-last-active', new Date(2010, 1, 1, 1, 1, 1, 1).getTime().toString(10));
const sessionProvider = new BacktraceBrowserSessionProvider();

sessionProvider.afterMetricsSubmission();

expect(sessionProvider.lastActive).toEqual(timestamp);
});
});
30 changes: 11 additions & 19 deletions packages/node/tests/client/clientTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ describe('Client tests', () => {
postError: jest.fn().mockResolvedValue(Promise.resolve()),
};

const defaultClientOptions = {
url: 'https://submit.backtrace.io/foo/bar/baz',
metrics: {
enable: false,
},
};
let client: BacktraceClient;
it('Should create a client', () => {
client = BacktraceClient.builder({
url: 'https://submit.backtrace.io/foo/bar/baz',
}).build();
client = BacktraceClient.builder(defaultClientOptions).build();

expect(client).toBeDefined();
});

describe('Send tests', () => {
beforeEach(() => {
client = BacktraceClient.builder({
url: 'https://submit.backtrace.io/foo/bar/baz',
})
.useRequestHandler(requestHandler)
.build();
client = BacktraceClient.builder(defaultClientOptions).useRequestHandler(requestHandler).build();
});
it(`Should not throw an error when sending a message`, async () => {
expect(async () => await client.send('test')).not.toThrow();
Expand All @@ -46,10 +46,7 @@ describe('Client tests', () => {
const fileContent = fs.readFileSync(sampleFile, 'utf8');

it(`Should generate an attachment list based on the client options`, async () => {
client = BacktraceClient.builder({
url: 'https://submit.backtrace.io/foo/bar/baz',
attachments: [sampleFile],
})
client = BacktraceClient.builder({ ...defaultClientOptions, attachments: [sampleFile] })
.useRequestHandler(requestHandler)
.build();

Expand All @@ -66,7 +63,7 @@ describe('Client tests', () => {
it(`Should allow to setup bufer attachment`, async () => {
const testedBuffer = Buffer.from('test');
client = BacktraceClient.builder({
url: 'https://submit.backtrace.io/foo/bar/baz',
...defaultClientOptions,
attachments: [new BacktraceBufferAttachment('test', testedBuffer)],
})
.useRequestHandler(requestHandler)
Expand All @@ -79,12 +76,7 @@ describe('Client tests', () => {

it(`Should allow to add more attachments`, async () => {
const testedAttachment = new BacktraceFileAttachment(sampleFile);
client = BacktraceClient.builder({
url: 'https://submit.backtrace.io/foo/bar/baz',
attachments: [],
})
.useRequestHandler(requestHandler)
.build();
client = BacktraceClient.builder(defaultClientOptions).useRequestHandler(requestHandler).build();

client.attachments.push(testedAttachment);
expect(client.attachments).toBeDefined();
Expand Down
Loading