segmentio / analytics.js-core

The hassle-free way to integrate analytics into any web application.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Warn if continue to use shim after real analytics loaded?

tmeasday opened this issue · comments

I had some code that looked like this:

const analytics = window.analytics;

function page() {
  analytics.page(...)
}

It worked completely fine except in the rare case that the code ran before analytics.js was loaded.

In such cases the analytics.page() call still works and is queued by the snippet, to be flushed later by the true Analytics object (once it loads), I suppose. But the subtle bug in the above is that the reference const analytics above will in such cases always point to the shim! So future calls to page() will just not do anything (they will make calls on the now discarded shim).

Can I suggest to help future people who run into this that when you flush the messages from the shim, you also set the existing "factory" functions to throw and point users to some documentation explaining where they might be going wrong?

[Apologies if I am using the wrong terminology, I'm not what vocabulary you use for this stuff].

Thanks! I don't think we can change the behaviour here to throw an error, but we should definitely try to log it. I've triaged this internally https://segment.atlassian.net/browse/LIB-238, and we'll post back here when there are any updates.

Should it be let analytics = ... or is the pattern just problematic?

Moving my comment from segmentio/analytics.js#580 over to this issue.

As far as I can tell, when the library loads, it stubs out the API at window.analytics and then enqueues operations until the library has fully loaded. But once its loaded it changes the window.analytics reference. Anything that has a reference to the original object is now left enqueuing operations and its not at all obvious why its failing to send events.

If you wanted to e.g. inject analytics into some service instead of using the global object directly you end up with confusing behaviour. Here is a contrived example:

const wrapService = (analytics) => {
	setTimeout(() => {
 		window.analytics === analytics // false
        // Any calls to analytics will not be sent if you call analytics.X
        // you HAVE to call window.analytics because the ref has changed
	}, 1000)
}

// If this is called too soon then the analytics calls 
// inside `wrapService` will fail
wrapService(window.analytics)

I understand the library is loading asynchronously but changing out the lib ref like that seems highly unexpected to me. I don't see why I shouldn't be able to pass window.analytics as an argument and have it work.

This was also causing issues for us, as we were storing a reference to analytics in a shared file, thus when it was replaced in .load we lost that reference.

For anyone else who runs into it, we were able to fix by using code similar to this:

if (!window.analytics.initialized) {
    window.analytics.ready(() => setAnalytics(window.analytics));
}
setAnalytics(window.analytics);

This ensured our stored reference gets updated and all calls are going through now using the analytics that is stored in our setAnalytics method.

That being said, it'd be great to have some sort of build in warning when calls continue to be made to the old reference, as the issue suggests, because this did take us some time to track down.

Closing this issue. If this is still a problem, please reopen so that we can get it properly reprioritized.

@danieljackins in case it wasn't clear--the issue here wasn't that I (and @kirkchris, similarly it seems) wasn't able to solve the problem, it was an easy fix once we'd tracked down what was going on.

The issue is that it is quite opaque what is happening and easy to miss this bug. What it means is that events get dropped in certain timing-dependent situations; I imagine many, many of your users are not even aware it is happening.

I don't know if it is still a problem or not, our code has already been fixed and so it wouldn't be something I'd notice. @f2prateek said someone would post back if it was changed, and no-one did so I can only assume it hasn't been 🤷‍♂

In any case, it is up to you what to do with this; I was just trying to avoid others wasting a bunch of time like I did.