Pacote NPM
O SDK @surtai/guardian-web executa o fluxo collect() do Guardian em qualquer navegador moderno. Ele coleta sinais do dispositivo, criptografa-os localmente e retorna um payload opaco que seu backend envia ao endpoint evaluate da Surt. O SDK não faz chamadas de rede e não guarda nenhuma chave de API - com uma exceção: quando você passa um geolocationJwt opcional, collect() faz uma única chamada de melhor esforço GET /geolocation/client-ip para resolver o IP público do navegador e embuti-lo no payload. Sem geolocationJwt, collect() faz zero chamadas de rede. O JWT é um token de curta duração gerado pelo seu backend, não uma chave de API, então ainda não há chave de API no navegador.
npm install @surtai/guardian-web
Diferente dos SDKs nativos (iOS / Android / React Native), o SDK web não expõe um método verify(). Todas as decisões de risco acontecem no lado do servidor assim que seu backend encaminha o payload. Veja Collect (Servidor para Servidor) para o fluxo completo do backend.
Uso
import { collect } from '@surtai/guardian-web';
// Variante 1: sem busca de IP, zero chamadas de rede.
const { payload } = await collect({ collectLocation: false });
// Variante 2: resolve o IP público do navegador para dentro do payload.
// Seu backend gera o JWT de curta duração via preflight.
const { payload: payloadWithIp } = await collect({
collectLocation: false,
geolocationJwt: jwt,
});
// Encaminhe o payload para o seu backend.
await fetch('https://your-api.com/verify-device', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 'user_123', payload }),
});
Seu backend gera o geolocationJwt chamando o preflight com sua chave sp_live_* - veja Autenticação. Passe o JWT para collect() apenas quando quiser resolver o IP público; caso contrário, omita-o.
Opções
| Opção | Tipo | Obrigatório | Padrão | Descrição |
|---|---|---|---|---|
collectLocation | boolean | Não | false | Quando true, solicita a API de Geolocalização do navegador. É a única coisa em collect() que pode acionar um prompt de permissão. |
geolocationJwt | string | Não | (nenhum) | Quando fornecido, collect() resolve o IP público do navegador via GET /geolocation/client-ip e o embute no payload. Omita-o para pular a busca de IP (e todas as chamadas de rede). |
CollectResult
interface CollectResult {
/** Payload em base64. Passe como `payload.data` na requisição evaluate mostrada abaixo. */
payload: string;
/** O que o SDK observou durante a coleta — aditivo, seguro para ignorar. */
diagnostics: {
location?: 'collected' | 'denied' | 'unavailable' | 'timeout' | 'not_requested';
networkIntel?: 'collected' | 'unavailable' | 'not_requested';
warnings: { code: string; signal: string; detail?: string }[];
};
}
O objeto diagnostics informa o que aconteceu no dispositivo — por exemplo, se a localização foi coletada ou se o usuário a negou — para que você reaja na sua interface:
const { payload, diagnostics } = await collect({ collectLocation: true });
if (diagnostics.location === 'denied') {
// peça ao usuário para ativar a localização e tente novamente
}
Veja Diagnóstico do resultado para a referência completa dos campos.
Backend: encaminhar para a Surt
Seu backend recebe o payload do navegador e o envia ao endpoint evaluate da Surt:
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 de collect()>"
},
"config": {
"response": {
"address": { "type": "include" }
}
}
}
O formato da requisição, os valores suportados de transaction_type, as opções de config e o esquema de resposta são idênticos em todos os SDKs do Guardian. Veja Collect (Servidor para Servidor) para a referência completa, incluindo exemplos em Node / Java / Python.
Erros
collect() lança um único GuardianError em três condições:
import { collect, GuardianError } from '@surtai/guardian-web';
try {
const { payload } = await collect();
} catch (err) {
if (err instanceof GuardianError) {
switch (err.code) {
case 'CRYPTO_UNAVAILABLE': /* não está em um contexto seguro */ break;
case 'ENCRYPTION_FAILED': /* a criptografia falhou (raro) */ break;
case 'INVALID_OPTIONS': /* argumentos inválidos */ break;
}
}
}
| Código | Significado |
|---|---|
CRYPTO_UNAVAILABLE | window.crypto.subtle está ausente. O SDK requer um contexto seguro (HTTPS ou localhost). |
ENCRYPTION_FAILED | A etapa de criptografia falhou. Trate como bug: capture e reporte. |
INVALID_OPTIONS | O argumento de collect() não é um objeto. |
Os coletores individuais (impressão digital, bateria, geolocalização, rede, etc.) nunca lançam: falham silenciosamente. Uma permissão revogada ou uma API não suportada apenas significa que o campo correspondente é omitido do payload. A busca do IP público também é de melhor esforço: se a chamada GET /geolocation/client-ip falhar ou o JWT for rejeitado, o campo de IP é simplesmente omitido e collect() ainda retorna um payload. O backend tolera payloads incompletos.
Suporte de navegador
- Qualquer navegador moderno com
window.crypto.subtle(Chrome, Edge, Firefox, Safari, Opera). - Contexto seguro requerido: HTTPS em produção,
localhostpara desenvolvimento. O SDK lançaCRYPTO_UNAVAILABLEfora de um contexto seguro. - Geolocalização, Battery Status, NetworkInformation e outras APIs opcionais degradam com elegância quando indisponíveis.
Exemplos de frameworks
- React
- Next.js
- Vue
- Svelte
- Vanilla JS
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 ? 'Verificando...' : 'Continuar'}
</button>
);
}
'use client';
import { collect } from '@surtai/guardian-web';
export function SignInForm() {
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const { payload } = await collect();
await fetch('/api/sign-in', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: new FormData(e.currentTarget).get('email'),
payload,
}),
});
// Sua rota /api/sign-in encaminha `payload` ao endpoint /evaluate da Surt.
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<button type="submit">Entrar</button>
</form>
);
}
<script setup lang="ts">
import { ref } from 'vue';
import { collect } from '@surtai/guardian-web';
const loading = ref(false);
const props = defineProps<{ userId: string }>();
async function verify() {
loading.value = true;
try {
const { payload } = await collect();
await fetch('/api/verify-device', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: props.userId, payload }),
});
} finally {
loading.value = false;
}
}
</script>
<template>
<button :disabled="loading" @click="verify">
{{ loading ? 'Verificando...' : 'Continuar' }}
</button>
</template>
<script>
import { collect } from '@surtai/guardian-web';
export let userId;
let loading = false;
async function verify() {
loading = true;
try {
const { payload } = await collect();
await fetch('/api/verify-device', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, payload }),
});
} finally {
loading = false;
}
}
</script>
<button on:click={verify} disabled={loading}>
{loading ? 'Verificando...' : 'Continuar'}
</button>
<button id="verify">Continuar</button>
<script type="module">
import { collect } from 'https://esm.sh/@surtai/guardian-web';
document.getElementById('verify').addEventListener('click', async () => {
const { payload } = await collect({ collectLocation: false });
await fetch('/api/verify-device', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 'user_123', payload }),
});
});
</script>
Diferenças em relação aos SDKs nativos
@surtai/guardian-web | @surtai/guardian-rn / iOS / Android | |
|---|---|---|
Método verify() | Não | Sim |
Método collect() | Sim (única API) | Sim |
| Inicialização no nível do app | Nenhuma | initialize(options) |
| Contexto de cliente / transação | Definido pelo seu backend | Carregado nas claims do JWT gerado pelo backend |
| Chave de API no cliente | Não | Não (usa um JWT gerado pelo backend, não uma chave de API) |
| Chamadas de rede do SDK | Nenhuma (a menos que geolocationJwt seja passado) | Sim |
| Verificação de integridade do dispositivo | Não (sem equivalente no navegador) | Sim |
| Estado por cliente persistente | Não (sem estado entre chamadas) | Não |
Web é intencionalmente enxuto: uma única função estática que produz um payload criptografado. A associação ao cliente, os metadados da transação e as decisões acontecem todos no seu backend.