@sparkvault/sdk 1.21.11 → 1.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/identity/container.d.ts +7 -0
- package/dist/identity/index.d.ts +4 -2
- package/dist/identity/inline-container.d.ts +5 -0
- package/dist/identity/modal.d.ts +8 -0
- package/dist/identity/renderer.d.ts +12 -0
- package/dist/identity/types.d.ts +4 -0
- package/dist/identity/views/app-not-activated.d.ts +21 -0
- package/dist/identity/views/index.d.ts +2 -0
- package/dist/identity/views/service-unavailable.d.ts +21 -0
- package/dist/index.d.ts +5 -7
- package/dist/sparkvault.cjs.js +626 -18
- package/dist/sparkvault.cjs.js.map +1 -1
- package/dist/sparkvault.esm.js +626 -18
- package/dist/sparkvault.esm.js.map +1 -1
- package/dist/sparkvault.js +1 -1
- package/dist/sparkvault.js.map +1 -1
- package/dist/vaults/index.d.ts +1 -1
- package/dist/vaults/upload/index.d.ts +3 -7
- package/dist/vaults/upload/types.d.ts +0 -4
- package/package.json +1 -1
package/dist/sparkvault.esm.js
CHANGED
|
@@ -2340,6 +2340,250 @@ function getStyles(options) {
|
|
|
2340
2340
|
flex: 1;
|
|
2341
2341
|
}
|
|
2342
2342
|
|
|
2343
|
+
/* ========================================
|
|
2344
|
+
SERVICE UNAVAILABLE VIEW
|
|
2345
|
+
======================================== */
|
|
2346
|
+
|
|
2347
|
+
.sv-unavailable-view {
|
|
2348
|
+
display: flex;
|
|
2349
|
+
flex-direction: column;
|
|
2350
|
+
align-items: center;
|
|
2351
|
+
text-align: center;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
.sv-unavailable-icon-container {
|
|
2355
|
+
margin-bottom: 16px;
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
.sv-unavailable-icon {
|
|
2359
|
+
width: 48px;
|
|
2360
|
+
height: 48px;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
.sv-unavailable-content {
|
|
2364
|
+
margin-bottom: 24px;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
.sv-unavailable-title {
|
|
2368
|
+
font-size: 18px;
|
|
2369
|
+
font-weight: 600;
|
|
2370
|
+
letter-spacing: -0.02em;
|
|
2371
|
+
margin: 0 0 8px 0;
|
|
2372
|
+
color: ${tokens.textPrimary};
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
.sv-unavailable-message {
|
|
2376
|
+
font-size: 14px;
|
|
2377
|
+
line-height: 1.5;
|
|
2378
|
+
color: ${tokens.textSecondary};
|
|
2379
|
+
margin: 0;
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
.sv-unavailable-actions {
|
|
2383
|
+
display: flex;
|
|
2384
|
+
gap: 10px;
|
|
2385
|
+
width: 100%;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
.sv-unavailable-actions .sv-btn {
|
|
2389
|
+
flex: 1;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
@media (max-width: 480px) {
|
|
2393
|
+
.sv-unavailable-actions {
|
|
2394
|
+
flex-direction: column;
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
/* ========================================
|
|
2399
|
+
MINIMAL CHROME (full-dialog views)
|
|
2400
|
+
======================================== */
|
|
2401
|
+
|
|
2402
|
+
.sv-modal-minimal {
|
|
2403
|
+
position: relative;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
.sv-modal-minimal > .sv-header {
|
|
2407
|
+
display: none;
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
.sv-modal-minimal > .sv-body {
|
|
2411
|
+
padding: 36px 32px 28px;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
.sv-floating-close {
|
|
2415
|
+
position: absolute;
|
|
2416
|
+
top: 14px;
|
|
2417
|
+
right: 14px;
|
|
2418
|
+
width: 32px;
|
|
2419
|
+
height: 32px;
|
|
2420
|
+
display: flex;
|
|
2421
|
+
align-items: center;
|
|
2422
|
+
justify-content: center;
|
|
2423
|
+
background: transparent;
|
|
2424
|
+
border: none;
|
|
2425
|
+
border-radius: 8px;
|
|
2426
|
+
cursor: pointer;
|
|
2427
|
+
color: ${tokens.textMuted};
|
|
2428
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
2429
|
+
z-index: 1;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
.sv-floating-close:hover {
|
|
2433
|
+
background: ${tokens.bgHover};
|
|
2434
|
+
color: ${tokens.textPrimary};
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
.sv-floating-close:focus-visible {
|
|
2438
|
+
outline: 2px solid ${tokens.borderFocus};
|
|
2439
|
+
outline-offset: 2px;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
.sv-floating-close:active {
|
|
2443
|
+
transform: scale(0.96);
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
/* ========================================
|
|
2447
|
+
APP NOT ACTIVATED VIEW
|
|
2448
|
+
======================================== */
|
|
2449
|
+
|
|
2450
|
+
.sv-not-activated-view {
|
|
2451
|
+
display: flex;
|
|
2452
|
+
flex-direction: column;
|
|
2453
|
+
align-items: center;
|
|
2454
|
+
text-align: center;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
.sv-not-activated-eyebrow {
|
|
2458
|
+
display: inline-flex;
|
|
2459
|
+
align-items: center;
|
|
2460
|
+
gap: 8px;
|
|
2461
|
+
padding: 5px 10px;
|
|
2462
|
+
border-radius: 999px;
|
|
2463
|
+
background: ${tokens.bgHover};
|
|
2464
|
+
color: ${tokens.textSecondary};
|
|
2465
|
+
font-size: 11px;
|
|
2466
|
+
font-weight: 600;
|
|
2467
|
+
letter-spacing: 0.04em;
|
|
2468
|
+
text-transform: uppercase;
|
|
2469
|
+
margin-bottom: 20px;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
.sv-not-activated-mark {
|
|
2473
|
+
display: inline-flex;
|
|
2474
|
+
align-items: center;
|
|
2475
|
+
gap: 5px;
|
|
2476
|
+
color: ${tokens.textPrimary};
|
|
2477
|
+
font-weight: 700;
|
|
2478
|
+
letter-spacing: -0.01em;
|
|
2479
|
+
text-transform: none;
|
|
2480
|
+
font-size: 12px;
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
.sv-not-activated-eyebrow-divider {
|
|
2484
|
+
color: ${tokens.textMuted};
|
|
2485
|
+
opacity: 0.6;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
.sv-not-activated-eyebrow-label {
|
|
2489
|
+
color: ${tokens.textSecondary};
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
.sv-not-activated-badge {
|
|
2493
|
+
margin-bottom: 20px;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
.sv-not-activated-badge-svg {
|
|
2497
|
+
display: block;
|
|
2498
|
+
filter: drop-shadow(0 6px 14px rgba(245, 158, 11, 0.18));
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
.sv-not-activated-title {
|
|
2502
|
+
font-size: 22px;
|
|
2503
|
+
font-weight: 700;
|
|
2504
|
+
letter-spacing: -0.02em;
|
|
2505
|
+
line-height: 1.2;
|
|
2506
|
+
margin: 0 0 10px 0;
|
|
2507
|
+
color: ${tokens.textPrimary};
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
.sv-not-activated-message {
|
|
2511
|
+
font-size: 14px;
|
|
2512
|
+
line-height: 1.55;
|
|
2513
|
+
color: ${tokens.textSecondary};
|
|
2514
|
+
margin: 0 0 22px 0;
|
|
2515
|
+
max-width: 320px;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
.sv-not-activated-owner {
|
|
2519
|
+
width: 100%;
|
|
2520
|
+
display: flex;
|
|
2521
|
+
flex-direction: column;
|
|
2522
|
+
gap: 4px;
|
|
2523
|
+
padding: 14px 16px;
|
|
2524
|
+
border: 1px solid ${tokens.border};
|
|
2525
|
+
border-radius: 12px;
|
|
2526
|
+
background: ${tokens.bgSubtle};
|
|
2527
|
+
text-align: left;
|
|
2528
|
+
margin-bottom: 22px;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
.sv-not-activated-owner-label {
|
|
2532
|
+
font-size: 13px;
|
|
2533
|
+
font-weight: 600;
|
|
2534
|
+
color: ${tokens.textPrimary};
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
.sv-not-activated-owner-hint {
|
|
2538
|
+
font-size: 12.5px;
|
|
2539
|
+
line-height: 1.5;
|
|
2540
|
+
color: ${tokens.textSecondary};
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
.sv-not-activated-actions {
|
|
2544
|
+
display: flex;
|
|
2545
|
+
flex-direction: column;
|
|
2546
|
+
align-items: stretch;
|
|
2547
|
+
gap: 10px;
|
|
2548
|
+
width: 100%;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
.sv-not-activated-cta {
|
|
2552
|
+
display: inline-flex;
|
|
2553
|
+
align-items: center;
|
|
2554
|
+
justify-content: center;
|
|
2555
|
+
gap: 8px;
|
|
2556
|
+
width: 100%;
|
|
2557
|
+
text-decoration: none;
|
|
2558
|
+
height: 44px;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
.sv-not-activated-cta-icon {
|
|
2562
|
+
opacity: 0.85;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
.sv-not-activated-dismiss {
|
|
2566
|
+
background: transparent;
|
|
2567
|
+
border: none;
|
|
2568
|
+
cursor: pointer;
|
|
2569
|
+
padding: 8px;
|
|
2570
|
+
font-size: 13px;
|
|
2571
|
+
font-weight: 500;
|
|
2572
|
+
color: ${tokens.textMuted};
|
|
2573
|
+
transition: color 0.15s ease;
|
|
2574
|
+
align-self: center;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
.sv-not-activated-dismiss:hover {
|
|
2578
|
+
color: ${tokens.textPrimary};
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
.sv-not-activated-dismiss:focus-visible {
|
|
2582
|
+
outline: 2px solid ${tokens.borderFocus};
|
|
2583
|
+
outline-offset: 2px;
|
|
2584
|
+
border-radius: 6px;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2343
2587
|
/* ========================================
|
|
2344
2588
|
TOTP VIEW
|
|
2345
2589
|
======================================== */
|
|
@@ -3007,6 +3251,8 @@ class ModalContainer {
|
|
|
3007
3251
|
this.backdropBlur = true;
|
|
3008
3252
|
this.effectiveTheme = 'light';
|
|
3009
3253
|
this.headerElement = null;
|
|
3254
|
+
this.floatingCloseBtn = null;
|
|
3255
|
+
this.floatingCloseHandler = null;
|
|
3010
3256
|
}
|
|
3011
3257
|
/**
|
|
3012
3258
|
* Create and show the modal immediately with loading state.
|
|
@@ -3034,6 +3280,39 @@ class ModalContainer {
|
|
|
3034
3280
|
}
|
|
3035
3281
|
this.headerElement = newHeader;
|
|
3036
3282
|
}
|
|
3283
|
+
/**
|
|
3284
|
+
* Toggle minimal-chrome mode. In minimal mode the header (logo + close)
|
|
3285
|
+
* is hidden and a floating close button is positioned over the modal so
|
|
3286
|
+
* the body view can render edge-to-edge as its own full-dialog design.
|
|
3287
|
+
*/
|
|
3288
|
+
setMinimalChrome(enabled) {
|
|
3289
|
+
if (!this.elements)
|
|
3290
|
+
return;
|
|
3291
|
+
const { modal } = this.elements;
|
|
3292
|
+
if (enabled) {
|
|
3293
|
+
modal.classList.add('sv-modal-minimal');
|
|
3294
|
+
if (!this.floatingCloseBtn) {
|
|
3295
|
+
const btn = document.createElement('button');
|
|
3296
|
+
btn.className = 'sv-floating-close';
|
|
3297
|
+
btn.type = 'button';
|
|
3298
|
+
btn.setAttribute('aria-label', 'Close');
|
|
3299
|
+
btn.appendChild(createCloseIcon());
|
|
3300
|
+
this.floatingCloseHandler = () => this.handleClose();
|
|
3301
|
+
btn.addEventListener('click', this.floatingCloseHandler);
|
|
3302
|
+
this.floatingCloseBtn = btn;
|
|
3303
|
+
modal.appendChild(btn);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
else {
|
|
3307
|
+
modal.classList.remove('sv-modal-minimal');
|
|
3308
|
+
if (this.floatingCloseBtn && this.floatingCloseHandler) {
|
|
3309
|
+
this.floatingCloseBtn.removeEventListener('click', this.floatingCloseHandler);
|
|
3310
|
+
this.floatingCloseBtn.remove();
|
|
3311
|
+
this.floatingCloseBtn = null;
|
|
3312
|
+
this.floatingCloseHandler = null;
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3037
3316
|
/**
|
|
3038
3317
|
* Update the backdrop blur setting.
|
|
3039
3318
|
* Called after SDK config loads with resolved value (client override > server config > default).
|
|
@@ -3198,6 +3477,12 @@ class ModalContainer {
|
|
|
3198
3477
|
this.closeBtnClickHandler = null;
|
|
3199
3478
|
this.closeBtn = null;
|
|
3200
3479
|
}
|
|
3480
|
+
// Clean up floating close button (used in minimal-chrome mode)
|
|
3481
|
+
if (this.floatingCloseBtn && this.floatingCloseHandler) {
|
|
3482
|
+
this.floatingCloseBtn.removeEventListener('click', this.floatingCloseHandler);
|
|
3483
|
+
this.floatingCloseBtn = null;
|
|
3484
|
+
this.floatingCloseHandler = null;
|
|
3485
|
+
}
|
|
3201
3486
|
// Remove modal from DOM
|
|
3202
3487
|
if (this.elements) {
|
|
3203
3488
|
this.elements.overlay.remove();
|
|
@@ -4418,6 +4703,257 @@ class ErrorView {
|
|
|
4418
4703
|
}
|
|
4419
4704
|
}
|
|
4420
4705
|
|
|
4706
|
+
/**
|
|
4707
|
+
* Service Unavailable View
|
|
4708
|
+
*
|
|
4709
|
+
* Shown when the identity config endpoint is unreachable.
|
|
4710
|
+
* Clean, professional, branded — no error codes or technical jargon.
|
|
4711
|
+
*/
|
|
4712
|
+
function createShieldIcon() {
|
|
4713
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
4714
|
+
svg.setAttribute('viewBox', '0 0 48 48');
|
|
4715
|
+
svg.setAttribute('width', '48');
|
|
4716
|
+
svg.setAttribute('height', '48');
|
|
4717
|
+
svg.setAttribute('fill', 'none');
|
|
4718
|
+
svg.classList.add('sv-unavailable-icon');
|
|
4719
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
4720
|
+
circle.setAttribute('cx', '24');
|
|
4721
|
+
circle.setAttribute('cy', '24');
|
|
4722
|
+
circle.setAttribute('r', '22');
|
|
4723
|
+
circle.setAttribute('fill', '#EEF2FF');
|
|
4724
|
+
circle.setAttribute('stroke', '#E0E7FF');
|
|
4725
|
+
circle.setAttribute('stroke-width', '1');
|
|
4726
|
+
svg.appendChild(circle);
|
|
4727
|
+
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
4728
|
+
group.setAttribute('transform', 'translate(12, 10)');
|
|
4729
|
+
// Shield shape
|
|
4730
|
+
const shield = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
4731
|
+
shield.setAttribute('d', 'M12 2L3 6v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V6l-9-4z');
|
|
4732
|
+
shield.setAttribute('fill', '#A5B4FC');
|
|
4733
|
+
group.appendChild(shield);
|
|
4734
|
+
// Pause bars (indicates temporary)
|
|
4735
|
+
const bar1 = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
4736
|
+
bar1.setAttribute('x', '9');
|
|
4737
|
+
bar1.setAttribute('y', '9');
|
|
4738
|
+
bar1.setAttribute('width', '2.5');
|
|
4739
|
+
bar1.setAttribute('height', '8');
|
|
4740
|
+
bar1.setAttribute('rx', '1');
|
|
4741
|
+
bar1.setAttribute('fill', '#FFFFFF');
|
|
4742
|
+
group.appendChild(bar1);
|
|
4743
|
+
const bar2 = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
4744
|
+
bar2.setAttribute('x', '12.5');
|
|
4745
|
+
bar2.setAttribute('y', '9');
|
|
4746
|
+
bar2.setAttribute('width', '2.5');
|
|
4747
|
+
bar2.setAttribute('height', '8');
|
|
4748
|
+
bar2.setAttribute('rx', '1');
|
|
4749
|
+
bar2.setAttribute('fill', '#FFFFFF');
|
|
4750
|
+
group.appendChild(bar2);
|
|
4751
|
+
svg.appendChild(group);
|
|
4752
|
+
return svg;
|
|
4753
|
+
}
|
|
4754
|
+
class ServiceUnavailableView {
|
|
4755
|
+
constructor(props) {
|
|
4756
|
+
this.retryButton = null;
|
|
4757
|
+
this.closeButton = null;
|
|
4758
|
+
this.props = props;
|
|
4759
|
+
this.boundHandleRetry = () => this.props.onRetry();
|
|
4760
|
+
this.boundHandleClose = () => this.props.onClose();
|
|
4761
|
+
}
|
|
4762
|
+
render() {
|
|
4763
|
+
const container = div('sv-unavailable-view');
|
|
4764
|
+
// Icon
|
|
4765
|
+
const iconContainer = div('sv-unavailable-icon-container');
|
|
4766
|
+
iconContainer.appendChild(createShieldIcon());
|
|
4767
|
+
container.appendChild(iconContainer);
|
|
4768
|
+
// Content
|
|
4769
|
+
const content = div('sv-unavailable-content');
|
|
4770
|
+
const title = document.createElement('h3');
|
|
4771
|
+
title.className = 'sv-unavailable-title';
|
|
4772
|
+
title.textContent = 'Temporarily Unavailable';
|
|
4773
|
+
content.appendChild(title);
|
|
4774
|
+
const message = document.createElement('p');
|
|
4775
|
+
message.className = 'sv-unavailable-message';
|
|
4776
|
+
message.textContent = 'Authentication is experiencing a brief interruption. Please try again in a moment.';
|
|
4777
|
+
content.appendChild(message);
|
|
4778
|
+
container.appendChild(content);
|
|
4779
|
+
// Actions
|
|
4780
|
+
const actions = div('sv-unavailable-actions');
|
|
4781
|
+
this.retryButton = document.createElement('button');
|
|
4782
|
+
this.retryButton.className = 'sv-btn sv-btn-primary';
|
|
4783
|
+
this.retryButton.textContent = 'Try Again';
|
|
4784
|
+
this.retryButton.addEventListener('click', this.boundHandleRetry);
|
|
4785
|
+
actions.appendChild(this.retryButton);
|
|
4786
|
+
this.closeButton = document.createElement('button');
|
|
4787
|
+
this.closeButton.className = 'sv-btn sv-btn-secondary';
|
|
4788
|
+
this.closeButton.textContent = 'Close';
|
|
4789
|
+
this.closeButton.addEventListener('click', this.boundHandleClose);
|
|
4790
|
+
actions.appendChild(this.closeButton);
|
|
4791
|
+
container.appendChild(actions);
|
|
4792
|
+
return container;
|
|
4793
|
+
}
|
|
4794
|
+
destroy() {
|
|
4795
|
+
if (this.retryButton) {
|
|
4796
|
+
this.retryButton.removeEventListener('click', this.boundHandleRetry);
|
|
4797
|
+
}
|
|
4798
|
+
if (this.closeButton) {
|
|
4799
|
+
this.closeButton.removeEventListener('click', this.boundHandleClose);
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
|
|
4804
|
+
/**
|
|
4805
|
+
* App Not Activated View
|
|
4806
|
+
*
|
|
4807
|
+
* Full-dialog terminal screen shown when the SparkVault Identity app has not
|
|
4808
|
+
* been activated for the account that the JS SDK is configured against. The
|
|
4809
|
+
* audience is the website owner / developer — this is a misconfiguration
|
|
4810
|
+
* surface, not an end-user error — so the dialog drops customer branding and
|
|
4811
|
+
* presents itself as an unmistakable SparkVault notice.
|
|
4812
|
+
*/
|
|
4813
|
+
const ACTIVATION_URL = 'https://app.sparkvault.com/apps';
|
|
4814
|
+
function createBadge() {
|
|
4815
|
+
const badge = div('sv-not-activated-badge');
|
|
4816
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
4817
|
+
svg.setAttribute('viewBox', '0 0 56 56');
|
|
4818
|
+
svg.setAttribute('width', '72');
|
|
4819
|
+
svg.setAttribute('height', '72');
|
|
4820
|
+
svg.setAttribute('fill', 'none');
|
|
4821
|
+
svg.classList.add('sv-not-activated-badge-svg');
|
|
4822
|
+
// Soft outer ring
|
|
4823
|
+
const ring = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
4824
|
+
ring.setAttribute('cx', '28');
|
|
4825
|
+
ring.setAttribute('cy', '28');
|
|
4826
|
+
ring.setAttribute('r', '27');
|
|
4827
|
+
ring.setAttribute('fill', '#FFFBEB');
|
|
4828
|
+
ring.setAttribute('stroke', '#FDE68A');
|
|
4829
|
+
ring.setAttribute('stroke-width', '1');
|
|
4830
|
+
svg.appendChild(ring);
|
|
4831
|
+
// Shield body
|
|
4832
|
+
const shield = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
4833
|
+
shield.setAttribute('d', 'M28 13l-10 4.2v8.6c0 6.7 4.27 12.95 10 14.4 5.73-1.45 10-7.7 10-14.4v-8.6L28 13z');
|
|
4834
|
+
shield.setAttribute('fill', '#F59E0B');
|
|
4835
|
+
svg.appendChild(shield);
|
|
4836
|
+
// Exclamation bar
|
|
4837
|
+
const bar = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
4838
|
+
bar.setAttribute('x', '26.6');
|
|
4839
|
+
bar.setAttribute('y', '20');
|
|
4840
|
+
bar.setAttribute('width', '2.8');
|
|
4841
|
+
bar.setAttribute('height', '10');
|
|
4842
|
+
bar.setAttribute('rx', '1.4');
|
|
4843
|
+
bar.setAttribute('fill', '#FFFFFF');
|
|
4844
|
+
svg.appendChild(bar);
|
|
4845
|
+
// Exclamation dot
|
|
4846
|
+
const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
4847
|
+
dot.setAttribute('cx', '28');
|
|
4848
|
+
dot.setAttribute('cy', '34');
|
|
4849
|
+
dot.setAttribute('r', '1.6');
|
|
4850
|
+
dot.setAttribute('fill', '#FFFFFF');
|
|
4851
|
+
svg.appendChild(dot);
|
|
4852
|
+
badge.appendChild(svg);
|
|
4853
|
+
return badge;
|
|
4854
|
+
}
|
|
4855
|
+
function createExternalLinkIcon() {
|
|
4856
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
4857
|
+
svg.setAttribute('viewBox', '0 0 16 16');
|
|
4858
|
+
svg.setAttribute('width', '14');
|
|
4859
|
+
svg.setAttribute('height', '14');
|
|
4860
|
+
svg.setAttribute('fill', 'none');
|
|
4861
|
+
svg.classList.add('sv-not-activated-cta-icon');
|
|
4862
|
+
const arrow = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
4863
|
+
arrow.setAttribute('d', 'M6 3h7v7M13 3l-8 8');
|
|
4864
|
+
arrow.setAttribute('stroke', 'currentColor');
|
|
4865
|
+
arrow.setAttribute('stroke-width', '2');
|
|
4866
|
+
arrow.setAttribute('stroke-linecap', 'round');
|
|
4867
|
+
arrow.setAttribute('stroke-linejoin', 'round');
|
|
4868
|
+
svg.appendChild(arrow);
|
|
4869
|
+
return svg;
|
|
4870
|
+
}
|
|
4871
|
+
function createSparkVaultMark() {
|
|
4872
|
+
const mark = div('sv-not-activated-mark');
|
|
4873
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
4874
|
+
svg.setAttribute('viewBox', '0 0 16 16');
|
|
4875
|
+
svg.setAttribute('width', '14');
|
|
4876
|
+
svg.setAttribute('height', '14');
|
|
4877
|
+
svg.setAttribute('fill', 'none');
|
|
4878
|
+
const flame = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
4879
|
+
flame.setAttribute('d', 'M8 1.5c.6 1.8 2 2.7 2 4.4 0 .9-.5 1.6-1.2 1.6-.6 0-1-.4-1-.9 0-.4.2-.8.2-1.2 0-.7-.5-1.1-1-1.1-1.4 0-2.5 1.5-2.5 3.4 0 2.4 1.9 4.3 4.5 4.3s4.5-1.9 4.5-4.3c0-3.6-3-4.6-5.5-6.2z');
|
|
4880
|
+
flame.setAttribute('fill', 'currentColor');
|
|
4881
|
+
svg.appendChild(flame);
|
|
4882
|
+
const label = document.createElement('span');
|
|
4883
|
+
label.textContent = 'SparkVault';
|
|
4884
|
+
mark.appendChild(svg);
|
|
4885
|
+
mark.appendChild(label);
|
|
4886
|
+
return mark;
|
|
4887
|
+
}
|
|
4888
|
+
class AppNotActivatedView {
|
|
4889
|
+
constructor(props) {
|
|
4890
|
+
this.dismissButton = null;
|
|
4891
|
+
this.props = props;
|
|
4892
|
+
this.boundHandleClose = () => this.props.onClose();
|
|
4893
|
+
}
|
|
4894
|
+
render() {
|
|
4895
|
+
const container = div('sv-not-activated-view');
|
|
4896
|
+
const eyebrow = div('sv-not-activated-eyebrow');
|
|
4897
|
+
eyebrow.appendChild(createSparkVaultMark());
|
|
4898
|
+
const eyebrowDivider = document.createElement('span');
|
|
4899
|
+
eyebrowDivider.className = 'sv-not-activated-eyebrow-divider';
|
|
4900
|
+
eyebrowDivider.textContent = '•';
|
|
4901
|
+
eyebrow.appendChild(eyebrowDivider);
|
|
4902
|
+
const eyebrowLabel = document.createElement('span');
|
|
4903
|
+
eyebrowLabel.className = 'sv-not-activated-eyebrow-label';
|
|
4904
|
+
eyebrowLabel.textContent = 'Identity';
|
|
4905
|
+
eyebrow.appendChild(eyebrowLabel);
|
|
4906
|
+
container.appendChild(eyebrow);
|
|
4907
|
+
const badge = createBadge();
|
|
4908
|
+
container.appendChild(badge);
|
|
4909
|
+
const title = document.createElement('h2');
|
|
4910
|
+
title.className = 'sv-not-activated-title';
|
|
4911
|
+
title.id = 'sv-modal-title';
|
|
4912
|
+
title.textContent = 'Identity app not activated';
|
|
4913
|
+
container.appendChild(title);
|
|
4914
|
+
const message = document.createElement('p');
|
|
4915
|
+
message.className = 'sv-not-activated-message';
|
|
4916
|
+
message.textContent =
|
|
4917
|
+
'Sign-in is unavailable because the site owner hasn’t activated SparkVault Identity for this account. Activate the app to enable passwordless authentication.';
|
|
4918
|
+
container.appendChild(message);
|
|
4919
|
+
const ownerCard = div('sv-not-activated-owner');
|
|
4920
|
+
const ownerLabel = document.createElement('span');
|
|
4921
|
+
ownerLabel.className = 'sv-not-activated-owner-label';
|
|
4922
|
+
ownerLabel.textContent = 'Are you the site owner?';
|
|
4923
|
+
const ownerHint = document.createElement('span');
|
|
4924
|
+
ownerHint.className = 'sv-not-activated-owner-hint';
|
|
4925
|
+
ownerHint.textContent =
|
|
4926
|
+
'Sign in to your SparkVault account and install the Identity app to start accepting sign-ins.';
|
|
4927
|
+
ownerCard.appendChild(ownerLabel);
|
|
4928
|
+
ownerCard.appendChild(ownerHint);
|
|
4929
|
+
container.appendChild(ownerCard);
|
|
4930
|
+
const actions = div('sv-not-activated-actions');
|
|
4931
|
+
const cta = document.createElement('a');
|
|
4932
|
+
cta.className = 'sv-btn sv-btn-primary sv-not-activated-cta';
|
|
4933
|
+
cta.href = ACTIVATION_URL;
|
|
4934
|
+
cta.target = '_blank';
|
|
4935
|
+
cta.rel = 'noopener noreferrer';
|
|
4936
|
+
const ctaLabel = document.createElement('span');
|
|
4937
|
+
ctaLabel.textContent = 'Activate on SparkVault';
|
|
4938
|
+
cta.appendChild(ctaLabel);
|
|
4939
|
+
cta.appendChild(createExternalLinkIcon());
|
|
4940
|
+
actions.appendChild(cta);
|
|
4941
|
+
this.dismissButton = document.createElement('button');
|
|
4942
|
+
this.dismissButton.className = 'sv-not-activated-dismiss';
|
|
4943
|
+
this.dismissButton.type = 'button';
|
|
4944
|
+
this.dismissButton.textContent = 'Dismiss';
|
|
4945
|
+
this.dismissButton.addEventListener('click', this.boundHandleClose);
|
|
4946
|
+
actions.appendChild(this.dismissButton);
|
|
4947
|
+
container.appendChild(actions);
|
|
4948
|
+
return container;
|
|
4949
|
+
}
|
|
4950
|
+
destroy() {
|
|
4951
|
+
if (this.dismissButton) {
|
|
4952
|
+
this.dismissButton.removeEventListener('click', this.boundHandleClose);
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4421
4957
|
/**
|
|
4422
4958
|
* Identity View Renderer
|
|
4423
4959
|
*
|
|
@@ -4474,14 +5010,23 @@ class IdentityRenderer {
|
|
|
4474
5010
|
let config = null;
|
|
4475
5011
|
if (this.api.isConfigPreloaded()) {
|
|
4476
5012
|
// Config fetch already started - race it against a 50ms timeout
|
|
4477
|
-
// If config is ready (or almost ready), we skip the loading flash
|
|
5013
|
+
// If config is ready (or almost ready), we skip the loading flash.
|
|
5014
|
+
// A rejected preload (e.g. app_not_installed) must not abort start() —
|
|
5015
|
+
// swallow here and let the in-modal try/catch below route to the
|
|
5016
|
+
// correct error view.
|
|
4478
5017
|
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), 50));
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
5018
|
+
try {
|
|
5019
|
+
const result = await Promise.race([
|
|
5020
|
+
this.api.getConfig().then((c) => c),
|
|
5021
|
+
timeoutPromise,
|
|
5022
|
+
]);
|
|
5023
|
+
if (result !== null) {
|
|
5024
|
+
config = result;
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
catch {
|
|
5028
|
+
// Preload failed - fall through to fetchConfigWithRetry which will
|
|
5029
|
+
// surface the real error inside the modal.
|
|
4485
5030
|
}
|
|
4486
5031
|
}
|
|
4487
5032
|
// Create modal - if we have config, skip loading state entirely
|
|
@@ -4515,9 +5060,21 @@ class IdentityRenderer {
|
|
|
4515
5060
|
}
|
|
4516
5061
|
}
|
|
4517
5062
|
catch (error) {
|
|
4518
|
-
this.
|
|
5063
|
+
this.setState(this.viewStateForConfigError(error));
|
|
4519
5064
|
}
|
|
4520
5065
|
}
|
|
5066
|
+
/**
|
|
5067
|
+
* Map a config-fetch error to the appropriate terminal view.
|
|
5068
|
+
* `app_not_installed` is a misconfiguration (account hasn't installed the
|
|
5069
|
+
* Identity app) — show a developer-targeted "Not Activated" screen with a
|
|
5070
|
+
* link to SparkVault.com instead of the generic service-unavailable view.
|
|
5071
|
+
*/
|
|
5072
|
+
viewStateForConfigError(error) {
|
|
5073
|
+
if (error instanceof IdentityApiError && error.code === 'app_not_installed') {
|
|
5074
|
+
return { view: 'app-not-activated' };
|
|
5075
|
+
}
|
|
5076
|
+
return { view: 'service-unavailable' };
|
|
5077
|
+
}
|
|
4521
5078
|
/**
|
|
4522
5079
|
* Fetch config with a single retry on network errors.
|
|
4523
5080
|
* Handles transient failures common during subdomain switches
|
|
@@ -4539,6 +5096,35 @@ class IdentityRenderer {
|
|
|
4539
5096
|
return this.api.getConfig();
|
|
4540
5097
|
}
|
|
4541
5098
|
}
|
|
5099
|
+
/**
|
|
5100
|
+
* Retry loading config after a service-unavailable error.
|
|
5101
|
+
* Shows loading state, re-fetches config, then proceeds normally.
|
|
5102
|
+
*/
|
|
5103
|
+
async retryConfigLoad() {
|
|
5104
|
+
this.setState({ view: 'loading' });
|
|
5105
|
+
try {
|
|
5106
|
+
const config = await this.fetchConfigWithRetry();
|
|
5107
|
+
this.verificationState.setConfig(config);
|
|
5108
|
+
if (config.branding) {
|
|
5109
|
+
this.container.updateBranding(config.branding);
|
|
5110
|
+
}
|
|
5111
|
+
const resolvedBackdropBlur = this.options.backdropBlur !== undefined
|
|
5112
|
+
? this.options.backdropBlur
|
|
5113
|
+
: config.branding?.backdrop_blur !== undefined
|
|
5114
|
+
? config.branding.backdrop_blur
|
|
5115
|
+
: true;
|
|
5116
|
+
this.container.updateBackdropBlur(resolvedBackdropBlur);
|
|
5117
|
+
if (this.recipient) {
|
|
5118
|
+
this.showMethodSelect();
|
|
5119
|
+
}
|
|
5120
|
+
else {
|
|
5121
|
+
this.showIdentityInput();
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
catch (error) {
|
|
5125
|
+
this.setState(this.viewStateForConfigError(error));
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
4542
5128
|
/**
|
|
4543
5129
|
* Close the modal and clean up.
|
|
4544
5130
|
* Cancels all pending API requests to prevent orphaned operations.
|
|
@@ -4570,6 +5156,10 @@ class IdentityRenderer {
|
|
|
4570
5156
|
if (!body)
|
|
4571
5157
|
return;
|
|
4572
5158
|
this.destroyCurrentView();
|
|
5159
|
+
// Full-dialog terminal screens own the entire modal; toggle the container
|
|
5160
|
+
// chrome accordingly so the customer header doesn't bleed into a
|
|
5161
|
+
// SparkVault-owned message.
|
|
5162
|
+
this.container.setMinimalChrome(this.viewState.view === 'app-not-activated');
|
|
4573
5163
|
const view = this.createViewForState(this.viewState);
|
|
4574
5164
|
this.currentView = view;
|
|
4575
5165
|
clearChildren(body);
|
|
@@ -4666,6 +5256,15 @@ class IdentityRenderer {
|
|
|
4666
5256
|
onRetry: () => this.showIdentityInput(),
|
|
4667
5257
|
onClose: () => this.handleClose(),
|
|
4668
5258
|
});
|
|
5259
|
+
case 'service-unavailable':
|
|
5260
|
+
return new ServiceUnavailableView({
|
|
5261
|
+
onRetry: () => this.retryConfigLoad(),
|
|
5262
|
+
onClose: () => this.handleClose(),
|
|
5263
|
+
});
|
|
5264
|
+
case 'app-not-activated':
|
|
5265
|
+
return new AppNotActivatedView({
|
|
5266
|
+
onClose: () => this.handleClose(),
|
|
5267
|
+
});
|
|
4669
5268
|
}
|
|
4670
5269
|
}
|
|
4671
5270
|
showIdentityInput(error) {
|
|
@@ -5441,6 +6040,13 @@ class InlineContainer {
|
|
|
5441
6040
|
updateBackdropBlur(_enabled) {
|
|
5442
6041
|
// No-op - inline containers don't have backdrop blur
|
|
5443
6042
|
}
|
|
6043
|
+
/**
|
|
6044
|
+
* Minimal-chrome toggle (no-op for inline container).
|
|
6045
|
+
* Inline containers don't own page-level chrome to hide.
|
|
6046
|
+
*/
|
|
6047
|
+
setMinimalChrome(_enabled) {
|
|
6048
|
+
// No-op
|
|
6049
|
+
}
|
|
5444
6050
|
/**
|
|
5445
6051
|
* Get the body element for content rendering.
|
|
5446
6052
|
*/
|
|
@@ -5804,15 +6410,19 @@ class IdentityModule {
|
|
|
5804
6410
|
}
|
|
5805
6411
|
return new InlineContainer(element);
|
|
5806
6412
|
}
|
|
5807
|
-
//
|
|
6413
|
+
// Public-API aliases. The documented method names in the customer-facing
|
|
6414
|
+
// SDK README and the API docs are pop() and render(); verify() is the
|
|
6415
|
+
// underlying primitive. Both remain canonical entry points.
|
|
5808
6416
|
/**
|
|
5809
|
-
*
|
|
6417
|
+
* Open the identity verification modal as a popup. Equivalent to
|
|
6418
|
+
* `verify(options)`.
|
|
5810
6419
|
*/
|
|
5811
6420
|
async pop(options = {}) {
|
|
5812
6421
|
return this.verify(options);
|
|
5813
6422
|
}
|
|
5814
6423
|
/**
|
|
5815
|
-
*
|
|
6424
|
+
* Render the identity verification UI inline into a target element.
|
|
6425
|
+
* Equivalent to `verify({ ...options, target })`.
|
|
5816
6426
|
*/
|
|
5817
6427
|
async render(options) {
|
|
5818
6428
|
return this.verify(options);
|
|
@@ -8844,16 +9454,14 @@ class VaultUploadModule {
|
|
|
8844
9454
|
}
|
|
8845
9455
|
return new UploadInlineContainer(element);
|
|
8846
9456
|
}
|
|
8847
|
-
//
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
9457
|
+
// Public-API aliases. The web vault-edit screen instructs customers to
|
|
9458
|
+
// call sv.vaults.upload.pop(vaultId) / .render(vaultId, target) — both
|
|
9459
|
+
// are documented entry points that defer to upload() under the hood.
|
|
9460
|
+
/** Open the upload widget as a popup. Equivalent to `upload(options)`. */
|
|
8851
9461
|
async pop(options) {
|
|
8852
9462
|
return this.upload(options);
|
|
8853
9463
|
}
|
|
8854
|
-
/**
|
|
8855
|
-
* @deprecated Use `upload({ target })` instead. Will be removed in v2.0.
|
|
8856
|
-
*/
|
|
9464
|
+
/** Render the upload widget inline. Equivalent to `upload({ ...options, target })`. */
|
|
8857
9465
|
async render(options) {
|
|
8858
9466
|
return this.upload(options);
|
|
8859
9467
|
}
|