Guides
Svelte Integration
Store-based patterns for Svelte and SvelteKit applications
Svelte Integration
This guide covers Svelte-specific patterns including stores, SvelteKit integration, form actions, and SSR considerations. Svelte’s reactive stores pair naturally with BotSigged’s real-time updates.
Installation
npm install @botsigged/sdk
# or
yarn add @botsigged/sdk
# or
pnpm add @botsigged/sdk
Svelte Store
Creating the Store
// lib/stores/botsigged.ts
import { writable, derived, get } from 'svelte/store';
import { browser } from '$app/environment';
import { BotSigged, ScoreUpdate } from '@botsigged/sdk';
function createBotSiggedStore() {
const sdk = writable<BotSigged | null>(null);
const score = writable<ScoreUpdate | null>(null);
const isConnected = writable(false);
const isReady = writable(false);
function init(apiKey: string) {
if (!browser) return;
const instance = new BotSigged({
apiKey,
autoStart: true,
onScoreUpdate: (newScore) => {
score.set(newScore);
isReady.set(true);
},
onConnectionChange: (connected) => {
isConnected.set(connected);
},
});
sdk.set(instance);
}
async function waitUntilReady() {
const instance = get(sdk);
return instance?.waitUntilReady();
}
function getSessionId() {
const instance = get(sdk);
return instance?.getSessionId();
}
function canSubmit() {
const instance = get(sdk);
return instance?.canSubmit() ?? { allowed: false, reason: 'SDK not initialized' };
}
async function stop() {
const instance = get(sdk);
await instance?.stop();
sdk.set(null);
isReady.set(false);
}
// Derived stores
const botScore = derived(score, ($score) => $score?.bot_score ?? 0);
const classification = derived(score, ($score) => $score?.classification ?? 'unknown');
const isBot = derived(botScore, ($botScore) => $botScore > 70);
return {
// State
sdk,
score,
isConnected,
isReady,
botScore,
classification,
isBot,
// Methods
init,
waitUntilReady,
getSessionId,
canSubmit,
stop,
};
}
export const botSigged = createBotSiggedStore();
Initializing in Layout
<!-- routes/+layout.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
import { botSigged } from '$lib/stores/botsigged';
import { PUBLIC_BOTSIGGED_KEY } from '$env/static/public';
onMount(() => {
botSigged.init(PUBLIC_BOTSIGGED_KEY);
});
</script>
<slot />
Form Protection
Basic Protected Form
<!-- components/SignupForm.svelte -->
<script lang="ts">
import { botSigged } from '$lib/stores/botsigged';
let email = '';
let isSubmitting = false;
let error: string | null = null;
async function handleSubmit() {
error = null;
isSubmitting = true;
try {
const result = await botSigged.waitUntilReady();
if (result?.score && result.score > 70) {
error = 'Unable to verify your request. Please try again.';
return;
}
await fetch('/api/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
} finally {
isSubmitting = false;
}
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<input bind:value={email} type="email" required />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Sign Up'}
</button>
{#if error}
<p class="error">{error}</p>
{/if}
</form>
Reactive Submit Button
<script lang="ts">
import { botSigged } from '$lib/stores/botsigged';
const { isReady, isBot } = botSigged;
$: buttonText = !$isReady ? 'Verifying...' : $isBot ? 'Blocked' : 'Submit';
$: buttonDisabled = !$isReady || $isBot;
</script>
<button type="submit" disabled={buttonDisabled}>
{buttonText}
</button>
Form Action Component
Create a reusable action for form protection:
// lib/actions/protectedSubmit.ts
import { botSigged } from '$lib/stores/botsigged';
interface ProtectedSubmitOptions {
threshold?: number;
onBlocked?: (score: number) => void;
}
export function protectedSubmit(node: HTMLFormElement, options: ProtectedSubmitOptions = {}) {
const { threshold = 70, onBlocked } = options;
async function handleSubmit(event: SubmitEvent) {
event.preventDefault();
const result = await botSigged.waitUntilReady();
if (result?.score && result.score > threshold) {
onBlocked?.(result.score);
return;
}
// Re-dispatch the submit event
node.dispatchEvent(new CustomEvent('protectedSubmit', {
detail: new FormData(node),
}));
}
node.addEventListener('submit', handleSubmit);
return {
destroy() {
node.removeEventListener('submit', handleSubmit);
},
update(newOptions: ProtectedSubmitOptions) {
// Update options if needed
},
};
}
Usage:
<script lang="ts">
import { protectedSubmit } from '$lib/actions/protectedSubmit';
function onSubmit(event: CustomEvent<FormData>) {
const formData = event.detail;
// Process form data
}
function onBlocked(score: number) {
alert(`Blocked with score: ${score}`);
}
</script>
<form
use:protectedSubmit={{ threshold: 60, onBlocked }}
on:protectedSubmit={onSubmit}
>
<input name="email" type="email" />
<button type="submit">Submit</button>
</form>
SvelteKit Integration
Form Actions
Integrate with SvelteKit’s form actions:
<!-- routes/signup/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import { botSigged } from '$lib/stores/botsigged';
import type { ActionData } from './$types';
export let form: ActionData;
let isSubmitting = false;
</script>
<form
method="POST"
use:enhance={async ({ formData, cancel }) => {
isSubmitting = true;
const result = await botSigged.waitUntilReady();
if (result?.score && result.score > 70) {
cancel();
isSubmitting = false;
return;
}
// Add session ID to form data
formData.append('sessionId', botSigged.getSessionId() ?? '');
return async ({ update }) => {
await update();
isSubmitting = false;
};
}}
>
<input name="email" type="email" required />
<button disabled={isSubmitting}>Sign Up</button>
</form>
{#if form?.error}
<p class="error">{form.error}</p>
{/if}
Server-side action:
// routes/signup/+page.server.ts
import type { Actions } from './$types';
import { fail } from '@sveltejs/kit';
export const actions: Actions = {
default: async ({ request }) => {
const data = await request.formData();
const email = data.get('email');
const sessionId = data.get('sessionId');
// Optionally verify session server-side
if (sessionId) {
const session = await fetch(`${BOTSIGGED_API}/sessions/${sessionId}`);
const { bot_score } = await session.json();
if (bot_score > 70) {
return fail(403, { error: 'Request blocked' });
}
}
// Process signup
return { success: true };
},
};
Hooks for Server-Side Verification
// hooks.server.ts
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
const sessionId = event.request.headers.get('X-BotSigged-Session');
if (sessionId && event.url.pathname.startsWith('/api/')) {
// Verify session with BotSigged API
try {
const response = await fetch(`${BOTSIGGED_API}/sessions/${sessionId}`);
const { bot_score } = await response.json();
if (bot_score > 80) {
return new Response('Forbidden', { status: 403 });
}
} catch {
// Continue if verification fails
}
}
return resolve(event);
};
Load Function Integration
// routes/checkout/+page.ts
import type { PageLoad } from './$types';
import { browser } from '$app/environment';
import { redirect } from '@sveltejs/kit';
import { botSigged } from '$lib/stores/botsigged';
import { get } from 'svelte/store';
export const load: PageLoad = async () => {
if (browser) {
const result = await botSigged.waitUntilReady();
if (result?.score && result.score > 80) {
throw redirect(307, '/blocked');
}
}
return {};
};
Score Display Component
<!-- components/BotScoreIndicator.svelte -->
<script lang="ts">
import { botSigged } from '$lib/stores/botsigged';
const { score, isConnected, isReady, classification } = botSigged;
$: statusClass = !$isConnected
? 'disconnected'
: !$isReady
? 'loading'
: $classification;
</script>
<div class="score-indicator {statusClass}">
{#if !$isConnected}
Disconnected
{:else if !$isReady}
Analyzing...
{:else}
<span>Score: {$score?.bot_score ?? 0}</span>
<span>{$classification}</span>
{/if}
</div>
<style>
.score-indicator {
padding: 0.5rem 1rem;
border-radius: 4px;
}
.human { background: #d4edda; }
.suspicious { background: #fff3cd; }
.bot { background: #f8d7da; }
.disconnected, .loading { background: #e2e3e5; }
</style>
Superforms Integration
If using Superforms for form handling:
<script lang="ts">
import { superForm } from 'sveltekit-superforms/client';
import { botSigged } from '$lib/stores/botsigged';
export let data;
const { form, errors, enhance, submitting } = superForm(data.form, {
onSubmit: async ({ cancel }) => {
const result = await botSigged.waitUntilReady();
if (result?.score && result.score > 70) {
cancel();
// Show error
}
},
});
</script>
<form method="POST" use:enhance>
<input name="email" bind:value={$form.email} />
{#if $errors.email}
<span class="error">{$errors.email}</span>
{/if}
<button disabled={$submitting}>Submit</button>
</form>
Context API Alternative
For component-scoped state:
<!-- routes/+layout.svelte -->
<script lang="ts">
import { setContext, onMount } from 'svelte';
import { writable } from 'svelte/store';
import { BotSigged } from '@botsigged/sdk';
import { PUBLIC_BOTSIGGED_KEY } from '$env/static/public';
const sdk = writable<BotSigged | null>(null);
const score = writable(null);
setContext('botSigged', { sdk, score });
onMount(() => {
const instance = new BotSigged({
apiKey: PUBLIC_BOTSIGGED_KEY,
onScoreUpdate: (s) => score.set(s),
});
sdk.set(instance);
return () => instance.stop();
});
</script>
<slot />
Child components:
<script lang="ts">
import { getContext } from 'svelte';
const { sdk, score } = getContext('botSigged');
</script>
Runes (Svelte 5)
Using Svelte 5 runes for reactive state:
// lib/botsigged.svelte.ts
import { BotSigged, ScoreUpdate } from '@botsigged/sdk';
class BotSiggedState {
sdk = $state<BotSigged | null>(null);
score = $state<ScoreUpdate | null>(null);
isConnected = $state(false);
isReady = $state(false);
botScore = $derived(this.score?.bot_score ?? 0);
classification = $derived(this.score?.classification ?? 'unknown');
isBot = $derived(this.botScore > 70);
init(apiKey: string) {
this.sdk = new BotSigged({
apiKey,
autoStart: true,
onScoreUpdate: (s) => {
this.score = s;
this.isReady = true;
},
onConnectionChange: (c) => {
this.isConnected = c;
},
});
}
async waitUntilReady() {
return this.sdk?.waitUntilReady();
}
getSessionId() {
return this.sdk?.getSessionId();
}
}
export const botSigged = new BotSiggedState();
Usage:
<script lang="ts">
import { botSigged } from '$lib/botsigged.svelte';
async function handleSubmit() {
if (botSigged.isBot) {
alert('Blocked');
return;
}
// submit
}
</script>
<p>Score: {botSigged.botScore}</p>
<button disabled={botSigged.isBot}>Submit</button>
Troubleshooting
SSR Errors
The SDK must only run in the browser. Always check:
import { browser } from '$app/environment';
if (browser) {
botSigged.init(apiKey);
}
Store Not Reactive
Ensure you’re using the $ prefix to subscribe:
<!-- Wrong -->
{botSigged.score}
<!-- Correct -->
{$botSigged.score}
Or with destructuring:
<script>
const { score } = botSigged;
</script>
{$score}
Memory Leaks
Clean up on component unmount:
<script>
import { onDestroy } from 'svelte';
const unsubscribe = botSigged.score.subscribe((s) => {
// handle score
});
onDestroy(unsubscribe);
</script>
Or use auto-subscription with $:
<!-- Auto-unsubscribes when component is destroyed -->
{$score}