@sparkvault/sdk 1.23.1 → 1.23.3

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.
@@ -5446,14 +5446,9 @@ class IdentityRenderer {
5446
5446
  const currentMethod = this.verificationState.totp.method ?? 'email';
5447
5447
  const result = await this.totpHandler.verify(code);
5448
5448
  if (result.success && result.result) {
5449
- // Handle redirect for OIDC/simple mode flows
5450
- if (result.result.redirect) {
5451
- this.close();
5452
- window.location.href = result.result.redirect;
5453
- return;
5454
- }
5455
- // Check if we should prompt for passkey registration
5456
- if (await this.shouldShowPasskeyPrompt()) {
5449
+ // Passkey upsell only when there's no redirect target with one,
5450
+ // we head to the destination immediately.
5451
+ if (!result.result.redirect && await this.shouldShowPasskeyPrompt()) {
5457
5452
  this.setState({
5458
5453
  view: 'passkey-prompt',
5459
5454
  email: this.recipient,
@@ -5462,7 +5457,18 @@ class IdentityRenderer {
5462
5457
  return;
5463
5458
  }
5464
5459
  this.close();
5465
- this.callbacks.onSuccess(result.result);
5460
+ // Hand the full result (including redirect) to the host first so the
5461
+ // page-level handler can act before we navigate away.
5462
+ try {
5463
+ this.callbacks.onSuccess(result.result);
5464
+ }
5465
+ catch (err) {
5466
+ // eslint-disable-next-line no-console
5467
+ console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
5468
+ }
5469
+ if (typeof result.result.redirect === 'string' && result.result.redirect) {
5470
+ window.location.href = result.result.redirect;
5471
+ }
5466
5472
  }
5467
5473
  else {
5468
5474
  // Check if error is expiry - auto-resend if so
@@ -5525,14 +5531,17 @@ class IdentityRenderer {
5525
5531
  ? await this.passkeyHandler.register()
5526
5532
  : await this.passkeyHandler.verify();
5527
5533
  if (result.success && result.result) {
5528
- // Handle redirect for OIDC/simple mode flows
5529
- if (result.result.redirect) {
5530
- this.close();
5534
+ this.close();
5535
+ try {
5536
+ this.callbacks.onSuccess(result.result);
5537
+ }
5538
+ catch (err) {
5539
+ // eslint-disable-next-line no-console
5540
+ console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
5541
+ }
5542
+ if (typeof result.result.redirect === 'string' && result.result.redirect) {
5531
5543
  window.location.href = result.result.redirect;
5532
- return;
5533
5544
  }
5534
- this.close();
5535
- this.callbacks.onSuccess(result.result);
5536
5545
  }
5537
5546
  else {
5538
5547
  // Handle different error types
@@ -5570,14 +5579,17 @@ class IdentityRenderer {
5570
5579
  const pendingResult = this.verificationState.passkey.pendingResult;
5571
5580
  if (pendingResult) {
5572
5581
  this.verificationState.setPendingResult(null);
5573
- // Handle redirect for OIDC/simple mode flows
5574
- if (pendingResult.redirect) {
5575
- this.close();
5582
+ this.close();
5583
+ try {
5584
+ this.callbacks.onSuccess(pendingResult);
5585
+ }
5586
+ catch (err) {
5587
+ // eslint-disable-next-line no-console
5588
+ console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
5589
+ }
5590
+ if (typeof pendingResult.redirect === 'string' && pendingResult.redirect) {
5576
5591
  window.location.href = pendingResult.redirect;
5577
- return;
5578
5592
  }
5579
- this.close();
5580
- this.callbacks.onSuccess(pendingResult);
5581
5593
  }
5582
5594
  }
5583
5595
  handleSocialLogin(provider) {
@@ -5659,15 +5671,18 @@ class IdentityRenderer {
5659
5671
  // Directly trigger passkey registration
5660
5672
  const result = await this.passkeyHandler.register();
5661
5673
  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
5674
  // Passkey created successfully - use the new token
5669
5675
  this.close();
5670
- this.callbacks.onSuccess(result.result);
5676
+ try {
5677
+ this.callbacks.onSuccess(result.result);
5678
+ }
5679
+ catch (err) {
5680
+ // eslint-disable-next-line no-console
5681
+ console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
5682
+ }
5683
+ if (typeof result.result.redirect === 'string' && result.result.redirect) {
5684
+ window.location.href = result.result.redirect;
5685
+ }
5671
5686
  }
5672
5687
  else if (result.errorType === 'cancelled') {
5673
5688
  // User cancelled browser dialog - go back to prompt so they can skip or try again
@@ -5694,15 +5709,18 @@ class IdentityRenderer {
5694
5709
  handlePasskeyPromptSkip(pendingResult) {
5695
5710
  // Set 30-day cookie to suppress future prompts
5696
5711
  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
5712
  // Complete the verification
5704
5713
  this.close();
5705
- this.callbacks.onSuccess(pendingResult);
5714
+ try {
5715
+ this.callbacks.onSuccess(pendingResult);
5716
+ }
5717
+ catch (err) {
5718
+ // eslint-disable-next-line no-console
5719
+ console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
5720
+ }
5721
+ if (typeof pendingResult.redirect === 'string' && pendingResult.redirect) {
5722
+ window.location.href = pendingResult.redirect;
5723
+ }
5706
5724
  }
5707
5725
  /**
5708
5726
  * Open a WebSocket connection and return the connectionId.
@@ -5804,12 +5822,29 @@ class IdentityRenderer {
5804
5822
  catch {
5805
5823
  return;
5806
5824
  }
5825
+ // Diagnostic: dump the parsed message so we can see on the client
5826
+ // EXACTLY what arrives. Keys + redirect prefix tell us whether the
5827
+ // field made it across API Gateway. Remove once we've confirmed.
5828
+ // eslint-disable-next-line no-console
5829
+ console.info('[SparkVault SDK] sparklink WS message', {
5830
+ type: data.type,
5831
+ keys: Object.keys(data),
5832
+ hasRedirect: typeof data.redirect === 'string',
5833
+ redirectPrefix: typeof data.redirect === 'string'
5834
+ ? data.redirect.slice(0, 80)
5835
+ : null,
5836
+ rawLength: event.data.length,
5837
+ });
5807
5838
  switch (data.type) {
5808
5839
  case 'sparklink_verified':
5809
5840
  this.handleSparkLinkVerified({
5810
5841
  token: data.token,
5811
5842
  identity: data.identity,
5812
5843
  identityType: data.identityType || 'email',
5844
+ // For simple-mode flows the server also emits a signed
5845
+ // success-callback URL; without it the SDK has nowhere to send
5846
+ // the user after WebSocket success.
5847
+ redirect: typeof data.redirect === 'string' ? data.redirect : undefined,
5813
5848
  });
5814
5849
  break;
5815
5850
  case 'sparklink_failed':
@@ -5838,17 +5873,18 @@ class IdentityRenderer {
5838
5873
  }
5839
5874
  /**
5840
5875
  * Handle successful SparkLink verification via WebSocket push.
5876
+ *
5877
+ * Always invokes the host callback with the full result (including any
5878
+ * redirect URL) before attempting top-level navigation. The host page
5879
+ * is the source of truth for what should happen after verification:
5880
+ * the SDK's window.location.href call is a fallback for hosts that
5881
+ * delegate navigation entirely to the SDK.
5841
5882
  */
5842
5883
  async handleSparkLinkVerified(result) {
5843
5884
  this.cleanupSparkLink();
5844
- // Handle redirect for OIDC/simple mode flows
5845
- if (result.redirect) {
5846
- this.close();
5847
- window.location.href = result.redirect;
5848
- return;
5849
- }
5850
- // Check if we should prompt for passkey registration
5851
- if (await this.shouldShowPasskeyPrompt()) {
5885
+ // Passkey upsell only when there's no redirect target once we have
5886
+ // a destination we go there immediately, no upsell.
5887
+ if (!result.redirect && await this.shouldShowPasskeyPrompt()) {
5852
5888
  this.setState({
5853
5889
  view: 'passkey-prompt',
5854
5890
  email: this.recipient,
@@ -5857,7 +5893,22 @@ class IdentityRenderer {
5857
5893
  return;
5858
5894
  }
5859
5895
  this.close();
5860
- this.callbacks.onSuccess(result);
5896
+ // Hand the full result (including redirect) to the host first so the
5897
+ // page-level handler can do app-specific work (logging, navigation,
5898
+ // etc.) before we steal the browsing context with a navigation.
5899
+ try {
5900
+ this.callbacks.onSuccess(result);
5901
+ }
5902
+ catch (err) {
5903
+ // eslint-disable-next-line no-console
5904
+ console.warn('[SparkVault SDK] onSuccess threw, continuing to redirect', err);
5905
+ }
5906
+ // SDK-side fallback navigation. If the host already navigated this is
5907
+ // a no-op (the document is already unloading); if not, this rescues
5908
+ // simple-mode flows whose host forgot to act on result.redirect.
5909
+ if (typeof result.redirect === 'string' && result.redirect) {
5910
+ window.location.href = result.redirect;
5911
+ }
5861
5912
  }
5862
5913
  /**
5863
5914
  * Clean up SparkLink WebSocket connection.