Vue Integration
SPA-specific patterns for Vue 3 and Nuxt
Vue Integration
This guide covers integration patterns for Vue 3 with Composition API and Nuxt 3. Vue’s reactivity system pairs naturally with BotSigged’s real-time score updates.
Installation
npm install @botsigged/sdk
# or
yarn add @botsigged/sdk
# or
pnpm add @botsigged/sdk
Vue 3 Setup
Composable
Create a composable to manage the SDK instance:
// composables/useBotSigged.ts
import { ref, onMounted, onUnmounted, readonly } from 'vue';
import { BotSigged, ScoreUpdate } from '@botsigged/sdk';
const sdk = ref<BotSigged | null>(null);
const score = ref<ScoreUpdate | null>(null);
const isConnected = ref(false);
const isReady = ref(false);
let initialized = false;
export function useBotSigged(apiKey?: string) {
onMounted(() => {
if (initialized || !apiKey) return;
initialized = true;
sdk.value = new BotSigged({
apiKey,
autoStart: true,
onScoreUpdate: (newScore) => {
score.value = newScore;
isReady.value = true;
},
onConnectionChange: (connected) => {
isConnected.value = connected;
},
});
});
onUnmounted(() => {
// Keep SDK alive across component unmounts
// Only stop on full app teardown
});
const waitUntilReady = async () => {
return sdk.value?.waitUntilReady();
};
const getSessionId = () => sdk.value?.getSessionId();
const canSubmit = () => sdk.value?.canSubmit();
return {
sdk: readonly(sdk),
score: readonly(score),
isConnected: readonly(isConnected),
isReady: readonly(isReady),
waitUntilReady,
getSessionId,
canSubmit,
};
}
Plugin (Optional)
For global access, create a Vue plugin:
// plugins/botsigged.ts
import { App, ref } from 'vue';
import { BotSigged, ScoreUpdate } from '@botsigged/sdk';
export const botSiggedPlugin = {
install(app: App, options: { apiKey: string }) {
const sdk = new BotSigged({
apiKey: options.apiKey,
autoStart: true,
});
app.config.globalProperties.$botSigged = sdk;
app.provide('botSigged', sdk);
},
};
Register in your app:
// main.ts
import { createApp } from 'vue';
import { botSiggedPlugin } from './plugins/botsigged';
import App from './App.vue';
createApp(App)
.use(botSiggedPlugin, { apiKey: import.meta.env.VITE_BOTSIGGED_KEY })
.mount('#app');
App.vue Setup
Initialize the SDK at the app root:
<!-- App.vue -->
<script setup lang="ts">
import { useBotSigged } from '@/composables/useBotSigged';
// Initialize once at app root
useBotSigged(import.meta.env.VITE_BOTSIGGED_KEY);
</script>
<template>
<RouterView />
</template>
Form Protection
Basic Protected Form
<script setup lang="ts">
import { ref } from 'vue';
import { useBotSigged } from '@/composables/useBotSigged';
const { waitUntilReady, score, isReady } = useBotSigged();
const email = ref('');
const isSubmitting = ref(false);
const error = ref<string | null>(null);
async function handleSubmit() {
error.value = null;
isSubmitting.value = true;
try {
const result = await waitUntilReady();
if (result?.score && result.score > 70) {
error.value = 'Unable to verify your request. Please try again.';
return;
}
await fetch('/api/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email.value }),
});
} finally {
isSubmitting.value = false;
}
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<input v-model="email" type="email" required />
<button type="submit" :disabled="isSubmitting">
{{ isSubmitting ? 'Submitting...' : 'Sign Up' }}
</button>
<p v-if="error" class="error">{{ error }}</p>
</form>
</template>
Conditional Submit Button
<script setup lang="ts">
import { computed } from 'vue';
import { useBotSigged } from '@/composables/useBotSigged';
const { canSubmit, isReady, score } = useBotSigged();
const submitState = computed(() => {
if (!isReady.value) return { disabled: true, text: 'Verifying...' };
if (score.value && score.value.bot_score > 70) return { disabled: true, text: 'Blocked' };
return { disabled: false, text: 'Submit' };
});
</script>
<template>
<button type="submit" :disabled="submitState.disabled">
{{ submitState.text }}
</button>
</template>
Reusable Form Wrapper
Create a component that wraps any form with protection:
<!-- ProtectedForm.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import { useBotSigged } from '@/composables/useBotSigged';
const props = defineProps<{
threshold?: number;
}>();
const emit = defineEmits<{
submit: [data: FormData];
blocked: [score: number];
}>();
const { waitUntilReady } = useBotSigged();
const isSubmitting = ref(false);
async function handleSubmit(e: Event) {
const form = e.target as HTMLFormElement;
isSubmitting.value = true;
try {
const result = await waitUntilReady();
const threshold = props.threshold ?? 70;
if (result?.score && result.score > threshold) {
emit('blocked', result.score);
return;
}
emit('submit', new FormData(form));
} finally {
isSubmitting.value = false;
}
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<slot :isSubmitting="isSubmitting" />
</form>
</template>
Usage:
<template>
<ProtectedForm @submit="onSubmit" @blocked="onBlocked" v-slot="{ isSubmitting }">
<input name="email" type="email" />
<button :disabled="isSubmitting">Submit</button>
</ProtectedForm>
</template>
Pinia Store
For complex applications, manage BotSigged state in Pinia:
// stores/botsigged.ts
import { defineStore } from 'pinia';
import { BotSigged, ScoreUpdate } from '@botsigged/sdk';
export const useBotSiggedStore = defineStore('botsigged', {
state: () => ({
sdk: null as BotSigged | null,
score: null as ScoreUpdate | null,
isConnected: false,
isReady: false,
}),
getters: {
botScore: (state) => state.score?.bot_score ?? 0,
classification: (state) => state.score?.classification ?? 'unknown',
isBot: (state) => (state.score?.bot_score ?? 0) > 70,
},
actions: {
init(apiKey: string) {
if (this.sdk) return;
this.sdk = new BotSigged({
apiKey,
autoStart: true,
onScoreUpdate: (score) => {
this.score = score;
this.isReady = true;
},
onConnectionChange: (connected) => {
this.isConnected = connected;
},
});
},
async waitUntilReady() {
return this.sdk?.waitUntilReady();
},
async stop() {
await this.sdk?.stop();
this.sdk = null;
this.isReady = false;
},
},
});
Usage in components:
<script setup lang="ts">
import { useBotSiggedStore } from '@/stores/botsigged';
import { storeToRefs } from 'pinia';
const store = useBotSiggedStore();
const { score, isReady, isBot } = storeToRefs(store);
async function handleSubmit() {
if (store.isBot) {
alert('Submission blocked');
return;
}
await store.waitUntilReady();
// submit form
}
</script>
Nuxt 3
Plugin Setup
Create a Nuxt plugin for client-side initialization:
// plugins/botsigged.client.ts
import { BotSigged } from '@botsigged/sdk';
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig();
const sdk = new BotSigged({
apiKey: config.public.botsiggedKey,
autoStart: true,
});
return {
provide: {
botSigged: sdk,
},
};
});
Configure the runtime key:
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public: {
botsiggedKey: process.env.NUXT_PUBLIC_BOTSIGGED_KEY,
},
},
});
Composable for Nuxt
// composables/useBotSigged.ts
export function useBotSigged() {
const { $botSigged } = useNuxtApp();
const score = ref<ScoreUpdate | null>(null);
const isReady = ref(false);
onMounted(() => {
// Subscribe to score updates
$botSigged.onScoreUpdate?.((newScore) => {
score.value = newScore;
isReady.value = true;
});
});
return {
sdk: $botSigged,
score: readonly(score),
isReady: readonly(isReady),
waitUntilReady: () => $botSigged?.waitUntilReady(),
getSessionId: () => $botSigged?.getSessionId(),
};
}
Middleware Protection
Protect routes based on bot score:
// middleware/bot-check.ts
export default defineNuxtRouteMiddleware(async (to) => {
// Only run on client
if (import.meta.server) return;
const { $botSigged } = useNuxtApp();
const result = await $botSigged?.waitUntilReady();
if (result?.score && result.score > 80) {
return navigateTo('/blocked');
}
});
Apply to specific pages:
<!-- pages/checkout.vue -->
<script setup lang="ts">
definePageMeta({
middleware: ['bot-check'],
});
</script>
VeeValidate Integration
<script setup lang="ts">
import { useForm } from 'vee-validate';
import { useBotSigged } from '@/composables/useBotSigged';
import * as yup from 'yup';
const { waitUntilReady, getSessionId } = useBotSigged();
const schema = yup.object({
email: yup.string().email().required(),
message: yup.string().min(10).required(),
});
const { handleSubmit, errors, isSubmitting } = useForm({
validationSchema: schema,
});
const onSubmit = handleSubmit(async (values) => {
const result = await waitUntilReady();
if (result?.score && result.score > 70) {
throw new Error('Submission blocked');
}
await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-BotSigged-Session': getSessionId() ?? '',
},
body: JSON.stringify(values),
});
});
</script>
<template>
<form @submit="onSubmit">
<input name="email" />
<span>{{ errors.email }}</span>
<textarea name="message" />
<span>{{ errors.message }}</span>
<button :disabled="isSubmitting">Send</button>
</form>
</template>
Score Display Component
<script setup lang="ts">
import { computed } from 'vue';
import { useBotSigged } from '@/composables/useBotSigged';
const { score, isConnected, isReady } = useBotSigged();
const statusClass = computed(() => {
if (!isConnected.value) return 'disconnected';
if (!isReady.value) return 'loading';
return score.value?.classification ?? 'unknown';
});
</script>
<template>
<div :class="['score-indicator', statusClass]">
<template v-if="!isConnected">Disconnected</template>
<template v-else-if="!isReady">Analyzing...</template>
<template v-else>
<span>Score: {{ score?.bot_score ?? 0 }}</span>
<span>{{ score?.classification }}</span>
</template>
</div>
</template>
<style scoped>
.score-indicator {
padding: 0.5rem 1rem;
border-radius: 4px;
}
.human { background: #d4edda; }
.suspicious { background: #fff3cd; }
.bot { background: #f8d7da; }
.disconnected, .loading { background: #e2e3e5; }
</style>
Troubleshooting
SSR Hydration Mismatch
The SDK must only run on the client. In Nuxt, use .client.ts suffix for plugins or check import.meta.client:
if (import.meta.client) {
// Initialize SDK
}
Reactivity Not Updating
Ensure you’re using ref() for SDK state and updating values correctly:
// Wrong - won't trigger reactivity
score = newScore;
// Correct
score.value = newScore;
Multiple Instances
Guard against multiple initializations:
let initialized = false;
export function useBotSigged(apiKey?: string) {
if (initialized) return existingState;
initialized = true;
// ... initialize
}