Simple vanilia JS vite project with ATProto OAuth out of the box
at main 4.7 kB view raw
1const appElement = document.getElementById('app') 2 3/** 4 * Shows the login form and sets a event listener for form submission to start the OAuth flow. 5 */ 6function showLoginForm() { 7 appElement.innerHTML = ` 8 <div class="container"> 9 <h1>ATProto OAuth Playground</h1> 10 <form id="login-form"> 11 <div class="form-group"> 12 <label for="handle">ATProto Handle</label> 13 <input 14 type="text" 15 id="handle" 16 name="handle" 17 placeholder="jcsalterego.bsky.social" 18 required 19 /> 20 <div id="error" class="error"></div> 21 </div> 22 <button type="submit">Sign In</button> 23 </form> 24 </div> 25 ` 26 27 document.querySelector('#login-form').addEventListener('submit', async (e) => { 28 e.preventDefault() 29 30 const handle = document.querySelector('#handle').value.trim() 31 const errorEl = document.querySelector('#error') 32 errorEl.textContent = '' 33 34 try { 35 // This will redirect to the OAuth authorization page on the PDS 36 await window.oauthClient.signIn(handle) 37 } catch (error) { 38 console.error('Sign in error:', error) 39 errorEl.textContent = error.message || 'Failed to sign in. Please check your handle and try again.' 40 } 41 }) 42} 43 44/** 45 * Demo component to show an authenticated request by fetching the user's notifications. 46 * @returns {Promise<string>} 47 */ 48async function notificationsList(){ 49 50 const notifications = await window.atpAgent.app.bsky.notification.listNotifications({ 51 limit: 5 52 }) 53 54 return notifications.data.notifications.map(notif => { 55 const reasonText = { 56 'like': 'liked your post', 57 'repost': 'reposted your post', 58 'follow': 'followed you', 59 'mention': 'mentioned you', 60 'reply': 'replied to your post', 61 'quote': 'quoted your post' 62 }[notif.reason] || notif.reason 63 64 return ` 65 <div class="notification-item ${!notif.isRead ? 'unread' : ''}"> 66 <img src="${notif.author.avatar || '/vite.svg'}" alt="${notif.author.displayName}" class="notification-avatar" /> 67 <div class="notification-content"> 68 <p class="notification-text"> 69 <strong>${notif.author.displayName || notif.author.handle}</strong> ${reasonText} 70 </p> 71 <p class="notification-time">${new Date(notif.indexedAt).toLocaleString()}</p> 72 </div> 73 </div> 74 ` 75 }).join('') 76 77} 78 79/** 80 * Shows the logged in page with the user's profile and notifications. 81 */ 82async function showLoggedInPage(session) { 83 const profile = await window.atpAgent.getProfile({ 84 actor: session.sub 85 }) 86 87 const { avatar, displayName, handle, followersCount, followsCount } = profile.data 88 89 90 appElement.innerHTML = ` 91 <div class="container"> 92 <h1>Logged In</h1> 93 <div class="profile-card"> 94 <img src="${avatar || '/vite.svg'}" alt="Profile picture" class="profile-avatar" /> 95 <div class="profile-info"> 96 <h2 class="profile-name">${displayName || handle}</h2> 97 <p class="profile-handle">@${handle}</p> 98 <div class="profile-stats"> 99 <span><strong>${followersCount || 0}</strong> Followers</span> 100 <span><strong>${followsCount || 0}</strong> Following</span> 101 </div> 102 </div> 103 </div> 104 <div class="notifications-section"> 105 <h3>Recent Notifications</h3> 106 <div class="notifications-list"> 107 ${await notificationsList()} 108 </div> 109 </div> 110 <button id="logout">Sign Out</button> 111 </div> 112 ` 113 114 document.querySelector('#logout').addEventListener('click', async () => { 115 try { 116 await window.oauthClient.revoke(session.sub) 117 showLoginForm() 118 } catch (error) { 119 console.error('Sign out error:', error) 120 } 121 }) 122} 123 124/** 125 * 126 * DANGER WILL ROBINSON 127 */ 128function showError(message) { 129 appElement.innerHTML = ` 130 <div class="container"> 131 <h1>ATProto OAuth Playground</h1> 132 <div class="error">${message}</div> 133 <a href="/">Back to login</a> 134 </div> 135 ` 136} 137 138 139export { showLoginForm, showLoggedInPage, showError }