Skip to content

Commit 53dd061

Browse files
Add an integration for offline errors
To handle errors occuring offline in JavaScript this integration saves them in a queue and processes these queued up errors when back online. As a storage API localForage (https://github.com/localForage/localForage) is used. A new function listens to the 'online' event to start draining the queue. I am missing correct error handling and tests. Also I am not sure about the correct way to include the interation to sdk.ts. Building .min and .es6 files is also not working with 'yarn build'. Minifing with uglify-js works. Thanks especially to @kamilogorek and @ seromenho Resolves: getsentry#1633 See also: getsentry#1665 getsentry#279
1 parent f71c174 commit 53dd061

File tree

8 files changed

+152
-6
lines changed

8 files changed

+152
-6
lines changed

packages/browser/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"@sentry/core": "5.6.2",
2020
"@sentry/types": "5.6.1",
2121
"@sentry/utils": "5.6.1",
22-
"tslib": "^1.9.3"
22+
"tslib": "^1.9.3",
23+
"localforage": "1.7.3"
2324
},
2425
"devDependencies": {
2526
"@types/md5": "2.1.33",

packages/browser/src/integrations/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export { TryCatch } from './trycatch';
33
export { Breadcrumbs } from './breadcrumbs';
44
export { LinkedErrors } from './linkederrors';
55
export { UserAgent } from './useragent';
6+
export { Offline } from './offline';
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core';
2+
import { Event, Integration } from '@sentry/types';
3+
import localForage from 'localforage';
4+
5+
import { captureEvent } from '../index';
6+
7+
/**
8+
* store errors occuring offline and send them when online again
9+
*/
10+
export class Offline implements Integration {
11+
/**
12+
* @inheritDoc
13+
*/
14+
public readonly name: string = Offline.id;
15+
16+
/**
17+
* @inheritDoc
18+
*/
19+
public static id: string = 'Offline';
20+
21+
/**
22+
* the key to store the offline event queue
23+
*/
24+
private readonly _storrageKey: string = 'offlineEventStore';
25+
26+
public offlineEventStore: LocalForage;
27+
28+
/**
29+
* @inheritDoc
30+
*/
31+
public setupOnce(): void {
32+
addGlobalEventProcessor(async (event: Event) => {
33+
const self = getCurrentHub().getIntegration(Offline);
34+
if (self) {
35+
if (navigator.onLine) {
36+
return event;
37+
}
38+
await this._storeEvent(event);
39+
return null;
40+
// self._storeEvent(event);
41+
}
42+
return event;
43+
});
44+
}
45+
46+
public constructor() {
47+
this.offlineEventStore = localForage.createInstance({
48+
name: 'sentryOfflineEventStore',
49+
});
50+
window.addEventListener('online', () => {
51+
this._drainQueue().catch(function(): void {
52+
// TODO: handle rejected promise
53+
});
54+
});
55+
}
56+
57+
/**
58+
* store an event
59+
* @param event an event
60+
*/
61+
private async _storeEvent(event: Event): Promise<void> {
62+
const storrageKey = this._storrageKey;
63+
const offlineEventStore = this.offlineEventStore;
64+
const promise: Promise<void> = new Promise(async function(resolve: () => void, reject: () => void): Promise<void> {
65+
let queue: Event[] = [];
66+
const value = await offlineEventStore.getItem(storrageKey);
67+
// .then(function(value: unknown): void {
68+
// })
69+
// .catch(function(err: Error): void {
70+
// console.log(err);
71+
// });
72+
if (typeof value === 'string') {
73+
queue = JSON.parse(value);
74+
}
75+
queue.push(event);
76+
await offlineEventStore.setItem(storrageKey, JSON.stringify(queue)).catch(function(): void {
77+
// reject promise because saving to the localForge store did not work
78+
reject();
79+
});
80+
resolve();
81+
});
82+
return promise;
83+
}
84+
85+
/**
86+
* capture all events in the queue
87+
*/
88+
private async _drainQueue(): Promise<void> {
89+
const storrageKey = this._storrageKey;
90+
const offlineEventStore = this.offlineEventStore;
91+
const promise: Promise<void> = new Promise(async function(resolve: () => void, reject: () => void): Promise<void> {
92+
let queue: Event[] = [];
93+
// get queue
94+
const value = await offlineEventStore.getItem(storrageKey).catch(function(): void {
95+
// could not get queue from localForge, TODO: how to handle error?
96+
});
97+
// TODO: check if value in localForge can be converted with JSON.parse
98+
if (typeof value === 'string') {
99+
queue = JSON.parse(value);
100+
}
101+
await offlineEventStore.removeItem(storrageKey).catch(function(): void {
102+
// could not remove queue from localForge
103+
reject();
104+
});
105+
// process all events in the queue
106+
while (queue.length > 0) {
107+
const event = queue.pop();
108+
if (typeof event !== 'undefined') {
109+
captureEvent(event);
110+
}
111+
}
112+
resolve();
113+
});
114+
return promise;
115+
}
116+
}

packages/browser/src/sdk.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getGlobalObject } from '@sentry/utils';
44
import { BrowserOptions } from './backend';
55
import { BrowserClient, ReportDialogOptions } from './client';
66
import { wrap as internalWrap } from './helpers';
7-
import { Breadcrumbs, GlobalHandlers, LinkedErrors, TryCatch, UserAgent } from './integrations';
7+
import { Breadcrumbs, GlobalHandlers, LinkedErrors, TryCatch, UserAgent, Offline } from './integrations';
88

99
export const defaultIntegrations = [
1010
new CoreIntegrations.InboundFilters(),
@@ -14,6 +14,7 @@ export const defaultIntegrations = [
1414
new GlobalHandlers(),
1515
new LinkedErrors(),
1616
new UserAgent(),
17+
new Offline(),
1718
];
1819

1920
/**

packages/browser/tsconfig.build.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
"extends": "../../tsconfig.json",
33
"compilerOptions": {
44
"baseUrl": ".",
5-
"outDir": "dist"
5+
"outDir": "./dist"
66
},
7-
"include": ["src/**/*"]
7+
"include": ["src/**/*"],
8+
"exclude": [
9+
"build/**/*",
10+
"dist/**/*"
11+
]
812
}

packages/browser/tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"exclude": ["dist"],
55
"compilerOptions": {
66
"rootDir": ".",
7-
"types": ["node", "mocha", "chai", "sinon"]
7+
"types": ["node", "mocha", "chai", "sinon"],
8+
"esModuleInterop": true,
9+
"allowSyntheticDefaultImports": true
810
}
911
}

tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"@sentry/*": ["*/src"],
88
"raven-js": ["raven-js/src/singleton.js"],
99
"raven-node": ["raven-node/lib/client.js"]
10-
}
10+
},
11+
"allowSyntheticDefaultImports": true,
12+
"esModuleInterop": true
1113
}
1214
}

yarn.lock

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5490,6 +5490,11 @@ ignore@^3.3.5:
54905490
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
54915491
integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==
54925492

5493+
immediate@~3.0.5:
5494+
version "3.0.6"
5495+
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
5496+
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
5497+
54935498
import-fresh@^2.0.0:
54945499
version "2.0.0"
54955500
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
@@ -6940,6 +6945,13 @@ libnpmpublish@^1.1.1:
69406945
semver "^5.5.1"
69416946
ssri "^6.0.1"
69426947

6948+
6949+
version "3.1.1"
6950+
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
6951+
integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
6952+
dependencies:
6953+
immediate "~3.0.5"
6954+
69436955
load-json-file@^1.0.0:
69446956
version "1.1.0"
69456957
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -6973,6 +6985,13 @@ loader-utils@^1.1.0:
69736985
emojis-list "^2.0.0"
69746986
json5 "^0.5.0"
69756987

6988+
6989+
version "1.7.3"
6990+
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.7.3.tgz#0082b3ca9734679e1bd534995bdd3b24cf10f204"
6991+
integrity sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==
6992+
dependencies:
6993+
lie "3.1.1"
6994+
69766995
locate-path@^2.0.0:
69776996
version "2.0.0"
69786997
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"

0 commit comments

Comments
 (0)