jackocnr / intl-tel-input

A JavaScript plugin for entering and validating international telephone numbers
https://intl-tel-input.com
MIT License
7.54k stars 1.94k forks source link

Advise: Multiple instances of intl-tel-input getting loaded, causing global "window" collision #1577

Closed postama closed 3 months ago

postama commented 4 months ago

Thank you for your excellent work on this library and on its maintenance. This is not a bug, nor a feature enhancement, but looking for advisement.

We have a page on our site that uses intl-tel-input. Recently, a 3rd party widget was added (https://www.leadconnectorhq.com/) which also uses intl-tel-input.

The problem is, that both scripts get loaded, and therefore both load window.intlTelInputGlobal. This conflict is causing one of the two to always break, since each version has one instance.

What options do we have at an implementation level? Are there ways we can avoid this clash?

I can see two possible solutions at a library level, which would both require changes (so non-ideal):

  1. Modify library initialization to check for window.intlTelInputGlobal, and if it already exists, don't re-initialize. This seems like it could cause versioning problems if there are multiple different versions getting loaded though.
  2. Allow for users to name their own instance instead of using the global "window". This is similar to how jQuery supports namespaces: https://stackoverflow.com/questions/1535691/how-to-change-namespace-of-jquery
jackocnr commented 4 months ago

We have a page on our site that uses intl-tel-input. Recently, a 3rd party widget was added (https://www.leadconnectorhq.com/) which also uses intl-tel-input.

Wow, I guess this plugin is a victim of its own success! 🎉

It's an interesting problem. I like the idea of borrowing from jQuery's noConflict mode. The easiest way to handle this would be with a new initialisation option e.g. called noConflict, which takes a custom string e.g. in your case you could use "Postama", and it uses that to modify the name of the global so it doesn't clash with any others e.g. changing intlTelInputGlobals to intlTelInputGlobalsPostama. The problem is that currently, window.intlTelInputGlobals gets defined as soon as the plugin script is parsed by the browser (before plugin initialisation), but thinking about it, we might be able to change that, so it doesn't get defined until the first initialisation - I need to look into this.

Additionally, if you load the utils script, it defines its own global: intlTelInputUtils. In this case, it might be possible to do something similar, and in the plugin's onload handler that fires when the utils script has loaded, we immediately rename the global e.g. from intlTelInputUtils to intlTelInputUtilsPostama. There is the possibility that the other plugin script (e.g. from leadconnectorhq) has already loaded and defined window.intlTelInputUtils first, in which case, we need to be careful not to override it. I don't think there's a way to pass the noConflict variable into the loaded utils script, so it's always going to end up defining window.intlTelInputUtils but maybe we could check if one already exists, and if so back it up somewhere e.g.

// utils.js
if (window.intlTelInputUtils) {
  window.intlTelInputUtilsBackup = window.intlTelInputUtils;
}
window.intlTelInputUtils = {...};

And then in the plugin code:

// onload handler (e.g. we've just finished loaded the utils script, which has defined window.intlTelInputUtils)
if (noConflict) {
  window.intlTelInputUtilsPostama = window.intlTelInputUtils;
  // if a backup exists, then restore it
  if (window.intlTelInputUtilsBackup) {
    window.intlTelInputUtils = window.intlTelInputUtilsBackup;
  }
}

Then we'll need to update all of the references to these globals in the code e.g.

this.globalsName = `intlTelInputGlobals${this.options.noConflict}`;
this.utilsName = `intlTelInputUtils${this.options.noConflict}`;

// existing code using these globals
window[this.globalsName].loadUtils(...);
// and
window[this.utilsName].formatNumber(...);

And then if you access these globals in your own code, then... well you know what they're called!

jackocnr commented 4 months ago

Unfortunately, since we currently offer the static method window.intlTelInputGlobals.getCountryData() before the plugin is initialised, we will need to change how this works, and so it will be a breaking change, and so will have to wait until the next major release, which may not be for a while.

postama commented 4 months ago

Ahh okay, thank you for letting me know that, and for your detailed response.

jackocnr commented 4 months ago

I've just released v21.2.5 which includes a little fix, so from now on, it should still work fine if there are two plugin scripts on the page at the same time, as long as they're both using the same version of the plugin (starting with this new version). So if you can upgrade to this version in your own code, and get leadconnectorhq to do the same, then things should work ok. Then at least you have a way of making things work for now, until we're able to do the full noConflict implementation.

jackocnr commented 3 months ago

In v22.0.0 I've removed all of the globals vars, so if you upgrade to that version, it should no longer clash with any older version on the same page. Let me know if you have any issues.