@purplecallio/vue
| Install | |
|---|---|
npm install @purplecallio/vue |
|
| Latest Version: | 0.2.0 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Oct 23, 2026 |
| Links: | npm |
@purplecallio/vue
Official Vue 3 (Composition API) composable and components for PurpleCallio, a developer-focused real-time communication infrastructure platform for audio, video, screen sharing and WebRTC applications.
This package is a thin, framework-idiomatic adapter around
@purplecallio/sdk's headless PurpleCallioMeeting engine — it
does not reimplement any WebRTC/signaling logic itself. If you're building
with React, React Native or Angular instead, see @purplecallio/react,
@purplecallio/react-native and @purplecallio/angular.
Install
npm install @purplecallio/vue @purplecallio/sdk vue
vue (>=3.2.0) is a peer dependency — bring your own Vue install rather
than have one bundled with this package, so your app only ever has a single
Vue instance (required for reactivity/lifecycle hooks to work correctly).
Security: where does token come from?
usePurpleCallio({ token, ... }) takes a short-lived participant token,
not a PurpleCallio API key.
- A PurpleCallio API key is a server-only secret. It authenticates
privileged, account-level operations (creating calls, minting tokens) via
@purplecallio/sdk'sPurpleCallioClientand must never be embedded in, shipped to, or generated by client-side code — including this package. - A participant token is a narrowly-scoped, short-lived credential for
one participant in one call. Your own backend calls
PurpleCallioClientserver-side (using your API key) to mint one, then hands it to your frontend (e.g. as the response to your app's own "join this call" API endpoint). That token is what you pass intousePurpleCallio().
This mirrors exactly how @purplecallio/react, @purplecallio/react-native
and @purplecallio/angular all handle it — there is no client-side path to a
token that doesn't go through your backend first.
Usage
<script setup lang="ts">
import { usePurpleCallio, PurpleCallioVideo } from '@purplecallio/vue';
const props = defineProps<{ callId: string; token: string }>();
// `props.token` was fetched from YOUR OWN backend before this component
// mounted (see examples/App.vue + examples/CallView.vue for the full flow,
// including why the fetch happens in a parent component, not here).
const {
connectionState, // Ref<ConnectionState>
participants, // Ref<Participant[]>
media, // Ref<{ camera, microphone, screenShare }>
remoteStream, // ShallowRef<MediaStream | null>
localStream, // ShallowRef<MediaStream | null>
participantId, // Ref<string | null>
join,
leave,
camera, // { enable(), disable(), toggle(), isEnabled() }
microphone, // { enable(), disable(), toggle(), isEnabled() }
screenShare, // { start(), stop(), isActive() }
} = usePurpleCallio({
token: props.token,
callId: props.callId,
// Your PurpleCallio host: Socket.IO origin, and the REST API under /api.
signalUrl: 'https://<your-purplecallio-host>',
apiUrl: 'https://<your-purplecallio-host>/api',
});
</script>
<template>
<div>
<p>{{ connectionState }} — {{ participants.length }} participant(s)</p>
<PurpleCallioVideo :stream="localStream" :muted="true" />
<PurpleCallioVideo :stream="remoteStream" :muted="false" />
<button @click="join">Join</button>
<button @click="camera.toggle()">Toggle camera</button>
<button @click="microphone.toggle()">Toggle microphone</button>
<button @click="leave">Leave</button>
</div>
</template>
See examples/App.vue and examples/CallView.vue for a complete, realistic
walkthrough: fetch a token from your own backend → configure/join the call →
render local + remote video → toggle camera/mic → leave → cleanup.
Automatic cleanup
When usePurpleCallio() is called inside a component's setup(), it
registers an onUnmounted hook that calls leave() as a last-resort safety
net if the component unmounts while the call is still connected (mirroring
the intent of @purplecallio/angular's PurpleCallioService.ngOnDestroy
safety net). This is not a substitute for calling leave() explicitly at
the right point in your own app flow (e.g. a "Leave call" button) — relying
on unmount alone means the call stays connected for as long as the component
happens to be mounted, which is rarely what you actually want for an
intentional hang-up.
usePurpleCallio() also works outside a component's setup() (e.g. from a
Pinia store, or composed by another composable-of-composables) —
getCurrentInstance() is used internally to detect that case and skip
registering the unmount hook, since there is no component instance to attach
it to. In that mode, cleanup is entirely your own responsibility: call
leave() yourself when you're done, since nothing will do it for you.
Each call to usePurpleCallio() creates its own new engine instance and its
own independent reactive state — it is not a shared singleton across calls,
the same way @purplecallio/react's <MeetingProvider> and
@purplecallio/react-native's <PurpleCallioProvider> each own one engine
per mounted provider.
<PurpleCallioVideo>
<PurpleCallioVideo :stream="remoteStream" :muted="false" />
Binds a MediaStream (local or remote) to a <video> element's srcObject,
re-attaching whenever the stream prop changes and clearing it on unmount —
the same concept as @purplecallio/angular's purplecallioVideo directive,
expressed as a Vue component instead. Props: stream (MediaStream | null | undefined), muted (default true — flip to false for a remote
participant's video so you actually hear them), autoplay (default true),
playsInline (default true, required on iOS Safari to avoid full-screen
takeover).
Build tooling
This package ships one real .vue Single-File Component
(PurpleCallioVideo.vue). SFCs are not valid input to plain tsc — the
<template>/<script setup> blocks need @vue/compiler-sfc to be turned
into plain JS/TS first, which is exactly what @vitejs/plugin-vue does. So
the build is:
vite build(library mode,vite.config.ts) —@vitejs/plugin-vuecompiles the.vuefile, Vite/Rollup bundlessrc/index.tsinto a single ESM output,vueand@purplecallio/sdkare kept external (never bundled — both must resolve to the consuming app's own install).vite-plugin-dtsgenerates the.d.tsdeclarations for the whole package, including a real, accurate declaration forPurpleCallioVideo.vue(via@vue/language-core, the same engine that powersvue-tsc— soPurpleCallioVideoPropsisn't hand-rolled orany-typed in the output).
Output is ESM-only (dist/index.mjs + dist/index.d.ts), which is
standard for a Vue 3 library — Vue itself, and the Vue ecosystem's build
tooling, are ESM-first. package.json's exports map only advertises an
"import" condition (no "require"); a CJS consumer would need their own
bundler/interop, same as most modern Vue libraries.
PurpleCallioVideoProps is factored into a plain PurpleCallioVideo.types.ts
file (imported by the SFC via defineProps<PurpleCallioVideoProps>()) rather
than declared inline in <script setup>, purely so it can be re-exported
cleanly from src/index.ts without depending on a .vue file's own
generated declaration shape.
Testing
vitest + @vue/test-utils (jsdom environment). @purplecallio/sdk is
mocked with vi.mock(...) in every test — no real WebRTC/socket code ever
runs. See vitest.config.ts for why the @purplecallio/sdk module
specifier is also aliased straight at its built CJS entry point: the SDK's
published exports map declares an "import" condition pointing at
dist/index.mjs, a file that doesn't actually exist (only the CJS
dist/index.js is built) — a pre-existing mismatch in packages/sdk itself
that this package does not modify, but that trips up Vite's module
resolution before vi.mock's replacement even gets a chance to apply.
npm test
Covers: usePurpleCallio's reactive refs updating on every engine event,
join()/leave() delegating to the engine correctly, the unmount safety net
(calls leave() only when still connected, never twice, stops forwarding
events after unmount), a regression guard pinning camera.toggle() /
microphone.toggle() to engine.*.toggle() (not .enable()/.disable() —
see "Known limitations" below), and <PurpleCallioVideo> binding/unbinding
srcObject on mount, prop change, and unmount.
Known limitations
@purplecallio/react'scontext.tsxhas a pre-existing bug where itstoggleCamera/toggleMicrophonecallengine.camera.enable()/engine.microphone.disable()instead of the engine's own.toggle().@purplecallio/vuedoes not repeat this:camera.toggle()andmicrophone.toggle()both call the engine's.toggle()directly — seetests/composable.spec.ts's "toggle semantics (regression guard)" suite.- Like the local-stream handling in every sibling adapter,
localStreamis only populated oncejoin()resolves (the engine has no dedicated "local stream ready" event to subscribe to) — there is no local preview before joining. - This package does not add device-enumeration (
enumerateDevices) or network-state helpers the way@purplecallio/reactand@purplecallio/react-nativedo — those are convenience layers over browser/platform APIs unrelated to the core engine, and were left out to keep this first version focused. They can be added as additional composables (useDevices(), etc.) later without changingusePurpleCallio's shape.
Related Packages
LiveKit integration for Nuxt: realtime video, audio, and data with client compos...
Build plugins for Briefing, the secure video chat: plugin API, Vite preset and t...
Embed the Pouchy companion — chat, voice, tools, memory, live world-state, insta...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.2.0 | >=3.2.0 | MIT | |
| 0.1.0 | >=3.2.0 | MIT |