Skip to content

Commit

Permalink
feat: use embed iframe by default
Browse files Browse the repository at this point in the history
  • Loading branch information
darlanalves authored Jul 30, 2024
1 parent 1cb679a commit d2d7b5d
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 103 deletions.
138 changes: 56 additions & 82 deletions assets/index.mjs
Original file line number Diff line number Diff line change
@@ -1,40 +1,14 @@
const authDomain = "https://__API_URL__";
const fetchOptions = { credentials: "include", mode: "cors" };

export const events = new EventTarget();

async function toJson(r) {
if (r.ok) {
return await r.json();
}

throw new Error(r.status + ": " + r.statusText);
}

function toBoolean(r) {
if (r.ok) {
return true;
}

throw new Error(r.status + ": " + r.statusText);
}

export async function getProfile() {
const r = await fetch(authDomain, fetchOptions);
return toJson(r);
}

export async function isAuthenticated() {
const r = await fetch(authDomain, { ...fetchOptions, method: 'HEAD' });
return Boolean(r.ok && r.status < 300);
}
const commandQueue = {};

let popup = null;
let embedded = null;

window.addEventListener('message', (e) => {
export const events = new EventTarget();

window.addEventListener("message", (e) => {
if (e.origin !== authDomain) {
console.log('Discarded event', e);
console.log("Discarded event", e);
return;
}

Expand All @@ -44,31 +18,55 @@ window.addEventListener('message', (e) => {
return;
}

if (event === 'signin' && popup) {
popup.close();
popup = null;
switch (event) {
case "error":
if (commandQueue[detail.id]) {
commandQueue[detail.id].reject(detail.error);
}
break;
case "success":
if (commandQueue[detail.id]) {
commandQueue[detail.id].resolve(detail.result);
}
break;
case "signin":
if (popup) {
popup.close();
popup = null;
}
break;

default:
events.dispatchEvent(new CustomEvent(event, { detail }));
}

events.dispatchEvent(new CustomEvent(event, { detail }));
});

export function useEmbedded() {
if (embedded) return;

embedded = document.createElement('iframe');
embedded = document.createElement("iframe");
embedded.src = String(new URL("/embed", authDomain));
document.body.append(embedded);

setInterval(() => !popup && embedded.contentWindow.postMessage('ping', authDomain), 1000 * 30);
setInterval(
() => !popup && embedded.contentWindow.postMessage("ping", authDomain),
1000 * 30
);
}

window.addEventListener("DOMContentLoaded", useEmbedded);

export function signIn(usePopUp) {
if (usePopUp) {
const { innerWidth, innerHeight } = window;
const left = Math.round((innerWidth - 640)/2);
const top = Math.round((innerHeight - 480)/2);

popup = window.open(String(new URL("/login", authDomain)), 'signin', `popup,width=640,height=480,left=${left},top=${top}`);
const left = Math.round((innerWidth - 640) / 2);
const top = Math.round((innerHeight - 480) / 2);

popup = window.open(
String(new URL("/login", authDomain)),
"signin",
`popup,width=640,height=480,left=${left},top=${top}`
);
return;
}

Expand All @@ -80,49 +78,17 @@ export function signIn(usePopUp) {
location.href = String(url);
}

export async function signOut() {
const r = await fetch(authDomain, {
...fetchOptions,
method: "DELETE",
});

const ok = await toBoolean(r);
events.dispatchEvent(new CustomEvent('signout', { detail: ok }));
return ok;
}

export async function getProperties() {
const r = await fetch(new URL("/properties", authDomain), fetchOptions);
return await toJson(r);
}

export async function getProperty(property) {
const r = await fetch(new URL("/properties/" + property, authDomain), fetchOptions);
return r.ok ? (await toJson(r)).value : '';
}

export async function setProperty(property, value) {
const r = await fetch(new URL("/properties", authDomain), {
...fetchOptions,
method: "PUT",
headers: { "content-type": "application/json" },
body: JSON.stringify({ key: property, value }),
});

return toBoolean(r);
}

export async function deleteProperty(key) {
const r = await fetch(new URL("/properties/" + key, authDomain), {
...fetchOptions,
method: "DELETE",
});

return toBoolean(r);
function fetchCommand(command) {
return async (...args) =>
new Promise((resolve, reject) => {
const id = Math.random();
commandQueue[id] = { resolve, reject };
embedded.contentWindow.postMessage({ id, command, args }, authDomain);
});
}

let ns = location.host;
const getNS = (p) => ns + ':' + p;
const getNS = (p) => ns + ":" + p;

export function setNS(newNS) {
ns = newNS;
Expand All @@ -139,3 +105,11 @@ export function setPropertyNS(property, value) {
export function deletePropertyNS(property) {
return deleteProperty(getNS(property));
}

export const getProperty = fetchCommand("getProperty");
export const setProperty = fetchCommand("setProperty");
export const deleteProperty = fetchCommand("deleteProperty");
export const getProperties = fetchCommand("getProperties");
export const getProfile = fetchCommand("getProfile");
export const isAuthenticated = fetchCommand("isAuthenticated");
export const signOut = fetchCommand("signOut");
82 changes: 82 additions & 0 deletions assets/lib.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const authDomain = "https://__API_URL__";
const fetchOptions = { credentials: "include", mode: "cors" };

async function toJson(r) {
if (r.ok) {
return await r.json();
}

throw new Error(r.status + ": " + r.statusText);
}

function toBoolean(r) {
if (r.ok) {
return true;
}

throw new Error(r.status + ": " + r.statusText);
}

async function getProfile() {
const r = await fetch(authDomain, fetchOptions);
return toJson(r);
}

async function isAuthenticated() {
const r = await fetch(authDomain, { ...fetchOptions, method: "HEAD" });
return Boolean(r.ok && r.status < 300);
}

async function signOut() {
const r = await fetch(authDomain, {
...fetchOptions,
method: "DELETE",
});

const ok = await toBoolean(r);
events.dispatchEvent(new CustomEvent("signout", { detail: ok }));
return ok;
}

async function getProperties() {
const r = await fetch(new URL("/properties", authDomain), fetchOptions);
return await toJson(r);
}

async function getProperty(property) {
const r = await fetch(
new URL("/properties/" + property, authDomain),
fetchOptions
);
return r.ok ? (await toJson(r)).value : "";
}

async function setProperty(property, value) {
const r = await fetch(new URL("/properties", authDomain), {
...fetchOptions,
method: "PUT",
headers: { "content-type": "application/json" },
body: JSON.stringify({ key: property, value }),
});

return toBoolean(r);
}

async function deleteProperty(key) {
const r = await fetch(new URL("/properties/" + key, authDomain), {
...fetchOptions,
method: "DELETE",
});

return toBoolean(r);
}

export const commands = {
getProfile,
isAuthenticated,
signOut,
getProperties,
getProperty,
setProperty,
deleteProperty,
};
55 changes: 34 additions & 21 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {

const googleSvg = readFileSync("./assets/google.svg", "utf8");
const esLibrary = readFileSync("./assets/index.mjs", "utf8");
const esHelper = readFileSync("./assets/lib.mjs", "utf8");

function protectedRoute(req, res, next) {
if (!req.isAuthenticated || !req.isAuthenticated() || !req.user?.id) {
Expand Down Expand Up @@ -84,8 +85,14 @@ function makeProfile(user: User) {
<div class="bg-gray-100 h-screen w-screen flex items-center justify-center hidden" id="p">
<div class="bg-white rounded-xl mx-auto p-8 border shadow-lg">
<figure>
<img class="w-24 h-24 rounded-full mx-auto" src="${user.photo}" alt="" width="384" height="512" />
<figcaption class="block pt-4 text-center">Hello, ${user.name}!<br/><span class="text-sm text-gray-400">${user.userId}</span></figcaption>
<img class="w-24 h-24 rounded-full mx-auto" src="${
user.photo
}" alt="" width="384" height="512" />
<figcaption class="block pt-4 text-center">Hello, ${
user.name
}!<br/><span class="text-sm text-gray-400">${
user.userId
}</span></figcaption>
</figure>
<hr class="mt-4" />
<button type="button" onclick="l()" class="block bg-white text-gray-800 p-2 text-sm rounded shadow border border-gray-200 mt-4 mx-auto">Logout</button>
Expand All @@ -101,32 +108,37 @@ function makeProfile(user: User) {
}
window.p.classList.remove('hidden');
(opener||window).postMessage({ event: 'signin', detail: ${JSON.stringify(user)} }, '*');
(opener||window).postMessage({ event: 'signin', detail: ${JSON.stringify(
user
)} }, '*');
});
</script>`
);
}

async function makeEmbedPage(req, res) {
const uid = req.user?.id;
const user = uid ? await findByUserId(uid) : null;
const allowedOrigins = (process.env.EMBED_ALLOWED_ORIGINS || "")
.split(",")
.map((s) => s.trim());

res.send(`<script type="module">
const allowedOrigins = ${JSON.stringify(allowedOrigins)};
const profile = ${user ? JSON.stringify(user) : "null"};
window.addEventListener("message", function (event) {
if (!allowedOrigins.some(o => event.origin.endsWith(o))) {
console.log('Origin not allowed: ' + event.origin, event);
return;
}
import { commands } from '/lib.mjs';
const allowedOrigins = ${JSON.stringify(allowedOrigins)};
window.addEventListener("message", async function (event) {
if (!allowedOrigins.some(o => event.origin.endsWith(o))) {
console.log('Origin not allowed: ' + event.origin, event);
return;
}
event.source.postMessage({ event: 'state', detail: profile }, event.origin);
}, false);
setTimeout(() => window.location.reload(), 1000 * 60);
</script>`);
const { command, args, id } = event.data;
if (command in commands) {
const result = await commands[command].apply(null, args);
event.source.postMessage({ event: 'success', detail: { id, result } }, event.origin);
} else {
event.source.postMessage({ event: 'error', detail: { id, error: 'Invalid command' } }, event.origin);
}
}, false);
</script>`);
}

const scopes = {
Expand All @@ -137,7 +149,7 @@ const scopes = {

const app = express();

app.set('trust proxy', 1);
app.set("trust proxy", 1);
app.use(session);
app.use(passport.initialize());
app.use(passport.session());
Expand All @@ -154,17 +166,18 @@ app.get("/me", protectedRoute, getProfile);
app.get("/auth/google", passport.authenticate("google", scopes));
app.get(callback, passport.authenticate("google", scopes));

const serveEsModule = (req, res) => {
const serveEsModule = (source) => (req, res) => {
const es = esLibrary.replace("__API_URL__", req.get("x-forwarded-for"));
res
.set("Content-Type", "text/javascript")
.set("Access-Control-Allow-Origin", "*")
.send(es);
};

app.get("/auth.js", serveEsModule);
app.get("/index.js", serveEsModule);
app.get("/index.mjs", serveEsModule);
app.get("/auth.js", serveEsModule(esLibrary));
app.get("/index.js", serveEsModule(esLibrary));
app.get("/index.mjs", serveEsModule(esLibrary));
app.get("/lib.mjs", serveEsModule(esHelper));

app.put("/properties", protectedRoute, (req, res) => {
const a = [];
Expand Down

0 comments on commit d2d7b5d

Please sign in to comment.