@usesophi/sophi-web-sdk
v0.1.0-beta.6
Published
Framework-agnostic SDK for embedding Sophi AI widget
Readme
Sophi Web SDK
A framework-agnostic, TypeScript-first SDK for embedding the Sophi AI widget into any web application. Works seamlessly with vanilla JavaScript, React, Vue, Svelte, Angular, Next.js, and more.
Features
- 🚀 Framework Agnostic - Works with any JavaScript framework or vanilla JS
- 📦 Zero Dependencies - Lightweight with no runtime dependencies
- 🔒 Secure - Built-in authentication and origin validation
- 🔑 Automatic Session Management - Handles authentication automatically
- 💪 TypeScript First - Full type definitions included
- 🎯 Event-Driven - Clean event-based API for loose coupling
- 📱 Modern - ESM and CommonJS support with tree-shaking
- 🎨 Flexible - Client controls all UX (toasts, errors, styling)
- 📁 Well-Structured - Modular architecture for easy maintenance
Security Note
Q: Will users see my API URLs?
A: Yes, and that's okay!
API URLs in client-side code are always visible to users (they can see them in browser DevTools network tab). This is normal and safe because:
- ✅ API URLs themselves aren't secrets - they're just endpoints
- ✅ Real security comes from your
apiKeyandclientIdvalidation on the server - ✅ Your API should authenticate and authorize every request
- ✅ Never put actual secrets (API keys, tokens) in client code - they come from your backend
Think of API URLs like a street address - it's public information. The security is the lock on the door (your API authentication).
Installation
npm install sophi-web-sdkOr with yarn:
yarn add sophi-web-sdkOr with pnpm:
pnpm add sophi-web-sdkQuick Start
Vanilla JavaScript
import { SophiWidget } from "sophi-web-sdk";
const widget = new SophiWidget();
// Initialize with authentication (async)
await widget.init({
apiKey: "your-api-key", // From parent app
clientId: "your-client-id", // From parent app
userId: "user-12345", // Current user ID
container: "#sophi-widget",
environment: "production", // API URL determined automatically
metadata: {
// Optional metadata
theme: "dark",
plan: "premium",
},
onReady: () => console.log("Widget ready!"),
});
// Listen for events
widget.on("add_to_cart", (data) => {
console.log("Products to add:", data.products);
// Handle cart logic here
});Note: The
init()method is now async and automatically handles authentication with the Sophi API before loading the widget. The API URL is determined automatically based on theenvironmentsetting.
React
import { useEffect, useRef } from "react";
import { SophiWidget } from "sophi-web-sdk";
function MyComponent() {
const widgetRef = useRef<SophiWidget | null>(null);
useEffect(() => {
const widget = new SophiWidget();
widgetRef.current = widget;
// Async initialization
const initWidget = async () => {
try {
await widget.init({
apiKey: "your-api-key",
clientId: "your-client-id",
userId: "user-12345",
container: "#sophi-widget",
environment: "production",
metadata: { theme: "dark" },
});
widget.on("add_to_cart", (data) => {
// Handle add to cart
});
} catch (error) {
console.error("Failed to initialize widget:", error);
}
};
initWidget();
return () => widget.destroy();
}, []);
return <div id="sophi-widget" />;
}See examples/react-example/ for a complete React example with custom hooks.
Vue 3
<template>
<div id="sophi-widget"></div>
</template>
<script setup>
import { onMounted, onUnmounted } from "vue";
import { SophiWidget } from "sophi-web-sdk";
let widget = null;
onMounted(() => {
widget = new SophiWidget();
widget.init({
apiKey: "your-api-key",
container: "#sophi-widget",
});
widget.on("add_to_cart", (data) => {
// Handle add to cart
});
});
onUnmounted(() => {
if (widget) {
widget.destroy();
}
});
</script>Svelte
<script>
import { onMount, onDestroy } from 'svelte';
import { SophiWidget } from 'sophi-web-sdk';
let widget;
onMount(() => {
widget = new SophiWidget();
widget.init({
apiKey: 'your-api-key',
container: '#sophi-widget',
});
widget.on('add_to_cart', (data) => {
// Handle add to cart
});
});
onDestroy(() => {
if (widget) {
widget.destroy();
}
});
</script>
<div id="sophi-widget"></div>Angular
import { Component, OnInit, OnDestroy } from "@angular/core";
import { SophiWidget } from "sophi-web-sdk";
@Component({
selector: "app-widget",
template: '<div id="sophi-widget"></div>',
})
export class WidgetComponent implements OnInit, OnDestroy {
private widget: SophiWidget | null = null;
ngOnInit() {
this.widget = new SophiWidget();
this.widget.init({
apiKey: "your-api-key",
container: "#sophi-widget",
});
this.widget.on("add_to_cart", (data) => {
// Handle add to cart
});
}
ngOnDestroy() {
if (this.widget) {
this.widget.destroy();
}
}
}API Reference
SophiWidget
The main class for interacting with the Sophi widget.
init(config: SophiConfig): Promise<void>
Initialize the widget with configuration. This method is now async and handles authentication automatically.
await widget.init({
// Required fields
apiKey: "your-api-key", // Your API key (x-api-key header)
clientId: "your-client-id", // Client ID (x-sophi-client-id header)
userId: "user-12345", // External user ID from your app
container: "#sophi-widget", // CSS selector or HTMLElement
// Optional fields
environment: "production", // 'test' | 'staging' | 'production' (determines API URL)
metadata: {
// Custom metadata for the session
theme: "dark",
plan: "premium",
},
width: "100%", // Widget width (default: '100%')
height: "600px", // Widget height (default: '600px')
onReady: () => {}, // Callback when widget is ready
onError: (error) => {}, // Callback for errors
});Authentication Flow:
- SDK validates configuration
- SDK determines API URL based on
environmentsetting - SDK calls authentication API with
apiKey,clientId, anduserId - API returns session data (accessToken, sessionId, etc.)
- SDK creates iframe and sends session data via postMessage
- Widget is ready to use
Environment to API URL Mapping:
test: Test API URL (for development/testing)staging: Staging API URLproduction: Production API URL (default)
See AUTHENTICATION.md for detailed authentication documentation.
sendUserData(data: UserData): void
Send user data to the widget via postMessage.
widget.sendUserData({
userId: "user-123",
email: "[email protected]",
name: "John Doe",
// Add any custom fields
customField: "value",
});on<K extends EventName>(event: K, handler: EventHandler<SophiEventMap[K]>): void
Register an event listener.
// Listen for add_to_cart events
widget.on("add_to_cart", (data) => {
console.log("Products:", data.products);
});
// Listen for ready events
widget.on("ready", () => {
console.log("Widget is ready");
});
// Listen for error events
widget.on("error", (error) => {
console.error("Widget error:", error);
});off<K extends EventName>(event: K, handler: EventHandler<SophiEventMap[K]>): void
Unregister an event listener.
const handler = (data) => console.log(data);
widget.on("add_to_cart", handler);
widget.off("add_to_cart", handler);show(): void
Show the widget (sets display to 'block').
widget.show();hide(): void
Hide the widget (sets display to 'none').
widget.hide();destroy(): void
Destroy the widget and cleanup all resources.
widget.destroy();isReady(): boolean
Check if the widget is initialized and ready.
if (widget.isReady()) {
widget.sendUserData({ userId: "123" });
}TypeScript Support
The SDK is written in TypeScript and includes full type definitions.
import { SophiWidget, SophiConfig, AddToCartEvent, UserData } from "sophi-web-sdk";
const config: SophiConfig = {
apiKey: "your-api-key",
container: "#widget",
};
const widget = new SophiWidget();
widget.init(config);
widget.on("add_to_cart", (data: AddToCartEvent) => {
data.products.forEach((product) => {
console.log(product.productId, product.variantId);
});
});Events
add_to_cart
Fired when the widget requests to add products to the cart.
interface AddToCartEvent {
products: Array<{
productId: string;
variantId?: string;
}>;
}
widget.on("add_to_cart", (data: AddToCartEvent) => {
// Handle cart logic
// Show toast notifications
// Update cart UI
});ready
Fired when the widget iframe is ready.
widget.on("ready", () => {
console.log("Widget is ready");
});error
Fired when an error occurs.
widget.on("error", (error: Error) => {
console.error("Error:", error.message);
});Security
The SDK includes comprehensive security features:
- Authentication: Automatic session creation with JWT tokens
- Origin Validation: All postMessage events are validated against the iframe origin
- Message Validation: Incoming messages are validated for correct structure
- Type Safety: TypeScript ensures type-safe message handling
- Secure Headers: API key and client ID sent via secure headers
- Token Expiration: Session tokens include expiration timestamps
See AUTHENTICATION.md for security best practices.
Documentation
- AUTHENTICATION.md - Detailed authentication flow and API documentation
- FILE_STRUCTURE.md - Project structure and architecture
- GETTING_STARTED.md - Complete getting started guide
- QUICK_START_TESTING.md - Quick testing guide
- TESTING.md - Comprehensive testing documentation
Examples
Check out the examples/ directory for complete working examples:
examples/vanilla-html/- Pure HTML/JS example with no build stepexamples/react-example/- React example with custom hooks
To run the examples:
# Vanilla HTML example
cd examples/vanilla-html
# Open index.html in your browser
# React example
cd examples/react-example
npm install
npm run devBoth examples include the new authentication flow with all required fields.
Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT © Sophi
Support
For issues and questions, please open an issue on GitHub or contact [email protected].
