@vizamodo/modo-dispatcher 1.1.77

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 (109) hide show
  1. package/README.md +43 -0
  2. package/dist/artifacts/builders.d.ts +32 -0
  3. package/dist/artifacts/builders.js +72 -0
  4. package/dist/artifacts/downloader.d.ts +28 -0
  5. package/dist/artifacts/downloader.js +102 -0
  6. package/dist/artifacts/normalizer.d.ts +22 -0
  7. package/dist/artifacts/normalizer.js +65 -0
  8. package/dist/artifacts/types.d.ts +94 -0
  9. package/dist/artifacts/types.js +1 -0
  10. package/dist/auth/identity/extract-identity.d.ts +8 -0
  11. package/dist/auth/identity/extract-identity.js +15 -0
  12. package/dist/auth/identity/normalize-teams.d.ts +1 -0
  13. package/dist/auth/identity/normalize-teams.js +5 -0
  14. package/dist/auth/identity/validate-claims.d.ts +2 -0
  15. package/dist/auth/identity/validate-claims.js +5 -0
  16. package/dist/auth/jwt/parse.d.ts +2 -0
  17. package/dist/auth/jwt/parse.js +16 -0
  18. package/dist/auth/jwt/verify.d.ts +1 -0
  19. package/dist/auth/jwt/verify.js +9 -0
  20. package/dist/auth/oauth/auth-error.d.ts +12 -0
  21. package/dist/auth/oauth/auth-error.js +47 -0
  22. package/dist/auth/oauth/auth-session-store.d.ts +30 -0
  23. package/dist/auth/oauth/auth-session-store.js +46 -0
  24. package/dist/auth/oauth/callback-server.d.ts +21 -0
  25. package/dist/auth/oauth/callback-server.js +319 -0
  26. package/dist/auth/oauth/github.d.ts +14 -0
  27. package/dist/auth/oauth/github.js +100 -0
  28. package/dist/auth/oauth/sse.d.ts +11 -0
  29. package/dist/auth/oauth/sse.js +67 -0
  30. package/dist/auth/oauth/templates/failed.html +629 -0
  31. package/dist/auth/oauth/templates/pending.html +620 -0
  32. package/dist/auth/oauth/templates/success.html +577 -0
  33. package/dist/auth/session/access-token.d.ts +15 -0
  34. package/dist/auth/session/access-token.js +64 -0
  35. package/dist/auth/session/login.d.ts +18 -0
  36. package/dist/auth/session/login.js +144 -0
  37. package/dist/auth/session/logout.d.ts +3 -0
  38. package/dist/auth/session/logout.js +21 -0
  39. package/dist/auth/session/refresh-token.d.ts +8 -0
  40. package/dist/auth/session/refresh-token.js +16 -0
  41. package/dist/auth/session/refresh.d.ts +2 -0
  42. package/dist/auth/session/refresh.js +61 -0
  43. package/dist/auth/session/rotate.d.ts +4 -0
  44. package/dist/auth/session/rotate.js +4 -0
  45. package/dist/auth/session/session-manager.d.ts +16 -0
  46. package/dist/auth/session/session-manager.js +54 -0
  47. package/dist/auth/session/token-state.d.ts +20 -0
  48. package/dist/auth/session/token-state.js +55 -0
  49. package/dist/auth/session/types.d.ts +35 -0
  50. package/dist/auth/session/types.js +1 -0
  51. package/dist/auth/storage/keychain.d.ts +16 -0
  52. package/dist/auth/storage/keychain.js +107 -0
  53. package/dist/auth/storage/memory.d.ts +10 -0
  54. package/dist/auth/storage/memory.js +15 -0
  55. package/dist/auth/storage/types.d.ts +5 -0
  56. package/dist/auth/storage/types.js +1 -0
  57. package/dist/config/defaults.d.ts +94 -0
  58. package/dist/config/defaults.js +116 -0
  59. package/dist/core/auth-bootstrap.d.ts +15 -0
  60. package/dist/core/auth-bootstrap.js +33 -0
  61. package/dist/core/bootstrap.d.ts +73 -0
  62. package/dist/core/bootstrap.js +248 -0
  63. package/dist/core/dispatcher.d.ts +2 -0
  64. package/dist/core/dispatcher.js +28 -0
  65. package/dist/core/ensure-bootstrap.d.ts +3 -0
  66. package/dist/core/ensure-bootstrap.js +25 -0
  67. package/dist/core/errors.d.ts +69 -0
  68. package/dist/core/errors.js +121 -0
  69. package/dist/core/runtime-orchestrator.d.ts +17 -0
  70. package/dist/core/runtime-orchestrator.js +47 -0
  71. package/dist/core/runtime.d.ts +16 -0
  72. package/dist/core/runtime.js +52 -0
  73. package/dist/core/validation.d.ts +2 -0
  74. package/dist/core/validation.js +21 -0
  75. package/dist/crypto/encrypt.d.ts +12 -0
  76. package/dist/crypto/encrypt.js +80 -0
  77. package/dist/flows/email-otp-flow.d.ts +7 -0
  78. package/dist/flows/email-otp-flow.js +249 -0
  79. package/dist/gateway/auth-header.d.ts +2 -0
  80. package/dist/gateway/auth-header.js +21 -0
  81. package/dist/gateway/client.d.ts +7 -0
  82. package/dist/gateway/client.js +24 -0
  83. package/dist/gateway/dispatch-with-bootstrap-retry.d.ts +25 -0
  84. package/dist/gateway/dispatch-with-bootstrap-retry.js +157 -0
  85. package/dist/gateway/dispatch.d.ts +23 -0
  86. package/dist/gateway/dispatch.js +110 -0
  87. package/dist/gateway/session-waiter.d.ts +11 -0
  88. package/dist/gateway/session-waiter.js +126 -0
  89. package/dist/gateway/session.d.ts +21 -0
  90. package/dist/gateway/session.js +74 -0
  91. package/dist/gateway/types.d.ts +113 -0
  92. package/dist/gateway/types.js +19 -0
  93. package/dist/index.d.ts +22 -0
  94. package/dist/index.js +21 -0
  95. package/dist/runtime/runtime-context.d.ts +6 -0
  96. package/dist/runtime/runtime-context.js +1 -0
  97. package/dist/types/dispatcher.d.ts +275 -0
  98. package/dist/types/dispatcher.js +1 -0
  99. package/dist/types/payload.d.ts +5 -0
  100. package/dist/types/payload.js +2 -0
  101. package/dist/types/runtime-env.d.ts +2 -0
  102. package/dist/types/runtime-env.js +5 -0
  103. package/dist/ui/banner.d.ts +4 -0
  104. package/dist/ui/banner.js +20 -0
  105. package/dist/utils/request-dedup.d.ts +75 -0
  106. package/dist/utils/request-dedup.js +102 -0
  107. package/dist/utils/type-guards.d.ts +173 -0
  108. package/dist/utils/type-guards.js +232 -0
  109. package/package.json +43 -0
@@ -0,0 +1,577 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Viza Development Platform — Authenticated</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap"
9
+ rel="stylesheet" />
10
+ <style>
11
+ :root {
12
+ --bg: #0d1117;
13
+ --surface: #161b22;
14
+ --surface2: #1f242d;
15
+ --border: rgba(255, 255, 255, 0.08);
16
+ --border-accent: rgba(34, 197, 94, 0.18);
17
+ --text-primary: #e6edf3;
18
+ --text-secondary: #9aa4b2;
19
+ --text-muted: #6b7280;
20
+ --accent: #22c55e;
21
+ --accent-dim: rgba(34, 197, 94, 0.06);
22
+ --accent-glow: rgba(34, 197, 94, 0.05);
23
+ --warning: #f59e0b;
24
+ --mono: 'JetBrains Mono', monospace;
25
+ --sans: 'Inter', sans-serif;
26
+ }
27
+
28
+ @media (prefers-color-scheme: light) {
29
+ :root {
30
+ --bg: #eef2f6;
31
+ --surface: #ffffff;
32
+ --surface2: #f3f5f8;
33
+ --border: rgba(0, 0, 0, 0.08);
34
+ --border-accent: rgba(22, 163, 74, 0.16);
35
+ --text-primary: #0f1117;
36
+ --text-secondary: #5a6476;
37
+ --text-muted: #a0aab8;
38
+ --accent: #16a34a;
39
+ --accent-dim: rgba(22, 163, 74, 0.05);
40
+ --accent-glow: rgba(22, 163, 74, 0.04);
41
+ --warning: #d97706;
42
+ }
43
+ }
44
+
45
+ *,
46
+ *::before,
47
+ *::after {
48
+ box-sizing: border-box;
49
+ margin: 0;
50
+ padding: 0;
51
+ }
52
+
53
+ html,
54
+ body {
55
+ height: 100%;
56
+ background: var(--bg);
57
+ color: var(--text-primary);
58
+ font-family: var(--sans);
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ padding: 24px;
63
+ }
64
+
65
+ /* Subtle background grid */
66
+ body::before {
67
+ content: '';
68
+ position: fixed;
69
+ inset: 0;
70
+ background-image:
71
+ linear-gradient(var(--border) 1px, transparent 1px),
72
+ linear-gradient(90deg, var(--border) 1px, transparent 1px);
73
+ background-size: 72px 72px;
74
+ pointer-events: none;
75
+ z-index: 0;
76
+ opacity: 0.28;
77
+ }
78
+
79
+ body::after {
80
+ content: '';
81
+ position: fixed;
82
+ inset: 0;
83
+ background: radial-gradient(ellipse 55% 45% at 50% 50%, var(--accent-glow), transparent 75%);
84
+ pointer-events: none;
85
+ z-index: 0;
86
+ }
87
+
88
+ .card {
89
+ position: relative;
90
+ z-index: 1;
91
+ width: 100%;
92
+ max-width: 620px;
93
+ background: var(--surface);
94
+ border: 1px solid var(--border);
95
+ border-radius: 20px;
96
+ padding: 40px 40px 36px;
97
+ box-shadow:
98
+ 0 0 0 1px var(--border),
99
+ 0 18px 48px rgba(0, 0, 0, 0.28),
100
+ 0 0 0 1px rgba(255,255,255,0.02);
101
+ animation: fadeUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
102
+ }
103
+
104
+ @keyframes fadeUp {
105
+ from {
106
+ opacity: 0;
107
+ transform: translateY(20px) scale(0.98);
108
+ }
109
+
110
+ to {
111
+ opacity: 1;
112
+ transform: translateY(0) scale(1);
113
+ }
114
+ }
115
+
116
+ /* Header */
117
+ .header {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 12px;
121
+ margin-bottom: 18px;
122
+ padding-bottom: 14px;
123
+ border-bottom: 1px solid var(--border);
124
+ }
125
+
126
+ .logo {
127
+ width: 36px;
128
+ height: 36px;
129
+ background: var(--accent);
130
+ border-radius: 10px;
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ flex-shrink: 0;
135
+ }
136
+
137
+ .logo svg {
138
+ width: 20px;
139
+ height: 20px;
140
+ }
141
+
142
+ .brand {
143
+ display: flex;
144
+ flex-direction: column;
145
+ gap: 1px;
146
+ }
147
+
148
+ .brand-name {
149
+ font-size: 14px;
150
+ font-weight: 700;
151
+ letter-spacing: 0.02em;
152
+ text-transform: uppercase;
153
+ color: var(--text-primary);
154
+ }
155
+
156
+ .brand-sub {
157
+ font-family: var(--mono);
158
+ font-size: 10px;
159
+ color: var(--text-muted);
160
+ letter-spacing: 0.04em;
161
+ }
162
+
163
+ /* Success badge */
164
+ .status-badge {
165
+ margin-left: auto;
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 6px;
169
+ background: var(--accent-dim);
170
+ border: 1px solid var(--border-accent);
171
+ border-radius: 100px;
172
+ padding: 5px 12px 5px 8px;
173
+ font-family: var(--mono);
174
+ font-size: 10px;
175
+ font-weight: 500;
176
+ color: #4ade80;
177
+ letter-spacing: 0.03em;
178
+ animation: fadeIn 0.4s 0.4s both;
179
+ }
180
+
181
+ @keyframes fadeIn {
182
+ from {
183
+ opacity: 0;
184
+ }
185
+
186
+ to {
187
+ opacity: 1;
188
+ }
189
+ }
190
+
191
+ .dot {
192
+ width: 7px;
193
+ height: 7px;
194
+ border-radius: 50%;
195
+ background: var(--accent);
196
+ animation: pulse 2s ease-in-out infinite;
197
+ }
198
+
199
+ @keyframes pulse {
200
+
201
+ 0%,
202
+ 100% {
203
+ opacity: 1;
204
+ transform: scale(1);
205
+ }
206
+
207
+ 50% {
208
+ opacity: 0.5;
209
+ transform: scale(0.8);
210
+ }
211
+ }
212
+
213
+ /* Title area */
214
+ .title-area {
215
+ display: flex;
216
+ align-items: center;
217
+ gap: 12px;
218
+ margin-bottom: 12px;
219
+ animation: fadeUp 0.5s 0.15s cubic-bezier(0.16, 1, 0.3, 1) both;
220
+ padding-bottom: 10px;
221
+ }
222
+
223
+ .title-content {
224
+ display: flex;
225
+ flex-direction: column;
226
+ justify-content: center;
227
+ min-width: 0;
228
+ }
229
+
230
+ .check-icon {
231
+ width: 72px;
232
+ height: 72px;
233
+ border-radius: 18px;
234
+ background: var(--accent-dim);
235
+ border: 1px solid var(--border-accent);
236
+ display: flex;
237
+ align-items: center;
238
+ justify-content: center;
239
+ margin-bottom: 0;
240
+ flex-shrink: 0;
241
+ }
242
+
243
+ .check-icon svg {
244
+ width: 34px;
245
+ height: 34px;
246
+ color: var(--accent);
247
+ }
248
+
249
+ h1 {
250
+ font-size: 28px;
251
+ font-weight: 700;
252
+ letter-spacing: -0.03em;
253
+ color: var(--text-primary);
254
+ line-height: 1.05;
255
+ margin-bottom: 6px;
256
+ }
257
+
258
+ .subtitle {
259
+ font-size: 13px;
260
+ font-weight: 400;
261
+ color: var(--text-secondary);
262
+ line-height: 1.75;
263
+ max-width: 520px;
264
+ }
265
+
266
+ /* Info rows */
267
+ .info-block {
268
+ background: var(--surface2);
269
+ border: 1px solid var(--border);
270
+ border-radius: 12px;
271
+ overflow: hidden;
272
+ margin-bottom: 20px;
273
+ animation: fadeUp 0.5s 0.25s cubic-bezier(0.16, 1, 0.3, 1) both;
274
+ backdrop-filter: blur(8px);
275
+ }
276
+
277
+ .info-row {
278
+ display: flex;
279
+ align-items: center;
280
+ justify-content: space-between;
281
+ padding: 16px 18px;
282
+ gap: 12px;
283
+ }
284
+
285
+ .info-row+.info-row {
286
+ border-top: 1px solid var(--border);
287
+ }
288
+
289
+ .info-label {
290
+ font-family: var(--mono);
291
+ font-size: 10px;
292
+ color: var(--text-muted);
293
+ text-transform: uppercase;
294
+ letter-spacing: 0.07em;
295
+ flex-shrink: 0;
296
+ }
297
+
298
+ .info-value {
299
+ font-family: var(--mono);
300
+ font-size: 13px;
301
+ color: var(--text-secondary);
302
+ text-align: right;
303
+ word-break: break-word;
304
+ overflow-wrap: break-word;
305
+ white-space: normal;
306
+ }
307
+
308
+ .info-value.highlight {
309
+ color: var(--text-primary);
310
+ font-weight: 500;
311
+ }
312
+
313
+ .avatar-row {
314
+ display: flex;
315
+ align-items: center;
316
+ gap: 8px;
317
+ }
318
+
319
+ .avatar {
320
+ width: 22px;
321
+ height: 22px;
322
+ border-radius: 50%;
323
+ background: var(--accent-dim);
324
+ border: 1px solid var(--border-accent);
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: center;
328
+ overflow: hidden;
329
+ flex-shrink: 0;
330
+ }
331
+
332
+ .avatar img {
333
+ width: 100%;
334
+ height: 100%;
335
+ object-fit: cover;
336
+ }
337
+
338
+ .avatar-placeholder {
339
+ font-size: 11px;
340
+ font-weight: 700;
341
+ color: var(--accent);
342
+ font-family: var(--sans);
343
+ }
344
+
345
+ /* Scope tags */
346
+ .scopes {
347
+ display: flex;
348
+ flex-wrap: wrap;
349
+ gap: 6px;
350
+ justify-content: flex-end;
351
+ }
352
+
353
+ .scope-tag {
354
+ font-family: var(--mono);
355
+ font-size: 10px;
356
+ padding: 3px 8px;
357
+ border-radius: 4px;
358
+ background: rgba(34, 197, 94, 0.05);
359
+ border: 1px solid var(--border-accent);
360
+ color: var(--accent);
361
+ letter-spacing: 0.03em;
362
+ }
363
+
364
+ /* Close hint */
365
+ .close-hint {
366
+ display: flex;
367
+ align-items: center;
368
+ gap: 10px;
369
+ padding: 14px 16px;
370
+ background: var(--surface2);
371
+ border: 1px solid var(--border);
372
+ border-radius: 12px;
373
+ animation: fadeUp 0.5s 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
374
+ }
375
+
376
+ .close-hint svg {
377
+ color: var(--text-muted);
378
+ flex-shrink: 0;
379
+ width: 16px;
380
+ height: 16px;
381
+ }
382
+
383
+ .close-hint-text {
384
+ font-family: var(--mono);
385
+ font-size: 12px;
386
+ color: var(--text-secondary);
387
+ line-height: 1.4;
388
+ }
389
+
390
+ .close-hint-text strong {
391
+ color: var(--text-primary);
392
+ font-weight: 500;
393
+ }
394
+
395
+ /* Footer */
396
+ .footer {
397
+ margin-top: 28px;
398
+ display: flex;
399
+ align-items: center;
400
+ justify-content: space-between;
401
+ animation: fadeIn 0.4s 0.5s both;
402
+ }
403
+
404
+ .timestamp {
405
+ font-family: var(--mono);
406
+ font-size: 11px;
407
+ color: var(--text-muted);
408
+ }
409
+
410
+ .security-badge {
411
+ display: flex;
412
+ align-items: center;
413
+ gap: 5px;
414
+ font-family: var(--mono);
415
+ font-size: 10px;
416
+ color: var(--text-muted);
417
+ letter-spacing: 0.04em;
418
+ }
419
+
420
+ .security-badge svg {
421
+ width: 12px;
422
+ height: 12px;
423
+ }
424
+
425
+ @media (max-width: 720px) {
426
+ .title-area {
427
+ align-items: flex-start;
428
+ gap: 16px;
429
+ }
430
+
431
+ .check-icon {
432
+ width: 58px;
433
+ height: 58px;
434
+ }
435
+
436
+ .check-icon svg {
437
+ width: 28px;
438
+ height: 28px;
439
+ }
440
+
441
+ h1 {
442
+ font-size: 32px;
443
+ }
444
+
445
+ .subtitle {
446
+ font-size: 14px;
447
+ }
448
+ }
449
+ </style>
450
+ </head>
451
+
452
+ <body>
453
+ <div class="card">
454
+
455
+ <!-- Header -->
456
+ <div class="header">
457
+ <div class="logo">
458
+ <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
459
+ <path d="M4 10L8.5 15L16 5" stroke="#0a0c10" stroke-width="2.5" stroke-linecap="round"
460
+ stroke-linejoin="round" />
461
+ </svg>
462
+ </div>
463
+ <div class="brand">
464
+ <span class="brand-name">Viza Development Platform</span>
465
+ <span class="brand-sub">github.com · oauth2</span>
466
+ </div>
467
+ <div class="status-badge">
468
+ <span class="dot"></span>
469
+ AUTHORIZED
470
+ </div>
471
+ </div>
472
+
473
+ <!-- Title -->
474
+ <div class="title-area">
475
+ <div class="check-icon">
476
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
477
+ stroke-linecap="round" stroke-linejoin="round">
478
+ <path d="M20 6L9 17l-5-5" />
479
+ </svg>
480
+ </div>
481
+
482
+ <div class="title-content">
483
+ <h1>Authentication Successful</h1>
484
+ <p class="subtitle">
485
+ Your GitHub account has been connected.
486
+ You may now return to the application.
487
+ </p>
488
+ </div>
489
+ </div>
490
+
491
+ <!-- Info block -->
492
+ <div class="info-block">
493
+ <div class="info-row">
494
+ <span class="info-label">Account</span>
495
+ <div class="avatar-row">
496
+ <div class="avatar">
497
+ <!-- Replace src with actual avatar URL from OAuth response -->
498
+ <img id="avatar-img" src="__AVATAR_URL__"
499
+ onerror="this.style.display='none'; document.getElementById('avatar-placeholder').style.display='flex';" />
500
+ <span id="avatar-placeholder" class="avatar-placeholder" style="display:flex">
501
+ <!-- JS fills initials -->
502
+ </span>
503
+ </div>
504
+ <span class="info-value highlight" id="username">__USERNAME__</span>
505
+ </div>
506
+ </div>
507
+ <div class="info-row">
508
+ <span class="info-label">Email</span>
509
+ <span class="info-value" id="email">__EMAIL__</span>
510
+ </div>
511
+ <div class="info-row">
512
+ <span class="info-label">Teams</span>
513
+ <span class="info-value highlight" id="teams">__TEAMS__</span>
514
+ </div>
515
+ <div class="info-row">
516
+ <span class="info-label">Provider</span>
517
+ <span class="info-value highlight">GitHub</span>
518
+ </div>
519
+ <div class="info-row">
520
+ <span class="info-label">Scopes</span>
521
+ <div class="scopes">
522
+ <span class="scope-tag">read:user</span>
523
+ <span class="scope-tag">user:email</span>
524
+ <span class="scope-tag">repo</span>
525
+ </div>
526
+ </div>
527
+ </div>
528
+
529
+ <!-- Close hint -->
530
+ <div class="close-hint">
531
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
532
+ stroke-linejoin="round">
533
+ <rect x="3" y="3" width="18" height="18" rx="3" />
534
+ <path d="M9 3v18M15 3v18M3 9h18M3 15h18" opacity="0.4" />
535
+ <path d="M9 12h6" />
536
+ </svg>
537
+ <span class="close-hint-text">
538
+ <strong>You can close this window.</strong> The CLI is now authenticated and ready to use.
539
+ </span>
540
+ </div>
541
+
542
+ <!-- Footer -->
543
+ <div class="footer">
544
+ <span class="timestamp" id="timestamp">—</span>
545
+ <div class="security-badge">
546
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
547
+ stroke-linecap="round" stroke-linejoin="round">
548
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
549
+ </svg>
550
+ OAuth 2.0 · TLS 1.3
551
+ </div>
552
+ </div>
553
+
554
+ </div>
555
+
556
+ <script>
557
+ const usernameEl = document.getElementById("username");
558
+ const avatarPlaceholderEl = document.getElementById(
559
+ "avatar-placeholder"
560
+ );
561
+ const timestampEl = document.getElementById("timestamp");
562
+
563
+ if (timestampEl) {
564
+ timestampEl.textContent = new Date().toLocaleString();
565
+ }
566
+
567
+ if (usernameEl && avatarPlaceholderEl) {
568
+ const username = usernameEl.textContent?.trim() || "U";
569
+
570
+ avatarPlaceholderEl.textContent = username
571
+ .slice(0, 2)
572
+ .toUpperCase();
573
+ }
574
+ </script>
575
+ </body>
576
+
577
+ </html>
@@ -0,0 +1,15 @@
1
+ import { MemoryTokenStore } from "../storage/memory.js";
2
+ import { SecureStore } from "../storage/types.js";
3
+ import { TokenClaims } from "./types.js";
4
+ export declare class AccessTokenService {
5
+ private readonly memoryStore;
6
+ private readonly secureStore?;
7
+ private hydrateInFlight;
8
+ constructor(memoryStore: MemoryTokenStore, secureStore?: SecureStore | undefined);
9
+ set(token: string): void;
10
+ get(): string | undefined;
11
+ clear(): void;
12
+ hydrate(): Promise<void>;
13
+ isValid(): boolean;
14
+ getClaims(): TokenClaims;
15
+ }
@@ -0,0 +1,64 @@
1
+ import { parseJwtClaims } from "../jwt/parse.js";
2
+ import { isJwtExpired } from "../jwt/verify.js";
3
+ const ACCESS_TOKEN_KEY = "viza.access-token";
4
+ export class AccessTokenService {
5
+ memoryStore;
6
+ secureStore;
7
+ hydrateInFlight = null;
8
+ constructor(memoryStore, secureStore) {
9
+ this.memoryStore = memoryStore;
10
+ this.secureStore = secureStore;
11
+ }
12
+ set(token) {
13
+ const claims = parseJwtClaims(token);
14
+ const exp = typeof claims.exp === "number" ? claims.exp * 1000 : undefined;
15
+ this.memoryStore.setAccessToken(token, exp);
16
+ if (this.secureStore) {
17
+ void this.secureStore.set(ACCESS_TOKEN_KEY, token);
18
+ }
19
+ }
20
+ get() {
21
+ return this.memoryStore.getAccessToken().token;
22
+ }
23
+ clear() {
24
+ this.memoryStore.clear();
25
+ if (this.secureStore) {
26
+ void this.secureStore.remove(ACCESS_TOKEN_KEY);
27
+ }
28
+ }
29
+ async hydrate() {
30
+ if (!this.secureStore || this.get()) {
31
+ return;
32
+ }
33
+ if (!this.hydrateInFlight) {
34
+ this.hydrateInFlight = (async () => {
35
+ const token = await this.secureStore?.get(ACCESS_TOKEN_KEY);
36
+ if (!token) {
37
+ return;
38
+ }
39
+ const claims = parseJwtClaims(token);
40
+ if (typeof claims.exp !== "number") {
41
+ await this.secureStore?.remove(ACCESS_TOKEN_KEY);
42
+ return;
43
+ }
44
+ this.memoryStore.setAccessToken(token, claims.exp * 1000);
45
+ })();
46
+ }
47
+ try {
48
+ await this.hydrateInFlight;
49
+ }
50
+ finally {
51
+ this.hydrateInFlight = null;
52
+ }
53
+ }
54
+ isValid() {
55
+ const token = this.get();
56
+ if (!token)
57
+ return false;
58
+ return !isJwtExpired(token, 60);
59
+ }
60
+ getClaims() {
61
+ const token = this.get();
62
+ return token ? parseJwtClaims(token) : {};
63
+ }
64
+ }
@@ -0,0 +1,18 @@
1
+ import { AccessTokenService } from "./access-token.js";
2
+ import { RefreshTokenService } from "./refresh-token.js";
3
+ export interface LoginOptions {
4
+ loginEndpoint?: string;
5
+ exchangeEndpoint?: string;
6
+ }
7
+ export interface LoginDeps {
8
+ accessTokenService: AccessTokenService;
9
+ refreshTokenService: RefreshTokenService;
10
+ }
11
+ /**
12
+ * Result of a completed interactive login.
13
+ * Includes tokens and optional bootstrap configs from the exchange response.
14
+ */
15
+ export interface LoginResult {
16
+ bootstrapReceived: boolean;
17
+ }
18
+ export declare function performInteractiveLogin(deps: LoginDeps, options: LoginOptions): Promise<LoginResult>;