返回 AiToEarn
callback.ejs
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
330 lines Plain Text