createLogin = () => {
let firstResolve;
const updateResult = () => {
const newValue = userFirebase.auth().currentUser;
if (firstResolve) {
firstResolve(newValue);
}
if (!newValue) {
if (!firstResolve)
userUi.value = new Promise((resolve) => (firstResolve = resolve));
else userUi.value = undefined;
} else {
userUi.value = newValue;
}
userUi.dispatchEvent(
new CustomEvent("input", {
bubbles: true,
detail: {
user: userUi.value || null
}
})
);
};
const userUi = html`<span>${viewroutine(async function* () {
let response;
let err = "";
const actionWas = (action) => response && response.actions.includes(action);
if (!mutable authStateKnown) {
let ready = null;
const isReady = new Promise((resolve) => (ready = resolve));
userFirebase.auth().onAuthStateChanged(ready);
await isReady;
mutable authStateKnown = true;
}
await new Promise((r) => r());
while (true) {
try {
updateResult();
if (!userFirebase.auth().currentUser) {
const loginUi = screen({
actions: ["login"]
});
const unsubscribe = userFirebase.auth().onAuthStateChanged((user) => {
if (user)
loginUi.dispatchEvent(new Event("input", { bubbles: true }));
});
response = yield* ask(loginUi);
unsubscribe();
} else {
const logoutUi = screen({
actions: ["logout"]
});
const unsubscribe = userFirebase.auth().onAuthStateChanged((user) => {
if (!user)
logoutUi.dispatchEvent(new Event("input", { bubbles: true }));
});
response = yield* ask(logoutUi);
unsubscribe();
}
if (actionWas("logout")) {
console.log("Logging out");
yield screen({
info: md`Logging out...`
});
await userFirebase.auth().signOut();
}
if (actionWas("login")) {
console.log("login");
const privateCode = randomId(64);
const publicCode = await hash(privateCode);
yield screen({
info: md`Preparing...`
});
await prepare(publicCode);
let relmeauth = false;
while (!userFirebase.auth().currentUser) {
while (!actionWas("verify")) {
console.log("prompt verify");
response = yield* ask(
screen({
info: md`1. ${err}Add comment containing **${publicCode}** to this notebook using the cell burger menu to the left.
2. Click login to complete login.
<img width=300px src="${await FileAttachment(
"ezgif.com-gif-maker.webp"
).url()}"></img>
\n⚠️ Logging in discloses your [Observable](https://observablehq.com/) username to the notebook author.
${actionWas("copy") ? "\ncopied to clipboard" : ""}`,
actions: ["copy", "verify"],
toggles: [
{
code: "profile_relmeauth",
label: html`[optional] scan for teams?`,
value: relmeauth,
caption: html`<details class='e-info' style="display: inline-block;font-size: 14px;"><summary>how does scanning work?</summary>
${md`From your Observablehq profile URL we look for weblinks to team profile URLs, and if those profile URLs also weblink to your profile URL we consider you to have admin access for that team (see [relmeauth](https://observablehq.com/@endpointservices/auth#observable_features))`}
</details>`
}
]
})
);
if (actionWas("copy")) {
navigator.clipboard.writeText(
"public auth code: " + publicCode
);
}
relmeauth = response.profile_relmeauth === true;
}
console.log("Relmeauth scan?", relmeauth);
response = undefined;
console.log("verify");
yield screen({
info: md`Checking...`
});
try {
const verification = await verify({
notebookURL: html`<a href=""/>`.href.split("?")[0],
privateCode,
relmeauth
});
if (verification.access_token) {
await userFirebase
.auth()
.signInWithCustomToken(verification.access_token);
} else {
throw new Error("no token returned");
}
} catch (error) {
err = `<mark>⚠️ ${error.message}</mark>\n\n`;
}
}
}
} catch (err) {
yield* ask(
screen({
info: md`Uexpected error: ${err.message}`,
actions: ["ok"]
})
);
}
}
})}`;
return userUi;
}