Skip to content

Commit

Permalink
updates example app
Browse files Browse the repository at this point in the history
  • Loading branch information
rishabhpoddar committed Apr 25, 2023
1 parent 8f06522 commit 40af56e
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 1 deletion.
11 changes: 11 additions & 0 deletions examples/with-next-iframe/README.md
Expand Up @@ -33,6 +33,17 @@ ngrok http 3000
npm run build && npm run start
```

## A note for safari and chrome incognito

Safari doesn't allow writing to `document.cookie` in an iframe, so we need to switch to using `localstorage` instead. This works, with a few restrictions:

- You cannot share a session across sub domains. So if you have loaded an iframe on `example.com` and another one on `abc.example.com`, they both will not see the same session.
- `localstorage` is cleared after 7 days of inactivity. So if the user is inactive for 7 days, they will be logged out.

Chrome incognito doesn't even allow writing to `localstorage`, so we must use an in memory storage. This means that whenever a user refreshes the page, or if your app does a full page navigation, they will be logged out.

To do these, we pass in a `cookieHandler` and `windowHandler` into the frontendConfig's `supertokens.init` function call.

## Author

Created with :heart: by the folks at supertokens.com.
Expand Down
2 changes: 1 addition & 1 deletion examples/with-next-iframe/config/appInfo.js
@@ -1,6 +1,6 @@
// const port = process.env.APP_PORT || 3000;

export const websiteDomain = "https://e7b4-94-27-211-139.ngrok.io";
export const websiteDomain = "https://ea5c-2405-201-b-c8d8-3106-c37c-c4e3-e3e3.ngrok-free.app";
export const apiBasePath = "/api/auth/";
export const appInfo = {
appName: "SuperTokens Demo App",
Expand Down
124 changes: 124 additions & 0 deletions examples/with-next-iframe/config/cookieHandler.js
@@ -0,0 +1,124 @@
const frontendCookiesKey = "frontendCookies";

let inMemoryStorage = {};

function setKeyValue(key, value) {
try {
window.localStorage.setItem(key, value);
} catch (err) {
inMemoryStorage[key] = value;
}
}

function getKeyValue(key) {
try {
return window.localStorage.getItem(key);
} catch (err) {
if (inMemoryStorage[key] === undefined) {
return null;
} else {
return inMemoryStorage[key];
}
}
}

function getCookiesFromStorage() {
const cookiesFromStorage = getKeyValue(frontendCookiesKey);

if (cookiesFromStorage === null) {
setKeyValue(frontendCookiesKey, "[]");
return "";
}

/**
* Because we store cookies in local storage, we need to manually check
* for expiry before returning all cookies
*/
const cookieArrayInStorage = JSON.parse(cookiesFromStorage);
let cookieArrayToReturn = [];

for (let cookieIndex = 0; cookieIndex < cookieArrayInStorage.length; cookieIndex++) {
const currentCookieString = cookieArrayInStorage[cookieIndex];
const parts = currentCookieString.split(";");
let expirationString = "";

for (let partIndex = 0; partIndex < parts.length; partIndex++) {
const currentPart = parts[partIndex];

if (currentPart.toLocaleLowerCase().includes("expires=")) {
expirationString = currentPart;
break;
}
}

if (expirationString !== "") {
const expirationValueString = expirationString.split("=")[1];
const expirationDate = new Date(expirationValueString);
const currentTimeInMillis = Date.now();

// if the cookie has expired, we skip it
if (expirationDate.getTime() < currentTimeInMillis) {
continue;
}
}

cookieArrayToReturn.push(currentCookieString);
}

/**
* After processing and removing expired cookies we need to update the cookies
* in storage so we dont have to process the expired ones again
*/
setKeyValue(frontendCookiesKey, JSON.stringify(cookieArrayToReturn));

return cookieArrayToReturn.join("; ");
}

function setCookieToStorage(cookieString) {
const cookieName = cookieString.split(";")[0].split("=")[0];
const cookiesFromStorage = getKeyValue(frontendCookiesKey);
let cookiesArray = [];

if (cookiesFromStorage !== null) {
const cookiesArrayFromStorage = JSON.parse(cookiesFromStorage);
cookiesArray = cookiesArrayFromStorage;
}

let cookieIndex = -1;

for (let i = 0; i < cookiesArray.length; i++) {
const currentCookie = cookiesArray[i];

if (currentCookie.indexOf(`${cookieName}=`) !== -1) {
cookieIndex = i;
break;
}
}

/**
* If a cookie with the same name already exists (index != -1) then we
* need to remove the old value and replace it with the new one.
*
* If it does not exist then simply add the new cookie
*/
if (cookieIndex !== -1) {
cookiesArray[cookieIndex] = cookieString;
} else {
cookiesArray.push(cookieString);
}

setKeyValue(frontendCookiesKey, JSON.stringify(cookiesArray));
}

export default function getCookieHandler(original) {
return {
...original,
getCookie: async function () {
const cookies = getCookiesFromStorage();
return cookies;
},
setCookie: async function (cookieString) {
setCookieToStorage(cookieString);
},
};
}
4 changes: 4 additions & 0 deletions examples/with-next-iframe/config/frontendConfig.js
Expand Up @@ -2,10 +2,14 @@ import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import EmailVerification from "supertokens-auth-react/recipe/emailverification";
import Session from "supertokens-auth-react/recipe/session";
import { appInfo } from "./appInfo";
import getCookieHandler from "./cookieHandler";
import getWindowHandler from "./windowHandler";

export const frontendConfig = () => {
return {
appInfo,
cookieHandler: getCookieHandler,
windowHandler: getWindowHandler,
recipeList: [
EmailVerification.init({
mode: "REQUIRED",
Expand Down
95 changes: 95 additions & 0 deletions examples/with-next-iframe/config/windowHandler.js
@@ -0,0 +1,95 @@
/**
* This example app uses HashRouter from react-router-dom. The SuperTokens SDK relies on
* some window properties like location hash, query params etc. Because HashRouter places
* everything other than the website base in the location hash, we need to add custom
* handling for some of the properties of the Window API
*/

import Router from "next/router";

let inmemstorage = {};

export default function getWindowHandler(original) {
return {
...original,
location: {
...original.location,
setHref: (href) => {
Router.push(href);
},
},
localStorage: {
...original.localStorage,
key: async (index) => {
try {
return window.localStorage.key(index);
} catch (err) {
return Object.keys(inmemstorage)[index];
}
},
getItem: async (key) => {
try {
return window.localStorage.getItem(key);
} catch (err) {
return inmemstorage[key] === undefined ? null : inmemstorage[key];
}
},
clear: async () => {
try {
return window.localStorage.clear();
} catch (err) {
inmemstorage = {};
}
},
removeItem: async (key) => {
try {
return window.localStorage.removeItem(key);
} catch (err) {
delete inmemstorage[key];
}
},
setItem: async (key, value) => {
try {
return window.localStorage.setItem(key, value);
} catch (err) {
inmemstorage[key] = value;
}
},
keySync: (index) => {
try {
return window.localStorage.key(index);
} catch (err) {
return Object.keys(inmemstorage)[index];
}
},
getItemSync: (key) => {
try {
return window.localStorage.getItem(key);
} catch (err) {
return inmemstorage[key] === undefined ? null : inmemstorage[key];
}
},
clearSync: () => {
try {
return window.localStorage.clear();
} catch (err) {
inmemstorage = {};
}
},
removeItemSync: (key) => {
try {
return window.localStorage.removeItem(key);
} catch (err) {
delete inmemstorage[key];
}
},
setItemSync: (key, value) => {
try {
return window.localStorage.setItem(key, value);
} catch (err) {
inmemstorage[key] = value;
}
},
},
};
}

0 comments on commit 40af56e

Please sign in to comment.