@conference-kit/vue 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ export * from "./useMeshRoom";
2
+ export * from "./signaling";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./useMeshRoom";
2
+ export * from "./signaling";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
@@ -0,0 +1,49 @@
1
+ type EventMap = {
2
+ open: void;
3
+ close: CloseEvent | undefined;
4
+ error: Error;
5
+ signal: {
6
+ from: string;
7
+ data: unknown;
8
+ };
9
+ broadcast: {
10
+ from: string;
11
+ room?: string | null;
12
+ data: unknown;
13
+ };
14
+ presence: {
15
+ room?: string | null;
16
+ peerId: string;
17
+ peers: string[];
18
+ action: "join" | "leave";
19
+ };
20
+ };
21
+ type EventKey = keyof EventMap;
22
+ type Handler<K extends EventKey> = (payload: EventMap[K]) => void;
23
+ export type SignalingClientOptions = {
24
+ url: string;
25
+ peerId: string;
26
+ room?: string | null;
27
+ autoReconnect?: boolean;
28
+ reconnectDelayMs?: number;
29
+ };
30
+ export declare class SignalingClient {
31
+ private ws;
32
+ private options;
33
+ private emitter;
34
+ private reconnectTimeout;
35
+ private shouldReconnect;
36
+ private pendingQueue;
37
+ constructor(options: SignalingClientOptions);
38
+ connect(): void;
39
+ close(): void;
40
+ sendSignal(to: string, data: unknown): void;
41
+ broadcast(data: unknown): void;
42
+ on<K extends EventKey>(event: K, handler: Handler<K>): void;
43
+ off<K extends EventKey>(event: K, handler: Handler<K>): void;
44
+ private enqueue;
45
+ private flushQueue;
46
+ private scheduleReconnect;
47
+ }
48
+ export {};
49
+ //# sourceMappingURL=signaling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signaling.d.ts","sourceRoot":"","sources":["../src/signaling.ts"],"names":[],"mappings":"AAAA,KAAK,QAAQ,GAAG;IACd,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC;IAC9B,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;IACxC,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;IACjE,QAAQ,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF,KAAK,QAAQ,GAAG,MAAM,QAAQ,CAAC;AAE/B,KAAK,OAAO,CAAC,CAAC,SAAS,QAAQ,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;AAwBlE,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAiBF,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,gBAAgB,CAA8C;IACtE,OAAO,CAAC,eAAe,CAAU;IACjC,OAAO,CAAC,YAAY,CAAyB;gBAEjC,OAAO,EAAE,sBAAsB;IAK3C,OAAO;IAwDP,KAAK;IAOL,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;IAIpC,SAAS,CAAC,IAAI,EAAE,OAAO;IAIvB,EAAE,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAIpD,GAAG,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAIrD,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,iBAAiB;CAM1B"}
@@ -0,0 +1,129 @@
1
+ function createEmitter() {
2
+ const listeners = new Map();
3
+ return {
4
+ on(event, handler) {
5
+ const set = listeners.get(event) ?? new Set();
6
+ set.add(handler);
7
+ listeners.set(event, set);
8
+ },
9
+ off(event, handler) {
10
+ const set = listeners.get(event);
11
+ if (!set)
12
+ return;
13
+ set.delete(handler);
14
+ if (set.size === 0)
15
+ listeners.delete(event);
16
+ },
17
+ emit(event, payload) {
18
+ const set = listeners.get(event);
19
+ if (!set)
20
+ return;
21
+ for (const handler of Array.from(set))
22
+ handler(payload);
23
+ },
24
+ };
25
+ }
26
+ export class SignalingClient {
27
+ constructor(options) {
28
+ this.ws = null;
29
+ this.emitter = createEmitter();
30
+ this.reconnectTimeout = null;
31
+ this.pendingQueue = [];
32
+ this.options = options;
33
+ this.shouldReconnect = options.autoReconnect ?? true;
34
+ }
35
+ connect() {
36
+ if (typeof window === "undefined")
37
+ return;
38
+ if (this.ws &&
39
+ (this.ws.readyState === WebSocket.OPEN ||
40
+ this.ws.readyState === WebSocket.CONNECTING)) {
41
+ return;
42
+ }
43
+ const { url, peerId, room } = this.options;
44
+ const wsUrl = `${url}?peerId=${encodeURIComponent(peerId)}${room ? `&room=${encodeURIComponent(room)}` : ""}`;
45
+ this.ws = new WebSocket(wsUrl);
46
+ this.ws.addEventListener("open", () => {
47
+ this.emitter.emit("open", undefined);
48
+ this.flushQueue();
49
+ });
50
+ this.ws.addEventListener("message", (event) => {
51
+ const payload = typeof event.data === "string" ? event.data : "";
52
+ try {
53
+ const parsed = JSON.parse(payload);
54
+ if (parsed.type === "signal") {
55
+ this.emitter.emit("signal", { from: parsed.from, data: parsed.data });
56
+ }
57
+ else if (parsed.type === "broadcast") {
58
+ this.emitter.emit("broadcast", {
59
+ from: parsed.from,
60
+ room: parsed.room,
61
+ data: parsed.data,
62
+ });
63
+ }
64
+ else if (parsed.type === "presence") {
65
+ this.emitter.emit("presence", {
66
+ room: parsed.room,
67
+ peerId: parsed.peerId,
68
+ peers: parsed.peers,
69
+ action: parsed.action,
70
+ });
71
+ }
72
+ }
73
+ catch (error) {
74
+ this.emitter.emit("error", error);
75
+ }
76
+ });
77
+ this.ws.addEventListener("close", (event) => {
78
+ this.emitter.emit("close", event);
79
+ this.scheduleReconnect();
80
+ });
81
+ this.ws.addEventListener("error", () => {
82
+ this.emitter.emit("error", new Error("WebSocket error"));
83
+ });
84
+ }
85
+ close() {
86
+ this.shouldReconnect = false;
87
+ if (this.reconnectTimeout)
88
+ clearTimeout(this.reconnectTimeout);
89
+ this.ws?.close();
90
+ this.ws = null;
91
+ }
92
+ sendSignal(to, data) {
93
+ this.enqueue({ type: "signal", to, data });
94
+ }
95
+ broadcast(data) {
96
+ this.enqueue({ type: "broadcast", data });
97
+ }
98
+ on(event, handler) {
99
+ this.emitter.on(event, handler);
100
+ }
101
+ off(event, handler) {
102
+ this.emitter.off(event, handler);
103
+ }
104
+ enqueue(message) {
105
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
106
+ this.ws.send(JSON.stringify(message));
107
+ return;
108
+ }
109
+ this.pendingQueue.push(message);
110
+ }
111
+ flushQueue() {
112
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
113
+ return;
114
+ while (this.pendingQueue.length) {
115
+ const msg = this.pendingQueue.shift();
116
+ if (msg)
117
+ this.ws.send(JSON.stringify(msg));
118
+ }
119
+ }
120
+ scheduleReconnect() {
121
+ if (!this.shouldReconnect)
122
+ return;
123
+ const delay = this.options.reconnectDelayMs ?? 1000;
124
+ if (this.reconnectTimeout)
125
+ clearTimeout(this.reconnectTimeout);
126
+ this.reconnectTimeout = setTimeout(() => this.connect(), delay);
127
+ }
128
+ }
129
+ //# sourceMappingURL=signaling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signaling.js","sourceRoot":"","sources":["../src/signaling.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { Peer } from "@conference-kit/core";
2
+ export type MeshParticipant = {
3
+ id: string;
4
+ peer: Peer;
5
+ remoteStream: MediaStream | null;
6
+ connectionState: RTCPeerConnectionState;
7
+ iceState: RTCIceConnectionState;
8
+ };
9
+ export type UseMeshRoomOptions = {
10
+ peerId: string;
11
+ room: string;
12
+ signalingUrl: string;
13
+ mediaConstraints?: MediaStreamConstraints;
14
+ rtcConfig?: RTCConfiguration;
15
+ trickle?: boolean;
16
+ autoReconnect?: boolean;
17
+ features?: {
18
+ enableDataChannel?: boolean;
19
+ };
20
+ };
21
+ export declare function useMeshRoom(options: UseMeshRoomOptions): {
22
+ readonly localStream: import("vue").Ref<MediaStream | null>;
23
+ readonly requesting: import("vue").Ref<boolean>;
24
+ readonly ready: import("vue").Ref<boolean>;
25
+ readonly mediaError: import("vue").Ref<Error | null>;
26
+ readonly participants: import("vue").Ref<MeshParticipant[]>;
27
+ readonly roster: import("vue").Ref<string[]>;
28
+ readonly requestStream: () => Promise<void>;
29
+ readonly stopStream: () => void;
30
+ readonly leave: () => void;
31
+ readonly error: import("vue").Ref<Error | null>;
32
+ };
33
+ //# sourceMappingURL=useMeshRoom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMeshRoom.d.ts","sourceRoot":"","sources":["../src/useMeshRoom.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAkC,MAAM,sBAAsB,CAAC;AAG5E,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,eAAe,EAAE,sBAAsB,CAAC;IACxC,QAAQ,EAAE,qBAAqB,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAC1C,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE;QAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC5C,CAAC;AAEF,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB;;;;;;;;;;;EAmMtD"}
@@ -0,0 +1,162 @@
1
+ import { ref, onMounted, onUnmounted, computed, watch } from "vue";
2
+ import { Peer } from "@conference-kit/core";
3
+ import { SignalingClient } from "./signaling";
4
+ export function useMeshRoom(options) {
5
+ const { peerId, room, signalingUrl, mediaConstraints, rtcConfig, trickle, autoReconnect, } = options;
6
+ const localStream = ref(null);
7
+ const requesting = ref(false);
8
+ const mediaError = ref(null);
9
+ const previousStream = ref(null);
10
+ const roster = ref([]);
11
+ const participants = ref([]);
12
+ const error = ref(null);
13
+ const signaling = new SignalingClient({
14
+ url: signalingUrl,
15
+ peerId,
16
+ room,
17
+ autoReconnect,
18
+ });
19
+ const enableDataChannel = options.features?.enableDataChannel ?? true;
20
+ const peers = new Map();
21
+ const sideForPeer = (otherId) => peerId > otherId ? "initiator" : "responder";
22
+ const requestStream = async () => {
23
+ try {
24
+ requesting.value = true;
25
+ const constraints = mediaConstraints ?? { audio: true, video: true };
26
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
27
+ localStream.value = stream;
28
+ mediaError.value = null;
29
+ }
30
+ catch (err) {
31
+ mediaError.value = err;
32
+ localStream.value = null;
33
+ }
34
+ finally {
35
+ requesting.value = false;
36
+ }
37
+ };
38
+ const stopStream = () => {
39
+ localStream.value?.getTracks().forEach((t) => t.stop());
40
+ localStream.value = null;
41
+ };
42
+ const destroyPeer = (id) => {
43
+ const peer = peers.get(id);
44
+ if (peer) {
45
+ peer.destroy();
46
+ peers.delete(id);
47
+ }
48
+ participants.value = participants.value.filter((p) => p.id !== id);
49
+ };
50
+ const upsertParticipant = (id, patch) => {
51
+ const existing = participants.value.find((p) => p.id === id);
52
+ if (!existing) {
53
+ participants.value = [
54
+ ...participants.value,
55
+ {
56
+ id,
57
+ peer: patch.peer,
58
+ remoteStream: patch.remoteStream ?? null,
59
+ connectionState: patch.connectionState ?? "new",
60
+ iceState: patch.iceState ?? "new",
61
+ },
62
+ ];
63
+ return;
64
+ }
65
+ participants.value = participants.value.map((p) => p.id === id ? { ...p, ...patch } : p);
66
+ };
67
+ const ensurePeer = (id, side) => {
68
+ if (id === peerId)
69
+ return null;
70
+ const existing = peers.get(id);
71
+ if (existing)
72
+ return existing;
73
+ const peer = new Peer({
74
+ side: side ?? sideForPeer(id),
75
+ stream: localStream.value ?? undefined,
76
+ config: rtcConfig,
77
+ trickle,
78
+ enableDataChannel,
79
+ });
80
+ peers.set(id, peer);
81
+ upsertParticipant(id, {
82
+ peer,
83
+ remoteStream: null,
84
+ connectionState: "new",
85
+ iceState: "new",
86
+ });
87
+ const handlers = {
88
+ signal: (data) => signaling.sendSignal(id, data),
89
+ stream: (remote) => upsertParticipant(id, { remoteStream: remote }),
90
+ error: (err) => (error.value = err),
91
+ connectionStateChange: (state) => upsertParticipant(id, { connectionState: state }),
92
+ iceStateChange: (state) => upsertParticipant(id, { iceState: state }),
93
+ close: () => destroyPeer(id),
94
+ };
95
+ peer.on("signal", handlers.signal);
96
+ peer.on("stream", handlers.stream);
97
+ peer.on("error", handlers.error);
98
+ peer.on("connectionStateChange", handlers.connectionStateChange);
99
+ peer.on("iceStateChange", handlers.iceStateChange);
100
+ peer.on("close", handlers.close);
101
+ return peer;
102
+ };
103
+ const handlePresence = (payload) => {
104
+ roster.value = payload.peers;
105
+ payload.peers.filter((id) => id !== peerId).forEach((id) => ensurePeer(id));
106
+ participants.value = participants.value.filter((p) => payload.peers.includes(p.id));
107
+ Array.from(peers.keys()).forEach((id) => {
108
+ if (!payload.peers.includes(id))
109
+ destroyPeer(id);
110
+ });
111
+ };
112
+ const handleSignal = ({ from, data }) => {
113
+ const peer = ensurePeer(from, sideForPeer(from));
114
+ void peer?.signal(data);
115
+ };
116
+ onMounted(() => {
117
+ signaling.on("presence", handlePresence);
118
+ signaling.on("signal", handleSignal);
119
+ signaling.connect();
120
+ });
121
+ onUnmounted(() => {
122
+ signaling.off("presence", handlePresence);
123
+ signaling.off("signal", handleSignal);
124
+ Array.from(peers.values()).forEach((p) => p.destroy());
125
+ peers.clear();
126
+ stopStream();
127
+ signaling.close();
128
+ });
129
+ const leave = () => {
130
+ Array.from(peers.values()).forEach((p) => p.destroy());
131
+ peers.clear();
132
+ participants.value = [];
133
+ roster.value = [];
134
+ stopStream();
135
+ signaling.close();
136
+ };
137
+ const ready = computed(() => Boolean(localStream.value));
138
+ watch(() => localStream.value, (next, prev) => {
139
+ if (prev === next)
140
+ return;
141
+ Array.from(peers.values()).forEach((peer) => {
142
+ if (prev)
143
+ peer.removeStream(prev);
144
+ if (next)
145
+ peer.addStream(next);
146
+ });
147
+ previousStream.value = next ?? null;
148
+ });
149
+ return {
150
+ localStream,
151
+ requesting,
152
+ ready,
153
+ mediaError,
154
+ participants,
155
+ roster,
156
+ requestStream,
157
+ stopStream,
158
+ leave,
159
+ error,
160
+ };
161
+ }
162
+ //# sourceMappingURL=useMeshRoom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMeshRoom.js","sourceRoot":"","sources":["../src/useMeshRoom.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@conference-kit/vue",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc -p tsconfig.json"
10
+ },
11
+ "peerDependencies": {
12
+ "vue": "^3.4.0"
13
+ },
14
+ "dependencies": {
15
+ "@conference-kit/core": "^0.0.1"
16
+ }
17
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./useMeshRoom";
2
+ export * from "./signaling";
@@ -0,0 +1,28 @@
1
+ declare module "@conference-kit/core" {
2
+ export type PeerSide = "initiator" | "responder";
3
+ export type SignalData = RTCSessionDescriptionInit | RTCIceCandidateInit;
4
+
5
+ export interface PeerEventHandler {
6
+ (payload: any): void;
7
+ }
8
+
9
+ export interface Peer {
10
+ on(event: string, handler: PeerEventHandler): void;
11
+ off(event: string, handler: PeerEventHandler): void;
12
+ destroy(): void;
13
+ addStream(stream: MediaStream): void;
14
+ removeStream(stream: MediaStream): void;
15
+ signal(data: SignalData): Promise<void>;
16
+ }
17
+
18
+ export const Peer: {
19
+ new (config: {
20
+ side: PeerSide;
21
+ stream?: MediaStream;
22
+ config?: RTCConfiguration;
23
+ channelLabel?: string;
24
+ trickle?: boolean;
25
+ enableDataChannel?: boolean;
26
+ }): Peer;
27
+ };
28
+ }
@@ -0,0 +1,11 @@
1
+ declare module "vue" {
2
+ export type Ref<T> = { value: T };
3
+ export function ref<T>(value: T): Ref<T>;
4
+ export function computed<T>(getter: () => T): Ref<T>;
5
+ export function watch<T>(
6
+ source: () => T,
7
+ cb: (next: T, prev: T) => void
8
+ ): void;
9
+ export function onMounted(cb: () => void): void;
10
+ export function onUnmounted(cb: () => void): void;
11
+ }
@@ -0,0 +1,178 @@
1
+ type EventMap = {
2
+ open: void;
3
+ close: CloseEvent | undefined;
4
+ error: Error;
5
+ signal: { from: string; data: unknown };
6
+ broadcast: { from: string; room?: string | null; data: unknown };
7
+ presence: {
8
+ room?: string | null;
9
+ peerId: string;
10
+ peers: string[];
11
+ action: "join" | "leave";
12
+ };
13
+ };
14
+
15
+ type EventKey = keyof EventMap;
16
+
17
+ type Handler<K extends EventKey> = (payload: EventMap[K]) => void;
18
+
19
+ function createEmitter() {
20
+ const listeners = new Map<EventKey, Set<Handler<any>>>();
21
+ return {
22
+ on<K extends EventKey>(event: K, handler: Handler<K>) {
23
+ const set = listeners.get(event) ?? new Set();
24
+ set.add(handler as Handler<any>);
25
+ listeners.set(event, set);
26
+ },
27
+ off<K extends EventKey>(event: K, handler: Handler<K>) {
28
+ const set = listeners.get(event);
29
+ if (!set) return;
30
+ set.delete(handler as Handler<any>);
31
+ if (set.size === 0) listeners.delete(event);
32
+ },
33
+ emit<K extends EventKey>(event: K, payload: EventMap[K]) {
34
+ const set = listeners.get(event);
35
+ if (!set) return;
36
+ for (const handler of Array.from(set)) handler(payload);
37
+ },
38
+ };
39
+ }
40
+
41
+ export type SignalingClientOptions = {
42
+ url: string;
43
+ peerId: string;
44
+ room?: string | null;
45
+ autoReconnect?: boolean;
46
+ reconnectDelayMs?: number;
47
+ };
48
+
49
+ type IncomingMessage =
50
+ | { type: "signal"; from: string; data: unknown }
51
+ | { type: "broadcast"; from: string; room?: string | null; data: unknown }
52
+ | {
53
+ type: "presence";
54
+ room?: string | null;
55
+ peerId: string;
56
+ peers: string[];
57
+ action: "join" | "leave";
58
+ };
59
+
60
+ type OutgoingMessage =
61
+ | { type: "signal"; to: string; data: unknown }
62
+ | { type: "broadcast"; data: unknown };
63
+
64
+ export class SignalingClient {
65
+ private ws: WebSocket | null = null;
66
+ private options: SignalingClientOptions;
67
+ private emitter = createEmitter();
68
+ private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
69
+ private shouldReconnect: boolean;
70
+ private pendingQueue: OutgoingMessage[] = [];
71
+
72
+ constructor(options: SignalingClientOptions) {
73
+ this.options = options;
74
+ this.shouldReconnect = options.autoReconnect ?? true;
75
+ }
76
+
77
+ connect() {
78
+ if (typeof window === "undefined") return;
79
+ if (
80
+ this.ws &&
81
+ (this.ws.readyState === WebSocket.OPEN ||
82
+ this.ws.readyState === WebSocket.CONNECTING)
83
+ ) {
84
+ return;
85
+ }
86
+
87
+ const { url, peerId, room } = this.options;
88
+ const wsUrl = `${url}?peerId=${encodeURIComponent(peerId)}${
89
+ room ? `&room=${encodeURIComponent(room)}` : ""
90
+ }`;
91
+ this.ws = new WebSocket(wsUrl);
92
+
93
+ this.ws.addEventListener("open", () => {
94
+ this.emitter.emit("open", undefined as void);
95
+ this.flushQueue();
96
+ });
97
+
98
+ this.ws.addEventListener("message", (event) => {
99
+ const payload = typeof event.data === "string" ? event.data : "";
100
+ try {
101
+ const parsed: IncomingMessage = JSON.parse(payload);
102
+ if (parsed.type === "signal") {
103
+ this.emitter.emit("signal", { from: parsed.from, data: parsed.data });
104
+ } else if (parsed.type === "broadcast") {
105
+ this.emitter.emit("broadcast", {
106
+ from: parsed.from,
107
+ room: parsed.room,
108
+ data: parsed.data,
109
+ });
110
+ } else if (parsed.type === "presence") {
111
+ this.emitter.emit("presence", {
112
+ room: parsed.room,
113
+ peerId: parsed.peerId,
114
+ peers: parsed.peers,
115
+ action: parsed.action,
116
+ });
117
+ }
118
+ } catch (error) {
119
+ this.emitter.emit("error", error as Error);
120
+ }
121
+ });
122
+
123
+ this.ws.addEventListener("close", (event) => {
124
+ this.emitter.emit("close", event);
125
+ this.scheduleReconnect();
126
+ });
127
+
128
+ this.ws.addEventListener("error", () => {
129
+ this.emitter.emit("error", new Error("WebSocket error"));
130
+ });
131
+ }
132
+
133
+ close() {
134
+ this.shouldReconnect = false;
135
+ if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
136
+ this.ws?.close();
137
+ this.ws = null;
138
+ }
139
+
140
+ sendSignal(to: string, data: unknown) {
141
+ this.enqueue({ type: "signal", to, data });
142
+ }
143
+
144
+ broadcast(data: unknown) {
145
+ this.enqueue({ type: "broadcast", data });
146
+ }
147
+
148
+ on<K extends EventKey>(event: K, handler: Handler<K>) {
149
+ this.emitter.on(event, handler);
150
+ }
151
+
152
+ off<K extends EventKey>(event: K, handler: Handler<K>) {
153
+ this.emitter.off(event, handler);
154
+ }
155
+
156
+ private enqueue(message: OutgoingMessage) {
157
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
158
+ this.ws.send(JSON.stringify(message));
159
+ return;
160
+ }
161
+ this.pendingQueue.push(message);
162
+ }
163
+
164
+ private flushQueue() {
165
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
166
+ while (this.pendingQueue.length) {
167
+ const msg = this.pendingQueue.shift();
168
+ if (msg) this.ws.send(JSON.stringify(msg));
169
+ }
170
+ }
171
+
172
+ private scheduleReconnect() {
173
+ if (!this.shouldReconnect) return;
174
+ const delay = this.options.reconnectDelayMs ?? 1000;
175
+ if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
176
+ this.reconnectTimeout = setTimeout(() => this.connect(), delay);
177
+ }
178
+ }
@@ -0,0 +1,219 @@
1
+ import { ref, onMounted, onUnmounted, computed, watch } from "vue";
2
+ import { Peer, type PeerSide, type SignalData } from "@conference-kit/core";
3
+ import { SignalingClient } from "./signaling";
4
+
5
+ export type MeshParticipant = {
6
+ id: string;
7
+ peer: Peer;
8
+ remoteStream: MediaStream | null;
9
+ connectionState: RTCPeerConnectionState;
10
+ iceState: RTCIceConnectionState;
11
+ };
12
+
13
+ export type UseMeshRoomOptions = {
14
+ peerId: string;
15
+ room: string;
16
+ signalingUrl: string;
17
+ mediaConstraints?: MediaStreamConstraints;
18
+ rtcConfig?: RTCConfiguration;
19
+ trickle?: boolean;
20
+ autoReconnect?: boolean;
21
+ features?: { enableDataChannel?: boolean };
22
+ };
23
+
24
+ export function useMeshRoom(options: UseMeshRoomOptions) {
25
+ const {
26
+ peerId,
27
+ room,
28
+ signalingUrl,
29
+ mediaConstraints,
30
+ rtcConfig,
31
+ trickle,
32
+ autoReconnect,
33
+ } = options;
34
+
35
+ const localStream = ref<MediaStream | null>(null);
36
+ const requesting = ref(false);
37
+ const mediaError = ref<Error | null>(null);
38
+ const previousStream = ref<MediaStream | null>(null);
39
+
40
+ const roster = ref<string[]>([]);
41
+ const participants = ref<MeshParticipant[]>([]);
42
+ const error = ref<Error | null>(null);
43
+
44
+ const signaling = new SignalingClient({
45
+ url: signalingUrl,
46
+ peerId,
47
+ room,
48
+ autoReconnect,
49
+ });
50
+
51
+ const enableDataChannel = options.features?.enableDataChannel ?? true;
52
+
53
+ const peers = new Map<string, Peer>();
54
+
55
+ const sideForPeer = (otherId: string): PeerSide =>
56
+ peerId > otherId ? "initiator" : "responder";
57
+
58
+ const requestStream = async () => {
59
+ try {
60
+ requesting.value = true;
61
+ const constraints = mediaConstraints ?? { audio: true, video: true };
62
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
63
+ localStream.value = stream;
64
+ mediaError.value = null;
65
+ } catch (err) {
66
+ mediaError.value = err as Error;
67
+ localStream.value = null;
68
+ } finally {
69
+ requesting.value = false;
70
+ }
71
+ };
72
+
73
+ const stopStream = () => {
74
+ localStream.value?.getTracks().forEach((t) => t.stop());
75
+ localStream.value = null;
76
+ };
77
+
78
+ const destroyPeer = (id: string) => {
79
+ const peer = peers.get(id);
80
+ if (peer) {
81
+ peer.destroy();
82
+ peers.delete(id);
83
+ }
84
+ participants.value = participants.value.filter((p) => p.id !== id);
85
+ };
86
+
87
+ const upsertParticipant = (id: string, patch: Partial<MeshParticipant>) => {
88
+ const existing = participants.value.find((p) => p.id === id);
89
+ if (!existing) {
90
+ participants.value = [
91
+ ...participants.value,
92
+ {
93
+ id,
94
+ peer: patch.peer as MeshParticipant["peer"],
95
+ remoteStream: patch.remoteStream ?? null,
96
+ connectionState: patch.connectionState ?? "new",
97
+ iceState: patch.iceState ?? "new",
98
+ },
99
+ ];
100
+ return;
101
+ }
102
+ participants.value = participants.value.map((p) =>
103
+ p.id === id ? { ...p, ...patch } : p
104
+ );
105
+ };
106
+
107
+ const ensurePeer = (id: string, side?: PeerSide) => {
108
+ if (id === peerId) return null;
109
+ const existing = peers.get(id);
110
+ if (existing) return existing;
111
+ const peer = new Peer({
112
+ side: side ?? sideForPeer(id),
113
+ stream: localStream.value ?? undefined,
114
+ config: rtcConfig,
115
+ trickle,
116
+ enableDataChannel,
117
+ });
118
+ peers.set(id, peer);
119
+ upsertParticipant(id, {
120
+ peer,
121
+ remoteStream: null,
122
+ connectionState: "new",
123
+ iceState: "new",
124
+ });
125
+
126
+ const handlers = {
127
+ signal: (data: SignalData) => signaling.sendSignal(id, data),
128
+ stream: (remote: MediaStream) =>
129
+ upsertParticipant(id, { remoteStream: remote }),
130
+ error: (err: Error) => (error.value = err),
131
+ connectionStateChange: (state: RTCPeerConnectionState) =>
132
+ upsertParticipant(id, { connectionState: state }),
133
+ iceStateChange: (state: RTCIceConnectionState) =>
134
+ upsertParticipant(id, { iceState: state }),
135
+ close: () => destroyPeer(id),
136
+ };
137
+
138
+ peer.on("signal", handlers.signal);
139
+ peer.on("stream", handlers.stream);
140
+ peer.on("error", handlers.error);
141
+ peer.on("connectionStateChange", handlers.connectionStateChange);
142
+ peer.on("iceStateChange", handlers.iceStateChange);
143
+ peer.on("close", handlers.close);
144
+
145
+ return peer;
146
+ };
147
+
148
+ const handlePresence = (payload: {
149
+ peers: string[];
150
+ peerId: string;
151
+ room?: string | null;
152
+ action: "join" | "leave";
153
+ }) => {
154
+ roster.value = payload.peers;
155
+ payload.peers.filter((id) => id !== peerId).forEach((id) => ensurePeer(id));
156
+ participants.value = participants.value.filter((p) =>
157
+ payload.peers.includes(p.id)
158
+ );
159
+ Array.from(peers.keys()).forEach((id) => {
160
+ if (!payload.peers.includes(id)) destroyPeer(id);
161
+ });
162
+ };
163
+
164
+ const handleSignal = ({ from, data }: { from: string; data: unknown }) => {
165
+ const peer = ensurePeer(from, sideForPeer(from));
166
+ void peer?.signal(data as SignalData);
167
+ };
168
+
169
+ onMounted(() => {
170
+ signaling.on("presence", handlePresence as any);
171
+ signaling.on("signal", handleSignal as any);
172
+ signaling.connect();
173
+ });
174
+
175
+ onUnmounted(() => {
176
+ signaling.off("presence", handlePresence as any);
177
+ signaling.off("signal", handleSignal as any);
178
+ Array.from(peers.values()).forEach((p) => p.destroy());
179
+ peers.clear();
180
+ stopStream();
181
+ signaling.close();
182
+ });
183
+
184
+ const leave = () => {
185
+ Array.from(peers.values()).forEach((p) => p.destroy());
186
+ peers.clear();
187
+ participants.value = [];
188
+ roster.value = [];
189
+ stopStream();
190
+ signaling.close();
191
+ };
192
+
193
+ const ready = computed(() => Boolean(localStream.value));
194
+
195
+ watch(
196
+ () => localStream.value,
197
+ (next, prev) => {
198
+ if (prev === next) return;
199
+ Array.from(peers.values()).forEach((peer) => {
200
+ if (prev) peer.removeStream(prev);
201
+ if (next) peer.addStream(next);
202
+ });
203
+ previousStream.value = next ?? null;
204
+ }
205
+ );
206
+
207
+ return {
208
+ localStream,
209
+ requesting,
210
+ ready,
211
+ mediaError,
212
+ participants,
213
+ roster,
214
+ requestStream,
215
+ stopStream,
216
+ leave,
217
+ error,
218
+ } as const;
219
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "jsx": "preserve"
7
+ },
8
+ "include": ["src/**/*"]
9
+ }