@touchque/web 0.1.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 +27 -0
- package/LICENSE +21 -0
- package/README.md +191 -0
- package/dist/index.d.mts +228 -0
- package/dist/index.d.ts +228 -0
- package/dist/index.js +393 -0
- package/dist/index.mjs +383 -0
- package/dist/touchque-behavioral.global.js +1 -0
- package/package.json +65 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. The
|
|
4
|
+
format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
## [0.1.0] — 2026-08-27
|
|
7
|
+
|
|
8
|
+
Initial release.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `createTouchQueWeb({ baseUrl, paths?, credentials?, headers?, fetch? })` —
|
|
12
|
+
a browser client that runs WebAuthn ceremonies and posts the JSON to the
|
|
13
|
+
partner's own relay backend (never TouchQue directly).
|
|
14
|
+
- `passkeys.register()`, `passkeys.authenticate({ email })`,
|
|
15
|
+
`passkeys.approveLogin({ requestId })`, `passkeys.list()`,
|
|
16
|
+
`passkeys.remove(id)`.
|
|
17
|
+
- `authenticate()` classifies the flow-control outcomes (`ok` /
|
|
18
|
+
`requiresStepUp` / `pending2fa`) and returns the relay's untouched body as
|
|
19
|
+
`raw`.
|
|
20
|
+
- Typed errors: `PasskeyDismissedError`, `PasskeyNotRegisteredError`,
|
|
21
|
+
`PasskeyDisabledError`, `TouchQueWebAPIError`, `TouchQueWebError`.
|
|
22
|
+
- `isPasskeySupported()` / `isPlatformAuthenticatorAvailable()`.
|
|
23
|
+
- Behavioral biometrics widget: `tq.behavioral.attach(...)` /
|
|
24
|
+
`attachBehavioral(...)` (container-scoped mouse / click / keystroke-timing
|
|
25
|
+
telemetry). Also shipped as a standalone `<script>` build
|
|
26
|
+
(`dist/touchque-behavioral.global.js`, `window.TouchQueBehavioral`),
|
|
27
|
+
superseding the private `@touchque/behavioral-widget` package.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TouchQue
|
|
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,191 @@
|
|
|
1
|
+
# @touchque/web
|
|
2
|
+
|
|
3
|
+
Browser SDK for [TouchQue Authenticator](https://touchque.com) — passkey
|
|
4
|
+
sign-in / registration and behavioral telemetry.
|
|
5
|
+
|
|
6
|
+
It runs the WebAuthn ceremonies in the browser and posts the collected JSON
|
|
7
|
+
to **your own backend**, which relays it to TouchQue with its API key. The
|
|
8
|
+
browser never holds the API key and never calls TouchQue's `/webauthn/*`
|
|
9
|
+
endpoints directly.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @touchque/web
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## The relay pattern
|
|
16
|
+
|
|
17
|
+
`@touchque/web` talks only to your backend (`baseUrl`). Your backend forwards
|
|
18
|
+
each call to the TouchQue Authenticator API using the server SDK
|
|
19
|
+
[`@touchque/node`](https://www.npmjs.com/package/@touchque/node) (or a signed
|
|
20
|
+
HTTP call). You expose these routes — the paths are configurable, these are
|
|
21
|
+
the defaults:
|
|
22
|
+
|
|
23
|
+
| SDK method | Default browser path (`baseUrl` + …) | Relays to (`@touchque/node`) |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `passkeys.register()` step 1 | `POST /passkey/register/options` | `tq.webauthn.registerOptions({ externalUsername, discoverable: true })` |
|
|
26
|
+
| `passkeys.register()` step 2 | `POST /passkey/register/verify` | `tq.webauthn.registerVerify({ externalUsername, response, label })` |
|
|
27
|
+
| `passkeys.authenticate()` step 1 | `POST /passkey/authenticate/options` | `tq.webauthn.primaryOptions({ externalUsername: body.email })` |
|
|
28
|
+
| `passkeys.authenticate()` step 2 | `POST /passkey/authenticate/verify` | `tq.webauthn.primaryVerify({ attemptId, response })` |
|
|
29
|
+
| `passkeys.approveLogin()` step 1 | `POST /passkey/login/options` | `tq.webauthn.authenticateOptions({ requestId })` |
|
|
30
|
+
| `passkeys.approveLogin()` step 2 | `POST /passkey/login/verify` | `tq.webauthn.authenticateVerify({ requestId, response })` |
|
|
31
|
+
| `passkeys.list()` | `GET /passkey/credentials` | `tq.webauthn.listCredentials({ externalUsername })` |
|
|
32
|
+
| `passkeys.remove(id)` | `DELETE /passkey/credentials/:id` | `tq.webauthn.deleteCredential(id)` |
|
|
33
|
+
|
|
34
|
+
Your backend is responsible for authenticating the browser (a session cookie,
|
|
35
|
+
a bearer token, …) and for deriving `externalUsername` from that session — the
|
|
36
|
+
SDK never sends it for `register` / `list` / `remove`.
|
|
37
|
+
|
|
38
|
+
### Minimal Express relay
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const express = require('express');
|
|
42
|
+
const { TouchQue } = require('@touchque/node');
|
|
43
|
+
|
|
44
|
+
const tq = new TouchQue({ apiKey: process.env.TQ_API_KEY, apiSecret: process.env.TQ_API_SECRET });
|
|
45
|
+
const app = express();
|
|
46
|
+
app.use(express.json());
|
|
47
|
+
|
|
48
|
+
// your own auth middleware sets req.user
|
|
49
|
+
app.post('/passkey/authenticate/options', async (req, res) => {
|
|
50
|
+
try {
|
|
51
|
+
res.json(await tq.webauthn.primaryOptions({ externalUsername: req.body.email }));
|
|
52
|
+
} catch (e) {
|
|
53
|
+
res.status(e.status || 502).json({ error: e.data?.error || 'failed' });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
app.post('/passkey/authenticate/verify', async (req, res) => {
|
|
57
|
+
const r = await tq.webauthn.primaryVerify({ attemptId: req.body.attemptId, response: req.body.response });
|
|
58
|
+
if (r.success) {
|
|
59
|
+
// issue your session here, then:
|
|
60
|
+
res.json({ status: 'success', redirect_url: '/dashboard' });
|
|
61
|
+
} else if (r.requiresStepUp) {
|
|
62
|
+
res.json({ requiresStepUp: true });
|
|
63
|
+
} else {
|
|
64
|
+
res.status(401).json({ error: 'verification_failed' });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
app.post('/passkey/register/options', requireLogin, async (req, res) => {
|
|
69
|
+
res.json(await tq.webauthn.registerOptions({ externalUsername: req.user.email, discoverable: true }));
|
|
70
|
+
});
|
|
71
|
+
app.post('/passkey/register/verify', requireLogin, async (req, res) => {
|
|
72
|
+
res.json(await tq.webauthn.registerVerify({
|
|
73
|
+
externalUsername: req.user.email, response: req.body.response, label: req.body.label,
|
|
74
|
+
}));
|
|
75
|
+
});
|
|
76
|
+
app.get('/passkey/credentials', requireLogin, async (req, res) => {
|
|
77
|
+
res.json(await tq.webauthn.listCredentials({ externalUsername: req.user.email }));
|
|
78
|
+
});
|
|
79
|
+
app.delete('/passkey/credentials/:id', requireLogin, async (req, res) => {
|
|
80
|
+
res.json(await tq.webauthn.deleteCredential(req.params.id));
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Browser usage
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { createTouchQueWeb, PasskeyNotRegisteredError } from '@touchque/web';
|
|
88
|
+
|
|
89
|
+
const tq = createTouchQueWeb({
|
|
90
|
+
baseUrl: 'https://api.example.com',
|
|
91
|
+
// send cookies if your relay authenticates the browser with a session cookie
|
|
92
|
+
credentials: 'include',
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Passwordless sign-in
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
try {
|
|
100
|
+
const result = await tq.passkeys.authenticate({
|
|
101
|
+
email,
|
|
102
|
+
context: { client_id, redirect_uri }, // merged into both relay calls
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (result.ok) {
|
|
106
|
+
// signed in — result.raw is your relay's untouched response
|
|
107
|
+
window.location.href = result.raw.redirect_url as string;
|
|
108
|
+
} else if (result.requiresStepUp) {
|
|
109
|
+
startPushOrNumberMatchFlow();
|
|
110
|
+
} else if (result.pending2fa) {
|
|
111
|
+
continuePending2faFlow(result.raw);
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
if (err instanceof PasskeyNotRegisteredError) {
|
|
115
|
+
showPasswordForm(); // this account has no passkey
|
|
116
|
+
}
|
|
117
|
+
// ... see the error table below
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Register a passkey (signed-in user)
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
await tq.passkeys.register({ label: 'MacBook Touch ID' });
|
|
125
|
+
const passkeys = await tq.passkeys.list();
|
|
126
|
+
await tq.passkeys.remove(passkeys[0].id);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Approve a pending login with a passkey (second factor)
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
await tq.passkeys.approveLogin({ requestId });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Feature detection
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { isPasskeySupported, isPlatformAuthenticatorAvailable } from '@touchque/web';
|
|
139
|
+
|
|
140
|
+
if (!isPasskeySupported()) hidePasskeyButton();
|
|
141
|
+
if (await isPlatformAuthenticatorAvailable()) preferPlatformUx();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Behavioral biometrics widget
|
|
145
|
+
|
|
146
|
+
This is the one call that goes **directly** from the browser to TouchQue,
|
|
147
|
+
gated by a per-request `telemetryToken` your backend mints server-side
|
|
148
|
+
(`POST /login/request` with `TenantPolicy.behavioralBiometricsEnabled`).
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
const widget = tq.behavioral.attach('#tq-2fa-box', {
|
|
152
|
+
telemetryToken: SERVER.telemetryToken,
|
|
153
|
+
requestId: SERVER.requestId,
|
|
154
|
+
apiBaseUrl: 'https://api-authenticator.touchque.com',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// call periodically and/or right before the challenge resolves
|
|
158
|
+
await widget.submit();
|
|
159
|
+
// on unmount
|
|
160
|
+
widget.stop();
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Or, without a bundler, via `<script>`:
|
|
164
|
+
|
|
165
|
+
```html
|
|
166
|
+
<script src="https://unpkg.com/@touchque/web/dist/touchque-behavioral.global.js"></script>
|
|
167
|
+
<script>
|
|
168
|
+
const widget = TouchQueBehavioral.attach('#tq-2fa-box', { telemetryToken, requestId, apiBaseUrl });
|
|
169
|
+
</script>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The widget only observes the container element (mouse movement, click count,
|
|
173
|
+
inter-keystroke timing) — never `window`/`document`, and never a key or
|
|
174
|
+
character. A failed telemetry POST is swallowed and never surfaces on your
|
|
175
|
+
2FA flow.
|
|
176
|
+
|
|
177
|
+
## Errors
|
|
178
|
+
|
|
179
|
+
| Class | When | Typical handling |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `PasskeyDismissedError` | User closed the system prompt (`NotAllowedError` / `AbortError`) | Let them retry or use a password |
|
|
182
|
+
| `PasskeyNotRegisteredError` | Relay returned 404 / `no_passkey_registered` | Fall back to the password form |
|
|
183
|
+
| `PasskeyDisabledError` | Relay returned 403 / `passwordless_login_disabled` | Fall back to the password form |
|
|
184
|
+
| `TouchQueWebAPIError` | Any other non-2xx from the relay (`.status`, `.body`) | Show a generic error |
|
|
185
|
+
| `TouchQueWebError` | Base class / an unexpected ceremony failure | Show a generic error |
|
|
186
|
+
|
|
187
|
+
`requiresStepUp` and `pending2fa` are **not** errors — they come back as
|
|
188
|
+
fields on the `authenticate()` result.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The relay endpoint paths on the *partner's own backend*. The browser SDK
|
|
3
|
+
* never talks to TouchQue directly for passkey ceremonies — the partner
|
|
4
|
+
* backend holds the API key and relays to TouchQue.
|
|
5
|
+
*
|
|
6
|
+
* Every path is appended to `baseUrl`. Override any subset; the rest keep
|
|
7
|
+
* their defaults.
|
|
8
|
+
*/
|
|
9
|
+
interface TouchQuePaths {
|
|
10
|
+
/** POST — returns `PublicKeyCredentialCreationOptionsJSON` (at the top level). */
|
|
11
|
+
registerOptions: string;
|
|
12
|
+
/** POST — body `{ response, ...extra }`; returns `{ verified, credentialId }`. */
|
|
13
|
+
registerVerify: string;
|
|
14
|
+
/** POST — body `{ email, ...context }`; returns `{ attemptId, options }`. */
|
|
15
|
+
authenticateOptions: string;
|
|
16
|
+
/** POST — body `{ attemptId, response, ...context }`; returns the relay's passthrough. */
|
|
17
|
+
authenticateVerify: string;
|
|
18
|
+
/** POST — body `{ requestId }`; returns `PublicKeyCredentialRequestOptionsJSON`. */
|
|
19
|
+
approveOptions: string;
|
|
20
|
+
/** POST — body `{ requestId, response }`; returns `{ success }`. */
|
|
21
|
+
approveVerify: string;
|
|
22
|
+
/** GET — returns `{ credentials: PasskeySummary[] }`. */
|
|
23
|
+
list: string;
|
|
24
|
+
/** DELETE `${remove}/${id}` — returns `{ deleted: true }`. */
|
|
25
|
+
remove: string;
|
|
26
|
+
}
|
|
27
|
+
interface TouchQueWebConfig {
|
|
28
|
+
/** Base URL of the partner's own relay backend, e.g. `https://api.example.com`. */
|
|
29
|
+
baseUrl: string;
|
|
30
|
+
/** Override any subset of the relay paths. */
|
|
31
|
+
paths?: Partial<TouchQuePaths>;
|
|
32
|
+
/**
|
|
33
|
+
* Whether the SDK's relay requests send credentials (cookies). Use
|
|
34
|
+
* `'include'` when the relay authenticates the browser with a cookie
|
|
35
|
+
* session. Default: `'same-origin'`.
|
|
36
|
+
*/
|
|
37
|
+
credentials?: RequestCredentials;
|
|
38
|
+
/** Extra headers to attach to every relay request (e.g. a CSRF token). */
|
|
39
|
+
headers?: Record<string, string>;
|
|
40
|
+
/** Injectable fetch implementation. Defaults to the global `fetch`. */
|
|
41
|
+
fetch?: typeof fetch;
|
|
42
|
+
}
|
|
43
|
+
interface PasskeySummary {
|
|
44
|
+
id: string;
|
|
45
|
+
credentialId: string;
|
|
46
|
+
deviceType: string | null;
|
|
47
|
+
backedUp: boolean;
|
|
48
|
+
label: string | null;
|
|
49
|
+
createdAt: string;
|
|
50
|
+
lastUsedAt: string | null;
|
|
51
|
+
}
|
|
52
|
+
interface RegisterResult {
|
|
53
|
+
verified: boolean;
|
|
54
|
+
credentialId: string;
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Result of `passkeys.authenticate()`. The SDK does not interpret a
|
|
59
|
+
* successful passwordless login for you — `raw` is whatever the relay
|
|
60
|
+
* returned (a redirect URL, a session, etc.). The SDK only classifies the
|
|
61
|
+
* three flow-control outcomes.
|
|
62
|
+
*/
|
|
63
|
+
interface AuthenticateResult {
|
|
64
|
+
/** `true` when the assertion verified and no step-up is required. */
|
|
65
|
+
ok: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* `true` when risk/policy demands a second factor. The caller should start
|
|
68
|
+
* the normal push / number-match flow instead of trusting the assertion.
|
|
69
|
+
*/
|
|
70
|
+
requiresStepUp: boolean;
|
|
71
|
+
/** `true` when the relay wants the caller to continue a pending 2FA flow. */
|
|
72
|
+
pending2fa: boolean;
|
|
73
|
+
/** The relay's untouched response body. */
|
|
74
|
+
raw: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
interface ApproveResult {
|
|
77
|
+
success: boolean;
|
|
78
|
+
[key: string]: unknown;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Thin fetch wrapper for the partner relay backend. Joins `baseUrl` + path,
|
|
83
|
+
* sends/parses JSON, and normalizes the relay's error responses into the
|
|
84
|
+
* SDK's error classes.
|
|
85
|
+
*/
|
|
86
|
+
declare class RelayHttp {
|
|
87
|
+
private readonly baseUrl;
|
|
88
|
+
private readonly credentials;
|
|
89
|
+
private readonly headers;
|
|
90
|
+
private readonly fetchImpl;
|
|
91
|
+
constructor(config: TouchQueWebConfig);
|
|
92
|
+
post<T>(path: string, body?: unknown): Promise<T>;
|
|
93
|
+
get<T>(path: string): Promise<T>;
|
|
94
|
+
del<T>(path: string): Promise<T>;
|
|
95
|
+
private request;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
declare class Passkeys {
|
|
99
|
+
private readonly http;
|
|
100
|
+
private readonly paths;
|
|
101
|
+
constructor(http: RelayHttp, paths: TouchQuePaths);
|
|
102
|
+
/**
|
|
103
|
+
* Register a passkey for the currently signed-in user.
|
|
104
|
+
*
|
|
105
|
+
* `extra` is merged into the verify request body — use it for a device
|
|
106
|
+
* label, for example: `register({ label: 'MacBook Touch ID' })`.
|
|
107
|
+
*/
|
|
108
|
+
register(extra?: Record<string, unknown>): Promise<RegisterResult>;
|
|
109
|
+
/**
|
|
110
|
+
* Passwordless-primary sign-in: authenticate a user from zero with a
|
|
111
|
+
* passkey. `context` is merged into both relay calls (pass OAuth params,
|
|
112
|
+
* a redirect URI, etc.).
|
|
113
|
+
*
|
|
114
|
+
* Inspect the result: `ok` = signed in; `requiresStepUp` = start a push /
|
|
115
|
+
* number-match flow; `pending2fa` = continue a pending 2FA flow. `raw` is
|
|
116
|
+
* the relay's untouched response.
|
|
117
|
+
*/
|
|
118
|
+
authenticate(input: {
|
|
119
|
+
email: string;
|
|
120
|
+
context?: Record<string, unknown>;
|
|
121
|
+
}): Promise<AuthenticateResult>;
|
|
122
|
+
/**
|
|
123
|
+
* Approve an already-pending login request (second-factor flow) with a
|
|
124
|
+
* passkey, instead of the mobile push.
|
|
125
|
+
*/
|
|
126
|
+
approveLogin(input: {
|
|
127
|
+
requestId: string;
|
|
128
|
+
}): Promise<ApproveResult>;
|
|
129
|
+
/** List the signed-in user's registered passkeys (metadata only). */
|
|
130
|
+
list(): Promise<PasskeySummary[]>;
|
|
131
|
+
/** Remove one of the signed-in user's passkeys by its record id. */
|
|
132
|
+
remove(id: string): Promise<{
|
|
133
|
+
deleted: boolean;
|
|
134
|
+
}>;
|
|
135
|
+
}
|
|
136
|
+
/** `true` if the current browser exposes the WebAuthn API at all. */
|
|
137
|
+
declare function isPasskeySupported(): boolean;
|
|
138
|
+
/** `true` if a built-in platform authenticator (Touch ID / Windows Hello / …) is usable. */
|
|
139
|
+
declare function isPlatformAuthenticatorAvailable(): Promise<boolean>;
|
|
140
|
+
|
|
141
|
+
interface BehavioralMetrics {
|
|
142
|
+
mouseDistance: number;
|
|
143
|
+
mouseJitter: number;
|
|
144
|
+
clicks: number;
|
|
145
|
+
keystrokes: number;
|
|
146
|
+
keystrokeSpeedAvg: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface AttachBehavioralOptions {
|
|
150
|
+
/** Per-request token minted by the partner backend. Required. */
|
|
151
|
+
telemetryToken: string;
|
|
152
|
+
/** The pending login request id this telemetry belongs to. Required. */
|
|
153
|
+
requestId: string;
|
|
154
|
+
/** TouchQue API base URL, e.g. `https://api-authenticator.touchque.com`. */
|
|
155
|
+
apiBaseUrl: string;
|
|
156
|
+
/** Opt in to canvas/device fingerprinting (bigger privacy footprint). */
|
|
157
|
+
collectDeviceFingerprint?: boolean;
|
|
158
|
+
/** Sampling interval (ms) for the internal tracker. Default 1000. */
|
|
159
|
+
sampleIntervalMs?: number;
|
|
160
|
+
/** Injectable fetch implementation. Defaults to the global `fetch`. */
|
|
161
|
+
fetch?: typeof fetch;
|
|
162
|
+
}
|
|
163
|
+
interface BehavioralHandle {
|
|
164
|
+
/** Stop tracking and remove the container listeners. */
|
|
165
|
+
stop(): void;
|
|
166
|
+
/** Zero the accumulated metrics without detaching. */
|
|
167
|
+
reset(): void;
|
|
168
|
+
/** Send the current metrics to TouchQue. Best-effort, never throws. */
|
|
169
|
+
submit(): Promise<void>;
|
|
170
|
+
}
|
|
171
|
+
declare function attachBehavioral(target: string | Element, options: AttachBehavioralOptions): BehavioralHandle;
|
|
172
|
+
|
|
173
|
+
/** Base class for every error thrown by this SDK. */
|
|
174
|
+
declare class TouchQueWebError extends Error {
|
|
175
|
+
constructor(message: string);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The WebAuthn ceremony did not complete because the user dismissed the
|
|
179
|
+
* system prompt (or the browser aborted it). Maps `NotAllowedError` /
|
|
180
|
+
* `AbortError` from `navigator.credentials.*`.
|
|
181
|
+
*/
|
|
182
|
+
declare class PasskeyDismissedError extends TouchQueWebError {
|
|
183
|
+
constructor(message?: string);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The account has no passkey registered. The relay backend returned 404 (or
|
|
187
|
+
* `error: "no_passkey_registered"`). The caller should fall back to another
|
|
188
|
+
* sign-in method (e.g. password).
|
|
189
|
+
*/
|
|
190
|
+
declare class PasskeyNotRegisteredError extends TouchQueWebError {
|
|
191
|
+
constructor(message?: string);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Passwordless sign-in is not enabled for this tenant/integration. The relay
|
|
195
|
+
* backend returned 403 (or `error: "passwordless_login_disabled"`).
|
|
196
|
+
*/
|
|
197
|
+
declare class PasskeyDisabledError extends TouchQueWebError {
|
|
198
|
+
constructor(message?: string);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The relay backend returned a non-2xx response that is not one of the
|
|
202
|
+
* specific cases above. `status` is the HTTP status, `body` is the parsed
|
|
203
|
+
* response body (if any).
|
|
204
|
+
*/
|
|
205
|
+
declare class TouchQueWebAPIError extends TouchQueWebError {
|
|
206
|
+
readonly status: number;
|
|
207
|
+
readonly body: unknown;
|
|
208
|
+
constructor(status: number, body: unknown, message?: string);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface TouchQueWeb {
|
|
212
|
+
passkeys: Passkeys;
|
|
213
|
+
behavioral: {
|
|
214
|
+
/**
|
|
215
|
+
* Attach the behavioral biometrics widget to a container. Unlike the
|
|
216
|
+
* passkey calls, this posts directly to TouchQue (`apiBaseUrl`), gated
|
|
217
|
+
* by the per-request `telemetryToken`.
|
|
218
|
+
*/
|
|
219
|
+
attach(target: string | Element, options: AttachBehavioralOptions): BehavioralHandle;
|
|
220
|
+
};
|
|
221
|
+
/** `true` if the browser exposes the WebAuthn API. */
|
|
222
|
+
isPasskeySupported(): boolean;
|
|
223
|
+
/** `true` if a built-in platform authenticator is usable. */
|
|
224
|
+
isPlatformAuthenticatorAvailable(): Promise<boolean>;
|
|
225
|
+
}
|
|
226
|
+
declare function createTouchQueWeb(config: TouchQueWebConfig): TouchQueWeb;
|
|
227
|
+
|
|
228
|
+
export { type ApproveResult, type AttachBehavioralOptions, type AuthenticateResult, type BehavioralHandle, type BehavioralMetrics, PasskeyDisabledError, PasskeyDismissedError, PasskeyNotRegisteredError, type PasskeySummary, type RegisterResult, type TouchQuePaths, type TouchQueWeb, TouchQueWebAPIError, type TouchQueWebConfig, TouchQueWebError, attachBehavioral, createTouchQueWeb, isPasskeySupported, isPlatformAuthenticatorAvailable };
|