options.js (3000 bytes)
1 // The options page: sign in to a sync server, sync now, sign out. 2 "use strict"; 3 4 const api = globalThis.browser ?? globalThis.chrome; 5 const $ = (id) => document.getElementById(id); 6 const DEFAULT_SERVER = "https://sbm.subread.space"; 7 8 // The data that the add-on sends to the server after a sign-in. Firefox 140 9 // and later ask the user for it; other browsers have no such question. 10 const DATA = ["bookmarksInfo", "personallyIdentifyingInfo", "authenticationInfo"]; 11 let askData = false; 12 api.permissions.getAll().then((p) => { 13 askData = !!p.data_collection; 14 }); 15 16 function send(msg) { 17 return api.runtime.sendMessage(msg).then((r) => { 18 if (r && r.error) throw new Error(r.error); 19 return r && r.value; 20 }); 21 } 22 23 function say(text) { 24 $("message").textContent = text; 25 } 26 27 function origin(server) { 28 const s = server.trim().replace(/\/+$/, ""); 29 return new URL(s.includes("://") ? s : "https://" + s).origin; 30 } 31 32 async function show() { 33 const s = await api.storage.local.get(["server", "email", "token", "synced", "error", "text"]); 34 const server = s.server || DEFAULT_SERVER; 35 $("signed-out").hidden = !!s.token; 36 $("signed-in").hidden = !s.token; 37 if (!$("server").value) $("server").value = server; 38 if (!$("email").value) $("email").value = s.email || ""; 39 $("signup").href = origin($("server").value || server) + "/signup"; 40 $("account").href = server + "/account"; 41 const n = Tsv.parse(s.text || "").length; 42 $("who").textContent = "Signed in to " + server + " as " + s.email + ". " + n + " bookmarks" + 43 (s.synced ? ", synced " + new Date(s.synced).toLocaleString() : "") + "."; 44 if (s.error) say("Sync: " + s.error); 45 } 46 47 $("server").addEventListener("input", () => { 48 try { 49 $("signup").href = origin($("server").value) + "/signup"; 50 } catch (e) { 51 // not an address yet 52 } 53 }); 54 55 $("sign-in").addEventListener("submit", async (e) => { 56 e.preventDefault(); 57 let want; 58 try { 59 want = { origins: [origin($("server").value) + "/*"] }; 60 } catch (err) { 61 say("The sync server is not an address."); 62 return; 63 } 64 if (askData) want.data_collection = DATA; 65 // The browser asks the user. The request must come before any await: only 66 // then does the browser see it as part of the click. 67 const granted = api.permissions.request(want); 68 say("Signing in…"); 69 try { 70 if (!(await granted)) { 71 say("Without that permission the add-on cannot sync."); 72 return; 73 } 74 await send({ type: "signIn", server: $("server").value, email: $("email").value, password: $("password").value }); 75 $("password").value = ""; 76 say(""); 77 } catch (err) { 78 say(err.message); 79 } 80 show(); 81 }); 82 83 $("sync").addEventListener("click", async () => { 84 say("Syncing…"); 85 try { 86 await send({ type: "sync" }); 87 say(""); 88 } catch (err) { 89 say(err.message); 90 } 91 show(); 92 }); 93 94 $("sign-out").addEventListener("click", async () => { 95 await send({ type: "signOut" }); 96 say("Signed out. Your bookmarks stay in this browser."); 97 show(); 98 }); 99 100 show();