655 lines
23 KiB
HTML
655 lines
23 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<!--
|
|||
|
|
================================================================================
|
|||
|
|
TEMPL MIGRATION GUIDE: demo.html → views/demo.templ
|
|||
|
|
================================================================================
|
|||
|
|
|
|||
|
|
PAGE OVERVIEW:
|
|||
|
|
- Demo page showcasing auth views in mini browser window webview style
|
|||
|
|
- Similar to "Sign in with Google" popup windows
|
|||
|
|
- Demonstrates welcome, login, register, and authorize flows in iframe
|
|||
|
|
- Controls to switch between views and adjust window size
|
|||
|
|
- Simulates how dApps would integrate Sonr authentication
|
|||
|
|
|
|||
|
|
MAIN TEMPL COMPONENT:
|
|||
|
|
templ DemoPage() {
|
|||
|
|
@layouts.Base("Auth Demo - Sonr Motr Wallet") {
|
|||
|
|
@DemoHeader()
|
|||
|
|
@DemoControls()
|
|||
|
|
@WebviewFrame()
|
|||
|
|
@IntegrationCodeSamples()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HTMX INTEGRATION:
|
|||
|
|
- View switching: hx-get="/demo/frame/{view}" hx-target="#webview-frame"
|
|||
|
|
- Size presets: Client-side JavaScript for iframe resizing
|
|||
|
|
- Theme toggle: Client-side for dark/light mode preview
|
|||
|
|
|
|||
|
|
SUB-COMPONENTS TO EXTRACT:
|
|||
|
|
- DemoHeader()
|
|||
|
|
- DemoControls()
|
|||
|
|
- ViewSelector(views []string, selected string)
|
|||
|
|
- SizePresets(sizes []Size, selected string)
|
|||
|
|
- WebviewFrame(src string, width int, height int)
|
|||
|
|
- IntegrationCodeSamples()
|
|||
|
|
- CodeBlock(language string, code string)
|
|||
|
|
|
|||
|
|
STATE/PROPS:
|
|||
|
|
type DemoState struct {
|
|||
|
|
CurrentView string // "welcome", "login", "register", "authorize"
|
|||
|
|
FrameWidth int
|
|||
|
|
FrameHeight int
|
|||
|
|
Theme string // "light", "dark", "system"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type Size struct {
|
|||
|
|
Name string
|
|||
|
|
Width int
|
|||
|
|
Height int
|
|||
|
|
}
|
|||
|
|
================================================================================
|
|||
|
|
-->
|
|||
|
|
<html lang="en" class="wa-cloak">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="utf-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|||
|
|
<title>Auth Demo - Sonr Motr Wallet</title>
|
|||
|
|
<script src="https://cdn.sonr.org/wa/autoloader.js"></script>
|
|||
|
|
<style>
|
|||
|
|
:root {
|
|||
|
|
--wa-color-primary: #17c2ff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
html, body {
|
|||
|
|
min-height: 100%;
|
|||
|
|
padding: 0;
|
|||
|
|
margin: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.demo-layout {
|
|||
|
|
min-height: 100vh;
|
|||
|
|
background: var(--wa-color-surface-alt);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.demo-header {
|
|||
|
|
background: var(--wa-color-surface);
|
|||
|
|
border-bottom: 1px solid var(--wa-color-neutral-200);
|
|||
|
|
padding: var(--wa-space-m) var(--wa-space-xl);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.demo-header-content {
|
|||
|
|
max-width: 1400px;
|
|||
|
|
margin: 0 auto;
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.demo-main {
|
|||
|
|
max-width: 1400px;
|
|||
|
|
margin: 0 auto;
|
|||
|
|
padding: var(--wa-space-xl);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.demo-controls {
|
|||
|
|
background: var(--wa-color-surface);
|
|||
|
|
border-radius: var(--wa-radius-l);
|
|||
|
|
padding: var(--wa-space-l);
|
|||
|
|
margin-bottom: var(--wa-space-xl);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|||
|
|
gap: var(--wa-space-l);
|
|||
|
|
align-items: end;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.webview-container {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: var(--wa-space-l);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.webview-wrapper {
|
|||
|
|
position: relative;
|
|||
|
|
background: var(--wa-color-neutral-900);
|
|||
|
|
border-radius: var(--wa-radius-l);
|
|||
|
|
padding: var(--wa-space-xs);
|
|||
|
|
box-shadow:
|
|||
|
|
0 0 0 1px var(--wa-color-neutral-700),
|
|||
|
|
0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.webview-titlebar {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: var(--wa-space-s);
|
|||
|
|
padding: var(--wa-space-xs) var(--wa-space-s);
|
|||
|
|
background: var(--wa-color-neutral-800);
|
|||
|
|
border-radius: var(--wa-radius-m) var(--wa-radius-m) 0 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.titlebar-buttons {
|
|||
|
|
display: flex;
|
|||
|
|
gap: var(--wa-space-2xs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.titlebar-btn {
|
|||
|
|
width: 12px;
|
|||
|
|
height: 12px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.titlebar-btn.close { background: #ff5f57; }
|
|||
|
|
.titlebar-btn.minimize { background: #febc2e; }
|
|||
|
|
.titlebar-btn.maximize { background: #28c840; }
|
|||
|
|
|
|||
|
|
.titlebar-url {
|
|||
|
|
flex: 1;
|
|||
|
|
background: var(--wa-color-neutral-700);
|
|||
|
|
border-radius: var(--wa-radius-s);
|
|||
|
|
padding: var(--wa-space-2xs) var(--wa-space-s);
|
|||
|
|
font-family: var(--wa-font-mono);
|
|||
|
|
font-size: var(--wa-font-size-xs);
|
|||
|
|
color: var(--wa-color-neutral-300);
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: var(--wa-space-xs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.titlebar-url wa-icon {
|
|||
|
|
font-size: 10px;
|
|||
|
|
color: var(--wa-color-success);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.webview-frame {
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 0 0 var(--wa-radius-m) var(--wa-radius-m);
|
|||
|
|
background: white;
|
|||
|
|
transition: width 0.3s ease, height 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.size-indicator {
|
|||
|
|
font-family: var(--wa-font-mono);
|
|||
|
|
font-size: var(--wa-font-size-xs);
|
|||
|
|
color: var(--wa-color-neutral-500);
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.integration-section {
|
|||
|
|
margin-top: var(--wa-space-2xl);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.code-block {
|
|||
|
|
background: var(--wa-color-neutral-900);
|
|||
|
|
border-radius: var(--wa-radius-m);
|
|||
|
|
padding: var(--wa-space-m);
|
|||
|
|
overflow-x: auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.code-block pre {
|
|||
|
|
margin: 0;
|
|||
|
|
font-family: var(--wa-font-mono);
|
|||
|
|
font-size: var(--wa-font-size-xs);
|
|||
|
|
color: var(--wa-color-neutral-100);
|
|||
|
|
line-height: 1.6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.code-block .comment { color: var(--wa-color-neutral-500); }
|
|||
|
|
.code-block .keyword { color: #ff79c6; }
|
|||
|
|
.code-block .string { color: #f1fa8c; }
|
|||
|
|
.code-block .function { color: #50fa7b; }
|
|||
|
|
.code-block .number { color: #bd93f9; }
|
|||
|
|
|
|||
|
|
.view-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|||
|
|
gap: var(--wa-space-m);
|
|||
|
|
margin-top: var(--wa-space-xl);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.view-card {
|
|||
|
|
background: var(--wa-color-surface);
|
|||
|
|
border-radius: var(--wa-radius-m);
|
|||
|
|
padding: var(--wa-space-m);
|
|||
|
|
border: 2px solid transparent;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: border-color 0.2s, transform 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.view-card:hover {
|
|||
|
|
border-color: var(--wa-color-primary);
|
|||
|
|
transform: translateY(-2px);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.view-card.active {
|
|||
|
|
border-color: var(--wa-color-primary);
|
|||
|
|
background: var(--wa-color-primary-subtle);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.view-card-icon {
|
|||
|
|
width: 48px;
|
|||
|
|
height: 48px;
|
|||
|
|
border-radius: var(--wa-radius-m);
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 24px;
|
|||
|
|
margin-bottom: var(--wa-space-s);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.view-card-icon.welcome { background: var(--wa-color-primary-subtle); color: var(--wa-color-primary); }
|
|||
|
|
.view-card-icon.login { background: var(--wa-color-success-subtle); color: var(--wa-color-success); }
|
|||
|
|
.view-card-icon.register { background: var(--wa-color-warning-subtle); color: var(--wa-color-warning); }
|
|||
|
|
.view-card-icon.authorize { background: var(--wa-color-danger-subtle); color: var(--wa-color-danger); }
|
|||
|
|
|
|||
|
|
.popup-overlay {
|
|||
|
|
display: none;
|
|||
|
|
position: fixed;
|
|||
|
|
inset: 0;
|
|||
|
|
background: rgba(0, 0, 0, 0.6);
|
|||
|
|
backdrop-filter: blur(4px);
|
|||
|
|
z-index: 1000;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.popup-overlay.active {
|
|||
|
|
display: flex;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.popup-window {
|
|||
|
|
animation: popup-in 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes popup-in {
|
|||
|
|
from {
|
|||
|
|
opacity: 0;
|
|||
|
|
transform: scale(0.9) translateY(20px);
|
|||
|
|
}
|
|||
|
|
to {
|
|||
|
|
opacity: 1;
|
|||
|
|
transform: scale(1) translateY(0);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="demo-layout">
|
|||
|
|
<header class="demo-header">
|
|||
|
|
<div class="demo-header-content">
|
|||
|
|
<div class="wa-cluster wa-gap-m">
|
|||
|
|
<wa-icon name="wallet" family="duotone" style="font-size: 28px; color: var(--wa-color-primary);"></wa-icon>
|
|||
|
|
<div class="wa-stack wa-gap-0">
|
|||
|
|
<span class="wa-heading-m">Sonr Auth Demo</span>
|
|||
|
|
<span class="wa-caption-xs" style="color: var(--wa-color-neutral-500);">Mini Browser Webview Showcase</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="wa-cluster wa-gap-s">
|
|||
|
|
<wa-button appearance="plain" size="small" onclick="window.open('https://docs.sonr.io', '_blank')">
|
|||
|
|
<wa-icon slot="start" name="book"></wa-icon>
|
|||
|
|
Docs
|
|||
|
|
</wa-button>
|
|||
|
|
<wa-button appearance="plain" size="small" onclick="window.open('https://github.com/sonr-io', '_blank')">
|
|||
|
|
<wa-icon slot="start" name="github" family="brands"></wa-icon>
|
|||
|
|
GitHub
|
|||
|
|
</wa-button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</header>
|
|||
|
|
|
|||
|
|
<main class="demo-main">
|
|||
|
|
<div class="wa-stack wa-gap-m" style="text-align: center; margin-bottom: var(--wa-space-xl);">
|
|||
|
|
<h1 class="wa-heading-xl">Authentication Webview Demo</h1>
|
|||
|
|
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600); max-width: 600px; margin: 0 auto;">
|
|||
|
|
Preview how Sonr authentication flows appear in popup windows and embedded webviews,
|
|||
|
|
similar to "Sign in with Google" or "Connect Wallet" experiences.
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="demo-controls">
|
|||
|
|
<div class="controls-grid">
|
|||
|
|
<div class="wa-stack wa-gap-xs">
|
|||
|
|
<label class="wa-label-s">View</label>
|
|||
|
|
<wa-select id="view-select" value="welcome">
|
|||
|
|
<wa-option value="welcome">Welcome (Onboarding)</wa-option>
|
|||
|
|
<wa-option value="login">Login (Sign In)</wa-option>
|
|||
|
|
<wa-option value="register">Register (Create Account)</wa-option>
|
|||
|
|
<wa-option value="authorize">Authorize (OAuth Consent)</wa-option>
|
|||
|
|
</wa-select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="wa-stack wa-gap-xs">
|
|||
|
|
<label class="wa-label-s">Window Size</label>
|
|||
|
|
<wa-select id="size-select" value="medium">
|
|||
|
|
<wa-option value="small">Small (360 × 540)</wa-option>
|
|||
|
|
<wa-option value="medium">Medium (420 × 600)</wa-option>
|
|||
|
|
<wa-option value="large">Large (480 × 680)</wa-option>
|
|||
|
|
<wa-option value="mobile">Mobile (375 × 667)</wa-option>
|
|||
|
|
</wa-select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="wa-stack wa-gap-xs">
|
|||
|
|
<label class="wa-label-s">Actions</label>
|
|||
|
|
<div class="wa-cluster wa-gap-s">
|
|||
|
|
<wa-button variant="brand" id="popup-btn">
|
|||
|
|
<wa-icon slot="start" name="arrow-up-right-from-square"></wa-icon>
|
|||
|
|
Open as Popup
|
|||
|
|
</wa-button>
|
|||
|
|
<wa-button variant="neutral" appearance="outlined" id="new-tab-btn">
|
|||
|
|
<wa-icon slot="start" name="arrow-up-right"></wa-icon>
|
|||
|
|
New Tab
|
|||
|
|
</wa-button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="webview-container">
|
|||
|
|
<div class="webview-wrapper" id="webview-wrapper">
|
|||
|
|
<div class="webview-titlebar">
|
|||
|
|
<div class="titlebar-buttons">
|
|||
|
|
<div class="titlebar-btn close"></div>
|
|||
|
|
<div class="titlebar-btn minimize"></div>
|
|||
|
|
<div class="titlebar-btn maximize"></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="titlebar-url" id="titlebar-url">
|
|||
|
|
<wa-icon name="lock"></wa-icon>
|
|||
|
|
<span id="url-display">auth.sonr.io/welcome</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<iframe
|
|||
|
|
id="webview-frame"
|
|||
|
|
class="webview-frame"
|
|||
|
|
src="welcome.html"
|
|||
|
|
width="420"
|
|||
|
|
height="600"
|
|||
|
|
></iframe>
|
|||
|
|
</div>
|
|||
|
|
<div class="size-indicator" id="size-indicator">420 × 600</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="view-grid">
|
|||
|
|
<div class="view-card active" data-view="welcome">
|
|||
|
|
<div class="view-card-icon welcome">
|
|||
|
|
<wa-icon name="hand-wave"></wa-icon>
|
|||
|
|
</div>
|
|||
|
|
<span class="wa-heading-s">Welcome</span>
|
|||
|
|
<p class="wa-caption-s" style="color: var(--wa-color-neutral-500); margin: var(--wa-space-xs) 0 0 0;">
|
|||
|
|
Onboarding flow with feature highlights and navigation to login/register
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="view-card" data-view="login">
|
|||
|
|
<div class="view-card-icon login">
|
|||
|
|
<wa-icon name="right-to-bracket"></wa-icon>
|
|||
|
|
</div>
|
|||
|
|
<span class="wa-heading-s">Login</span>
|
|||
|
|
<p class="wa-caption-s" style="color: var(--wa-color-neutral-500); margin: var(--wa-space-xs) 0 0 0;">
|
|||
|
|
WebAuthn sign-in with passkey, security key, and QR code options
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="view-card" data-view="register">
|
|||
|
|
<div class="view-card-icon register">
|
|||
|
|
<wa-icon name="user-plus"></wa-icon>
|
|||
|
|
</div>
|
|||
|
|
<span class="wa-heading-s">Register</span>
|
|||
|
|
<p class="wa-caption-s" style="color: var(--wa-color-neutral-500); margin: var(--wa-space-xs) 0 0 0;">
|
|||
|
|
Account creation wizard with device capability detection
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="view-card" data-view="authorize">
|
|||
|
|
<div class="view-card-icon authorize">
|
|||
|
|
<wa-icon name="shield-check"></wa-icon>
|
|||
|
|
</div>
|
|||
|
|
<span class="wa-heading-s">Authorize</span>
|
|||
|
|
<p class="wa-caption-s" style="color: var(--wa-color-neutral-500); margin: var(--wa-space-xs) 0 0 0;">
|
|||
|
|
OAuth consent screen for connect, sign, and transaction requests
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="integration-section">
|
|||
|
|
<wa-card>
|
|||
|
|
<div slot="header">
|
|||
|
|
<span class="wa-heading-m">Integration Examples</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<wa-tab-group>
|
|||
|
|
<wa-tab panel="popup">Popup Window</wa-tab>
|
|||
|
|
<wa-tab panel="iframe">Embedded Iframe</wa-tab>
|
|||
|
|
<wa-tab panel="redirect">Redirect Flow</wa-tab>
|
|||
|
|
|
|||
|
|
<wa-tab-panel name="popup">
|
|||
|
|
<div class="wa-stack wa-gap-m">
|
|||
|
|
<p class="wa-caption-s" style="color: var(--wa-color-neutral-600);">
|
|||
|
|
Open authentication in a centered popup window, similar to "Sign in with Google".
|
|||
|
|
</p>
|
|||
|
|
<div class="code-block">
|
|||
|
|
<pre><span class="comment">// Open Sonr Auth as popup window</span>
|
|||
|
|
<span class="keyword">function</span> <span class="function">openSonrAuth</span>() {
|
|||
|
|
<span class="keyword">const</span> width = <span class="number">420</span>;
|
|||
|
|
<span class="keyword">const</span> height = <span class="number">600</span>;
|
|||
|
|
<span class="keyword">const</span> left = (screen.width - width) / <span class="number">2</span>;
|
|||
|
|
<span class="keyword">const</span> top = (screen.height - height) / <span class="number">2</span>;
|
|||
|
|
|
|||
|
|
<span class="keyword">const</span> popup = window.<span class="function">open</span>(
|
|||
|
|
<span class="string">'https://auth.sonr.io/authorize?client_id=YOUR_APP&scope=openid+profile'</span>,
|
|||
|
|
<span class="string">'SonrAuth'</span>,
|
|||
|
|
<span class="string">`width=${width},height=${height},left=${left},top=${top}`</span>
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
<span class="comment">// Listen for auth completion</span>
|
|||
|
|
window.<span class="function">addEventListener</span>(<span class="string">'message'</span>, (event) => {
|
|||
|
|
<span class="keyword">if</span> (event.origin === <span class="string">'https://auth.sonr.io'</span>) {
|
|||
|
|
<span class="keyword">const</span> { token, user } = event.data;
|
|||
|
|
<span class="function">handleAuthSuccess</span>(token, user);
|
|||
|
|
popup.<span class="function">close</span>();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}</pre>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</wa-tab-panel>
|
|||
|
|
|
|||
|
|
<wa-tab-panel name="iframe">
|
|||
|
|
<div class="wa-stack wa-gap-m">
|
|||
|
|
<p class="wa-caption-s" style="color: var(--wa-color-neutral-600);">
|
|||
|
|
Embed authentication inline within a modal or container.
|
|||
|
|
</p>
|
|||
|
|
<div class="code-block">
|
|||
|
|
<pre><span class="comment">// Embed Sonr Auth in iframe</span>
|
|||
|
|
<span class="keyword">const</span> container = document.<span class="function">getElementById</span>(<span class="string">'auth-container'</span>);
|
|||
|
|
|
|||
|
|
<span class="keyword">const</span> iframe = document.<span class="function">createElement</span>(<span class="string">'iframe'</span>);
|
|||
|
|
iframe.src = <span class="string">'https://auth.sonr.io/authorize?client_id=YOUR_APP&mode=embedded'</span>;
|
|||
|
|
iframe.width = <span class="number">420</span>;
|
|||
|
|
iframe.height = <span class="number">600</span>;
|
|||
|
|
iframe.style.border = <span class="string">'none'</span>;
|
|||
|
|
iframe.style.borderRadius = <span class="string">'12px'</span>;
|
|||
|
|
|
|||
|
|
container.<span class="function">appendChild</span>(iframe);
|
|||
|
|
|
|||
|
|
<span class="comment">// Handle postMessage from iframe</span>
|
|||
|
|
window.<span class="function">addEventListener</span>(<span class="string">'message'</span>, (event) => {
|
|||
|
|
<span class="keyword">if</span> (event.origin === <span class="string">'https://auth.sonr.io'</span>) {
|
|||
|
|
<span class="keyword">if</span> (event.data.type === <span class="string">'AUTH_SUCCESS'</span>) {
|
|||
|
|
<span class="function">handleAuthSuccess</span>(event.data.payload);
|
|||
|
|
} <span class="keyword">else if</span> (event.data.type === <span class="string">'AUTH_CANCEL'</span>) {
|
|||
|
|
<span class="function">handleAuthCancel</span>();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});</pre>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</wa-tab-panel>
|
|||
|
|
|
|||
|
|
<wa-tab-panel name="redirect">
|
|||
|
|
<div class="wa-stack wa-gap-m">
|
|||
|
|
<p class="wa-caption-s" style="color: var(--wa-color-neutral-600);">
|
|||
|
|
Standard OAuth 2.0 redirect flow for server-side applications.
|
|||
|
|
</p>
|
|||
|
|
<div class="code-block">
|
|||
|
|
<pre><span class="comment">// Redirect to Sonr Auth (OAuth 2.0 flow)</span>
|
|||
|
|
<span class="keyword">function</span> <span class="function">redirectToSonrAuth</span>() {
|
|||
|
|
<span class="keyword">const</span> params = <span class="keyword">new</span> <span class="function">URLSearchParams</span>({
|
|||
|
|
client_id: <span class="string">'YOUR_CLIENT_ID'</span>,
|
|||
|
|
redirect_uri: <span class="string">'https://yourapp.com/callback'</span>,
|
|||
|
|
response_type: <span class="string">'code'</span>,
|
|||
|
|
scope: <span class="string">'openid profile wallet:read'</span>,
|
|||
|
|
state: <span class="function">generateRandomState</span>(),
|
|||
|
|
nonce: <span class="function">generateRandomNonce</span>()
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
window.location.href = <span class="string">`https://auth.sonr.io/authorize?${params}`</span>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
<span class="comment">// Handle callback on your server</span>
|
|||
|
|
<span class="comment">// GET /callback?code=AUTH_CODE&state=STATE</span>
|
|||
|
|
<span class="keyword">async function</span> <span class="function">handleCallback</span>(code, state) {
|
|||
|
|
<span class="keyword">const</span> tokens = <span class="keyword">await</span> <span class="function">exchangeCodeForTokens</span>(code);
|
|||
|
|
<span class="keyword">const</span> user = <span class="keyword">await</span> <span class="function">getUserInfo</span>(tokens.access_token);
|
|||
|
|
<span class="keyword">return</span> { tokens, user };
|
|||
|
|
}</pre>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</wa-tab-panel>
|
|||
|
|
</wa-tab-group>
|
|||
|
|
</wa-card>
|
|||
|
|
</div>
|
|||
|
|
</main>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="popup-overlay" id="popup-overlay">
|
|||
|
|
<div class="popup-window">
|
|||
|
|
<div class="webview-wrapper">
|
|||
|
|
<div class="webview-titlebar">
|
|||
|
|
<div class="titlebar-buttons">
|
|||
|
|
<div class="titlebar-btn close" id="popup-close"></div>
|
|||
|
|
<div class="titlebar-btn minimize"></div>
|
|||
|
|
<div class="titlebar-btn maximize"></div>
|
|||
|
|
</div>
|
|||
|
|
<div class="titlebar-url">
|
|||
|
|
<wa-icon name="lock"></wa-icon>
|
|||
|
|
<span id="popup-url-display">auth.sonr.io/welcome</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<iframe
|
|||
|
|
id="popup-frame"
|
|||
|
|
class="webview-frame"
|
|||
|
|
src="welcome.html"
|
|||
|
|
width="420"
|
|||
|
|
height="600"
|
|||
|
|
></iframe>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
const viewSelect = document.getElementById('view-select');
|
|||
|
|
const sizeSelect = document.getElementById('size-select');
|
|||
|
|
const webviewFrame = document.getElementById('webview-frame');
|
|||
|
|
const popupFrame = document.getElementById('popup-frame');
|
|||
|
|
const urlDisplay = document.getElementById('url-display');
|
|||
|
|
const popupUrlDisplay = document.getElementById('popup-url-display');
|
|||
|
|
const sizeIndicator = document.getElementById('size-indicator');
|
|||
|
|
const viewCards = document.querySelectorAll('.view-card');
|
|||
|
|
const popupOverlay = document.getElementById('popup-overlay');
|
|||
|
|
const popupClose = document.getElementById('popup-close');
|
|||
|
|
const popupBtn = document.getElementById('popup-btn');
|
|||
|
|
const newTabBtn = document.getElementById('new-tab-btn');
|
|||
|
|
|
|||
|
|
const views = {
|
|||
|
|
welcome: { url: 'welcome.html', path: 'auth.sonr.io/welcome' },
|
|||
|
|
login: { url: 'login.html', path: 'auth.sonr.io/login' },
|
|||
|
|
register: { url: 'register.html', path: 'auth.sonr.io/register' },
|
|||
|
|
authorize: { url: 'authorize.html', path: 'auth.sonr.io/authorize' }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const sizes = {
|
|||
|
|
small: { width: 360, height: 540 },
|
|||
|
|
medium: { width: 420, height: 600 },
|
|||
|
|
large: { width: 480, height: 680 },
|
|||
|
|
mobile: { width: 375, height: 667 }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
function updateView(view) {
|
|||
|
|
const viewData = views[view];
|
|||
|
|
if (!viewData) return;
|
|||
|
|
|
|||
|
|
webviewFrame.src = viewData.url;
|
|||
|
|
popupFrame.src = viewData.url;
|
|||
|
|
urlDisplay.textContent = viewData.path;
|
|||
|
|
popupUrlDisplay.textContent = viewData.path;
|
|||
|
|
|
|||
|
|
viewCards.forEach(card => {
|
|||
|
|
card.classList.toggle('active', card.dataset.view === view);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
viewSelect.value = view;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function updateSize(size) {
|
|||
|
|
const sizeData = sizes[size];
|
|||
|
|
if (!sizeData) return;
|
|||
|
|
|
|||
|
|
webviewFrame.width = sizeData.width;
|
|||
|
|
webviewFrame.height = sizeData.height;
|
|||
|
|
popupFrame.width = sizeData.width;
|
|||
|
|
popupFrame.height = sizeData.height;
|
|||
|
|
sizeIndicator.textContent = `${sizeData.width} × ${sizeData.height}`;
|
|||
|
|
sizeSelect.value = size;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
viewSelect.addEventListener('wa-change', (e) => {
|
|||
|
|
updateView(e.target.value);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
sizeSelect.addEventListener('wa-change', (e) => {
|
|||
|
|
updateSize(e.target.value);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
viewCards.forEach(card => {
|
|||
|
|
card.addEventListener('click', () => {
|
|||
|
|
updateView(card.dataset.view);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
popupBtn.addEventListener('click', () => {
|
|||
|
|
popupFrame.src = webviewFrame.src;
|
|||
|
|
popupUrlDisplay.textContent = urlDisplay.textContent;
|
|||
|
|
popupOverlay.classList.add('active');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
popupClose.addEventListener('click', () => {
|
|||
|
|
popupOverlay.classList.remove('active');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
popupOverlay.addEventListener('click', (e) => {
|
|||
|
|
if (e.target === popupOverlay) {
|
|||
|
|
popupOverlay.classList.remove('active');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
newTabBtn.addEventListener('click', () => {
|
|||
|
|
const currentView = viewSelect.value;
|
|||
|
|
window.open(views[currentView].url, '_blank');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
document.addEventListener('keydown', (e) => {
|
|||
|
|
if (e.key === 'Escape' && popupOverlay.classList.contains('active')) {
|
|||
|
|
popupOverlay.classList.remove('active');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
updateView('welcome');
|
|||
|
|
updateSize('medium');
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|