@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/README.md
CHANGED
|
@@ -16,7 +16,7 @@ The official SparkVault JavaScript SDK for browser applications. Add passwordles
|
|
|
16
16
|
- [Error Handling](#error-handling)
|
|
17
17
|
- [TypeScript Support](#typescript-support)
|
|
18
18
|
- [Browser Support](#browser-support)
|
|
19
|
-
- [
|
|
19
|
+
- [Build & Release](#build--release)
|
|
20
20
|
- [License](#license)
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
@@ -717,7 +717,7 @@ The SDK supports all modern browsers:
|
|
|
717
717
|
|
|
718
718
|
**Note:** Passkey authentication requires WebAuthn support. On unsupported browsers, passkey will not appear as an option — other methods will still work.
|
|
719
719
|
|
|
720
|
-
##
|
|
720
|
+
## Build & Release
|
|
721
721
|
|
|
722
722
|
### Setup
|
|
723
723
|
|
|
@@ -96,6 +96,11 @@ export declare class IdentityRenderer {
|
|
|
96
96
|
* Open a WebSocket connection and return the connectionId.
|
|
97
97
|
* The server echoes back the connectionId on init; this is used to route
|
|
98
98
|
* the SparkLink verification result back to this SDK instance.
|
|
99
|
+
*
|
|
100
|
+
* Returns:
|
|
101
|
+
* - { connectionId: string } on success
|
|
102
|
+
* - { error: 'csp' } if blocked by Content Security Policy
|
|
103
|
+
* - { error: 'connection' } for other connection failures
|
|
99
104
|
*/
|
|
100
105
|
private openSparkLinkWebSocket;
|
|
101
106
|
/**
|
package/dist/identity/utils.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { arrayBufferToBase64url, base64urlToArrayBuffer, base64urlToString, } fr
|
|
|
10
10
|
/**
|
|
11
11
|
* Extract the root (registrable) domain from a hostname for cross-subdomain cookies.
|
|
12
12
|
* Returns the domain prefixed with a dot (e.g., ".client.com") or null when
|
|
13
|
-
* the domain attribute should not be set (
|
|
13
|
+
* the domain attribute should not be set (loopback hosts, IP addresses).
|
|
14
14
|
*
|
|
15
15
|
* @param hostname - The hostname to extract the root domain from
|
|
16
16
|
* @returns Root domain with leading dot, or null
|
package/dist/sparkvault.cjs.js
CHANGED
|
@@ -565,13 +565,13 @@ const MULTI_PART_TLDS = new Set([
|
|
|
565
565
|
/**
|
|
566
566
|
* Extract the root (registrable) domain from a hostname for cross-subdomain cookies.
|
|
567
567
|
* Returns the domain prefixed with a dot (e.g., ".client.com") or null when
|
|
568
|
-
* the domain attribute should not be set (
|
|
568
|
+
* the domain attribute should not be set (loopback hosts, IP addresses).
|
|
569
569
|
*
|
|
570
570
|
* @param hostname - The hostname to extract the root domain from
|
|
571
571
|
* @returns Root domain with leading dot, or null
|
|
572
572
|
*/
|
|
573
573
|
function getRootDomain(hostname) {
|
|
574
|
-
//
|
|
574
|
+
// Loopback host — browsers reject domain attribute for localhost
|
|
575
575
|
if (hostname === 'localhost' || hostname === '127.0.0.1')
|
|
576
576
|
return null;
|
|
577
577
|
// IPv4 addresses
|
|
@@ -1422,10 +1422,6 @@ class PasskeyHandler {
|
|
|
1422
1422
|
if (hostname === 'sparkvault.com' || hostname.endsWith('.sparkvault.com')) {
|
|
1423
1423
|
return true;
|
|
1424
1424
|
}
|
|
1425
|
-
// Allow localhost for development
|
|
1426
|
-
if (hostname === 'localhost') {
|
|
1427
|
-
return true;
|
|
1428
|
-
}
|
|
1429
1425
|
return false;
|
|
1430
1426
|
}
|
|
1431
1427
|
catch {
|
|
@@ -4794,17 +4790,27 @@ class IdentityRenderer {
|
|
|
4794
4790
|
case 'sparklink': {
|
|
4795
4791
|
this.setState({ view: 'loading' });
|
|
4796
4792
|
// Open WebSocket and get connectionId before sending
|
|
4797
|
-
const
|
|
4798
|
-
if (
|
|
4793
|
+
const wsResult = await this.openSparkLinkWebSocket();
|
|
4794
|
+
if ('error' in wsResult) {
|
|
4799
4795
|
this.cleanupSparkLink();
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4796
|
+
if (wsResult.error === 'csp') {
|
|
4797
|
+
// CSP blocking WebSocket - provide developer-friendly error
|
|
4798
|
+
this.setState({
|
|
4799
|
+
view: 'error',
|
|
4800
|
+
message: 'Connection blocked by security policy. Please contact the site administrator to add wss://*.sparkvault.com to the Content-Security-Policy connect-src directive.',
|
|
4801
|
+
code: 'sparklink_csp_blocked',
|
|
4802
|
+
});
|
|
4803
|
+
}
|
|
4804
|
+
else {
|
|
4805
|
+
this.setState({
|
|
4806
|
+
view: 'error',
|
|
4807
|
+
message: 'Failed to establish connection',
|
|
4808
|
+
code: 'sparklink_ws_failed',
|
|
4809
|
+
});
|
|
4810
|
+
}
|
|
4805
4811
|
return;
|
|
4806
4812
|
}
|
|
4807
|
-
const result = await this.sparkLinkHandler.send(connectionId);
|
|
4813
|
+
const result = await this.sparkLinkHandler.send(wsResult.connectionId);
|
|
4808
4814
|
if (!result.success) {
|
|
4809
4815
|
this.cleanupSparkLink();
|
|
4810
4816
|
this.setState({
|
|
@@ -5107,6 +5113,11 @@ class IdentityRenderer {
|
|
|
5107
5113
|
* Open a WebSocket connection and return the connectionId.
|
|
5108
5114
|
* The server echoes back the connectionId on init; this is used to route
|
|
5109
5115
|
* the SparkLink verification result back to this SDK instance.
|
|
5116
|
+
*
|
|
5117
|
+
* Returns:
|
|
5118
|
+
* - { connectionId: string } on success
|
|
5119
|
+
* - { error: 'csp' } if blocked by Content Security Policy
|
|
5120
|
+
* - { error: 'connection' } for other connection failures
|
|
5110
5121
|
*/
|
|
5111
5122
|
openSparkLinkWebSocket() {
|
|
5112
5123
|
this.cleanupSparkLink();
|
|
@@ -5114,13 +5125,27 @@ class IdentityRenderer {
|
|
|
5114
5125
|
const wsUrl = `wss://${wsDomain}`;
|
|
5115
5126
|
return new Promise((resolve) => {
|
|
5116
5127
|
let resolved = false;
|
|
5128
|
+
let cspViolationDetected = false;
|
|
5129
|
+
// Listen for CSP violations to provide a more helpful error message
|
|
5130
|
+
const cspHandler = (event) => {
|
|
5131
|
+
// Check if this CSP violation is for our WebSocket connection
|
|
5132
|
+
if (event.violatedDirective === 'connect-src' &&
|
|
5133
|
+
event.blockedURI.includes(wsDomain)) {
|
|
5134
|
+
cspViolationDetected = true;
|
|
5135
|
+
}
|
|
5136
|
+
};
|
|
5137
|
+
document.addEventListener('securitypolicyviolation', cspHandler);
|
|
5138
|
+
const cleanup = () => {
|
|
5139
|
+
document.removeEventListener('securitypolicyviolation', cspHandler);
|
|
5140
|
+
};
|
|
5117
5141
|
const ws = new WebSocket(wsUrl);
|
|
5118
5142
|
this.sparkLinkWs = ws;
|
|
5119
5143
|
const timeout = setTimeout(() => {
|
|
5120
5144
|
if (!resolved) {
|
|
5121
5145
|
resolved = true;
|
|
5146
|
+
cleanup();
|
|
5122
5147
|
ws.close();
|
|
5123
|
-
resolve(
|
|
5148
|
+
resolve({ error: cspViolationDetected ? 'csp' : 'connection' });
|
|
5124
5149
|
}
|
|
5125
5150
|
}, 10000);
|
|
5126
5151
|
ws.onopen = () => {
|
|
@@ -5130,8 +5155,9 @@ class IdentityRenderer {
|
|
|
5130
5155
|
catch {
|
|
5131
5156
|
if (!resolved) {
|
|
5132
5157
|
resolved = true;
|
|
5158
|
+
cleanup();
|
|
5133
5159
|
clearTimeout(timeout);
|
|
5134
|
-
resolve(
|
|
5160
|
+
resolve({ error: 'connection' });
|
|
5135
5161
|
}
|
|
5136
5162
|
}
|
|
5137
5163
|
};
|
|
@@ -5145,24 +5171,27 @@ class IdentityRenderer {
|
|
|
5145
5171
|
}
|
|
5146
5172
|
if (!resolved && data.type === 'connection' && typeof data.connectionId === 'string') {
|
|
5147
5173
|
resolved = true;
|
|
5174
|
+
cleanup();
|
|
5148
5175
|
clearTimeout(timeout);
|
|
5149
5176
|
this.setupSparkLinkWsHandler(ws);
|
|
5150
|
-
resolve(data.connectionId);
|
|
5177
|
+
resolve({ connectionId: data.connectionId });
|
|
5151
5178
|
return;
|
|
5152
5179
|
}
|
|
5153
5180
|
};
|
|
5154
5181
|
ws.onerror = () => {
|
|
5155
5182
|
if (!resolved) {
|
|
5156
5183
|
resolved = true;
|
|
5184
|
+
cleanup();
|
|
5157
5185
|
clearTimeout(timeout);
|
|
5158
|
-
resolve(
|
|
5186
|
+
resolve({ error: cspViolationDetected ? 'csp' : 'connection' });
|
|
5159
5187
|
}
|
|
5160
5188
|
};
|
|
5161
5189
|
ws.onclose = () => {
|
|
5162
5190
|
if (!resolved) {
|
|
5163
5191
|
resolved = true;
|
|
5192
|
+
cleanup();
|
|
5164
5193
|
clearTimeout(timeout);
|
|
5165
|
-
resolve(
|
|
5194
|
+
resolve({ error: cspViolationDetected ? 'csp' : 'connection' });
|
|
5166
5195
|
}
|
|
5167
5196
|
};
|
|
5168
5197
|
});
|
|
@@ -5252,17 +5281,26 @@ class IdentityRenderer {
|
|
|
5252
5281
|
*/
|
|
5253
5282
|
async handleSparkLinkResend() {
|
|
5254
5283
|
// Open new WebSocket for the resend
|
|
5255
|
-
const
|
|
5256
|
-
if (
|
|
5284
|
+
const wsResult = await this.openSparkLinkWebSocket();
|
|
5285
|
+
if ('error' in wsResult) {
|
|
5257
5286
|
this.cleanupSparkLink();
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5287
|
+
if (wsResult.error === 'csp') {
|
|
5288
|
+
this.setState({
|
|
5289
|
+
view: 'error',
|
|
5290
|
+
message: 'Connection blocked by security policy. Please contact the site administrator to add wss://*.sparkvault.com to the Content-Security-Policy connect-src directive.',
|
|
5291
|
+
code: 'sparklink_csp_blocked',
|
|
5292
|
+
});
|
|
5293
|
+
}
|
|
5294
|
+
else {
|
|
5295
|
+
this.setState({
|
|
5296
|
+
view: 'error',
|
|
5297
|
+
message: 'Unable to establish a secure connection. Please try again or choose a different method.',
|
|
5298
|
+
code: 'sparklink_ws_failed',
|
|
5299
|
+
});
|
|
5300
|
+
}
|
|
5263
5301
|
return;
|
|
5264
5302
|
}
|
|
5265
|
-
const result = await this.sparkLinkHandler.send(connectionId);
|
|
5303
|
+
const result = await this.sparkLinkHandler.send(wsResult.connectionId);
|
|
5266
5304
|
if (result.success) {
|
|
5267
5305
|
this.setState({
|
|
5268
5306
|
view: 'sparklink-waiting',
|