n8n-nodes-github-copilot 3.38.25 → 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.
Files changed (36) hide show
  1. package/dist/credentials/GitHubCopilotApi.credentials.d.ts +1 -1
  2. package/dist/credentials/GitHubCopilotApi.credentials.js +25 -25
  3. package/dist/nodes/GitHubCopilot/GitHubCopilot.node.d.ts +1 -1
  4. package/dist/nodes/GitHubCopilot/GitHubCopilot.node.js +166 -166
  5. package/dist/nodes/GitHubCopilotAuthHelper/GitHubCopilotAuthHelper.node.d.ts +1 -1
  6. package/dist/nodes/GitHubCopilotAuthHelper/GitHubCopilotAuthHelper.node.js +539 -539
  7. package/dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.d.ts +1 -1
  8. package/dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.js +46 -44
  9. package/dist/nodes/GitHubCopilotChatAPI/nodeProperties.d.ts +1 -1
  10. package/dist/nodes/GitHubCopilotChatAPI/nodeProperties.js +82 -82
  11. package/dist/nodes/GitHubCopilotChatAPI/utils/helpers.d.ts +2 -2
  12. package/dist/nodes/GitHubCopilotChatAPI/utils/helpers.js +26 -26
  13. package/dist/nodes/GitHubCopilotChatAPI/utils/imageProcessor.d.ts +2 -2
  14. package/dist/nodes/GitHubCopilotChatAPI/utils/imageProcessor.js +12 -12
  15. package/dist/nodes/GitHubCopilotChatAPI/utils/index.d.ts +4 -4
  16. package/dist/nodes/GitHubCopilotChatAPI/utils/mediaDetection.d.ts +3 -3
  17. package/dist/nodes/GitHubCopilotChatAPI/utils/mediaDetection.js +19 -19
  18. package/dist/nodes/GitHubCopilotChatAPI/utils/modelCapabilities.d.ts +1 -1
  19. package/dist/nodes/GitHubCopilotChatAPI/utils/modelCapabilities.js +23 -23
  20. package/dist/nodes/GitHubCopilotChatAPI/utils/types.d.ts +5 -5
  21. package/dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.d.ts +1 -1
  22. package/dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.js +115 -106
  23. package/dist/nodes/GitHubCopilotEmbeddings/GitHubCopilotEmbeddings.node.d.ts +1 -1
  24. package/dist/nodes/GitHubCopilotEmbeddings/GitHubCopilotEmbeddings.node.js +114 -114
  25. package/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.d.ts +1 -1
  26. package/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.js +74 -69
  27. package/dist/nodes/GitHubCopilotOpenAI/nodeProperties.d.ts +1 -1
  28. package/dist/nodes/GitHubCopilotOpenAI/nodeProperties.js +181 -181
  29. package/dist/nodes/GitHubCopilotOpenAI/utils/index.d.ts +2 -2
  30. package/dist/nodes/GitHubCopilotOpenAI/utils/openaiCompat.d.ts +10 -10
  31. package/dist/nodes/GitHubCopilotOpenAI/utils/openaiCompat.js +53 -53
  32. package/dist/nodes/GitHubCopilotOpenAI/utils/types.d.ts +12 -12
  33. package/dist/nodes/GitHubCopilotTest/GitHubCopilotTest.node.d.ts +1 -1
  34. package/dist/nodes/GitHubCopilotTest/GitHubCopilotTest.node.js +120 -116
  35. package/dist/package.json +1 -1
  36. 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: "GitHub Copilot Auth Helper",
8
- name: "githubCopilotAuthHelper",
9
- icon: "file:../../shared/icons/copilot.svg",
10
- group: ["trigger"],
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: "Interactive OAuth Device Flow - serves HTML page with proxy to avoid CORS",
12
+ description: 'Interactive OAuth Device Flow - serves HTML page with proxy to avoid CORS',
13
13
  defaults: {
14
- name: "GitHub Copilot Auth",
14
+ name: 'GitHub Copilot Auth',
15
15
  },
16
16
  inputs: [],
17
17
  outputs: [],
18
18
  webhooks: [
19
19
  {
20
- name: "default",
21
- httpMethod: "GET",
22
- responseMode: "onReceived",
23
- path: "github-auth",
20
+ name: 'default',
21
+ httpMethod: 'GET',
22
+ responseMode: 'onReceived',
23
+ path: 'github-auth',
24
24
  },
25
25
  {
26
- name: "default",
27
- httpMethod: "POST",
28
- responseMode: "onReceived",
29
- path: "github-auth",
26
+ name: 'default',
27
+ httpMethod: 'POST',
28
+ responseMode: 'onReceived',
29
+ path: 'github-auth',
30
30
  },
31
31
  ],
32
32
  properties: [
33
33
  {
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.",
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: "📋 Passo 1: Ativar Workflow",
41
- name: "notice2",
42
- type: "notice",
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: "🔗 Passo 2: Copiar URL",
47
- name: "notice3",
48
- type: "notice",
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: "🌐 Passo 3: Usuário Acessa URL",
53
- name: "notice4",
54
- type: "notice",
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: "💡 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.",
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: "Client ID",
65
- name: "clientId",
66
- type: "string",
67
- default: "01ab8ac9400c4e429b23",
64
+ displayName: 'Client ID',
65
+ name: 'clientId',
66
+ type: 'string',
67
+ default: '01ab8ac9400c4e429b23',
68
68
  required: true,
69
- description: "GitHub OAuth Client ID (VS Code official)",
69
+ description: 'GitHub OAuth Client ID (VS Code official)',
70
70
  },
71
71
  {
72
- displayName: "Scopes",
73
- name: "scopes",
74
- type: "string",
75
- default: "repo user:email",
72
+ displayName: 'Scopes',
73
+ name: 'scopes',
74
+ type: 'string',
75
+ default: 'repo user:email',
76
76
  required: true,
77
- description: "OAuth scopes required for GitHub Copilot",
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("clientId");
86
- const scopes = this.getNodeParameter("scopes");
87
- if (req.method === "POST") {
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 === "device_code") {
92
- const response = await fetch("https://github.com/login/device/code", {
93
- method: "POST",
91
+ if (action === 'device_code') {
92
+ const response = await fetch('https://github.com/login/device/code', {
93
+ method: 'POST',
94
94
  headers: {
95
- "Accept": "application/json",
96
- "Content-Type": "application/x-www-form-urlencoded",
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("Content-Type", "application/json");
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 === "poll_token") {
110
+ if (action === 'poll_token') {
111
111
  const deviceCode = body.device_code;
112
- const response = await fetch("https://github.com/login/oauth/access_token", {
113
- method: "POST",
112
+ const response = await fetch('https://github.com/login/oauth/access_token', {
113
+ method: 'POST',
114
114
  headers: {
115
- "Accept": "application/json",
116
- "Content-Type": "application/x-www-form-urlencoded",
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: "urn:ietf:params:oauth:grant-type:device_code",
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("Content-Type", "application/json");
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("Content-Type", "application/json");
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("default");
141
+ const webhookUrl = this.getNodeWebhookUrl('default');
142
142
  const html = generateAuthPage(webhookUrl);
143
- res.setHeader("Content-Type", "text/html; charset=utf-8");
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
  }