n8n-nodes-github-copilot 3.38.24 → 3.38.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/credentials/GitHubCopilotApi.credentials.d.ts +1 -1
- package/dist/credentials/GitHubCopilotApi.credentials.js +25 -25
- package/dist/nodes/GitHubCopilot/GitHubCopilot.node.d.ts +1 -1
- package/dist/nodes/GitHubCopilot/GitHubCopilot.node.js +166 -166
- package/dist/nodes/GitHubCopilotAuthHelper/GitHubCopilotAuthHelper.node.d.ts +1 -1
- package/dist/nodes/GitHubCopilotAuthHelper/GitHubCopilotAuthHelper.node.js +539 -539
- package/dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.d.ts +1 -1
- package/dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.js +46 -44
- package/dist/nodes/GitHubCopilotChatAPI/nodeProperties.d.ts +1 -1
- package/dist/nodes/GitHubCopilotChatAPI/nodeProperties.js +82 -82
- package/dist/nodes/GitHubCopilotChatAPI/utils/helpers.d.ts +2 -2
- package/dist/nodes/GitHubCopilotChatAPI/utils/helpers.js +26 -26
- package/dist/nodes/GitHubCopilotChatAPI/utils/imageProcessor.d.ts +2 -2
- package/dist/nodes/GitHubCopilotChatAPI/utils/imageProcessor.js +12 -12
- package/dist/nodes/GitHubCopilotChatAPI/utils/index.d.ts +4 -4
- package/dist/nodes/GitHubCopilotChatAPI/utils/mediaDetection.d.ts +3 -3
- package/dist/nodes/GitHubCopilotChatAPI/utils/mediaDetection.js +19 -19
- package/dist/nodes/GitHubCopilotChatAPI/utils/modelCapabilities.d.ts +1 -1
- package/dist/nodes/GitHubCopilotChatAPI/utils/modelCapabilities.js +23 -23
- package/dist/nodes/GitHubCopilotChatAPI/utils/types.d.ts +5 -5
- package/dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.d.ts +1 -1
- package/dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.js +163 -110
- package/dist/nodes/GitHubCopilotEmbeddings/GitHubCopilotEmbeddings.node.d.ts +1 -1
- package/dist/nodes/GitHubCopilotEmbeddings/GitHubCopilotEmbeddings.node.js +114 -114
- package/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.d.ts +1 -1
- package/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.js +74 -69
- package/dist/nodes/GitHubCopilotOpenAI/nodeProperties.d.ts +1 -1
- package/dist/nodes/GitHubCopilotOpenAI/nodeProperties.js +181 -181
- package/dist/nodes/GitHubCopilotOpenAI/utils/index.d.ts +2 -2
- package/dist/nodes/GitHubCopilotOpenAI/utils/openaiCompat.d.ts +10 -10
- package/dist/nodes/GitHubCopilotOpenAI/utils/openaiCompat.js +53 -53
- package/dist/nodes/GitHubCopilotOpenAI/utils/types.d.ts +12 -12
- package/dist/nodes/GitHubCopilotTest/GitHubCopilotTest.node.d.ts +1 -1
- package/dist/nodes/GitHubCopilotTest/GitHubCopilotTest.node.js +120 -116
- package/dist/package.json +1 -1
- package/package.json +1 -1
|
@@ -4,77 +4,77 @@ exports.GitHubCopilotAuthHelper = void 0;
|
|
|
4
4
|
class GitHubCopilotAuthHelper {
|
|
5
5
|
constructor() {
|
|
6
6
|
this.description = {
|
|
7
|
-
displayName:
|
|
8
|
-
name:
|
|
9
|
-
icon:
|
|
10
|
-
group: [
|
|
7
|
+
displayName: 'GitHub Copilot Auth Helper',
|
|
8
|
+
name: 'githubCopilotAuthHelper',
|
|
9
|
+
icon: 'file:../../shared/icons/copilot.svg',
|
|
10
|
+
group: ['trigger'],
|
|
11
11
|
version: 1,
|
|
12
|
-
description:
|
|
12
|
+
description: 'Interactive OAuth Device Flow - serves HTML page with proxy to avoid CORS',
|
|
13
13
|
defaults: {
|
|
14
|
-
name:
|
|
14
|
+
name: 'GitHub Copilot Auth',
|
|
15
15
|
},
|
|
16
16
|
inputs: [],
|
|
17
17
|
outputs: [],
|
|
18
18
|
webhooks: [
|
|
19
19
|
{
|
|
20
|
-
name:
|
|
21
|
-
httpMethod:
|
|
22
|
-
responseMode:
|
|
23
|
-
path:
|
|
20
|
+
name: 'default',
|
|
21
|
+
httpMethod: 'GET',
|
|
22
|
+
responseMode: 'onReceived',
|
|
23
|
+
path: 'github-auth',
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
|
-
name:
|
|
27
|
-
httpMethod:
|
|
28
|
-
responseMode:
|
|
29
|
-
path:
|
|
26
|
+
name: 'default',
|
|
27
|
+
httpMethod: 'POST',
|
|
28
|
+
responseMode: 'onReceived',
|
|
29
|
+
path: 'github-auth',
|
|
30
30
|
},
|
|
31
31
|
],
|
|
32
32
|
properties: [
|
|
33
33
|
{
|
|
34
|
-
displayName:
|
|
35
|
-
name:
|
|
36
|
-
type:
|
|
37
|
-
default:
|
|
34
|
+
displayName: '✨ Autenticação Visual - Sem Terminal!',
|
|
35
|
+
name: 'notice1',
|
|
36
|
+
type: 'notice',
|
|
37
|
+
default: 'Este node permite que usuários finais obtenham tokens GitHub Copilot sem usar terminal.',
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
|
-
displayName:
|
|
41
|
-
name:
|
|
42
|
-
type:
|
|
40
|
+
displayName: '📋 Passo 1: Ativar Workflow',
|
|
41
|
+
name: 'notice2',
|
|
42
|
+
type: 'notice',
|
|
43
43
|
default: "Clique no botão 'Active' no canto superior direito para ativar o workflow. A Production URL só funciona com workflow ativo!",
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
|
-
displayName:
|
|
47
|
-
name:
|
|
48
|
-
type:
|
|
46
|
+
displayName: '🔗 Passo 2: Copiar URL',
|
|
47
|
+
name: 'notice3',
|
|
48
|
+
type: 'notice',
|
|
49
49
|
default: "Copie a 'Production URL' que aparece abaixo (em Webhook URLs) e envie para o usuário final.",
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
|
-
displayName:
|
|
53
|
-
name:
|
|
54
|
-
type:
|
|
52
|
+
displayName: '🌐 Passo 3: Usuário Acessa URL',
|
|
53
|
+
name: 'notice4',
|
|
54
|
+
type: 'notice',
|
|
55
55
|
default: "O usuário abre a URL no navegador e segue as instruções visuais: 1) Clica 'Começar', 2) Copia o código, 3) Autoriza no GitHub, 4) Copia o token gerado automaticamente.",
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
|
-
displayName:
|
|
59
|
-
name:
|
|
60
|
-
type:
|
|
61
|
-
default:
|
|
58
|
+
displayName: '💡 Sem CORS!',
|
|
59
|
+
name: 'notice5',
|
|
60
|
+
type: 'notice',
|
|
61
|
+
default: 'O n8n faz proxy das requisições para GitHub API, então não há problemas de CORS quando usuário acessa a página pelo navegador.',
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
|
-
displayName:
|
|
65
|
-
name:
|
|
66
|
-
type:
|
|
67
|
-
default:
|
|
64
|
+
displayName: 'Client ID',
|
|
65
|
+
name: 'clientId',
|
|
66
|
+
type: 'string',
|
|
67
|
+
default: '01ab8ac9400c4e429b23',
|
|
68
68
|
required: true,
|
|
69
|
-
description:
|
|
69
|
+
description: 'GitHub OAuth Client ID (VS Code official)',
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
|
-
displayName:
|
|
73
|
-
name:
|
|
74
|
-
type:
|
|
75
|
-
default:
|
|
72
|
+
displayName: 'Scopes',
|
|
73
|
+
name: 'scopes',
|
|
74
|
+
type: 'string',
|
|
75
|
+
default: 'repo user:email',
|
|
76
76
|
required: true,
|
|
77
|
-
description:
|
|
77
|
+
description: 'OAuth scopes required for GitHub Copilot',
|
|
78
78
|
},
|
|
79
79
|
],
|
|
80
80
|
};
|
|
@@ -82,18 +82,18 @@ class GitHubCopilotAuthHelper {
|
|
|
82
82
|
async webhook() {
|
|
83
83
|
const req = this.getRequestObject();
|
|
84
84
|
const res = this.getResponseObject();
|
|
85
|
-
const clientId = this.getNodeParameter(
|
|
86
|
-
const scopes = this.getNodeParameter(
|
|
87
|
-
if (req.method ===
|
|
85
|
+
const clientId = this.getNodeParameter('clientId');
|
|
86
|
+
const scopes = this.getNodeParameter('scopes');
|
|
87
|
+
if (req.method === 'POST') {
|
|
88
88
|
const body = this.getBodyData();
|
|
89
89
|
const action = body.action;
|
|
90
90
|
try {
|
|
91
|
-
if (action ===
|
|
92
|
-
const response = await fetch(
|
|
93
|
-
method:
|
|
91
|
+
if (action === 'device_code') {
|
|
92
|
+
const response = await fetch('https://github.com/login/device/code', {
|
|
93
|
+
method: 'POST',
|
|
94
94
|
headers: {
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
Accept: 'application/json',
|
|
96
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
97
97
|
},
|
|
98
98
|
body: new URLSearchParams({
|
|
99
99
|
client_id: clientId,
|
|
@@ -101,28 +101,28 @@ class GitHubCopilotAuthHelper {
|
|
|
101
101
|
}),
|
|
102
102
|
});
|
|
103
103
|
const data = await response.json();
|
|
104
|
-
res.setHeader(
|
|
104
|
+
res.setHeader('Content-Type', 'application/json');
|
|
105
105
|
res.status(200).json(data);
|
|
106
106
|
return {
|
|
107
107
|
noWebhookResponse: true,
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
|
-
if (action ===
|
|
110
|
+
if (action === 'poll_token') {
|
|
111
111
|
const deviceCode = body.device_code;
|
|
112
|
-
const response = await fetch(
|
|
113
|
-
method:
|
|
112
|
+
const response = await fetch('https://github.com/login/oauth/access_token', {
|
|
113
|
+
method: 'POST',
|
|
114
114
|
headers: {
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
Accept: 'application/json',
|
|
116
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
117
117
|
},
|
|
118
118
|
body: new URLSearchParams({
|
|
119
119
|
client_id: clientId,
|
|
120
120
|
device_code: deviceCode,
|
|
121
|
-
grant_type:
|
|
121
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
122
122
|
}),
|
|
123
123
|
});
|
|
124
124
|
const data = await response.json();
|
|
125
|
-
res.setHeader(
|
|
125
|
+
res.setHeader('Content-Type', 'application/json');
|
|
126
126
|
res.status(200).json(data);
|
|
127
127
|
return {
|
|
128
128
|
noWebhookResponse: true,
|
|
@@ -131,16 +131,16 @@ class GitHubCopilotAuthHelper {
|
|
|
131
131
|
throw new Error(`Unknown action: ${action}`);
|
|
132
132
|
}
|
|
133
133
|
catch (error) {
|
|
134
|
-
res.setHeader(
|
|
134
|
+
res.setHeader('Content-Type', 'application/json');
|
|
135
135
|
res.status(500).json({ error: error.message });
|
|
136
136
|
return {
|
|
137
137
|
noWebhookResponse: true,
|
|
138
138
|
};
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
|
-
const webhookUrl = this.getNodeWebhookUrl(
|
|
141
|
+
const webhookUrl = this.getNodeWebhookUrl('default');
|
|
142
142
|
const html = generateAuthPage(webhookUrl);
|
|
143
|
-
res.setHeader(
|
|
143
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
144
144
|
res.status(200).send(html);
|
|
145
145
|
return {
|
|
146
146
|
noWebhookResponse: true,
|
|
@@ -149,484 +149,484 @@ class GitHubCopilotAuthHelper {
|
|
|
149
149
|
}
|
|
150
150
|
exports.GitHubCopilotAuthHelper = GitHubCopilotAuthHelper;
|
|
151
151
|
function generateAuthPage(proxyUrl) {
|
|
152
|
-
return `<!DOCTYPE html>
|
|
153
|
-
<html lang="pt-BR">
|
|
154
|
-
<head>
|
|
155
|
-
<meta charset="UTF-8">
|
|
156
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
157
|
-
<title>GitHub Copilot Authentication</title>
|
|
158
|
-
<style>
|
|
159
|
-
* {
|
|
160
|
-
margin: 0;
|
|
161
|
-
padding: 0;
|
|
162
|
-
box-sizing: border-box;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
body {
|
|
166
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
167
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
168
|
-
min-height: 100vh;
|
|
169
|
-
display: flex;
|
|
170
|
-
justify-content: center;
|
|
171
|
-
align-items: center;
|
|
172
|
-
padding: 20px;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.container {
|
|
176
|
-
background: white;
|
|
177
|
-
border-radius: 16px;
|
|
178
|
-
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
179
|
-
max-width: 600px;
|
|
180
|
-
width: 100%;
|
|
181
|
-
padding: 40px;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
h1 {
|
|
185
|
-
color: #333;
|
|
186
|
-
margin-bottom: 10px;
|
|
187
|
-
font-size: 28px;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
.subtitle {
|
|
191
|
-
color: #666;
|
|
192
|
-
margin-bottom: 30px;
|
|
193
|
-
font-size: 14px;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
.step {
|
|
197
|
-
background: #f8f9fa;
|
|
198
|
-
border-radius: 12px;
|
|
199
|
-
padding: 25px;
|
|
200
|
-
margin-bottom: 20px;
|
|
201
|
-
border: 2px solid #e9ecef;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
.step.active {
|
|
205
|
-
border-color: #667eea;
|
|
206
|
-
background: #f0f4ff;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.step h2 {
|
|
210
|
-
color: #333;
|
|
211
|
-
font-size: 18px;
|
|
212
|
-
margin-bottom: 15px;
|
|
213
|
-
display: flex;
|
|
214
|
-
align-items: center;
|
|
215
|
-
gap: 10px;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
.step-number {
|
|
219
|
-
background: #667eea;
|
|
220
|
-
color: white;
|
|
221
|
-
width: 30px;
|
|
222
|
-
height: 30px;
|
|
223
|
-
border-radius: 50%;
|
|
224
|
-
display: inline-flex;
|
|
225
|
-
align-items: center;
|
|
226
|
-
justify-content: center;
|
|
227
|
-
font-size: 14px;
|
|
228
|
-
font-weight: bold;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.code-box {
|
|
232
|
-
background: #2d3748;
|
|
233
|
-
color: #fff;
|
|
234
|
-
padding: 20px;
|
|
235
|
-
border-radius: 8px;
|
|
236
|
-
font-size: 32px;
|
|
237
|
-
text-align: center;
|
|
238
|
-
letter-spacing: 8px;
|
|
239
|
-
font-family: 'Courier New', monospace;
|
|
240
|
-
margin: 15px 0;
|
|
241
|
-
user-select: all;
|
|
242
|
-
cursor: pointer;
|
|
243
|
-
transition: all 0.3s;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
.code-box:hover {
|
|
247
|
-
background: #1a202c;
|
|
248
|
-
transform: scale(1.02);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.btn {
|
|
252
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
253
|
-
color: white;
|
|
254
|
-
padding: 15px 30px;
|
|
255
|
-
border: none;
|
|
256
|
-
border-radius: 8px;
|
|
257
|
-
font-size: 16px;
|
|
258
|
-
font-weight: 600;
|
|
259
|
-
cursor: pointer;
|
|
260
|
-
width: 100%;
|
|
261
|
-
transition: all 0.3s;
|
|
262
|
-
display: flex;
|
|
263
|
-
align-items: center;
|
|
264
|
-
justify-content: center;
|
|
265
|
-
gap: 10px;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
.btn:hover {
|
|
269
|
-
transform: translateY(-2px);
|
|
270
|
-
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
.btn:disabled {
|
|
274
|
-
opacity: 0.6;
|
|
275
|
-
cursor: not-allowed;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
.status {
|
|
279
|
-
text-align: center;
|
|
280
|
-
margin-top: 30px;
|
|
281
|
-
padding: 20px;
|
|
282
|
-
background: #f8f9fa;
|
|
283
|
-
border-radius: 8px;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
.status h3 {
|
|
287
|
-
color: #333;
|
|
288
|
-
margin-bottom: 15px;
|
|
289
|
-
font-size: 16px;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
.spinner {
|
|
293
|
-
border: 4px solid #f3f3f3;
|
|
294
|
-
border-top: 4px solid #667eea;
|
|
295
|
-
border-radius: 50%;
|
|
296
|
-
width: 40px;
|
|
297
|
-
height: 40px;
|
|
298
|
-
animation: spin 1s linear infinite;
|
|
299
|
-
margin: 0 auto;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
@keyframes spin {
|
|
303
|
-
0% { transform: rotate(0deg); }
|
|
304
|
-
100% { transform: rotate(360deg); }
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
.success-box {
|
|
308
|
-
background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
|
|
309
|
-
padding: 30px;
|
|
310
|
-
border-radius: 12px;
|
|
311
|
-
margin-top: 20px;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
.success-box h2 {
|
|
315
|
-
color: #155724;
|
|
316
|
-
margin-bottom: 15px;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
.token-display {
|
|
320
|
-
background: white;
|
|
321
|
-
padding: 15px;
|
|
322
|
-
border-radius: 8px;
|
|
323
|
-
word-break: break-all;
|
|
324
|
-
font-family: 'Courier New', monospace;
|
|
325
|
-
font-size: 14px;
|
|
326
|
-
color: #333;
|
|
327
|
-
margin: 15px 0;
|
|
328
|
-
border: 2px solid #28a745;
|
|
329
|
-
max-height: 150px;
|
|
330
|
-
overflow-y: auto;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.error-box {
|
|
334
|
-
background: #f8d7da;
|
|
335
|
-
color: #721c24;
|
|
336
|
-
padding: 20px;
|
|
337
|
-
border-radius: 8px;
|
|
338
|
-
margin-top: 20px;
|
|
339
|
-
border: 2px solid #f5c6cb;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
.hidden {
|
|
343
|
-
display: none;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
.info-text {
|
|
347
|
-
color: #666;
|
|
348
|
-
font-size: 14px;
|
|
349
|
-
margin-top: 10px;
|
|
350
|
-
line-height: 1.6;
|
|
351
|
-
}
|
|
352
|
-
</style>
|
|
353
|
-
</head>
|
|
354
|
-
<body>
|
|
355
|
-
<div class="container">
|
|
356
|
-
<h1>🚀 GitHub Copilot Authentication</h1>
|
|
357
|
-
<p class="subtitle">OAuth Device Flow - Obtenha seu token de autenticação</p>
|
|
358
|
-
|
|
359
|
-
<div id="step1" class="step active">
|
|
360
|
-
<h2><span class="step-number">1</span> Iniciar Autenticação</h2>
|
|
361
|
-
<button class="btn" onclick="startDeviceFlow()">
|
|
362
|
-
<span>▶️</span>
|
|
363
|
-
<span>Começar</span>
|
|
364
|
-
</button>
|
|
365
|
-
<p class="info-text">
|
|
366
|
-
Clique para solicitar um código de autorização do GitHub
|
|
367
|
-
</p>
|
|
368
|
-
</div>
|
|
369
|
-
|
|
370
|
-
<div id="step2" class="step hidden">
|
|
371
|
-
<h2><span class="step-number">2</span> Copie o Código</h2>
|
|
372
|
-
<div class="code-box" id="userCode" onclick="copyCode()">
|
|
373
|
-
Carregando...
|
|
374
|
-
</div>
|
|
375
|
-
<button class="btn" onclick="copyCode()">
|
|
376
|
-
<span>📋</span>
|
|
377
|
-
<span>Copiar Código</span>
|
|
378
|
-
</button>
|
|
379
|
-
<p class="info-text">
|
|
380
|
-
Clique no código ou no botão para copiar
|
|
381
|
-
</p>
|
|
382
|
-
</div>
|
|
383
|
-
|
|
384
|
-
<div id="step3" class="step hidden">
|
|
385
|
-
<h2><span class="step-number">3</span> Autorize no GitHub</h2>
|
|
386
|
-
<button class="btn" onclick="openGitHub()">
|
|
387
|
-
<span>🌐</span>
|
|
388
|
-
<span>Abrir GitHub</span>
|
|
389
|
-
</button>
|
|
390
|
-
<p class="info-text">
|
|
391
|
-
Cole o código copiado e autorize a aplicação
|
|
392
|
-
</p>
|
|
393
|
-
</div>
|
|
394
|
-
|
|
395
|
-
<div id="statusSection" class="status hidden">
|
|
396
|
-
<h3>Status: <span id="statusText">Aguardando...</span></h3>
|
|
397
|
-
<div id="spinner" class="spinner"></div>
|
|
398
|
-
</div>
|
|
399
|
-
|
|
400
|
-
<div id="successSection" class="success-box hidden">
|
|
401
|
-
<h2>✅ Token Obtido com Sucesso!</h2>
|
|
402
|
-
<p>Copie o token abaixo e use na credencial n8n:</p>
|
|
403
|
-
<div class="token-display" id="tokenDisplay"></div>
|
|
404
|
-
<button class="btn" onclick="copyToken()">
|
|
405
|
-
<span>📋</span>
|
|
406
|
-
<span>Copiar Token</span>
|
|
407
|
-
</button>
|
|
408
|
-
<p class="info-text" style="color: #155724; margin-top: 15px;">
|
|
409
|
-
✨ Cole este token na credencial "GitHub Copilot API" no n8n
|
|
410
|
-
</p>
|
|
411
|
-
</div>
|
|
412
|
-
|
|
413
|
-
<div id="errorSection" class="error-box hidden">
|
|
414
|
-
<h3>❌ Erro</h3>
|
|
415
|
-
<p id="errorText"></p>
|
|
416
|
-
</div>
|
|
417
|
-
</div>
|
|
418
|
-
|
|
419
|
-
<script>
|
|
420
|
-
const PROXY_URL = "${proxyUrl}";
|
|
421
|
-
|
|
422
|
-
let deviceCode = "";
|
|
423
|
-
let userCode = "";
|
|
424
|
-
let verificationUri = "";
|
|
425
|
-
let accessToken = "";
|
|
426
|
-
|
|
427
|
-
async function startDeviceFlow() {
|
|
428
|
-
try {
|
|
429
|
-
document.getElementById("step1").querySelector(".btn").disabled = true;
|
|
430
|
-
document.getElementById("step1").querySelector(".btn").innerHTML = '<span>⏳</span><span>Solicitando...</span>';
|
|
431
|
-
|
|
432
|
-
// Request device code via n8n proxy
|
|
433
|
-
const response = await fetch(PROXY_URL, {
|
|
434
|
-
method: "POST",
|
|
435
|
-
headers: {
|
|
436
|
-
"Content-Type": "application/json",
|
|
437
|
-
},
|
|
438
|
-
body: JSON.stringify({
|
|
439
|
-
action: "device_code"
|
|
440
|
-
})
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
if (!response.ok) {
|
|
444
|
-
throw new Error(\`Erro ao solicitar device code: \${response.status}\`);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const data = await response.json();
|
|
448
|
-
deviceCode = data.device_code;
|
|
449
|
-
userCode = data.user_code;
|
|
450
|
-
verificationUri = data.verification_uri_complete || data.verification_uri;
|
|
451
|
-
|
|
452
|
-
// Show step 2
|
|
453
|
-
document.getElementById("step1").classList.remove("active");
|
|
454
|
-
document.getElementById("step2").classList.remove("hidden");
|
|
455
|
-
document.getElementById("step2").classList.add("active");
|
|
456
|
-
document.getElementById("userCode").textContent = userCode;
|
|
457
|
-
|
|
458
|
-
// Show step 3
|
|
459
|
-
document.getElementById("step3").classList.remove("hidden");
|
|
460
|
-
document.getElementById("step3").classList.add("active");
|
|
461
|
-
|
|
462
|
-
} catch (error) {
|
|
463
|
-
showError(error.message);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
function copyCode() {
|
|
468
|
-
// Try modern clipboard API first
|
|
469
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
470
|
-
navigator.clipboard.writeText(userCode).then(() => {
|
|
471
|
-
showCopySuccess("step2");
|
|
472
|
-
}).catch((err) => {
|
|
473
|
-
// Fallback to execCommand
|
|
474
|
-
fallbackCopyCode();
|
|
475
|
-
});
|
|
476
|
-
} else {
|
|
477
|
-
// Fallback for older browsers
|
|
478
|
-
fallbackCopyCode();
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function fallbackCopyCode() {
|
|
483
|
-
// Create temporary textarea
|
|
484
|
-
const textarea = document.createElement("textarea");
|
|
485
|
-
textarea.value = userCode;
|
|
486
|
-
textarea.style.position = "fixed";
|
|
487
|
-
textarea.style.opacity = "0";
|
|
488
|
-
document.body.appendChild(textarea);
|
|
489
|
-
textarea.select();
|
|
490
|
-
|
|
491
|
-
try {
|
|
492
|
-
document.execCommand("copy");
|
|
493
|
-
showCopySuccess("step2");
|
|
494
|
-
} catch (err) {
|
|
495
|
-
alert("Erro ao copiar. Código: " + userCode);
|
|
496
|
-
} finally {
|
|
497
|
-
document.body.removeChild(textarea);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function showCopySuccess(stepId) {
|
|
502
|
-
const btn = document.getElementById(stepId).querySelector(".btn");
|
|
503
|
-
const originalText = btn.innerHTML;
|
|
504
|
-
btn.innerHTML = '<span>✅</span><span>Copiado!</span>';
|
|
505
|
-
setTimeout(() => {
|
|
506
|
-
btn.innerHTML = originalText;
|
|
507
|
-
}, 2000);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function openGitHub() {
|
|
511
|
-
window.open(verificationUri, "_blank");
|
|
512
|
-
startPolling();
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
async function startPolling() {
|
|
516
|
-
document.getElementById("statusSection").classList.remove("hidden");
|
|
517
|
-
document.getElementById("statusText").textContent = "Verificando autorização...";
|
|
518
|
-
|
|
519
|
-
const maxAttempts = 180; // 15 minutes
|
|
520
|
-
let interval = 5000; // 5 seconds
|
|
521
|
-
|
|
522
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
523
|
-
await sleep(interval);
|
|
524
|
-
|
|
525
|
-
document.getElementById("statusText").textContent = \`Verificando... (tentativa \${attempt}/\${maxAttempts})\`;
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
const response = await fetch(PROXY_URL, {
|
|
529
|
-
method: "POST",
|
|
530
|
-
headers: {
|
|
531
|
-
"Content-Type": "application/json",
|
|
532
|
-
},
|
|
533
|
-
body: JSON.stringify({
|
|
534
|
-
action: "poll_token",
|
|
535
|
-
device_code: deviceCode
|
|
536
|
-
})
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
const data = await response.json();
|
|
540
|
-
|
|
541
|
-
if (data.access_token) {
|
|
542
|
-
accessToken = data.access_token;
|
|
543
|
-
showSuccess();
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
if (data.error === "authorization_pending") {
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
if (data.error === "slow_down") {
|
|
552
|
-
interval += 5000; // Increase interval by 5 seconds
|
|
553
|
-
continue;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
if (data.error === "expired_token") {
|
|
557
|
-
throw new Error("Código expirado. Por favor, recomece o processo.");
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
if (data.error === "access_denied") {
|
|
561
|
-
throw new Error("Autorização negada pelo usuário.");
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (data.error) {
|
|
565
|
-
throw new Error(\`Erro OAuth: \${data.error}\`);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
} catch (error) {
|
|
569
|
-
showError(error.message);
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
showError("Tempo esgotado. A autorização demorou muito. Por favor, tente novamente.");
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function showSuccess() {
|
|
578
|
-
document.getElementById("statusSection").classList.add("hidden");
|
|
579
|
-
document.getElementById("successSection").classList.remove("hidden");
|
|
580
|
-
document.getElementById("tokenDisplay").textContent = accessToken;
|
|
581
|
-
|
|
582
|
-
// Scroll to success section
|
|
583
|
-
document.getElementById("successSection").scrollIntoView({ behavior: "smooth" });
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function showError(message) {
|
|
587
|
-
document.getElementById("statusSection").classList.add("hidden");
|
|
588
|
-
document.getElementById("errorSection").classList.remove("hidden");
|
|
589
|
-
document.getElementById("errorText").textContent = message;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
function copyToken() {
|
|
593
|
-
// Try modern clipboard API first
|
|
594
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
595
|
-
navigator.clipboard.writeText(accessToken).then(() => {
|
|
596
|
-
showCopySuccess("successSection");
|
|
597
|
-
}).catch((err) => {
|
|
598
|
-
// Fallback to execCommand
|
|
599
|
-
fallbackCopyToken();
|
|
600
|
-
});
|
|
601
|
-
} else {
|
|
602
|
-
// Fallback for older browsers
|
|
603
|
-
fallbackCopyToken();
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
function fallbackCopyToken() {
|
|
608
|
-
// Create temporary textarea
|
|
609
|
-
const textarea = document.createElement("textarea");
|
|
610
|
-
textarea.value = accessToken;
|
|
611
|
-
textarea.style.position = "fixed";
|
|
612
|
-
textarea.style.opacity = "0";
|
|
613
|
-
document.body.appendChild(textarea);
|
|
614
|
-
textarea.select();
|
|
615
|
-
|
|
616
|
-
try {
|
|
617
|
-
document.execCommand("copy");
|
|
618
|
-
showCopySuccess("successSection");
|
|
619
|
-
} catch (err) {
|
|
620
|
-
alert("Erro ao copiar token. Por favor, selecione e copie manualmente.");
|
|
621
|
-
} finally {
|
|
622
|
-
document.body.removeChild(textarea);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
function sleep(ms) {
|
|
627
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
628
|
-
}
|
|
629
|
-
</script>
|
|
630
|
-
</body>
|
|
152
|
+
return `<!DOCTYPE html>
|
|
153
|
+
<html lang="pt-BR">
|
|
154
|
+
<head>
|
|
155
|
+
<meta charset="UTF-8">
|
|
156
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
157
|
+
<title>GitHub Copilot Authentication</title>
|
|
158
|
+
<style>
|
|
159
|
+
* {
|
|
160
|
+
margin: 0;
|
|
161
|
+
padding: 0;
|
|
162
|
+
box-sizing: border-box;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
body {
|
|
166
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
167
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
168
|
+
min-height: 100vh;
|
|
169
|
+
display: flex;
|
|
170
|
+
justify-content: center;
|
|
171
|
+
align-items: center;
|
|
172
|
+
padding: 20px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.container {
|
|
176
|
+
background: white;
|
|
177
|
+
border-radius: 16px;
|
|
178
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
179
|
+
max-width: 600px;
|
|
180
|
+
width: 100%;
|
|
181
|
+
padding: 40px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
h1 {
|
|
185
|
+
color: #333;
|
|
186
|
+
margin-bottom: 10px;
|
|
187
|
+
font-size: 28px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.subtitle {
|
|
191
|
+
color: #666;
|
|
192
|
+
margin-bottom: 30px;
|
|
193
|
+
font-size: 14px;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.step {
|
|
197
|
+
background: #f8f9fa;
|
|
198
|
+
border-radius: 12px;
|
|
199
|
+
padding: 25px;
|
|
200
|
+
margin-bottom: 20px;
|
|
201
|
+
border: 2px solid #e9ecef;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.step.active {
|
|
205
|
+
border-color: #667eea;
|
|
206
|
+
background: #f0f4ff;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.step h2 {
|
|
210
|
+
color: #333;
|
|
211
|
+
font-size: 18px;
|
|
212
|
+
margin-bottom: 15px;
|
|
213
|
+
display: flex;
|
|
214
|
+
align-items: center;
|
|
215
|
+
gap: 10px;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.step-number {
|
|
219
|
+
background: #667eea;
|
|
220
|
+
color: white;
|
|
221
|
+
width: 30px;
|
|
222
|
+
height: 30px;
|
|
223
|
+
border-radius: 50%;
|
|
224
|
+
display: inline-flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
justify-content: center;
|
|
227
|
+
font-size: 14px;
|
|
228
|
+
font-weight: bold;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.code-box {
|
|
232
|
+
background: #2d3748;
|
|
233
|
+
color: #fff;
|
|
234
|
+
padding: 20px;
|
|
235
|
+
border-radius: 8px;
|
|
236
|
+
font-size: 32px;
|
|
237
|
+
text-align: center;
|
|
238
|
+
letter-spacing: 8px;
|
|
239
|
+
font-family: 'Courier New', monospace;
|
|
240
|
+
margin: 15px 0;
|
|
241
|
+
user-select: all;
|
|
242
|
+
cursor: pointer;
|
|
243
|
+
transition: all 0.3s;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.code-box:hover {
|
|
247
|
+
background: #1a202c;
|
|
248
|
+
transform: scale(1.02);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.btn {
|
|
252
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
253
|
+
color: white;
|
|
254
|
+
padding: 15px 30px;
|
|
255
|
+
border: none;
|
|
256
|
+
border-radius: 8px;
|
|
257
|
+
font-size: 16px;
|
|
258
|
+
font-weight: 600;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
width: 100%;
|
|
261
|
+
transition: all 0.3s;
|
|
262
|
+
display: flex;
|
|
263
|
+
align-items: center;
|
|
264
|
+
justify-content: center;
|
|
265
|
+
gap: 10px;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.btn:hover {
|
|
269
|
+
transform: translateY(-2px);
|
|
270
|
+
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.btn:disabled {
|
|
274
|
+
opacity: 0.6;
|
|
275
|
+
cursor: not-allowed;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.status {
|
|
279
|
+
text-align: center;
|
|
280
|
+
margin-top: 30px;
|
|
281
|
+
padding: 20px;
|
|
282
|
+
background: #f8f9fa;
|
|
283
|
+
border-radius: 8px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.status h3 {
|
|
287
|
+
color: #333;
|
|
288
|
+
margin-bottom: 15px;
|
|
289
|
+
font-size: 16px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.spinner {
|
|
293
|
+
border: 4px solid #f3f3f3;
|
|
294
|
+
border-top: 4px solid #667eea;
|
|
295
|
+
border-radius: 50%;
|
|
296
|
+
width: 40px;
|
|
297
|
+
height: 40px;
|
|
298
|
+
animation: spin 1s linear infinite;
|
|
299
|
+
margin: 0 auto;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@keyframes spin {
|
|
303
|
+
0% { transform: rotate(0deg); }
|
|
304
|
+
100% { transform: rotate(360deg); }
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.success-box {
|
|
308
|
+
background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
|
|
309
|
+
padding: 30px;
|
|
310
|
+
border-radius: 12px;
|
|
311
|
+
margin-top: 20px;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.success-box h2 {
|
|
315
|
+
color: #155724;
|
|
316
|
+
margin-bottom: 15px;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.token-display {
|
|
320
|
+
background: white;
|
|
321
|
+
padding: 15px;
|
|
322
|
+
border-radius: 8px;
|
|
323
|
+
word-break: break-all;
|
|
324
|
+
font-family: 'Courier New', monospace;
|
|
325
|
+
font-size: 14px;
|
|
326
|
+
color: #333;
|
|
327
|
+
margin: 15px 0;
|
|
328
|
+
border: 2px solid #28a745;
|
|
329
|
+
max-height: 150px;
|
|
330
|
+
overflow-y: auto;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.error-box {
|
|
334
|
+
background: #f8d7da;
|
|
335
|
+
color: #721c24;
|
|
336
|
+
padding: 20px;
|
|
337
|
+
border-radius: 8px;
|
|
338
|
+
margin-top: 20px;
|
|
339
|
+
border: 2px solid #f5c6cb;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.hidden {
|
|
343
|
+
display: none;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.info-text {
|
|
347
|
+
color: #666;
|
|
348
|
+
font-size: 14px;
|
|
349
|
+
margin-top: 10px;
|
|
350
|
+
line-height: 1.6;
|
|
351
|
+
}
|
|
352
|
+
</style>
|
|
353
|
+
</head>
|
|
354
|
+
<body>
|
|
355
|
+
<div class="container">
|
|
356
|
+
<h1>🚀 GitHub Copilot Authentication</h1>
|
|
357
|
+
<p class="subtitle">OAuth Device Flow - Obtenha seu token de autenticação</p>
|
|
358
|
+
|
|
359
|
+
<div id="step1" class="step active">
|
|
360
|
+
<h2><span class="step-number">1</span> Iniciar Autenticação</h2>
|
|
361
|
+
<button class="btn" onclick="startDeviceFlow()">
|
|
362
|
+
<span>▶️</span>
|
|
363
|
+
<span>Começar</span>
|
|
364
|
+
</button>
|
|
365
|
+
<p class="info-text">
|
|
366
|
+
Clique para solicitar um código de autorização do GitHub
|
|
367
|
+
</p>
|
|
368
|
+
</div>
|
|
369
|
+
|
|
370
|
+
<div id="step2" class="step hidden">
|
|
371
|
+
<h2><span class="step-number">2</span> Copie o Código</h2>
|
|
372
|
+
<div class="code-box" id="userCode" onclick="copyCode()">
|
|
373
|
+
Carregando...
|
|
374
|
+
</div>
|
|
375
|
+
<button class="btn" onclick="copyCode()">
|
|
376
|
+
<span>📋</span>
|
|
377
|
+
<span>Copiar Código</span>
|
|
378
|
+
</button>
|
|
379
|
+
<p class="info-text">
|
|
380
|
+
Clique no código ou no botão para copiar
|
|
381
|
+
</p>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<div id="step3" class="step hidden">
|
|
385
|
+
<h2><span class="step-number">3</span> Autorize no GitHub</h2>
|
|
386
|
+
<button class="btn" onclick="openGitHub()">
|
|
387
|
+
<span>🌐</span>
|
|
388
|
+
<span>Abrir GitHub</span>
|
|
389
|
+
</button>
|
|
390
|
+
<p class="info-text">
|
|
391
|
+
Cole o código copiado e autorize a aplicação
|
|
392
|
+
</p>
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
<div id="statusSection" class="status hidden">
|
|
396
|
+
<h3>Status: <span id="statusText">Aguardando...</span></h3>
|
|
397
|
+
<div id="spinner" class="spinner"></div>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<div id="successSection" class="success-box hidden">
|
|
401
|
+
<h2>✅ Token Obtido com Sucesso!</h2>
|
|
402
|
+
<p>Copie o token abaixo e use na credencial n8n:</p>
|
|
403
|
+
<div class="token-display" id="tokenDisplay"></div>
|
|
404
|
+
<button class="btn" onclick="copyToken()">
|
|
405
|
+
<span>📋</span>
|
|
406
|
+
<span>Copiar Token</span>
|
|
407
|
+
</button>
|
|
408
|
+
<p class="info-text" style="color: #155724; margin-top: 15px;">
|
|
409
|
+
✨ Cole este token na credencial "GitHub Copilot API" no n8n
|
|
410
|
+
</p>
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
<div id="errorSection" class="error-box hidden">
|
|
414
|
+
<h3>❌ Erro</h3>
|
|
415
|
+
<p id="errorText"></p>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
<script>
|
|
420
|
+
const PROXY_URL = "${proxyUrl}";
|
|
421
|
+
|
|
422
|
+
let deviceCode = "";
|
|
423
|
+
let userCode = "";
|
|
424
|
+
let verificationUri = "";
|
|
425
|
+
let accessToken = "";
|
|
426
|
+
|
|
427
|
+
async function startDeviceFlow() {
|
|
428
|
+
try {
|
|
429
|
+
document.getElementById("step1").querySelector(".btn").disabled = true;
|
|
430
|
+
document.getElementById("step1").querySelector(".btn").innerHTML = '<span>⏳</span><span>Solicitando...</span>';
|
|
431
|
+
|
|
432
|
+
// Request device code via n8n proxy
|
|
433
|
+
const response = await fetch(PROXY_URL, {
|
|
434
|
+
method: "POST",
|
|
435
|
+
headers: {
|
|
436
|
+
"Content-Type": "application/json",
|
|
437
|
+
},
|
|
438
|
+
body: JSON.stringify({
|
|
439
|
+
action: "device_code"
|
|
440
|
+
})
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
if (!response.ok) {
|
|
444
|
+
throw new Error(\`Erro ao solicitar device code: \${response.status}\`);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const data = await response.json();
|
|
448
|
+
deviceCode = data.device_code;
|
|
449
|
+
userCode = data.user_code;
|
|
450
|
+
verificationUri = data.verification_uri_complete || data.verification_uri;
|
|
451
|
+
|
|
452
|
+
// Show step 2
|
|
453
|
+
document.getElementById("step1").classList.remove("active");
|
|
454
|
+
document.getElementById("step2").classList.remove("hidden");
|
|
455
|
+
document.getElementById("step2").classList.add("active");
|
|
456
|
+
document.getElementById("userCode").textContent = userCode;
|
|
457
|
+
|
|
458
|
+
// Show step 3
|
|
459
|
+
document.getElementById("step3").classList.remove("hidden");
|
|
460
|
+
document.getElementById("step3").classList.add("active");
|
|
461
|
+
|
|
462
|
+
} catch (error) {
|
|
463
|
+
showError(error.message);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function copyCode() {
|
|
468
|
+
// Try modern clipboard API first
|
|
469
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
470
|
+
navigator.clipboard.writeText(userCode).then(() => {
|
|
471
|
+
showCopySuccess("step2");
|
|
472
|
+
}).catch((err) => {
|
|
473
|
+
// Fallback to execCommand
|
|
474
|
+
fallbackCopyCode();
|
|
475
|
+
});
|
|
476
|
+
} else {
|
|
477
|
+
// Fallback for older browsers
|
|
478
|
+
fallbackCopyCode();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function fallbackCopyCode() {
|
|
483
|
+
// Create temporary textarea
|
|
484
|
+
const textarea = document.createElement("textarea");
|
|
485
|
+
textarea.value = userCode;
|
|
486
|
+
textarea.style.position = "fixed";
|
|
487
|
+
textarea.style.opacity = "0";
|
|
488
|
+
document.body.appendChild(textarea);
|
|
489
|
+
textarea.select();
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
document.execCommand("copy");
|
|
493
|
+
showCopySuccess("step2");
|
|
494
|
+
} catch (err) {
|
|
495
|
+
alert("Erro ao copiar. Código: " + userCode);
|
|
496
|
+
} finally {
|
|
497
|
+
document.body.removeChild(textarea);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function showCopySuccess(stepId) {
|
|
502
|
+
const btn = document.getElementById(stepId).querySelector(".btn");
|
|
503
|
+
const originalText = btn.innerHTML;
|
|
504
|
+
btn.innerHTML = '<span>✅</span><span>Copiado!</span>';
|
|
505
|
+
setTimeout(() => {
|
|
506
|
+
btn.innerHTML = originalText;
|
|
507
|
+
}, 2000);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function openGitHub() {
|
|
511
|
+
window.open(verificationUri, "_blank");
|
|
512
|
+
startPolling();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function startPolling() {
|
|
516
|
+
document.getElementById("statusSection").classList.remove("hidden");
|
|
517
|
+
document.getElementById("statusText").textContent = "Verificando autorização...";
|
|
518
|
+
|
|
519
|
+
const maxAttempts = 180; // 15 minutes
|
|
520
|
+
let interval = 5000; // 5 seconds
|
|
521
|
+
|
|
522
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
523
|
+
await sleep(interval);
|
|
524
|
+
|
|
525
|
+
document.getElementById("statusText").textContent = \`Verificando... (tentativa \${attempt}/\${maxAttempts})\`;
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
const response = await fetch(PROXY_URL, {
|
|
529
|
+
method: "POST",
|
|
530
|
+
headers: {
|
|
531
|
+
"Content-Type": "application/json",
|
|
532
|
+
},
|
|
533
|
+
body: JSON.stringify({
|
|
534
|
+
action: "poll_token",
|
|
535
|
+
device_code: deviceCode
|
|
536
|
+
})
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const data = await response.json();
|
|
540
|
+
|
|
541
|
+
if (data.access_token) {
|
|
542
|
+
accessToken = data.access_token;
|
|
543
|
+
showSuccess();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (data.error === "authorization_pending") {
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (data.error === "slow_down") {
|
|
552
|
+
interval += 5000; // Increase interval by 5 seconds
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (data.error === "expired_token") {
|
|
557
|
+
throw new Error("Código expirado. Por favor, recomece o processo.");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (data.error === "access_denied") {
|
|
561
|
+
throw new Error("Autorização negada pelo usuário.");
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (data.error) {
|
|
565
|
+
throw new Error(\`Erro OAuth: \${data.error}\`);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
} catch (error) {
|
|
569
|
+
showError(error.message);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
showError("Tempo esgotado. A autorização demorou muito. Por favor, tente novamente.");
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function showSuccess() {
|
|
578
|
+
document.getElementById("statusSection").classList.add("hidden");
|
|
579
|
+
document.getElementById("successSection").classList.remove("hidden");
|
|
580
|
+
document.getElementById("tokenDisplay").textContent = accessToken;
|
|
581
|
+
|
|
582
|
+
// Scroll to success section
|
|
583
|
+
document.getElementById("successSection").scrollIntoView({ behavior: "smooth" });
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function showError(message) {
|
|
587
|
+
document.getElementById("statusSection").classList.add("hidden");
|
|
588
|
+
document.getElementById("errorSection").classList.remove("hidden");
|
|
589
|
+
document.getElementById("errorText").textContent = message;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function copyToken() {
|
|
593
|
+
// Try modern clipboard API first
|
|
594
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
595
|
+
navigator.clipboard.writeText(accessToken).then(() => {
|
|
596
|
+
showCopySuccess("successSection");
|
|
597
|
+
}).catch((err) => {
|
|
598
|
+
// Fallback to execCommand
|
|
599
|
+
fallbackCopyToken();
|
|
600
|
+
});
|
|
601
|
+
} else {
|
|
602
|
+
// Fallback for older browsers
|
|
603
|
+
fallbackCopyToken();
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function fallbackCopyToken() {
|
|
608
|
+
// Create temporary textarea
|
|
609
|
+
const textarea = document.createElement("textarea");
|
|
610
|
+
textarea.value = accessToken;
|
|
611
|
+
textarea.style.position = "fixed";
|
|
612
|
+
textarea.style.opacity = "0";
|
|
613
|
+
document.body.appendChild(textarea);
|
|
614
|
+
textarea.select();
|
|
615
|
+
|
|
616
|
+
try {
|
|
617
|
+
document.execCommand("copy");
|
|
618
|
+
showCopySuccess("successSection");
|
|
619
|
+
} catch (err) {
|
|
620
|
+
alert("Erro ao copiar token. Por favor, selecione e copie manualmente.");
|
|
621
|
+
} finally {
|
|
622
|
+
document.body.removeChild(textarea);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function sleep(ms) {
|
|
627
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
628
|
+
}
|
|
629
|
+
</script>
|
|
630
|
+
</body>
|
|
631
631
|
</html>`;
|
|
632
632
|
}
|