Docs / Contact / sdks/javascript/getting-started
JavaScript SDK: Getting Started
Go from zero to publishing data in under a minute. The SDK uses the native fetch API with zero external dependencies and includes React hooks for instant component integration.
Requirements: Node.js 16+ or modern browser (Chrome 88+, Firefox 84+, Safari 14+, Edge 88+)
Install#
The SDK installs straight from GitHub:
npm install github:tendrl-inc-labs/contact-js
It installs under its package name @tendrl/contact — the imports below work unchanged. (The SDK is plain JavaScript with no build step, so the GitHub install ships exactly what a registry install would.)
Every client needs an entity API key. If you haven't created one yet, create your first entity and copy its API key from the Connection Instructions dialog.
Your First Message (4 Lines)#
const client = new TendrlClient({ apiKey: 'your_api_key' });
client.start();
client.publish({ temperature: 23.5, humidity: 60 }, ['sensor']);
The message is queued, batched, and delivered. Tags route it to your flows and connectors.
Pointing at a different server#
By default the client talks to https://app.tendrl.com. Set TENDRL_APP_URL to point it at a local development stack instead:
TENDRL_APP_URL=http://localhost:8000 node app.js
It accepts either a bare origin (http://localhost:8000) or a full base URL ending in /api, so the same variable works for the Python, Go, and nano-agent clients too. In a browser, where there is no process.env, pass apiBaseUrl to the constructor instead. See Testing against a local or staging stack.
React: One Hook, Full Integration#
The useTendrlClient hook gives you a connected client and publish/receive functions, managed automatically with the component lifecycle:
function SensorDashboard() {
const [online, setOnline] = useState(false);
const { client, publish } = useTendrlClient({
onMessage: (msg) => console.log('Command:', msg.data),
offlineStorage: true
});
useEffect(() => {
if (!client) return;
const id = setInterval(async () => {
setOnline(await client.checkConnectionState());
}, 5000);
return () => clearInterval(id);
}, [client]);
return (
<div>
<p>Status: {online ? 'Online' : 'Offline'}</p>
<button onClick={() => publish({ temperature: 23.5 }, ['sensor'])}>
Send Reading
</button>
);
}
The hook starts the client on mount and stops it on unmount. No cleanup code to write.
The API key is only read from the environment — the hook has no apiKey option, and passing one is ignored. Set it before starting your dev server or build:
REACT_APP_TENDRL_KEY=your_api_key
With that variable unset, the hook logs an error to the console and creates no client at all, so client stays null and publish is a no-op.
It is read straight off the client instance during render, not held in React state, so nothing re-renders when connectivity changes — and it is false on the first render, because the client is created in an effect that runs afterwards. Polling client.checkConnectionState() into useState, as above, is what actually updates the UI.
Receive Inbound Messages#
Handle messages sent back from Contact flows. There is one receive mechanism: a single callback, set either on the constructor or with setMessageCallback. There is no client.on() router in this SDK — branch on the message's tags yourself.
client.setMessageCallback((message) => {
// The SDK moves the server's tags into message.context.tags
const tags = message.context?.tags || [];
if (tags.includes('diagnostic')) {
client.publish({ self_test: 'pass' }, ['diagnostic-result']);
} else if (tags.includes('ai-response')) {
console.log('AI:', message.data?.response);
} else if (tags.includes('alert') || tags.includes('anomaly')) {
console.log('Alert:', message.data);
}
});
client.setMessageCheckRate(3000);
Polling only runs while a callback is set. Setting one on a running client starts polling immediately; setMessageCheckRate and setMessageCheckLimit re-arm it with the new values.
Track Device State#
Every entity has a persistent state table, but the JavaScript SDK does not wrap it. There is no getState, updateState, replaceState or onState. Call the REST endpoints with the entity's API key:
const appUrl = process.env.TENDRL_APP_URL || 'https://app.tendrl.com';
const base = `${appUrl}/api/entities/status-table`;
const headers = {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
};
// Read current state
const state = await (await fetch(base, { headers })).json();
// Merge new keys (PATCH)
await fetch(base, {
method: 'PATCH',
headers,
body: JSON.stringify({ firmware: '2.1.0', status: 'active' }),
});
// Replace the whole table (PUT)
await fetch(base, {
method: 'PUT',
headers,
body: JSON.stringify({ firmware: '2.1.0' }),
});
See State Table for the full endpoint reference.
Offline Storage with Zero Configuration#
Enable IndexedDB persistence with one flag. Messages queue locally when the network drops and send automatically when it returns:
const client = new TendrlClient({
apiKey: 'your_key',
offlineStorage: true
});
No retry logic to write. No local database to manage. The SDK handles storage, TTL expiration, and batch replay.
What You Get for Free#
When you call client.start(), the SDK automatically:
- Batches messages: groups publishes into efficient bulk sends
- Adapts to load: batch size scales with queue depth
- Monitors connectivity: detects network changes and updates entity status
- Stores offline messages: IndexedDB persistence with automatic retry, when
offlineStorage: true - Cleans up expired data: TTL-based cleanup every 60 seconds
Without offlineStorage, a batch that fails to send — or a message that arrives when the queue is already full — is discarded, and nothing is logged unless you also pass debug: true. Turn both on while you are getting a client working.
Complete Example#
const client = new TendrlClient({
apiKey: 'your_api_key',
offlineStorage: true
});
client.setMessageCallback((msg) => {
if ((msg.context?.tags || []).includes('ai-response')) {
console.log(`[${msg.msg_type}] ${JSON.stringify(msg.data)}`);
}
});
client.setMessageCheckRate(5000);
client.start();
// Publish sensor data every 10 seconds
setInterval(() => {
client.publish(
{ temperature: 23.5, humidity: 60 },
['sensor', 'environment']
);
}, 10000);
What's Next#
- Configuration: All config options and tuning
- Examples: React dashboards, Node.js services, and more
- API Reference: Full method documentation
Tendrl