From 378c344e5d89703ac7dcf3064b79fe8c913eb2f9 Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Mon, 27 Jul 2020 23:30:11 -0400 Subject: [PATCH 01/14] Add integration for offline support Offline events are cached in the browser with localforage. --- packages/browser/package.json | 2 + packages/browser/src/integrations/index.ts | 1 + packages/browser/src/integrations/offline.ts | 100 +++++++++++++++++++ yarn.lock | 54 +++++++++- 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 packages/browser/src/integrations/offline.ts diff --git a/packages/browser/package.json b/packages/browser/package.json index a8e5bf864bbb..7ac8306756c5 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -17,8 +17,10 @@ }, "dependencies": { "@sentry/core": "5.20.1", + "@sentry/minimal": "5.20.1", "@sentry/types": "5.20.1", "@sentry/utils": "5.20.1", + "localforage": "^1.8.1", "tslib": "^1.9.3" }, "devDependencies": { diff --git a/packages/browser/src/integrations/index.ts b/packages/browser/src/integrations/index.ts index 21a076b636f8..9afd53c80f2c 100644 --- a/packages/browser/src/integrations/index.ts +++ b/packages/browser/src/integrations/index.ts @@ -3,3 +3,4 @@ export { TryCatch } from './trycatch'; export { Breadcrumbs } from './breadcrumbs'; export { LinkedErrors } from './linkederrors'; export { UserAgent } from './useragent'; +export { Offline } from './offline'; diff --git a/packages/browser/src/integrations/offline.ts b/packages/browser/src/integrations/offline.ts new file mode 100644 index 000000000000..f72181ed1c8a --- /dev/null +++ b/packages/browser/src/integrations/offline.ts @@ -0,0 +1,100 @@ +import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core'; +import { captureEvent } from '@sentry/minimal'; +import { Event, Integration } from '@sentry/types'; +import { getGlobalObject, uuid4 } from '@sentry/utils'; +// @ts-ignore +import localforage = require('localforage'); + +/** + * cache offline errors and send when connected + */ +export class Offline implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Offline'; + + /** + * @inheritDoc + */ + public readonly name: string = Offline.id; + + /** + * event cache + */ + public offlineEventStore: any; + + /** + * @inheritDoc + */ + public constructor() { + this.offlineEventStore = localforage.createInstance({ + name: 'sentry/offlineEventStore', + }); + + getGlobalObject().addEventListener('online', () => { + this._sendEvents().catch(() => { + // todo: handle localforage error + }); + }); + } + + /** + * @inheritDoc + */ + public setupOnce(): void { + addGlobalEventProcessor(async (event: Event) => { + if (getCurrentHub().getIntegration(Offline)) { + const global = getGlobalObject(); + + // cache if we are positively offline + if ('navigator' in global && 'onLine' in global.navigator && !global.navigator.onLine) { + try { + await this._cacheEvent(event); + } catch (error) { + // todo: handle localforage error + } + + // return null on success or failure, because being offline will still result in an error + return null; + } + } + + return event; + }); + } + + /** + * cache an event to send later + * @param event an event + */ + private async _cacheEvent(event: Event): Promise { + return this.offlineEventStore.setItem(uuid4(), event); + } + + /** + * purge event from cache + */ + private async _purgeEvent(cacheKey: string): Promise { + return this.offlineEventStore.removeItem(cacheKey); + } + + /** + * send all events + */ + private async _sendEvents(): Promise { + return this.offlineEventStore.iterate((event: any, cacheKey: string, _index: number) => { + try { + const newEventId = captureEvent(event); + + if (newEventId) { + this._purgeEvent(cacheKey) + .then(_ => _) + .catch(_ => _); + } + } catch (error) { + // handle JSON.parse error + } + }); + } +} diff --git a/yarn.lock b/yarn.lock index 396297e1e022..ba6aa67cf9f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1939,11 +1939,32 @@ after@0.8.2: resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= -agent-base@4, agent-base@5, agent-base@6, agent-base@^4.3.0, agent-base@~4.2.1: +agent-base@4, agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== + dependencies: + es6-promisify "^5.0.0" + +agent-base@5: version "5.1.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== +agent-base@6: + version "6.0.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" + integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg== + dependencies: + debug "4" + +agent-base@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" + agentkeepalive@^3.4.1: version "3.5.2" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67" @@ -5114,6 +5135,18 @@ es6-object-assign@^1.1.0: resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + escalade@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" @@ -6528,6 +6561,11 @@ ignore@^5.1.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" @@ -8106,6 +8144,13 @@ libnpmpublish@^1.1.1: semver "^5.5.1" ssri "^6.0.1" +lie@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" + integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= + dependencies: + immediate "~3.0.5" + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -8155,6 +8200,13 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +localforage@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.8.1.tgz#f6c0a24b41ab33b10e4dc84342dd696f6f3e3433" + integrity sha512-azSSJJfc7h4bVpi0PGi+SmLQKJl2/8NErI+LhJsrORNikMZnhaQ7rv9fHj+ofwgSHrKRlsDCL/639a6nECIKuQ== + dependencies: + lie "3.1.1" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" From 5ac1d5f6b8e7eb1280ebc1cdf22c4aeab46a9a15 Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Tue, 28 Jul 2020 21:39:08 -0400 Subject: [PATCH 02/14] Add logging statements --- packages/browser/src/integrations/offline.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/browser/src/integrations/offline.ts b/packages/browser/src/integrations/offline.ts index f72181ed1c8a..2e39d592f146 100644 --- a/packages/browser/src/integrations/offline.ts +++ b/packages/browser/src/integrations/offline.ts @@ -1,7 +1,7 @@ import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core'; import { captureEvent } from '@sentry/minimal'; import { Event, Integration } from '@sentry/types'; -import { getGlobalObject, uuid4 } from '@sentry/utils'; +import { getGlobalObject, logger, uuid4 } from '@sentry/utils'; // @ts-ignore import localforage = require('localforage'); @@ -34,7 +34,7 @@ export class Offline implements Integration { getGlobalObject().addEventListener('online', () => { this._sendEvents().catch(() => { - // todo: handle localforage error + logger.warn('could not send cached events'); }); }); } @@ -51,8 +51,8 @@ export class Offline implements Integration { if ('navigator' in global && 'onLine' in global.navigator && !global.navigator.onLine) { try { await this._cacheEvent(event); - } catch (error) { - // todo: handle localforage error + } catch (_error) { + logger.warn('could not cache event while offline'); } // return null on success or failure, because being offline will still result in an error @@ -92,8 +92,8 @@ export class Offline implements Integration { .then(_ => _) .catch(_ => _); } - } catch (error) { - // handle JSON.parse error + } catch (_error) { + logger.warn('could not send cached event'); } }); } From fc6462bfbb159d7c17bf2985d4004bfb454209b6 Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Wed, 29 Jul 2020 22:41:14 -0400 Subject: [PATCH 03/14] Move 'offline' to the integrations package --- packages/browser/package.json | 2 -- packages/browser/src/integrations/index.ts | 1 - packages/integrations/package.json | 2 ++ packages/integrations/src/index.ts | 1 + .../src/integrations => integrations/src}/offline.ts | 12 ++++++------ 5 files changed, 9 insertions(+), 9 deletions(-) rename packages/{browser/src/integrations => integrations/src}/offline.ts (88%) diff --git a/packages/browser/package.json b/packages/browser/package.json index 7ac8306756c5..a8e5bf864bbb 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -17,10 +17,8 @@ }, "dependencies": { "@sentry/core": "5.20.1", - "@sentry/minimal": "5.20.1", "@sentry/types": "5.20.1", "@sentry/utils": "5.20.1", - "localforage": "^1.8.1", "tslib": "^1.9.3" }, "devDependencies": { diff --git a/packages/browser/src/integrations/index.ts b/packages/browser/src/integrations/index.ts index 9afd53c80f2c..21a076b636f8 100644 --- a/packages/browser/src/integrations/index.ts +++ b/packages/browser/src/integrations/index.ts @@ -3,4 +3,3 @@ export { TryCatch } from './trycatch'; export { Breadcrumbs } from './breadcrumbs'; export { LinkedErrors } from './linkederrors'; export { UserAgent } from './useragent'; -export { Offline } from './offline'; diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 5d843929afe3..abe04eb42e3d 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -16,8 +16,10 @@ "module": "esm/index.js", "types": "dist/index.d.ts", "dependencies": { + "@sentry/minimal": "5.20.1", "@sentry/types": "5.20.1", "@sentry/utils": "5.20.1", + "localforage": "^1.8.1", "tslib": "^1.9.3" }, "devDependencies": { diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index c1e8d69794ad..f1ba52e92026 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -4,6 +4,7 @@ export { Debug } from './debug'; export { Dedupe } from './dedupe'; export { Ember } from './ember'; export { ExtraErrorData } from './extraerrordata'; +export { Offline } from './offline'; export { ReportingObserver } from './reportingobserver'; export { RewriteFrames } from './rewriteframes'; export { SessionTiming } from './sessiontiming'; diff --git a/packages/browser/src/integrations/offline.ts b/packages/integrations/src/offline.ts similarity index 88% rename from packages/browser/src/integrations/offline.ts rename to packages/integrations/src/offline.ts index 2e39d592f146..1de29b45d61a 100644 --- a/packages/browser/src/integrations/offline.ts +++ b/packages/integrations/src/offline.ts @@ -1,14 +1,14 @@ -import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core'; import { captureEvent } from '@sentry/minimal'; -import { Event, Integration } from '@sentry/types'; +import { Event, EventProcessor, Hub, Integration } from '@sentry/types'; import { getGlobalObject, logger, uuid4 } from '@sentry/utils'; -// @ts-ignore -import localforage = require('localforage'); +import localforage from 'localforage'; /** * cache offline errors and send when connected */ export class Offline implements Integration { + // tslint:disable: no-unsafe-any + /** * @inheritDoc */ @@ -42,7 +42,7 @@ export class Offline implements Integration { /** * @inheritDoc */ - public setupOnce(): void { + public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { addGlobalEventProcessor(async (event: Event) => { if (getCurrentHub().getIntegration(Offline)) { const global = getGlobalObject(); @@ -83,7 +83,7 @@ export class Offline implements Integration { * send all events */ private async _sendEvents(): Promise { - return this.offlineEventStore.iterate((event: any, cacheKey: string, _index: number) => { + return this.offlineEventStore.iterate((event: any, cacheKey: string, _index: number): void => { try { const newEventId = captureEvent(event); From 26dd7b0ed5fa8e694265e73cc2b5f16495b28cd9 Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Wed, 29 Jul 2020 22:51:13 -0400 Subject: [PATCH 04/14] Revert to import assignment I thought this build was passing, but it looks like there is not an easy way to use a standard import for localforage in this library. --- packages/integrations/src/offline.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/integrations/src/offline.ts b/packages/integrations/src/offline.ts index 1de29b45d61a..7aa7ed06cb85 100644 --- a/packages/integrations/src/offline.ts +++ b/packages/integrations/src/offline.ts @@ -1,7 +1,8 @@ import { captureEvent } from '@sentry/minimal'; import { Event, EventProcessor, Hub, Integration } from '@sentry/types'; import { getGlobalObject, logger, uuid4 } from '@sentry/utils'; -import localforage from 'localforage'; +// @ts-ignore +import localforage = require('localforage'); /** * cache offline errors and send when connected From b38a7af07e364aeada693dbb59bbe42db236cb9a Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Thu, 30 Jul 2020 12:38:02 -0400 Subject: [PATCH 05/14] Use Hub.captureEvent instead of adding @sentry/minimal --- packages/integrations/package.json | 1 - packages/integrations/src/offline.ts | 24 +++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/integrations/package.json b/packages/integrations/package.json index abe04eb42e3d..4f08546071cc 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -16,7 +16,6 @@ "module": "esm/index.js", "types": "dist/index.d.ts", "dependencies": { - "@sentry/minimal": "5.20.1", "@sentry/types": "5.20.1", "@sentry/utils": "5.20.1", "localforage": "^1.8.1", diff --git a/packages/integrations/src/offline.ts b/packages/integrations/src/offline.ts index 7aa7ed06cb85..bc6280eb3661 100644 --- a/packages/integrations/src/offline.ts +++ b/packages/integrations/src/offline.ts @@ -1,4 +1,3 @@ -import { captureEvent } from '@sentry/minimal'; import { Event, EventProcessor, Hub, Integration } from '@sentry/types'; import { getGlobalObject, logger, uuid4 } from '@sentry/utils'; // @ts-ignore @@ -20,6 +19,11 @@ export class Offline implements Integration { */ public readonly name: string = Offline.id; + /** + * the current hub instance + */ + public hub?: Hub; + /** * event cache */ @@ -44,8 +48,10 @@ export class Offline implements Integration { * @inheritDoc */ public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { + this.hub = getCurrentHub(); + addGlobalEventProcessor(async (event: Event) => { - if (getCurrentHub().getIntegration(Offline)) { + if (this.hub && this.hub.getIntegration(Offline)) { const global = getGlobalObject(); // cache if we are positively offline @@ -86,12 +92,16 @@ export class Offline implements Integration { private async _sendEvents(): Promise { return this.offlineEventStore.iterate((event: any, cacheKey: string, _index: number): void => { try { - const newEventId = captureEvent(event); + if (this.hub) { + const newEventId = this.hub.captureEvent(event); - if (newEventId) { - this._purgeEvent(cacheKey) - .then(_ => _) - .catch(_ => _); + if (newEventId) { + this._purgeEvent(cacheKey) + .then(_ => _) + .catch(_ => _); + } + } else { + logger.warn('no hub found - could not send cached event'); } } catch (_error) { logger.warn('could not send cached event'); From f122fb5c88c90c8ce1082c3a61385679a17e5468 Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Thu, 30 Jul 2020 14:34:33 -0400 Subject: [PATCH 06/14] Use localforage types --- packages/integrations/src/offline.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/integrations/src/offline.ts b/packages/integrations/src/offline.ts index bc6280eb3661..d68f82896107 100644 --- a/packages/integrations/src/offline.ts +++ b/packages/integrations/src/offline.ts @@ -7,8 +7,6 @@ import localforage = require('localforage'); * cache offline errors and send when connected */ export class Offline implements Integration { - // tslint:disable: no-unsafe-any - /** * @inheritDoc */ @@ -27,7 +25,7 @@ export class Offline implements Integration { /** * event cache */ - public offlineEventStore: any; + public offlineEventStore: LocalForage; // type imported from localforage /** * @inheritDoc @@ -37,7 +35,7 @@ export class Offline implements Integration { name: 'sentry/offlineEventStore', }); - getGlobalObject().addEventListener('online', () => { + getGlobalObject().addEventListener('online', (): void => { this._sendEvents().catch(() => { logger.warn('could not send cached events'); }); @@ -76,7 +74,7 @@ export class Offline implements Integration { * @param event an event */ private async _cacheEvent(event: Event): Promise { - return this.offlineEventStore.setItem(uuid4(), event); + return this.offlineEventStore.setItem(uuid4(), event); } /** @@ -90,15 +88,15 @@ export class Offline implements Integration { * send all events */ private async _sendEvents(): Promise { - return this.offlineEventStore.iterate((event: any, cacheKey: string, _index: number): void => { + return this.offlineEventStore.iterate((event: Event, cacheKey: string, _index: number): void => { try { if (this.hub) { const newEventId = this.hub.captureEvent(event); if (newEventId) { - this._purgeEvent(cacheKey) - .then(_ => _) - .catch(_ => _); + this._purgeEvent(cacheKey).catch((_error: any): void => { + logger.warn('could not purge event from cache'); + }); } } else { logger.warn('no hub found - could not send cached event'); From 47cbc4c305c43192a2a2ebaf9eeac9af5e16bc9e Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Thu, 30 Jul 2020 18:06:25 -0400 Subject: [PATCH 07/14] Remove async/await syntax to keep bundle size down --- packages/integrations/src/offline.ts | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/integrations/src/offline.ts b/packages/integrations/src/offline.ts index d68f82896107..a62142dab629 100644 --- a/packages/integrations/src/offline.ts +++ b/packages/integrations/src/offline.ts @@ -48,17 +48,15 @@ export class Offline implements Integration { public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { this.hub = getCurrentHub(); - addGlobalEventProcessor(async (event: Event) => { + addGlobalEventProcessor((event: Event) => { if (this.hub && this.hub.getIntegration(Offline)) { const global = getGlobalObject(); // cache if we are positively offline if ('navigator' in global && 'onLine' in global.navigator && !global.navigator.onLine) { - try { - await this._cacheEvent(event); - } catch (_error) { + this._cacheEvent(event).catch((_error: any) => { logger.warn('could not cache event while offline'); - } + }); // return null on success or failure, because being offline will still result in an error return null; @@ -89,20 +87,16 @@ export class Offline implements Integration { */ private async _sendEvents(): Promise { return this.offlineEventStore.iterate((event: Event, cacheKey: string, _index: number): void => { - try { - if (this.hub) { - const newEventId = this.hub.captureEvent(event); + if (this.hub) { + const newEventId = this.hub.captureEvent(event); - if (newEventId) { - this._purgeEvent(cacheKey).catch((_error: any): void => { - logger.warn('could not purge event from cache'); - }); - } - } else { - logger.warn('no hub found - could not send cached event'); + if (newEventId) { + this._purgeEvent(cacheKey).catch((_error: any): void => { + logger.warn('could not purge event from cache'); + }); } - } catch (_error) { - logger.warn('could not send cached event'); + } else { + logger.warn('no hub found - could not send cached event'); } }); } From 1df5a02946fcabb8bc67f1278da0cd6a813ab694 Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Sun, 2 Aug 2020 20:48:03 -0400 Subject: [PATCH 08/14] Check for addEventListener in global --- packages/integrations/src/offline.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/integrations/src/offline.ts b/packages/integrations/src/offline.ts index a62142dab629..8653eca3ee7a 100644 --- a/packages/integrations/src/offline.ts +++ b/packages/integrations/src/offline.ts @@ -35,11 +35,15 @@ export class Offline implements Integration { name: 'sentry/offlineEventStore', }); - getGlobalObject().addEventListener('online', (): void => { - this._sendEvents().catch(() => { - logger.warn('could not send cached events'); + const global = getGlobalObject(); + + if ('addEventListener' in global) { + global.addEventListener('online', (): void => { + this._sendEvents().catch(() => { + logger.warn('could not send cached events'); + }); }); - }); + } } /** From de32dc3dad6e8fab9efb94f61e85c6edcaacbedb Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Sun, 2 Aug 2020 20:48:17 -0400 Subject: [PATCH 09/14] Add tests for Offline integration --- packages/integrations/test/offline.test.ts | 133 +++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 packages/integrations/test/offline.test.ts diff --git a/packages/integrations/test/offline.test.ts b/packages/integrations/test/offline.test.ts new file mode 100644 index 000000000000..1597d1bebce3 --- /dev/null +++ b/packages/integrations/test/offline.test.ts @@ -0,0 +1,133 @@ +import { Event, EventProcessor, Hub, Integration } from '@sentry/types'; +import * as utils from '@sentry/utils'; + +import { Offline } from '../src/offline'; + +// mock localforage methods +jest.mock('localforage', () => ({ + createInstance(_options: object): any { + let items: object[] = []; + + return { + async getItem(key: string): object { + return items.find(item => item.key === key); + }, + async iterate(callback: () => void): void { + items.forEach((item, index) => { + callback(item.value, item.key, index); + }); + }, + async length(): number { + return items.length; + }, + async removeItem(key: string): void { + items = items.filter(item => item.key !== key); + }, + async setItem(key: string, value: Event): void { + items.push({ + key, + value, + }); + }, + }; + }, +})); + +// mock sentry utils +jest.mock('@sentry/utils'); + +let integration: Integration; +let online: boolean; + +describe('Offline', () => { + describe('when app is online', () => { + beforeEach(() => { + online = true; + + setupIntegration(); + processEvents(); + }); + + it('does not store events in offline store', async () => { + expect(await integration.offlineEventStore.length()).toEqual(0); + }); + }); + + describe('when app is offline', () => { + beforeEach(() => { + online = false; + + setupIntegration(); + processEvents(); + }); + + it('stores events in offline store', async () => { + expect(await integration.offlineEventStore.length()).toEqual(1); + }); + + describe('when connectivity is restored', () => { + it('sends stored events', async done => { + processEventListeners(); + + expect(await integration.offlineEventStore.length()).toEqual(0); + + setImmediate(done); + }); + }); + }); +}); + +let eventListeners: any[]; +let eventProcessors: EventProcessor[]; + +/** JSDoc */ +function addGlobalEventProcessor(callback: () => void): void { + eventProcessors.push(callback); +} + +/** JSDoc */ +function getCurrentHub(): Hub { + return { + captureEvent(_event: Event): string { + return 'an-event-id'; + }, + getIntegration(_integration: Integration): any { + // pretend integration is enabled + return true; + }, + }; +} + +/** JSDoc */ +function processEventListeners(): void { + eventListeners.forEach(listener => { + listener(); + }); +} + +/** JSDoc */ +function processEvents(): void { + eventProcessors.forEach(processor => { + processor({ + message: 'There was an error!', + }); + }); +} + +/** JSDoc */ +function setupIntegration(): void { + eventListeners = []; + eventProcessors = []; + + utils.getGlobalObject.mockImplementation(() => ({ + addEventListener: (_windowEvent, callback) => { + eventListeners.push(callback); + }, + navigator: { + onLine: online, + }, + })); + + integration = new Offline(); + integration.setupOnce(addGlobalEventProcessor, getCurrentHub); +} From 3146924db088359848f4f3cf650741c06c53230c Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Mon, 3 Aug 2020 23:09:11 -0400 Subject: [PATCH 10/14] Fixing lockfile after migrating to @sentry/integrations --- yarn.lock | 41 ++++------------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/yarn.lock b/yarn.lock index ba6aa67cf9f3..1f5bd3c1daa7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1939,32 +1939,11 @@ after@0.8.2: resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= -agent-base@4, agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - -agent-base@5: +agent-base@4, agent-base@5, agent-base@6, agent-base@^4.3.0, agent-base@~4.2.1: version "5.1.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== -agent-base@6: - version "6.0.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" - integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg== - dependencies: - debug "4" - -agent-base@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== - dependencies: - es6-promisify "^5.0.0" - agentkeepalive@^3.4.1: version "3.5.2" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67" @@ -5135,18 +5114,6 @@ es6-object-assign@^1.1.0: resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - escalade@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" @@ -8201,9 +8168,9 @@ loader-utils@^2.0.0: json5 "^2.1.2" localforage@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.8.1.tgz#f6c0a24b41ab33b10e4dc84342dd696f6f3e3433" - integrity sha512-azSSJJfc7h4bVpi0PGi+SmLQKJl2/8NErI+LhJsrORNikMZnhaQ7rv9fHj+ofwgSHrKRlsDCL/639a6nECIKuQ== + version "1.9.0" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1" + integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g== dependencies: lie "3.1.1" From efbee73a3a837ff42e5b19a2c5bd506077b1e813 Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Mon, 3 Aug 2020 23:23:40 -0400 Subject: [PATCH 11/14] Add TS compiler flag to allow import of localforage --- packages/integrations/src/offline.ts | 4 ++-- packages/integrations/tsconfig.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/integrations/src/offline.ts b/packages/integrations/src/offline.ts index 8653eca3ee7a..6a2a8c4c3488 100644 --- a/packages/integrations/src/offline.ts +++ b/packages/integrations/src/offline.ts @@ -1,7 +1,7 @@ import { Event, EventProcessor, Hub, Integration } from '@sentry/types'; import { getGlobalObject, logger, uuid4 } from '@sentry/utils'; -// @ts-ignore -import localforage = require('localforage'); +// @ts-ignore: Module '"localforage"' has no default export. +import localforage from 'localforage'; /** * cache offline errors and send when connected diff --git a/packages/integrations/tsconfig.json b/packages/integrations/tsconfig.json index 6564f8267130..8cbf422e8cd9 100644 --- a/packages/integrations/tsconfig.json +++ b/packages/integrations/tsconfig.json @@ -3,6 +3,7 @@ "include": ["src/**/*.ts", "test/**/*.ts"], "exclude": ["dist"], "compilerOptions": { + "esModuleInterop": true, "declarationMap": false, "rootDir": ".", "types": ["node", "jest"] From d758d229de48d2aa78af8339cb25f153f0409034 Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Tue, 4 Aug 2020 10:13:15 -0400 Subject: [PATCH 12/14] Handle scenario where app has stored events from previous offline session --- packages/integrations/src/offline.ts | 23 +++++--- packages/integrations/test/offline.test.ts | 63 ++++++++++++++++------ 2 files changed, 62 insertions(+), 24 deletions(-) diff --git a/packages/integrations/src/offline.ts b/packages/integrations/src/offline.ts index 6a2a8c4c3488..aa796631f319 100644 --- a/packages/integrations/src/offline.ts +++ b/packages/integrations/src/offline.ts @@ -17,6 +17,11 @@ export class Offline implements Integration { */ public readonly name: string = Offline.id; + /** + * the global instance + */ + public global: Window; + /** * the current hub instance */ @@ -31,14 +36,13 @@ export class Offline implements Integration { * @inheritDoc */ public constructor() { + this.global = getGlobalObject(); this.offlineEventStore = localforage.createInstance({ name: 'sentry/offlineEventStore', }); - const global = getGlobalObject(); - - if ('addEventListener' in global) { - global.addEventListener('online', (): void => { + if ('addEventListener' in this.global) { + this.global.addEventListener('online', (): void => { this._sendEvents().catch(() => { logger.warn('could not send cached events'); }); @@ -54,10 +58,8 @@ export class Offline implements Integration { addGlobalEventProcessor((event: Event) => { if (this.hub && this.hub.getIntegration(Offline)) { - const global = getGlobalObject(); - // cache if we are positively offline - if ('navigator' in global && 'onLine' in global.navigator && !global.navigator.onLine) { + if ('navigator' in this.global && 'onLine' in this.global.navigator && !this.global.navigator.onLine) { this._cacheEvent(event).catch((_error: any) => { logger.warn('could not cache event while offline'); }); @@ -69,6 +71,13 @@ export class Offline implements Integration { return event; }); + + // if online now, send any events stored in a previous offline session + if ('navigator' in this.global && 'onLine' in this.global.navigator && this.global.navigator.onLine) { + this._sendEvents().catch(() => { + logger.warn('could not send cached events'); + }); + } } /** diff --git a/packages/integrations/test/offline.test.ts b/packages/integrations/test/offline.test.ts index 1597d1bebce3..c67ad2af7b0e 100644 --- a/packages/integrations/test/offline.test.ts +++ b/packages/integrations/test/offline.test.ts @@ -44,20 +44,45 @@ describe('Offline', () => { beforeEach(() => { online = true; - setupIntegration(); - processEvents(); + initIntegration(); }); it('does not store events in offline store', async () => { + setupOnce(); + processEvents(); + expect(await integration.offlineEventStore.length()).toEqual(0); }); + + describe('when there are already events in the cache from a previous offline session', () => { + beforeEach(done => { + const event = { message: 'previous event' }; + + integration.offlineEventStore + .setItem('previous', event) + .then(() => { + done(); + }) + .catch(error => error); + }); + + it('sends stored events', async () => { + expect(await integration.offlineEventStore.length()).toEqual(1); + + setupOnce(); + processEvents(); + + expect(await integration.offlineEventStore.length()).toEqual(0); + }); + }); }); describe('when app is offline', () => { beforeEach(() => { online = false; - setupIntegration(); + initIntegration(); + setupOnce(); processEvents(); }); @@ -98,6 +123,23 @@ function getCurrentHub(): Hub { }; } +/** JSDoc */ +function initIntegration(): void { + eventListeners = []; + eventProcessors = []; + + utils.getGlobalObject.mockImplementation(() => ({ + addEventListener: (_windowEvent, callback) => { + eventListeners.push(callback); + }, + navigator: { + onLine: online, + }, + })); + + integration = new Offline(); +} + /** JSDoc */ function processEventListeners(): void { eventListeners.forEach(listener => { @@ -115,19 +157,6 @@ function processEvents(): void { } /** JSDoc */ -function setupIntegration(): void { - eventListeners = []; - eventProcessors = []; - - utils.getGlobalObject.mockImplementation(() => ({ - addEventListener: (_windowEvent, callback) => { - eventListeners.push(callback); - }, - navigator: { - onLine: online, - }, - })); - - integration = new Offline(); +function setupOnce(): void { integration.setupOnce(addGlobalEventProcessor, getCurrentHub); } From bd3ce31b7c35a7fdaca5bbf04c6b77c73a2f6a3b Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Sat, 22 Aug 2020 13:53:34 -0400 Subject: [PATCH 13/14] Add config for maxStoredEvents with a default limit of 30 --- packages/integrations/src/offline.ts | 53 +++++++++++++-- packages/integrations/test/offline.test.ts | 75 +++++++++++++++++++--- 2 files changed, 113 insertions(+), 15 deletions(-) diff --git a/packages/integrations/src/offline.ts b/packages/integrations/src/offline.ts index aa796631f319..d42c4cefbd08 100644 --- a/packages/integrations/src/offline.ts +++ b/packages/integrations/src/offline.ts @@ -27,6 +27,11 @@ export class Offline implements Integration { */ public hub?: Hub; + /** + * maximum number of events to store while offline + */ + public maxStoredEvents: number; + /** * event cache */ @@ -35,14 +40,15 @@ export class Offline implements Integration { /** * @inheritDoc */ - public constructor() { + public constructor(options: { maxStoredEvents?: number } = {}) { this.global = getGlobalObject(); + this.maxStoredEvents = options.maxStoredEvents || 30; // set a reasonable default this.offlineEventStore = localforage.createInstance({ name: 'sentry/offlineEventStore', }); if ('addEventListener' in this.global) { - this.global.addEventListener('online', (): void => { + this.global.addEventListener('online', () => { this._sendEvents().catch(() => { logger.warn('could not send cached events'); }); @@ -60,9 +66,11 @@ export class Offline implements Integration { if (this.hub && this.hub.getIntegration(Offline)) { // cache if we are positively offline if ('navigator' in this.global && 'onLine' in this.global.navigator && !this.global.navigator.onLine) { - this._cacheEvent(event).catch((_error: any) => { - logger.warn('could not cache event while offline'); - }); + this._cacheEvent(event) + .then((_event: Event): Promise => this._enforceMaxEvents()) + .catch((_error: any): void => { + logger.warn('could not cache event while offline'); + }); // return null on success or failure, because being offline will still result in an error return null; @@ -88,6 +96,33 @@ export class Offline implements Integration { return this.offlineEventStore.setItem(uuid4(), event); } + /** + * purge excess events if necessary + */ + private async _enforceMaxEvents(): Promise { + const events: Array<{ event: Event; cacheKey: string }> = []; + + return this.offlineEventStore + .iterate((event: Event, cacheKey: string, _index: number): void => { + // aggregate events + events.push({ cacheKey, event }); + }) + .then( + (): Promise => + // this promise resolves when the iteration is finished + this._purgeEvents( + // purge all events past maxStoredEvents in reverse chronological order + events + .sort((a, b) => (b.event.timestamp || 0) - (a.event.timestamp || 0)) + .slice(this.maxStoredEvents < events.length ? this.maxStoredEvents : events.length) + .map(event => event.cacheKey), + ), + ) + .catch((_error: any): void => { + logger.warn('could not enforce max events'); + }); + } + /** * purge event from cache */ @@ -95,6 +130,14 @@ export class Offline implements Integration { return this.offlineEventStore.removeItem(cacheKey); } + /** + * purge events from cache + */ + private async _purgeEvents(cacheKeys: string[]): Promise { + // trail with .then to ensure the return type as void and not void|void[] + return Promise.all(cacheKeys.map(cacheKey => this._purgeEvent(cacheKey))).then(); + } + /** * send all events */ diff --git a/packages/integrations/test/offline.test.ts b/packages/integrations/test/offline.test.ts index c67ad2af7b0e..a9cce78c1edf 100644 --- a/packages/integrations/test/offline.test.ts +++ b/packages/integrations/test/offline.test.ts @@ -33,9 +33,6 @@ jest.mock('localforage', () => ({ }, })); -// mock sentry utils -jest.mock('@sentry/utils'); - let integration: Integration; let online: boolean; @@ -80,18 +77,64 @@ describe('Offline', () => { describe('when app is offline', () => { beforeEach(() => { online = false; + }); + it('stores events in offline store', async () => { initIntegration(); setupOnce(); + prepopulateEvents(1); processEvents(); - }); - it('stores events in offline store', async () => { expect(await integration.offlineEventStore.length()).toEqual(1); }); + it('enforces a default of 30 maxStoredEvents', done => { + initIntegration(); + setupOnce(); + prepopulateEvents(50); + processEvents(); + + setImmediate(async () => { + // allow background promises to finish resolving + expect(await integration.offlineEventStore.length()).toEqual(30); + done(); + }); + }); + + it('does not purge events when below the maxStoredEvents threshold', done => { + initIntegration(); + setupOnce(); + prepopulateEvents(5); + processEvents(); + + setImmediate(async () => { + // allow background promises to finish resolving + expect(await integration.offlineEventStore.length()).toEqual(5); + done(); + }); + }); + + describe('when maxStoredEvents is supplied', () => { + it('respects the configuration', done => { + initIntegration({ maxStoredEvents: 5 }); + setupOnce(); + prepopulateEvents(50); + processEvents(); + + setImmediate(async () => { + // allow background promises to finish resolving + expect(await integration.offlineEventStore.length()).toEqual(5); + done(); + }); + }); + }); + describe('when connectivity is restored', () => { it('sends stored events', async done => { + initIntegration(); + setupOnce(); + prepopulateEvents(1); + processEvents(); processEventListeners(); expect(await integration.offlineEventStore.length()).toEqual(0); @@ -104,6 +147,7 @@ describe('Offline', () => { let eventListeners: any[]; let eventProcessors: EventProcessor[]; +let events: Event[]; /** JSDoc */ function addGlobalEventProcessor(callback: () => void): void { @@ -124,11 +168,12 @@ function getCurrentHub(): Hub { } /** JSDoc */ -function initIntegration(): void { +function initIntegration(options: { maxStoredEvents?: number } = {}): void { eventListeners = []; eventProcessors = []; + events = []; - utils.getGlobalObject.mockImplementation(() => ({ + utils.getGlobalObject = jest.fn(() => ({ addEventListener: (_windowEvent, callback) => { eventListeners.push(callback); }, @@ -137,7 +182,17 @@ function initIntegration(): void { }, })); - integration = new Offline(); + integration = new Offline(options); +} + +/** JSDoc */ +function prepopulateEvents(count: number = 1): void { + for (let i = 0; i < count; i++) { + events.push({ + message: 'There was an error!', + timestamp: new Date().getTime(), + }); + } } /** JSDoc */ @@ -150,8 +205,8 @@ function processEventListeners(): void { /** JSDoc */ function processEvents(): void { eventProcessors.forEach(processor => { - processor({ - message: 'There was an error!', + events.forEach(event => { + processor(event); }); }); } From 83e4f07958a1a47937428019b53b8e03c5fa99ab Mon Sep 17 00:00:00 2001 From: "David R. Myers" Date: Mon, 24 Aug 2020 12:12:12 -0400 Subject: [PATCH 14/14] Address new linter rules --- packages/integrations/src/offline.ts | 54 +++++++++++++--------- packages/integrations/test/offline.test.ts | 8 ++-- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/packages/integrations/src/offline.ts b/packages/integrations/src/offline.ts index d42c4cefbd08..cd8c7ff9fa55 100644 --- a/packages/integrations/src/offline.ts +++ b/packages/integrations/src/offline.ts @@ -68,9 +68,11 @@ export class Offline implements Integration { if ('navigator' in this.global && 'onLine' in this.global.navigator && !this.global.navigator.onLine) { this._cacheEvent(event) .then((_event: Event): Promise => this._enforceMaxEvents()) - .catch((_error: any): void => { - logger.warn('could not cache event while offline'); - }); + .catch( + (_error): void => { + logger.warn('could not cache event while offline'); + }, + ); // return null on success or failure, because being offline will still result in an error return null; @@ -103,10 +105,12 @@ export class Offline implements Integration { const events: Array<{ event: Event; cacheKey: string }> = []; return this.offlineEventStore - .iterate((event: Event, cacheKey: string, _index: number): void => { - // aggregate events - events.push({ cacheKey, event }); - }) + .iterate( + (event: Event, cacheKey: string, _index: number): void => { + // aggregate events + events.push({ cacheKey, event }); + }, + ) .then( (): Promise => // this promise resolves when the iteration is finished @@ -118,9 +122,11 @@ export class Offline implements Integration { .map(event => event.cacheKey), ), ) - .catch((_error: any): void => { - logger.warn('could not enforce max events'); - }); + .catch( + (_error): void => { + logger.warn('could not enforce max events'); + }, + ); } /** @@ -142,18 +148,22 @@ export class Offline implements Integration { * send all events */ private async _sendEvents(): Promise { - return this.offlineEventStore.iterate((event: Event, cacheKey: string, _index: number): void => { - if (this.hub) { - const newEventId = this.hub.captureEvent(event); - - if (newEventId) { - this._purgeEvent(cacheKey).catch((_error: any): void => { - logger.warn('could not purge event from cache'); - }); + return this.offlineEventStore.iterate( + (event: Event, cacheKey: string, _index: number): void => { + if (this.hub) { + const newEventId = this.hub.captureEvent(event); + + if (newEventId) { + this._purgeEvent(cacheKey).catch( + (_error): void => { + logger.warn('could not purge event from cache'); + }, + ); + } + } else { + logger.warn('no hub found - could not send cached event'); } - } else { - logger.warn('no hub found - could not send cached event'); - } - }); + }, + ); } } diff --git a/packages/integrations/test/offline.test.ts b/packages/integrations/test/offline.test.ts index a9cce78c1edf..b88f24f5b339 100644 --- a/packages/integrations/test/offline.test.ts +++ b/packages/integrations/test/offline.test.ts @@ -5,11 +5,11 @@ import { Offline } from '../src/offline'; // mock localforage methods jest.mock('localforage', () => ({ - createInstance(_options: object): any { - let items: object[] = []; + createInstance(_options: { name: string }): any { + let items: { key: string; value: Event }[] = []; return { - async getItem(key: string): object { + async getItem(key: string): Event { return items.find(item => item.key === key); }, async iterate(callback: () => void): void { @@ -206,7 +206,7 @@ function processEventListeners(): void { function processEvents(): void { eventProcessors.forEach(processor => { events.forEach(event => { - processor(event); + processor(event) as Event | null; }); }); }