@cloudillo/core
Overview
The @cloudillo/core library is the core SDK for Cloudillo applications. It provides initialization via the message bus, API client creation, CRDT document support, and URL helpers.
Installation
Core Pattern: AppMessageBus
The main API is accessed through the getAppBus() singleton. This provides authentication state, storage, and shell communication for apps running in the Cloudillo shell.
import { getAppBus } from '@cloudillo/core'
// Get the singleton message bus
const bus = getAppBus ()
// Initialize your app (communicates with shell)
const state = await bus .init ('my-app' )
// Access state via bus properties
console .log ('Token:' , bus .accessToken )
console .log ('User ID:' , bus .idTag )
console .log ('Tenant ID:' , bus .tnId )
console .log ('Roles:' , bus .roles )
console .log ('Access level:' , bus .access )
console .log ('Dark mode:' , bus .darkMode )
AppState Properties
After calling bus.init(), these properties are available on the bus:
Property
Type
Description
accessToken
string | undefined
Current JWT access token
idTag
string | undefined
User’s identity tag (e.g., “alice.cloudillo.net”)
tnId
number | undefined
Tenant ID
roles
string[] | undefined
User’s roles
access
'read' | 'write'
Access level to current resource
darkMode
boolean
Dark mode preference
tokenLifetime
number | undefined
Token lifetime in seconds
displayName
string | undefined
Display name (for anonymous guests)
embedded
boolean
Whether the app is running as an embedded document
theme
string | undefined
Theme name
navState
string | undefined
Initial navigation state (from parent embed or shell)
ancestors
string[] | undefined
Ancestor file IDs in the embed chain
Storage API
The bus provides namespaced key-value storage:
const bus = getAppBus ()
await bus .init ('my-app' )
// Store data
await bus .storage .set ('my-app' , 'settings' , { theme : 'dark' })
// Retrieve data
const settings = await bus .storage .get < { theme : string }> ('my-app' , 'settings' )
// List keys
const keys = await bus .storage .list ('my-app' , 'user-' )
// Delete data
await bus .storage .delete ('my-app' , 'settings' )
// Clear namespace
await bus .storage .clear ('my-app' )
// Check quota
const quota = await bus .storage .quota ('my-app' )
console .log (`Used ${ quota .used } of ${ quota .limit } bytes` )
App Lifecycle Notifications
Notify the shell about your app’s loading progress:
const bus = getAppBus ()
await bus .init ('my-app' )
// After auth init (called automatically by init())
bus .notifyReady ('auth' )
// After CRDT sync complete
bus .notifyReady ('synced' )
// When fully interactive
bus .notifyReady ('ready' )
Token Refresh
Request a fresh token when needed:
const bus = getAppBus ()
// Manually refresh token
const newToken = await bus .refreshToken ()
// Listen for token updates pushed from shell
bus .on ('auth:token.push' , (msg ) => {
console .log ('Token updated:' , bus .accessToken )
})
Error Notification
Notify the shell about errors in your app:
bus .notifyError (404 , 'Document not found' )
Event Handling
Register and unregister message handlers:
bus .on ('auth:token.push' , handler )
bus .off ('auth:token.push' , handler )
Open the shell’s media picker dialog:
const result = await bus .pickMedia ({
mediaType : 'image/*' , // MIME filter
enableCrop : true , // Enable crop UI
cropAspects : ['16:9' , '1:1' ], // Allowed aspect ratios
documentFileId : 'abc123' , // Context document
title : 'Choose image'
})
// Returns: { fileId, fileName, contentType, dim?, visibility?, croppedVariantId? }
Document Picker
Open the shell’s document picker:
const result = await bus .pickDocument ({
fileTp : 'CRDT' , // File type filter
contentType : 'cloudillo/quillo' , // Content type filter
sourceFileId : 'abc123' , // Source context
title : 'Choose document'
})
// Returns: { fileId, fileName, contentType, fileTp?, appId? }
Camera Capture
Capture an image from the device camera:
const result = await bus .captureImage ({
facing : 'environment' , // 'user' or 'environment'
maxResolution : 1920
})
// Returns: { imageData (base64), width, height }
Camera Preview
Open a camera preview with overlay support:
const session = await bus .openCamera ({ facing : 'environment' })
// Preview frames
bus .previewFrames (session .sessionId , (frameData ) => { /* ... */ })
// Add overlay shapes
bus .overlayShapes (session .sessionId , shapes )
// Wait for capture
const result = await session .result
Document Embedding
Request an embedded document view:
const { embedUrl , nonce , resId } = await bus .requestEmbed ({
targetFileId : 'abc123' ,
targetContentType : 'cloudillo/quillo' ,
sourceFileId : 'current-doc' ,
access : 'read' ,
navState : 'page=3'
})
Settings API
Access user settings through the bus:
await bus .settings .get ('key' )
await bus .settings .set ('key' , value )
const items = await bus .settings .list ('prefix' )
CRDT Cache Management
Manage offline CRDT caching:
const clientId = await bus .requestClientId (docId )
await bus .crdtCacheAppend (docId , update , clientId , clock )
const updates = await bus .crdtCacheRead (docId )
await bus .crdtCacheCompact (docId , state )
resetAppBus()
Reset the singleton message bus instance (useful for testing):
import { resetAppBus } from '@cloudillo/core'
resetAppBus ()
API Client
createApiClient(opts: ApiClientOpts): ApiClient
Create a type-safe REST API client.
import { createApiClient } from '@cloudillo/core'
const api = createApiClient ({
idTag : 'alice.cloudillo.net' , // Required: target tenant
authToken : 'jwt-token' // Optional: authentication token
})
// Use the API
const profile = await api .profiles .getOwn ()
const posts = await api .actions .list ({ type : 'POST' , limit : 20 })
Options:
interface ApiClientOpts {
idTag : string // Required: identity tag of the tenant
authToken? : string // Optional: JWT token for authentication
}
idTag is Required
Unlike the old documentation, idTag is required to create an API client. This specifies which Cloudillo instance to connect to.
Using with AppMessageBus
import { getAppBus , createApiClient } from '@cloudillo/core'
const bus = getAppBus ()
const state = await bus .init ('my-app' )
const api = createApiClient ({
idTag : bus.idTag ! ,
authToken : bus.accessToken
})
const files = await api .files .list ({ limit : 20 })
CRDT Document Functions
openYDoc(yDoc: Y.Doc, docId: string): Promise<DocConnection>
Open a Yjs document for collaborative editing with WebSocket synchronization.
Info
openYDoc is exported from @cloudillo/crdt, not from @cloudillo/core.
import { getAppBus } from '@cloudillo/core'
import { openYDoc } from '@cloudillo/crdt'
import * as Y from 'yjs'
const bus = getAppBus ()
await bus .init ('my-app' )
const yDoc = new Y .Doc ()
const { yDoc : doc , provider } = await openYDoc (yDoc , 'alice.cloudillo.net:document-id' )
// Use shared types
const yText = yDoc .getText ('content' )
yText .insert (0 , 'Hello, collaborative world!' )
// Listen for changes
yText .observe (() => {
console .log ('Text changed:' , yText .toString ())
})
// Access awareness (other users' cursors, selections)
provider .awareness .on ('change' , () => {
const states = provider .awareness .getStates ()
console .log ('Connected users:' , states .size )
})
Parameters:
yDoc - The Yjs document to synchronize
docId - Document ID in format "targetTag:resourceId"
Returns:
interface DocConnection {
yDoc : Y.Doc
provider : WebsocketProvider
}
Error Handling:
Throws if no access token (must call init() first)
Throws if docId format is invalid
WebSocket close codes 4401/4403/4404 stop reconnection (auth/permission/not found errors)
URL Helper Functions
Build URLs for Cloudillo services:
getInstanceUrl(idTag: string): string
Build the base URL for a Cloudillo instance.
import { getInstanceUrl } from '@cloudillo/core'
const url = getInstanceUrl ('alice.cloudillo.net' )
// Returns: "https://cl-o.alice.cloudillo.net"
getApiUrl(idTag: string): string
Build the API base URL.
import { getApiUrl } from '@cloudillo/core'
const url = getApiUrl ('alice.cloudillo.net' )
// Returns: "https://cl-o.alice.cloudillo.net/api"
getFileUrl(idTag: string, fileId: string, variant?: string): string
Build URL for file access with optional variant.
import { getFileUrl } from '@cloudillo/core'
// Basic file URL
const url = getFileUrl ('alice.cloudillo.net' , 'file-123' )
// Returns: "https://cl-o.alice.cloudillo.net/api/files/file-123"
// With variant
const thumbnailUrl = getFileUrl ('alice.cloudillo.net' , 'file-123' , 'vis.tn' )
// Returns: "https://cl-o.alice.cloudillo.net/api/files/file-123?variant=vis.tn"
getCrdtUrl(idTag: string): string
Build the CRDT WebSocket URL.
import { getCrdtUrl } from '@cloudillo/core'
const url = getCrdtUrl ('alice.cloudillo.net' )
// Returns: "wss://cl-o.alice.cloudillo.net/ws/crdt"
getRtdbUrl(idTag: string, fileId: string, token: string): string
Build the RTDB WebSocket URL with authentication.
import { getRtdbUrl } from '@cloudillo/core'
const url = getRtdbUrl ('alice.cloudillo.net' , 'db-file-id' , 'jwt-token' )
// Returns: "wss://cl-o.alice.cloudillo.net/ws/rtdb/db-file-id?token=jwt-token"
getMessageBusUrl(idTag: string, token: string): string
Build the Message Bus WebSocket URL.
import { getMessageBusUrl } from '@cloudillo/core'
const url = getMessageBusUrl ('alice.cloudillo.net' , 'jwt-token' )
// Returns: "wss://cl-o.alice.cloudillo.net/ws/bus?token=jwt-token"
Image/Video Variant Helpers
getOptimalImageVariant(context, localVariants?): string
Get the optimal image variant for a display context.
import { getOptimalImageVariant } from '@cloudillo/core'
// For thumbnail display
const variant = getOptimalImageVariant ('thumbnail' ) // 'vis.tn'
// For preview
const variant = getOptimalImageVariant ('preview' ) // 'vis.sd'
// For fullscreen/lightbox
const variant = getOptimalImageVariant ('fullscreen' ) // 'vis.hd'
// With available variants
const variant = getOptimalImageVariant ('fullscreen' , ['vis.sd' , 'vis.hd' , 'vis.xd' ])
// Returns highest quality available: 'vis.xd'
Image variants: vis.tn (150px), vis.sd (640px), vis.md (1280px), vis.hd (1920px), vis.xd (original)
getOptimalVideoVariant(context, localVariants?): string
Get the optimal video variant for a display context.
import { getOptimalVideoVariant } from '@cloudillo/core'
const variant = getOptimalVideoVariant ('preview' ) // 'vid.sd'
const variant = getOptimalVideoVariant ('fullscreen' ) // 'vid.hd'
Video variants: vid.sd, vid.md, vid.hd, vid.xd
getImageVariantForDisplaySize(width, height): string
Get optimal variant for a specific display size in pixels.
import { getImageVariantForDisplaySize } from '@cloudillo/core'
// For a 300x200 canvas at 2x zoom (600x400 screen pixels)
const variant = getImageVariantForDisplaySize (600 , 400 )
// Returns: 'vis.sd'
API Client Structure
The API client provides access to all REST endpoints organized by namespace:
const api = createApiClient ({ idTag : 'alice.cloudillo.net' , authToken : token })
// ============================================================================
// AUTH - Authentication & Authorization
// ============================================================================
await api .auth .login ({ idTag , password })
await api .auth .logout ()
await api .auth .loginInit () // Combined init (auth or QR+WebAuthn)
await api .auth .getLoginToken ()
await api .auth .getAccessToken ({ scope , lifetime })
await api .auth .getAccessTokenByRef (refId ) // Guest access via reference
await api .auth .getAccessTokenVia (via , scope ) // Scoped token via cross-document link
await api .auth .getAccessTokenByApiKey (apiKey ) // API key authentication
await api .auth .getProxyToken (targetIdTag ) // Federation proxy token
await api .auth .getVapidPublicKey () // Push notification key
await api .auth .changePassword ({ oldPassword , newPassword })
await api .auth .setPassword ({ refId , password }) // Set password via reset link
await api .auth .forgotPassword ({ email })
// WebAuthn
await api .auth .listWebAuthnCredentials ()
await api .auth .getWebAuthnRegChallenge ()
await api .auth .registerWebAuthnCredential ({ token , credential })
await api .auth .deleteWebAuthnCredential (credentialId )
await api .auth .getWebAuthnLoginChallenge ()
await api .auth .webAuthnLogin ({ token , credential })
// API Keys
await api .auth .listApiKeys ()
await api .auth .createApiKey ({ name , scopes })
await api .auth .deleteApiKey (keyId )
// QR Login
await api .auth .initQrLogin () // Start QR login session
await api .auth .getQrLoginStatus (sessionId , secret ) // Poll for login status
await api .auth .getQrLoginDetails (sessionId ) // Get session details (mobile)
await api .auth .respondQrLogin (sessionId , data ) // Approve/deny from mobile
// ============================================================================
// PROFILE - Registration & Profile Creation
// ============================================================================
await api .profile .verify ({ type , idTag , email }) // Check availability
await api .profile .register ({ type , idTag , name , email , password })
// ============================================================================
// PROFILES - Profile Management
// ============================================================================
await api .profiles .getOwn () // GET /me
await api .profiles .getOwnFull () // GET /me/full
await api .profiles .getRemoteFull (idTag ) // GET full profile from remote server
await api .profiles .updateOwn ({ name , bio }) // PATCH /me
await api .profiles .list ({ q , type , role }) // List/search profiles
await api .profiles .get (idTag ) // Get profile by idTag
await api .profiles .updateConnection (idTag , data ) // Update relationship
await api .profiles .adminUpdate (idTag , { status , roles }) // Admin operations
// ============================================================================
// ACTIONS - Social Interactions
// ============================================================================
await api .actions .list ({ type , status , limit }) // List actions
await api .actions .listPaginated ({ cursor , limit }) // With cursor pagination
await api .actions .create ({ type , content , ... }) // Create action
await api .actions .get (actionId ) // Get single action
await api .actions .update (actionId , patch ) // Update draft
await api .actions .delete (actionId ) // Delete action
await api .actions .accept (actionId ) // Accept (e.g., connection)
await api .actions .reject (actionId ) // Reject
await api .actions .dismiss (actionId ) // Dismiss notification
await api .actions .updateStat (actionId , stat ) // Update statistics
await api .actions .addReaction (actionId , { type }) // Add reaction
await api .actions .publish (actionId , { publishAt }) // Publish draft (optionally scheduled)
await api .actions .cancel (actionId ) // Cancel scheduled (revert to draft)
// ============================================================================
// FILES - File Management
// ============================================================================
await api .files .list ({ parentId , tag , limit }) // List files
await api .files .listPaginated ({ cursor , limit }) // With cursor pagination
await api .files .create ({ fileName , fileTp }) // Create metadata-only file
await api .files .uploadBlob (preset , name , data , contentType ) // Upload file
await api .files .get (fileId ) // Get file content
await api .files .getVariant (variantId ) // Get specific variant
await api .files .getDescriptor (fileId ) // Get file metadata
await api .files .update (fileId , { fileName }) // Update metadata
await api .files .delete (fileId ) // Soft delete (to trash)
await api .files .permanentDelete (fileId ) // Permanent delete
await api .files .restore (fileId , parentId ) // Restore from trash
await api .files .updateUserData (fileId , { starred }) // Update user-specific data
await api .files .setStarred (fileId , true ) // Toggle starred
await api .files .setPinned (fileId , true ) // Toggle pinned
await api .files .addTag (fileId , 'tag' ) // Add tag
await api .files .removeTag (fileId , 'tag' ) // Remove tag
await api .files .duplicate (fileId , { fileName }) // Duplicate a CRDT/RTDB file
await api .files .listShares (fileId ) // List share entries for file
await api .files .createShare (fileId , data ) // Create share entry
await api .files .deleteShare (fileId , shareId ) // Delete share entry
// ============================================================================
// SHARES - Share Entry Queries
// ============================================================================
await api .shares .listBySubject (subjectId , subjectType ) // List shares by subject
// ============================================================================
// TRASH - Trash Management
// ============================================================================
await api .trash .list ({ limit }) // List trashed files
await api .trash .empty () // Empty trash permanently
// ============================================================================
// TAGS - Tag Management
// ============================================================================
await api .tags .list ({ prefix , withCounts }) // List tags
// ============================================================================
// SETTINGS - User Settings
// ============================================================================
await api .settings .list ({ prefix }) // List settings
await api .settings .get (name ) // Get setting value
await api .settings .update (name , { value }) // Update setting
// ============================================================================
// NOTIFICATIONS - Push Notifications
// ============================================================================
await api .notifications .subscribe ({ subscription }) // Subscribe to push
// ============================================================================
// REFS - Share Links & References
// ============================================================================
await api .refs .list ({ type , resourceId }) // List references
await api .refs .get (refId ) // Get reference
await api .refs .create ({ type , resourceId , access }) // Create share link
await api .refs .delete (refId ) // Delete reference
// ============================================================================
// IDP - Identity Provider (End User)
// ============================================================================
await api .idp .getInfo (providerDomain ) // Get provider info
await api .idp .activate ({ refId }) // Activate identity
// ============================================================================
// IDP MANAGEMENT - Identity Provider Admin
// ============================================================================
await api .idpManagement .listIdentities ({ q , status })
await api .idpManagement .createIdentity ({ idTag , email , createApiKey })
await api .idpManagement .getIdentity (idTag )
await api .idpManagement .updateIdentity (idTag , { dyndns })
await api .idpManagement .deleteIdentity (idTag )
await api .idpManagement .listApiKeys (idTag )
await api .idpManagement .createApiKey ({ idTag , name })
await api .idpManagement .deleteApiKey (keyId , idTag )
// ============================================================================
// COMMUNITIES - Community Management
// ============================================================================
await api .communities .create (idTag , { type , name , ownerIdTag })
await api .communities .verify ({ type , idTag }) // Deprecated: use profile.verify
// ============================================================================
// ADMIN - System Administration
// ============================================================================
await api .admin .listTenants ({ q , status , limit }) // List all tenants
await api .admin .sendPasswordReset (idTag ) // Send password reset
await api .admin .sendTestEmail (to ) // Test SMTP config
await api .admin .listProxySites () // List proxy site configs
await api .admin .createProxySite (data ) // Create proxy site
await api .admin .getProxySite (siteId ) // Get proxy site
await api .admin .updateProxySite (siteId , data ) // Update proxy site
await api .admin .deleteProxySite (siteId ) // Delete proxy site
await api .admin .renewProxySiteCert (siteId ) // Renew proxy site TLS cert
Helper Functions
delay(ms: number): Promise<void>
Delay execution for a specified time.
import { delay } from '@cloudillo/core'
await delay (1000 ) // Wait 1 second
See Also