@burdenoff/microfe-eventfullymanaged 2026.728.1 → 2026.731.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.
- package/dist/eventfullymanaged/EventfullyManagedRoutes.d.ts.map +1 -1
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js +60 -76
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js.map +1 -1
- package/dist/eventfullymanaged/api/eventApi.d.ts +245 -1
- package/dist/eventfullymanaged/api/eventApi.d.ts.map +1 -1
- package/dist/eventfullymanaged/api/eventApi.js +482 -264
- package/dist/eventfullymanaged/api/eventApi.js.map +1 -1
- package/dist/eventfullymanaged/hooks/useEvmSubscription.d.ts +18 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.d.ts.map +1 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.js +110 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.js.map +1 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.d.ts +47 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.d.ts.map +1 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.js +209 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.js.map +1 -0
- package/dist/eventfullymanaged/pages/events/EventDetailPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js +79 -71
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.d.ts +2 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.d.ts.map +1 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.js +353 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.js.map +1 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.d.ts +2 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.d.ts.map +1 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.js +336 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.js.map +1 -0
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.js +211 -177
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js +204 -174
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js.map +1 -1
- package/dist/eventfullymanaged/utils/shellBridge.d.ts +46 -0
- package/dist/eventfullymanaged/utils/shellBridge.d.ts.map +1 -0
- package/dist/eventfullymanaged/utils/shellBridge.js +19 -0
- package/dist/eventfullymanaged/utils/shellBridge.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type VirtualCallRole = 'HOST' | 'PRESENTER' | 'ATTENDEE';
|
|
2
|
+
export type VirtualCallSignalType = 'OFFER' | 'ANSWER' | 'ICE_CANDIDATE';
|
|
3
|
+
export interface VirtualCallParticipant {
|
|
4
|
+
actorId: string;
|
|
5
|
+
role: VirtualCallRole;
|
|
6
|
+
}
|
|
7
|
+
export interface IncomingVirtualSignal {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
fromActorId: string;
|
|
10
|
+
toActorId: string;
|
|
11
|
+
signalType: VirtualCallSignalType;
|
|
12
|
+
payload: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface UseVirtualCallArgs {
|
|
15
|
+
/** Null/empty disables the hook (no active session yet). */
|
|
16
|
+
sessionId: string | null | undefined;
|
|
17
|
+
selfActorId: string;
|
|
18
|
+
mode: 'MEETING' | 'WEBINAR';
|
|
19
|
+
selfRole: VirtualCallRole | null;
|
|
20
|
+
/** Full active roster, including self. */
|
|
21
|
+
participants: VirtualCallParticipant[];
|
|
22
|
+
sendSignal: (args: {
|
|
23
|
+
toActorId: string;
|
|
24
|
+
signalType: VirtualCallSignalType;
|
|
25
|
+
payload: unknown;
|
|
26
|
+
}) => Promise<unknown>;
|
|
27
|
+
muted: boolean;
|
|
28
|
+
videoEnabled: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface UseVirtualCallResult {
|
|
31
|
+
/** Local camera/mic stream, once granted. Null while pending/denied. */
|
|
32
|
+
localStream: MediaStream | null;
|
|
33
|
+
/** Remote peer streams keyed by actorId. */
|
|
34
|
+
remoteStreams: Map<string, MediaStream>;
|
|
35
|
+
/** Connection state per remote peer, for UI status dots. */
|
|
36
|
+
connectionStates: Map<string, RTCPeerConnectionState>;
|
|
37
|
+
/** Set when getUserMedia fails (denied / no device / insecure context). */
|
|
38
|
+
mediaError: string | null;
|
|
39
|
+
/** Feed a signal received from the `eventfullyManagedVirtualSignal` subscription/poll. */
|
|
40
|
+
handleIncomingSignal: (signal: IncomingVirtualSignal) => void;
|
|
41
|
+
}
|
|
42
|
+
/** Should `a` peer with `b` at all, given the session mode and their roles? */
|
|
43
|
+
export declare function shouldConnect(a: VirtualCallParticipant, b: VirtualCallParticipant, mode: 'MEETING' | 'WEBINAR'): boolean;
|
|
44
|
+
/** Does `self` initiate the offer toward `other`? */
|
|
45
|
+
export declare function isInitiator(self: VirtualCallParticipant, other: VirtualCallParticipant, mode: 'MEETING' | 'WEBINAR'): boolean;
|
|
46
|
+
export declare function useVirtualCall({ sessionId, selfActorId, mode, selfRole, participants, sendSignal, muted, videoEnabled, }: UseVirtualCallArgs): UseVirtualCallResult;
|
|
47
|
+
//# sourceMappingURL=useVirtualCall.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVirtualCall.d.ts","sourceRoot":"","sources":["../../../src/eventfullymanaged/hooks/useVirtualCall.ts"],"names":[],"mappings":"AAoCA,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;AAChE,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,QAAQ,GAAG,eAAe,CAAC;AAEzE,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,eAAe,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,qBAAqB,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,0CAA0C;IAC1C,YAAY,EAAE,sBAAsB,EAAE,CAAC;IACvC,UAAU,EAAE,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,qBAAqB,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnH,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,wEAAwE;IACxE,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,4CAA4C;IAC5C,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACxC,4DAA4D;IAC5D,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IACtD,2EAA2E;IAC3E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,0FAA0F;IAC1F,oBAAoB,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAC/D;AAID,+EAA+E;AAC/E,wBAAgB,aAAa,CAC3B,CAAC,EAAE,sBAAsB,EACzB,CAAC,EAAE,sBAAsB,EACzB,IAAI,EAAE,SAAS,GAAG,SAAS,GAC1B,OAAO,CAMT;AAED,qDAAqD;AACrD,wBAAgB,WAAW,CACzB,IAAI,EAAE,sBAAsB,EAC5B,KAAK,EAAE,sBAAsB,EAC7B,IAAI,EAAE,SAAS,GAAG,SAAS,GAC1B,OAAO,CAUT;AAED,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,WAAW,EACX,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,KAAK,EACL,YAAY,GACb,EAAE,kBAAkB,GAAG,oBAAoB,CA6Q3C"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { reportShellError as e } from "../utils/shellBridge.js";
|
|
2
|
+
import { useCallback as t, useEffect as n, useRef as r, useState as i } from "react";
|
|
3
|
+
//#region src/eventfullymanaged/hooks/useVirtualCall.ts
|
|
4
|
+
var a = [{ urls: "stun:stun.l.google.com:19302" }];
|
|
5
|
+
function o(e, t, n) {
|
|
6
|
+
if (n === "MEETING") return !0;
|
|
7
|
+
let r = e.role === "HOST" || e.role === "PRESENTER", i = t.role === "HOST" || t.role === "PRESENTER";
|
|
8
|
+
return r || i;
|
|
9
|
+
}
|
|
10
|
+
function s(e, t, n) {
|
|
11
|
+
if (n === "WEBINAR") {
|
|
12
|
+
let n = e.role === "HOST" || e.role === "PRESENTER";
|
|
13
|
+
if (n !== (t.role === "HOST" || t.role === "PRESENTER")) return n;
|
|
14
|
+
}
|
|
15
|
+
return e.actorId < t.actorId;
|
|
16
|
+
}
|
|
17
|
+
function c({ sessionId: c, selfActorId: l, mode: u, selfRole: d, participants: f, sendSignal: p, muted: m, videoEnabled: h }) {
|
|
18
|
+
let [g, _] = i(null), [v, y] = i(null), [b, x] = i(/* @__PURE__ */ new Map()), [S, C] = i(/* @__PURE__ */ new Map()), w = r(/* @__PURE__ */ new Map()), T = r(null), E = r(p), D = r(/* @__PURE__ */ new Map());
|
|
19
|
+
n(() => {
|
|
20
|
+
E.current = p;
|
|
21
|
+
}, [p]), n(() => {
|
|
22
|
+
if (!c) return;
|
|
23
|
+
let e = !1, t = typeof navigator < "u" ? navigator : void 0;
|
|
24
|
+
if (!t?.mediaDevices?.getUserMedia) {
|
|
25
|
+
y("This browser does not support camera/microphone capture (getUserMedia unavailable).");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
return t.mediaDevices.getUserMedia({
|
|
29
|
+
audio: !0,
|
|
30
|
+
video: !0
|
|
31
|
+
}).then((t) => {
|
|
32
|
+
if (e) {
|
|
33
|
+
t.getTracks().forEach((e) => e.stop());
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
T.current = t, _(t), y(null);
|
|
37
|
+
}).catch((t) => {
|
|
38
|
+
e || y(t instanceof Error ? t.message : "Camera/microphone access was denied.");
|
|
39
|
+
}), () => {
|
|
40
|
+
e = !0, T.current?.getTracks().forEach((e) => e.stop()), T.current = null, _(null);
|
|
41
|
+
};
|
|
42
|
+
}, [c]), n(() => {
|
|
43
|
+
g?.getAudioTracks().forEach((e) => {
|
|
44
|
+
e.enabled = !m;
|
|
45
|
+
});
|
|
46
|
+
}, [g, m]), n(() => {
|
|
47
|
+
g?.getVideoTracks().forEach((e) => {
|
|
48
|
+
e.enabled = h;
|
|
49
|
+
});
|
|
50
|
+
}, [g, h]);
|
|
51
|
+
let O = t((e) => {
|
|
52
|
+
let t = w.current.get(e);
|
|
53
|
+
if (t) {
|
|
54
|
+
try {
|
|
55
|
+
t.close();
|
|
56
|
+
} catch {}
|
|
57
|
+
w.current.delete(e);
|
|
58
|
+
}
|
|
59
|
+
D.current.delete(e), x((t) => {
|
|
60
|
+
if (!t.has(e)) return t;
|
|
61
|
+
let n = new Map(t);
|
|
62
|
+
return n.delete(e), n;
|
|
63
|
+
}), C((t) => {
|
|
64
|
+
if (!t.has(e)) return t;
|
|
65
|
+
let n = new Map(t);
|
|
66
|
+
return n.delete(e), n;
|
|
67
|
+
});
|
|
68
|
+
}, []), k = t((t) => {
|
|
69
|
+
let n = new RTCPeerConnection({ iceServers: a });
|
|
70
|
+
return w.current.set(t.actorId, n), T.current?.getTracks().forEach((e) => {
|
|
71
|
+
n.addTrack(e, T.current);
|
|
72
|
+
}), n.ontrack = (e) => {
|
|
73
|
+
let [n] = e.streams;
|
|
74
|
+
n && x((e) => {
|
|
75
|
+
let r = new Map(e);
|
|
76
|
+
return r.set(t.actorId, n), r;
|
|
77
|
+
});
|
|
78
|
+
}, n.onconnectionstatechange = () => {
|
|
79
|
+
C((e) => {
|
|
80
|
+
let r = new Map(e);
|
|
81
|
+
return r.set(t.actorId, n.connectionState), r;
|
|
82
|
+
}), (n.connectionState === "failed" || n.connectionState === "closed") && O(t.actorId);
|
|
83
|
+
}, n.onicecandidate = (n) => {
|
|
84
|
+
n.candidate && E.current({
|
|
85
|
+
toActorId: t.actorId,
|
|
86
|
+
signalType: "ICE_CANDIDATE",
|
|
87
|
+
payload: n.candidate.toJSON()
|
|
88
|
+
}).catch((t) => {
|
|
89
|
+
e(t, {
|
|
90
|
+
module: "eventfullymanaged",
|
|
91
|
+
context: { phase: "ice-candidate-send" }
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}, n;
|
|
95
|
+
}, [O]), A = t(async (t) => {
|
|
96
|
+
let n = k(t);
|
|
97
|
+
try {
|
|
98
|
+
let e = await n.createOffer();
|
|
99
|
+
await n.setLocalDescription(e), await E.current({
|
|
100
|
+
toActorId: t.actorId,
|
|
101
|
+
signalType: "OFFER",
|
|
102
|
+
payload: {
|
|
103
|
+
type: e.type,
|
|
104
|
+
sdp: e.sdp
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
} catch (n) {
|
|
108
|
+
e(n, {
|
|
109
|
+
module: "eventfullymanaged",
|
|
110
|
+
context: {
|
|
111
|
+
phase: "create-offer",
|
|
112
|
+
to: t.actorId
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}, [k]);
|
|
117
|
+
return n(() => {
|
|
118
|
+
if (!c || !d) return;
|
|
119
|
+
let e = {
|
|
120
|
+
actorId: l,
|
|
121
|
+
role: d
|
|
122
|
+
}, t = f.filter((e) => e.actorId !== l), n = new Set(t.filter((t) => o(e, t, u)).map((e) => e.actorId));
|
|
123
|
+
for (let e of Array.from(w.current.keys())) n.has(e) || O(e);
|
|
124
|
+
if (T.current) for (let r of t) n.has(r.actorId) && (w.current.has(r.actorId) || s(e, r, u) && A(r));
|
|
125
|
+
}, [
|
|
126
|
+
c,
|
|
127
|
+
l,
|
|
128
|
+
d,
|
|
129
|
+
u,
|
|
130
|
+
f,
|
|
131
|
+
g,
|
|
132
|
+
O,
|
|
133
|
+
A
|
|
134
|
+
]), n(() => {
|
|
135
|
+
if (!c) for (let e of Array.from(w.current.keys())) O(e);
|
|
136
|
+
}, [c, O]), n(() => () => {
|
|
137
|
+
for (let e of Array.from(w.current.keys())) O(e);
|
|
138
|
+
}, [O]), {
|
|
139
|
+
localStream: g,
|
|
140
|
+
remoteStreams: b,
|
|
141
|
+
connectionStates: S,
|
|
142
|
+
mediaError: v,
|
|
143
|
+
handleIncomingSignal: t((t) => {
|
|
144
|
+
if (t.sessionId !== c || t.toActorId !== l) return;
|
|
145
|
+
let n = t.fromActorId, r = (e) => {
|
|
146
|
+
let t = D.current.get(n);
|
|
147
|
+
if (t?.length) {
|
|
148
|
+
D.current.delete(n);
|
|
149
|
+
for (let n of t) e.addIceCandidate(new RTCIceCandidate(n)).catch(() => {});
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
if (t.signalType === "OFFER") {
|
|
153
|
+
let i = {
|
|
154
|
+
actorId: n,
|
|
155
|
+
role: "ATTENDEE"
|
|
156
|
+
}, a = w.current.get(n);
|
|
157
|
+
a ||= k(i);
|
|
158
|
+
let o = t.payload;
|
|
159
|
+
a.setRemoteDescription(new RTCSessionDescription(o)).then(() => (r(a), a.createAnswer())).then((e) => a.setLocalDescription(e).then(() => e)).then((e) => E.current({
|
|
160
|
+
toActorId: n,
|
|
161
|
+
signalType: "ANSWER",
|
|
162
|
+
payload: {
|
|
163
|
+
type: e.type,
|
|
164
|
+
sdp: e.sdp
|
|
165
|
+
}
|
|
166
|
+
})).catch((t) => {
|
|
167
|
+
e(t, {
|
|
168
|
+
module: "eventfullymanaged",
|
|
169
|
+
context: {
|
|
170
|
+
phase: "handle-offer",
|
|
171
|
+
from: n
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (t.signalType === "ANSWER") {
|
|
178
|
+
let i = w.current.get(n);
|
|
179
|
+
if (!i) return;
|
|
180
|
+
let a = t.payload;
|
|
181
|
+
i.setRemoteDescription(new RTCSessionDescription(a)).then(() => r(i)).catch((t) => {
|
|
182
|
+
e(t, {
|
|
183
|
+
module: "eventfullymanaged",
|
|
184
|
+
context: {
|
|
185
|
+
phase: "handle-answer",
|
|
186
|
+
from: n
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
let i = w.current.get(n), a = t.payload;
|
|
193
|
+
if (!i || !i.remoteDescription) {
|
|
194
|
+
let e = D.current.get(n) ?? [];
|
|
195
|
+
e.push(a), D.current.set(n, e);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
i.addIceCandidate(new RTCIceCandidate(a)).catch(() => {});
|
|
199
|
+
}, [
|
|
200
|
+
c,
|
|
201
|
+
l,
|
|
202
|
+
k
|
|
203
|
+
])
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
export { c as useVirtualCall };
|
|
208
|
+
|
|
209
|
+
//# sourceMappingURL=useVirtualCall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVirtualCall.js","names":[],"sources":["../../../src/eventfullymanaged/hooks/useVirtualCall.ts"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect -- getUserMedia failure is detected synchronously (missing API) before the async capture path even starts; there is no external-system callback to defer into */\n/**\n * useVirtualCall (BOFF-4318)\n *\n * Real browser-native WebRTC peer-connection manager for a virtual/hybrid\n * session. Mirrors the backend's two bounded topologies\n * (`VirtualSessionService`, `wspace-eventfullymanaged-svc`):\n *\n * - MEETING (full mesh, cap `MAX_MESH_PARTICIPANTS`): connects to every\n * other active participant.\n * - WEBINAR (star, cap `MAX_WEBINAR_PARTICIPANTS`): HOST/PRESENTER peers\n * connect to every attendee AND each other; ATTENDEE peers connect only\n * to HOST/PRESENTER peers, never to each other.\n *\n * Offer/answer polarity (who calls whom) is resolved without a central\n * signaling server picking sides:\n * - In a HOST/PRESENTER↔ATTENDEE pair, the HOST/PRESENTER always initiates\n * (matches the star's broadcast direction).\n * - Otherwise (mesh peers, or two HOST/PRESENTER peers), the\n * lexicographically smaller `actorId` initiates — deterministic without\n * coordination, avoids \"glare\" (both sides sending an offer at once).\n *\n * Signaling (SDP offer/answer + ICE candidates) is relayed via the\n * `eventfullyManagedSendVirtualSignal` mutation and delivered back over the\n * `eventfullyManagedVirtualSignal` subscription (see `useEvmSubscription`).\n * WebRTC itself never talks to any Burdenoff backend for media — only a\n * public STUN server (`stun.l.google.com`) for NAT traversal, the same\n * no-paid-vendor approach documented in ComradeCircle's BOFF-4450 voice\n * core. No TURN relay is configured — peers behind symmetric NAT/strict\n * firewalls may fail to connect; that is an explicit known limitation of a\n * STUN-only bounded core, not a bug.\n */\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { reportShellError } from '../utils/shellBridge';\n\nexport type VirtualCallRole = 'HOST' | 'PRESENTER' | 'ATTENDEE';\nexport type VirtualCallSignalType = 'OFFER' | 'ANSWER' | 'ICE_CANDIDATE';\n\nexport interface VirtualCallParticipant {\n actorId: string;\n role: VirtualCallRole;\n}\n\nexport interface IncomingVirtualSignal {\n sessionId: string;\n fromActorId: string;\n toActorId: string;\n signalType: VirtualCallSignalType;\n payload: unknown;\n}\n\nexport interface UseVirtualCallArgs {\n /** Null/empty disables the hook (no active session yet). */\n sessionId: string | null | undefined;\n selfActorId: string;\n mode: 'MEETING' | 'WEBINAR';\n selfRole: VirtualCallRole | null;\n /** Full active roster, including self. */\n participants: VirtualCallParticipant[];\n sendSignal: (args: { toActorId: string; signalType: VirtualCallSignalType; payload: unknown }) => Promise<unknown>;\n muted: boolean;\n videoEnabled: boolean;\n}\n\nexport interface UseVirtualCallResult {\n /** Local camera/mic stream, once granted. Null while pending/denied. */\n localStream: MediaStream | null;\n /** Remote peer streams keyed by actorId. */\n remoteStreams: Map<string, MediaStream>;\n /** Connection state per remote peer, for UI status dots. */\n connectionStates: Map<string, RTCPeerConnectionState>;\n /** Set when getUserMedia fails (denied / no device / insecure context). */\n mediaError: string | null;\n /** Feed a signal received from the `eventfullyManagedVirtualSignal` subscription/poll. */\n handleIncomingSignal: (signal: IncomingVirtualSignal) => void;\n}\n\nconst ICE_SERVERS: RTCIceServer[] = [{ urls: 'stun:stun.l.google.com:19302' }];\n\n/** Should `a` peer with `b` at all, given the session mode and their roles? */\nexport function shouldConnect(\n a: VirtualCallParticipant,\n b: VirtualCallParticipant,\n mode: 'MEETING' | 'WEBINAR'\n): boolean {\n if (mode === 'MEETING') return true;\n const aBroadcasts = a.role === 'HOST' || a.role === 'PRESENTER';\n const bBroadcasts = b.role === 'HOST' || b.role === 'PRESENTER';\n // WEBINAR (star): attendee<->attendee pairs never connect.\n return aBroadcasts || bBroadcasts;\n}\n\n/** Does `self` initiate the offer toward `other`? */\nexport function isInitiator(\n self: VirtualCallParticipant,\n other: VirtualCallParticipant,\n mode: 'MEETING' | 'WEBINAR'\n): boolean {\n if (mode === 'WEBINAR') {\n const selfBroadcasts = self.role === 'HOST' || self.role === 'PRESENTER';\n const otherBroadcasts = other.role === 'HOST' || other.role === 'PRESENTER';\n if (selfBroadcasts !== otherBroadcasts) {\n // Broadcaster always initiates toward a plain attendee.\n return selfBroadcasts;\n }\n }\n return self.actorId < other.actorId;\n}\n\nexport function useVirtualCall({\n sessionId,\n selfActorId,\n mode,\n selfRole,\n participants,\n sendSignal,\n muted,\n videoEnabled,\n}: UseVirtualCallArgs): UseVirtualCallResult {\n const [localStream, setLocalStream] = useState<MediaStream | null>(null);\n const [mediaError, setMediaError] = useState<string | null>(null);\n const [remoteStreams, setRemoteStreams] = useState<Map<string, MediaStream>>(new Map());\n const [connectionStates, setConnectionStates] = useState<Map<string, RTCPeerConnectionState>>(new Map());\n\n const peersRef = useRef<Map<string, RTCPeerConnection>>(new Map());\n const localStreamRef = useRef<MediaStream | null>(null);\n const sendSignalRef = useRef(sendSignal);\n const pendingCandidatesRef = useRef<Map<string, RTCIceCandidateInit[]>>(new Map());\n useEffect(() => {\n sendSignalRef.current = sendSignal;\n }, [sendSignal]);\n\n // Acquire local media once a session exists.\n useEffect(() => {\n if (!sessionId) return;\n let cancelled = false;\n const nav = typeof navigator !== 'undefined' ? navigator : undefined;\n if (!nav?.mediaDevices?.getUserMedia) {\n setMediaError('This browser does not support camera/microphone capture (getUserMedia unavailable).');\n return;\n }\n nav.mediaDevices\n .getUserMedia({ audio: true, video: true })\n .then((stream) => {\n if (cancelled) {\n stream.getTracks().forEach((t) => t.stop());\n return;\n }\n localStreamRef.current = stream;\n setLocalStream(stream);\n setMediaError(null);\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n const message = err instanceof Error ? err.message : 'Camera/microphone access was denied.';\n setMediaError(message);\n });\n return () => {\n cancelled = true;\n localStreamRef.current?.getTracks().forEach((t) => t.stop());\n localStreamRef.current = null;\n setLocalStream(null);\n };\n }, [sessionId]);\n\n // Reflect local mute/video toggles onto the actual media tracks.\n useEffect(() => {\n localStream?.getAudioTracks().forEach((track) => {\n track.enabled = !muted;\n });\n }, [localStream, muted]);\n useEffect(() => {\n localStream?.getVideoTracks().forEach((track) => {\n track.enabled = videoEnabled;\n });\n }, [localStream, videoEnabled]);\n\n const closePeer = useCallback((actorId: string) => {\n const pc = peersRef.current.get(actorId);\n if (pc) {\n try {\n pc.close();\n } catch {\n /* noop */\n }\n peersRef.current.delete(actorId);\n }\n pendingCandidatesRef.current.delete(actorId);\n setRemoteStreams((prev) => {\n if (!prev.has(actorId)) return prev;\n const next = new Map(prev);\n next.delete(actorId);\n return next;\n });\n setConnectionStates((prev) => {\n if (!prev.has(actorId)) return prev;\n const next = new Map(prev);\n next.delete(actorId);\n return next;\n });\n }, []);\n\n const createPeer = useCallback(\n (remote: VirtualCallParticipant): RTCPeerConnection => {\n const pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });\n peersRef.current.set(remote.actorId, pc);\n\n localStreamRef.current?.getTracks().forEach((track) => {\n pc.addTrack(track, localStreamRef.current as MediaStream);\n });\n\n pc.ontrack = (event) => {\n const [stream] = event.streams;\n if (!stream) return;\n setRemoteStreams((prev) => {\n const next = new Map(prev);\n next.set(remote.actorId, stream);\n return next;\n });\n };\n\n pc.onconnectionstatechange = () => {\n setConnectionStates((prev) => {\n const next = new Map(prev);\n next.set(remote.actorId, pc.connectionState);\n return next;\n });\n if (pc.connectionState === 'failed' || pc.connectionState === 'closed') {\n closePeer(remote.actorId);\n }\n };\n\n pc.onicecandidate = (event) => {\n if (!event.candidate) return;\n void sendSignalRef\n .current({\n toActorId: remote.actorId,\n signalType: 'ICE_CANDIDATE',\n payload: event.candidate.toJSON(),\n })\n .catch((err: unknown) => {\n reportShellError(err, { module: 'eventfullymanaged', context: { phase: 'ice-candidate-send' } });\n });\n };\n\n return pc;\n },\n [closePeer]\n );\n\n const startOffer = useCallback(\n async (remote: VirtualCallParticipant) => {\n const pc = createPeer(remote);\n try {\n const offer = await pc.createOffer();\n await pc.setLocalDescription(offer);\n await sendSignalRef.current({\n toActorId: remote.actorId,\n signalType: 'OFFER',\n payload: { type: offer.type, sdp: offer.sdp },\n });\n } catch (err) {\n reportShellError(err, { module: 'eventfullymanaged', context: { phase: 'create-offer', to: remote.actorId } });\n }\n },\n [createPeer]\n );\n\n // Reconcile peer connections whenever the roster or media readiness changes.\n useEffect(() => {\n if (!sessionId || !selfRole) return;\n const self: VirtualCallParticipant = { actorId: selfActorId, role: selfRole };\n const others = participants.filter((p) => p.actorId !== selfActorId);\n const desired = new Set(others.filter((p) => shouldConnect(self, p, mode)).map((p) => p.actorId));\n\n // Drop peers no longer in the desired set (left the session).\n for (const actorId of Array.from(peersRef.current.keys())) {\n if (!desired.has(actorId)) closePeer(actorId);\n }\n\n // Only initiate once local media is ready — the offer needs tracks attached.\n if (!localStreamRef.current) return;\n\n for (const remote of others) {\n if (!desired.has(remote.actorId)) continue;\n if (peersRef.current.has(remote.actorId)) continue;\n if (isInitiator(self, remote, mode)) {\n void startOffer(remote);\n }\n // Non-initiators wait for an incoming OFFER signal (handled below);\n // no connection object is created until then.\n }\n }, [sessionId, selfActorId, selfRole, mode, participants, localStream, closePeer, startOffer]);\n\n // Full cleanup on session end / unmount.\n useEffect(() => {\n if (sessionId) return undefined;\n for (const actorId of Array.from(peersRef.current.keys())) closePeer(actorId);\n return undefined;\n }, [sessionId, closePeer]);\n useEffect(\n () => () => {\n for (const actorId of Array.from(peersRef.current.keys())) closePeer(actorId);\n },\n [closePeer]\n );\n\n const handleIncomingSignal = useCallback(\n (signal: IncomingVirtualSignal) => {\n if (signal.sessionId !== sessionId || signal.toActorId !== selfActorId) return;\n const fromActorId = signal.fromActorId;\n\n const flushPendingCandidates = (pc: RTCPeerConnection) => {\n const queued = pendingCandidatesRef.current.get(fromActorId);\n if (!queued?.length) return;\n pendingCandidatesRef.current.delete(fromActorId);\n for (const candidate of queued) {\n void pc.addIceCandidate(new RTCIceCandidate(candidate)).catch(() => {});\n }\n };\n\n if (signal.signalType === 'OFFER') {\n const remote: VirtualCallParticipant = {\n actorId: fromActorId,\n // Role is unknown from a bare signal; peer creation itself is\n // role-agnostic (RTCPeerConnection doesn't care), only the\n // *decision to connect* used role — the sender already made\n // that call by sending us an OFFER.\n role: 'ATTENDEE',\n };\n let pc = peersRef.current.get(fromActorId);\n if (!pc) pc = createPeer(remote);\n const desc = signal.payload as RTCSessionDescriptionInit;\n void pc\n .setRemoteDescription(new RTCSessionDescription(desc))\n .then(() => {\n flushPendingCandidates(pc as RTCPeerConnection);\n return (pc as RTCPeerConnection).createAnswer();\n })\n .then((answer) => (pc as RTCPeerConnection).setLocalDescription(answer).then(() => answer))\n .then((answer) =>\n sendSignalRef.current({\n toActorId: fromActorId,\n signalType: 'ANSWER',\n payload: { type: answer.type, sdp: answer.sdp },\n })\n )\n .catch((err: unknown) => {\n reportShellError(err, {\n module: 'eventfullymanaged',\n context: { phase: 'handle-offer', from: fromActorId },\n });\n });\n return;\n }\n\n if (signal.signalType === 'ANSWER') {\n const pc = peersRef.current.get(fromActorId);\n if (!pc) return;\n const desc = signal.payload as RTCSessionDescriptionInit;\n void pc\n .setRemoteDescription(new RTCSessionDescription(desc))\n .then(() => flushPendingCandidates(pc))\n .catch((err: unknown) => {\n reportShellError(err, {\n module: 'eventfullymanaged',\n context: { phase: 'handle-answer', from: fromActorId },\n });\n });\n return;\n }\n\n // ICE_CANDIDATE\n const pc = peersRef.current.get(fromActorId);\n const candidate = signal.payload as RTCIceCandidateInit;\n if (!pc || !pc.remoteDescription) {\n const queue = pendingCandidatesRef.current.get(fromActorId) ?? [];\n queue.push(candidate);\n pendingCandidatesRef.current.set(fromActorId, queue);\n return;\n }\n void pc.addIceCandidate(new RTCIceCandidate(candidate)).catch(() => {});\n },\n [sessionId, selfActorId, createPeer]\n );\n\n return { localStream, remoteStreams, connectionStates, mediaError, handleIncomingSignal };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/eventfullymanaged/pages/events/EventDetailPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EventDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/eventfullymanaged/pages/events/EventDetailPage.tsx"],"names":[],"mappings":"AA8qBA,MAAM,CAAC,OAAO,UAAU,eAAe,4CAiOtC"}
|