Skip to content

Svelte integration guide

This guide covers patterns for integrating the Univerx Client SDK into Svelte applications, including reactive state with Svelte stores and SvelteKit SSR considerations.

Use onMount to initialise the widget and onDestroy to clean up. Both lifecycle functions are guaranteed to run only in the browser.

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { WidgetClient } from '@univerx/client-sdk';
let widget: WidgetClient | null = null;
onMount(() => {
widget = new WidgetClient({
widgetKey: 'your-widget-key',
onAgentAssigned: (agent) => console.log(`Agent: ${agent.name}`),
onCallEnded: () => widget?.destroy(),
});
widget.init();
});
onDestroy(() => {
widget?.destroy();
widget = null;
});
</script>
<!-- SDK renders its own UI; no markup needed -->

Use Svelte’s built-in writable stores to expose widget state to your template and other components:

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { writable } from 'svelte/store';
import { WidgetClient } from '@univerx/client-sdk';
import type { WidgetState, Agent } from '@univerx/client-sdk';
const state = writable<WidgetState>('idle');
const queuePosition = writable<number | null>(null);
const currentAgent = writable<Agent | null>(null);
let widget: WidgetClient | null = null;
onMount(() => {
widget = new WidgetClient({
widgetKey: 'your-widget-key',
onReady: () => state.set('ready'),
onCallStarted: () => state.set('in_call'),
onCallEnded: () => state.set('ended'),
});
widget.on('queue:update', (pos) => queuePosition.set(pos));
widget.on('queue:left', () => queuePosition.set(null));
widget.on('agent:assigned', (agent) => currentAgent.set(agent));
widget.init();
});
onDestroy(() => {
widget?.destroy();
widget = null;
});
async function joinQueue() {
await widget?.joinQueue({ terms: true });
state.set('queued');
}
async function endCall() {
await widget?.endSession();
}
</script>
{#if $state === 'ready'}
<button on:click={joinQueue}>Get support</button>
{/if}
{#if $state === 'queued'}
<p>Queue position: {$queuePosition}</p>
{/if}
{#if $state === 'in_call'}
<button on:click={endCall}>End call</button>
{/if}

To share the widget instance across multiple components, export a store and init function from a dedicated module:

lib/widget.ts
import { writable, get } from 'svelte/store';
import { WidgetClient } from '@univerx/client-sdk';
import type { WidgetConfig, WidgetState, Agent } from '@univerx/client-sdk';
export const widgetState = writable<WidgetState>('idle');
export const queuePosition = writable<number | null>(null);
export const currentAgent = writable<Agent | null>(null);
let instance: WidgetClient | null = null;
export function initWidget(config: WidgetConfig): void {
if (instance) return; // already initialised
instance = new WidgetClient({
...config,
onReady: () => widgetState.set('ready'),
onCallStarted: () => widgetState.set('in_call'),
onCallEnded: () => widgetState.set('ended'),
});
instance.on('queue:update', (pos) => queuePosition.set(pos));
instance.on('queue:left', () => queuePosition.set(null));
instance.on('agent:assigned', (agent) => currentAgent.set(agent));
instance.init();
}
export function destroyWidget(): void {
instance?.destroy();
instance = null;
}
export function getWidget(): WidgetClient | null {
return instance;
}

Use it in your root layout component:

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { initWidget, destroyWidget } from '$lib/widget';
onMount(() => {
initWidget({ widgetKey: 'your-widget-key' });
});
onDestroy(() => {
destroyWidget();
});
</script>
<slot />

Listen for cobrowse:consent:required and bind the handlers to local state to drive your consent UI:

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { WidgetClient } from '@univerx/client-sdk';
let widget: WidgetClient | null = null;
let consentHandlers: { accept: () => void; decline: () => void } | null = null;
onMount(() => {
widget = new WidgetClient({ widgetKey: 'your-widget-key' });
widget.on('cobrowse:consent:required', (handlers) => {
consentHandlers = handlers;
});
widget.init();
});
onDestroy(() => {
widget?.destroy();
});
</script>
{#if consentHandlers}
<div class="consent-dialog">
<p>The agent is requesting screen access. Do you allow cobrowsing?</p>
<button on:click={() => { consentHandlers?.accept(); consentHandlers = null; }}>
Allow
</button>
<button on:click={() => { consentHandlers?.decline(); consentHandlers = null; }}>
Decline
</button>
</div>
{/if}

SvelteKit renders pages on the server by default. Since onMount only runs in the browser, initialising inside it is already safe. However, if you reference the SDK at the top level of a +page.server.ts or in any server-only code, it will throw.

For dynamic imports in edge cases:

<script lang="ts">
import { onMount } from 'svelte';
import type { WidgetClient } from '@univerx/client-sdk';
let widget: WidgetClient | null = null;
onMount(async () => {
// Dynamically import to ensure the module is never evaluated on the server
const { WidgetClient } = await import('@univerx/client-sdk');
widget = new WidgetClient({ widgetKey: 'your-widget-key' });
widget.init();
});
</script>