UNPKG

4.62 kBJavaScriptView Raw
1import { logger, uuid4, normalize, GLOBAL_OBJ } from '@sentry/utils';
2import localForage from 'localforage';
3
4const WINDOW = GLOBAL_OBJ ;
5
6/**
7 * cache offline errors and send when connected
8 */
9class Offline {
10 /**
11 * @inheritDoc
12 */
13 static __initStatic() {this.id = 'Offline';}
14
15 /**
16 * @inheritDoc
17 */
18 __init() {this.name = Offline.id;}
19
20 /**
21 * the current hub instance
22 */
23
24 /**
25 * maximum number of events to store while offline
26 */
27
28 /**
29 * event cache
30 */
31
32 /**
33 * @inheritDoc
34 */
35 constructor(options = {}) {Offline.prototype.__init.call(this);
36 this.maxStoredEvents = options.maxStoredEvents || 30; // set a reasonable default
37 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
38 this.offlineEventStore = localForage.createInstance({
39 name: 'sentry/offlineEventStore',
40 });
41 }
42
43 /**
44 * @inheritDoc
45 */
46 setupOnce(addGlobalEventProcessor, getCurrentHub) {
47 this.hub = getCurrentHub();
48
49 if ('addEventListener' in WINDOW) {
50 WINDOW.addEventListener('online', () => {
51 void this._sendEvents().catch(() => {
52 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('could not send cached events');
53 });
54 });
55 }
56
57 const eventProcessor = event => {
58 if (this.hub && this.hub.getIntegration(Offline)) {
59 // cache if we are positively offline
60 if ('navigator' in WINDOW && 'onLine' in WINDOW.navigator && !WINDOW.navigator.onLine) {
61 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('Event dropped due to being a offline - caching instead');
62
63 void this._cacheEvent(event)
64 .then((_event) => this._enforceMaxEvents())
65 .catch((_error) => {
66 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('could not cache event while offline');
67 });
68
69 // return null on success or failure, because being offline will still result in an error
70 return null;
71 }
72 }
73
74 return event;
75 };
76
77 eventProcessor.id = this.name;
78 addGlobalEventProcessor(eventProcessor);
79
80 // if online now, send any events stored in a previous offline session
81 if ('navigator' in WINDOW && 'onLine' in WINDOW.navigator && WINDOW.navigator.onLine) {
82 void this._sendEvents().catch(() => {
83 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('could not send cached events');
84 });
85 }
86 }
87
88 /**
89 * cache an event to send later
90 * @param event an event
91 */
92 async _cacheEvent(event) {
93 return this.offlineEventStore.setItem(uuid4(), normalize(event));
94 }
95
96 /**
97 * purge excess events if necessary
98 */
99 async _enforceMaxEvents() {
100 const events = [];
101
102 return this.offlineEventStore
103 .iterate((event, cacheKey, _index) => {
104 // aggregate events
105 events.push({ cacheKey, event });
106 })
107 .then(
108 () =>
109 // this promise resolves when the iteration is finished
110 this._purgeEvents(
111 // purge all events past maxStoredEvents in reverse chronological order
112 events
113 .sort((a, b) => (b.event.timestamp || 0) - (a.event.timestamp || 0))
114 .slice(this.maxStoredEvents < events.length ? this.maxStoredEvents : events.length)
115 .map(event => event.cacheKey),
116 ),
117 )
118 .catch((_error) => {
119 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('could not enforce max events');
120 });
121 }
122
123 /**
124 * purge event from cache
125 */
126 async _purgeEvent(cacheKey) {
127 return this.offlineEventStore.removeItem(cacheKey);
128 }
129
130 /**
131 * purge events from cache
132 */
133 async _purgeEvents(cacheKeys) {
134 // trail with .then to ensure the return type as void and not void|void[]
135 return Promise.all(cacheKeys.map(cacheKey => this._purgeEvent(cacheKey))).then();
136 }
137
138 /**
139 * send all events
140 */
141 async _sendEvents() {
142 return this.offlineEventStore.iterate((event, cacheKey, _index) => {
143 if (this.hub) {
144 this.hub.captureEvent(event);
145
146 void this._purgeEvent(cacheKey).catch((_error) => {
147 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('could not purge event from cache');
148 });
149 } else {
150 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('no hub found - could not send cached event');
151 }
152 });
153 }
154} Offline.__initStatic();
155
156export { Offline };
157//# sourceMappingURL=offline.js.map