@unityevolv/ofiskit-realtime-client 0.1.0
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/LICENSE +661 -0
- package/README.md +36 -0
- package/dist/client.d.ts +190 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +326 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/office-state.d.ts +143 -0
- package/dist/office-state.d.ts.map +1 -0
- package/dist/office-state.js +228 -0
- package/dist/office-state.js.map +1 -0
- package/dist/rtc/adapter.d.ts +238 -0
- package/dist/rtc/adapter.d.ts.map +1 -0
- package/dist/rtc/adapter.js +80 -0
- package/dist/rtc/adapter.js.map +1 -0
- package/dist/rtc/devices.d.ts +80 -0
- package/dist/rtc/devices.d.ts.map +1 -0
- package/dist/rtc/devices.js +107 -0
- package/dist/rtc/devices.js.map +1 -0
- package/dist/rtc/mesh.d.ts +13 -0
- package/dist/rtc/mesh.d.ts.map +1 -0
- package/dist/rtc/mesh.js +756 -0
- package/dist/rtc/mesh.js.map +1 -0
- package/dist/rtc/screen.d.ts +104 -0
- package/dist/rtc/screen.d.ts.map +1 -0
- package/dist/rtc/screen.js +103 -0
- package/dist/rtc/screen.js.map +1 -0
- package/package.json +21 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @unityevolv/ofiskit-realtime-client
|
|
3
|
+
*
|
|
4
|
+
* The office state on the client: one snapshot, then diffs, and a resync when a
|
|
5
|
+
* sequence number skips. No DOM anywhere, so a React Native app imports this
|
|
6
|
+
* unchanged and only the drawing differs.
|
|
7
|
+
*/
|
|
8
|
+
export { applyChange, applyDiff, callDevices, callIn, callSeats, emptyOffice, fromSnapshot, handRaised, isLocked, isMuted, isPhoneOnly, isSharing, isSpeaking, lockedBy, occupancy, peopleIn, raisedHands, sharerIn, statusIsChosen, you, yourRoom, } from './office-state.js';
|
|
9
|
+
/**
|
|
10
|
+
* The provider interface, client half.
|
|
11
|
+
*
|
|
12
|
+
* Exported as types and constants only: the built-in implementation arrives with
|
|
13
|
+
* the signalling story, and an external provider's adapter is written against
|
|
14
|
+
* exactly this and nothing more.
|
|
15
|
+
*/
|
|
16
|
+
export { AUDIO_BITRATE, CONNECT_TIMEOUT_MS, SCREEN_CEILING, VIDEO_STEPS, } from './rtc/adapter.js';
|
|
17
|
+
export { meshAdapter, describeMediaError } from './rtc/mesh.js';
|
|
18
|
+
/**
|
|
19
|
+
* Choosing what to share, on a platform whose browser cannot ask.
|
|
20
|
+
*
|
|
21
|
+
* Interfaces and two pure functions. There is no desktop app in this repository
|
|
22
|
+
* and nothing here imports Electron: a host that has one supplies the list of
|
|
23
|
+
* screens and windows, and the shared picker draws it the same way everywhere.
|
|
24
|
+
*/
|
|
25
|
+
export { describeShareError, desktopConstraints, shareCancelled, usableSources, } from './rtc/screen.js';
|
|
26
|
+
/**
|
|
27
|
+
* Choosing a microphone, a camera and a speaker, and coping when the browser says
|
|
28
|
+
* no.
|
|
29
|
+
*
|
|
30
|
+
* Almost all of the value is in the failure cases: picking from a list is easy,
|
|
31
|
+
* and telling somebody the difference between "you denied permission" and
|
|
32
|
+
* "something else is holding your camera" is what stops a support ticket.
|
|
33
|
+
*/
|
|
34
|
+
export { labelFor, listDevices, memoryDeviceStorage, requestPermission, stillAvailable, watchDevices, } from './rtc/devices.js';
|
|
35
|
+
export { createOfisClient, } from './client.js';
|
|
36
|
+
/**
|
|
37
|
+
* The reactions, from the one place they are defined.
|
|
38
|
+
*
|
|
39
|
+
* The only runtime value this package takes from the core, and it is a frozen
|
|
40
|
+
* array of six strings — not the engine, which lives behind the package's other
|
|
41
|
+
* entry point and is never reachable from here. It has to be the same list on
|
|
42
|
+
* both sides: the server refuses anything not in it, and the picker draws
|
|
43
|
+
* exactly it. Two copies would drift, and the drift is a reaction one person
|
|
44
|
+
* sends and nobody else can see.
|
|
45
|
+
*/
|
|
46
|
+
export { REACTIONS } from '@unityevolv/ofiskit-realtime-core/protocol';
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,WAAW,EACX,SAAS,EACT,WAAW,EACX,MAAM,EACN,SAAS,EACT,WAAW,EACX,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,OAAO,EACP,WAAW,EACX,SAAS,EACT,UAAU,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,cAAc,EACd,GAAG,EACH,QAAQ,GAIT,MAAM,mBAAmB,CAAA;AAE1B;;;;;;GAMG;AACH,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,WAAW,GAMZ,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAE/D;;;;;;GAMG;AACH,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,aAAa,GAId,MAAM,iBAAiB,CAAA;AAExB;;;;;;;GAOG;AACH,OAAO,EACL,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,YAAY,GAKb,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,gBAAgB,GAMjB,MAAM,aAAa,CAAA;AAEpB;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAiB,MAAM,4CAA4C,CAAA"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { OfficeChange, OfficeDiff, ManualStatus, OfficeSnapshot, PublicPresence, RoomCall } from '@unityevolv/ofiskit-realtime-core/protocol';
|
|
2
|
+
/**
|
|
3
|
+
* The office as the client believes it to be.
|
|
4
|
+
*
|
|
5
|
+
* One snapshot on entry, then diffs. The whole design rests on the sequence
|
|
6
|
+
* number: a client that applies a diff numbered two higher than the last one has
|
|
7
|
+
* missed something, and drawing the office from then on would be drawing
|
|
8
|
+
* something quietly wrong that stays wrong until somebody reloads.
|
|
9
|
+
*
|
|
10
|
+
* So the rule is: notice the gap, ask for a snapshot, carry on. That is what
|
|
11
|
+
* `applyDiff` returning `resync` means, and it is the only correct response.
|
|
12
|
+
*
|
|
13
|
+
* A pure reducer with no socket and no framework in it, because the web map and
|
|
14
|
+
* a React Native list consume this same state and the only thing that differs
|
|
15
|
+
* between them is the drawing.
|
|
16
|
+
*/
|
|
17
|
+
export interface OfficeState {
|
|
18
|
+
officeId: string;
|
|
19
|
+
seq: number;
|
|
20
|
+
/** Keyed by user, because presence is per user and so are moves. */
|
|
21
|
+
people: Map<string, PublicPresence>;
|
|
22
|
+
/** roomId → who locked it. */
|
|
23
|
+
locks: Map<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* roomId → the call happening in it.
|
|
26
|
+
*
|
|
27
|
+
* Separate from the people in the room, exactly as it is on the server:
|
|
28
|
+
* entering a room does not join its call, and somebody is in the room whether
|
|
29
|
+
* or not they are in the conversation happening in it.
|
|
30
|
+
*/
|
|
31
|
+
calls: Map<string, RoomCall>;
|
|
32
|
+
you: {
|
|
33
|
+
userId: string;
|
|
34
|
+
deviceId: string;
|
|
35
|
+
manual: ManualStatus | null;
|
|
36
|
+
};
|
|
37
|
+
/** False until the first snapshot lands, so the map can show its skeleton. */
|
|
38
|
+
ready: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare function emptyOffice(officeId?: string): OfficeState;
|
|
41
|
+
export declare function fromSnapshot(snapshot: OfficeSnapshot): OfficeState;
|
|
42
|
+
/** What applying a diff concluded. */
|
|
43
|
+
export type DiffOutcome = {
|
|
44
|
+
kind: 'applied';
|
|
45
|
+
state: OfficeState;
|
|
46
|
+
}
|
|
47
|
+
/** Already seen. Duplicates arrive on a reconnect and are simply dropped. */
|
|
48
|
+
| {
|
|
49
|
+
kind: 'stale';
|
|
50
|
+
}
|
|
51
|
+
/** A gap. Ask the server for a fresh snapshot; do not guess. */
|
|
52
|
+
| {
|
|
53
|
+
kind: 'resync';
|
|
54
|
+
};
|
|
55
|
+
export declare function applyDiff(state: OfficeState, diff: OfficeDiff): DiffOutcome;
|
|
56
|
+
/**
|
|
57
|
+
* Apply one change.
|
|
58
|
+
*
|
|
59
|
+
* Every branch copies the map it touches rather than mutating in place, so a
|
|
60
|
+
* React render that captured the previous state keeps seeing the previous
|
|
61
|
+
* office. Mutating would make a component that memoised on the map miss the
|
|
62
|
+
* update entirely, which is the hardest class of bug to see in a UI.
|
|
63
|
+
*/
|
|
64
|
+
export declare function applyChange(state: OfficeState, change: OfficeChange): OfficeState;
|
|
65
|
+
/**
|
|
66
|
+
* Everyone in a room, in arrival order.
|
|
67
|
+
*
|
|
68
|
+
* Arrival order is what stops avatars shuffling around their cells as other
|
|
69
|
+
* people come and go. Somebody who has been sitting in the corner of a room for
|
|
70
|
+
* an hour should still be in that corner. The user id breaks ties, so two people
|
|
71
|
+
* who arrived in the same millisecond still have one stable order rather than
|
|
72
|
+
* swapping places on every render.
|
|
73
|
+
*/
|
|
74
|
+
export declare function peopleIn(state: OfficeState, roomId: string): PublicPresence[];
|
|
75
|
+
export declare function isLocked(state: OfficeState, roomId: string): boolean;
|
|
76
|
+
export declare function lockedBy(state: OfficeState, roomId: string): string | null;
|
|
77
|
+
/** How many people are in a room. Counts people, because presence is per user. */
|
|
78
|
+
export declare function occupancy(state: OfficeState, roomId: string): number;
|
|
79
|
+
export declare function you(state: OfficeState): PublicPresence | null;
|
|
80
|
+
/** The room the viewer is standing in, if the office has told us yet. */
|
|
81
|
+
export declare function yourRoom(state: OfficeState): string | null;
|
|
82
|
+
/**
|
|
83
|
+
* Whether the status you are showing is one you chose.
|
|
84
|
+
*
|
|
85
|
+
* The difference the control turns on: a chosen status offers a way back to
|
|
86
|
+
* automatic, and a worked-out one has nothing to go back from.
|
|
87
|
+
*/
|
|
88
|
+
export declare function statusIsChosen(state: OfficeState): boolean;
|
|
89
|
+
/** The call in a room, or null when nobody is talking in it. */
|
|
90
|
+
export declare function callIn(state: OfficeState, roomId: string): RoomCall | null;
|
|
91
|
+
/** Who is sharing a screen in a room, with their name resolved. */
|
|
92
|
+
export interface Sharer {
|
|
93
|
+
userId: string;
|
|
94
|
+
deviceId: string;
|
|
95
|
+
/** Empty for somebody the office has not heard of, which a caller can test. */
|
|
96
|
+
displayName: string;
|
|
97
|
+
startedAt: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Who is sharing in a room, or nobody.
|
|
101
|
+
*
|
|
102
|
+
* Read from the call rather than by looking for a device with its `sharing` flag
|
|
103
|
+
* set, because the call holds **one** slot: asking it is how every screen agrees
|
|
104
|
+
* about whose screen is on them, and a scan of everybody's devices could find two.
|
|
105
|
+
*
|
|
106
|
+
* The name is resolved here so that callers do not each do it: the answer is
|
|
107
|
+
* usually needed in a sentence — "Ada is sharing" — rather than as an id.
|
|
108
|
+
*/
|
|
109
|
+
export declare function sharerIn(state: OfficeState, roomId: string): Sharer | null;
|
|
110
|
+
/**
|
|
111
|
+
* Seats a room's call is using.
|
|
112
|
+
*
|
|
113
|
+
* Counts device legs rather than people, because somebody who added a second
|
|
114
|
+
* device is two real connections in the mesh and takes two of the places.
|
|
115
|
+
*/
|
|
116
|
+
export declare function callSeats(state: OfficeState, roomId: string): number;
|
|
117
|
+
/** The devices this person has in the call. Two means two tiles, one person. */
|
|
118
|
+
export declare function callDevices(person: PublicPresence): PublicPresence['devices'];
|
|
119
|
+
/** True when any of this person's devices is speaking right now. */
|
|
120
|
+
export declare function isSpeaking(person: PublicPresence): boolean;
|
|
121
|
+
/** True when they are in the call and every leg of theirs is muted. */
|
|
122
|
+
export declare function isMuted(person: PublicPresence): boolean;
|
|
123
|
+
export declare function isSharing(person: PublicPresence): boolean;
|
|
124
|
+
/** True when any of this person's devices has its hand up. */
|
|
125
|
+
export declare function handRaised(person: PublicPresence): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* The hands that are up in a room's call, in the order they went up.
|
|
128
|
+
*
|
|
129
|
+
* By device, because a leg is a screen. The order is the queue, and it is the
|
|
130
|
+
* same on every client because the server stamps the instant — a client that
|
|
131
|
+
* joined a minute ago knows just as well who asked first.
|
|
132
|
+
*/
|
|
133
|
+
export declare function raisedHands(state: OfficeState, roomId: string): string[];
|
|
134
|
+
/**
|
|
135
|
+
* Whether this person is here only from a phone.
|
|
136
|
+
*
|
|
137
|
+
* Drives the device badge. Presence is per user, so this is the only way to tell
|
|
138
|
+
* that somebody is on a phone and may not be able to do much. No badge when a
|
|
139
|
+
* desktop or web session is among their devices, because then it says nothing
|
|
140
|
+
* useful.
|
|
141
|
+
*/
|
|
142
|
+
export declare function isPhoneOnly(person: PublicPresence): boolean;
|
|
143
|
+
//# sourceMappingURL=office-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"office-state.d.ts","sourceRoot":"","sources":["../src/office-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
export function emptyOffice(officeId = '') {
|
|
2
|
+
return {
|
|
3
|
+
officeId,
|
|
4
|
+
seq: 0,
|
|
5
|
+
people: new Map(),
|
|
6
|
+
locks: new Map(),
|
|
7
|
+
calls: new Map(),
|
|
8
|
+
you: { userId: '', deviceId: '', manual: null },
|
|
9
|
+
ready: false,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function fromSnapshot(snapshot) {
|
|
13
|
+
return {
|
|
14
|
+
officeId: snapshot.officeId,
|
|
15
|
+
seq: snapshot.seq,
|
|
16
|
+
people: new Map(snapshot.people.map((person) => [person.userId, person])),
|
|
17
|
+
locks: new Map(snapshot.locks.map((lock) => [lock.roomId, lock.lockedBy])),
|
|
18
|
+
calls: new Map(snapshot.calls.map((call) => [call.roomId, call])),
|
|
19
|
+
you: snapshot.you,
|
|
20
|
+
ready: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function applyDiff(state, diff) {
|
|
24
|
+
// A diff before any snapshot is not a gap so much as nothing to apply it to.
|
|
25
|
+
if (!state.ready)
|
|
26
|
+
return { kind: 'resync' };
|
|
27
|
+
if (diff.seq <= state.seq)
|
|
28
|
+
return { kind: 'stale' };
|
|
29
|
+
if (diff.seq !== state.seq + 1)
|
|
30
|
+
return { kind: 'resync' };
|
|
31
|
+
let next = state;
|
|
32
|
+
for (const change of diff.changes)
|
|
33
|
+
next = applyChange(next, change);
|
|
34
|
+
return { kind: 'applied', state: { ...next, seq: diff.seq } };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Apply one change.
|
|
38
|
+
*
|
|
39
|
+
* Every branch copies the map it touches rather than mutating in place, so a
|
|
40
|
+
* React render that captured the previous state keeps seeing the previous
|
|
41
|
+
* office. Mutating would make a component that memoised on the map miss the
|
|
42
|
+
* update entirely, which is the hardest class of bug to see in a UI.
|
|
43
|
+
*/
|
|
44
|
+
export function applyChange(state, change) {
|
|
45
|
+
switch (change.kind) {
|
|
46
|
+
case 'person.entered':
|
|
47
|
+
case 'person.updated': {
|
|
48
|
+
const people = new Map(state.people);
|
|
49
|
+
people.set(change.presence.userId, change.presence);
|
|
50
|
+
return { ...state, people };
|
|
51
|
+
}
|
|
52
|
+
case 'person.moved': {
|
|
53
|
+
const existing = state.people.get(change.userId);
|
|
54
|
+
// A move for somebody we have never heard of means our state is behind.
|
|
55
|
+
// The sequence check above is what normally catches that, and ignoring it
|
|
56
|
+
// here is the safe response rather than inventing half a person.
|
|
57
|
+
if (!existing)
|
|
58
|
+
return state;
|
|
59
|
+
const people = new Map(state.people);
|
|
60
|
+
people.set(change.userId, {
|
|
61
|
+
...existing,
|
|
62
|
+
roomId: change.roomId,
|
|
63
|
+
arrivedAt: change.arrivedAt,
|
|
64
|
+
});
|
|
65
|
+
return { ...state, people };
|
|
66
|
+
}
|
|
67
|
+
case 'person.left': {
|
|
68
|
+
if (!state.people.has(change.userId))
|
|
69
|
+
return state;
|
|
70
|
+
const people = new Map(state.people);
|
|
71
|
+
people.delete(change.userId);
|
|
72
|
+
return { ...state, people };
|
|
73
|
+
}
|
|
74
|
+
case 'room.locked': {
|
|
75
|
+
const locks = new Map(state.locks);
|
|
76
|
+
locks.set(change.roomId, change.lockedBy);
|
|
77
|
+
return { ...state, locks };
|
|
78
|
+
}
|
|
79
|
+
case 'room.unlocked': {
|
|
80
|
+
if (!state.locks.has(change.roomId))
|
|
81
|
+
return state;
|
|
82
|
+
const locks = new Map(state.locks);
|
|
83
|
+
locks.delete(change.roomId);
|
|
84
|
+
return { ...state, locks };
|
|
85
|
+
}
|
|
86
|
+
case 'call.updated': {
|
|
87
|
+
const calls = new Map(state.calls);
|
|
88
|
+
calls.set(change.call.roomId, change.call);
|
|
89
|
+
return { ...state, calls };
|
|
90
|
+
}
|
|
91
|
+
case 'call.ended': {
|
|
92
|
+
if (!state.calls.has(change.roomId))
|
|
93
|
+
return state;
|
|
94
|
+
const calls = new Map(state.calls);
|
|
95
|
+
calls.delete(change.roomId);
|
|
96
|
+
return { ...state, calls };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------- reading it
|
|
101
|
+
/**
|
|
102
|
+
* Everyone in a room, in arrival order.
|
|
103
|
+
*
|
|
104
|
+
* Arrival order is what stops avatars shuffling around their cells as other
|
|
105
|
+
* people come and go. Somebody who has been sitting in the corner of a room for
|
|
106
|
+
* an hour should still be in that corner. The user id breaks ties, so two people
|
|
107
|
+
* who arrived in the same millisecond still have one stable order rather than
|
|
108
|
+
* swapping places on every render.
|
|
109
|
+
*/
|
|
110
|
+
export function peopleIn(state, roomId) {
|
|
111
|
+
return [...state.people.values()]
|
|
112
|
+
.filter((person) => person.roomId === roomId)
|
|
113
|
+
.sort((a, b) => a.arrivedAt.localeCompare(b.arrivedAt) || a.userId.localeCompare(b.userId));
|
|
114
|
+
}
|
|
115
|
+
export function isLocked(state, roomId) {
|
|
116
|
+
return state.locks.has(roomId);
|
|
117
|
+
}
|
|
118
|
+
export function lockedBy(state, roomId) {
|
|
119
|
+
return state.locks.get(roomId) ?? null;
|
|
120
|
+
}
|
|
121
|
+
/** How many people are in a room. Counts people, because presence is per user. */
|
|
122
|
+
export function occupancy(state, roomId) {
|
|
123
|
+
return peopleIn(state, roomId).length;
|
|
124
|
+
}
|
|
125
|
+
export function you(state) {
|
|
126
|
+
return state.people.get(state.you.userId) ?? null;
|
|
127
|
+
}
|
|
128
|
+
/** The room the viewer is standing in, if the office has told us yet. */
|
|
129
|
+
export function yourRoom(state) {
|
|
130
|
+
return you(state)?.roomId ?? null;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Whether the status you are showing is one you chose.
|
|
134
|
+
*
|
|
135
|
+
* The difference the control turns on: a chosen status offers a way back to
|
|
136
|
+
* automatic, and a worked-out one has nothing to go back from.
|
|
137
|
+
*/
|
|
138
|
+
export function statusIsChosen(state) {
|
|
139
|
+
return state.you.manual !== null;
|
|
140
|
+
}
|
|
141
|
+
/** The call in a room, or null when nobody is talking in it. */
|
|
142
|
+
export function callIn(state, roomId) {
|
|
143
|
+
return state.calls.get(roomId) ?? null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Who is sharing in a room, or nobody.
|
|
147
|
+
*
|
|
148
|
+
* Read from the call rather than by looking for a device with its `sharing` flag
|
|
149
|
+
* set, because the call holds **one** slot: asking it is how every screen agrees
|
|
150
|
+
* about whose screen is on them, and a scan of everybody's devices could find two.
|
|
151
|
+
*
|
|
152
|
+
* The name is resolved here so that callers do not each do it: the answer is
|
|
153
|
+
* usually needed in a sentence — "Ada is sharing" — rather than as an id.
|
|
154
|
+
*/
|
|
155
|
+
export function sharerIn(state, roomId) {
|
|
156
|
+
const sharing = state.calls.get(roomId)?.sharing;
|
|
157
|
+
if (!sharing)
|
|
158
|
+
return null;
|
|
159
|
+
return {
|
|
160
|
+
userId: sharing.userId,
|
|
161
|
+
deviceId: sharing.deviceId,
|
|
162
|
+
displayName: state.people.get(sharing.userId)?.displayName ?? '',
|
|
163
|
+
startedAt: sharing.startedAt,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Seats a room's call is using.
|
|
168
|
+
*
|
|
169
|
+
* Counts device legs rather than people, because somebody who added a second
|
|
170
|
+
* device is two real connections in the mesh and takes two of the places.
|
|
171
|
+
*/
|
|
172
|
+
export function callSeats(state, roomId) {
|
|
173
|
+
return callIn(state, roomId)?.participants.length ?? 0;
|
|
174
|
+
}
|
|
175
|
+
/** The devices this person has in the call. Two means two tiles, one person. */
|
|
176
|
+
export function callDevices(person) {
|
|
177
|
+
return person.devices.filter((device) => device.inCall);
|
|
178
|
+
}
|
|
179
|
+
/** True when any of this person's devices is speaking right now. */
|
|
180
|
+
export function isSpeaking(person) {
|
|
181
|
+
return person.devices.some((device) => device.inCall && device.speaking);
|
|
182
|
+
}
|
|
183
|
+
/** True when they are in the call and every leg of theirs is muted. */
|
|
184
|
+
export function isMuted(person) {
|
|
185
|
+
const legs = callDevices(person);
|
|
186
|
+
return legs.length > 0 && legs.every((device) => device.muted);
|
|
187
|
+
}
|
|
188
|
+
export function isSharing(person) {
|
|
189
|
+
return person.devices.some((device) => device.sharing);
|
|
190
|
+
}
|
|
191
|
+
/** True when any of this person's devices has its hand up. */
|
|
192
|
+
export function handRaised(person) {
|
|
193
|
+
return person.devices.some((device) => device.inCall && device.handRaisedAt !== null);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The hands that are up in a room's call, in the order they went up.
|
|
197
|
+
*
|
|
198
|
+
* By device, because a leg is a screen. The order is the queue, and it is the
|
|
199
|
+
* same on every client because the server stamps the instant — a client that
|
|
200
|
+
* joined a minute ago knows just as well who asked first.
|
|
201
|
+
*/
|
|
202
|
+
export function raisedHands(state, roomId) {
|
|
203
|
+
const call = callIn(state, roomId);
|
|
204
|
+
if (!call)
|
|
205
|
+
return [];
|
|
206
|
+
return call.participants
|
|
207
|
+
.map((participant) => {
|
|
208
|
+
const device = state.people
|
|
209
|
+
.get(participant.userId)
|
|
210
|
+
?.devices.find((one) => one.deviceId === participant.deviceId);
|
|
211
|
+
return { deviceId: participant.deviceId, at: device?.handRaisedAt ?? null };
|
|
212
|
+
})
|
|
213
|
+
.filter((one) => one.at !== null)
|
|
214
|
+
.sort((a, b) => a.at.localeCompare(b.at) || a.deviceId.localeCompare(b.deviceId))
|
|
215
|
+
.map((one) => one.deviceId);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Whether this person is here only from a phone.
|
|
219
|
+
*
|
|
220
|
+
* Drives the device badge. Presence is per user, so this is the only way to tell
|
|
221
|
+
* that somebody is on a phone and may not be able to do much. No badge when a
|
|
222
|
+
* desktop or web session is among their devices, because then it says nothing
|
|
223
|
+
* useful.
|
|
224
|
+
*/
|
|
225
|
+
export function isPhoneOnly(person) {
|
|
226
|
+
return person.devices.length > 0 && person.devices.every((device) => device.kind === 'mobile');
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=office-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"office-state.js","sourceRoot":"","sources":["../src/office-state.ts"],"names":[],"mappings":"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"}
|