unshared-clientjs-sdk 2.3.0 → 3.0.0-rc.14
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 +176 -151
- package/dist/client.d.ts +54 -25
- package/dist/client.js +1 -1
- package/dist/esm/client.d.mts +54 -25
- package/dist/esm/client.mjs +1 -1
- package/dist/esm/index.d.mts +8 -8
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/middleware/dispatch-processing.d.mts +6 -3
- package/dist/esm/middleware/dispatch-processing.mjs +1 -1
- package/dist/esm/middleware/index.d.mts +7 -5
- package/dist/esm/middleware/index.mjs +1 -1
- package/dist/esm/middleware/injection/fingerprint-script.d.mts +7 -0
- package/dist/esm/middleware/injection/fingerprint-script.mjs +1 -1
- package/dist/esm/middleware/injection/fp-bundle-source.d.mts +5 -4
- package/dist/esm/middleware/injection/fp-bundle-source.mjs +1 -1
- package/dist/esm/middleware/response-interceptor.d.mts +1 -1
- package/dist/esm/middleware/routes/interstitial.d.mts +2 -2
- package/dist/esm/middleware/routes/interstitial.mjs +1 -1
- package/dist/esm/middleware/routes/submit-fp.d.mts +5 -5
- package/dist/esm/middleware/routes/submit-fp.mjs +1 -1
- package/dist/esm/middleware/routes/verify.d.mts +3 -3
- package/dist/esm/middleware/routes/verify.mjs +1 -1
- package/dist/esm/middleware/utils/client-ip.d.mts +1 -1
- package/dist/esm/middleware/utils/cookies.d.mts +1 -1
- package/dist/esm/middleware/utils/device-id.d.mts +1 -1
- package/dist/esm/middleware/utils/device-id.mjs +1 -1
- package/dist/esm/middleware/utils/flagged-response.mjs +1 -1
- package/dist/esm/middleware/utils/http-helpers.d.mts +1 -1
- package/dist/esm/middleware/utils/permanent-device-id.d.mts +7 -0
- package/dist/esm/middleware/utils/permanent-device-id.mjs +1 -0
- package/dist/esm/middleware/utils/read-json-body.d.mts +3 -0
- package/dist/esm/middleware/utils/read-json-body.mjs +1 -0
- package/dist/esm/middleware/utils/secure.d.mts +1 -1
- package/dist/esm/middleware.d.mts +5 -6
- package/dist/esm/middleware.mjs +1 -1
- package/dist/esm/shared-types.d.mts +148 -0
- package/dist/esm/web/index.d.mts +6 -6
- package/dist/esm/web/index.mjs +1 -1
- package/dist/esm/web/protection-handler.d.mts +5 -5
- package/dist/esm/web/protection-handler.mjs +1 -1
- package/dist/esm/web/submit-handler.d.mts +2 -2
- package/dist/esm/web/submit-handler.mjs +1 -1
- package/dist/esm/web/types.d.mts +1 -1
- package/dist/esm/web/web-helpers.d.mts +11 -0
- package/dist/esm/web/web-helpers.mjs +1 -1
- package/dist/middleware/dispatch-processing.d.ts +3 -0
- package/dist/middleware/dispatch-processing.js +1 -1
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/injection/fingerprint-script.d.ts +7 -0
- package/dist/middleware/injection/fingerprint-script.js +1 -1
- package/dist/middleware/injection/fp-bundle-source.d.ts +5 -4
- package/dist/middleware/injection/fp-bundle-source.js +1 -1
- package/dist/middleware/routes/submit-fp.js +1 -1
- package/dist/middleware/routes/verify.js +1 -1
- package/dist/middleware/utils/permanent-device-id.d.ts +7 -0
- package/dist/middleware/utils/permanent-device-id.js +1 -0
- package/dist/middleware/utils/read-json-body.d.ts +3 -0
- package/dist/middleware/utils/read-json-body.js +1 -0
- package/dist/middleware.d.ts +3 -4
- package/dist/middleware.js +1 -1
- package/dist/shared-types.d.ts +148 -0
- package/dist/web/protection-handler.d.ts +1 -1
- package/dist/web/protection-handler.js +1 -1
- package/dist/web/submit-handler.js +1 -1
- package/dist/web/web-helpers.d.ts +11 -0
- package/dist/web/web-helpers.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,240 +1,265 @@
|
|
|
1
1
|
# unshared-clientjs-sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
---
|
|
3
|
+
Node.js SDK for Unshared v3 ingestion, risk checks, email verification, and application protection middleware.
|
|
6
4
|
|
|
7
5
|
## Install
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
npm install unshared-clientjs-sdk
|
|
8
|
+
npm install unshared-clientjs-sdk@^3
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Quick Start
|
|
11
|
+
Requires Node.js 20 or newer.
|
|
18
12
|
|
|
19
13
|
```typescript
|
|
20
14
|
import { UnsharedClient } from 'unshared-clientjs-sdk';
|
|
21
15
|
|
|
22
16
|
const client = new UnsharedClient({
|
|
23
|
-
apiKey: process.env.UNSHARED_API_KEY
|
|
17
|
+
apiKey: process.env.UNSHARED_API_KEY!, // usk_...
|
|
24
18
|
});
|
|
25
19
|
```
|
|
26
20
|
|
|
27
|
-
|
|
21
|
+
V3 fingerprint and user-event ingestion encrypts the complete JSON object with AES-256-GCM before sending it to Unshared. Check, Trigger, and Verify remain structured plaintext JSON over HTTPS.
|
|
22
|
+
|
|
23
|
+
## V3 Methods
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
### `submitFingerprint(payload)`
|
|
26
|
+
|
|
27
|
+
Sends any JSON object to `POST /v3/submit-fingerprint-event` without reshaping it.
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
const result = await client.submitFingerprint({
|
|
31
|
+
hash: 'full-456',
|
|
32
|
+
stable_hash: 'stable-123',
|
|
33
|
+
components: {},
|
|
34
|
+
custom_data: { nested: ['preserved', 1, false] },
|
|
35
|
+
});
|
|
36
|
+
```
|
|
30
37
|
|
|
31
|
-
### `processUserEvent(
|
|
38
|
+
### `processUserEvent(payload)`
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
Sends any JSON object to `POST /v3/process-user-event` without reshaping it.
|
|
34
41
|
|
|
35
42
|
```typescript
|
|
36
43
|
const result = await client.processUserEvent({
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
event_type: 'LOGIN',
|
|
45
|
+
identity: { user_id: user.id, email_address: user.email },
|
|
46
|
+
identifiers: {
|
|
47
|
+
unshared_device_id: permanentDeviceId,
|
|
48
|
+
device_id: deviceId,
|
|
49
|
+
session_hash: sessionId,
|
|
50
|
+
},
|
|
51
|
+
custom_data: { plan: 'pro' },
|
|
44
52
|
});
|
|
53
|
+
```
|
|
45
54
|
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
Both ingestion calls accept objects up to 1 MiB and return:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
{
|
|
59
|
+
event_id: string;
|
|
60
|
+
collected_at: string;
|
|
61
|
+
endpoint_version: 'v3';
|
|
48
62
|
}
|
|
49
63
|
```
|
|
50
64
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
---
|
|
65
|
+
Ingestion is not automatically retried because an ambiguous failure may already have created an event.
|
|
54
66
|
|
|
55
|
-
### `checkUser(
|
|
67
|
+
### `checkUser(...)`
|
|
56
68
|
|
|
57
|
-
|
|
69
|
+
Checks existing risk state through `POST /v3/check-user`.
|
|
58
70
|
|
|
59
71
|
```typescript
|
|
60
|
-
const result = await client.checkUser('user@example.com',
|
|
72
|
+
const result = await client.checkUser('user@example.com', {
|
|
73
|
+
permanentDeviceId,
|
|
74
|
+
deviceId,
|
|
75
|
+
fingerprintId: stableHash,
|
|
76
|
+
fullHash,
|
|
77
|
+
sessionHash,
|
|
78
|
+
});
|
|
61
79
|
|
|
62
80
|
if (result.data?.is_user_flagged) {
|
|
63
|
-
//
|
|
81
|
+
// Block or challenge.
|
|
64
82
|
}
|
|
65
83
|
```
|
|
66
84
|
|
|
67
|
-
|
|
85
|
+
You can also pass the v3 request object directly:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
await client.checkUser({
|
|
89
|
+
user_id: user.id,
|
|
90
|
+
email_address: user.email,
|
|
91
|
+
identifiers: { device_id: deviceId },
|
|
92
|
+
});
|
|
93
|
+
```
|
|
68
94
|
|
|
69
|
-
|
|
95
|
+
`checkUser` is read-only and can retry. On transport or HTTP failure it deliberately returns a clean verdict with `failedOpen` metadata so an outage does not accidentally block users.
|
|
70
96
|
|
|
71
|
-
###
|
|
97
|
+
### Email Verification
|
|
72
98
|
|
|
73
|
-
|
|
99
|
+
Trigger returns a challenge ID that Verify must send back:
|
|
74
100
|
|
|
75
101
|
```typescript
|
|
76
|
-
await client.triggerEmailVerification(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
102
|
+
const trigger = await client.triggerEmailVerification(user.email, deviceId, {
|
|
103
|
+
permanentDeviceId,
|
|
104
|
+
fingerprintId: stableHash,
|
|
105
|
+
});
|
|
80
106
|
|
|
81
|
-
|
|
107
|
+
const verify = await client.verifyEmail({
|
|
108
|
+
verification_id: trigger.data!.verification_id!,
|
|
109
|
+
code: submittedCode, // string preserves leading zeroes
|
|
110
|
+
});
|
|
111
|
+
```
|
|
82
112
|
|
|
83
|
-
|
|
113
|
+
`verifyEmail(...)` is stateless and recommended for distributed deployments. The source-compatible methods retain the challenge in the current `UnsharedClient` instance:
|
|
84
114
|
|
|
85
115
|
```typescript
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (!result.success) {
|
|
89
|
-
if (result.error?.code === 'VERIFICATION_FAILED') {
|
|
90
|
-
// Wrong or expired code — ask user to retry
|
|
91
|
-
} else {
|
|
92
|
-
// Transport error (DELIVERY_FAILED) — retry or show generic error
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
// Verified — success: true means the code was correct
|
|
96
|
-
}
|
|
116
|
+
await client.triggerEmailVerification(user.email, deviceId);
|
|
117
|
+
const verify = await client.verify(user.email, deviceId, submittedCode);
|
|
97
118
|
```
|
|
98
119
|
|
|
99
|
-
|
|
120
|
+
Trigger and Verify are never automatically retried because the first request may have committed before an ambiguous response failure.
|
|
100
121
|
|
|
101
|
-
|
|
122
|
+
## Compatibility Methods
|
|
102
123
|
|
|
103
|
-
|
|
124
|
+
Existing v2 package call sites remain valid while using v3 routes and encrypted ingestion envelopes.
|
|
104
125
|
|
|
105
126
|
```typescript
|
|
106
|
-
await client.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
127
|
+
const result = await client.processUserEvent({
|
|
128
|
+
eventType: 'login',
|
|
129
|
+
userId: user.id,
|
|
130
|
+
emailAddress: user.email,
|
|
131
|
+
deviceId,
|
|
132
|
+
sessionHash: sessionId,
|
|
133
|
+
ipAddress: req.ip,
|
|
134
|
+
userAgent: req.headers['user-agent'] ?? '',
|
|
110
135
|
});
|
|
136
|
+
|
|
137
|
+
if (result.success && result.data?.analysis.is_user_flagged) {
|
|
138
|
+
// Block or challenge.
|
|
139
|
+
}
|
|
111
140
|
```
|
|
112
141
|
|
|
113
|
-
|
|
142
|
+
`submitFingerprintEvent(fingerprint, opts)` is also retained. New proxy implementations should prefer `submitFingerprint(body)` so unknown future fields cannot be lost.
|
|
114
143
|
|
|
115
|
-
## Protection Middleware
|
|
144
|
+
## Protection Middleware
|
|
116
145
|
|
|
117
|
-
`unsharedBoundToUser`
|
|
146
|
+
`unsharedBoundToUser` injects browser fingerprint collection, protects routes, caches verdicts, and hosts the proxy verification flow.
|
|
118
147
|
|
|
119
148
|
```typescript
|
|
120
|
-
import
|
|
121
|
-
|
|
149
|
+
import express from 'express';
|
|
150
|
+
import {
|
|
151
|
+
UnsharedClient,
|
|
152
|
+
unsharedBoundToUser,
|
|
153
|
+
flaggedResponse,
|
|
154
|
+
} from 'unshared-clientjs-sdk';
|
|
155
|
+
|
|
156
|
+
const app = express();
|
|
122
157
|
app.set('trust proxy', 1);
|
|
123
|
-
app.use(express.json());
|
|
158
|
+
app.use(express.json()); // the default 100 KiB limit is supported
|
|
124
159
|
|
|
125
160
|
const client = new UnsharedClient({ apiKey: process.env.UNSHARED_API_KEY! });
|
|
126
|
-
|
|
127
161
|
app.use(unsharedBoundToUser(client, {
|
|
128
|
-
userId: (req) => req.
|
|
129
|
-
emailAddress: (req) => req.
|
|
130
|
-
includePathPrefix: ['/api/'],
|
|
162
|
+
userId: (req) => req.user?.id,
|
|
163
|
+
emailAddress: (req) => req.user?.email,
|
|
131
164
|
onFlagged: ({ emailAddress, res }) => {
|
|
132
165
|
res.status(403).json(flaggedResponse(emailAddress));
|
|
133
166
|
},
|
|
134
167
|
}));
|
|
135
168
|
```
|
|
136
169
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
**
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
Keep your existing parser order and limits. Mount authentication/session middleware
|
|
171
|
+
needed by your resolvers before Unshared. The SDK also reads its own unparsed POST
|
|
172
|
+
routes automatically, up to **1 MiB (1,048,576 UTF-8 bytes)**, without a configuration
|
|
173
|
+
option or an Express dependency. Ordinary application routes are not parsed by the
|
|
174
|
+
SDK. Existing parsed bodies are respected and checked against the same limit.
|
|
175
|
+
Structured browser-to-proxy submissions (including the inline script) fit Express's
|
|
176
|
+
default **100 KiB (102,400 serialized UTF-8 bytes)** without public configuration:
|
|
177
|
+
send the full payload if it fits; otherwise empty both local/session storage lists
|
|
178
|
+
while keeping every cookie; if still too large, omit `context.browser_storage`.
|
|
179
|
+
The complete raw fingerprint, including future fields, and all core identity,
|
|
180
|
+
device, session, SDK and event context are never trimmed. If those alone exceed
|
|
181
|
+
100 KiB, the browser returns `REQUEST_TOO_LARGE`; the inline script logs that code
|
|
182
|
+
and sends nothing. Cookies are never partially selected or truncated.
|
|
183
|
+
|
|
184
|
+
Low-level browser `submitFingerprint(payload)` preserves arbitrary objects exactly
|
|
185
|
+
and rejects proxy payloads above 100 KiB rather than reducing them. Direct browser
|
|
186
|
+
submission retains full snapshots and its 1 MiB logical limit. An earlier parser
|
|
187
|
+
or reverse proxy with a smaller limit can still reject a request before Unshared.
|
|
188
|
+
|
|
189
|
+
Oversized SDK request bodies return 413, malformed JSON returns 400, and unfinished
|
|
190
|
+
reads time out after 30 seconds with 408. Raw bodies must use identity encoding
|
|
191
|
+
(compressed bodies return 415). Fingerprints and retained cookies are not truncated;
|
|
192
|
+
duplicate SDK fingerprint caches are excluded from storage snapshots. The encrypted
|
|
193
|
+
platform request retains its existing 1.5 MiB envelope limit; the full logical payload,
|
|
194
|
+
including server-added context, must still fit within 1 MiB.
|
|
195
|
+
|
|
196
|
+
| Option | Description |
|
|
197
|
+
|---|---|
|
|
198
|
+
| `userId` | Required server-side user resolver |
|
|
199
|
+
| `emailAddress` | Server-side email resolver used for checks and verification |
|
|
200
|
+
| `routePrefix` | Internal route prefix; default `/__unshared` |
|
|
201
|
+
| `includePathPrefix` | Paths that trigger verdict checks and events |
|
|
202
|
+
| `onFlagged` | Custom blocked-user response |
|
|
203
|
+
| `disableBotFilter` | Include known bot user agents, useful in E2E |
|
|
204
|
+
| `checkUserTimeoutMs` | Bounded verdict-check timeout |
|
|
205
|
+
| `corsOrigins` | Allowed cross-origin frontend origins |
|
|
206
|
+
| `onError` | Background SDK error observer |
|
|
207
|
+
|
|
208
|
+
The middleware exposes:
|
|
209
|
+
|
|
210
|
+
- `POST /__unshared/submit-fp`
|
|
211
|
+
- `GET /__unshared/status`
|
|
212
|
+
- `POST /__unshared/verify-trigger`
|
|
213
|
+
- `POST /__unshared/verify`
|
|
214
|
+
- `GET /__unshared/fp.js`
|
|
215
|
+
- `GET /__unshared/interstitial-flow`
|
|
216
|
+
|
|
217
|
+
The verification trigger stores `verification_id` in an HttpOnly, ten-minute `__unshared_verification_id` cookie. The permanent readable `__unshared_device_id=upid_<uuid-v4>` cookie rolls for 400 days and is not tied to mutable fingerprint hashes.
|
|
218
|
+
|
|
219
|
+
## Simple Proxy Middleware
|
|
220
|
+
|
|
221
|
+
`createUnsharedMiddleware` only proxies fingerprint events. It preserves arbitrary fields and augments trusted request context.
|
|
165
222
|
|
|
166
223
|
```typescript
|
|
167
|
-
import { createUnsharedMiddleware } from 'unshared-clientjs-sdk';
|
|
224
|
+
import { createUnsharedMiddleware } from 'unshared-clientjs-sdk/middleware';
|
|
168
225
|
|
|
169
|
-
app.use(express.json());
|
|
170
226
|
app.use(createUnsharedMiddleware(client, {
|
|
171
227
|
userIdExtractor: (req) => req.user?.id,
|
|
228
|
+
corsOrigins: 'https://app.example.com',
|
|
172
229
|
}));
|
|
230
|
+
app.use(express.json());
|
|
173
231
|
```
|
|
174
232
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
| Option | Type | Default | Description |
|
|
178
|
-
|--------|------|---------|-------------|
|
|
179
|
-
| `userIdExtractor` | `(req) => string \| undefined` | — | Pull user ID from your auth session |
|
|
180
|
-
| `eventTypeExtractor` | `(req) => string \| undefined` | — | Override event type |
|
|
181
|
-
| `sessionIdExtractor` | `(req) => string \| undefined` | — | Override session ID |
|
|
182
|
-
| `ipAddressExtractor` | `(req) => string \| undefined` | — | Override IP address |
|
|
183
|
-
| `defaultEventType` | `string` | `"browser_event"` | Fallback event type |
|
|
184
|
-
| `routePrefix` | `string` | `"/unshared"` | Route mount prefix |
|
|
185
|
-
| `corsOrigins` | `string \| string[]` | — | Allowed CORS origins; handles OPTIONS preflight automatically |
|
|
186
|
-
|
|
187
|
-
> **Note:** This middleware uses a different default prefix (`/unshared`) and route (`submit-fingerprint-event`) than `unsharedBoundToUser` (`/__unshared`, `submit-fp`).
|
|
188
|
-
|
|
189
|
-
---
|
|
233
|
+
Its default route is `POST /unshared/submit-fingerprint-event`.
|
|
190
234
|
|
|
191
|
-
## Web
|
|
235
|
+
## Web Standard Handler
|
|
192
236
|
|
|
193
|
-
|
|
237
|
+
Use the edge/serverless entry point with Next.js, Cloudflare Workers, Vercel Edge, or other Web Standard runtimes:
|
|
194
238
|
|
|
195
239
|
```typescript
|
|
196
240
|
import { createWebProtectionMiddleware } from 'unshared-clientjs-sdk/web';
|
|
197
241
|
```
|
|
198
242
|
|
|
199
|
-
|
|
243
|
+
It mirrors the protection and verification behavior using `Request` and `Response`.
|
|
200
244
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
## Configuration
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
new UnsharedClient({
|
|
207
|
-
apiKey: 'usk_…', // required
|
|
208
|
-
baseUrl: 'https://api.unshared.ai', // optional
|
|
209
|
-
timeout: 10_000, // optional, ms
|
|
210
|
-
maxRetries: 3, // optional
|
|
211
|
-
});
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
---
|
|
215
|
-
|
|
216
|
-
## Response shape
|
|
217
|
-
|
|
218
|
-
All methods return `ApiResult<T>`:
|
|
245
|
+
## Result and Error Handling
|
|
219
246
|
|
|
220
247
|
```typescript
|
|
221
|
-
{
|
|
248
|
+
type ApiResult<T> = {
|
|
222
249
|
success: boolean;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
250
|
+
status: number; // 0 for a network failure
|
|
251
|
+
data?: T;
|
|
252
|
+
error?: {
|
|
253
|
+
code: string;
|
|
254
|
+
message: string;
|
|
255
|
+
retryAfter?: number;
|
|
256
|
+
};
|
|
257
|
+
failedOpen?: { status: number; reason?: 'no_device_id' };
|
|
258
|
+
};
|
|
227
259
|
```
|
|
228
260
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
## See Also
|
|
238
|
-
|
|
239
|
-
- [Quickstart](./docs/quickstart.md) — step-by-step Express setup from zero
|
|
240
|
-
- [Flag semantics and testing](./docs/flag-semantics.md) — how flags work, E2E testing tips, timeout behavior
|
|
261
|
+
- Success requires a valid `{ success: true, data }` response envelope.
|
|
262
|
+
- Bodies larger than 1 MiB fail locally with `REQUEST_TOO_LARGE`.
|
|
263
|
+
- `Retry-After` is exposed as `error.retryAfter` on 429 responses.
|
|
264
|
+
- Protected same-origin submission routes never return 5xx to browser code.
|
|
265
|
+
- Keep the secret API key server-side and exclude logical identity/event bodies from request logs before SDK encryption.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FingerprintWireFormat } from '
|
|
1
|
+
import type { CheckUserRequest, CheckUserResult as V3CheckUserResult, FingerprintWireFormat, IngestResult, JSONObject, VerifyEmailRequest, VerifyEmailResult } from './shared-types';
|
|
2
2
|
export interface UnsharedClientConfig {
|
|
3
3
|
/**
|
|
4
4
|
* Secret API key. Must be kept server-side.
|
|
@@ -6,7 +6,7 @@ export interface UnsharedClientConfig {
|
|
|
6
6
|
*/
|
|
7
7
|
apiKey: string;
|
|
8
8
|
/**
|
|
9
|
-
* Base URL of the Unshared Labs
|
|
9
|
+
* Base URL of the Unshared Labs ingress.
|
|
10
10
|
* @default "https://api.unshared.ai"
|
|
11
11
|
*/
|
|
12
12
|
baseUrl?: string;
|
|
@@ -70,14 +70,15 @@ export interface ApiResult<T = unknown> {
|
|
|
70
70
|
}
|
|
71
71
|
export interface SubmitFingerprintOptions {
|
|
72
72
|
userId?: string;
|
|
73
|
-
/** SDK encrypts before sending. */
|
|
74
73
|
emailAddress?: string;
|
|
75
74
|
sessionHash?: string;
|
|
76
75
|
eventType?: string;
|
|
77
|
-
/** SDK encrypts before sending. */
|
|
78
76
|
ipAddress?: string;
|
|
79
|
-
/** SDK encrypts before sending. */
|
|
80
77
|
userAgent?: string;
|
|
78
|
+
permanentDeviceId?: string;
|
|
79
|
+
deviceId?: string;
|
|
80
|
+
stableHash?: string;
|
|
81
|
+
fullHash?: string;
|
|
81
82
|
/**
|
|
82
83
|
* Client-supplied idempotency key, forwarded verbatim as X-Idempotency-Key.
|
|
83
84
|
* NOTE: the middleware that calls this (submit-fp.ts / protection-handler.ts)
|
|
@@ -92,25 +93,21 @@ export interface SubmitFingerprintOptions {
|
|
|
92
93
|
export interface SubmitFingerprintResult {
|
|
93
94
|
hash: string;
|
|
94
95
|
stable_hash: string;
|
|
95
|
-
collected_at: string;
|
|
96
|
-
/** Always present; set to "unknown" by the server if not provided. */
|
|
97
96
|
version: string;
|
|
97
|
+
event_id: string;
|
|
98
|
+
collected_at: string;
|
|
99
|
+
endpoint_version: 'v3';
|
|
98
100
|
}
|
|
99
101
|
export interface ProcessUserEventParams {
|
|
100
102
|
eventType: string;
|
|
101
|
-
/** SDK encrypts before sending. */
|
|
102
103
|
userId: string;
|
|
103
|
-
/** SDK encrypts before sending. */
|
|
104
104
|
ipAddress: string;
|
|
105
|
-
/** SDK encrypts before sending. */
|
|
106
105
|
deviceId: string;
|
|
107
106
|
sessionHash: string;
|
|
108
|
-
/** SDK encrypts before sending. */
|
|
109
107
|
userAgent: string;
|
|
110
|
-
/** SDK encrypts before sending. */
|
|
111
108
|
emailAddress: string;
|
|
112
|
-
/** SDK encrypts before sending. */
|
|
113
109
|
fingerprintId?: string;
|
|
110
|
+
permanentDeviceId?: string;
|
|
114
111
|
subscriptionStatus?: string | null;
|
|
115
112
|
eventDetails?: Record<string, unknown> | null;
|
|
116
113
|
}
|
|
@@ -121,6 +118,7 @@ export interface ProcessUserEventResult {
|
|
|
121
118
|
email_address: string;
|
|
122
119
|
ip_address: string;
|
|
123
120
|
device_id: string;
|
|
121
|
+
permanent_device_id?: string;
|
|
124
122
|
session_hash: string;
|
|
125
123
|
user_agent: string;
|
|
126
124
|
event_details?: string;
|
|
@@ -134,25 +132,31 @@ export interface ProcessUserEventResult {
|
|
|
134
132
|
}
|
|
135
133
|
export interface CheckUserResult {
|
|
136
134
|
is_user_flagged: boolean;
|
|
135
|
+
/** Present on authoritative v3 responses; omitted by local fail-open defaults. */
|
|
136
|
+
decision_id?: string;
|
|
137
|
+
/** Present on authoritative v3 responses; omitted by local fail-open defaults. */
|
|
138
|
+
decision_code?: V3CheckUserResult['decision_code'];
|
|
137
139
|
}
|
|
138
140
|
export interface TriggerEmailVerificationResult {
|
|
139
141
|
message: string;
|
|
142
|
+
verification_id?: string;
|
|
143
|
+
next_allowed_at?: string;
|
|
144
|
+
retry_after_seconds?: number;
|
|
140
145
|
}
|
|
141
146
|
export interface VerifyResult {
|
|
142
147
|
verified: boolean;
|
|
143
|
-
reason?: 'not_found' | 'code_mismatch' | 'code_expired';
|
|
148
|
+
reason?: 'not_found' | 'code_mismatch' | 'code_expired' | 'invalid_code';
|
|
149
|
+
verification_scope?: 'email';
|
|
144
150
|
}
|
|
145
151
|
export declare class UnsharedClient {
|
|
146
152
|
private readonly _apiKey;
|
|
147
153
|
private readonly _baseUrl;
|
|
148
154
|
private readonly _timeout;
|
|
149
155
|
private readonly _maxRetries;
|
|
150
|
-
private _encryptionKey;
|
|
151
156
|
private readonly _customFetch;
|
|
157
|
+
private _ingestionKey?;
|
|
158
|
+
private readonly _verificationIds;
|
|
152
159
|
constructor(config: UnsharedClientConfig);
|
|
153
|
-
/** Lazily derive + memoize the imported AES-GCM CryptoKey (async via WebCrypto). */
|
|
154
|
-
private _getKey;
|
|
155
|
-
private _encrypt;
|
|
156
160
|
/**
|
|
157
161
|
* Core HTTP method with retry logic.
|
|
158
162
|
* - 2xx → success result
|
|
@@ -161,22 +165,33 @@ export declare class UnsharedClient {
|
|
|
161
165
|
* - Network/timeout → retried, NETWORK_ERROR result returned
|
|
162
166
|
*/
|
|
163
167
|
private _fetch;
|
|
168
|
+
private _postIngestion;
|
|
169
|
+
/** Submit an arbitrary v3 fingerprint payload without reshaping it. */
|
|
170
|
+
submitFingerprint(payload: JSONObject, opts?: {
|
|
171
|
+
idempotencyKey?: string;
|
|
172
|
+
}): Promise<ApiResult<IngestResult>>;
|
|
164
173
|
/**
|
|
165
|
-
* Submit a browser fingerprint event
|
|
166
|
-
*
|
|
174
|
+
* Submit a browser fingerprint event using the source-compatible v2 method.
|
|
175
|
+
* The wire payload and endpoint use the v3 contract.
|
|
167
176
|
*/
|
|
168
177
|
submitFingerprintEvent(fingerprint: FingerprintWireFormat, opts?: SubmitFingerprintOptions): Promise<ApiResult<SubmitFingerprintResult>>;
|
|
169
178
|
/**
|
|
170
|
-
* Record a user event and receive
|
|
171
|
-
* Maps to: POST /
|
|
179
|
+
* Record a user event and receive the current risk decision.
|
|
180
|
+
* Maps to: POST /v3/process-user-event followed by POST /v3/check-user.
|
|
172
181
|
*/
|
|
173
182
|
processUserEvent(params: ProcessUserEventParams, overrides?: {
|
|
174
183
|
timeoutMs?: number;
|
|
175
184
|
maxRetries?: number;
|
|
176
185
|
}): Promise<ApiResult<ProcessUserEventResult>>;
|
|
186
|
+
processUserEvent(payload: JSONObject, overrides?: {
|
|
187
|
+
timeoutMs?: number;
|
|
188
|
+
maxRetries?: number;
|
|
189
|
+
}): Promise<ApiResult<IngestResult>>;
|
|
177
190
|
/**
|
|
178
191
|
* Check if a user is flagged in the naughty list.
|
|
179
|
-
* Maps to:
|
|
192
|
+
* Maps to: POST /v3/check-user
|
|
193
|
+
* Requires a nonblank email_address; user_id cannot replace it. Invalid email
|
|
194
|
+
* returns a local VALIDATION_ERROR without making a request.
|
|
180
195
|
*
|
|
181
196
|
* **Defensive default:** On any failure (network error, 4xx, 5xx, timeout) this
|
|
182
197
|
* method returns `{ success: true, data: { is_user_flagged: false } }` rather
|
|
@@ -190,19 +205,29 @@ export declare class UnsharedClient {
|
|
|
190
205
|
checkUser(emailAddress: string, opts: {
|
|
191
206
|
deviceId?: string;
|
|
192
207
|
fingerprintId?: string;
|
|
208
|
+
permanentDeviceId?: string;
|
|
209
|
+
fullHash?: string;
|
|
210
|
+
sessionHash?: string;
|
|
211
|
+
timeoutMs?: number;
|
|
212
|
+
maxRetries?: number;
|
|
213
|
+
}): Promise<ApiResult<CheckUserResult>>;
|
|
214
|
+
checkUser(request: CheckUserRequest, opts?: {
|
|
193
215
|
timeoutMs?: number;
|
|
194
216
|
maxRetries?: number;
|
|
195
217
|
}): Promise<ApiResult<CheckUserResult>>;
|
|
196
218
|
/**
|
|
197
219
|
* Send a 6-digit verification code to the user's email address.
|
|
198
|
-
* Maps to: POST /
|
|
220
|
+
* Maps to: POST /v3/trigger-email-verification
|
|
199
221
|
*/
|
|
200
222
|
triggerEmailVerification(emailAddress: string, deviceId: string, opts?: {
|
|
201
223
|
fingerprintId?: string;
|
|
224
|
+
permanentDeviceId?: string;
|
|
225
|
+
fullHash?: string;
|
|
226
|
+
sessionHash?: string;
|
|
202
227
|
}): Promise<ApiResult<TriggerEmailVerificationResult>>;
|
|
203
228
|
/**
|
|
204
229
|
* Verify a 6-digit code submitted by the user.
|
|
205
|
-
* Maps to: POST /
|
|
230
|
+
* Maps to: POST /v3/verify
|
|
206
231
|
*
|
|
207
232
|
* `result.success` reliably indicates whether verification succeeded:
|
|
208
233
|
* - `success: true` → code was correct, user is verified
|
|
@@ -219,7 +244,11 @@ export declare class UnsharedClient {
|
|
|
219
244
|
*/
|
|
220
245
|
verify(emailAddress: string, deviceId: string, code: string, opts?: {
|
|
221
246
|
fingerprintId?: string;
|
|
247
|
+
permanentDeviceId?: string;
|
|
248
|
+
verificationId?: string;
|
|
222
249
|
}): Promise<ApiResult<VerifyResult>>;
|
|
250
|
+
/** Verify a v3 email challenge without relying on process-local state. */
|
|
251
|
+
verifyEmail(request: VerifyEmailRequest): Promise<ApiResult<VerifyEmailResult>>;
|
|
223
252
|
/**
|
|
224
253
|
* Fetch the published interstitial flow for this company (secret key, server-side).
|
|
225
254
|
* Returns the projected flow definition to hand to the browser for rendering.
|