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