Pular para o conteúdo principal
Versão: Guardian v0.3.1

NPM Package

@surtai/guardian-web · v0.3.0

The @surtai/guardian-web SDK runs Guardian's collect() flow in any modern browser. It gathers device signals, encrypts them locally, and returns an opaque payload your backend sends to Surt's evaluate endpoint. The SDK makes no network calls and holds no API key - with one exception: when you pass an optional geolocationJwt, collect() makes a single best-effort GET /geolocation/client-ip call to resolve the browser's public IP and embed it in the payload. Without geolocationJwt, collect() makes zero network calls. The JWT is a short-lived token minted by your backend, not an API key, so there is still no API key in the browser.

npm install @surtai/guardian-web
Web is collect-only

Unlike the native SDKs (iOS / Android / React Native), the web SDK does not expose a verify() method. All risk decisions happen server-side once your backend forwards the payload. See Collect (Server-to-Server) for the full backend flow.

Usage

TypeScript
import { collect } from '@surtai/guardian-web';

// Variant 1: no IP lookup, zero network calls.
const { payload } = await collect({ collectLocation: false });

// Variant 2: resolve the browser's public IP into the payload.
// Your backend mints the short-lived JWT via preflight.
const { payload: payloadWithIp } = await collect({
collectLocation: false,
geolocationJwt: jwt,
});

// Forward the payload to your backend.
await fetch('https://your-api.com/verify-device', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 'user_123', payload }),
});

Your backend mints the geolocationJwt by calling preflight with your sp_live_* key - see Authentication. Pass the JWT to collect() only when you want the public IP resolved; otherwise omit it.

Options

OptionTypeRequiredDefaultDescription
collectLocationbooleanNofalseWhen true, prompts the browser's Geolocation API. The only thing in collect() that can trigger a permission prompt.
geolocationJwtstringNo(none)When provided, collect() resolves the browser's public IP via GET /geolocation/client-ip and embeds it in the payload. Omit it to skip the IP lookup (and all network calls).

CollectResult

interface CollectResult {
/** Base64 payload. Pass as `payload.data` in the evaluate request shown below. */
payload: string;
}

Backend: forward to Surt

Your backend receives the payload from the browser and posts to Surt's evaluate endpoint:

POST https://api.surt.com/geolocation/transactions/evaluate
Content-Type: application/json
Authorization: Bearer YOUR_SURT_API_KEY
{
"customer_id": "user_123",
"transaction_type": "login",
"transaction_name": "Sign in",
"payload": {
"type": "encrypted",
"data": "<payload from collect()>"
},
"config": {
"response": {
"address": { "type": "include" }
}
}
}

The request shape, supported transaction_type values, config options, and response schema are identical across all Guardian SDKs. See Collect (Server-to-Server) for the complete reference, including Node / Java / Python examples.

Errors

collect() throws a single GuardianError for three conditions:

import { collect, GuardianError } from '@surtai/guardian-web';

try {
const { payload } = await collect();
} catch (err) {
if (err instanceof GuardianError) {
switch (err.code) {
case 'CRYPTO_UNAVAILABLE': /* not in a secure context */ break;
case 'ENCRYPTION_FAILED': /* encryption failed (rare) */ break;
case 'INVALID_OPTIONS': /* bad arguments */ break;
}
}
}
CodeMeaning
CRYPTO_UNAVAILABLEwindow.crypto.subtle is missing. The SDK requires a secure context (HTTPS or localhost).
ENCRYPTION_FAILEDThe encryption step failed. Treat as a bug: capture and report.
INVALID_OPTIONSThe argument to collect() is not an object.

Individual collectors (fingerprint, battery, geolocation, network, etc.) never throw: they fail soft. A revoked permission or unsupported API just means the corresponding field is omitted from the payload. The public-IP lookup is also best-effort: if the GET /geolocation/client-ip call fails or the JWT is rejected, the IP field is simply omitted and collect() still returns a payload. The backend tolerates sparse payloads.

Browser support

  • Any evergreen browser with window.crypto.subtle (Chrome, Edge, Firefox, Safari, Opera).
  • Secure context required: HTTPS in production, localhost for development. The SDK throws CRYPTO_UNAVAILABLE outside a secure context.
  • Geolocation, Battery Status, NetworkInformation, and other optional APIs degrade gracefully when unavailable.

Framework examples

VerifyButton.tsx
import { useState } from 'react';
import { collect } from '@surtai/guardian-web';

export function VerifyButton({ userId }: { userId: string }) {
const [loading, setLoading] = useState(false);

const handleClick = async () => {
setLoading(true);
try {
const { payload } = await collect({ collectLocation: false });
await fetch('/api/verify-device', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, payload }),
});
} finally {
setLoading(false);
}
};

return (
<button onClick={handleClick} disabled={loading}>
{loading ? 'Verifying...' : 'Continue'}
</button>
);
}

What's different from the native SDKs

@surtai/guardian-web@surtai/guardian-rn / iOS / Android
verify() methodNoYes
collect() methodYes (only API)Yes
App-level initializationNoneinitialize(options)
Customer / transaction contextSet by your backendCarried in the backend-minted JWT claims
API key in clientNoNo (uses a backend-minted JWT, not an API key)
Network calls from SDKNone (unless geolocationJwt is passed)Yes
Hardware attestationNo (no browser equivalent)Yes
Persists per-customer stateNo (stateless per call)No

Web is intentionally lean: a single static function that produces an encrypted payload. Customer binding, transaction metadata, and decisions all happen in your backend.