@sparkvault/sdk 1.21.9 → 1.21.10
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/README.md +2 -2
- package/dist/identity/renderer.d.ts +5 -0
- package/dist/identity/utils.d.ts +1 -1
- package/dist/sparkvault.cjs.js +65 -27
- package/dist/sparkvault.cjs.js.map +1 -1
- package/dist/sparkvault.esm.js +65 -27
- 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
|
@@ -561,13 +561,13 @@ const MULTI_PART_TLDS = new Set([
|
|
|
561
561
|
/**
|
|
562
562
|
* Extract the root (registrable) domain from a hostname for cross-subdomain cookies.
|
|
563
563
|
* Returns the domain prefixed with a dot (e.g., ".client.com") or null when
|
|
564
|
-
* the domain attribute should not be set (
|
|
564
|
+
* the domain attribute should not be set (loopback hosts, IP addresses).
|
|
565
565
|
*
|
|
566
566
|
* @param hostname - The hostname to extract the root domain from
|
|
567
567
|
* @returns Root domain with leading dot, or null
|
|
568
568
|
*/
|
|
569
569
|
function getRootDomain(hostname) {
|
|
570
|
-
//
|
|
570
|
+
// Loopback host — browsers reject domain attribute for localhost
|
|
571
571
|
if (hostname === 'localhost' || hostname === '127.0.0.1')
|
|
572
572
|
return null;
|
|
573
573
|
// IPv4 addresses
|
|
@@ -1418,10 +1418,6 @@ class PasskeyHandler {
|
|
|
1418
1418
|
if (hostname === 'sparkvault.com' || hostname.endsWith('.sparkvault.com')) {
|
|
1419
1419
|
return true;
|
|
1420
1420
|
}
|
|
1421
|
-
// Allow localhost for development
|
|
1422
|
-
if (hostname === 'localhost') {
|
|
1423
|
-
return true;
|
|
1424
|
-
}
|
|
1425
1421
|
return false;
|
|
1426
1422
|
}
|
|
1427
1423
|
catch {
|
|
@@ -4790,17 +4786,27 @@ class IdentityRenderer {
|
|
|
4790
4786
|
case 'sparklink': {
|
|
4791
4787
|
this.setState({ view: 'loading' });
|
|
4792
4788
|
// Open WebSocket and get connectionId before sending
|
|
4793
|
-
const
|
|
4794
|
-
if (
|
|
4789
|
+
const wsResult = await this.openSparkLinkWebSocket();
|
|
4790
|
+
if ('error' in wsResult) {
|
|
4795
4791
|
this.cleanupSparkLink();
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4792
|
+
if (wsResult.error === 'csp') {
|
|
4793
|
+
// CSP blocking WebSocket - provide developer-friendly error
|
|
4794
|
+
this.setState({
|
|
4795
|
+
view: 'error',
|
|
4796
|
+
message: 'Connection blocked by security policy. Please contact the site administrator to add wss://*.sparkvault.com to the Content-Security-Policy connect-src directive.',
|
|
4797
|
+
code: 'sparklink_csp_blocked',
|
|
4798
|
+
});
|
|
4799
|
+
}
|
|
4800
|
+
else {
|
|
4801
|
+
this.setState({
|
|
4802
|
+
view: 'error',
|
|
4803
|
+
message: 'Failed to establish connection',
|
|
4804
|
+
code: 'sparklink_ws_failed',
|
|
4805
|
+
});
|
|
4806
|
+
}
|
|
4801
4807
|
return;
|
|
4802
4808
|
}
|
|
4803
|
-
const result = await this.sparkLinkHandler.send(connectionId);
|
|
4809
|
+
const result = await this.sparkLinkHandler.send(wsResult.connectionId);
|
|
4804
4810
|
if (!result.success) {
|
|
4805
4811
|
this.cleanupSparkLink();
|
|
4806
4812
|
this.setState({
|
|
@@ -5103,6 +5109,11 @@ class IdentityRenderer {
|
|
|
5103
5109
|
* Open a WebSocket connection and return the connectionId.
|
|
5104
5110
|
* The server echoes back the connectionId on init; this is used to route
|
|
5105
5111
|
* the SparkLink verification result back to this SDK instance.
|
|
5112
|
+
*
|
|
5113
|
+
* Returns:
|
|
5114
|
+
* - { connectionId: string } on success
|
|
5115
|
+
* - { error: 'csp' } if blocked by Content Security Policy
|
|
5116
|
+
* - { error: 'connection' } for other connection failures
|
|
5106
5117
|
*/
|
|
5107
5118
|
openSparkLinkWebSocket() {
|
|
5108
5119
|
this.cleanupSparkLink();
|
|
@@ -5110,13 +5121,27 @@ class IdentityRenderer {
|
|
|
5110
5121
|
const wsUrl = `wss://${wsDomain}`;
|
|
5111
5122
|
return new Promise((resolve) => {
|
|
5112
5123
|
let resolved = false;
|
|
5124
|
+
let cspViolationDetected = false;
|
|
5125
|
+
// Listen for CSP violations to provide a more helpful error message
|
|
5126
|
+
const cspHandler = (event) => {
|
|
5127
|
+
// Check if this CSP violation is for our WebSocket connection
|
|
5128
|
+
if (event.violatedDirective === 'connect-src' &&
|
|
5129
|
+
event.blockedURI.includes(wsDomain)) {
|
|
5130
|
+
cspViolationDetected = true;
|
|
5131
|
+
}
|
|
5132
|
+
};
|
|
5133
|
+
document.addEventListener('securitypolicyviolation', cspHandler);
|
|
5134
|
+
const cleanup = () => {
|
|
5135
|
+
document.removeEventListener('securitypolicyviolation', cspHandler);
|
|
5136
|
+
};
|
|
5113
5137
|
const ws = new WebSocket(wsUrl);
|
|
5114
5138
|
this.sparkLinkWs = ws;
|
|
5115
5139
|
const timeout = setTimeout(() => {
|
|
5116
5140
|
if (!resolved) {
|
|
5117
5141
|
resolved = true;
|
|
5142
|
+
cleanup();
|
|
5118
5143
|
ws.close();
|
|
5119
|
-
resolve(
|
|
5144
|
+
resolve({ error: cspViolationDetected ? 'csp' : 'connection' });
|
|
5120
5145
|
}
|
|
5121
5146
|
}, 10000);
|
|
5122
5147
|
ws.onopen = () => {
|
|
@@ -5126,8 +5151,9 @@ class IdentityRenderer {
|
|
|
5126
5151
|
catch {
|
|
5127
5152
|
if (!resolved) {
|
|
5128
5153
|
resolved = true;
|
|
5154
|
+
cleanup();
|
|
5129
5155
|
clearTimeout(timeout);
|
|
5130
|
-
resolve(
|
|
5156
|
+
resolve({ error: 'connection' });
|
|
5131
5157
|
}
|
|
5132
5158
|
}
|
|
5133
5159
|
};
|
|
@@ -5141,24 +5167,27 @@ class IdentityRenderer {
|
|
|
5141
5167
|
}
|
|
5142
5168
|
if (!resolved && data.type === 'connection' && typeof data.connectionId === 'string') {
|
|
5143
5169
|
resolved = true;
|
|
5170
|
+
cleanup();
|
|
5144
5171
|
clearTimeout(timeout);
|
|
5145
5172
|
this.setupSparkLinkWsHandler(ws);
|
|
5146
|
-
resolve(data.connectionId);
|
|
5173
|
+
resolve({ connectionId: data.connectionId });
|
|
5147
5174
|
return;
|
|
5148
5175
|
}
|
|
5149
5176
|
};
|
|
5150
5177
|
ws.onerror = () => {
|
|
5151
5178
|
if (!resolved) {
|
|
5152
5179
|
resolved = true;
|
|
5180
|
+
cleanup();
|
|
5153
5181
|
clearTimeout(timeout);
|
|
5154
|
-
resolve(
|
|
5182
|
+
resolve({ error: cspViolationDetected ? 'csp' : 'connection' });
|
|
5155
5183
|
}
|
|
5156
5184
|
};
|
|
5157
5185
|
ws.onclose = () => {
|
|
5158
5186
|
if (!resolved) {
|
|
5159
5187
|
resolved = true;
|
|
5188
|
+
cleanup();
|
|
5160
5189
|
clearTimeout(timeout);
|
|
5161
|
-
resolve(
|
|
5190
|
+
resolve({ error: cspViolationDetected ? 'csp' : 'connection' });
|
|
5162
5191
|
}
|
|
5163
5192
|
};
|
|
5164
5193
|
});
|
|
@@ -5248,17 +5277,26 @@ class IdentityRenderer {
|
|
|
5248
5277
|
*/
|
|
5249
5278
|
async handleSparkLinkResend() {
|
|
5250
5279
|
// Open new WebSocket for the resend
|
|
5251
|
-
const
|
|
5252
|
-
if (
|
|
5280
|
+
const wsResult = await this.openSparkLinkWebSocket();
|
|
5281
|
+
if ('error' in wsResult) {
|
|
5253
5282
|
this.cleanupSparkLink();
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5283
|
+
if (wsResult.error === 'csp') {
|
|
5284
|
+
this.setState({
|
|
5285
|
+
view: 'error',
|
|
5286
|
+
message: 'Connection blocked by security policy. Please contact the site administrator to add wss://*.sparkvault.com to the Content-Security-Policy connect-src directive.',
|
|
5287
|
+
code: 'sparklink_csp_blocked',
|
|
5288
|
+
});
|
|
5289
|
+
}
|
|
5290
|
+
else {
|
|
5291
|
+
this.setState({
|
|
5292
|
+
view: 'error',
|
|
5293
|
+
message: 'Unable to establish a secure connection. Please try again or choose a different method.',
|
|
5294
|
+
code: 'sparklink_ws_failed',
|
|
5295
|
+
});
|
|
5296
|
+
}
|
|
5259
5297
|
return;
|
|
5260
5298
|
}
|
|
5261
|
-
const result = await this.sparkLinkHandler.send(connectionId);
|
|
5299
|
+
const result = await this.sparkLinkHandler.send(wsResult.connectionId);
|
|
5262
5300
|
if (result.success) {
|
|
5263
5301
|
this.setState({
|
|
5264
5302
|
view: 'sparklink-waiting',
|