@weave-apps/sdk
v0.1.18
Published
SDK for building Weave Micro Apps
Maintainers
Readme
Weave App SDK
Official SDK for building third-party applications for the Weave Platform.
Features
- ✅ TypeScript Support - Write apps in TypeScript with full type safety
- ✅ Clean JavaScript Output - Transpiles to readable, unobfuscated JavaScript
- ✅ Security Review Ready - Generated code is easy to audit
- ✅ Project Scaffolding - Quick start with
weave-init - ✅ DOM API - Secure parent page DOM manipulation
- ✅ Shadow DOM Isolation - Scoped styles and encapsulation
- ✅ Background Services - Run code without user interaction
- ✅ State Persistence - Survive page reloads with auto-persist
- ✅ Scheduled Tasks - Cron jobs with standard cron syntax
- ✅ Multi-Page Flows - Pending operations across navigations
- ✅ Form Integration - Auto-fill forms with extracted data
- ✅ AI Integration - Leverage AI for smart form filling
- ✅ Data Persistence - Store app data with the Weave API
Table of Contents generated with DocToc
- Quick Start
- API Reference
- Advanced Topics
- Build Process
- Security
- Project Structure
- Troubleshooting
Quick Start
1. Initialize a New App
npx weave-init my-custom-app
cd my-custom-app
npm install
npm run build2. Develop Your App
Edit src/app.ts:
import { WeaveBaseApp } from '@weave-apps/sdk';
class MyCustomApp extends WeaveBaseApp {
constructor() {
super({
id: 'my-custom-app',
name: 'My Custom App',
version: '1.0.0',
category: 'utility',
description: 'My awesome app',
author: 'Your Name',
tags: ['custom']
});
this.state = {
count: 0
};
}
protected render(): void {
this.renderHTML(`
<style>
.container {
padding: 20px;
font-family: system-ui, sans-serif;
}
button {
padding: 10px 20px;
background: #3b82f6;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
button:hover {
background: #2563eb;
}
</style>
<div class="container">
<h1>Counter App</h1>
<p>Count: <span id="count">${this.state.count}</span></p>
<button id="incrementBtn">Increment</button>
</div>
`);
}
protected setupEventListeners(): void {
this.query('#incrementBtn')?.addEventListener('click', () => {
this.setState({ count: this.state.count + 1 });
this.render();
});
}
}
customElements.define('my-custom-app', MyCustomApp);3. Build Your App
npm run buildThis runs:
tsc- Compiles TypeScript to JavaScriptweave-compile- Transpiles to clean, platform-ready JavaScript
Output: dist/my-custom-app.js - ready for upload to Weave Platform.
4. Upload to Weave
Upload the generated dist/my-custom-app.js file to the Weave Platform via the Enterprise Management Console.
API Reference
WeaveBaseApp
Base class for all Weave apps. Handles state management, rendering, and lifecycle hooks.
Constructor
constructor(appInfo: WeaveAppInfo)App Info:
id(string) - Unique identifier (kebab-case)name(string) - Display nameversion(string) - Semantic version (x.y.z)category(string) - App categorydescription(string) - Detailed descriptionauthor(string) - Author nametags(string[])` - Optional tagspersistState(boolean) - Enable auto-persist across page reloads (default: false)persistDebounce(number) - Debounce delay in ms for state saves (default: 100)
Settings & State
Define typed settings and state:
interface MyAppSettings {
apiEndpoint?: string;
debugMode?: boolean;
}
interface MyAppState {
isLoading: boolean;
data: any;
}
class MyApp extends WeaveBaseApp<MyAppSettings, MyAppState> {
constructor() {
super({ /* ... */ });
this.state = {
isLoading: false,
data: null
};
// Access settings (injected from Enterprise Console)
console.log(this.appSettings?.apiEndpoint);
}
}Methods to Implement
render(): void | Promise<void>
Render your app's UI. Use this.renderHTML() to inject HTML.
protected async render(): Promise<void> {
this.renderHTML(`
<div>Content here</div>
`);
}setupEventListeners(): void (Optional)
Setup event listeners for your app's elements.
protected setupEventListeners(): void {
this.query('#myBtn')?.addEventListener('click', () => {
// Handle click
});
}onBackgroundService(): void (Optional)
Background service that runs when sidebar loads, even if app drawer isn't open.
async onBackgroundService(): Promise<void> {
const url = await window.weaveDOM.getPageUrl();
if (url.includes('mypage.com')) {
// Do something on background
}
}onUrlChange(newUrl: string): void (Optional)
Called when the page URL changes (SPA navigation).
async onUrlChange(newUrl: string): Promise<void> {
if (newUrl.includes('form')) {
await this.checkAndInjectButton();
}
}cleanup(): void (Optional)
Cleanup when app is removed from DOM. Clear intervals, listeners, watchers.
protected cleanup(): void {
if (this.myInterval) {
clearInterval(this.myInterval);
}
}Helper Methods
renderHTML(html: string): void
Renders HTML into the shadow root.
this.renderHTML('<h1>Hello</h1>');setState(updates: object): void
Update app state (shallow merge). If persistState: true, state is automatically saved to background service.
this.setState({ isLoading: true, count: 5 });this.background (Property)
Access the background API for state persistence and pending operations.
// Save state manually
await this.background?.saveState({ count: 5 });
// Load state manually
const state = await this.background?.loadState();
// Set pending operation before navigation
await this.background?.setPendingOperation({
type: 'fill-form',
data: { content: '...' }
});
// Get pending operation after navigation
const pending = await this.background?.getPendingOperation();
// Clear pending operation
await this.background?.clearPendingOperation();query<T>(selector: string): T | null
Query a single element in shadow root.
const btn = this.query<HTMLButtonElement>('#myBtn');queryAll<T>(selector: string): NodeListOf<T>
Query all matching elements in shadow root.
const buttons = this.queryAll<HTMLButtonElement>('button');WeaveDOMAPI
API for securely interacting with the parent page DOM. Available globally as window.weaveDOM.
URL & Navigation
// Get current page URL
const url = await window.weaveDOM.getPageUrl();
// Open new tab
window.open(url, '_blank');Read Operations
// Query element
const element = await window.weaveDOM.query('h1');
// Returns: { exists: boolean, value?: string, outerHTML?: string }
// Get text content
const text = await window.weaveDOM.getText('h1');
// Get form data
const formData = await window.weaveDOM.getFormData('form');
// Returns: { formId, formName, fields: [...] }Write Operations
// Set text
await window.weaveDOM.setText('h1', 'New Title');
// Set form field value
await window.weaveDOM.setFormFieldValue('#email', '[email protected]', true);
// Click element
await window.weaveDOM.clickElement('.submit-btn');
// Remove element
await window.weaveDOM.removeElement('.old-element');DOM Injection
// Inject HTML element
const elementId = await window.weaveDOM.injectElement(
'.target-container',
'beforeend',
'<button id="my-btn">Click Me</button>',
{
onClick: async () => {
console.log('Button clicked!');
}
}
);
// Remove injected element
await window.weaveDOM.removeInjectedElement(elementId);Event Listeners
// Start form click listener
await window.weaveDOM.startFormClickListener(async (data) => {
console.log('Form detected:', data.formData);
await window.weaveDOM.stopFormClickListener();
});
// Watch element for changes
const watcherId = await window.weaveDOM.watchElement(
'.target',
(data) => {
console.log('Element changed:', data.changeType);
},
{ watchChildren: true, watchAttributes: true }
);
// Stop watching
await window.weaveDOM.unwatchElement(watcherId);WeaveAPIClient
API for accessing backend services. Available as this.weaveAPI or window.weaveAPI.
App Data Management
// Create app data
await this.weaveAPI.appData.create({
dataKey: 'my-key',
data: { content: '...', timestamp: new Date() }
});
// Get all app data
const response = await this.weaveAPI.appData.getAll();
const allData = response.data || [];
// Update existing data
await this.weaveAPI.appData.update(dataId, {
dataKey: 'my-key',
data: { content: '...', timestamp: new Date() }
});AI Integration
// Call AI for text analysis
const response = await this.weaveAPI.ai.chat({
prompt: 'Extract patient name from this text: ...',
context: 'Medical form filling',
disableJsonExtraction: false
});
console.log(response.response);Utilities
// Convert HTML to Markdown
const markdown = window.weaveAPI.utils.htmlToMarkdown(htmlString);
// Convert Markdown to HTML
const html = window.weaveAPI.utils.markdownToHtml(markdownString);Advanced Topics
State Persistence (Survive Page Reloads)
Apps can persist state across page reloads using the background state service. There are two approaches:
Auto-Persist (Recommended)
Simply set persistState: true in your app config. State is automatically saved on every setState() call and restored when the app initializes after a page reload.
class MyApp extends WeaveBaseApp<Settings, State> {
constructor() {
super({
id: 'my-app',
name: 'My App',
version: '1.0.0',
category: 'utility',
description: 'App with persistent state',
author: 'Your Name',
persistState: true, // Enable auto-persist
persistDebounce: 100, // Optional: debounce delay (default 100ms)
});
this.state = { count: 0, items: [] };
}
async onBackgroundService() {
// State is already restored automatically!
console.log('Restored count:', this.state.count);
}
handleIncrement() {
// Automatically saved to background service
this.setState({ count: this.state.count + 1 });
}
}Multi-Page Flows (Pending Operations)
For operations that span multiple pages (e.g., extract data on page A, fill form on page B), use pending operations:
class DataTransferApp extends WeaveBaseApp {
async handleTransferClick() {
// Save pending operation BEFORE navigation
await this.background?.setPendingOperation({
type: 'fill-form',
data: {
content: this.state.extractedContent,
targetForm: 'patient-notes'
},
targetUrl: 'https://target-system.com/form'
});
// Navigate to target page
window.open('https://target-system.com/form', '_self');
}
async onBackgroundService() {
// Check for pending operation after page loads
const pending = await this.background?.getPendingOperation();
if (pending?.type === 'fill-form') {
const pageUrl = await window.weaveDOM.getPageUrl();
// Verify we're on the right page
if (pageUrl.includes('target-system.com/form')) {
await this.fillForm(pending.data);
await this.background?.clearPendingOperation();
}
}
}
}State TTL
Persisted state expires after 5 minutes by default. This prevents stale state from accumulating.
Scheduled Tasks (Cron Jobs)
Apps can register scheduled tasks using standard cron syntax. The browser extension acts as the master clock.
class PollingApp extends WeaveBaseApp {
constructor() {
super({
id: 'polling-app',
name: 'Polling App',
version: '1.0.0',
category: 'utility',
description: 'App with scheduled polling',
author: 'Your Name',
persistState: true, // Important for cron!
});
this.state = {
cronEnabled: false, // Track cron state
tickCount: 0
};
}
async onBackgroundService() {
// Re-register cron if it was enabled before page reload
if (this.state.cronEnabled) {
await this.startPolling();
}
}
async startPolling() {
// Register cron job - every 5 seconds
const jobId = await this.cronTab('*/5 * * * * *', this.handleTick, 'poller');
if (jobId) {
this.setState({ cronEnabled: true });
}
}
async stopPolling() {
await this.cronUnregisterAll();
this.setState({ cronEnabled: false });
}
handleTick() {
this.setState({ tickCount: this.state.tickCount + 1 });
console.log('Tick!', this.state.tickCount);
// Update UI if app is open
if (this.isConnected) {
this.render();
this.setupEventListeners();
}
}
}🔮 Secret Hack: Persistent Cron
Cron jobs don't survive page reloads by default. The secret hack is to combine persistState: true with a cronEnabled state flag:
- Track cron state - Add
cronEnabled: booleanto your state - Set flag when starting -
this.setState({ cronEnabled: true }) - Re-register on reload - Check
this.state.cronEnabledinonBackgroundService()
This way, when the page reloads, your state is restored (including cronEnabled: true), and onBackgroundService() automatically re-registers the cron job!
Cron Syntax
┌───────────── second (0-59) - optional
│ ┌───────────── minute (0-59)
│ │ ┌───────────── hour (0-23)
│ │ │ ┌───────────── day of month (1-31)
│ │ │ │ ┌───────────── month (1-12)
│ │ │ │ │ ┌───────────── day of week (0-6)
│ │ │ │ │ │
* * * * * *Common patterns:
* * * * * *- Every second*/5 * * * * *- Every 5 seconds*/30 * * * * *- Every 30 seconds0 * * * * *- Every minute0 */5 * * * *- Every 5 minutes
Settings & Configuration
Settings are injected from the Enterprise Console and displayed as auto-extracted form fields:
interface MyAppSettings {
/**
* @description API endpoint for the service
* @default "https://api.example.com"
*/
apiEndpoint?: string;
/**
* @description Enable debug logging
* @default false
*/
debugMode?: boolean;
}
class MyApp extends WeaveBaseApp<MyAppSettings, MyAppState> {
constructor() {
super({ /* ... */ });
const endpoint = this.appSettings?.apiEndpoint || 'https://api.example.com';
const debug = this.appSettings?.debugMode || false;
}
}Error Handling
try {
const data = await this.weaveAPI.appData.getAll();
} catch (error) {
console.error('Failed to fetch data:', error);
this.setState({ error: error.message });
this.render();
}Performance Tips
- Use
async/awaitfor API calls to avoid blocking UI - Debounce rapid state changes with timers
- Clear intervals and listeners in
cleanup() - Use
isCheckingContainerflags to prevent overlapping async callbacks - Minimize re-renders by updating state only when needed
Build Process
The build pipeline has two steps:
TypeScript Compilation (
tsc)- Compiles TypeScript to ES2020 JavaScript
- Outputs to
dist/
SDK Build (
weave-compile)- Removes SDK imports (SDK is global)
- Replaces SDK references with
window.* - Generates final output ready for deployment
You can also run manually:
tsc
weave-compileSecurity
- ✅ Selector Validation - Prevents dangerous selectors
- ✅ HTML Sanitization - Removes scripts and dangerous content
- ✅ Attribute Whitelist - Blocks dangerous attributes
- ✅ Shadow DOM Isolation - Scoped styles and encapsulation
- ✅ Secure Bridge - All DOM operations go through security validation
Project Structure
my-app/
├── src/
│ └── app.ts # Your TypeScript app
├── dist/
│ └── my-app.js # Compiled output (upload this)
├── package.json
├── tsconfig.json
├── SIDEKICK_SPEC.md # AI assistant guide
└── README.mdTroubleshooting
"TypeScript not found in SDK"
Make sure to run npm install in your app directory after initialization.
Form not filling
Check that selectors match the actual form structure. Use browser DevTools to inspect the form.
Button not injecting
Verify the target selector exists before injection attempt. Check browser console for errors.
State not persisting across page reloads
Enable persistState: true in your app config. State will automatically save on setState() and restore on page reload.
State not persisting long-term
Use this.weaveAPI.appData.* methods to persist data to backend. Background state only lasts 5 minutes.
