APIs for links and references in the ATmosphere

more (still problematic) flow-y stuff

+266 -216
+13 -5
who-am-i/demo/index.html
··· 16 16 <iframe src="http://127.0.0.1:9997/prompt" id="whoami" style="border: none" height="160" width="320"></iframe> 17 17 18 18 <script type="text/javascript"> 19 - window.onmessage = message => { 20 - if (!message || !message.data || message.data.source !== 'whoami') return; 21 - document.getElementById('whoami').remove(); 22 - document.getElementById('who').textContent = message.data.handle; 23 - }; 19 + (whoami => { 20 + const handleMessage = ev => { 21 + if (ev.source !== whoami.contentWindow) { 22 + console.log('nah'); 23 + return; 24 + } 25 + whoami.remove(); 26 + window.removeEventListener('message', handleMessage); 27 + 28 + document.getElementById('who').textContent = ev.data.handle; 29 + } 30 + window.addEventListener('message', handleMessage); 31 + })(document.getElementById('whoami')); 24 32 </script> 25 33 </body> 26 34 </html>
+26 -20
who-am-i/src/server.rs
··· 11 11 use axum_template::{RenderHtml, engine::Engine}; 12 12 use handlebars::{Handlebars, handlebars_helper}; 13 13 14 - use serde::{Deserialize, Serialize}; 14 + use serde::Deserialize; 15 15 use serde_json::{Value, json}; 16 16 use std::sync::Arc; 17 17 use std::time::Duration; ··· 23 23 24 24 const FAVICON: &[u8] = include_bytes!("../static/favicon.ico"); 25 25 const INDEX_HTML: &str = include_str!("../static/index.html"); 26 - const LOGIN_HTML: &str = include_str!("../static/login.html"); 27 26 28 27 const DID_COOKIE_KEY: &str = "did"; 29 28 ··· 83 82 .unwrap(); 84 83 } 85 84 86 - #[derive(Debug, Serialize)] 87 - struct Known { 88 - did: Value, 89 - fetch_key: Value, 90 - parent_host: String, 91 - } 92 85 async fn prompt( 93 86 State(AppState { 94 87 engine, ··· 111 104 let Some(parent_host) = url.host_str() else { 112 105 return "could nto get host from url".into_response(); 113 106 }; 114 - let m = if let Some(did) = jar.get(DID_COOKIE_KEY) { 107 + if let Some(did) = jar.get(DID_COOKIE_KEY) { 115 108 let did = did.value_trimmed().to_string(); 116 109 117 110 let task_shutdown = shutdown.child_token(); ··· 119 112 120 113 let json_did = Value::String(did); 121 114 let json_fetch_key = Value::String(fetch_key); 122 - let known = Known { 123 - did: json_did, 124 - fetch_key: json_fetch_key, 125 - parent_host: parent_host.to_string(), 126 - }; 127 - return (jar, RenderHtml("prompt-known", engine, known)).into_response(); 115 + RenderHtml( 116 + "prompt-known", 117 + engine, 118 + json!({ 119 + "did": json_did, 120 + "fetch_key": json_fetch_key, 121 + "parent_host": parent_host, 122 + }), 123 + ) 124 + .into_response() 128 125 } else { 129 - LOGIN_HTML.into_response() 130 - }; 131 - (jar, Html(m)).into_response() 126 + RenderHtml( 127 + "prompt-anon", 128 + engine, 129 + json!({ 130 + "parent_host": parent_host, 131 + }), 132 + ) 133 + .into_response() 134 + } 132 135 } 133 136 134 137 #[derive(Debug, Deserialize)] ··· 175 178 State(state): State<AppState>, 176 179 Query(params): Query<CallbackParams>, 177 180 jar: SignedCookieJar, 178 - ) -> (SignedCookieJar, Html<String>) { 181 + ) -> (SignedCookieJar, impl IntoResponse) { 179 182 let Ok((oauth_session, _)) = state.client.callback(params).await else { 180 183 panic!("failed to do client callback"); 181 184 }; ··· 186 189 .same_site(SameSite::None) 187 190 .max_age(std::time::Duration::from_secs(86_400).try_into().unwrap()); 188 191 let jar = jar.add(cookie); 189 - (jar, Html(format!("sup: {did:?}"))) 192 + ( 193 + jar, 194 + RenderHtml("authorized", state.engine, json!({ "did": did })), 195 + ) 190 196 }
-17
who-am-i/static/login.html
··· 1 - <!doctype html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="utf-8" /> 5 - <title>Who-am-i</title> 6 - <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 - <meta name="description" content="Log in" /> 8 - </head> 9 - <body> 10 - <form action="/auth" method="GET"> 11 - <label> 12 - @<input name="handle" placeholder="example.bsky.social" /> 13 - </label> 14 - <button type="submit">log in</button> 15 - </form> 16 - </body> 17 - </html>
who-am-i/static/prompt-anon.html

This is a binary file and will not be displayed.

+9
who-am-i/templates/authorized.hbs
··· 1 + <!doctype html> 2 + 3 + <p>oh sick. hey {{ did }}. you can close this window now.</p> 4 + 5 + <script> 6 + // TODO: tie this back to its source........... 7 + localStorage.setItem("did", {{{json did}}}); 8 + window.close(); 9 + </script>
+34
who-am-i/templates/prompt-anon.hbs
··· 1 + {{#*inline "main"}} 2 + <p> 3 + Share your identity with 4 + <span class="parent-host">{{ parent_host }}</span>? 5 + </p> 6 + 7 + <div id="user-info"> 8 + <form id="action" action="/auth" method="GET" target="_blank"> 9 + <label> 10 + @<input id="handle" name="handle" placeholder="example.bsky.social" /> 11 + </label> 12 + <button id="allow" type="submit">connect</button> 13 + </form> 14 + </div> 15 + 16 + <script> 17 + const formEl = document.getElementById('action'); 18 + const handleEl = document.getElementById('handle'); 19 + formEl.onsubmit = e => { 20 + e.preventDefault(); 21 + // TODO: include expected referer! (..this system is probably bad) 22 + var url = new URL('/auth', window.location); 23 + url.searchParams.set('handle', handleEl.value); 24 + var flow = window.open(url, '_blank'); 25 + window.f = flow; 26 + 27 + window.addEventListener('storage', e => { 28 + location.reload(); 29 + }); 30 + } 31 + </script> 32 + {{/inline}} 33 + 34 + {{#> prompt-base}}{{/prompt-base}}
+165
who-am-i/templates/prompt-base.hbs
··· 1 + <!doctype html> 2 + 3 + <style> 4 + body { 5 + color: #434; 6 + font-family: 'Iowan Old Style', 'Palatino Linotype', 'URW Palladio L', P052, serif; 7 + margin: 0; 8 + min-height: 100vh; 9 + padding: 0; 10 + } 11 + .wrap { 12 + border: 2px solid #221828; 13 + border-radius: 0.5rem; 14 + box-sizing: border-box; 15 + overflow: hidden; 16 + display: flex; 17 + flex-direction: column; 18 + height: 100vh; 19 + } 20 + header { 21 + background: #221828; 22 + display: flex; 23 + justify-content: space-between; 24 + padding: 0 0.25rem; 25 + color: #c9b; 26 + display: flex; 27 + gap: 0.5rem; 28 + align-items: baseline; 29 + } 30 + header > * { 31 + flex-basis: 33%; 32 + } 33 + header > .empty { 34 + font-size: 0.8rem; 35 + opacity: 0.5; 36 + } 37 + header > .title { 38 + text-align: center; 39 + } 40 + header > a.micro { 41 + text-decoration: none; 42 + font-size: 0.8rem; 43 + text-align: right; 44 + opacity: 0.5; 45 + } 46 + header > a.micro:hover { 47 + opacity: 1; 48 + } 49 + main { 50 + background: #ccc; 51 + display: flex; 52 + flex-direction: column; 53 + flex-grow: 1; 54 + padding: 0.25rem 0.5rem; 55 + } 56 + p { 57 + margin: 1rem 0 0; 58 + text-align: center; 59 + } 60 + .parent-host { 61 + font-weight: bold; 62 + color: #48c; 63 + display: inline-block; 64 + padding: 0 0.125rem; 65 + border-radius: 0.25rem; 66 + border: 1px solid #aaa; 67 + font-size: 0.8rem; 68 + } 69 + 70 + #loader { 71 + display: flex; 72 + flex-grow: 1; 73 + justify-content: center; 74 + align-items: center; 75 + } 76 + .spinner { 77 + animation: rotation 1.618s ease-in-out infinite; 78 + border-radius: 50%; 79 + border: 3px dashed #434; 80 + box-sizing: border-box; 81 + display: inline-block; 82 + height: 1.5em; 83 + width: 1.5em; 84 + } 85 + @keyframes rotation { 86 + 0% { transform: rotate(0deg) } 87 + 100% { transform: rotate(360deg) } 88 + } 89 + 90 + #user-info { 91 + flex-grow: 1; 92 + display: flex; 93 + flex-direction: column; 94 + justify-content: center; 95 + } 96 + #action { 97 + background: #eee; 98 + display: flex; 99 + justify-content: space-between; 100 + padding: 0.5rem 0.25rem 0.5rem 0.5rem; 101 + font-size: 0.8rem; 102 + align-items: baseline; 103 + border-radius: 0.5rem; 104 + border: 1px solid #bbb; 105 + cursor: pointer; 106 + } 107 + #action:hover { 108 + background: #fff; 109 + } 110 + #allow { 111 + background: transparent; 112 + border: none; 113 + border-left: 1px solid #bbb; 114 + padding: 0 0.5rem; 115 + color: #375; 116 + font: inherit; 117 + cursor: pointer; 118 + } 119 + #action:hover #allow { 120 + color: #285; 121 + } 122 + 123 + #or { 124 + font-size: 0.8rem; 125 + text-align: center; 126 + } 127 + #or p { 128 + margin: 0 0 1rem; 129 + } 130 + 131 + input#handle { 132 + border: none; 133 + border-bottom: 1px dashed #aaa; 134 + background: transparent; 135 + } 136 + 137 + .hidden { 138 + display: none !important; 139 + } 140 + 141 + </style> 142 + 143 + <div class="wrap"> 144 + <header> 145 + <div class="empty">🔒</div> 146 + <code class="title" style="font-family: monospace;" 147 + >who-am-i</code> 148 + <a href="https://microcosm.blue" target="_blank" class="micro" 149 + ><span style="color: #f396a9">m</span 150 + ><span style="color: #f49c5c">i</span 151 + ><span style="color: #c7b04c">c</span 152 + ><span style="color: #92be4c">r</span 153 + ><span style="color: #4ec688">o</span 154 + ><span style="color: #51c2b6">c</span 155 + ><span style="color: #54bed7">o</span 156 + ><span style="color: #8fb1f1">s</span 157 + ><span style="color: #ce9df1">m</span 158 + ></a> 159 + </header> 160 + 161 + <main> 162 + {{> main}} 163 + </main> 164 + </div> 165 +
+19 -174
who-am-i/templates/prompt-known.hbs
··· 1 - <!doctype html> 2 - 3 - <style> 4 - body { 5 - color: #434; 6 - font-family: 'Iowan Old Style', 'Palatino Linotype', 'URW Palladio L', P052, serif; 7 - margin: 0; 8 - min-height: 100vh; 9 - padding: 0; 10 - } 11 - .wrap { 12 - border: 2px solid #221828; 13 - border-radius: 0.5rem; 14 - box-sizing: border-box; 15 - overflow: hidden; 16 - display: flex; 17 - flex-direction: column; 18 - height: 100vh; 19 - } 20 - header { 21 - background: #221828; 22 - display: flex; 23 - justify-content: space-between; 24 - padding: 0 0.25rem; 25 - color: #c9b; 26 - display: flex; 27 - gap: 0.5rem; 28 - align-items: baseline; 29 - } 30 - header > * { 31 - flex-basis: 33%; 32 - } 33 - header > .empty { 34 - font-size: 0.8rem; 35 - opacity: 0.5; 36 - } 37 - header > .title { 38 - text-align: center; 39 - } 40 - header > a.micro { 41 - text-decoration: none; 42 - font-size: 0.8rem; 43 - text-align: right; 44 - opacity: 0.5; 45 - } 46 - header > a.micro:hover { 47 - opacity: 1; 48 - } 49 - main { 50 - background: #ccc; 51 - display: flex; 52 - flex-direction: column; 53 - flex-grow: 1; 54 - padding: 0.25rem 0.5rem; 55 - } 56 - p { 57 - margin: 1rem 0 0; 58 - text-align: center; 59 - } 60 - .parent-host { 61 - font-weight: bold; 62 - color: #48c; 63 - display: inline-block; 64 - padding: 0 0.125rem; 65 - border-radius: 0.25rem; 66 - border: 1px solid #aaa; 67 - font-size: 0.8rem; 68 - } 69 - 70 - #loader { 71 - display: flex; 72 - flex-grow: 1; 73 - justify-content: center; 74 - align-items: center; 75 - } 76 - .spinner { 77 - animation: rotation 1.618s ease-in-out infinite; 78 - border-radius: 50%; 79 - border: 3px dashed #434; 80 - box-sizing: border-box; 81 - display: inline-block; 82 - height: 1.5em; 83 - width: 1.5em; 84 - } 85 - @keyframes rotation { 86 - 0% { transform: rotate(0deg) } 87 - 100% { transform: rotate(360deg) } 88 - } 89 - 90 - #user-info { 91 - flex-grow: 1; 92 - display: flex; 93 - flex-direction: column; 94 - justify-content: center; 95 - } 96 - #action { 97 - background: #eee; 98 - display: flex; 99 - justify-content: space-between; 100 - padding: 0.5rem 0.25rem 0.5rem 0.5rem; 101 - font-size: 0.8rem; 102 - align-items: baseline; 103 - border-radius: 0.5rem; 104 - border: 1px solid #bbb; 105 - cursor: pointer; 106 - } 107 - #action:hover { 108 - background: #fff; 109 - } 110 - #allow { 111 - background: transparent; 112 - border: none; 113 - border-left: 1px solid #bbb; 114 - padding: 0 0.5rem; 115 - color: #375; 116 - font: inherit; 117 - cursor: pointer; 118 - } 119 - #action:hover #allow { 120 - color: #285; 121 - } 122 - 123 - #or { 124 - font-size: 0.8rem; 125 - text-align: center; 126 - } 127 - #or p { 128 - margin: 0 0 1rem; 129 - } 130 - 131 - .hidden { 132 - display: none !important; 133 - } 134 - 135 - </style> 136 - 137 - <div class="wrap"> 138 - <header> 139 - <div class="empty">🔒</div> 140 - <code class="title" style="font-family: monospace;" 141 - >who-am-i</code> 142 - <a href="https://microcosm.blue" target="_blank" class="micro" 143 - ><span style="color: #f396a9">m</span 144 - ><span style="color: #f49c5c">i</span 145 - ><span style="color: #c7b04c">c</span 146 - ><span style="color: #92be4c">r</span 147 - ><span style="color: #4ec688">o</span 148 - ><span style="color: #51c2b6">c</span 149 - ><span style="color: #54bed7">o</span 150 - ><span style="color: #8fb1f1">s</span 151 - ><span style="color: #ce9df1">m</span 152 - ></a> 153 - </header> 154 - 155 - <main> 156 - <p> 157 - Share your identity with 158 - <span class="parent-host">{{ parent_host }}</span>? 159 - </p> 160 - <div id="loader"> 161 - <span class="spinner"></span> 162 - </div> 163 - <div id="user-info" class="hidden"> 164 - <div id="action"> 165 - <span id="handle"></span> 166 - <button id="allow">Allow</button> 167 - </div> 168 - </div> 169 - <div id="or"> 170 - <p>or, <a id="switch" href="#">use another account</a></p> 171 - </div> 172 - </main> 1 + {{#*inline "main"}} 2 + <p> 3 + Share your identity with 4 + <span class="parent-host">{{ parent_host }}</span>? 5 + </p> 6 + <div id="loader"> 7 + <span class="spinner"></span> 8 + </div> 9 + <div id="user-info" class="hidden"> 10 + <div id="action"> 11 + <span id="handle"></span> 12 + <button id="allow">Allow</button> 13 + </div> 14 + </div> 15 + <div id="or"> 16 + <p>or, <a id="switch" href="#">use another account</a></p> 173 17 </div> 174 - 175 18 176 19 <script> 177 20 var loaderEl = document.getElementById('loader'); ··· 210 53 function share(handle) { 211 54 top.postMessage({ source: 'whoami', handle }, '*'); // TODO: pass the referrer back from server 212 55 } 213 - 214 56 </script> 57 + {{/inline}} 58 + 59 + {{#> prompt-base}}{{/prompt-base}}