@docyrus/addin-client
v0.0.6
Published
Client for Docyrus embedded add-ins to talk to the host app from an iframe using `postMessage`. It handles the handshake, RPC calls, method whitelisting, and event dispatching.
Downloads
58
Readme
@docyrus/addin-client
Client for Docyrus embedded add-ins to talk to the host app from an iframe using postMessage. It handles the handshake, RPC calls, method whitelisting, and event dispatching.
- ESM-only, TypeScript types included
- Works in browser iframes (not for Node/SSR)
Install
- pnpm:
pnpm add @docyrus/addin-client - npm:
npm i @docyrus/addin-client - yarn:
yarn add @docyrus/addin-client
Requires Node 18+ for development builds; runs in modern browsers.
Quick Start
Create the client, await the handshake, then call host APIs. Subscribe to host-emitted events as needed.
import { createClient } from '@docyrus/addin-client';
// In production, set your host origin explicitly instead of '*'
const client = createClient('*');
async function main() {
await client.ready();
const user = await client.getUser();
const tenant = await client.getTenant();
// Example CRUD
const rec = await client.getRecord('crm', 'contacts', '123', { columns: ['id', 'email'] });
// Subscribe to host events
const off = client.on('record:updated', (payload) => console.log('Updated:', payload));
// Generic HTTP-like call
const items = await client.list<{ id: string; name: string }>(
'/apps/crm/datasources/contacts',
{ limit: 10 }
);
off();
}
main().catch(console.error);API Overview
createClient(origin = '*'): DocyrusClient- Target origin for
postMessage. Use an explicit origin in production for security.
- Target origin for
client.ready(): Promise<void>- Resolves after handshake and method whitelist are confirmed. Rejects if required methods are missing.
client.on(eventName: string, handler: (payload: any) => void): () => void- Subscribes to host events; returns an unsubscribe function.
Detailed API Reference
Factory and Core
createClient(origin = '*'): DocyrusClient
Creates a new client and immediately starts the handshake with the host window (window.parent).
- Parameters:
origin(string, optional): Target origin forpostMessage. Use a specific origin in production (e.g.https://app.example.com). Default'*'for development.
- Returns:
DocyrusClient - Errors: none upon construction; subsequent calls will fail if the host doesn’t whitelist methods.
- Example:
import { createClient } from '@docyrus/addin-client';
const client = createClient('https://app.example.com');
await client.ready();client.ready(): Promise<void>
Resolves when the host responds with handshake_ack and whitelists allowed methods. Rejects if required methods are missing.
- Parameters: none
- Returns:
Promise<void> - Errors:
- Missing methods in host whitelist cause rejection with a descriptive error.
- If the host never responds, your subsequent calls will time out (15s per call).
- Example:
await client.ready();client.on(eventName: string, handler: (payload: any) => void): () => void
Subscribes to events pushed by the host.
- Parameters:
eventName(string): Host-defined event name (e.g.record:updated).handler((payload: any) => void): Callback invoked with event payload.
- Returns:
() => voidunsubscribe function. - Errors: none (no-op if event is never emitted).
- Example:
const off = client.on('record:updated', (p) => console.log(p));
// later
off();Host Methods
Methods proxied to the host application. All require await client.ready() first and are subject to method whitelisting and a 15s RPC timeout.
client.getUser(): Promise<User>
Fetches the current user from the host.
- Parameters: none
- Returns:
Promise<User> - Errors:
- Not whitelisted by host
- RPC timeout (15s)
- Host-reported error
- Example:
const user = await client.getUser();client.getTenant(): Promise<Tenant>
Fetches the current tenant.
- Parameters: none
- Returns:
Promise<Tenant> - Errors: same as above
- Example:
const tenant = await client.getTenant();client.getRecord(appSlug: string, dataSourceSlug: string, recordId: string, options: { columns?: string[] }): Promise<Record<string, any>>
Fetches a single record.
- Parameters:
appSlug(string): Application slug.dataSourceSlug(string): Data source slug within the app.recordId(string): Record identifier.options(object, optional):columns?(string[]): Subset of columns to fetch.
- Returns:
Promise<Record<string, any>> - Errors: same as above
- Example:
const rec = await client.getRecord('crm', 'contacts', '123', { columns: ['id', 'email'] });client.getRecords(appSlug: string, dataSourceSlug: string, options: { columns?: string[]; limit?: number; offset?: number; orderBy?: string; orderByDirection?: 'asc' | 'desc'; filters?: string[] }): Promise<Record<string, any>[]>
Lists records with pagination, sorting, and filtering.
- Parameters:
appSlug(string)dataSourceSlug(string)options(object):columns?(string[])limit?(number)offset?(number)orderBy?(string)orderByDirection?('asc' | 'desc')filters?(string[]): Host-defined filter expressions.
- Returns:
Promise<Record<string, any>[]> - Errors: same as above
- Example:
const list = await client.getRecords('crm', 'contacts', { limit: 20, orderBy: 'createdAt', orderByDirection: 'desc' });client.insertRecord(appSlug: string, dataSourceSlug: string, data: Record<string, any>): Promise<Record<string, any>>
Creates a new record.
- Parameters:
appSlug(string)dataSourceSlug(string)data(object): Plain JSON-serializable values.
- Returns:
Promise<Record<string, any>> - Errors: same as above
- Example:
const created = await client.insertRecord('crm', 'contacts', { email: '[email protected]', name: 'Ada' });client.updateRecord(appSlug: string, dataSourceSlug: string, recordId: string, data: Record<string, any>): Promise<Record<string, any>>
Updates an existing record.
- Parameters:
appSlug(string)dataSourceSlug(string)recordId(string)data(object)
- Returns:
Promise<Record<string, any>> - Errors: same as above
- Example:
const updated = await client.updateRecord('crm', 'contacts', '123', { name: 'Ada Lovelace' });client.deleteRecord(appSlug: string, dataSourceSlug: string, recordId: string): Promise<void>
Deletes a record by id.
- Parameters:
appSlug(string)dataSourceSlug(string)recordId(string)
- Returns:
Promise<void> - Errors: same as above
- Example:
await client.deleteRecord('crm', 'contacts', '123');client.viewRecord(appSlug: string, dataSourceSlug: string, recordId: string, options: { columns?: string[] }): Promise<Record<string, any>>
Navigates the host UI to a record’s detail view; returns the record data (shape host-defined).
- Parameters:
appSlug(string)dataSourceSlug(string)recordId(string)options(object, optional):columns?(string[])
- Returns:
Promise<Record<string, any>> - Errors: same as above
- Example:
await client.viewRecord('crm', 'contacts', '123', { columns: ['id', 'email'] });client.runAggregateQuery(appSlug: string, dataSourceSlug: string, rows: any[], calculations: any[]): Promise<Record<string, any>[]>
Runs an aggregate query; the rows and calculations structures are host-defined.
- Parameters:
appSlug(string)dataSourceSlug(string)rows(any[]): Grouping speccalculations(any[]): Aggregations to compute
- Returns:
Promise<Record<string, any>[]> - Errors: same as above
- Example:
const result = await client.runAggregateQuery('crm', 'contacts', [{ by: 'owner' }], [{ op: 'count' }]);HTTP-like Methods
Generic helpers for host-backed API calls. All payloads must be JSON-serializable. Use generics to type results.
client.list<T = any>(path: string, params?: Record<string, any>): Promise<T[]>
- Parameters:
path(string): Host-resolved API path (e.g./apps/crm/datasources/contacts).params(object, optional): Query params.
- Returns:
Promise<T[]> - Errors: not whitelisted, RPC timeout, host error
- Example:
const items = await client.list<{ id: string }>("/apps/crm/datasources/contacts", { limit: 10 });client.get<T = any>(path: string, params?: Record<string, any>): Promise<T>
- Parameters: same as
list - Returns:
Promise<T> - Errors: same as above
- Example:
const contact = await client.get<{ id: string; email: string }>("/apps/crm/datasources/contacts/123");client.post<T = any>(path: string, data?: Record<string, any>): Promise<T>
- Parameters:
path(string)data(object, optional): Body payload
- Returns:
Promise<T> - Errors: same as above
- Example:
const created = await client.post<{ id: string }>("/apps/crm/datasources/contacts", { name: 'Ada' });client.patch<T = any>(path: string, data?: Record<string, any>): Promise<T>
- Parameters: same as
post - Returns:
Promise<T> - Errors: same as above
- Example:
const updated = await client.patch("/apps/crm/datasources/contacts/123", { email: '[email protected]' });client.delete<T = any>(path: string, data?: Record<string, any>): Promise<T>
Note: named delete to mirror HTTP semantics; returns a payload if host provides one.
- Parameters:
path(string)data(object, optional): Some hosts support body with DELETE
- Returns:
Promise<T> - Errors: same as above
- Example:
await client.delete("/apps/crm/datasources/contacts/123");Events
The host can push events to the add-in. Use client.on to subscribe and keep the returned function to unsubscribe.
const off = client.on('record:updated', (payload) => {
// payload shape is host-defined
});
// later
off();Handshake, Security, and Errors
- Handshake: on creation, the client sends a
handshakemessage and waits forhandshake_ackthat includes anallowedMethodswhitelist. - Whitelisting: if any required method is missing from the host’s whitelist,
client.ready()rejects with a descriptive error. - Timeouts: each RPC has a 15s timeout; the returned promise rejects if no response arrives in time.
- Target origin: default is
'*'for convenience. In production, set an explicit origin increateClient('https://app.yourdomain.com')to prevent message spoofing. - Environment: runs in the browser (iframe) and posts to
window.parent. Not intended for SSR or Node.
Types
export interface User {
id: string;
email: string;
firstname: string;
lastname: string;
photo: string;
}
export interface Tenant {
id: string;
name: string;
logo: string;
}The package exports User, Tenant, DocyrusClient, and createClient.
Development
From the monorepo root:
- Build:
pnpm -C packages/addin-client build - Watch:
pnpm -C packages/addin-client dev - Typecheck:
pnpm -C packages/addin-client typecheck - Lint:
pnpm -C packages/addin-client lint
Or change into the package directory:
cd packages/addin-client
pnpm dev # or: pnpm buildOutputs ESM to dist/ with bundled types via tsup.
