Документация

TrueSift SDK за Svelte 5 и SvelteKit

Тази документация е само за @truesift/svelte, а не за @truesift/next. Svelte SDK използва нативни API на Svelte 5, но следва същия протокол за верификация и визуален дизайн като Next.js SDK. Публикувана версия: 0.2.0.

Изисквания и инсталация

Необходими са Svelte 5 и Node.js >=20.19.0 според metadata на пакета. Примерите са за SvelteKit със сървърни маршрути.

bash
pnpm add @truesift/svelte

Алтернатива: npm install @truesift/svelte.

Импорти и публични entry points

ПътПредназначение
@truesift/svelteКомпонент, контролер и типове за клиента
@truesift/svelte/clientTrueSiftCheck, createTrueSiftCheck и визуални помощни функции
@truesift/svelte/serverHandler-и за маршрути и verifyTrueSiftProof
@truesift/svelte/sharedОбщи протоколни типове
@truesift/svelte/styles.cssНезадължителен stylesheet - компонентът сам импортва стиловете си
ts
import { TrueSiftCheck, createTrueSiftCheck } from '@truesift/svelte/client';
import { createTrueSiftChallengeHandler, createTrueSiftVerifyHandler, verifyTrueSiftProof } from '@truesift/svelte/server';

Не импортирайте /server в клиентски Svelte компоненти.

Сървърни променливи на средата

dotenv
TRUESIFT_API_BASE_URL=https://api.truesift.de
TRUESIFT_SITE_KEY=your_site_key
TRUESIFT_SECRET_KEY=your_secret_key
TRUESIFT_DEFAULT_ACTION=your_registered_action
TRUESIFT_FAIL_OPEN=false
TRUESIFT_TIMEOUT_MS=2500

Base URL е само origin на TrueSift API, без /api/v1. SITE_KEY и SECRET_KEY са задължителни. В SvelteKit тайните се четат чрез $env/dynamic/private или друг защитен сървърен източник и *никога чрез `PUBLIC_**. По подразбиране политиката е **fail-closed (false`)**, а timeout - 2500 ms (валидни стойности 250–30000 ms).

Поток на верификацията

  1. Браузърът изпраща заявка към локалния same-origin маршрут /api/truesift/challenge.
  2. Компонентът извиква /api/truesift/verify и при успешна проверка получава краткоживеещ verificationToken.
  3. Защитеният business route валидира еднократния токен чрез verifyTrueSiftProof със зададени от сървъра Action, Path и Origin.
  4. Действието се изпълнява само след валиден сървърен proof. Клиентски резултат allow не разрешава сам по себе си действие.

Интеграция със SvelteKit

1. Challenge маршрут

Файл: src/routes/api/truesift/challenge/+server.ts

ts
import { env } from '$env/dynamic/private';
import type { RequestHandler } from './$types';
import { createTrueSiftChallengeHandler } from '@truesift/svelte/server';

const handleChallenge = createTrueSiftChallengeHandler({
  apiBaseUrl: env.TRUESIFT_API_BASE_URL,
  siteKey: env.TRUESIFT_SITE_KEY,
  secretKey: env.TRUESIFT_SECRET_KEY,
  defaultAction: env.TRUESIFT_DEFAULT_ACTION,
  failOpen: false
});

export const POST: RequestHandler = ({ request }) => handleChallenge(request);

2. Verify маршрут

Файл: src/routes/api/truesift/verify/+server.ts

ts
import { env } from '$env/dynamic/private';
import type { RequestHandler } from './$types';
import { createTrueSiftVerifyHandler } from '@truesift/svelte/server';

const handleVerify = createTrueSiftVerifyHandler({
  apiBaseUrl: env.TRUESIFT_API_BASE_URL,
  siteKey: env.TRUESIFT_SITE_KEY,
  secretKey: env.TRUESIFT_SECRET_KEY,
  defaultAction: env.TRUESIFT_DEFAULT_ACTION,
  failOpen: false
});

export const POST: RequestHandler = ({ request }) => handleVerify(request);

Двата маршрута очакват JSON POST заявки и прилагат same-origin проверки.

3. Подаване на доверен Action към страницата

Файл: src/routes/contact/+page.server.ts

ts
import { env } from '$env/dynamic/private';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = () => {
  const action = env.TRUESIFT_DEFAULT_ACTION?.trim();
  if (!action) error(500, 'TrueSift action is not configured');
  return { truesiftAction: action };
};

Страницата и защитеният маршрут трябва да съвпадат по Action, Path и Origin.

4. Svelte 5 компонент

Файл: src/routes/contact/+page.svelte

svelte
<script lang="ts">
  import { TrueSiftCheck } from '@truesift/svelte/client';
  import type { TrueSiftClientVerificationResult } from '@truesift/svelte';
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();
  let verificationToken = $state<string | null>(null);
  let submitting = $state(false);
  let message = $state('');

  function handleVerified(result: TrueSiftClientVerificationResult): void {
    verificationToken =
      result.status === 'verified' &&
      result.allowed &&
      result.decision === 'allow'
        ? result.verificationToken ?? null
        : null;
  }

  async function submitContact(): Promise<void> {
    if (!verificationToken || submitting) return;

    const token = verificationToken;
    verificationToken = null; // A verification token cannot be reused.
    submitting = true;
    message = '';

    try {
      const response = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          verificationToken: token
          // Include validated contact fields from your form.
        })
      });

      message = response.ok
        ? 'Verification accepted.'
        : 'Verification could not be completed. Please try again.';
    } catch {
      message = 'The request could not be completed. Please verify again.';
    } finally {
      submitting = false;
    }
  }
</script>

<TrueSiftCheck
  action={data.truesiftAction}
  path="/contact"
  visualMode="checkbox"
  size="normal"
  theme="auto"
  locale="auto"
  onVerified={handleVerified}
  onError={() => (verificationToken = null)}
/>

<button type="button" disabled={!verificationToken || submitting} onclick={submitContact}>
  Submit
</button>
<p role="status">{message}</p>

Забраняването на Submit бутона е само UX ограничение. Защитата реално се налага от сървърния маршрут.

5. Проверка на proof преди бизнес действието

Файл: src/routes/api/contact/+server.ts

ts
import { json } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';
import type { RequestHandler } from './$types';
import { verifyTrueSiftProof } from '@truesift/svelte/server';

export const POST: RequestHandler = async ({ request, url }) => {
  if (request.headers.get('origin') !== url.origin) {
    return json({ error: 'Invalid request origin' }, { status: 403 });
  }

  if (!request.headers.get('content-type')?.includes('application/json')) {
    return json({ error: 'Unsupported content type' }, { status: 415 });
  }

  const body: unknown = await request.json().catch(() => null);
  if (typeof body !== 'object' || body === null || Array.isArray(body)) {
    return json({ error: 'Invalid request' }, { status: 400 });
  }

  const { verificationToken } = body as Record<string, unknown>;
  const expectedAction = env.TRUESIFT_DEFAULT_ACTION?.trim();
  if (!expectedAction) {
    return json({ error: 'Verification is unavailable' }, { status: 503 });
  }

  const proof = await verifyTrueSiftProof(
    {
      verificationToken: typeof verificationToken === 'string' ? verificationToken : '',
      expectedAction,
      expectedPath: '/contact',
      expectedOrigin: url.origin
    },
    {
      apiBaseUrl: env.TRUESIFT_API_BASE_URL,
      siteKey: env.TRUESIFT_SITE_KEY,
      secretKey: env.TRUESIFT_SECRET_KEY,
      failOpen: false
    }
  );

  if (!proof.ok || !proof.allowed || proof.failOpen) {
    return json(
      { error: 'Verification rejected' },
      { status: proof.reason === 'unavailable' ? 503 : 403 }
    );
  }

  // The verification gate has passed. Validate and process the
  // contact request here, using your application's business logic.
  return json({ verified: true });
};

Показаният handler демонстрира единствено TrueSift проверката, не изпраща имейл и не записва формуляр или данни. Реалната бизнес валидация и запис трябва да се изпълнят след успешния proof. expectedAction, expectedPath и expectedOrigin идват само от доверения сървър, не от клиентски JSON.

Визуални режими, размери и език

СвойствоСтойности
visualModecheckbox, banner, inline, badge
sizenormal, compact, flexible, half
themeauto, light, dark
localeauto, en, de, bg

Само за Svelte SDK: size="half" ограничава банера (visualMode="banner") до 500 px максимална ширина и остава адаптивен на малки екрани. Не променя останалите режими. Този размер липсва в Next.js SDK v0.3.0.

svelte
<TrueSiftCheck action={data.truesiftAction} visualMode="banner" size="half" theme="auto" locale="bg" />

При собствен language switcher подавайте избрания език чрез locale, вместо auto. Текстовете могат да се заменят частично чрез labels.

Callback-и и ръчно управление

  • onReady() - извиква се след mount.
  • onVerified(result) - може да се извика и при review, block и fail-open. Клиентският статус не замества сървърния proof.
  • onError(error) - съобщава за грешки при заявки, конфигурация или мрежа.
  • С autoStart={false}, bind:this, start() и reset() можете да контролирате ръчно проверката.
svelte
<script lang="ts">
  import { TrueSiftCheck } from '@truesift/svelte/client';
  import type { TrueSiftCheckActions } from '@truesift/svelte/client';
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();
  let check = $state<TrueSiftCheckActions | undefined>();
</script>

<TrueSiftCheck bind:this={check} action={data.truesiftAction} autoStart={false} />
<button type="button" onclick={() => void check?.start()}>Verify</button>
<button type="button" onclick={() => check?.reset()}>Reset</button>

reset() не прави вече употребен verification token валиден повторно.

Headless контролер за Svelte 5

createTrueSiftCheck работи без визуалния компонент. Предоставя реактивни state, challenge, verification, error, isLoading, както и activate(), start(), reset() и destroy().

svelte
<script lang="ts">
  import { onMount } from 'svelte';
  import { createTrueSiftCheck } from '@truesift/svelte/client';
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();
  const check = createTrueSiftCheck(() => ({
    action: data.truesiftAction,
    path: '/contact'
  }));

  onMount(() => {
    check.activate();
    return () => check.destroy();
  });
</script>

<button type="button" disabled={check.isLoading} onclick={() => void check.start()}>
  Verify
</button>
<p aria-live="polite">{check.state}</p>

Анимация, поверителност и CSP

В пакета са включени локални dotLottie анимация и WebAssembly runtime, поддръжка на reduced-motion и SVG fallback. Не е нужен външен CDN за анимацията. CSP може да изисква script-src 'self' 'wasm-unsafe-eval' за локалния WASM runtime; не добавяйте без нужда 'unsafe-eval'.

Behavioral Signals v2

@truesift/svelte@0.2.0 използва агрегирани Behavioral Signals v2, съвместими с протокола на @truesift/next@0.4.0. По-старите v1 сигнали продължават да се приемат. След challenge Svelte 5 контролерът използва прекъсваем прозорец за наблюдение от 2500 ms (измерван от началото на опита за проверка), с отделна базова линия за взаимодействията за всеки challenge. Reset, unmount и отмяна прекратяват изчакването и премахват слушателите на събития.

Временните агрегати могат да включват telemetryVersion: 2, elapsedMs, interactionCount, challengeInteractionCount, pointerMoveCount, pointerDownCount, scrollCount, touchCount, keyPressCount, focusCount, blurCount, visibilityChangeCount, firstInteractionDelayMs и activeDurationMs. Движенията на курсора и scroll се семплират през 75 ms, а броячите са ограничени до 1000. Отчет за нулево взаимодействие се изпраща само след най-малко 1000 ms видимо наблюдение. Отделен сигнал не доказва дали посетителят е човек или бот.

Не се записват натиснати клавиши, координати, стойности от формуляри, DOM цели, fingerprints или постоянни идентификатори. До сървъра през same-origin SvelteKit BFF се предават само агрегати. Метаданните referrer и currentPageUrl съдържат само origin, без пътища, query параметри или fragments. По подразбиране proof path е pathname без query; изрично зададен path се запазва за съвместимост със съществуващите expectedPath договори. Не поставяйте токени в персонализирани пътища.

Защита на SvelteKit BFF във версия 0.2.0

  • Challenge и verify маршрутите приемат само JSON POST, проверяват строго браузърния Origin и same-origin Fetch Metadata, когато е подадена, и ограничават тялото до 32 KiB. Reverse proxy трябва да запазва публичния origin.
  • Поведенческите полета се валидират и ограничават; управлявани от клиента контекстни стойности не се включват в предупрежденията към upstream логовете. Сурови съобщения за upstream грешки не се разкриват.
  • Външните upstream сървъри изискват HTTPS; HTTP е допустим само за локален loopback (localhost, 127.0.0.1, [::1]).
  • BFF отговорите включват Cache-Control: no-store и X-Content-Type-Options: nosniff.
  • Противоречиви решения за проверка и невалидни proof отговори се отхвърлят; протоколна грешка не може да се превърне във fail-open.
  • Защитеното бизнес действие изисква проверен и еднократно използван proof на сървъра; клиентско решение или телеметрия не дават разрешение.

Промени във версия 0.2.0 (12.10.2026)

Добавени са Behavioral Signals v2, прекъсваемо наблюдение, по-строги Origin и размерни ограничения, защита на URL метаданните, безопасна обработка на upstream грешки и допълнителни регресионни тестове. Публичният Svelte 5 API и same-origin POST/JSON интеграцията са запазени.

Резултати от proof и правила за сигурност

  • ok: true, allowed: true, decision: 'allow', failOpen: false - доказано разрешена проверка.
  • reason: 'missing' | 'invalid' | 'expired' | 'replayed' | 'mismatch' - отказ.
  • decision: 'review' | 'block' заедно с reason: 'blocked' - отказ.
  • reason: 'unavailable' - при failOpen: false отказ; при failOpen: true няма авторитетен proof, дори allowed: true.

Изпълнявайте защитено действие само при proof.ok && proof.allowed && !proof.failOpen. Токените са краткоживеещи и еднократни. Освен TrueSift запазете проверките за CSRF, авторизация, rate limiting и валидиране на бизнес данните.

Лиценз

Кодът на SDK е под MIT License. Включената анимация е създадена от Maxence Chanel и се разпространява отделно под Lottie Simple License, а не под MIT лиценза на WebDigiTech.