@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
|
@@ -122,6 +122,12 @@ export declare class IdentityRenderer {
|
|
|
122
122
|
private setupSparkLinkWsHandler;
|
|
123
123
|
/**
|
|
124
124
|
* Handle successful SparkLink verification via WebSocket push.
|
|
125
|
+
*
|
|
126
|
+
* Always invokes the host callback with the full result (including any
|
|
127
|
+
* redirect URL) before attempting top-level navigation. The host page
|
|
128
|
+
* is the source of truth for what should happen after verification:
|
|
129
|
+
* the SDK's window.location.href call is a fallback for hosts that
|
|
130
|
+
* delegate navigation entirely to the SDK.
|
|
125
131
|
*/
|
|
126
132
|
private handleSparkLinkVerified;
|
|
127
133
|
/**
|
package/dist/sparkvault.cjs.js
CHANGED
|
@@ -1967,6 +1967,21 @@ function getStyles(options) {
|
|
|
1967
1967
|
text-align: left;
|
|
1968
1968
|
padding: 12px 14px;
|
|
1969
1969
|
margin-bottom: 8px;
|
|
1970
|
+
/* Inset 2px on either side so the focus outline (drawn outside the
|
|
1971
|
+
border-box) has room to render without clipping against the
|
|
1972
|
+
dialog's container edges. width:auto pairs with this to prevent
|
|
1973
|
+
the row from forcing horizontal overflow. */
|
|
1974
|
+
margin-left: 2px;
|
|
1975
|
+
margin-right: 2px;
|
|
1976
|
+
width: calc(100% - 4px);
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
/* The shared .sv-btn focus rule sets outline-offset:2px which sits
|
|
1980
|
+
outside the row's edges and gets clipped by the parent. Method
|
|
1981
|
+
rows are full-width by design, so render the focus indicator
|
|
1982
|
+
flush against the border instead. */
|
|
1983
|
+
.sv-btn-method:focus-visible {
|
|
1984
|
+
outline-offset: 0;
|
|
1970
1985
|
}
|
|
1971
1986
|
|
|
1972
1987
|
.sv-btn-method:hover:not(:disabled) {
|
|
@@ -5450,14 +5465,9 @@ class IdentityRenderer {
|
|
|
5450
5465
|
const currentMethod = this.verificationState.totp.method ?? 'email';
|
|
5451
5466
|
const result = await this.totpHandler.verify(code);
|
|
5452
5467
|
if (result.success && result.result) {
|
|
5453
|
-
//
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
window.location.href = result.result.redirect;
|
|
5457
|
-
return;
|
|
5458
|
-
}
|
|
5459
|
-
// Check if we should prompt for passkey registration
|
|
5460
|
-
if (await this.shouldShowPasskeyPrompt()) {
|
|
5468
|
+
// Passkey upsell only when there's no redirect target — with one,
|
|
5469
|
+
// we head to the destination immediately.
|
|
5470
|
+
if (!result.result.redirect && await this.shouldShowPasskeyPrompt()) {
|
|
5461
5471
|
this.setState({
|
|
5462
5472
|
view: 'passkey-prompt',
|
|
5463
5473
|
email: this.recipient,
|
|
@@ -5466,7 +5476,18 @@ class IdentityRenderer {
|
|
|
5466
5476
|
return;
|
|
5467
5477
|
}
|
|
5468
5478
|
this.close();
|
|
5469
|
-
|
|
5479
|
+
// Hand the full result (including redirect) to the host first so the
|
|
5480
|
+
// page-level handler can act before we navigate away.
|
|
5481
|
+
try {
|
|
5482
|
+
this.callbacks.onSuccess(result.result);
|
|
5483
|
+
}
|
|
5484
|
+
catch (err) {
|
|
5485
|
+
// eslint-disable-next-line no-console
|
|
5486
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5487
|
+
}
|
|
5488
|
+
if (typeof result.result.redirect === 'string' && result.result.redirect) {
|
|
5489
|
+
window.location.href = result.result.redirect;
|
|
5490
|
+
}
|
|
5470
5491
|
}
|
|
5471
5492
|
else {
|
|
5472
5493
|
// Check if error is expiry - auto-resend if so
|
|
@@ -5529,14 +5550,17 @@ class IdentityRenderer {
|
|
|
5529
5550
|
? await this.passkeyHandler.register()
|
|
5530
5551
|
: await this.passkeyHandler.verify();
|
|
5531
5552
|
if (result.success && result.result) {
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
this.
|
|
5553
|
+
this.close();
|
|
5554
|
+
try {
|
|
5555
|
+
this.callbacks.onSuccess(result.result);
|
|
5556
|
+
}
|
|
5557
|
+
catch (err) {
|
|
5558
|
+
// eslint-disable-next-line no-console
|
|
5559
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5560
|
+
}
|
|
5561
|
+
if (typeof result.result.redirect === 'string' && result.result.redirect) {
|
|
5535
5562
|
window.location.href = result.result.redirect;
|
|
5536
|
-
return;
|
|
5537
5563
|
}
|
|
5538
|
-
this.close();
|
|
5539
|
-
this.callbacks.onSuccess(result.result);
|
|
5540
5564
|
}
|
|
5541
5565
|
else {
|
|
5542
5566
|
// Handle different error types
|
|
@@ -5574,14 +5598,17 @@ class IdentityRenderer {
|
|
|
5574
5598
|
const pendingResult = this.verificationState.passkey.pendingResult;
|
|
5575
5599
|
if (pendingResult) {
|
|
5576
5600
|
this.verificationState.setPendingResult(null);
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
this.
|
|
5601
|
+
this.close();
|
|
5602
|
+
try {
|
|
5603
|
+
this.callbacks.onSuccess(pendingResult);
|
|
5604
|
+
}
|
|
5605
|
+
catch (err) {
|
|
5606
|
+
// eslint-disable-next-line no-console
|
|
5607
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5608
|
+
}
|
|
5609
|
+
if (typeof pendingResult.redirect === 'string' && pendingResult.redirect) {
|
|
5580
5610
|
window.location.href = pendingResult.redirect;
|
|
5581
|
-
return;
|
|
5582
5611
|
}
|
|
5583
|
-
this.close();
|
|
5584
|
-
this.callbacks.onSuccess(pendingResult);
|
|
5585
5612
|
}
|
|
5586
5613
|
}
|
|
5587
5614
|
handleSocialLogin(provider) {
|
|
@@ -5663,15 +5690,18 @@ class IdentityRenderer {
|
|
|
5663
5690
|
// Directly trigger passkey registration
|
|
5664
5691
|
const result = await this.passkeyHandler.register();
|
|
5665
5692
|
if (result.success && result.result) {
|
|
5666
|
-
// Handle redirect for OIDC/simple mode flows
|
|
5667
|
-
if (result.result.redirect) {
|
|
5668
|
-
this.close();
|
|
5669
|
-
window.location.href = result.result.redirect;
|
|
5670
|
-
return;
|
|
5671
|
-
}
|
|
5672
5693
|
// Passkey created successfully - use the new token
|
|
5673
5694
|
this.close();
|
|
5674
|
-
|
|
5695
|
+
try {
|
|
5696
|
+
this.callbacks.onSuccess(result.result);
|
|
5697
|
+
}
|
|
5698
|
+
catch (err) {
|
|
5699
|
+
// eslint-disable-next-line no-console
|
|
5700
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5701
|
+
}
|
|
5702
|
+
if (typeof result.result.redirect === 'string' && result.result.redirect) {
|
|
5703
|
+
window.location.href = result.result.redirect;
|
|
5704
|
+
}
|
|
5675
5705
|
}
|
|
5676
5706
|
else if (result.errorType === 'cancelled') {
|
|
5677
5707
|
// User cancelled browser dialog - go back to prompt so they can skip or try again
|
|
@@ -5698,15 +5728,18 @@ class IdentityRenderer {
|
|
|
5698
5728
|
handlePasskeyPromptSkip(pendingResult) {
|
|
5699
5729
|
// Set 30-day cookie to suppress future prompts
|
|
5700
5730
|
this.verificationState.dismissPasskeyPrompt();
|
|
5701
|
-
// Handle redirect for OIDC/simple mode flows
|
|
5702
|
-
if (pendingResult.redirect) {
|
|
5703
|
-
this.close();
|
|
5704
|
-
window.location.href = pendingResult.redirect;
|
|
5705
|
-
return;
|
|
5706
|
-
}
|
|
5707
5731
|
// Complete the verification
|
|
5708
5732
|
this.close();
|
|
5709
|
-
|
|
5733
|
+
try {
|
|
5734
|
+
this.callbacks.onSuccess(pendingResult);
|
|
5735
|
+
}
|
|
5736
|
+
catch (err) {
|
|
5737
|
+
// eslint-disable-next-line no-console
|
|
5738
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5739
|
+
}
|
|
5740
|
+
if (typeof pendingResult.redirect === 'string' && pendingResult.redirect) {
|
|
5741
|
+
window.location.href = pendingResult.redirect;
|
|
5742
|
+
}
|
|
5710
5743
|
}
|
|
5711
5744
|
/**
|
|
5712
5745
|
* Open a WebSocket connection and return the connectionId.
|
|
@@ -5808,6 +5841,19 @@ class IdentityRenderer {
|
|
|
5808
5841
|
catch {
|
|
5809
5842
|
return;
|
|
5810
5843
|
}
|
|
5844
|
+
// Diagnostic: dump the parsed message so we can see on the client
|
|
5845
|
+
// EXACTLY what arrives. Keys + redirect prefix tell us whether the
|
|
5846
|
+
// field made it across API Gateway. Remove once we've confirmed.
|
|
5847
|
+
// eslint-disable-next-line no-console
|
|
5848
|
+
console.info('[SparkVault SDK] sparklink WS message', {
|
|
5849
|
+
type: data.type,
|
|
5850
|
+
keys: Object.keys(data),
|
|
5851
|
+
hasRedirect: typeof data.redirect === 'string',
|
|
5852
|
+
redirectPrefix: typeof data.redirect === 'string'
|
|
5853
|
+
? data.redirect.slice(0, 80)
|
|
5854
|
+
: null,
|
|
5855
|
+
rawLength: event.data.length,
|
|
5856
|
+
});
|
|
5811
5857
|
switch (data.type) {
|
|
5812
5858
|
case 'sparklink_verified':
|
|
5813
5859
|
this.handleSparkLinkVerified({
|
|
@@ -5846,17 +5892,18 @@ class IdentityRenderer {
|
|
|
5846
5892
|
}
|
|
5847
5893
|
/**
|
|
5848
5894
|
* Handle successful SparkLink verification via WebSocket push.
|
|
5895
|
+
*
|
|
5896
|
+
* Always invokes the host callback with the full result (including any
|
|
5897
|
+
* redirect URL) before attempting top-level navigation. The host page
|
|
5898
|
+
* is the source of truth for what should happen after verification:
|
|
5899
|
+
* the SDK's window.location.href call is a fallback for hosts that
|
|
5900
|
+
* delegate navigation entirely to the SDK.
|
|
5849
5901
|
*/
|
|
5850
5902
|
async handleSparkLinkVerified(result) {
|
|
5851
5903
|
this.cleanupSparkLink();
|
|
5852
|
-
//
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
window.location.href = result.redirect;
|
|
5856
|
-
return;
|
|
5857
|
-
}
|
|
5858
|
-
// Check if we should prompt for passkey registration
|
|
5859
|
-
if (await this.shouldShowPasskeyPrompt()) {
|
|
5904
|
+
// Passkey upsell only when there's no redirect target — once we have
|
|
5905
|
+
// a destination we go there immediately, no upsell.
|
|
5906
|
+
if (!result.redirect && await this.shouldShowPasskeyPrompt()) {
|
|
5860
5907
|
this.setState({
|
|
5861
5908
|
view: 'passkey-prompt',
|
|
5862
5909
|
email: this.recipient,
|
|
@@ -5865,7 +5912,22 @@ class IdentityRenderer {
|
|
|
5865
5912
|
return;
|
|
5866
5913
|
}
|
|
5867
5914
|
this.close();
|
|
5868
|
-
|
|
5915
|
+
// Hand the full result (including redirect) to the host first so the
|
|
5916
|
+
// page-level handler can do app-specific work (logging, navigation,
|
|
5917
|
+
// etc.) before we steal the browsing context with a navigation.
|
|
5918
|
+
try {
|
|
5919
|
+
this.callbacks.onSuccess(result);
|
|
5920
|
+
}
|
|
5921
|
+
catch (err) {
|
|
5922
|
+
// eslint-disable-next-line no-console
|
|
5923
|
+
console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
|
|
5924
|
+
}
|
|
5925
|
+
// SDK-side fallback navigation. If the host already navigated this is
|
|
5926
|
+
// a no-op (the document is already unloading); if not, this rescues
|
|
5927
|
+
// simple-mode flows whose host forgot to act on result.redirect.
|
|
5928
|
+
if (typeof result.redirect === 'string' && result.redirect) {
|
|
5929
|
+
window.location.href = result.redirect;
|
|
5930
|
+
}
|
|
5869
5931
|
}
|
|
5870
5932
|
/**
|
|
5871
5933
|
* Clean up SparkLink WebSocket connection.
|