@egain/egain-mcp-server 1.0.1 → 1.0.4
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/bin/mcp-server.js +1271 -12
- package/bin/mcp-server.js.map +9 -8
- package/esm/src/hooks/auth-hook.d.ts.map +1 -1
- package/esm/src/hooks/auth-hook.js +1147 -0
- package/esm/src/hooks/auth-hook.js.map +1 -1
- package/esm/src/hooks/registration.d.ts.map +1 -1
- package/esm/src/hooks/registration.js +6 -0
- package/esm/src/hooks/registration.js.map +1 -1
- package/esm/src/hooks/version-check-hook.d.ts +11 -0
- package/esm/src/hooks/version-check-hook.d.ts.map +1 -0
- package/esm/src/hooks/version-check-hook.js +147 -0
- package/esm/src/hooks/version-check-hook.js.map +1 -0
- package/esm/src/lib/config.d.ts +2 -2
- package/esm/src/lib/config.js +2 -2
- package/esm/src/mcp-server/mcp-server.js +1 -1
- package/esm/src/mcp-server/server.js +1 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/src/hooks/auth-hook.ts +1153 -0
- package/src/hooks/registration.ts +7 -0
- package/src/hooks/version-check-hook.ts +168 -0
- package/src/lib/config.ts +2 -2
- package/src/mcp-server/mcp-server.ts +1 -1
- package/src/mcp-server/server.ts +1 -1
package/src/hooks/auth-hook.ts
CHANGED
|
@@ -69,6 +69,1141 @@ interface PKCEValues {
|
|
|
69
69
|
const CONFIG_SERVER_PORT = 3333;
|
|
70
70
|
const CONFIG_SERVER_HOST = 'localhost';
|
|
71
71
|
|
|
72
|
+
// Embedded HTML/JS content for auth pages (included in bundle for npm package compatibility)
|
|
73
|
+
const CONFIG_PAGE_HTML = `<!DOCTYPE html>
|
|
74
|
+
<html lang="en">
|
|
75
|
+
<head>
|
|
76
|
+
<meta charset="UTF-8">
|
|
77
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
78
|
+
<title>eGain MCP - Sign In</title>
|
|
79
|
+
<style>
|
|
80
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
81
|
+
body {
|
|
82
|
+
font-family: "Open Sans", "Segoe UI", "SegoeUI", "Helvetica Neue", Helvetica, Arial, sans-serif !important;
|
|
83
|
+
background: #fef1fd;
|
|
84
|
+
min-height: 100vh;
|
|
85
|
+
display: flex;
|
|
86
|
+
justify-content: center;
|
|
87
|
+
align-items: center;
|
|
88
|
+
padding: 20px;
|
|
89
|
+
}
|
|
90
|
+
.container {
|
|
91
|
+
background: white;
|
|
92
|
+
border-radius: 16px;
|
|
93
|
+
box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.12);
|
|
94
|
+
max-width: 500px;
|
|
95
|
+
width: 100%;
|
|
96
|
+
padding: 32px;
|
|
97
|
+
}
|
|
98
|
+
h1 { color: #333; margin-bottom: 8px; font-size: 24px; }
|
|
99
|
+
.subtitle { color: #666; margin-bottom: 20px; font-size: 13px; }
|
|
100
|
+
.quick-signin {
|
|
101
|
+
display: none;
|
|
102
|
+
text-align: center;
|
|
103
|
+
position: relative;
|
|
104
|
+
}
|
|
105
|
+
.quick-signin .icon { font-size: 64px; }
|
|
106
|
+
.quick-signin h1 { text-align: center; }
|
|
107
|
+
.saved-config {
|
|
108
|
+
background: #f8f9fa;
|
|
109
|
+
border-radius: 8px;
|
|
110
|
+
padding: 20px;
|
|
111
|
+
margin: 30px 0;
|
|
112
|
+
text-align: left;
|
|
113
|
+
}
|
|
114
|
+
.saved-config-title {
|
|
115
|
+
font-size: 12px;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
color: #666;
|
|
118
|
+
text-transform: uppercase;
|
|
119
|
+
margin-bottom: 15px;
|
|
120
|
+
text-align: center;
|
|
121
|
+
}
|
|
122
|
+
.config-item {
|
|
123
|
+
display: flex;
|
|
124
|
+
justify-content: space-between;
|
|
125
|
+
padding: 8px 0;
|
|
126
|
+
border-bottom: 1px solid #e1e4e8;
|
|
127
|
+
font-size: 13px;
|
|
128
|
+
}
|
|
129
|
+
.config-item:last-child { border-bottom: none; }
|
|
130
|
+
.config-label { color: #666; font-weight: 500; }
|
|
131
|
+
.config-value {
|
|
132
|
+
color: #333;
|
|
133
|
+
font-family: 'Courier New', monospace;
|
|
134
|
+
max-width: 300px;
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
text-overflow: ellipsis;
|
|
137
|
+
white-space: nowrap;
|
|
138
|
+
}
|
|
139
|
+
.config-value.masked { color: #999; }
|
|
140
|
+
.form-view { display: none; }
|
|
141
|
+
.form-group { margin-bottom: 16px; }
|
|
142
|
+
label {
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
gap: 6px;
|
|
146
|
+
margin-bottom: 6px;
|
|
147
|
+
color: #333;
|
|
148
|
+
font-weight: 500;
|
|
149
|
+
font-size: 14px;
|
|
150
|
+
}
|
|
151
|
+
input {
|
|
152
|
+
width: 100%;
|
|
153
|
+
padding: 10px 12px;
|
|
154
|
+
border: 2px solid #e1e4e8;
|
|
155
|
+
border-radius: 8px;
|
|
156
|
+
font-size: 13px;
|
|
157
|
+
font-family: "Helvetica Neue LT Pro", "Open Sans", 'Courier New', monospace !important;
|
|
158
|
+
transition: border-color 0.2s;
|
|
159
|
+
}
|
|
160
|
+
input:focus {
|
|
161
|
+
outline: none;
|
|
162
|
+
border-color: #b91d8f;
|
|
163
|
+
}
|
|
164
|
+
.optional {
|
|
165
|
+
color: #999;
|
|
166
|
+
font-weight: normal;
|
|
167
|
+
font-size: 12px;
|
|
168
|
+
}
|
|
169
|
+
.tooltip {
|
|
170
|
+
position: relative;
|
|
171
|
+
display: inline-flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
justify-content: center;
|
|
174
|
+
width: 16px;
|
|
175
|
+
height: 16px;
|
|
176
|
+
background: #b91d8f;
|
|
177
|
+
color: white;
|
|
178
|
+
border-radius: 50%;
|
|
179
|
+
font-size: 11px;
|
|
180
|
+
font-weight: bold;
|
|
181
|
+
cursor: pointer;
|
|
182
|
+
flex-shrink: 0;
|
|
183
|
+
user-select: none;
|
|
184
|
+
}
|
|
185
|
+
.tooltip-content {
|
|
186
|
+
display: none !important;
|
|
187
|
+
position: fixed;
|
|
188
|
+
max-width: 380px;
|
|
189
|
+
width: max-content;
|
|
190
|
+
background: white;
|
|
191
|
+
border-radius: 8px;
|
|
192
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
193
|
+
z-index: 10000;
|
|
194
|
+
overflow: hidden;
|
|
195
|
+
border: 2px solid #b91d8f;
|
|
196
|
+
pointer-events: auto;
|
|
197
|
+
}
|
|
198
|
+
.tooltip-content.active {
|
|
199
|
+
display: block !important;
|
|
200
|
+
}
|
|
201
|
+
.tooltip-arrow {
|
|
202
|
+
position: absolute;
|
|
203
|
+
width: 0;
|
|
204
|
+
height: 0;
|
|
205
|
+
border: 8px solid transparent;
|
|
206
|
+
z-index: 1;
|
|
207
|
+
}
|
|
208
|
+
.tooltip-arrow.left {
|
|
209
|
+
left: -16px;
|
|
210
|
+
top: 50%;
|
|
211
|
+
transform: translateY(-50%);
|
|
212
|
+
border-right-color: #b91d8f;
|
|
213
|
+
}
|
|
214
|
+
.tooltip-arrow.right {
|
|
215
|
+
right: -16px;
|
|
216
|
+
top: 50%;
|
|
217
|
+
transform: translateY(-50%);
|
|
218
|
+
border-left-color: #b91d8f;
|
|
219
|
+
}
|
|
220
|
+
.tooltip-arrow.top {
|
|
221
|
+
top: -16px;
|
|
222
|
+
left: 50%;
|
|
223
|
+
transform: translateX(-50%);
|
|
224
|
+
border-bottom-color: #b91d8f;
|
|
225
|
+
}
|
|
226
|
+
.tooltip-arrow.bottom {
|
|
227
|
+
bottom: -16px;
|
|
228
|
+
left: 50%;
|
|
229
|
+
transform: translateX(-50%);
|
|
230
|
+
border-top-color: #b91d8f;
|
|
231
|
+
}
|
|
232
|
+
.tooltip-header {
|
|
233
|
+
background: #b91d8f;
|
|
234
|
+
color: white;
|
|
235
|
+
padding: 8px 12px;
|
|
236
|
+
font-weight: 600;
|
|
237
|
+
font-size: 13px;
|
|
238
|
+
}
|
|
239
|
+
.tooltip-body {
|
|
240
|
+
padding: 10px 12px;
|
|
241
|
+
}
|
|
242
|
+
.tooltip-image {
|
|
243
|
+
width: 100%;
|
|
244
|
+
max-height: 200px;
|
|
245
|
+
object-fit: contain;
|
|
246
|
+
border-radius: 4px;
|
|
247
|
+
margin-bottom: 6px;
|
|
248
|
+
border: 1px solid #e1e4e8;
|
|
249
|
+
background: #f8f9fa;
|
|
250
|
+
}
|
|
251
|
+
.tooltip-text {
|
|
252
|
+
color: #555;
|
|
253
|
+
font-size: 12px;
|
|
254
|
+
line-height: 1.4;
|
|
255
|
+
font-style: italic;
|
|
256
|
+
}
|
|
257
|
+
.button-group {
|
|
258
|
+
display: flex;
|
|
259
|
+
gap: 10px;
|
|
260
|
+
margin-top: 20px;
|
|
261
|
+
}
|
|
262
|
+
button {
|
|
263
|
+
flex: 1;
|
|
264
|
+
padding: 12px;
|
|
265
|
+
border: 2px solid transparent;
|
|
266
|
+
border-radius: 8px;
|
|
267
|
+
font-size: 15px;
|
|
268
|
+
font-weight: 600;
|
|
269
|
+
cursor: pointer;
|
|
270
|
+
transition: all 0.2s;
|
|
271
|
+
}
|
|
272
|
+
.btn-primary {
|
|
273
|
+
background: linear-gradient(135deg, #b91d8f 0%, #7a1460 100%);
|
|
274
|
+
color: white;
|
|
275
|
+
}
|
|
276
|
+
.btn-primary:hover {
|
|
277
|
+
transform: translateY(-2px);
|
|
278
|
+
box-shadow: 0 6px 20px rgba(185, 29, 143, 0.4);
|
|
279
|
+
}
|
|
280
|
+
.btn-secondary {
|
|
281
|
+
background: #f6f8fa;
|
|
282
|
+
color: #666;
|
|
283
|
+
}
|
|
284
|
+
.btn-secondary:hover {
|
|
285
|
+
background: #e1e4e8;
|
|
286
|
+
}
|
|
287
|
+
.btn-danger {
|
|
288
|
+
background: white;
|
|
289
|
+
color: #b91d8f;
|
|
290
|
+
border: 2px solid #b91d8f;
|
|
291
|
+
}
|
|
292
|
+
.btn-danger:hover {
|
|
293
|
+
background: #fef1fd;
|
|
294
|
+
transform: translateY(-1px);
|
|
295
|
+
}
|
|
296
|
+
.btn-link {
|
|
297
|
+
background: transparent;
|
|
298
|
+
color: #b91d8f;
|
|
299
|
+
padding: 8px;
|
|
300
|
+
font-size: 14px;
|
|
301
|
+
}
|
|
302
|
+
.btn-link:hover {
|
|
303
|
+
background: #f6f8fa;
|
|
304
|
+
}
|
|
305
|
+
.status {
|
|
306
|
+
margin-top: 20px;
|
|
307
|
+
padding: 12px;
|
|
308
|
+
border-radius: 8px;
|
|
309
|
+
font-size: 14px;
|
|
310
|
+
display: none;
|
|
311
|
+
}
|
|
312
|
+
.status.success {
|
|
313
|
+
background: #d4edda;
|
|
314
|
+
color: #155724;
|
|
315
|
+
border: 1px solid #c3e6cb;
|
|
316
|
+
}
|
|
317
|
+
.status.error {
|
|
318
|
+
background: #f8d7da;
|
|
319
|
+
color: #721c24;
|
|
320
|
+
border: 1px solid #f5c6cb;
|
|
321
|
+
}
|
|
322
|
+
.status.info {
|
|
323
|
+
background: #d1ecf1;
|
|
324
|
+
color: #0c5460;
|
|
325
|
+
border: 1px solid #bee5eb;
|
|
326
|
+
}
|
|
327
|
+
/* Loading overlay and spinner */
|
|
328
|
+
.loading-overlay {
|
|
329
|
+
display: none;
|
|
330
|
+
position: absolute;
|
|
331
|
+
top: -32px;
|
|
332
|
+
left: -32px;
|
|
333
|
+
right: -32px;
|
|
334
|
+
bottom: -32px;
|
|
335
|
+
background: rgba(255, 255, 255, 0.85);
|
|
336
|
+
backdrop-filter: blur(2px);
|
|
337
|
+
border-radius: 16px;
|
|
338
|
+
z-index: 1000;
|
|
339
|
+
justify-content: center;
|
|
340
|
+
align-items: center;
|
|
341
|
+
flex-direction: column;
|
|
342
|
+
gap: 16px;
|
|
343
|
+
}
|
|
344
|
+
.loading-overlay.active {
|
|
345
|
+
display: flex;
|
|
346
|
+
}
|
|
347
|
+
.spinner {
|
|
348
|
+
width: 48px;
|
|
349
|
+
height: 48px;
|
|
350
|
+
border: 4px solid #e1e4e8;
|
|
351
|
+
border-top-color: #b91d8f;
|
|
352
|
+
border-radius: 50%;
|
|
353
|
+
animation: spin 0.8s linear infinite;
|
|
354
|
+
}
|
|
355
|
+
@keyframes spin {
|
|
356
|
+
to { transform: rotate(360deg); }
|
|
357
|
+
}
|
|
358
|
+
.loading-message {
|
|
359
|
+
color: #666;
|
|
360
|
+
font-size: 14px;
|
|
361
|
+
font-weight: 500;
|
|
362
|
+
text-align: center;
|
|
363
|
+
max-width: 300px;
|
|
364
|
+
}
|
|
365
|
+
.form-view {
|
|
366
|
+
position: relative;
|
|
367
|
+
}
|
|
368
|
+
.modal-overlay {
|
|
369
|
+
display: none;
|
|
370
|
+
position: fixed;
|
|
371
|
+
top: 0;
|
|
372
|
+
left: 0;
|
|
373
|
+
right: 0;
|
|
374
|
+
bottom: 0;
|
|
375
|
+
background: rgba(0, 0, 0, 0.5);
|
|
376
|
+
z-index: 10001;
|
|
377
|
+
justify-content: center;
|
|
378
|
+
align-items: center;
|
|
379
|
+
}
|
|
380
|
+
.modal-overlay.active {
|
|
381
|
+
display: flex;
|
|
382
|
+
}
|
|
383
|
+
.modal-content {
|
|
384
|
+
background: white;
|
|
385
|
+
border-radius: 12px;
|
|
386
|
+
padding: 30px;
|
|
387
|
+
max-width: 400px;
|
|
388
|
+
width: 90%;
|
|
389
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
390
|
+
}
|
|
391
|
+
.modal-title {
|
|
392
|
+
font-size: 20px;
|
|
393
|
+
font-weight: 600;
|
|
394
|
+
color: #333;
|
|
395
|
+
margin-bottom: 12px;
|
|
396
|
+
}
|
|
397
|
+
.modal-message {
|
|
398
|
+
font-size: 14px;
|
|
399
|
+
color: #666;
|
|
400
|
+
margin-bottom: 24px;
|
|
401
|
+
line-height: 1.5;
|
|
402
|
+
}
|
|
403
|
+
.modal-buttons {
|
|
404
|
+
display: flex;
|
|
405
|
+
gap: 10px;
|
|
406
|
+
justify-content: flex-end;
|
|
407
|
+
}
|
|
408
|
+
.modal-buttons button {
|
|
409
|
+
padding: 10px 20px;
|
|
410
|
+
font-size: 14px;
|
|
411
|
+
flex: none;
|
|
412
|
+
}
|
|
413
|
+
</style>
|
|
414
|
+
</head>
|
|
415
|
+
<body>
|
|
416
|
+
<div class="container">
|
|
417
|
+
<div id="quickSigninView" class="quick-signin">
|
|
418
|
+
<div id="loadingOverlayQuickSignin" class="loading-overlay">
|
|
419
|
+
<div class="spinner"></div>
|
|
420
|
+
<div id="loadingMessageQuickSignin" class="loading-message">Redirecting to login...</div>
|
|
421
|
+
</div>
|
|
422
|
+
<div class="icon">🔐</div>
|
|
423
|
+
<h1>Welcome Back!</h1>
|
|
424
|
+
<p class="subtitle">Ready to sign in with your saved configuration</p>
|
|
425
|
+
<div class="saved-config">
|
|
426
|
+
<div class="saved-config-title">Saved Configuration</div>
|
|
427
|
+
<div id="savedConfigList"></div>
|
|
428
|
+
</div>
|
|
429
|
+
<div class="button-group">
|
|
430
|
+
<button type="button" class="btn-danger" onclick="clearConfigAndShowForm()">Clear All</button>
|
|
431
|
+
<button type="button" class="btn-primary" onclick="signInWithSavedConfig()">Sign In</button>
|
|
432
|
+
</div>
|
|
433
|
+
<button type="button" class="btn-link" onclick="showForm()" style="width: 100%; margin-top: 10px;">Edit Configuration</button>
|
|
434
|
+
</div>
|
|
435
|
+
<div id="formView" class="form-view">
|
|
436
|
+
<div id="loadingOverlay" class="loading-overlay">
|
|
437
|
+
<div class="spinner"></div>
|
|
438
|
+
<div id="loadingMessage" class="loading-message">Saving configuration...</div>
|
|
439
|
+
</div>
|
|
440
|
+
<h1>🔐 eGain MCP Configuration</h1>
|
|
441
|
+
<p class="subtitle">Enter details from your eGain <strong>Admin Console</strong></p>
|
|
442
|
+
<form id="configForm">
|
|
443
|
+
<div class="form-group">
|
|
444
|
+
<label for="egainUrl">
|
|
445
|
+
<span>eGain Environment URL</span>
|
|
446
|
+
<span class="tooltip" onmouseenter="showTooltip(event, 'egainUrl')" onmouseleave="hideTooltip(event, 'egainUrl')">?</span>
|
|
447
|
+
</label>
|
|
448
|
+
<input type="text" id="egainUrl" name="egainUrl" placeholder="https://your-environment.egain.cloud" required>
|
|
449
|
+
</div>
|
|
450
|
+
<div class="form-group">
|
|
451
|
+
<label for="authUrl">
|
|
452
|
+
<span>Authorization URL</span>
|
|
453
|
+
<span class="tooltip" onmouseenter="showTooltip(event, 'authUrl')" onmouseleave="hideTooltip(event, 'authUrl')">?</span>
|
|
454
|
+
</label>
|
|
455
|
+
<input type="text" id="authUrl" name="authUrl" placeholder="https://login.egain.cloud/.../oauth2/authorize" required>
|
|
456
|
+
</div>
|
|
457
|
+
<div class="form-group">
|
|
458
|
+
<label for="accessTokenUrl">
|
|
459
|
+
<span>Access Token URL</span>
|
|
460
|
+
<span class="tooltip" onmouseenter="showTooltip(event, 'accessTokenUrl')" onmouseleave="hideTooltip(event, 'accessTokenUrl')">?</span>
|
|
461
|
+
</label>
|
|
462
|
+
<input type="text" id="accessTokenUrl" name="accessTokenUrl" placeholder="https://login.egain.cloud/.../oauth2/token" required>
|
|
463
|
+
</div>
|
|
464
|
+
<div class="form-group">
|
|
465
|
+
<label for="clientId">
|
|
466
|
+
<span>Client ID</span>
|
|
467
|
+
<span class="tooltip" onmouseenter="showTooltip(event, 'clientId')" onmouseleave="hideTooltip(event, 'clientId')">?</span>
|
|
468
|
+
</label>
|
|
469
|
+
<input type="text" id="clientId" name="clientId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" required>
|
|
470
|
+
</div>
|
|
471
|
+
<div class="form-group">
|
|
472
|
+
<label for="redirectUrl">
|
|
473
|
+
<span>Redirect URL</span>
|
|
474
|
+
<span class="tooltip" onmouseenter="showTooltip(event, 'redirectUrl')" onmouseleave="hideTooltip(event, 'redirectUrl')">?</span>
|
|
475
|
+
</label>
|
|
476
|
+
<input type="text" id="redirectUrl" name="redirectUrl" placeholder="https://your-redirect-url.com/" required>
|
|
477
|
+
</div>
|
|
478
|
+
|
|
479
|
+
<!-- Advanced Settings Toggle -->
|
|
480
|
+
<div style="margin: 12px 0;">
|
|
481
|
+
<button type="button" onclick="toggleAdvancedSettings()" style="padding: 6px 0; font-size: 13px; display: flex; align-items: center; gap: 6px; background: none; border: none; color: #b91d8f; cursor: pointer; font-family: inherit;">
|
|
482
|
+
<span id="advancedToggleIcon">▶</span>
|
|
483
|
+
<span>Advanced Settings</span>
|
|
484
|
+
<span style="color: #999; font-size: 11px;">(optional)</span>
|
|
485
|
+
</button>
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
<!-- Advanced Settings Section (hidden by default) -->
|
|
489
|
+
<div id="advancedSettings" style="display: none;">
|
|
490
|
+
<div class="form-group">
|
|
491
|
+
<label for="clientSecret">
|
|
492
|
+
<span>Client Secret</span>
|
|
493
|
+
<span class="tooltip" onmouseenter="showTooltip(event, 'clientSecret')" onmouseleave="hideTooltip(event, 'clientSecret')">?</span>
|
|
494
|
+
<span class="optional">(optional)</span>
|
|
495
|
+
</label>
|
|
496
|
+
<input type="password" id="clientSecret" name="clientSecret" placeholder="Required for normal authentication flow/non-PKCE">
|
|
497
|
+
</div>
|
|
498
|
+
<div class="form-group">
|
|
499
|
+
<label for="scopePrefix">
|
|
500
|
+
<span>Scope Prefix</span>
|
|
501
|
+
<span class="tooltip" onmouseenter="showTooltip(event, 'scopePrefix')" onmouseleave="hideTooltip(event, 'scopePrefix')">?</span>
|
|
502
|
+
<span class="optional">(optional)</span>
|
|
503
|
+
</label>
|
|
504
|
+
<input type="text" id="scopePrefix" name="scopePrefix" placeholder="https://your.scope-prefix.cloud/auth/">
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
<div style="font-size: 11px; color: #999; margin: 12px 0 0 0; line-height: 1.4;">
|
|
508
|
+
🔒 Your configuration will be securely saved to your <code style="background: #f0f0f0; padding: 2px 4px; border-radius: 3px; font-size: 10px;">~/.egain-mcp/config.json</code>.
|
|
509
|
+
</div>
|
|
510
|
+
<div class="button-group">
|
|
511
|
+
<button type="button" class="btn-secondary" onclick="cancelForm()">Cancel</button>
|
|
512
|
+
<button type="submit" class="btn-primary">Save & Authenticate</button>
|
|
513
|
+
</div>
|
|
514
|
+
</form>
|
|
515
|
+
</div>
|
|
516
|
+
<div id="status" class="status"></div>
|
|
517
|
+
</div>
|
|
518
|
+
|
|
519
|
+
<!-- Confirmation Modal -->
|
|
520
|
+
<div id="confirmModal" class="modal-overlay" onclick="if(event.target === this) closeModal(false)">
|
|
521
|
+
<div class="modal-content">
|
|
522
|
+
<div class="modal-title" id="modalTitle">Confirm Action</div>
|
|
523
|
+
<div class="modal-message" id="modalMessage">Are you sure?</div>
|
|
524
|
+
<div class="modal-buttons">
|
|
525
|
+
<button type="button" class="btn-secondary" onclick="closeModal(false)">Cancel</button>
|
|
526
|
+
<button type="button" class="btn-danger" id="modalConfirmBtn" onclick="closeModal(true)">Confirm</button>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
|
|
531
|
+
<!-- Tooltip Popups (outside container for proper fixed positioning) -->
|
|
532
|
+
<div id="tooltip-egainUrl" class="tooltip-content">
|
|
533
|
+
<div class="tooltip-header">eGain Environment URL</div>
|
|
534
|
+
<div class="tooltip-body">
|
|
535
|
+
<img src="/img/env-tooltip.png" class="tooltip-image" alt="eGain Environment URL" onerror="this.style.display='none'">
|
|
536
|
+
<div class="tooltip-text">Enter the domain URL displayed in your browser when accessing the eGain application.</div>
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
|
|
540
|
+
<div id="tooltip-authUrl" class="tooltip-content">
|
|
541
|
+
<div class="tooltip-header">Authorization URL</div>
|
|
542
|
+
<div class="tooltip-body">
|
|
543
|
+
<img src="/img/authurl-tooltip.png" class="tooltip-image" alt="Authorization URL" onerror="this.style.display='none'">
|
|
544
|
+
<div class="tooltip-text">In the Partition space, go to Integration → Client Application → Metadata, and copy the Authorization URL.</div>
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
|
|
548
|
+
<div id="tooltip-accessTokenUrl" class="tooltip-content">
|
|
549
|
+
<div class="tooltip-header">Access Token URL</div>
|
|
550
|
+
<div class="tooltip-body">
|
|
551
|
+
<img src="/img/accesstoken-tooltip.png" class="tooltip-image" alt="Access Token URL" onerror="this.style.display='none'">
|
|
552
|
+
<div class="tooltip-text">In the Partition space, go to Integration → Client Application → Metadata, and copy the Access Token URL.</div>
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
|
|
556
|
+
<div id="tooltip-clientId" class="tooltip-content">
|
|
557
|
+
<div class="tooltip-header">Client ID</div>
|
|
558
|
+
<div class="tooltip-body">
|
|
559
|
+
<img src="/img/clientid-tooltip.png" class="tooltip-image" alt="Client ID" onerror="this.style.display='none'">
|
|
560
|
+
<div class="tooltip-text">In the Partition space, go to Integration → Client Application, select your client app, and copy the Client ID.</div>
|
|
561
|
+
</div>
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
<div id="tooltip-redirectUrl" class="tooltip-content">
|
|
565
|
+
<div class="tooltip-header">Redirect URL</div>
|
|
566
|
+
<div class="tooltip-body">
|
|
567
|
+
<img src="/img/redirect-tooltip.png" class="tooltip-image" alt="Redirect URL" onerror="this.style.display='none'">
|
|
568
|
+
<div class="tooltip-text">In the Partition space, go to Integration → Client Application, select your client app, and copy the Redirect URL.</div>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
|
|
572
|
+
<div id="tooltip-clientSecret" class="tooltip-content">
|
|
573
|
+
<div class="tooltip-header">Client Secret</div>
|
|
574
|
+
<div class="tooltip-body">
|
|
575
|
+
<img src="/img/clientsecret-tooltip.png" class="tooltip-image" alt="Client Secret" onerror="this.style.display='none'">
|
|
576
|
+
<div class="tooltip-text">In the Partition space, go to Integration → Client Application, select your client app, and copy the Client Secret under Secrets.</div>
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
|
|
580
|
+
<div id="tooltip-scopePrefix" class="tooltip-content">
|
|
581
|
+
<div class="tooltip-header">Scope Prefix</div>
|
|
582
|
+
<div class="tooltip-body">
|
|
583
|
+
<img src="/img/scopeprefix-tooltip.png" class="tooltip-image" alt="Scope Prefix" onerror="this.style.display='none'">
|
|
584
|
+
<div class="tooltip-text">In the Partition space, go to Integration → Client Application → Metadata, and copy the API Permission Prefix.</div>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
<script src="/config-page.js"></script>
|
|
589
|
+
</body>
|
|
590
|
+
</html>`;
|
|
591
|
+
|
|
592
|
+
const CONFIG_PAGE_JS = `const FIELDS = ['egainUrl', 'authUrl', 'accessTokenUrl', 'clientId', 'redirectUrl', 'clientSecret', 'scopePrefix'];
|
|
593
|
+
const FIELD_LABELS = {
|
|
594
|
+
egainUrl: 'eGain URL', authUrl: 'Auth URL', accessTokenUrl: 'Token URL',
|
|
595
|
+
clientId: 'Client ID', redirectUrl: 'Redirect URL', clientSecret: 'Client Secret',
|
|
596
|
+
scopePrefix: 'Scope Prefix'
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
let savedConfigData = null; // Store config in memory only
|
|
600
|
+
let authenticationStarted = false; // Track if user started authentication process
|
|
601
|
+
let lastSubmittedConfig = null; // Track last submitted config to prevent duplicate submissions
|
|
602
|
+
let isSubmitting = false; // Track if form submission is in progress
|
|
603
|
+
|
|
604
|
+
// Fetch saved config from backend (secure file storage)
|
|
605
|
+
async function loadSavedConfig() {
|
|
606
|
+
try {
|
|
607
|
+
const response = await fetch('/get-config');
|
|
608
|
+
if (response.ok) {
|
|
609
|
+
const data = await response.json();
|
|
610
|
+
if (data.config && data.config.egainUrl && data.config.clientId) {
|
|
611
|
+
// Only set if we have valid required fields
|
|
612
|
+
savedConfigData = data.config;
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
} catch (error) {
|
|
617
|
+
console.error('Could not load saved config:', error);
|
|
618
|
+
}
|
|
619
|
+
savedConfigData = null;
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function hasSavedConfig() {
|
|
624
|
+
return savedConfigData &&
|
|
625
|
+
savedConfigData.egainUrl &&
|
|
626
|
+
savedConfigData.clientId &&
|
|
627
|
+
savedConfigData.authUrl &&
|
|
628
|
+
savedConfigData.accessTokenUrl;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function displaySavedConfig() {
|
|
632
|
+
const listEl = document.getElementById('savedConfigList');
|
|
633
|
+
listEl.innerHTML = '';
|
|
634
|
+
if (!savedConfigData) return;
|
|
635
|
+
|
|
636
|
+
FIELDS.forEach(field => {
|
|
637
|
+
const value = savedConfigData[field];
|
|
638
|
+
if (value) {
|
|
639
|
+
const item = document.createElement('div');
|
|
640
|
+
item.className = 'config-item';
|
|
641
|
+
const label = document.createElement('span');
|
|
642
|
+
label.className = 'config-label';
|
|
643
|
+
label.textContent = FIELD_LABELS[field];
|
|
644
|
+
const valueSpan = document.createElement('span');
|
|
645
|
+
valueSpan.className = 'config-value';
|
|
646
|
+
if (field === 'clientSecret') {
|
|
647
|
+
valueSpan.classList.add('masked');
|
|
648
|
+
valueSpan.textContent = '••••••••';
|
|
649
|
+
} else if (field === 'clientId') {
|
|
650
|
+
valueSpan.textContent = value.substring(0, 8) + '...';
|
|
651
|
+
} else {
|
|
652
|
+
valueSpan.textContent = value.length > 40 ? value.substring(0, 40) + '...' : value;
|
|
653
|
+
}
|
|
654
|
+
item.appendChild(label);
|
|
655
|
+
item.appendChild(valueSpan);
|
|
656
|
+
listEl.appendChild(item);
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function showQuickSignin() {
|
|
662
|
+
document.getElementById('quickSigninView').style.display = 'block';
|
|
663
|
+
document.getElementById('formView').style.display = 'none';
|
|
664
|
+
displaySavedConfig();
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function showForm() {
|
|
668
|
+
document.getElementById('quickSigninView').style.display = 'none';
|
|
669
|
+
document.getElementById('formView').style.display = 'block';
|
|
670
|
+
loadFormValues();
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function loadFormValues() {
|
|
674
|
+
if (!savedConfigData) return;
|
|
675
|
+
|
|
676
|
+
// Load all field values
|
|
677
|
+
FIELDS.forEach(field => {
|
|
678
|
+
const value = savedConfigData[field];
|
|
679
|
+
if (value) document.getElementById(field).value = value;
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// Show advanced settings if clientSecret or scopePrefix exist
|
|
683
|
+
if (savedConfigData.clientSecret || savedConfigData.scopePrefix) {
|
|
684
|
+
toggleAdvancedSettings();
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
async function clearConfigAndShowForm() {
|
|
689
|
+
showModal(
|
|
690
|
+
'Clear All Configuration?',
|
|
691
|
+
'This will delete all saved OAuth settings from your home directory. You will need to re-enter them next time.',
|
|
692
|
+
async (confirmed) => {
|
|
693
|
+
if (confirmed) {
|
|
694
|
+
try {
|
|
695
|
+
const response = await fetch('/clear-config', { method: 'POST' });
|
|
696
|
+
if (response.ok) {
|
|
697
|
+
savedConfigData = null;
|
|
698
|
+
FIELDS.forEach(field => {
|
|
699
|
+
document.getElementById(field).value = '';
|
|
700
|
+
});
|
|
701
|
+
showStatus('Configuration cleared successfully', 'success');
|
|
702
|
+
showForm();
|
|
703
|
+
} else {
|
|
704
|
+
showStatus('Failed to clear configuration', 'error');
|
|
705
|
+
}
|
|
706
|
+
} catch (error) {
|
|
707
|
+
showStatus('Error clearing configuration: ' + error.message, 'error');
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
'Clear All'
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function cancelForm() {
|
|
716
|
+
if (hasSavedConfig()) {
|
|
717
|
+
showQuickSignin();
|
|
718
|
+
} else {
|
|
719
|
+
showModal(
|
|
720
|
+
'Cancel Authentication?',
|
|
721
|
+
"You haven't saved any configuration yet. This will cancel the authentication process.",
|
|
722
|
+
async (confirmed) => {
|
|
723
|
+
if (confirmed) {
|
|
724
|
+
// Notify server that user cancelled
|
|
725
|
+
try {
|
|
726
|
+
await fetch('/cancel', { method: 'POST' });
|
|
727
|
+
} catch (error) {
|
|
728
|
+
console.error('Could not notify server of cancellation:', error);
|
|
729
|
+
}
|
|
730
|
+
window.close();
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
'Cancel'
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
async function signInWithSavedConfig() {
|
|
739
|
+
authenticationStarted = true; // Mark that auth has started
|
|
740
|
+
|
|
741
|
+
// Show loading overlay
|
|
742
|
+
showLoadingOverlay('Redirecting to login...');
|
|
743
|
+
|
|
744
|
+
try {
|
|
745
|
+
const response = await fetch('/get-oauth-url', { method: 'POST' });
|
|
746
|
+
const result = await response.json();
|
|
747
|
+
|
|
748
|
+
if (response.ok && result.oauthUrl) {
|
|
749
|
+
console.log('🔗 OAuth URL:', result.oauthUrl);
|
|
750
|
+
// Redirect after a brief delay to show loading state
|
|
751
|
+
setTimeout(() => {
|
|
752
|
+
window.location.href = result.oauthUrl;
|
|
753
|
+
}, 500);
|
|
754
|
+
} else {
|
|
755
|
+
hideLoadingOverlay();
|
|
756
|
+
showStatus('❌ ' + (result.error || 'Failed to get OAuth URL'), 'error');
|
|
757
|
+
authenticationStarted = false; // Reset on error
|
|
758
|
+
}
|
|
759
|
+
} catch (error) {
|
|
760
|
+
hideLoadingOverlay();
|
|
761
|
+
showStatus('❌ Error: ' + error.message, 'error');
|
|
762
|
+
authenticationStarted = false; // Reset on error
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function showStatus(message, type) {
|
|
767
|
+
const statusEl = document.getElementById('status');
|
|
768
|
+
statusEl.textContent = message;
|
|
769
|
+
statusEl.className = 'status ' + type;
|
|
770
|
+
statusEl.style.display = 'block';
|
|
771
|
+
// Auto-hide info and success messages after a delay
|
|
772
|
+
if (type === 'info') {
|
|
773
|
+
setTimeout(() => {
|
|
774
|
+
// Only hide if it's still an info message (not changed to success/error)
|
|
775
|
+
if (statusEl.className === 'status info') {
|
|
776
|
+
statusEl.style.display = 'none';
|
|
777
|
+
}
|
|
778
|
+
}, 3000);
|
|
779
|
+
} else if (type === 'success') {
|
|
780
|
+
setTimeout(() => {
|
|
781
|
+
// Only hide if it's still a success message (not changed to error)
|
|
782
|
+
if (statusEl.className === 'status success') {
|
|
783
|
+
statusEl.style.display = 'none';
|
|
784
|
+
}
|
|
785
|
+
}, 4000);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function configValuesEqual(config1, config2) {
|
|
790
|
+
// Compare all fields that matter
|
|
791
|
+
const fieldsToCompare = ['egainUrl', 'authUrl', 'accessTokenUrl', 'clientId', 'redirectUrl', 'clientSecret', 'scopePrefix'];
|
|
792
|
+
for (const field of fieldsToCompare) {
|
|
793
|
+
const val1 = (config1[field] || '').trim();
|
|
794
|
+
const val2 = (config2[field] || '').trim();
|
|
795
|
+
if (val1 !== val2) {
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function showLoadingOverlay(message) {
|
|
803
|
+
// Show overlay for form view
|
|
804
|
+
const overlay = document.getElementById('loadingOverlay');
|
|
805
|
+
const loadingMessage = document.getElementById('loadingMessage');
|
|
806
|
+
if (overlay) {
|
|
807
|
+
overlay.classList.add('active');
|
|
808
|
+
if (loadingMessage) {
|
|
809
|
+
loadingMessage.textContent = message || 'Saving configuration...';
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Show overlay for quick signin view
|
|
814
|
+
const overlayQuickSignin = document.getElementById('loadingOverlayQuickSignin');
|
|
815
|
+
const loadingMessageQuickSignin = document.getElementById('loadingMessageQuickSignin');
|
|
816
|
+
if (overlayQuickSignin) {
|
|
817
|
+
overlayQuickSignin.classList.add('active');
|
|
818
|
+
if (loadingMessageQuickSignin) {
|
|
819
|
+
loadingMessageQuickSignin.textContent = message || 'Redirecting to login...';
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function hideLoadingOverlay() {
|
|
825
|
+
// Hide overlay for form view
|
|
826
|
+
const overlay = document.getElementById('loadingOverlay');
|
|
827
|
+
if (overlay) {
|
|
828
|
+
overlay.classList.remove('active');
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Hide overlay for quick signin view
|
|
832
|
+
const overlayQuickSignin = document.getElementById('loadingOverlayQuickSignin');
|
|
833
|
+
if (overlayQuickSignin) {
|
|
834
|
+
overlayQuickSignin.classList.remove('active');
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
async function authenticateWithConfig(config) {
|
|
839
|
+
// Prevent duplicate submissions
|
|
840
|
+
if (isSubmitting) {
|
|
841
|
+
return; // Already submitting, ignore
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Check if values have changed since last submission
|
|
845
|
+
if (lastSubmittedConfig && configValuesEqual(config, lastSubmittedConfig)) {
|
|
846
|
+
showStatus('⚠️ Configuration unchanged. Already saved.', 'info');
|
|
847
|
+
return; // Values haven't changed, don't submit again
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
try {
|
|
851
|
+
isSubmitting = true; // Mark as submitting
|
|
852
|
+
const submitButton = document.querySelector('button[type="submit"]');
|
|
853
|
+
if (submitButton) {
|
|
854
|
+
submitButton.disabled = true;
|
|
855
|
+
submitButton.textContent = 'Saving...';
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Show loading overlay with spinner
|
|
859
|
+
showLoadingOverlay('Saving configuration...');
|
|
860
|
+
authenticationStarted = true; // Mark that auth has started
|
|
861
|
+
|
|
862
|
+
const response = await fetch('/authenticate', {
|
|
863
|
+
method: 'POST',
|
|
864
|
+
headers: { 'Content-Type': 'application/json' },
|
|
865
|
+
body: JSON.stringify(config)
|
|
866
|
+
});
|
|
867
|
+
const result = await response.json();
|
|
868
|
+
|
|
869
|
+
if (response.ok && result.oauthUrl) {
|
|
870
|
+
// Config saved! Store this config as last submitted
|
|
871
|
+
lastSubmittedConfig = { ...config };
|
|
872
|
+
|
|
873
|
+
console.log('🔗 OAuth URL:', result.oauthUrl);
|
|
874
|
+
|
|
875
|
+
// Update loading message (overlay already shows the status, no need for status message)
|
|
876
|
+
showLoadingOverlay('Configuration saved! Redirecting to login...');
|
|
877
|
+
|
|
878
|
+
setTimeout(() => {
|
|
879
|
+
window.location.href = result.oauthUrl;
|
|
880
|
+
}, 500);
|
|
881
|
+
} else if (response.ok && result.success) {
|
|
882
|
+
lastSubmittedConfig = { ...config };
|
|
883
|
+
hideLoadingOverlay();
|
|
884
|
+
showStatus('✅ ' + result.message, 'success');
|
|
885
|
+
setTimeout(() => { window.close(); }, 2000);
|
|
886
|
+
} else {
|
|
887
|
+
hideLoadingOverlay();
|
|
888
|
+
showStatus('❌ ' + (result.error || 'Authentication failed'), 'error');
|
|
889
|
+
authenticationStarted = false; // Reset on error
|
|
890
|
+
isSubmitting = false; // Reset submitting flag
|
|
891
|
+
if (submitButton) {
|
|
892
|
+
submitButton.disabled = false;
|
|
893
|
+
submitButton.textContent = 'Save & Authenticate';
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
} catch (error) {
|
|
897
|
+
hideLoadingOverlay();
|
|
898
|
+
showStatus('❌ Error: ' + error.message, 'error');
|
|
899
|
+
authenticationStarted = false; // Reset on error
|
|
900
|
+
isSubmitting = false; // Reset submitting flag
|
|
901
|
+
const submitButton = document.querySelector('button[type="submit"]');
|
|
902
|
+
if (submitButton) {
|
|
903
|
+
submitButton.disabled = false;
|
|
904
|
+
submitButton.textContent = 'Save & Authenticate';
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
document.getElementById('configForm').addEventListener('submit', async (e) => {
|
|
910
|
+
e.preventDefault();
|
|
911
|
+
|
|
912
|
+
// Prevent duplicate submissions
|
|
913
|
+
if (isSubmitting) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const formData = new FormData(e.target);
|
|
918
|
+
const config = {};
|
|
919
|
+
for (let [key, value] of formData.entries()) {
|
|
920
|
+
config[key] = value;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Config is now sent to backend for secure file storage (not cookies)
|
|
924
|
+
await authenticateWithConfig(config);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
// Initialize: Load saved config from backend
|
|
928
|
+
(async () => {
|
|
929
|
+
const hasConfig = await loadSavedConfig();
|
|
930
|
+
if (hasConfig) {
|
|
931
|
+
showQuickSignin();
|
|
932
|
+
} else {
|
|
933
|
+
showForm();
|
|
934
|
+
}
|
|
935
|
+
})();
|
|
936
|
+
|
|
937
|
+
// Custom modal functions
|
|
938
|
+
let modalCallback = null;
|
|
939
|
+
|
|
940
|
+
function showModal(title, message, callback, confirmText = 'Confirm') {
|
|
941
|
+
document.getElementById('modalTitle').textContent = title;
|
|
942
|
+
document.getElementById('modalMessage').textContent = message;
|
|
943
|
+
document.getElementById('modalConfirmBtn').textContent = confirmText;
|
|
944
|
+
modalCallback = callback;
|
|
945
|
+
document.getElementById('confirmModal').classList.add('active');
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function closeModal(confirmed) {
|
|
949
|
+
document.getElementById('confirmModal').classList.remove('active');
|
|
950
|
+
if (modalCallback) {
|
|
951
|
+
modalCallback(confirmed);
|
|
952
|
+
modalCallback = null;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Advanced settings toggle
|
|
957
|
+
function toggleAdvancedSettings() {
|
|
958
|
+
const advancedSection = document.getElementById('advancedSettings');
|
|
959
|
+
const toggleIcon = document.getElementById('advancedToggleIcon');
|
|
960
|
+
|
|
961
|
+
if (advancedSection.style.display === 'none') {
|
|
962
|
+
advancedSection.style.display = 'block';
|
|
963
|
+
toggleIcon.textContent = '▼';
|
|
964
|
+
} else {
|
|
965
|
+
advancedSection.style.display = 'none';
|
|
966
|
+
toggleIcon.textContent = '▶';
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Tooltip functions
|
|
971
|
+
function showTooltip(event, fieldName) {
|
|
972
|
+
event.preventDefault(); // Prevent label from focusing input
|
|
973
|
+
event.stopPropagation(); // Prevent event from bubbling
|
|
974
|
+
|
|
975
|
+
const tooltipId = 'tooltip-' + fieldName;
|
|
976
|
+
let tooltipElement = document.getElementById(tooltipId);
|
|
977
|
+
const button = event.currentTarget;
|
|
978
|
+
|
|
979
|
+
if (tooltipElement) {
|
|
980
|
+
// Show the tooltip first to get its dimensions
|
|
981
|
+
tooltipElement.classList.add('active');
|
|
982
|
+
|
|
983
|
+
// Get actual dimensions after showing
|
|
984
|
+
const buttonRect = button.getBoundingClientRect();
|
|
985
|
+
const tooltipRect = tooltipElement.getBoundingClientRect();
|
|
986
|
+
const tooltipWidth = tooltipRect.width || 380;
|
|
987
|
+
const tooltipHeight = tooltipRect.height || 300;
|
|
988
|
+
|
|
989
|
+
const spacing = 16; // Space between button and tooltip
|
|
990
|
+
const viewportPadding = 10; // Padding from viewport edges
|
|
991
|
+
const horizontalComfortZone = 50; // Extra space needed to avoid cramped horizontal positioning
|
|
992
|
+
|
|
993
|
+
// Calculate available space on all sides
|
|
994
|
+
const spaceOnRight = window.innerWidth - buttonRect.right;
|
|
995
|
+
const spaceOnLeft = buttonRect.left;
|
|
996
|
+
const spaceBelow = window.innerHeight - buttonRect.bottom;
|
|
997
|
+
const spaceAbove = buttonRect.top;
|
|
998
|
+
|
|
999
|
+
let left, top;
|
|
1000
|
+
let arrowSide = 'left'; // Default: tooltip on right, arrow on left
|
|
1001
|
+
|
|
1002
|
+
// Check if horizontal positioning would be too cramped (tooltip might cover the icon)
|
|
1003
|
+
const horizontalSpaceTight = (spaceOnRight < tooltipWidth + horizontalComfortZone) &&
|
|
1004
|
+
(spaceOnLeft < tooltipWidth + horizontalComfortZone);
|
|
1005
|
+
|
|
1006
|
+
if (horizontalSpaceTight) {
|
|
1007
|
+
// Use vertical positioning to avoid covering the icon
|
|
1008
|
+
// Determine if we're in the top or bottom half of the viewport
|
|
1009
|
+
const inTopHalf = buttonRect.top < window.innerHeight / 2;
|
|
1010
|
+
|
|
1011
|
+
if (inTopHalf && spaceBelow >= tooltipHeight + spacing + viewportPadding) {
|
|
1012
|
+
// Position below
|
|
1013
|
+
left = buttonRect.left + (buttonRect.width / 2) - (tooltipWidth / 2);
|
|
1014
|
+
top = buttonRect.bottom + spacing;
|
|
1015
|
+
arrowSide = 'top';
|
|
1016
|
+
} else if (!inTopHalf && spaceAbove >= tooltipHeight + spacing + viewportPadding) {
|
|
1017
|
+
// Position above
|
|
1018
|
+
left = buttonRect.left + (buttonRect.width / 2) - (tooltipWidth / 2);
|
|
1019
|
+
top = buttonRect.top - tooltipHeight - spacing;
|
|
1020
|
+
arrowSide = 'bottom';
|
|
1021
|
+
} else if (spaceBelow > spaceAbove) {
|
|
1022
|
+
// Not enough vertical space either, prefer below
|
|
1023
|
+
left = buttonRect.left + (buttonRect.width / 2) - (tooltipWidth / 2);
|
|
1024
|
+
top = buttonRect.bottom + spacing;
|
|
1025
|
+
arrowSide = 'top';
|
|
1026
|
+
} else {
|
|
1027
|
+
// Prefer above
|
|
1028
|
+
left = buttonRect.left + (buttonRect.width / 2) - (tooltipWidth / 2);
|
|
1029
|
+
top = buttonRect.top - tooltipHeight - spacing;
|
|
1030
|
+
arrowSide = 'bottom';
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Keep horizontally centered within viewport
|
|
1034
|
+
if (left < viewportPadding) {
|
|
1035
|
+
left = viewportPadding;
|
|
1036
|
+
}
|
|
1037
|
+
if (left + tooltipWidth > window.innerWidth - viewportPadding) {
|
|
1038
|
+
left = window.innerWidth - tooltipWidth - viewportPadding;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
} else {
|
|
1042
|
+
// Use horizontal positioning (original logic)
|
|
1043
|
+
// Prefer right side if there's enough space
|
|
1044
|
+
if (spaceOnRight >= tooltipWidth + spacing + viewportPadding) {
|
|
1045
|
+
// Position to the right
|
|
1046
|
+
left = buttonRect.right + spacing;
|
|
1047
|
+
arrowSide = 'left';
|
|
1048
|
+
} else if (spaceOnLeft >= tooltipWidth + spacing + viewportPadding) {
|
|
1049
|
+
// Position to the left
|
|
1050
|
+
left = buttonRect.left - tooltipWidth - spacing;
|
|
1051
|
+
arrowSide = 'right';
|
|
1052
|
+
} else {
|
|
1053
|
+
// Not enough space on either side, use the side with more space
|
|
1054
|
+
if (spaceOnRight > spaceOnLeft) {
|
|
1055
|
+
left = buttonRect.right + spacing;
|
|
1056
|
+
arrowSide = 'left';
|
|
1057
|
+
// Allow tooltip to go to edge of screen
|
|
1058
|
+
if (left + tooltipWidth > window.innerWidth - viewportPadding) {
|
|
1059
|
+
left = window.innerWidth - tooltipWidth - viewportPadding;
|
|
1060
|
+
}
|
|
1061
|
+
} else {
|
|
1062
|
+
left = buttonRect.left - tooltipWidth - spacing;
|
|
1063
|
+
arrowSide = 'right';
|
|
1064
|
+
// Allow tooltip to go to edge of screen
|
|
1065
|
+
if (left < viewportPadding) {
|
|
1066
|
+
left = viewportPadding;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Center vertically relative to button
|
|
1072
|
+
top = buttonRect.top + (buttonRect.height / 2) - (tooltipHeight / 2);
|
|
1073
|
+
|
|
1074
|
+
// Keep tooltip within viewport vertically
|
|
1075
|
+
if (top < viewportPadding) {
|
|
1076
|
+
top = viewportPadding;
|
|
1077
|
+
}
|
|
1078
|
+
if (top + tooltipHeight > window.innerHeight - viewportPadding) {
|
|
1079
|
+
top = window.innerHeight - tooltipHeight - viewportPadding;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Apply positioning
|
|
1084
|
+
tooltipElement.style.left = left + 'px';
|
|
1085
|
+
tooltipElement.style.top = top + 'px';
|
|
1086
|
+
|
|
1087
|
+
// Update arrow direction
|
|
1088
|
+
const arrow = tooltipElement.querySelector('.tooltip-arrow');
|
|
1089
|
+
if (arrow) {
|
|
1090
|
+
arrow.className = 'tooltip-arrow ' + arrowSide;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function hideTooltip(event, fieldName) {
|
|
1096
|
+
const tooltipId = 'tooltip-' + fieldName;
|
|
1097
|
+
let tooltipElement = document.getElementById(tooltipId);
|
|
1098
|
+
|
|
1099
|
+
if (tooltipElement) {
|
|
1100
|
+
tooltipElement.classList.remove('active');
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Close tooltip on ESC key
|
|
1105
|
+
document.addEventListener('keydown', function(e) {
|
|
1106
|
+
if (e.key === 'Escape') {
|
|
1107
|
+
document.querySelectorAll('.tooltip-content').forEach(tip => {
|
|
1108
|
+
tip.classList.remove('active');
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
// Notify server if window is closed without starting authentication
|
|
1114
|
+
window.addEventListener('beforeunload', function(e) {
|
|
1115
|
+
// Only send cancel if user never started the authentication process
|
|
1116
|
+
// (If they started auth, they either completed it or clicked cancel explicitly)
|
|
1117
|
+
if (!authenticationStarted) {
|
|
1118
|
+
// Use sendBeacon for reliable delivery even as page unloads
|
|
1119
|
+
navigator.sendBeacon('/cancel', '');
|
|
1120
|
+
}
|
|
1121
|
+
});`;
|
|
1122
|
+
|
|
1123
|
+
const SAFARI_WARNING_HTML = `<!DOCTYPE html>
|
|
1124
|
+
<html lang="en">
|
|
1125
|
+
<head>
|
|
1126
|
+
<meta charset="UTF-8">
|
|
1127
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1128
|
+
<title>Safari Not Supported - eGain MCP</title>
|
|
1129
|
+
<style>
|
|
1130
|
+
* {
|
|
1131
|
+
margin: 0;
|
|
1132
|
+
padding: 0;
|
|
1133
|
+
box-sizing: border-box;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
body {
|
|
1137
|
+
font-family: "Open Sans", "Segoe UI", "SegoeUI", "Helvetica Neue", Helvetica, Arial, sans-serif !important;
|
|
1138
|
+
background: #fef1fd;
|
|
1139
|
+
min-height: 100vh;
|
|
1140
|
+
display: flex;
|
|
1141
|
+
justify-content: center;
|
|
1142
|
+
align-items: center;
|
|
1143
|
+
padding: 20px;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
.container {
|
|
1147
|
+
background: white;
|
|
1148
|
+
border-radius: 16px;
|
|
1149
|
+
box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.12);
|
|
1150
|
+
max-width: 500px;
|
|
1151
|
+
width: 100%;
|
|
1152
|
+
padding: 32px;
|
|
1153
|
+
text-align: center;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.warning-icon {
|
|
1157
|
+
font-size: 64px;
|
|
1158
|
+
margin-bottom: 20px;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
h1 {
|
|
1162
|
+
color: #e74c3c;
|
|
1163
|
+
font-size: 28px;
|
|
1164
|
+
margin-bottom: 30px;
|
|
1165
|
+
font-weight: 600;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
.reason {
|
|
1169
|
+
background: #fff0f6;
|
|
1170
|
+
border-left: 4px solid #d946a6;
|
|
1171
|
+
padding: 20px;
|
|
1172
|
+
text-align: left;
|
|
1173
|
+
border-radius: 4px;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.reason strong {
|
|
1177
|
+
color: #a21361;
|
|
1178
|
+
display: block;
|
|
1179
|
+
margin-bottom: 12px;
|
|
1180
|
+
font-size: 16px;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
.reason p {
|
|
1184
|
+
color: #a21361;
|
|
1185
|
+
font-size: 14px;
|
|
1186
|
+
line-height: 1.6;
|
|
1187
|
+
}
|
|
1188
|
+
</style>
|
|
1189
|
+
</head>
|
|
1190
|
+
<body>
|
|
1191
|
+
<div class="container">
|
|
1192
|
+
<div class="warning-icon">⚠️</div>
|
|
1193
|
+
<h1>Safari Not Supported</h1>
|
|
1194
|
+
|
|
1195
|
+
<div class="reason">
|
|
1196
|
+
<strong>Why?</strong>
|
|
1197
|
+
<p>
|
|
1198
|
+
Safari doesn't support private browsing mode via command line, which is required
|
|
1199
|
+
to protect your OAuth credentials from being cached or leaked. We prioritize your
|
|
1200
|
+
security over convenience.
|
|
1201
|
+
</p>
|
|
1202
|
+
</div>
|
|
1203
|
+
</div>
|
|
1204
|
+
</body>
|
|
1205
|
+
</html>`;
|
|
1206
|
+
|
|
72
1207
|
export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
73
1208
|
private token: string | null = null;
|
|
74
1209
|
private authConfig: AuthConfig;
|
|
@@ -477,6 +1612,12 @@ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
|
477
1612
|
* Generate Safari warning page (Safari doesn't support private browsing via CLI)
|
|
478
1613
|
*/
|
|
479
1614
|
private getSafariWarningPage(): string {
|
|
1615
|
+
// Use embedded content first (works in npm package)
|
|
1616
|
+
if (SAFARI_WARNING_HTML) {
|
|
1617
|
+
return SAFARI_WARNING_HTML;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
// Fallback to file reading for development
|
|
480
1621
|
try {
|
|
481
1622
|
const projectRoot = getProjectRoot();
|
|
482
1623
|
const htmlPath = path.join(projectRoot, 'src', 'hooks', 'auth-pages', 'safari-warning.html');
|
|
@@ -492,6 +1633,12 @@ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
|
492
1633
|
* Load HTML page for browser-based configuration
|
|
493
1634
|
*/
|
|
494
1635
|
private getConfigPage(): string {
|
|
1636
|
+
// Use embedded content first (works in npm package)
|
|
1637
|
+
if (CONFIG_PAGE_HTML) {
|
|
1638
|
+
return CONFIG_PAGE_HTML;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// Fallback to file reading for development
|
|
495
1642
|
try {
|
|
496
1643
|
const projectRoot = getProjectRoot();
|
|
497
1644
|
const htmlPath = path.join(projectRoot, 'src', 'hooks', 'auth-pages', 'config-page.html');
|
|
@@ -507,6 +1654,12 @@ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
|
507
1654
|
* Serve JavaScript for config page
|
|
508
1655
|
*/
|
|
509
1656
|
private getConfigPageJS(): string {
|
|
1657
|
+
// Use embedded content first (works in npm package)
|
|
1658
|
+
if (CONFIG_PAGE_JS) {
|
|
1659
|
+
return CONFIG_PAGE_JS;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// Fallback to file reading for development
|
|
510
1663
|
try {
|
|
511
1664
|
const projectRoot = getProjectRoot();
|
|
512
1665
|
const jsPath = path.join(projectRoot, 'src', 'hooks', 'auth-pages', 'config-page.js');
|