-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add integration for offline support #2778
Conversation
18c37fb
to
b0d0260
Compare
b0d0260
to
0fbf345
Compare
Offline events are cached in the browser with localforage.
0fbf345
to
378c344
Compare
Okay, I think I have this in a good place now, but I still need to write some tests. The final failing check is related to bundle size, so I'm not entirely sure how to proceed. I assume this is from the addition of the |
This looks good at an initial glance, but before we do a deeper review, @voraciousdev do you mind if you could move this integration into our integrations package? I think this is the best place for features like this. https://github.com/getsentry/sentry-javascript/tree/master/packages/integrations. Also do you have a demo repo or something of the sort you've been testing this with? Would help with review/testing on our side (also to explore all the use cases). Thanks for taking this on, the Sentry team appreciates it ❤️ As an aside it seems that the bundle check is failing because you are trying to merge from a fork? Don't think it is your problem, if you look all the size checks still pass.
We will investigate this on our side. |
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.
@AbhiPrasad I appreciate the feedback! I believe I moved this to the correct place now. The repo I've been using to test this is closed source, but I'll see if I can throw an example together today. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some more thoughts I had.
Once you get around to the tests, I'll do a more involved review (and clone the branch locally for some local testing).
* @inheritDoc | ||
*/ | ||
public constructor() { | ||
this.offlineEventStore = localforage.createInstance({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could createInstance
fail? What happens to this.offlineEventStore
then?
We can import localforage typings if they exist as well.
Also, what browsers does localforage
support? It's fine if it doesn't match Sentry's (https://docs.sentry.io/platforms/javascript/#browser-table) - we just have to document it then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good catch. I meant to wrap this in a try/catch
. I will look into importing the type file too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it meets the compatibility requirements. It is a wrapper around multiple storage mechanisms. It attempts to use IndexedDB, then falls back to WebSQL, and if all else fails, it falls back to localStorage.
https://github.com/localForage/localForage/wiki/Supported-Browsers-Platforms
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice ok, makes sense. Just one last thing, what happens when local storage is full? Is it a queue, or do new events just get dropped?
@bruno-garcia, how do we handle offline queue in mobile right now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only envelopes are cached (no event json alone are cached anymore is what I mean). The order they were cached is kept when attempting to send things to Sentry (i.e device is back online). If maxItems
is reached (by default 30 IIRC), the oldest file is deleted to make space to the newest one.
We don't use dependencies or a DB of any sort. It's just files written to a directory.
If session data is stored (release health), the init=true
flag must be preserved. So when deleting a file to make room for a new one (max items reached), the envelope would need to be unwrapped, checked for sessions and if init=true exists, that must be moved to the next session update queued up for submission (from oldest to newest).
Events capture when session tracking is on must be in an envelope together with a session update.
Note that the order being kept means that if you get a call to captureException
, you won't be sending that to Sentry if there's stuff cached. You must prioritize what's on the storage first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Bruno for coming through 🚀. We can ignore all the sessions stuff, and envelope details - although important for me, probably not as for you @voraciousdev
Basically there are two things we need to keep in mind.
-
Enforce some kind of
maxItems
in local storage. This can be changed through an option. -
Make sure that we send all events from local storage first before we continue on with other events. I know we are using an event listener right now to listen to
online
, do you think that is good enough? Maybe we need another check in theglobalEventProcessor
to be sure.
As for how this will work with sessions, we can get to that when release health comes to JS 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AbhiPrasad the LocalForage.setItem
promise will be rejected if there is not enough space on the device. With the way it's currently written in this integration, that would result in logger.warn('could not cache event while offline')
being called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok perfect we should be good then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure that we send all events from local storage first before we continue on with other events. I know we are using an event listener right now to listen to
online
, do you think that is good enough? Maybe we need another check in theglobalEventProcessor
to be sure.
@AbhiPrasad I'm not sure if this is directed at me, but there is definitely a possible race condition here regarding the online
event. Given the nature of this integration (to support offline-capable apps), I think it's a real scenario that we could have real error events triggered as soon as the online
event is fired. For offline apps, it's common practice to listen to the online
event and run some code (which could then lead to an error being thrown/recorded).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just added localforage typings as well. Good call on that. 👍
@AbhiPrasad I believe I have addressed all open comments, and I just added tests. Mind taking another look and letting me know if there's anything else you need me to change? Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice LGTM, thanks for sticking with it. Just before approval, I would like to get a double check from @HazAT to see if there is anything else left (also to see if this might cause any issues with rate limiting we have to concern ourselves with). Once he gives to OK, I will approve and merge.
Also, I thought about the race condition a little bit more, but I think it's still fine to listen on online
. We can come back to this if many people report a problem.
10fd6b2
to
efbee73
Compare
@AbhiPrasad I did some more testing in my own project, and I discovered a couple of small issues that I believe are now fixed via the extra commits you see here. To break them down:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First of all thanks for this, this is awesome a lot of users already requested this 🥇
Generally, it looks good.
I have one additional ask, please add an option for max stored offline events. I see that localForage
has a limit but I would like to have more control over this.
So for example, maxStoredEvents: 30
and after we reach more than 30, it acts like a ring buffer dropping the oldest events and replaces it with new ones coming in.
We had troubles with other SDKs already where this was more or less unbound so we need to add a reasonable limit by default.
@HazAT thank you for following up! I can definitely manage that. I'll tag you for another review once it's ready. |
@HazAT I finally got around to adding the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @voraciousdev thanks for keeping the ball rolling, aside from the merge conflicts I think we are good to go :)
@HazAT it looks like there were some lint rules that changed since I started this. I will get it updated. |
I don't usually like merge commits, but the GitHub UI created one automatically when I resolved those conflicts. Let me know if you would prefer a rebase. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We squash commit anyway :)
Thanks @voraciousdev contribution of the year 🥇
@HazAT any idea when this will be released? |
@voraciousdev tomorrow :) |
if (this.hub) { | ||
const newEventId = this.hub.captureEvent(event); | ||
|
||
if (newEventId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Hub.captureEvent
method always returns new event ID, so event will always be purged from event store (even that client failed to send it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in #2890
name: 'sentry/offlineEventStore', | ||
}); | ||
|
||
if ('addEventListener' in this.global) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Event listener should be set up after hub is set on instance (in setupOnce
method) as event may be fired before hub is set (hub is used in _sendEvents
).
IMHO it's bad practice to have business logic in constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in #2889
* Add documentation for Offline integration See getsentry/sentry-javascript#2778 Remove backticks from plugin.mdx's description; PageGrid doesn't render them as monospace, so they appear as literals in https://docs.sentry.io/platforms/javascript/configuration/integrations/, and the default.mdx description doesn't use them for its class names. * Apply suggestions from code review Co-authored-by: Isabel <76437239+imatwawana@users.noreply.github.com> Co-authored-by: Isabel <76437239+imatwawana@users.noreply.github.com>
Before submitting a pull request, please take a look at our
Contributing guidelines and verify:
yarn lint
) & (yarn test
).Summary
Cache offline events to send when connectivity is reestablished.
This PR is an adaptation of #2216. Resolves #1633.