Multiple Meteor apps running on the same domain & how to override packages
Recently, we came across a frustrating problem of not being able to run two or more Meteor applications on the same domain while using different paths similar to a traditional web application (such as: www.example.com/app1 & www.example.com/app2). We noticed that users couldn’t remain logged in to both: once logged in to the second one, that first one was immediately logging them out!
The culprit behind this behavior is a limitation with the Web Storage API and the way that Meteor’s localstorage package is saving the session information in the user’s browser. The API’s limitation is that, by design, the storage is shared by all pages that use the same domain. This allows different browser tabs/windows to share the data with each other. But what is missing is the ability to segregate the data based on the current path of the page, which is supported by our Web 1.0 friend/foe: cookies.
Meteor’s problem
To understand what is happening in our case, we need to take a look at Meteor’s localstorage package:
if (key === retrieved) { Meteor._localStorage = { getItem: function (key) { return window.localStorage.getItem(key); }, setItem: function (key, value) { window.localStorage.setItem(key, value); }, removeItem: function (key) { window.localStorage.removeItem(key); } }; }
The functions above are used quite frequently in the client side code and one of those cases is for storing the user’s session information after he/she has logged in, specifically the values for Meteor.userId, Meteor.loginToken and Meteor.loginTokenExpires.
Now, if you followed closely above, you may have spotted the main problem already. Due to the fact that the apps are running on the same domain, and thus share the same Web Storage “container,” Meteor will overwrite app1 values with the ones from app2 as soon as the user logs in to app2. This effectively throws the values out of app1 as they will no longer match the ones in the database. An easy work around would be to use a different subdomain for each application, however, an IT restriction prevented us from doing so in the above case.
Hacking core is bad but overriding Meteor packages is plain easy
An inexperienced programmer (or, rather, someone that doesn’t have to provide long term support for an application) might be tempted to simply go into Meteor’s localstorage package to manually edit and solve the problem. Unfortunately, this will cause issues down the line: updating Meteor will erase the changes and thus might cause unexpected issues to end users if it’s not caught in time.
The proper solution is to override Meteor’s localstorage package until this upstream bughas been resolved by the community. Luckily, for us, overriding a Meteor package is extremely simple. It just takes creating a packages directory in our project’s root and copying the package from the Meteor packages directory.
After this, we may change the code in localstorage.js in the following way:
if (key === retrieved) { var path = window.location.pathname.replace(/^\/([^\/]*).*$/, '$1') + '.'; Meteor._localStorage = { getItem: function (key) { return window.localStorage.getItem(path + key); }, setItem: function (key, value) { window.localStorage.setItem(path + key, value); }, removeItem: function (key) { window.localStorage.removeItem(path + key); } }; }
The path variable that we’ve added here is storing the first argument in the current url and prepending it to all values that are being stored in the browser’s Web Storage, along with a dot for readability. What this accomplishes is that for two apps running at www.example.com/app1 & www.example.com/app2, the session data will be stored and retrieved from different keys with no collisions or accidental overwrites.
Thus, if you log into both applications and inspect your browser’s Web Storage, you’ll see the following values:
app1.Meteor.userId app1.Meteor.loginToken app1.Meteor.loginTokenExpires app2.Meteor.userId app2.Meteor.loginToken app2.Meteor.loginTokenExpires
Nice and tidy, isn’t it?
The way forward
There is one final step with this solution. Whenever you change Meteor versions, make sure to compare the two packages (default & custom) and apply any changes that have been made to the default one by the Meteor team. However, given that this is a really small package, those changes should be rather minimal. Even though this is a perfectly viable solution, note that we have already submitted a pull request on the Github issue for this bug. We hope that it will soon be accepted and merged to ensure that everyone facing this problem won’t have to maintain additional packages in the future.
Have you dealt with the problem above and/or overriding Meteor packages? Do you have any additional tips on the subject to share? If so, drop us a line!