@pmate/chat-sdk 0.7.3 → 0.8.0-meeting-review.2
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/lib/runtime/call.d.ts +13 -0
- package/lib/runtime/call.js +41 -7
- package/lib/runtime/meeting.d.ts +2 -0
- package/lib/runtime/meeting.js +7 -5
- package/package.json +1 -1
package/lib/runtime/call.d.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
export type MeetingRoom = {
|
|
2
|
+
groupId: string;
|
|
3
|
+
meetingId: string;
|
|
4
|
+
startedAt: number;
|
|
5
|
+
initiatorId: string;
|
|
6
|
+
profileIds: string[];
|
|
7
|
+
};
|
|
1
8
|
export type CallPhase = "idle" | "requesting" | "ringing" | "incoming" | "connecting" | "connected" | "ended";
|
|
2
9
|
export type CallSnapshot = {
|
|
3
10
|
phase: CallPhase;
|
|
4
11
|
ready: boolean;
|
|
5
12
|
watchedPeerId?: string;
|
|
6
13
|
peerOnline?: boolean;
|
|
14
|
+
peerInCall?: boolean;
|
|
15
|
+
rooms?: MeetingRoom[];
|
|
16
|
+
roomsError?: string;
|
|
17
|
+
screenPending?: boolean;
|
|
7
18
|
callId?: string;
|
|
8
19
|
peerId?: string;
|
|
9
20
|
direction?: "incoming" | "outgoing";
|
|
@@ -48,6 +59,7 @@ export declare class CallClient {
|
|
|
48
59
|
private closed;
|
|
49
60
|
private capturePending;
|
|
50
61
|
private requestId?;
|
|
62
|
+
private groupIds;
|
|
51
63
|
private heartbeat?;
|
|
52
64
|
private timeout?;
|
|
53
65
|
private reconnect?;
|
|
@@ -57,6 +69,7 @@ export declare class CallClient {
|
|
|
57
69
|
subscribe(listener: (state: CallSnapshot) => void): () => void;
|
|
58
70
|
connect(): void;
|
|
59
71
|
watchPeer(peerId?: string): void;
|
|
72
|
+
watchGroups(groupIds: string[]): void;
|
|
60
73
|
start(peerId: string): Promise<void>;
|
|
61
74
|
accept(): Promise<void>;
|
|
62
75
|
hangup(): void;
|
package/lib/runtime/call.js
CHANGED
|
@@ -13,6 +13,7 @@ export class CallClient {
|
|
|
13
13
|
closed = false;
|
|
14
14
|
capturePending = false;
|
|
15
15
|
requestId;
|
|
16
|
+
groupIds = [];
|
|
16
17
|
heartbeat;
|
|
17
18
|
timeout;
|
|
18
19
|
reconnect;
|
|
@@ -53,16 +54,25 @@ export class CallClient {
|
|
|
53
54
|
clearInterval(this.heartbeat);
|
|
54
55
|
const active = !["idle", "ended"].includes(this.state.phase);
|
|
55
56
|
this.release();
|
|
56
|
-
this.update({ ready: false, peerOnline: undefined, phase: active ? "ended" : this.state.phase, error: event.code === 1008 ? "UNAUTHORIZED" : "DISCONNECTED" });
|
|
57
|
+
this.update({ ready: false, peerOnline: undefined, peerInCall: undefined, rooms: undefined, phase: active ? "ended" : this.state.phase, error: event.code === 1008 ? "UNAUTHORIZED" : "DISCONNECTED" });
|
|
57
58
|
if (!this.closed && event.code !== 1008)
|
|
58
59
|
this.reconnect = setTimeout(() => this.connect(), 3000);
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
62
|
watchPeer(peerId) {
|
|
62
|
-
this.update({ watchedPeerId: peerId, peerOnline: undefined });
|
|
63
|
+
this.update({ watchedPeerId: peerId, peerOnline: undefined, peerInCall: undefined });
|
|
63
64
|
if (this.state.ready)
|
|
64
65
|
this.send({ type: "presence.watch", peerId: peerId ?? "" });
|
|
65
66
|
}
|
|
67
|
+
watchGroups(groupIds) {
|
|
68
|
+
const next = [...new Set(groupIds)].sort().slice(0, 64);
|
|
69
|
+
if (JSON.stringify(next) === JSON.stringify(this.groupIds))
|
|
70
|
+
return;
|
|
71
|
+
this.groupIds = next;
|
|
72
|
+
this.update({ rooms: this.state.rooms?.filter(room => next.includes(room.groupId)), roomsError: undefined });
|
|
73
|
+
if (this.state.ready)
|
|
74
|
+
this.send({ type: "rooms.watch", groupIds: next });
|
|
75
|
+
}
|
|
66
76
|
async start(peerId) {
|
|
67
77
|
if (!this.state.ready || !["idle", "ended"].includes(this.state.phase))
|
|
68
78
|
return;
|
|
@@ -122,6 +132,7 @@ export class CallClient {
|
|
|
122
132
|
return this.stopSharing();
|
|
123
133
|
const gen = this.generation;
|
|
124
134
|
this.capturePending = true;
|
|
135
|
+
this.update({ screenPending: true, error: undefined });
|
|
125
136
|
try {
|
|
126
137
|
const media = this.options.mediaDevices ?? navigator.mediaDevices;
|
|
127
138
|
if (!media.getDisplayMedia)
|
|
@@ -147,13 +158,14 @@ export class CallClient {
|
|
|
147
158
|
catch (error) {
|
|
148
159
|
if (gen === this.generation) {
|
|
149
160
|
await this.stopSharing();
|
|
150
|
-
|
|
151
|
-
this.update({ error: "SCREEN_UNAVAILABLE" });
|
|
161
|
+
this.update({ error: error instanceof DOMException && error.name === "NotAllowedError" ? "SCREEN_NOT_ALLOWED" : "SCREEN_UNAVAILABLE" });
|
|
152
162
|
}
|
|
153
163
|
}
|
|
154
164
|
finally {
|
|
155
|
-
if (gen === this.generation)
|
|
165
|
+
if (gen === this.generation) {
|
|
156
166
|
this.capturePending = false;
|
|
167
|
+
this.update({ screenPending: false });
|
|
168
|
+
}
|
|
157
169
|
}
|
|
158
170
|
}
|
|
159
171
|
async stopSharing() {
|
|
@@ -208,25 +220,40 @@ export class CallClient {
|
|
|
208
220
|
stream?.getTracks().forEach(t => { t.onended = null; t.stop(); });
|
|
209
221
|
this.microphone = undefined;
|
|
210
222
|
this.screen = undefined;
|
|
211
|
-
this.update({ muted: false, sharing: false, remoteSharing: false, remoteStream: undefined, localScreen: undefined });
|
|
223
|
+
this.update({ muted: false, sharing: false, remoteSharing: false, remoteStream: undefined, localScreen: undefined, screenPending: false });
|
|
212
224
|
}
|
|
213
225
|
async receive(message) {
|
|
214
226
|
if (message.type === "ready") {
|
|
215
227
|
this.update({ ready: true, error: undefined });
|
|
216
228
|
if (this.state.watchedPeerId)
|
|
217
229
|
this.send({ type: "presence.watch", peerId: this.state.watchedPeerId });
|
|
230
|
+
if (this.groupIds.length)
|
|
231
|
+
this.send({ type: "rooms.watch", groupIds: this.groupIds });
|
|
218
232
|
clearInterval(this.heartbeat);
|
|
219
233
|
this.heartbeat = setInterval(() => this.send({ type: "ping" }), 20000);
|
|
220
234
|
return;
|
|
221
235
|
}
|
|
222
236
|
if (message.type === "presence") {
|
|
223
237
|
if (message.peerId === this.state.watchedPeerId)
|
|
224
|
-
this.update({ peerOnline: typeof message.online === "boolean" ? message.online : undefined });
|
|
238
|
+
this.update({ peerOnline: typeof message.online === "boolean" ? message.online : undefined, peerInCall: typeof message.inCall === "boolean" ? message.inCall : undefined });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (message.type === "rooms.snapshot") {
|
|
242
|
+
this.update({ rooms: message.rooms.filter((room) => this.groupIds.includes(room.groupId)), roomsError: undefined });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (message.type === "rooms.changed") {
|
|
246
|
+
if (this.groupIds.includes(message.groupId))
|
|
247
|
+
this.update({ rooms: [...(this.state.rooms ?? []).filter(room => room.groupId !== message.groupId), ...(message.room ? [message.room] : [])], roomsError: undefined });
|
|
225
248
|
return;
|
|
226
249
|
}
|
|
227
250
|
if (message.type === "pong")
|
|
228
251
|
return;
|
|
229
252
|
if (message.type === "error") {
|
|
253
|
+
if (message.scope === "rooms") {
|
|
254
|
+
this.update({ rooms: undefined, roomsError: message.code });
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
230
257
|
if (message.requestId && message.requestId !== this.requestId)
|
|
231
258
|
return;
|
|
232
259
|
if (!message.callId || message.callId === this.state.callId)
|
|
@@ -304,6 +331,13 @@ export class CallClient {
|
|
|
304
331
|
for (const candidate of this.candidates.splice(0))
|
|
305
332
|
await pc.addIceCandidate(candidate);
|
|
306
333
|
if (message.description.type === "offer") {
|
|
334
|
+
const negotiated = pc.getTransceivers().find(t => t.mid !== null && t.receiver.track.kind === "video");
|
|
335
|
+
if (negotiated) {
|
|
336
|
+
if (this.video !== negotiated && this.video?.mid === null)
|
|
337
|
+
this.video.stop();
|
|
338
|
+
this.video = negotiated;
|
|
339
|
+
negotiated.direction = "sendrecv";
|
|
340
|
+
}
|
|
307
341
|
await pc.setLocalDescription(await pc.createAnswer());
|
|
308
342
|
if (gen === this.generation)
|
|
309
343
|
this.send({ type: "sdp", callId: this.state.callId, description: pc.localDescription });
|
package/lib/runtime/meeting.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type MeetingParticipant = {
|
|
|
9
9
|
export type MeetingSnapshot = {
|
|
10
10
|
phase: "idle" | "joining" | "joined" | "ended";
|
|
11
11
|
groupId?: string;
|
|
12
|
+
startedAt?: number;
|
|
13
|
+
screenPending?: boolean;
|
|
12
14
|
meetingId?: string;
|
|
13
15
|
selfId?: string;
|
|
14
16
|
participants: MeetingParticipant[];
|
package/lib/runtime/meeting.js
CHANGED
|
@@ -69,6 +69,7 @@ export class GroupMeetingClient {
|
|
|
69
69
|
}
|
|
70
70
|
const gen = this.generation;
|
|
71
71
|
this.pendingCapture = true;
|
|
72
|
+
this.update({ screenPending: true, error: undefined });
|
|
72
73
|
try {
|
|
73
74
|
const stream = await (this.options.mediaDevices ?? navigator.mediaDevices).getDisplayMedia({ video: { width: { ideal: 1280, max: 1920 }, height: { ideal: 720, max: 1080 }, frameRate: { ideal: 10, max: 15 } }, audio: false });
|
|
74
75
|
if (gen !== this.generation) {
|
|
@@ -88,13 +89,14 @@ export class GroupMeetingClient {
|
|
|
88
89
|
catch (e) {
|
|
89
90
|
if (gen === this.generation) {
|
|
90
91
|
await this.stopSharing();
|
|
91
|
-
|
|
92
|
-
this.update({ error: "SCREEN_UNAVAILABLE" });
|
|
92
|
+
this.update({ error: e instanceof DOMException && e.name === "NotAllowedError" ? "SCREEN_NOT_ALLOWED" : "SCREEN_UNAVAILABLE" });
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
finally {
|
|
96
|
-
if (gen === this.generation)
|
|
96
|
+
if (gen === this.generation) {
|
|
97
97
|
this.pendingCapture = false;
|
|
98
|
+
this.update({ screenPending: false });
|
|
99
|
+
}
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
102
|
async stopSharing() {
|
|
@@ -130,7 +132,7 @@ export class GroupMeetingClient {
|
|
|
130
132
|
stream?.getTracks().forEach(t => { t.onended = null; t.stop(); });
|
|
131
133
|
this.microphone = undefined;
|
|
132
134
|
this.screen = undefined;
|
|
133
|
-
this.update({ participants: [], meetingId: undefined, selfId: undefined, sharingId: undefined, localScreen: undefined, muted: false });
|
|
135
|
+
this.update({ participants: [], meetingId: undefined, selfId: undefined, sharingId: undefined, localScreen: undefined, muted: false, screenPending: false, startedAt: undefined });
|
|
134
136
|
}
|
|
135
137
|
participant(id, patch) { this.update({ participants: this.state.participants.map(p => p.id === id ? { ...p, ...patch } : p) }); }
|
|
136
138
|
link(id, gen) {
|
|
@@ -194,7 +196,7 @@ export class GroupMeetingClient {
|
|
|
194
196
|
return;
|
|
195
197
|
clearTimeout(this.timer);
|
|
196
198
|
this.iceServers = m.iceServers;
|
|
197
|
-
this.update({ phase: "joined", meetingId: m.meetingId, selfId: m.selfId });
|
|
199
|
+
this.update({ phase: "joined", meetingId: m.meetingId, selfId: m.selfId, startedAt: m.startedAt });
|
|
198
200
|
return;
|
|
199
201
|
}
|
|
200
202
|
if (m.meetingId !== this.state.meetingId || this.state.phase !== "joined")
|
package/package.json
CHANGED