
Security News
The Hidden Blast Radius of the Axios Compromise
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.
@docyrus/addin-client
Advanced tools
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.
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.
pnpm add @docyrus/addin-clientnpm i @docyrus/addin-clientyarn add @docyrus/addin-clientRequires Node 18+ for development builds; runs in modern browsers.
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);
createClient(origin = '*'): DocyrusClient
postMessage. Use an explicit origin in production for security.client.ready(): Promise<void>
client.on(eventName: string, handler: (payload: any) => void): () => void
createClient(origin = '*'): DocyrusClientCreates a new client and immediately starts the handshake with the host window (window.parent).
origin (string, optional): Target origin for postMessage. Use a specific origin in production (e.g. https://app.example.com). Default '*' for development.DocyrusClientimport { 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.
Promise<void>await client.ready();
client.on(eventName: string, handler: (payload: any) => void): () => voidSubscribes to events pushed by the host.
eventName (string): Host-defined event name (e.g. record:updated).handler ((payload: any) => void): Callback invoked with event payload.() => void unsubscribe function.const off = client.on('record:updated', (p) => console.log(p));
// later
off();
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.
Promise<User>const user = await client.getUser();
client.getTenant(): Promise<Tenant>Fetches the current tenant.
Promise<Tenant>const tenant = await client.getTenant();
client.getRecord(appSlug: string, dataSourceSlug: string, recordId: string, options: { columns?: string[] }): Promise<Record<string, any>>Fetches a single record.
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.Promise<Record<string, any>>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.
appSlug (string)dataSourceSlug (string)options (object):
columns? (string[])limit? (number)offset? (number)orderBy? (string)orderByDirection? ('asc' | 'desc')filters? (string[]): Host-defined filter expressions.Promise<Record<string, any>[]>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.
appSlug (string)dataSourceSlug (string)data (object): Plain JSON-serializable values.Promise<Record<string, any>>const created = await client.insertRecord('crm', 'contacts', { email: 'a@b.co', name: 'Ada' });
client.updateRecord(appSlug: string, dataSourceSlug: string, recordId: string, data: Record<string, any>): Promise<Record<string, any>>Updates an existing record.
appSlug (string)dataSourceSlug (string)recordId (string)data (object)Promise<Record<string, any>>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.
appSlug (string)dataSourceSlug (string)recordId (string)Promise<void>await client.deleteRecord('crm', 'contacts', '123');
client.showRecordDetailForm(appSlug: string, dataSourceSlug: string, recordId: string, options?: Record<string, any>): Promise<void>Opens the host UI record detail form in view mode.
appSlug (string)dataSourceSlug (string)recordId (string)options (object, optional): Additional host-defined options.Promise<void>await client.showRecordDetailForm(‘crm’, ‘contacts’, ‘123’);
client.showCreateRecordForm(appSlug: string, dataSourceSlug: string, options?: Record<string, any>): Promise<void>Opens the host UI record form in create mode.
appSlug (string)dataSourceSlug (string)options (object, optional): Additional host-defined options.Promise<void>await client.showCreateRecordForm(‘crm’, ‘contacts’);
client.showEditRecordForm(appSlug: string, dataSourceSlug: string, recordId: string, options?: Record<string, any>): Promise<void>Opens the host UI record form in edit mode.
appSlug (string)dataSourceSlug (string)recordId (string)options (object, optional): Additional host-defined options.Promise<void>await client.showEditRecordForm(‘crm’, ‘contacts’, ‘123’);
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.
appSlug (string)dataSourceSlug (string)rows (any[]): Grouping speccalculations (any[]): Aggregations to computePromise<Record<string, any>[]>const result = await client.runAggregateQuery('crm', 'contacts', [{ by: 'owner' }], [{ op: 'count' }]);
client.runCustomQuery(customQueryId: string, filters?: string[], runBalanceQuery?: boolean): Promise<Record<string, any>[]>Runs a predefined SQL query by its ID and returns the results.
customQueryId (string): The ID of the custom query to run.filters (string[], optional): Array of filter expressions to apply.runBalanceQuery (boolean, optional): Whether to run as a balance calculation query (if main query has an additional balance query)Promise<Record<string, any>[]>const results = await client.runCustomQuery('daily-sales-report', ['date > "2023-01-01"']);
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[]>path (string): Host-resolved API path (e.g. /apps/crm/datasources/contacts).params (object, optional): Query params.Promise<T[]>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>listPromise<T>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>path (string)data (object, optional): Body payloadPromise<T>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>postPromise<T>const updated = await client.patch("/apps/crm/datasources/contacts/123", { email: 'ada@example.com' });
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.
path (string)data (object, optional): Some hosts support body with DELETEPromise<T>await client.delete("/apps/crm/datasources/contacts/123");
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 message and waits for handshake_ack that includes an allowedMethods whitelist.client.ready() rejects with a descriptive error.'*' for convenience. In production, set an explicit origin in createClient('https://app.yourdomain.com') to prevent message spoofing.window.parent. Not intended for SSR or Node.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.
From the monorepo root:
pnpm -C packages/addin-client buildpnpm -C packages/addin-client devpnpm -C packages/addin-client typecheckpnpm -C packages/addin-client lintOr change into the package directory:
cd packages/addin-client
pnpm dev # or: pnpm build
Outputs ESM to dist/ with bundled types via tsup.
FAQs
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.
We found that @docyrus/addin-client demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.