@soliton-id/web-sdk 1.0.0
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/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +180 -0
- package/dist/hostedUrl.d.ts +17 -0
- package/dist/index.cjs +259 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.mjs +237 -0
- package/dist/overlay.d.ts +17 -0
- package/dist/types.d.ts +69 -0
- package/package.json +51 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@soliton-id/web-sdk`. The `soliton-v1` postMessage
|
|
4
|
+
protocol is frozen: changes are additive only, so older SDK versions keep
|
|
5
|
+
working against the hosted flow.
|
|
6
|
+
|
|
7
|
+
## 1.0.0 — 2026-08-01
|
|
8
|
+
|
|
9
|
+
First public release.
|
|
10
|
+
|
|
11
|
+
- `open({ url, onComplete, onError, onClose, locale })` opens a verification
|
|
12
|
+
(minted server-side via `POST /api/v1/verifications`) in a full-viewport
|
|
13
|
+
overlay iframe and returns a handle with `close()`.
|
|
14
|
+
- Ships ESM + CJS + TypeScript declarations; also available from the hosted
|
|
15
|
+
classic and cross-origin ESM bundles under `https://soliton.id/sdk/v1/`.
|
|
16
|
+
- Refuses to frame attacker-controlled hosts: verification URLs are pinned to
|
|
17
|
+
the production/staging Soliton origins, with explicit HTTP localhost
|
|
18
|
+
exceptions for development.
|
|
19
|
+
- Callbacks carry lifecycle only; verdicts are only ever released
|
|
20
|
+
server-to-server against your API key.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Soliton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# @soliton-id/web-sdk
|
|
2
|
+
|
|
3
|
+
Soliton identity verification for the browser. `open()` overlays the Soliton
|
|
4
|
+
hosted verification flow on your page in a full-viewport iframe, so your user
|
|
5
|
+
never leaves your site, and reports lifecycle events back to your code. The
|
|
6
|
+
capture engine itself always runs on soliton.id inside the iframe, which is
|
|
7
|
+
why anti-injection and anti-deepfake defenses update continuously without you
|
|
8
|
+
shipping anything.
|
|
9
|
+
|
|
10
|
+
Web SDK guide (this package): <https://soliton.id/docs/integration/sdk>
|
|
11
|
+
Server API guide (create, verdicts, portraits):
|
|
12
|
+
<https://soliton.id/docs/integration/api>
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @soliton-id/web-sdk
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
No runtime dependencies, no framework requirements, ~7 KB unminified.
|
|
21
|
+
|
|
22
|
+
Prefer a script tag? The same API ships as a hosted bundle that updates with
|
|
23
|
+
every Soliton deploy:
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<script src="https://soliton.id/sdk/v1/soliton-web-sdk.js"></script>
|
|
27
|
+
<!-- exposes the global SolitonVerify; SolitonVerify.open({...}) -->
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
A cross-origin-enabled hosted ESM build is also available for direct browser
|
|
31
|
+
imports at
|
|
32
|
+
`https://soliton.id/sdk/v1/soliton-web-sdk.esm.js`. If you use either hosted
|
|
33
|
+
build, allow `https://soliton.id` in your page's `script-src`; an app that
|
|
34
|
+
bundles the npm package does not need that remote script source.
|
|
35
|
+
|
|
36
|
+
## Quickstart
|
|
37
|
+
|
|
38
|
+
Your backend creates a verification with your secret API key (never do this
|
|
39
|
+
from the browser), hands the returned `url` to your page, and your page opens
|
|
40
|
+
it:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { open } from "@soliton-id/web-sdk";
|
|
44
|
+
// also available: import SolitonVerify from "@soliton-id/web-sdk";
|
|
45
|
+
// SolitonVerify.open({...})
|
|
46
|
+
|
|
47
|
+
// Fetch this from YOUR backend, which called POST /api/v1/verifications.
|
|
48
|
+
const { url } = await fetch("/your-api/start-verification").then(r => r.json());
|
|
49
|
+
|
|
50
|
+
const handle = open({
|
|
51
|
+
url,
|
|
52
|
+
onComplete: ({ verificationId }) => {
|
|
53
|
+
// The flow reached a terminal state (pass OR final fail; deliberately
|
|
54
|
+
// not disclosed here). Treat this purely as a "check now" signal and
|
|
55
|
+
// have YOUR BACKEND fetch the result. See "Getting the result" below.
|
|
56
|
+
},
|
|
57
|
+
onError: ({ code, message }) => {
|
|
58
|
+
// link_expired | link_invalid | origin_not_allowed |
|
|
59
|
+
// storage_unavailable | internal
|
|
60
|
+
},
|
|
61
|
+
onClose: () => {
|
|
62
|
+
// The user dismissed the flow before completion.
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// handle.close() dismisses the overlay programmatically.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The overlay is always full-viewport: the flow runs a screen-light challenge
|
|
70
|
+
that needs the whole screen, so it cannot be clipped into a widget.
|
|
71
|
+
|
|
72
|
+
## Callback semantics
|
|
73
|
+
|
|
74
|
+
- `onComplete({ verificationId })`: fired once, when the user reaches a
|
|
75
|
+
terminal state. It carries ONLY the verification id, never a verdict.
|
|
76
|
+
- `onError({ code, message })`: flow-level errors (`link_expired`,
|
|
77
|
+
`link_invalid`, `origin_not_allowed`, `storage_unavailable` when the user's
|
|
78
|
+
browser blocks the session storage the flow needs, `internal`). The overlay
|
|
79
|
+
stays open so the hosted page can show its own error card; call
|
|
80
|
+
`handle.close()` if you want it gone.
|
|
81
|
+
- `onClose()`: the user dismissed the flow (the X button, the hosted Cancel,
|
|
82
|
+
or `handle.close()`) before completion. Not fired after `onComplete`.
|
|
83
|
+
|
|
84
|
+
## Getting the result (server-to-server only)
|
|
85
|
+
|
|
86
|
+
`onComplete` gives you an id. Everything sensitive is then fetched by YOUR
|
|
87
|
+
BACKEND with your API key:
|
|
88
|
+
|
|
89
|
+
1. `GET /api/v1/verifications/{id}`: the verdict (`verified`,
|
|
90
|
+
`partial_verification`, plus `portrait_available`).
|
|
91
|
+
2. `GET /api/v1/verifications/{id}/portrait`: a JPEG portrait of the verified
|
|
92
|
+
user, selected server-side from the capture under neutral lighting.
|
|
93
|
+
Returns 404 until the verification is complete or when no acceptable
|
|
94
|
+
portrait frame existed. Right after completion, `portrait_available` can
|
|
95
|
+
briefly read `false` while extraction finishes; once status is `complete`
|
|
96
|
+
just fetch the portrait and treat a 404 after one short retry as final.
|
|
97
|
+
|
|
98
|
+
Your page and the SDK never see the verdict or the portrait: callbacks and
|
|
99
|
+
postMessage payloads carry only the verification id, and both results are
|
|
100
|
+
released exclusively to your backend against your API key. The hosted screen
|
|
101
|
+
does tell the end user how their own attempt went, with no detail about which
|
|
102
|
+
checks ran or why, but that screen is for the user, not for your integration:
|
|
103
|
+
the browser side (your user, your page, their extensions) is untrusted by
|
|
104
|
+
design, and any machine-readable pass/fail surfaced to your page would hand
|
|
105
|
+
fraudsters an oracle for tuning attacks. Your backend is the only place the
|
|
106
|
+
result exists for you, which is also what makes it trustworthy.
|
|
107
|
+
|
|
108
|
+
### Bind the verification to your user
|
|
109
|
+
|
|
110
|
+
You already know the `verificationId` when you create the verification: store
|
|
111
|
+
it server-side against your authenticated user or session at that moment.
|
|
112
|
+
When `onComplete` fires, have your backend fetch the id it stored, never the
|
|
113
|
+
id from the callback. An end user controls everything in their browser,
|
|
114
|
+
including that callback value, and could substitute a different id, such as
|
|
115
|
+
one that previously passed. Comparing the callback id against your stored one
|
|
116
|
+
is fine; using it as the lookup key is not. Set `client_reference_id` at
|
|
117
|
+
creation and check it on every status read to make this auditable.
|
|
118
|
+
|
|
119
|
+
## Frame-origin registration
|
|
120
|
+
|
|
121
|
+
Embedding is off by default and gated on BOTH layers:
|
|
122
|
+
|
|
123
|
+
1. Your API key must have the exact origins of the pages that will embed the
|
|
124
|
+
flow registered (scheme + host + port, e.g. `https://app.example.com`).
|
|
125
|
+
Ask us to register yours.
|
|
126
|
+
2. The same origins must be present in the soliton.id deploy-time frame
|
|
127
|
+
ancestors allowlist (we handle this when we register your key).
|
|
128
|
+
|
|
129
|
+
A verification link opened inside an iframe on any other origin refuses to
|
|
130
|
+
start and reports `origin_not_allowed`.
|
|
131
|
+
|
|
132
|
+
During the invite-only pilot, Soliton manually manages both layers. Send your
|
|
133
|
+
Soliton contact every exact production, staging, and local origin before
|
|
134
|
+
testing. Wildcards are not accepted, and adding or changing an origin can
|
|
135
|
+
require a Soliton deployment.
|
|
136
|
+
|
|
137
|
+
## Customer security headers
|
|
138
|
+
|
|
139
|
+
If your embedding page sends restrictive response headers, merge these
|
|
140
|
+
sources into its existing policies (do not replace the rest of your policy
|
|
141
|
+
with this example):
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
Content-Security-Policy: frame-src https://soliton.id https://staging.soliton.id; script-src https://soliton.id https://staging.soliton.id
|
|
145
|
+
Permissions-Policy: camera=(self "https://soliton.id" "https://staging.soliton.id"), microphone=(self "https://soliton.id" "https://staging.soliton.id")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- `frame-src` must include the Soliton origin you use because the capture flow
|
|
149
|
+
runs in a Soliton iframe.
|
|
150
|
+
- `script-src` must include the Soliton origin you use only for the classic
|
|
151
|
+
script or hosted ESM distribution. It is not needed when your application
|
|
152
|
+
bundles the npm package.
|
|
153
|
+
- If your top-level `Permissions-Policy` explicitly restricts camera or
|
|
154
|
+
microphone, delegate both features to the production and/or staging Soliton
|
|
155
|
+
origin you use. The SDK already sets the matching
|
|
156
|
+
`allow="camera; microphone"` attribute on its iframe.
|
|
157
|
+
|
|
158
|
+
For local development, add the exact localhost origin (including its port) to
|
|
159
|
+
the corresponding directives as well.
|
|
160
|
+
|
|
161
|
+
## Pilot test keys
|
|
162
|
+
|
|
163
|
+
`sk_test_` identifies a pilot credential; it is not an isolated or simulated
|
|
164
|
+
sandbox. Test keys use the same hosted capture, biometric processing, and data
|
|
165
|
+
handling as live keys. The prefix does not create synthetic results or trigger
|
|
166
|
+
automatic deletion. Use only informed, consenting pilot participants, give
|
|
167
|
+
them your applicable privacy notice, and follow your Soliton pilot terms.
|
|
168
|
+
|
|
169
|
+
## How it works
|
|
170
|
+
|
|
171
|
+
`open()` accepts hosted URLs only from `https://soliton.id` or
|
|
172
|
+
`https://staging.soliton.id` (plus HTTP localhost/127.0.0.1 development URLs),
|
|
173
|
+
requires a `/v/{token}` path, and preserves query parameters. It renders a
|
|
174
|
+
full-viewport overlay with an iframe pointing at that validated origin and
|
|
175
|
+
listens for the hosted page's lifecycle messages. Every message is checked
|
|
176
|
+
three ways (origin, source window, protocol marker) before it reaches your
|
|
177
|
+
callbacks, and unknown message types are ignored, so wrapper versions stay
|
|
178
|
+
compatible as the hosted flow evolves. Redirect-style integration (no SDK,
|
|
179
|
+
we redirect the user back to you) remains fully supported; the same
|
|
180
|
+
verification URL works in both modes.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation of the hosted verification URL handed to SolitonVerify.open().
|
|
3
|
+
*
|
|
4
|
+
* The SDK only ever frames URLs that look exactly like the ones minted by
|
|
5
|
+
* POST /api/v1/verifications: an approved Soliton origin (http strictly for
|
|
6
|
+
* localhost dev) and a bare /v/{32 hex} path. Everything else throws
|
|
7
|
+
* synchronously — a bad URL is an integration bug, not a runtime flow event,
|
|
8
|
+
* so it must fail loudly at the call site instead of rendering an
|
|
9
|
+
* attacker-chosen page in our overlay.
|
|
10
|
+
*/
|
|
11
|
+
export interface ParsedHostedUrl {
|
|
12
|
+
/** The validated URL, normalized by the URL parser. */
|
|
13
|
+
url: URL;
|
|
14
|
+
/** Origin the SDK will trust for postMessage events. */
|
|
15
|
+
hostedOrigin: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function parseHostedUrl(raw: string): ParsedHostedUrl;
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/* Soliton Web SDK v1. Built from sdk/src via `npm run build:sdk`; do not edit by hand. */
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// sdk/src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
SolitonVerify: () => SolitonVerify,
|
|
25
|
+
default: () => index_default,
|
|
26
|
+
open: () => open
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// sdk/src/hostedUrl.ts
|
|
31
|
+
var HOSTED_PATH_RE = /^\/v\/[0-9a-f]{32}$/;
|
|
32
|
+
var ALLOWED_HTTPS_ORIGINS = /* @__PURE__ */ new Set([
|
|
33
|
+
"https://soliton.id",
|
|
34
|
+
"https://staging.soliton.id"
|
|
35
|
+
]);
|
|
36
|
+
function parseHostedUrl(raw) {
|
|
37
|
+
let url;
|
|
38
|
+
try {
|
|
39
|
+
url = new URL(raw);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
throw new Error(`SolitonVerify: "url" is not a valid absolute URL`);
|
|
42
|
+
}
|
|
43
|
+
const isLocalHttp = url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1");
|
|
44
|
+
if (!ALLOWED_HTTPS_ORIGINS.has(url.origin) && !isLocalHttp) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
'SolitonVerify: "url" must be hosted on https://soliton.id or https://staging.soliton.id (http is allowed for localhost/127.0.0.1 development only)'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (url.username || url.password) {
|
|
50
|
+
throw new Error('SolitonVerify: "url" must not embed credentials');
|
|
51
|
+
}
|
|
52
|
+
if (!HOSTED_PATH_RE.test(url.pathname)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'SolitonVerify: "url" does not look like a hosted verification URL (/v/{token})'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return { url, hostedOrigin: url.origin };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// sdk/src/overlay.ts
|
|
61
|
+
var MAX_Z_INDEX = "2147483647";
|
|
62
|
+
var openOverlayCount = 0;
|
|
63
|
+
var savedBodyOverflow = "";
|
|
64
|
+
function createOverlay(iframeSrc, onCloseClick) {
|
|
65
|
+
const doc = document;
|
|
66
|
+
const container = doc.createElement("div");
|
|
67
|
+
container.setAttribute("data-soliton-sdk", "overlay");
|
|
68
|
+
container.style.cssText = [
|
|
69
|
+
"position:fixed",
|
|
70
|
+
"inset:0",
|
|
71
|
+
`z-index:${MAX_Z_INDEX}`,
|
|
72
|
+
"background:#0f110b",
|
|
73
|
+
"display:block"
|
|
74
|
+
].join(";");
|
|
75
|
+
const style = doc.createElement("style");
|
|
76
|
+
style.textContent = "@keyframes soliton-sdk-spin{to{transform:rotate(360deg)}}";
|
|
77
|
+
container.appendChild(style);
|
|
78
|
+
const loading = doc.createElement("div");
|
|
79
|
+
loading.setAttribute("data-soliton-sdk", "loading");
|
|
80
|
+
loading.style.cssText = [
|
|
81
|
+
"position:absolute",
|
|
82
|
+
"inset:0",
|
|
83
|
+
"display:flex",
|
|
84
|
+
"flex-direction:column",
|
|
85
|
+
"align-items:center",
|
|
86
|
+
"justify-content:center",
|
|
87
|
+
"gap:14px",
|
|
88
|
+
"color:rgba(255,255,255,0.75)",
|
|
89
|
+
"font:14px/1.4 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif"
|
|
90
|
+
].join(";");
|
|
91
|
+
const spinner = doc.createElement("div");
|
|
92
|
+
spinner.style.cssText = [
|
|
93
|
+
"width:22px",
|
|
94
|
+
"height:22px",
|
|
95
|
+
"border-radius:999px",
|
|
96
|
+
"border:2px solid rgba(255,255,255,0.25)",
|
|
97
|
+
"border-top-color:rgba(255,255,255,0.9)",
|
|
98
|
+
"animation:soliton-sdk-spin 0.8s linear infinite"
|
|
99
|
+
].join(";");
|
|
100
|
+
const loadingText = doc.createElement("div");
|
|
101
|
+
loadingText.textContent = "Loading secure verification...";
|
|
102
|
+
loading.appendChild(spinner);
|
|
103
|
+
loading.appendChild(loadingText);
|
|
104
|
+
container.appendChild(loading);
|
|
105
|
+
const iframe = doc.createElement("iframe");
|
|
106
|
+
iframe.setAttribute("allow", "camera; microphone; fullscreen; autoplay");
|
|
107
|
+
iframe.setAttribute("title", "Soliton identity verification");
|
|
108
|
+
iframe.style.cssText = [
|
|
109
|
+
"position:absolute",
|
|
110
|
+
"inset:0",
|
|
111
|
+
"width:100%",
|
|
112
|
+
"height:100%",
|
|
113
|
+
"border:0",
|
|
114
|
+
"display:block"
|
|
115
|
+
].join(";");
|
|
116
|
+
iframe.src = iframeSrc;
|
|
117
|
+
container.appendChild(iframe);
|
|
118
|
+
const closeButton = doc.createElement("button");
|
|
119
|
+
closeButton.type = "button";
|
|
120
|
+
closeButton.setAttribute("aria-label", "Close verification");
|
|
121
|
+
closeButton.textContent = "\xD7";
|
|
122
|
+
closeButton.style.cssText = [
|
|
123
|
+
"position:absolute",
|
|
124
|
+
"top:14px",
|
|
125
|
+
"right:14px",
|
|
126
|
+
"width:36px",
|
|
127
|
+
"height:36px",
|
|
128
|
+
"border-radius:999px",
|
|
129
|
+
"border:1px solid rgba(255,255,255,0.3)",
|
|
130
|
+
"background:rgba(0,0,0,0.45)",
|
|
131
|
+
"color:rgba(255,255,255,0.85)",
|
|
132
|
+
"font:20px/1 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif",
|
|
133
|
+
"cursor:pointer",
|
|
134
|
+
"padding:0"
|
|
135
|
+
].join(";");
|
|
136
|
+
closeButton.addEventListener("click", onCloseClick);
|
|
137
|
+
container.appendChild(closeButton);
|
|
138
|
+
if (openOverlayCount === 0) {
|
|
139
|
+
savedBodyOverflow = doc.body.style.overflow;
|
|
140
|
+
}
|
|
141
|
+
openOverlayCount += 1;
|
|
142
|
+
doc.body.style.overflow = "hidden";
|
|
143
|
+
doc.body.appendChild(container);
|
|
144
|
+
let destroyed = false;
|
|
145
|
+
return {
|
|
146
|
+
iframe,
|
|
147
|
+
markReady() {
|
|
148
|
+
loading.style.display = "none";
|
|
149
|
+
},
|
|
150
|
+
destroy() {
|
|
151
|
+
if (destroyed) return;
|
|
152
|
+
destroyed = true;
|
|
153
|
+
openOverlayCount -= 1;
|
|
154
|
+
if (openOverlayCount === 0) {
|
|
155
|
+
doc.body.style.overflow = savedBodyOverflow;
|
|
156
|
+
}
|
|
157
|
+
if (container.parentNode) container.parentNode.removeChild(container);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// sdk/src/types.ts
|
|
163
|
+
var EMBED_PROTOCOL = "soliton-v1";
|
|
164
|
+
|
|
165
|
+
// sdk/src/index.ts
|
|
166
|
+
var AUTO_CLOSE_DELAY_MS = 250;
|
|
167
|
+
var HELLO_INTERVAL_MS = 250;
|
|
168
|
+
var HELLO_MAX_PINGS = 40;
|
|
169
|
+
function open(options) {
|
|
170
|
+
if (!options || typeof options.url !== "string") {
|
|
171
|
+
throw new Error('SolitonVerify: open() requires an options object with a "url"');
|
|
172
|
+
}
|
|
173
|
+
if (typeof options.onComplete !== "function") {
|
|
174
|
+
throw new Error("SolitonVerify: open() requires an onComplete callback");
|
|
175
|
+
}
|
|
176
|
+
const { url, hostedOrigin } = parseHostedUrl(options.url);
|
|
177
|
+
const src = new URL(url.toString());
|
|
178
|
+
src.searchParams.set("mode", "embed");
|
|
179
|
+
src.searchParams.set("sdk", "1");
|
|
180
|
+
if (options.locale) src.searchParams.set("locale", options.locale);
|
|
181
|
+
let closed = false;
|
|
182
|
+
let terminalDelivered = false;
|
|
183
|
+
let autoCloseTimer = null;
|
|
184
|
+
const overlay = createOverlay(src.toString(), () => close());
|
|
185
|
+
let helloPings = 0;
|
|
186
|
+
const postHello = () => {
|
|
187
|
+
var _a;
|
|
188
|
+
helloPings += 1;
|
|
189
|
+
if (helloPings > HELLO_MAX_PINGS) {
|
|
190
|
+
stopHello();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
(_a = overlay.iframe.contentWindow) == null ? void 0 : _a.postMessage(
|
|
195
|
+
{ protocol: EMBED_PROTOCOL, type: "soliton:hello" },
|
|
196
|
+
hostedOrigin
|
|
197
|
+
);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const helloTimer = setInterval(postHello, HELLO_INTERVAL_MS);
|
|
202
|
+
const onIframeLoad = () => postHello();
|
|
203
|
+
overlay.iframe.addEventListener("load", onIframeLoad);
|
|
204
|
+
function stopHello() {
|
|
205
|
+
clearInterval(helloTimer);
|
|
206
|
+
overlay.iframe.removeEventListener("load", onIframeLoad);
|
|
207
|
+
}
|
|
208
|
+
const onMessage = (event) => {
|
|
209
|
+
var _a;
|
|
210
|
+
if (event.origin !== hostedOrigin) return;
|
|
211
|
+
if (!overlay.iframe.contentWindow || event.source !== overlay.iframe.contentWindow) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const data = event.data;
|
|
215
|
+
if (!data || typeof data !== "object" || data.protocol !== EMBED_PROTOCOL) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
stopHello();
|
|
219
|
+
switch (data.type) {
|
|
220
|
+
case "soliton:ready":
|
|
221
|
+
overlay.markReady();
|
|
222
|
+
break;
|
|
223
|
+
case "soliton:complete": {
|
|
224
|
+
if (terminalDelivered) break;
|
|
225
|
+
terminalDelivered = true;
|
|
226
|
+
autoCloseTimer = setTimeout(() => close(), AUTO_CLOSE_DELAY_MS);
|
|
227
|
+
options.onComplete({
|
|
228
|
+
verificationId: typeof data.verificationId === "string" ? data.verificationId : ""
|
|
229
|
+
});
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case "soliton:error":
|
|
233
|
+
(_a = options.onError) == null ? void 0 : _a.call(options, {
|
|
234
|
+
code: typeof data.code === "string" ? data.code : "internal",
|
|
235
|
+
message: typeof data.message === "string" ? data.message : ""
|
|
236
|
+
});
|
|
237
|
+
break;
|
|
238
|
+
case "soliton:close":
|
|
239
|
+
close();
|
|
240
|
+
break;
|
|
241
|
+
default:
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
window.addEventListener("message", onMessage);
|
|
246
|
+
function close() {
|
|
247
|
+
var _a;
|
|
248
|
+
if (closed) return;
|
|
249
|
+
closed = true;
|
|
250
|
+
if (autoCloseTimer !== null) clearTimeout(autoCloseTimer);
|
|
251
|
+
stopHello();
|
|
252
|
+
window.removeEventListener("message", onMessage);
|
|
253
|
+
overlay.destroy();
|
|
254
|
+
if (!terminalDelivered) (_a = options.onClose) == null ? void 0 : _a.call(options);
|
|
255
|
+
}
|
|
256
|
+
return { close };
|
|
257
|
+
}
|
|
258
|
+
var SolitonVerify = { open };
|
|
259
|
+
var index_default = SolitonVerify;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soliton Web SDK v1: embedded verification widget (Stripe/Sumsub wrapper
|
|
3
|
+
* pattern). open() frames the EXISTING hosted verification page in a
|
|
4
|
+
* full-viewport overlay on the customer's site and relays its lifecycle via
|
|
5
|
+
* callbacks; the capture engine itself always runs on soliton.id inside the
|
|
6
|
+
* iframe, so anti-injection defenses update with every deploy and the
|
|
7
|
+
* verdict never touches the customer page (only the verification id does).
|
|
8
|
+
*
|
|
9
|
+
* Built artifacts (npm run build:sdk):
|
|
10
|
+
* public/sdk/v1/soliton-web-sdk.js IIFE, global `SolitonVerify`
|
|
11
|
+
* public/sdk/v1/soliton-web-sdk.esm.js ESM, `import { open } ...`
|
|
12
|
+
* sdk/dist/index.mjs + index.cjs npm package `@soliton-id/web-sdk`
|
|
13
|
+
*/
|
|
14
|
+
import type { SolitonVerifyHandle, SolitonVerifyOptions } from "./types";
|
|
15
|
+
export type { SolitonVerifyHandle, SolitonVerifyOptions };
|
|
16
|
+
export declare function open(options: SolitonVerifyOptions): SolitonVerifyHandle;
|
|
17
|
+
export declare const SolitonVerify: {
|
|
18
|
+
open: typeof open;
|
|
19
|
+
};
|
|
20
|
+
export default SolitonVerify;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/* Soliton Web SDK v1. Built from sdk/src via `npm run build:sdk`; do not edit by hand. */
|
|
2
|
+
|
|
3
|
+
// sdk/src/hostedUrl.ts
|
|
4
|
+
var HOSTED_PATH_RE = /^\/v\/[0-9a-f]{32}$/;
|
|
5
|
+
var ALLOWED_HTTPS_ORIGINS = /* @__PURE__ */ new Set([
|
|
6
|
+
"https://soliton.id",
|
|
7
|
+
"https://staging.soliton.id"
|
|
8
|
+
]);
|
|
9
|
+
function parseHostedUrl(raw) {
|
|
10
|
+
let url;
|
|
11
|
+
try {
|
|
12
|
+
url = new URL(raw);
|
|
13
|
+
} catch (e) {
|
|
14
|
+
throw new Error(`SolitonVerify: "url" is not a valid absolute URL`);
|
|
15
|
+
}
|
|
16
|
+
const isLocalHttp = url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1");
|
|
17
|
+
if (!ALLOWED_HTTPS_ORIGINS.has(url.origin) && !isLocalHttp) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'SolitonVerify: "url" must be hosted on https://soliton.id or https://staging.soliton.id (http is allowed for localhost/127.0.0.1 development only)'
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
if (url.username || url.password) {
|
|
23
|
+
throw new Error('SolitonVerify: "url" must not embed credentials');
|
|
24
|
+
}
|
|
25
|
+
if (!HOSTED_PATH_RE.test(url.pathname)) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
'SolitonVerify: "url" does not look like a hosted verification URL (/v/{token})'
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return { url, hostedOrigin: url.origin };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// sdk/src/overlay.ts
|
|
34
|
+
var MAX_Z_INDEX = "2147483647";
|
|
35
|
+
var openOverlayCount = 0;
|
|
36
|
+
var savedBodyOverflow = "";
|
|
37
|
+
function createOverlay(iframeSrc, onCloseClick) {
|
|
38
|
+
const doc = document;
|
|
39
|
+
const container = doc.createElement("div");
|
|
40
|
+
container.setAttribute("data-soliton-sdk", "overlay");
|
|
41
|
+
container.style.cssText = [
|
|
42
|
+
"position:fixed",
|
|
43
|
+
"inset:0",
|
|
44
|
+
`z-index:${MAX_Z_INDEX}`,
|
|
45
|
+
"background:#0f110b",
|
|
46
|
+
"display:block"
|
|
47
|
+
].join(";");
|
|
48
|
+
const style = doc.createElement("style");
|
|
49
|
+
style.textContent = "@keyframes soliton-sdk-spin{to{transform:rotate(360deg)}}";
|
|
50
|
+
container.appendChild(style);
|
|
51
|
+
const loading = doc.createElement("div");
|
|
52
|
+
loading.setAttribute("data-soliton-sdk", "loading");
|
|
53
|
+
loading.style.cssText = [
|
|
54
|
+
"position:absolute",
|
|
55
|
+
"inset:0",
|
|
56
|
+
"display:flex",
|
|
57
|
+
"flex-direction:column",
|
|
58
|
+
"align-items:center",
|
|
59
|
+
"justify-content:center",
|
|
60
|
+
"gap:14px",
|
|
61
|
+
"color:rgba(255,255,255,0.75)",
|
|
62
|
+
"font:14px/1.4 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif"
|
|
63
|
+
].join(";");
|
|
64
|
+
const spinner = doc.createElement("div");
|
|
65
|
+
spinner.style.cssText = [
|
|
66
|
+
"width:22px",
|
|
67
|
+
"height:22px",
|
|
68
|
+
"border-radius:999px",
|
|
69
|
+
"border:2px solid rgba(255,255,255,0.25)",
|
|
70
|
+
"border-top-color:rgba(255,255,255,0.9)",
|
|
71
|
+
"animation:soliton-sdk-spin 0.8s linear infinite"
|
|
72
|
+
].join(";");
|
|
73
|
+
const loadingText = doc.createElement("div");
|
|
74
|
+
loadingText.textContent = "Loading secure verification...";
|
|
75
|
+
loading.appendChild(spinner);
|
|
76
|
+
loading.appendChild(loadingText);
|
|
77
|
+
container.appendChild(loading);
|
|
78
|
+
const iframe = doc.createElement("iframe");
|
|
79
|
+
iframe.setAttribute("allow", "camera; microphone; fullscreen; autoplay");
|
|
80
|
+
iframe.setAttribute("title", "Soliton identity verification");
|
|
81
|
+
iframe.style.cssText = [
|
|
82
|
+
"position:absolute",
|
|
83
|
+
"inset:0",
|
|
84
|
+
"width:100%",
|
|
85
|
+
"height:100%",
|
|
86
|
+
"border:0",
|
|
87
|
+
"display:block"
|
|
88
|
+
].join(";");
|
|
89
|
+
iframe.src = iframeSrc;
|
|
90
|
+
container.appendChild(iframe);
|
|
91
|
+
const closeButton = doc.createElement("button");
|
|
92
|
+
closeButton.type = "button";
|
|
93
|
+
closeButton.setAttribute("aria-label", "Close verification");
|
|
94
|
+
closeButton.textContent = "\xD7";
|
|
95
|
+
closeButton.style.cssText = [
|
|
96
|
+
"position:absolute",
|
|
97
|
+
"top:14px",
|
|
98
|
+
"right:14px",
|
|
99
|
+
"width:36px",
|
|
100
|
+
"height:36px",
|
|
101
|
+
"border-radius:999px",
|
|
102
|
+
"border:1px solid rgba(255,255,255,0.3)",
|
|
103
|
+
"background:rgba(0,0,0,0.45)",
|
|
104
|
+
"color:rgba(255,255,255,0.85)",
|
|
105
|
+
"font:20px/1 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif",
|
|
106
|
+
"cursor:pointer",
|
|
107
|
+
"padding:0"
|
|
108
|
+
].join(";");
|
|
109
|
+
closeButton.addEventListener("click", onCloseClick);
|
|
110
|
+
container.appendChild(closeButton);
|
|
111
|
+
if (openOverlayCount === 0) {
|
|
112
|
+
savedBodyOverflow = doc.body.style.overflow;
|
|
113
|
+
}
|
|
114
|
+
openOverlayCount += 1;
|
|
115
|
+
doc.body.style.overflow = "hidden";
|
|
116
|
+
doc.body.appendChild(container);
|
|
117
|
+
let destroyed = false;
|
|
118
|
+
return {
|
|
119
|
+
iframe,
|
|
120
|
+
markReady() {
|
|
121
|
+
loading.style.display = "none";
|
|
122
|
+
},
|
|
123
|
+
destroy() {
|
|
124
|
+
if (destroyed) return;
|
|
125
|
+
destroyed = true;
|
|
126
|
+
openOverlayCount -= 1;
|
|
127
|
+
if (openOverlayCount === 0) {
|
|
128
|
+
doc.body.style.overflow = savedBodyOverflow;
|
|
129
|
+
}
|
|
130
|
+
if (container.parentNode) container.parentNode.removeChild(container);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// sdk/src/types.ts
|
|
136
|
+
var EMBED_PROTOCOL = "soliton-v1";
|
|
137
|
+
|
|
138
|
+
// sdk/src/index.ts
|
|
139
|
+
var AUTO_CLOSE_DELAY_MS = 250;
|
|
140
|
+
var HELLO_INTERVAL_MS = 250;
|
|
141
|
+
var HELLO_MAX_PINGS = 40;
|
|
142
|
+
function open(options) {
|
|
143
|
+
if (!options || typeof options.url !== "string") {
|
|
144
|
+
throw new Error('SolitonVerify: open() requires an options object with a "url"');
|
|
145
|
+
}
|
|
146
|
+
if (typeof options.onComplete !== "function") {
|
|
147
|
+
throw new Error("SolitonVerify: open() requires an onComplete callback");
|
|
148
|
+
}
|
|
149
|
+
const { url, hostedOrigin } = parseHostedUrl(options.url);
|
|
150
|
+
const src = new URL(url.toString());
|
|
151
|
+
src.searchParams.set("mode", "embed");
|
|
152
|
+
src.searchParams.set("sdk", "1");
|
|
153
|
+
if (options.locale) src.searchParams.set("locale", options.locale);
|
|
154
|
+
let closed = false;
|
|
155
|
+
let terminalDelivered = false;
|
|
156
|
+
let autoCloseTimer = null;
|
|
157
|
+
const overlay = createOverlay(src.toString(), () => close());
|
|
158
|
+
let helloPings = 0;
|
|
159
|
+
const postHello = () => {
|
|
160
|
+
var _a;
|
|
161
|
+
helloPings += 1;
|
|
162
|
+
if (helloPings > HELLO_MAX_PINGS) {
|
|
163
|
+
stopHello();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
(_a = overlay.iframe.contentWindow) == null ? void 0 : _a.postMessage(
|
|
168
|
+
{ protocol: EMBED_PROTOCOL, type: "soliton:hello" },
|
|
169
|
+
hostedOrigin
|
|
170
|
+
);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const helloTimer = setInterval(postHello, HELLO_INTERVAL_MS);
|
|
175
|
+
const onIframeLoad = () => postHello();
|
|
176
|
+
overlay.iframe.addEventListener("load", onIframeLoad);
|
|
177
|
+
function stopHello() {
|
|
178
|
+
clearInterval(helloTimer);
|
|
179
|
+
overlay.iframe.removeEventListener("load", onIframeLoad);
|
|
180
|
+
}
|
|
181
|
+
const onMessage = (event) => {
|
|
182
|
+
var _a;
|
|
183
|
+
if (event.origin !== hostedOrigin) return;
|
|
184
|
+
if (!overlay.iframe.contentWindow || event.source !== overlay.iframe.contentWindow) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const data = event.data;
|
|
188
|
+
if (!data || typeof data !== "object" || data.protocol !== EMBED_PROTOCOL) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
stopHello();
|
|
192
|
+
switch (data.type) {
|
|
193
|
+
case "soliton:ready":
|
|
194
|
+
overlay.markReady();
|
|
195
|
+
break;
|
|
196
|
+
case "soliton:complete": {
|
|
197
|
+
if (terminalDelivered) break;
|
|
198
|
+
terminalDelivered = true;
|
|
199
|
+
autoCloseTimer = setTimeout(() => close(), AUTO_CLOSE_DELAY_MS);
|
|
200
|
+
options.onComplete({
|
|
201
|
+
verificationId: typeof data.verificationId === "string" ? data.verificationId : ""
|
|
202
|
+
});
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case "soliton:error":
|
|
206
|
+
(_a = options.onError) == null ? void 0 : _a.call(options, {
|
|
207
|
+
code: typeof data.code === "string" ? data.code : "internal",
|
|
208
|
+
message: typeof data.message === "string" ? data.message : ""
|
|
209
|
+
});
|
|
210
|
+
break;
|
|
211
|
+
case "soliton:close":
|
|
212
|
+
close();
|
|
213
|
+
break;
|
|
214
|
+
default:
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
window.addEventListener("message", onMessage);
|
|
219
|
+
function close() {
|
|
220
|
+
var _a;
|
|
221
|
+
if (closed) return;
|
|
222
|
+
closed = true;
|
|
223
|
+
if (autoCloseTimer !== null) clearTimeout(autoCloseTimer);
|
|
224
|
+
stopHello();
|
|
225
|
+
window.removeEventListener("message", onMessage);
|
|
226
|
+
overlay.destroy();
|
|
227
|
+
if (!terminalDelivered) (_a = options.onClose) == null ? void 0 : _a.call(options);
|
|
228
|
+
}
|
|
229
|
+
return { close };
|
|
230
|
+
}
|
|
231
|
+
var SolitonVerify = { open };
|
|
232
|
+
var index_default = SolitonVerify;
|
|
233
|
+
export {
|
|
234
|
+
SolitonVerify,
|
|
235
|
+
index_default as default,
|
|
236
|
+
open
|
|
237
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-viewport overlay hosting the verification iframe.
|
|
3
|
+
*
|
|
4
|
+
* The overlay is deliberately full-screen and opaque: the capture flow runs
|
|
5
|
+
* a screen-light challenge, and a small inline widget would both break that
|
|
6
|
+
* physics and shrink the camera view. Everything is plain DOM with inline
|
|
7
|
+
* styles so the SDK needs no stylesheet and cannot clash with host CSS
|
|
8
|
+
* (spinner keyframes ride in a scoped <style> tag inside the overlay).
|
|
9
|
+
*/
|
|
10
|
+
export interface Overlay {
|
|
11
|
+
iframe: HTMLIFrameElement;
|
|
12
|
+
/** Hide the loading state once the hosted page reports soliton:ready. */
|
|
13
|
+
markReady(): void;
|
|
14
|
+
/** Remove the overlay from the DOM and restore body scroll. Idempotent. */
|
|
15
|
+
destroy(): void;
|
|
16
|
+
}
|
|
17
|
+
export declare function createOverlay(iframeSrc: string, onCloseClick: () => void): Overlay;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for the Soliton Web SDK, plus the child -> parent postMessage
|
|
3
|
+
* protocol shared with the hosted page (app/utils/embedMessaging.ts is the
|
|
4
|
+
* child-side counterpart; keep the two in sync).
|
|
5
|
+
*
|
|
6
|
+
* COMPATIBILITY CONTRACT (frozen since the SDK ships on npm): wrapper
|
|
7
|
+
* versions live inside customer bundles for months, so the soliton-v1
|
|
8
|
+
* message shapes below can never change or be removed. The hosted page MAY
|
|
9
|
+
* add new message types in the future; old wrappers silently ignore unknown
|
|
10
|
+
* types (the switch in index.ts must keep its no-op default), so additions
|
|
11
|
+
* are safe. Never rename a type, never remove or retype a field, and never
|
|
12
|
+
* add payload fields the wrapper is required to understand.
|
|
13
|
+
*/
|
|
14
|
+
export interface SolitonVerifyOptions {
|
|
15
|
+
/** Hosted verification URL from POST /api/v1/verifications. */
|
|
16
|
+
url: string;
|
|
17
|
+
/**
|
|
18
|
+
* Terminal state reached (pass OR final fail; deliberately not
|
|
19
|
+
* distinguished here). Carries only the verification id: confirm the
|
|
20
|
+
* verdict server-side via GET /api/v1/verifications/{id}.
|
|
21
|
+
*/
|
|
22
|
+
onComplete: (e: {
|
|
23
|
+
verificationId: string;
|
|
24
|
+
}) => void;
|
|
25
|
+
/** Flow-level errors: link_expired, link_invalid, origin_not_allowed, storage_unavailable, internal. */
|
|
26
|
+
onError?: (e: {
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
}) => void;
|
|
30
|
+
/** User dismissed the flow before completion. */
|
|
31
|
+
onClose?: () => void;
|
|
32
|
+
/** Reserved; passed through to the hosted page as a query param. */
|
|
33
|
+
locale?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface SolitonVerifyHandle {
|
|
36
|
+
close(): void;
|
|
37
|
+
}
|
|
38
|
+
/** Every hosted-page message is tagged with this protocol marker. */
|
|
39
|
+
export declare const EMBED_PROTOCOL = "soliton-v1";
|
|
40
|
+
export type EmbedMessage = {
|
|
41
|
+
protocol: typeof EMBED_PROTOCOL;
|
|
42
|
+
type: "soliton:ready";
|
|
43
|
+
} | {
|
|
44
|
+
protocol: typeof EMBED_PROTOCOL;
|
|
45
|
+
type: "soliton:complete";
|
|
46
|
+
verificationId: string;
|
|
47
|
+
} | {
|
|
48
|
+
protocol: typeof EMBED_PROTOCOL;
|
|
49
|
+
type: "soliton:error";
|
|
50
|
+
code: string;
|
|
51
|
+
message: string;
|
|
52
|
+
} | {
|
|
53
|
+
protocol: typeof EMBED_PROTOCOL;
|
|
54
|
+
type: "soliton:close";
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Parent -> child (the only message in that direction): the wrapper pings
|
|
58
|
+
* this at the hosted iframe so the hosted page can read the embedding origin
|
|
59
|
+
* from MessageEvent.origin, which the browser sets and nothing can spoof.
|
|
60
|
+
* Firefox has no location.ancestorOrigins, and Referrer-Policy: no-referrer
|
|
61
|
+
* on the customer page blanks the referrer fallback; without this ping such
|
|
62
|
+
* embeds would always fail the frame-origin gate. Added before the first npm
|
|
63
|
+
* publish, so it is part of the frozen soliton-v1 surface. Deliberately
|
|
64
|
+
* carries nothing beyond the protocol marker.
|
|
65
|
+
*/
|
|
66
|
+
export type ParentHelloMessage = {
|
|
67
|
+
protocol: typeof EMBED_PROTOCOL;
|
|
68
|
+
type: "soliton:hello";
|
|
69
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": "Publishable manifest for the npm Web SDK. No devDependencies and no own node_modules by design: every build runs from the next-app root with its existing toolchain (npm run build:sdk), so the lockfile never grows for packaging. dist/ is rebuilt automatically by the prepack hook and is not committed.",
|
|
3
|
+
"name": "@soliton-id/web-sdk",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Soliton identity verification for the browser: opens the hosted verification flow in a full-viewport overlay iframe on your page and reports lifecycle events. Verdicts and portraits are fetched by your backend, never exposed in the browser.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Soliton",
|
|
8
|
+
"homepage": "https://soliton.id/docs/integration/sdk",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/georgesabr/next-app.git",
|
|
12
|
+
"directory": "sdk"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/georgesabr/next-app/issues"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public",
|
|
19
|
+
"registry": "https://registry.npmjs.org/"
|
|
20
|
+
},
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"main": "./dist/index.cjs",
|
|
23
|
+
"module": "./dist/index.mjs",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.mjs",
|
|
29
|
+
"require": "./dist/index.cjs"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"CHANGELOG.md"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"prepack": "node build.mjs"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=16"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"identity",
|
|
46
|
+
"verification",
|
|
47
|
+
"liveness",
|
|
48
|
+
"kyc",
|
|
49
|
+
"soliton"
|
|
50
|
+
]
|
|
51
|
+
}
|