Render the styled confirmation page with the edge auth token embedded. The page displays the confirmation code so the user can verify it matches their terminal. Clicking "Connect" sends an XHR POST (not a redirect) to the CLI's localhost callback server, keeping the token out of browser history and the URL bar.
(
gateway_addr: &str,
callback_port: u16,
cf_token: &str,
code: &str,
nonce: &str,
)
| 161 | /// the CLI's localhost callback server, keeping the token out of browser |
| 162 | /// history and the URL bar. |
| 163 | fn render_connect_page( |
| 164 | gateway_addr: &str, |
| 165 | callback_port: u16, |
| 166 | cf_token: &str, |
| 167 | code: &str, |
| 168 | nonce: &str, |
| 169 | ) -> String { |
| 170 | // Use JSON serialization for JS-safe string embedding — handles all |
| 171 | // edge cases including \n, \r, U+2028, U+2029 that break JS string |
| 172 | // literals. serde_json::to_string produces a quoted JSON string |
| 173 | // (e.g., "value") which is a valid JS string literal. |
| 174 | // |
| 175 | // We additionally escape < and > to \u003c / \u003e because while |
| 176 | // they're valid in JSON, they're dangerous inside an HTML <script> |
| 177 | // block (the HTML parser sees </script> before the JS parser runs). |
| 178 | let json_token = serde_json::to_string(cf_token) |
| 179 | .unwrap_or_else(|_| "\"\"".to_string()) |
| 180 | .replace('<', "\\u003c") |
| 181 | .replace('>', "\\u003e"); |
| 182 | let json_code = serde_json::to_string(code) |
| 183 | .unwrap_or_else(|_| "\"\"".to_string()) |
| 184 | .replace('<', "\\u003c") |
| 185 | .replace('>', "\\u003e"); |
| 186 | |
| 187 | // HTML-safe version of the code for display in the page body. |
| 188 | let html_code = html_escape(code); |
| 189 | |
| 190 | let version = openshell_core::VERSION; |
| 191 | |
| 192 | format!( |
| 193 | r#"<!DOCTYPE html> |
| 194 | <html lang="en"> |
| 195 | <head> |
| 196 | <meta charset="utf-8"> |
| 197 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 198 | <title>OpenShell — Connect to Gateway</title> |
| 199 | <style> |
| 200 | * {{ margin: 0; padding: 0; box-sizing: border-box; }} |
| 201 | body {{ |
| 202 | font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace; |
| 203 | background: #f5f5f5; |
| 204 | color: #1a1a1a; |
| 205 | min-height: 100vh; |
| 206 | display: flex; |
| 207 | align-items: center; |
| 208 | justify-content: center; |
| 209 | }} |
| 210 | .card {{ |
| 211 | background: #ffffff; |
| 212 | border: 1px solid #e0e0e0; |
| 213 | border-radius: 12px; |
| 214 | padding: 48px; |
| 215 | max-width: 480px; |
| 216 | width: 100%; |
| 217 | text-align: center; |
| 218 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
| 219 | }} |
| 220 | .logo {{ |