| 1 | <!DOCTYPE html> |
| 2 | <% |
| 3 | const pageLocale = typeof locale !== 'undefined' ? locale : 'en-US' |
| 4 | const t = typeof messages !== 'undefined' ? messages : { |
| 5 | title: 'Authorization', |
| 6 | connectedTitle: 'Authorization completed', |
| 7 | redirectingDescription: 'Redirecting you back now.', |
| 8 | closeDescription: 'You can close this page now.', |
| 9 | connectedAccounts: 'Connected accounts', |
| 10 | accountId: 'Account ID', |
| 11 | platformUid: 'Platform ID', |
| 12 | account: 'Account' |
| 13 | } |
| 14 | const rawAccounts = typeof accounts !== 'undefined' ? accounts : [] |
| 15 | const callbackAccounts = Array.isArray(rawAccounts) ? rawAccounts : [] |
| 16 | const callbackPayloadAccounts = callbackAccounts.map(function(account) { |
| 17 | return { |
| 18 | relayAccountRef: account.accountId, |
| 19 | nickname: account.displayName, |
| 20 | avatar: account.avatarUrl, |
| 21 | platformUid: account.platformUid, |
| 22 | account: account.account, |
| 23 | platform: account.platform |
| 24 | } |
| 25 | }) |
| 26 | const firstCallbackAccount = callbackPayloadAccounts[0] || {} |
| 27 | const pageCallbackUrl = typeof callbackUrl !== 'undefined' ? callbackUrl : '' |
| 28 | const pageRedirectUri = typeof redirectUri !== 'undefined' ? redirectUri : '' |
| 29 | const shouldRedirect = pageCallbackUrl || pageRedirectUri |
| 30 | const pagePlatformDisplayName = typeof platformDisplayName !== 'undefined' ? platformDisplayName : '' |
| 31 | const pagePlatformLogoUrl = typeof platformLogoUrl !== 'undefined' ? platformLogoUrl : '' |
| 32 | function toJsStringLiteral(value) { |
| 33 | return JSON.stringify(String(value)).replace(/[<>&]/g, function(char) { |
| 34 | return { |
| 35 | '<': '\\u003C', |
| 36 | '>': '\\u003E', |
| 37 | '&': '\\u0026' |
| 38 | }[char] |
| 39 | }) |
| 40 | } |
| 41 | %> |
| 42 | <html lang="<%= pageLocale %>"> |
| 43 | |
| 44 | <head> |
| 45 | <meta charset="UTF-8" /> |
| 46 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| 47 | <title><%= t.connectedTitle %></title> |
| 48 | <style> |
| 49 | * { |
| 50 | box-sizing: border-box; |
| 51 | } |
| 52 | |
| 53 | :root { |
| 54 | --background: #f7f8fb; |
| 55 | --foreground: #171717; |
| 56 | --muted: #6b7280; |
| 57 | --muted-bg: #f3f4f6; |
| 58 | --border: #e5e7eb; |
| 59 | --primary: #2563eb; |
| 60 | --primary-soft: #eff6ff; |
| 61 | --success: #16a34a; |
| 62 | --card: #fff; |
| 63 | } |
| 64 | |
| 65 | body { |
| 66 | margin: 0; |
| 67 | min-height: 100vh; |
| 68 | font-family: Suisseintl, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; |
| 69 | color: var(--foreground); |
| 70 | background: |
| 71 | radial-gradient(circle at 1px 1px, rgba(17, 24, 39, 0.08) 1px, transparent 0), |
| 72 | var(--background); |
| 73 | background-size: 18px 18px; |
| 74 | display: flex; |
| 75 | align-items: center; |
| 76 | justify-content: center; |
| 77 | padding: 20px; |
| 78 | } |
| 79 | |
| 80 | main { |
| 81 | width: min(100%, 520px); |
| 82 | background: var(--card); |
| 83 | border: 1px solid var(--border); |
| 84 | border-radius: 12px; |
| 85 | box-shadow: 0 18px 45px rgba(15, 23, 42, 0.08); |
| 86 | overflow: hidden; |
| 87 | } |
| 88 | |
| 89 | header { |
| 90 | padding: 24px; |
| 91 | border-bottom: 1px solid var(--border); |
| 92 | } |
| 93 | |
| 94 | .platform { |
| 95 | display: flex; |
| 96 | align-items: center; |
| 97 | gap: 12px; |
| 98 | margin-bottom: 16px; |
| 99 | min-width: 0; |
| 100 | } |
| 101 | |
| 102 | .platform-logo { |
| 103 | width: 38px; |
| 104 | height: 38px; |
| 105 | border-radius: 10px; |
| 106 | border: 1px solid var(--border); |
| 107 | object-fit: contain; |
| 108 | background: #fff; |
| 109 | flex: 0 0 auto; |
| 110 | } |
| 111 | |
| 112 | .status { |
| 113 | display: inline-flex; |
| 114 | align-items: center; |
| 115 | gap: 8px; |
| 116 | min-height: 28px; |
| 117 | padding: 4px 10px; |
| 118 | border-radius: 999px; |
| 119 | color: #166534; |
| 120 | background: #dcfce7; |
| 121 | font-size: 13px; |
| 122 | font-weight: 700; |
| 123 | } |
| 124 | |
| 125 | .dot { |
| 126 | width: 8px; |
| 127 | height: 8px; |
| 128 | border-radius: 50%; |
| 129 | background: var(--success); |
| 130 | } |
| 131 | |
| 132 | h1 { |
| 133 | margin: 0 0 10px; |
| 134 | font-size: 24px; |
| 135 | line-height: 1.25; |
| 136 | } |
| 137 | |
| 138 | p { |
| 139 | margin: 0; |
| 140 | color: var(--muted); |
| 141 | font-size: 14px; |
| 142 | line-height: 1.6; |
| 143 | } |
| 144 | |
| 145 | .content { |
| 146 | padding: 20px 24px 24px; |
| 147 | } |
| 148 | |
| 149 | .account-id { |
| 150 | margin: 0 0 14px; |
| 151 | padding: 12px; |
| 152 | border-radius: 10px; |
| 153 | background: var(--muted-bg); |
| 154 | color: #374151; |
| 155 | font-size: 13px; |
| 156 | word-break: break-all; |
| 157 | } |
| 158 | |
| 159 | .section-title { |
| 160 | margin: 0 0 10px; |
| 161 | color: var(--foreground); |
| 162 | font-size: 14px; |
| 163 | font-weight: 700; |
| 164 | } |
| 165 | |
| 166 | .account-list { |
| 167 | display: grid; |
| 168 | gap: 10px; |
| 169 | margin: 0; |
| 170 | padding: 0; |
| 171 | list-style: none; |
| 172 | } |
| 173 | |
| 174 | .account-row { |
| 175 | display: flex; |
| 176 | align-items: center; |
| 177 | gap: 12px; |
| 178 | min-height: 64px; |
| 179 | border: 1px solid var(--border); |
| 180 | border-radius: 10px; |
| 181 | padding: 12px; |
| 182 | background: #fff; |
| 183 | } |
| 184 | |
| 185 | .avatar { |
| 186 | width: 38px; |
| 187 | height: 38px; |
| 188 | border-radius: 50%; |
| 189 | background: var(--muted-bg); |
| 190 | border: 1px solid var(--border); |
| 191 | flex: 0 0 auto; |
| 192 | object-fit: cover; |
| 193 | } |
| 194 | |
| 195 | .account-text { |
| 196 | min-width: 0; |
| 197 | display: block; |
| 198 | } |
| 199 | |
| 200 | .name { |
| 201 | display: block; |
| 202 | font-size: 15px; |
| 203 | font-weight: 600; |
| 204 | line-height: 1.35; |
| 205 | overflow: hidden; |
| 206 | text-overflow: ellipsis; |
| 207 | white-space: nowrap; |
| 208 | } |
| 209 | |
| 210 | .uid { |
| 211 | display: block; |
| 212 | margin-top: 3px; |
| 213 | color: var(--muted); |
| 214 | font-size: 12px; |
| 215 | overflow: hidden; |
| 216 | text-overflow: ellipsis; |
| 217 | white-space: nowrap; |
| 218 | } |
| 219 | |
| 220 | .loading { |
| 221 | width: 18px; |
| 222 | height: 18px; |
| 223 | margin-left: auto; |
| 224 | border: 2px solid #dbeafe; |
| 225 | border-top-color: var(--primary); |
| 226 | border-radius: 50%; |
| 227 | animation: spin 0.9s linear infinite; |
| 228 | } |
| 229 | |
| 230 | @keyframes spin { |
| 231 | to { |
| 232 | transform: rotate(360deg); |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | @media (max-width: 520px) { |
| 237 | body { |
| 238 | align-items: stretch; |
| 239 | padding: 12px; |
| 240 | } |
| 241 | |
| 242 | header, |
| 243 | .content { |
| 244 | padding-left: 18px; |
| 245 | padding-right: 18px; |
| 246 | } |
| 247 | } |
| 248 | </style> |
| 249 | </head> |
| 250 | |
| 251 | <body> |
| 252 | <main> |
| 253 | <header> |
| 254 | <div class="platform"> |
| 255 | <% if (pagePlatformLogoUrl) { %> |
| 256 | <img class="platform-logo" src="<%= pagePlatformLogoUrl %>" alt="" /> |
| 257 | <% } %> |
| 258 | <span class="status"><span class="dot"></span><%= pagePlatformDisplayName || t.title %></span> |
| 259 | </div> |
| 260 | <h1><%= t.connectedTitle %></h1> |
| 261 | <p><%= shouldRedirect ? t.redirectingDescription : t.closeDescription %></p> |
| 262 | </header> |
| 263 | |
| 264 | <div class="content"> |
| 265 | <% if (typeof accountId !== 'undefined' && accountId) { %> |
| 266 | <div class="account-id"><%= t.accountId %>: <%= accountId %></div> |
| 267 | <% } %> |
| 268 | |
| 269 | <% if (callbackAccounts.length > 0) { %> |
| 270 | <p class="section-title"><%= t.connectedAccounts %></p> |
| 271 | <ul class="account-list"> |
| 272 | <% callbackAccounts.forEach(function(account) { %> |
| 273 | <li class="account-row"> |
| 274 | <% if (account.avatarUrl) { %> |
| 275 | <img class="avatar" src="<%= account.avatarUrl %>" alt="" /> |
| 276 | <% } else { %> |
| 277 | <span class="avatar"></span> |
| 278 | <% } %> |
| 279 | <span class="account-text"> |
| 280 | <span class="name"><%= account.displayName %></span> |
| 281 | <span class="uid"><%= t.platformUid %>: <%= account.platformUid %></span> |
| 282 | <% if (account.account) { %> |
| 283 | <span class="uid"><%= t.account %>: <%= account.account %></span> |
| 284 | <% } %> |
| 285 | </span> |
| 286 | <% if (shouldRedirect) { %> |
| 287 | <span class="loading"></span> |
| 288 | <% } %> |
| 289 | </li> |
| 290 | <% }) %> |
| 291 | </ul> |
| 292 | <% } %> |
| 293 | </div> |
| 294 | |
| 295 | <% if (pageCallbackUrl) { %> |
| 296 | <form id="callbackForm" method="POST" action="<%= pageCallbackUrl %>" style="display:none"> |
| 297 | <input type="hidden" name="relayAccountRef" value="<%= firstCallbackAccount.relayAccountRef || '' %>" /> |
| 298 | <input type="hidden" name="nickname" value="<%= firstCallbackAccount.nickname || '' %>" /> |
| 299 | <input type="hidden" name="avatar" value="<%= firstCallbackAccount.avatar || '' %>" /> |
| 300 | <input type="hidden" name="platformUid" value="<%= firstCallbackAccount.platformUid || '' %>" /> |
| 301 | <input type="hidden" name="account" value="<%= firstCallbackAccount.account || '' %>" /> |
| 302 | <input type="hidden" name="platform" value="<%= firstCallbackAccount.platform || '' %>" /> |
| 303 | <% if (pageRedirectUri) { %> |
| 304 | <input type="hidden" name="redirectUri" value="<%= pageRedirectUri %>" /> |
| 305 | <% } %> |
| 306 | <input type="hidden" name="accounts" value="<%= JSON.stringify(callbackPayloadAccounts) %>" /> |
| 307 | </form> |
| 308 | <% } %> |
| 309 | </main> |
| 310 | |
| 311 | <script> |
| 312 | const callbackUrl = <%- toJsStringLiteral(pageCallbackUrl) %>; |
| 313 | const redirectUri = <%- toJsStringLiteral(pageRedirectUri) %>; |
| 314 | |
| 315 | if (callbackUrl) { |
| 316 | document.getElementById('callbackForm').submit(); |
| 317 | } else if (redirectUri) { |
| 318 | setTimeout(function() { |
| 319 | window.location.replace(redirectUri); |
| 320 | }, 800); |
| 321 | } else { |
| 322 | setTimeout(function() { |
| 323 | window.close(); |
| 324 | }, 3000); |
| 325 | } |
| 326 | </script> |
| 327 | </body> |
| 328 | |
| 329 | </html> |
| 330 |