
Company News
Socket Joins New OpenJS Program to Fund Node.js Security Work
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.
@embeddable/sdk
Advanced tools
A TypeScript/JavaScript SDK with React utilities and hooks for embeddable applications
A TypeScript/JavaScript SDK with React utilities and hooks for embeddable applications. Built with tree shaking support and modern development practices.
npm install @embeddable/sdk
# or
yarn add @embeddable/sdk
# or
pnpm add @embeddable/sdk
import { useLocalStorage, debounce } from '@embeddable/sdk';
// Import only hooks
import { useLocalStorage } from '@embeddable/sdk/hooks';
// Import only utilities
import { debounce } from '@embeddable/sdk/utils';
The SDK supports global configuration through the EmbeddableProvider context. This allows you to set a widgetId and version and mode once and access them throughout your application.
Wrap your application with the EmbeddableProvider:
import { EmbeddableProvider } from '@embeddable/sdk';
import type { EmbeddableConfig } from '@embeddable/sdk';
function App() {
const config: EmbeddableConfig = {
widgetId: 'my-widget-123',
version: '1.0.0', // 'dev' | 'latest' | string
mode: 'embeddable', // 'embeddable' | 'standalone' | 'preview'
ignoreCache: false,
lazyLoad: false,
loader: true,
_containerId: 'my-container-id',
_shadowRoot: undefined,
};
return (
<EmbeddableProvider config={config}>
<YourAppComponents />
</EmbeddableProvider>
);
}
Access the global configuration in any component:
import { useEmbeddableConfig, useApi } from '@embeddable/sdk';
function WidgetComponent() {
const config = useEmbeddableConfig();
return (
<div>
<h2>Widget: {config.widgetId}</h2>
<p>Version: {config.version}</p>
<p>Mode: {config.mode}</p>
<p>Ignore Cache: {config.ignoreCache ? 'Yes' : 'No'}</p>
<p>Lazy Load: {config.lazyLoad ? 'Yes' : 'No'}</p>
<p>Show Loader: {config.loader ? 'Yes' : 'No'}</p>
{/* Your widget content */}
</div>
);
}
useLocalStorage<T>(key: string, initialValue: T, options?: LocalStorageOptions)A React hook for localStorage with state synchronization across tabs.
import { useLocalStorage } from '@embeddable/sdk/hooks'
function MyComponent() {
const [user, setUser, removeUser] = useLocalStorage('user', { name: '', email: '' })
return (
<div>
<input
value={user.name}
onChange={(e) => setUser(prev => ({ ...prev, name: e.target.value }))}
/>
<button onClick={removeUser}>Clear</button>
</div>
)
}
useDebounce<T>(value: T, delay: number)A React hook that debounces a value.
import { useDebounce } from '@embeddable/sdk/hooks'
function SearchInput() {
const [searchTerm, setSearchTerm] = useState('')
const debouncedSearchTerm = useDebounce(searchTerm, 300)
useEffect(() => {
if (debouncedSearchTerm) {
// Perform search
}
}, [debouncedSearchTerm])
return <input onChange={(e) => setSearchTerm(e.target.value)} />
}
useEmbeddableConfig()A React hook to access the global SDK configuration.
import { useEmbeddableConfig } from '@embeddable/sdk/hooks';
function MyWidget() {
const config = useEmbeddableConfig();
return (
<div>
<h3>Widget ID: {config.widgetId}</h3>
<p>Version: {config.version}</p>
<p>Mode: {config.mode}</p>
</div>
);
}
Note: This hook must be used within an EmbeddableProvider.
useFormSubmission<TPayload>(options?: FormSubmissionOptions)A React hook for handling form submissions with loading states, error handling, and payload validation. Automatically uses the widget ID from the global configuration.
import { useFormSubmission } from '@embeddable/sdk/hooks';
// Define your form data type
interface ContactForm {
name: string;
email: string;
message: string;
}
function ContactFormComponent() {
const [formData, setFormData] = useState<ContactForm>({
name: '',
email: '',
message: ''
});
const { submit, loading, error, success, reset } = useFormSubmission<ContactForm>({
collectionName: 'contact-forms', // Optional, defaults to 'submissions'
validatePayload: (payload) => {
if (!payload.email) return 'Email is required';
if (!payload.name) return 'Name is required';
if (!payload.message) return 'Message is required';
return null; // No validation errors
}
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const result = await submit(formData);
if (result.success) {
console.log('Form submitted successfully!', result.id);
// Reset form or show success message
setFormData({ name: '', email: '', message: '' });
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Name"
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
/>
<input
type="email"
placeholder="Email"
value={formData.email}
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
/>
<textarea
placeholder="Message"
value={formData.message}
onChange={(e) => setFormData(prev => ({ ...prev, message: e.target.value }))}
/>
<button type="submit" disabled={loading}>
{loading ? 'Submitting...' : 'Submit'}
</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
{success && <p style={{ color: 'green' }}>Message sent successfully!</p>}
<button type="button" onClick={reset}>Reset Status</button>
</form>
);
}
Options:
collectionName?: string - The collection name for submissions (defaults to 'submissions')validatePayload?: (payload: any) => string | null - Optional validation functionReturns:
submit: (payload: TPayload) => Promise<FormSubmissionResponse> - Function to submit the formloading: boolean - Whether the submission is in progresserror: string | null - Error message if submission failedsuccess: boolean - Whether the submission was successfulreset: () => void - Function to reset the hook stateuseVote(options?: VoteOptions)A React hook for handling votes with loading states and error handling. Automatically uses the widget ID from the global configuration.
import { useVote } from '@embeddable/sdk/hooks';
function VotingComponent() {
const { vote, loading, error, success, reset } = useVote({
collectionName: 'product-poll' // Optional, defaults to 'votes'
});
const handleVote = async (optionId: string) => {
const result = await vote(optionId);
if (result.success) {
console.log('Vote submitted!', result.id);
}
};
return (
<div>
<h3>Which product do you prefer?</h3>
<button
onClick={() => handleVote('product-a')}
disabled={loading}
>
Product A
</button>
<button
onClick={() => handleVote('product-b')}
disabled={loading}
>
Product B
</button>
<button
onClick={() => handleVote('product-c')}
disabled={loading}
>
Product C
</button>
{loading && <p>Submitting your vote...</p>}
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
{success && <p style={{ color: 'green' }}>Thank you for voting!</p>}
<button onClick={reset}>Reset</button>
</div>
);
}
// Example with dynamic voting options
function DynamicVotingComponent() {
const { vote, loading, error, success } = useVote({
collectionName: 'feature-requests'
});
const features = [
{ id: 'dark-mode', name: 'Dark Mode' },
{ id: 'mobile-app', name: 'Mobile App' },
{ id: 'api-access', name: 'API Access' },
];
const handleFeatureVote = async (featureId: string, featureName: string) => {
const result = await vote(featureId);
if (result.success) {
alert(`Thank you for voting for ${featureName}!`);
}
};
return (
<div>
<h3>Vote for the next feature:</h3>
{features.map(feature => (
<button
key={feature.id}
onClick={() => handleFeatureVote(feature.id, feature.name)}
disabled={loading}
style={{
margin: '5px',
padding: '10px',
opacity: loading ? 0.6 : 1
}}
>
{feature.name}
</button>
))}
{loading && <p>Processing vote...</p>}
{error && <p style={{ color: 'red' }}>{error}</p>}
{success && <p style={{ color: 'green' }}>Vote recorded!</p>}
</div>
);
}
Options:
collectionName?: string - The collection name for votes (defaults to 'votes')Returns:
vote: (voteFor: string) => Promise<VoteResponse> - Function to submit a voteloading: boolean - Whether the vote submission is in progresserror: string | null - Error message if vote submission failedsuccess: boolean - Whether the vote was submitted successfullyreset: () => void - Function to reset the hook stateNote: Both useFormSubmission and useVote hooks must be used within an EmbeddableProvider as they automatically retrieve the widgetId from the global configuration.
useVoteAggregations(options?: UseVoteAggregationsOptions)A React hook for fetching vote aggregations with loading states, error handling, and optional auto-refresh. Automatically uses the widget ID from the global configuration.
import { useVoteAggregations } from '@embeddable/sdk/hooks';
function VoteResultsComponent() {
const { data, loading, error, refetch, reset } = useVoteAggregations({
collectionName: 'product-poll', // Optional, defaults to 'votes'
autoFetch: true, // Optional, defaults to true
refetchInterval: 30000, // Optional, refetch every 30 seconds
});
if (loading) {
return <div>Loading vote results...</div>;
}
if (error) {
return (
<div>
<p style={{ color: 'red' }}>Error: {error}</p>
<button onClick={refetch}>Retry</button>
</div>
);
}
if (!data) {
return <div>No vote data available</div>;
}
return (
<div>
<h3>Vote Results</h3>
<div>
<p><strong>Total Votes:</strong> {data.summary.totalVotes}</p>
<p><strong>Total Options:</strong> {data.summary.totalOptions}</p>
</div>
<div>
{data.results.map((result) => (
<div key={result._id} style={{ margin: '10px 0', padding: '10px', border: '1px solid #ccc' }}>
<h4>{result._id}</h4>
<p>Votes: {result.count}</p>
{result.percentage && <p>Percentage: {result.percentage.toFixed(1)}%</p>}
<div style={{ width: '100%', backgroundColor: '#e0e0e0', borderRadius: '4px' }}>
<div
style={{
width: `${result.percentage || 0}%`,
backgroundColor: '#4caf50',
height: '20px',
borderRadius: '4px',
transition: 'width 0.3s ease'
}}
/>
</div>
</div>
))}
</div>
<button onClick={refetch}>Refresh Results</button>
<button onClick={reset}>Reset</button>
</div>
);
}
// Example with manual fetching
function ManualVoteResultsComponent() {
const { data, loading, error, fetch } = useVoteAggregations({
collectionName: 'feature-requests',
autoFetch: false, // Don't fetch automatically
});
const handleLoadResults = async () => {
const response = await fetch();
if (response.success) {
console.log('Results loaded:', response.data);
}
};
return (
<div>
<button onClick={handleLoadResults} disabled={loading}>
{loading ? 'Loading...' : 'Load Vote Results'}
</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
{data && (
<div>
<h3>Feature Request Votes</h3>
<p>Total: {data.summary.totalVotes} votes</p>
<ul>
{data.results.map(result => (
<li key={result._id}>
{result._id}: {result.count} votes
</li>
))}
</ul>
</div>
)}
</div>
);
}
// Example with real-time updates
function LiveVoteResultsComponent() {
const { data, loading, error } = useVoteAggregations({
collectionName: 'live-poll',
refetchInterval: 5000, // Update every 5 seconds
});
return (
<div>
<h3>Live Poll Results {loading && '(Updating...)'}</h3>
{error ? (
<p style={{ color: 'red' }}>Failed to load results</p>
) : (
<div>
{data?.results.map(result => (
<div key={result._id} style={{ display: 'flex', alignItems: 'center', margin: '5px 0' }}>
<span style={{ minWidth: '100px' }}>{result._id}:</span>
<div style={{ flex: 1, margin: '0 10px', backgroundColor: '#e0e0e0', borderRadius: '10px' }}>
<div
style={{
width: `${result.percentage || 0}%`,
backgroundColor: '#2196f3',
height: '20px',
borderRadius: '10px',
transition: 'all 0.5s ease'
}}
/>
</div>
<span>{result.count} votes</span>
</div>
))}
<p style={{ marginTop: '10px', fontSize: '12px', color: '#666' }}>
Last updated: {new Date().toLocaleTimeString()}
</p>
</div>
)}
</div>
);
}
Options:
collectionName?: string - The collection name for votes (defaults to 'votes')autoFetch?: boolean - Whether to automatically fetch data on mount (defaults to true)refetchInterval?: number - Interval in milliseconds for automatic refetching (optional)Returns:
data: VoteAggregationData | null - The aggregation data with results and summaryloading: boolean - Whether a request is in progresserror: string | null - Error message if request failedfetch: () => Promise<VoteAggregationResponse> - Function to manually fetch aggregationsrefetch: () => Promise<VoteAggregationResponse> - Alias for fetch functionreset: () => void - Function to reset the hook stateData Structure:
interface VoteAggregationData {
results: Array<{
_id: string; // The option that was voted for
count: number; // Number of votes for this option
percentage?: number; // Percentage of total votes
}>;
summary: {
totalVotes: number; // Total number of votes cast
totalOptions: number; // Number of different options
groupBy: string; // The field used for grouping
};
}
Note: This hook must be used within an EmbeddableProvider as it automatically retrieves the widgetId from the global configuration.
useAI(options: UseAIOptions)A React hook for handling AI API calls with loading states, error handling, and support for multiple AI platforms. Automatically uses the widget ID from the global configuration.
import { useAI } from '@embeddable/sdk/hooks';
function ChatComponent() {
const { callAI, loading, error, success, reset } = useAI({
platform: 'openai' // 'openai' | 'anthropic' | 'gemini'
});
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const handleSendMessage = async () => {
if (!input.trim()) return;
const userMessage = { role: 'user', content: input };
const newMessages = [...messages, userMessage];
setMessages(newMessages);
setInput('');
const result = await callAI(input, messages);
if (result.success && result.data) {
setMessages(prev => [...prev, {
role: 'assistant',
content: result.data.response
}]);
console.log('Tokens used:', result.data.tokensUsed);
console.log('Model used:', result.metadata?.model);
}
};
return (
<div>
<div style={{ height: '400px', overflowY: 'auto', border: '1px solid #ccc', padding: '10px' }}>
{messages.map((msg, index) => (
<div key={index} style={{ marginBottom: '10px' }}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
{loading && <div>AI is thinking...</div>}
</div>
<div style={{ marginTop: '10px', display: 'flex', gap: '10px' }}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
placeholder="Type your message..."
style={{ flex: 1, padding: '8px' }}
disabled={loading}
/>
<button onClick={handleSendMessage} disabled={loading || !input.trim()}>
Send
</button>
</div>
{error && <p style={{ color: 'red', marginTop: '10px' }}>Error: {error}</p>}
{success && <p style={{ color: 'green', marginTop: '10px' }}>Message sent successfully!</p>}
<button onClick={reset} style={{ marginTop: '10px' }}>Reset Status</button>
</div>
);
}
Options:
platform: 'openai' | 'anthropic' | 'gemini' - The AI platform to use for API callsReturns:
callAI: (prompt: string, history?: Array<ChatMessage>) => Promise<AIResponse> - Function to make AI API callsloading: boolean - Whether an AI request is in progresserror: string | null - Error message if the request failedsuccess: boolean - Whether the last request was successfulreset: () => void - Function to reset the hook stateData Structure:
interface ChatMessage {
role: 'user' | 'assistant';
content: string;
}
interface AIResponse {
success: boolean;
message?: string;
error?: string;
data?: {
response: string;
tokensUsed: number | null;
};
metadata?: {
model: string | null;
integrationKey: string;
capabilityId: string;
executedAt: string;
[key: string]: any; // Additional metadata fields
};
}
debounce<T>(func: T, wait: number)Creates a debounced function.
import { debounce } from '@embeddable/sdk/utils';
const debouncedSave = debounce(data => {
// Save data
}, 500);
createApiClient(config: EmbeddableApiConfig)Creates a type-safe API client.
import { createApiClient } from '@embeddable/sdk/utils';
const api = createApiClient({
apiKey: 'your-api-key',
baseUrl: 'https://api.example.com',
debug: true,
});
const response = await api.get('/users');
storageEnhanced localStorage utility with serialization support.
import { storage } from '@embeddable/sdk/utils';
// Basic usage
storage.set('user', { name: 'John', age: 30 });
const user = storage.get('user');
// With options
storage.set('data', complexObject, {
prefix: 'myapp_',
serialize: JSON.stringify,
deserialize: JSON.parse,
});
The SDK exports TypeScript types for better development experience:
import type {
EmbeddableConfig,
EmbeddableApiConfig,
ApiResponse,
LocalStorageOptions,
FormSubmission,
FormSubmissionResponse,
FormSubmissionOptions,
Vote,
VoteResponse,
VoteOptions,
VoteAggregationResult,
VoteAggregationSummary,
VoteAggregationData,
VoteAggregationResponse,
ChatMessage,
AIResponse,
UseAIOptions,
} from '@embeddable/sdk';
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build the package
npm run build
# Run linter
npm run lint
# Fix linting issues
npm run lint:fix
# Format code with Prettier
npm run format
# Check code formatting
npm run format:check
# Type check
npm run type-check
git checkout -b feature/amazing-feature)git commit -m 'feat: add amazing feature')git push origin feature/amazing-feature)MIT ยฉ Embeddable Team
See CHANGELOG.md for a list of changes.
FAQs
A TypeScript/JavaScript SDK with React utilities and hooks for embeddable applications
The npm package @embeddable/sdk receives a total of 1 weekly downloads. As such, @embeddable/sdk popularity was classified as not popular.
We found that @embeddable/sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.