9 Commits

Author SHA1 Message Date
Elijah Duffy
dccf4b01f7 0.1.1 2026-05-04 03:34:38 -07:00
Elijah Duffy
99264dca61 fix ErrorOccurredEvent structure for errorOccurred 2026-04-30 16:36:43 -07:00
Elijah Duffy
b7c619c872 0.1.0 2026-04-07 17:47:43 -07:00
Elijah Duffy
408d3635f6 remove left snippet, leave this behaviour to consumer 2026-04-07 17:05:12 -07:00
Elijah Duffy
ff2dd60a95 remove tailwindcss classes in favour of raw styles 2026-04-07 16:52:39 -07:00
Elijah Duffy
bb07744081 update readme 2026-04-07 16:52:26 -07:00
Elijah Duffy
8c57fd9d7b refactor component props with improved state & event handling 2026-04-07 15:34:22 -07:00
Elijah Duffy
4d9cd25b19 add .nvmrc 2026-04-07 15:33:52 -07:00
Elijah Duffy
2cff9c79a7 add license to readme 2026-04-07 15:33:47 -07:00
5 changed files with 111 additions and 96 deletions

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
24.12.0

View File

@@ -1,65 +1,3 @@
# Svelte library
# Jitsi iFrame Svelte Integration
Everything you need to build a Svelte library, powered by [`sv`](https://npmjs.com/package/sv).
Read more about creating a library [in the docs](https://svelte.dev/docs/kit/packaging).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```sh
# create a new project in the current directory
npx sv create
# create a new project in my-app
npx sv create my-app
```
To recreate this project with the same configuration:
```sh
# recreate this project
pnpm dlx sv create --template library --types ts --add prettier eslint devtools-json --install pnpm jitsi
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```sh
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
## Building
To build your library:
```sh
npm pack
```
To create a production version of your showcase app:
```sh
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
## Publishing
Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
To publish your library to [npm](https://www.npmjs.com):
```sh
npm publish
```
This package provides a component and typed API for the Jitsi Meet iFrame. If you prefer to build your own component, refer directly to the exported type definitions for the Jitsi iFrame API.

View File

@@ -1,6 +1,7 @@
{
"name": "@svelte-toolkit/jitsi",
"version": "0.0.4",
"version": "0.1.1",
"license": "BSD-3-Clause",
"repository": {
"type": "git",
"url": "https://gitea.auvem.com/svelte-toolkit/jitsi.git"

View File

@@ -1,18 +1,22 @@
<!-- @component
The Jitsi component is a lightweight wrapper that manages loading the Jitsi Meet External API
and joining a Jitsi meeting room. It accepts configuration options and provides a callback
when the API is ready for use.
and joining a Jitsi meeting room. It accepts configuration options and several callbacks that
allow you to respond to basic meeting events such as joining and leaving. For advanced event
handling, use the getAPI() method to interact with the Jitsi instance directly.
WARNING: Assume that ALL properties are NOT reactive. Due to the nature of the Jitsi Meet
External API, changing properties after initialization will NOT update the Jitsi instance.
Instead, use the getAPI() method to interact with the Jitsi instance directly.
WARNING: While properties are reactive, in most cases changing them after the API has
initialized will trigger a complete re-render of the component, which will destroy the
existing Jitsi instance and interrupt any ongoing meetings. Use the getAPI() method to
interact with the Jitsi instance directly without causing a re-render.
-->
<script lang="ts">
import { onMount, type Snippet } from 'svelte';
import type {
JitsiMeetExternalAPIOptions,
JitsiMeetExternalAPI
JitsiMeetExternalAPI,
VideoConferenceJoinedEvent,
VideoConferenceLeftEvent
} from '$lib/jitsi-iframe-api.d.ts';
import type { ClassValue } from 'svelte/elements';
@@ -32,45 +36,72 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
*/
class?: ClassValue;
/**
* Children are rendered inside the Jitsi container until the API is initialized.
* Optional snippet rendered until the API is initialized and the meeting
* is joined. Can be used to provide a loading indicator or placeholder content.
*/
children?: Snippet;
loading?: Snippet;
/** Whether to hold loading state until the meeting is joined (default: false). */
holdLoadingUntilJoined?: boolean;
/**
* Optional snippet rendered after the API is initialized and the meeting is joined.
* Can be used to provide additional UI elements or controls related to the meeting.
* Overlay has pointer-events disabled to allow interaction with the underlying Jitsi
* interface.
*/
joinedOverlay?: Snippet<[api: JitsiMeetExternalAPI]>;
/**
* Callback function that is called when the Jitsi API is loaded. Can
* be used to provide a loading indicator until the API is ready.
* @param api The Jitsi Meet External API instance.
*/
onready?: (api: JitsiMeetExternalAPI) => void;
onapiready?: (api: JitsiMeetExternalAPI) => void;
/** Callback triggered when the local user joins the meeting. */
onjoin?: (ev: VideoConferenceJoinedEvent) => void;
/** Callback triggered when the local user leaves the meeting. */
onleave?: (ev: VideoConferenceLeftEvent) => void;
}
let {
domain = 'https://meet.jit.si',
options,
class: classValue,
children,
onready
loading,
holdLoadingUntilJoined = false,
joinedOverlay,
onapiready,
onjoin,
onleave
}: Props = $props();
const src = $derived(`${new URL('external_api.js', domain).toString()}`);
let container: HTMLDivElement;
let ready = $state(false);
/** tracks whether the Jitsi API is ready */
let apiReady = $state(false);
/** tracks whether the local user has joined the meeting */
let joinedMeeting = $state(false);
/** tracks whether the local user has left the meeting */
let leftMeeting = $state(false);
/** whether to display the loading state */
const showLoading = $derived(holdLoadingUntilJoined ? !joinedMeeting : !apiReady);
let api = $state<JitsiMeetExternalAPI | null>(null);
onMount(() => {
// check if we have a duplicate container
if (document.querySelectorAll('#jitsi-container').length > 1) {
if (document.querySelectorAll('#jitsi-iframe').length > 1) {
console.warn('Duplicate Jitsi container detected. This may lead to unexpected behavior.');
}
});
const onload = () => {
/** handles Jitsi script load event */
const handleScriptLoad = () => {
if (typeof window.JitsiMeetExternalAPI === 'undefined') {
console.error('Jitsi Meet External API is not available on the window object.');
return;
}
ready = true;
apiReady = true;
// Initialize the Jitsi Meet External API
api = new window.JitsiMeetExternalAPI(new URL(domain).host, {
...options,
@@ -80,8 +111,18 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
console.error('Failed to initialize Jitsi Meet External API.');
return;
}
// Call the onready callback if provided
onready?.(api);
// Call the onapiready callback if provided
onapiready?.(api);
// Attach event listeners for meeting join and leave events
api.addListener('videoConferenceJoined', (ev: VideoConferenceJoinedEvent) => {
joinedMeeting = true;
onjoin?.(ev);
});
api.addListener('videoConferenceLeft', (ev: VideoConferenceLeftEvent) => {
leftMeeting = true;
onleave?.(ev);
});
};
/**
@@ -94,11 +135,43 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
</script>
<svelte:head>
<script {src} {onload}></script>
<script {src} onload={handleScriptLoad}></script>
</svelte:head>
<div id="jitsi-container" bind:this={container} class={[classValue]}>
{#if !ready}
{@render children?.()}
<div class={['jitsi-container', classValue]}>
{#if showLoading}
<div class="overlay-container">{@render loading?.()}</div>
{/if}
{#if joinedMeeting && api && joinedOverlay}
<div class="overlay-container passthrough">{@render joinedOverlay(api)}</div>
{/if}
<div
id="jitsi-iframe"
bind:this={container}
class="fill-container"
style={leftMeeting ? 'display: none;' : undefined}
></div>
</div>
<style lang="css">
.jitsi-container {
position: relative;
}
.fill-container {
width: 100%;
height: 100%;
min-width: 0px;
}
.overlay-container {
position: absolute;
inset: 0;
}
.passthrough {
pointer-events: none;
}
</style>

View File

@@ -1430,16 +1430,18 @@ interface FaceLandmarkDetectedEvent {
}
interface ErrorOccurredEvent {
/** Additional error details. */
details?: Record<string, unknown>;
/** The error message. */
message?: string;
/** The coded name of the error. */
name: string;
/** Error type/source: 'CONFIG', 'CONNECTION', 'CONFERENCE'. */
type: ErrorType;
/** Whether this is a fatal error which triggered a reconnect overlay. */
isFatal: boolean;
error: {
/** Additional error details. */
details?: Record<string, unknown>;
/** The error message. */
message?: string;
/** The coded name of the error. */
name: string;
/** Error type/source: 'CONFIG', 'CONNECTION', 'CONFERENCE'. */
type: ErrorType;
/** Whether this is a fatal error which triggered a reconnect overlay. */
isFatal: boolean;
};
}
interface KnockingParticipantEvent {