@sparkvault/sdk 1.23.2 → 1.23.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/dist/identity/renderer.d.ts +6 -0
- package/dist/sparkvault.cjs.js +106 -44
- package/dist/sparkvault.cjs.js.map +1 -1
- package/dist/sparkvault.esm.js +106 -44
- package/dist/sparkvault.esm.js.map +1 -1
- package/dist/sparkvault.js +1 -1
- package/dist/sparkvault.js.map +1 -1
- package/package.json +1 -1
package/dist/sparkvault.esm.js
CHANGED
|
@@ -1963,6 +1963,21 @@ function getStyles(options) {
|
|
|
1963
1963
|
text-align: left;
|
|
1964
1964
|
padding: 12px 14px;
|
|
1965
1965
|
margin-bottom: 8px;
|
|
1966
|
+
/* Inset 2px on either side so the focus outline (drawn outside the
|
|
1967
|
+
border-box) has room to render without clipping against the
|
|
1968
|
+
dialog's container edges. width:auto pairs with this to prevent
|
|
1969
|
+
the row from forcing horizontal overflow. */
|
|
1970
|
+
margin-left: 2px;
|
|
1971
|
+
margin-right: 2px;
|
|
1972
|
+
width: calc(100% - 4px);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
/* The shared .sv-btn focus rule sets outline-offset:2px which sits
|
|
1976
|
+
outside the row's edges and gets clipped by the parent. Method
|
|
1977
|
+
rows are full-width by design, so render the focus indicator
|
|
1978
|
+
flush against the border instead. */
|
|
1979
|
+
.sv-btn-method:focus-visible {
|
|
1980
|
+
outline-offset: 0;
|
|
1966
1981
|
}
|
|
1967
1982
|
|
|
1968
1983
|
.sv-btn-method:hover:not(:disabled) {
|
|
@@ -5446,14 +5461,9 @@ class IdentityRenderer {
|
|
|
5446
5461
|
const currentMethod = this.verificationState.totp.method ?? 'email';
|
|
5447
5462
|
const result = await this.totpHandler.verify(code);
|
|
5448
5463
|
if (result.success && result.result) {
|
|
5449
|
-
//
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
window.location.href = result.result.redirect;
|
|
5453
|
-
return;
|
|
5454
|
-
}
|
|
5455
|
-
// Check if we should prompt for passkey registration
|
|
5456
|
-
if (await this.shouldShowPasskeyPrompt()) {
|
|
5464
|
+
// Passkey upsell only when there's no redirect target — with one,
|
|
5465
|
+
// we head to the destination immediately.
|
|
5466
|
+
if (!result.result.redirect && await this.shouldShowPasskeyPrompt()) {
|
|
5457
5467
|
this.setState({
|
|
5458
5468
|
view: 'passkey-prompt',
|
|
5459
5469
|
email: this.recipient,
|
|
@@ -5462,7 +5472,18 @@ class IdentityRenderer {
|
|
|
5462
5472
|
return;
|
|
5463
5473
|
}
|
|
5464
5474
|
this.close();
|
|
5465
|
-
|
|
5475
|
+
// Hand the full result (including redirect) to the host first so the
|
|
5476
|
+
// page-level handler can act before we navigate away.
|
|
5477
|
+
try {
|
|
5478
|
+
this.callbacks.onSuccess(result.result);
|
|
5479
|
+
}
|
|
5480
|
+
catch (err) {
|
|
5481
|
+
// eslint-disable-next-line no-console
|
|
5482
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5483
|
+
}
|
|
5484
|
+
if (typeof result.result.redirect === 'string' && result.result.redirect) {
|
|
5485
|
+
window.location.href = result.result.redirect;
|
|
5486
|
+
}
|
|
5466
5487
|
}
|
|
5467
5488
|
else {
|
|
5468
5489
|
// Check if error is expiry - auto-resend if so
|
|
@@ -5525,14 +5546,17 @@ class IdentityRenderer {
|
|
|
5525
5546
|
? await this.passkeyHandler.register()
|
|
5526
5547
|
: await this.passkeyHandler.verify();
|
|
5527
5548
|
if (result.success && result.result) {
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
this.
|
|
5549
|
+
this.close();
|
|
5550
|
+
try {
|
|
5551
|
+
this.callbacks.onSuccess(result.result);
|
|
5552
|
+
}
|
|
5553
|
+
catch (err) {
|
|
5554
|
+
// eslint-disable-next-line no-console
|
|
5555
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5556
|
+
}
|
|
5557
|
+
if (typeof result.result.redirect === 'string' && result.result.redirect) {
|
|
5531
5558
|
window.location.href = result.result.redirect;
|
|
5532
|
-
return;
|
|
5533
5559
|
}
|
|
5534
|
-
this.close();
|
|
5535
|
-
this.callbacks.onSuccess(result.result);
|
|
5536
5560
|
}
|
|
5537
5561
|
else {
|
|
5538
5562
|
// Handle different error types
|
|
@@ -5570,14 +5594,17 @@ class IdentityRenderer {
|
|
|
5570
5594
|
const pendingResult = this.verificationState.passkey.pendingResult;
|
|
5571
5595
|
if (pendingResult) {
|
|
5572
5596
|
this.verificationState.setPendingResult(null);
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
this.
|
|
5597
|
+
this.close();
|
|
5598
|
+
try {
|
|
5599
|
+
this.callbacks.onSuccess(pendingResult);
|
|
5600
|
+
}
|
|
5601
|
+
catch (err) {
|
|
5602
|
+
// eslint-disable-next-line no-console
|
|
5603
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5604
|
+
}
|
|
5605
|
+
if (typeof pendingResult.redirect === 'string' && pendingResult.redirect) {
|
|
5576
5606
|
window.location.href = pendingResult.redirect;
|
|
5577
|
-
return;
|
|
5578
5607
|
}
|
|
5579
|
-
this.close();
|
|
5580
|
-
this.callbacks.onSuccess(pendingResult);
|
|
5581
5608
|
}
|
|
5582
5609
|
}
|
|
5583
5610
|
handleSocialLogin(provider) {
|
|
@@ -5659,15 +5686,18 @@ class IdentityRenderer {
|
|
|
5659
5686
|
// Directly trigger passkey registration
|
|
5660
5687
|
const result = await this.passkeyHandler.register();
|
|
5661
5688
|
if (result.success && result.result) {
|
|
5662
|
-
// Handle redirect for OIDC/simple mode flows
|
|
5663
|
-
if (result.result.redirect) {
|
|
5664
|
-
this.close();
|
|
5665
|
-
window.location.href = result.result.redirect;
|
|
5666
|
-
return;
|
|
5667
|
-
}
|
|
5668
5689
|
// Passkey created successfully - use the new token
|
|
5669
5690
|
this.close();
|
|
5670
|
-
|
|
5691
|
+
try {
|
|
5692
|
+
this.callbacks.onSuccess(result.result);
|
|
5693
|
+
}
|
|
5694
|
+
catch (err) {
|
|
5695
|
+
// eslint-disable-next-line no-console
|
|
5696
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5697
|
+
}
|
|
5698
|
+
if (typeof result.result.redirect === 'string' && result.result.redirect) {
|
|
5699
|
+
window.location.href = result.result.redirect;
|
|
5700
|
+
}
|
|
5671
5701
|
}
|
|
5672
5702
|
else if (result.errorType === 'cancelled') {
|
|
5673
5703
|
// User cancelled browser dialog - go back to prompt so they can skip or try again
|
|
@@ -5694,15 +5724,18 @@ class IdentityRenderer {
|
|
|
5694
5724
|
handlePasskeyPromptSkip(pendingResult) {
|
|
5695
5725
|
// Set 30-day cookie to suppress future prompts
|
|
5696
5726
|
this.verificationState.dismissPasskeyPrompt();
|
|
5697
|
-
// Handle redirect for OIDC/simple mode flows
|
|
5698
|
-
if (pendingResult.redirect) {
|
|
5699
|
-
this.close();
|
|
5700
|
-
window.location.href = pendingResult.redirect;
|
|
5701
|
-
return;
|
|
5702
|
-
}
|
|
5703
5727
|
// Complete the verification
|
|
5704
5728
|
this.close();
|
|
5705
|
-
|
|
5729
|
+
try {
|
|
5730
|
+
this.callbacks.onSuccess(pendingResult);
|
|
5731
|
+
}
|
|
5732
|
+
catch (err) {
|
|
5733
|
+
// eslint-disable-next-line no-console
|
|
5734
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5735
|
+
}
|
|
5736
|
+
if (typeof pendingResult.redirect === 'string' && pendingResult.redirect) {
|
|
5737
|
+
window.location.href = pendingResult.redirect;
|
|
5738
|
+
}
|
|
5706
5739
|
}
|
|
5707
5740
|
/**
|
|
5708
5741
|
* Open a WebSocket connection and return the connectionId.
|
|
@@ -5804,6 +5837,19 @@ class IdentityRenderer {
|
|
|
5804
5837
|
catch {
|
|
5805
5838
|
return;
|
|
5806
5839
|
}
|
|
5840
|
+
// Diagnostic: dump the parsed message so we can see on the client
|
|
5841
|
+
// EXACTLY what arrives. Keys + redirect prefix tell us whether the
|
|
5842
|
+
// field made it across API Gateway. Remove once we've confirmed.
|
|
5843
|
+
// eslint-disable-next-line no-console
|
|
5844
|
+
console.info('[SparkVault SDK] sparklink WS message', {
|
|
5845
|
+
type: data.type,
|
|
5846
|
+
keys: Object.keys(data),
|
|
5847
|
+
hasRedirect: typeof data.redirect === 'string',
|
|
5848
|
+
redirectPrefix: typeof data.redirect === 'string'
|
|
5849
|
+
? data.redirect.slice(0, 80)
|
|
5850
|
+
: null,
|
|
5851
|
+
rawLength: event.data.length,
|
|
5852
|
+
});
|
|
5807
5853
|
switch (data.type) {
|
|
5808
5854
|
case 'sparklink_verified':
|
|
5809
5855
|
this.handleSparkLinkVerified({
|
|
@@ -5842,17 +5888,18 @@ class IdentityRenderer {
|
|
|
5842
5888
|
}
|
|
5843
5889
|
/**
|
|
5844
5890
|
* Handle successful SparkLink verification via WebSocket push.
|
|
5891
|
+
*
|
|
5892
|
+
* Always invokes the host callback with the full result (including any
|
|
5893
|
+
* redirect URL) before attempting top-level navigation. The host page
|
|
5894
|
+
* is the source of truth for what should happen after verification:
|
|
5895
|
+
* the SDK's window.location.href call is a fallback for hosts that
|
|
5896
|
+
* delegate navigation entirely to the SDK.
|
|
5845
5897
|
*/
|
|
5846
5898
|
async handleSparkLinkVerified(result) {
|
|
5847
5899
|
this.cleanupSparkLink();
|
|
5848
|
-
//
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
window.location.href = result.redirect;
|
|
5852
|
-
return;
|
|
5853
|
-
}
|
|
5854
|
-
// Check if we should prompt for passkey registration
|
|
5855
|
-
if (await this.shouldShowPasskeyPrompt()) {
|
|
5900
|
+
// Passkey upsell only when there's no redirect target — once we have
|
|
5901
|
+
// a destination we go there immediately, no upsell.
|
|
5902
|
+
if (!result.redirect && await this.shouldShowPasskeyPrompt()) {
|
|
5856
5903
|
this.setState({
|
|
5857
5904
|
view: 'passkey-prompt',
|
|
5858
5905
|
email: this.recipient,
|
|
@@ -5861,7 +5908,22 @@ class IdentityRenderer {
|
|
|
5861
5908
|
return;
|
|
5862
5909
|
}
|
|
5863
5910
|
this.close();
|
|
5864
|
-
|
|
5911
|
+
// Hand the full result (including redirect) to the host first so the
|
|
5912
|
+
// page-level handler can do app-specific work (logging, navigation,
|
|
5913
|
+
// etc.) before we steal the browsing context with a navigation.
|
|
5914
|
+
try {
|
|
5915
|
+
this.callbacks.onSuccess(result);
|
|
5916
|
+
}
|
|
5917
|
+
catch (err) {
|
|
5918
|
+
// eslint-disable-next-line no-console
|
|
5919
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5920
|
+
}
|
|
5921
|
+
// SDK-side fallback navigation. If the host already navigated this is
|
|
5922
|
+
// a no-op (the document is already unloading); if not, this rescues
|
|
5923
|
+
// simple-mode flows whose host forgot to act on result.redirect.
|
|
5924
|
+
if (typeof result.redirect === 'string' && result.redirect) {
|
|
5925
|
+
window.location.href = result.redirect;
|
|
5926
|
+
}
|
|
5865
5927
|
}
|
|
5866
5928
|
/**
|
|
5867
5929
|
* Clean up SparkLink WebSocket connection.
|