diff --git a/client/modules/core/startup.js b/client/modules/core/startup.js index d0649c6e22a..5558ad21ea0 100644 --- a/client/modules/core/startup.js +++ b/client/modules/core/startup.js @@ -2,7 +2,11 @@ import store from "amplify-store"; import { Meteor } from "meteor/meteor"; import { Tracker } from "meteor/tracker"; import { Accounts } from "meteor/accounts-base"; -import Reaction from "./main"; + +import { Reaction, Logger } from "/client/api"; + + +const cookieName = "_RcFallbackLoginToken"; /** * Startup Reaction @@ -14,6 +18,20 @@ Meteor.startup(function () { // initialize anonymous guest users return Tracker.autorun(function () { const userId = Meteor.userId(); + + if (userId && !isLocalStorageAvailable() && !readCookie(cookieName)) { + Logger.debug("No local storage available. About to set up fallback login " + + "mechanism with cookie login token."); + Meteor.call("accounts/createFallbackLoginToken", (err, token) => { + if (!err && token) { + window.onbeforeunload = () => createSessionCookie(cookieName, token); + return; + } + // Can't set login cookie. Fail silently. + Logger.error("Setting up fallback login mechanism failed!"); + }); + } + // TODO: maybe `visibilityState` will be better here let loggingIn; let sessionId; @@ -21,10 +39,45 @@ Meteor.startup(function () { loggingIn = Accounts.loggingIn(); sessionId = store("Reaction.session"); }); + if (!userId) { if (!loggingIn || typeof sessionId !== "string") { - Accounts.loginWithAnonymous(); + if (!isLocalStorageAvailable() && readCookie(cookieName)) { + // If re-login through local storage fails, RC falls back + // to cookie-based login. E.g. Applies for Safari browser in + // incognito mode. + Accounts.loginWithToken(readCookie(cookieName)); + } else { + Accounts.loginWithAnonymous(); + } } } }); }); + +function isLocalStorageAvailable() { + try { + localStorage.testKey = "testValue"; + } catch (e) { + return false; + } + delete localStorage.testKey; + return true; +} + +function readCookie(name) { + const nameEq = name + "="; + const ca = document.cookie.split(";"); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) === " ") c = c.substring(1, c.length); + if (c.indexOf(nameEq) === 0) { + return c.substring(nameEq.length, c.length); + } + } + return null; +} + +function createSessionCookie(name, value) { + document.cookie = name + "=" + value + "; path=/"; +} diff --git a/server/methods/accounts/accounts.js b/server/methods/accounts/accounts.js index 83b51fd928a..1867b1d1aa7 100644 --- a/server/methods/accounts/accounts.js +++ b/server/methods/accounts/accounts.js @@ -761,6 +761,22 @@ export function setUserPermissions(userId, permissions, group) { } } +/** + * accounts/createFallbackLoginToken + * @returns {String} returns a new loginToken for current user, + * that can be used for special login scenarios - e.g. store the + * newly created token as cookie on the browser, if the client + * does not offer local storage. + */ +export function createFallbackLoginToken() { + if (this.userId) { + const stampedLoginToken = MeteorAccounts._generateStampedLoginToken(); + const loginToken = stampedLoginToken.token; + MeteorAccounts._insertLoginToken(this.userId, stampedLoginToken); + return loginToken; + } +} + /** * Reaction Account Methods */ @@ -775,5 +791,6 @@ Meteor.methods({ "accounts/sendWelcomeEmail": sendWelcomeEmail, "accounts/addUserPermissions": addUserPermissions, "accounts/removeUserPermissions": removeUserPermissions, - "accounts/setUserPermissions": setUserPermissions + "accounts/setUserPermissions": setUserPermissions, + "accounts/createFallbackLoginToken": createFallbackLoginToken });