@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/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @unityevolv/ofiskit-realtime-client
|
|
2
|
+
|
|
3
|
+
The office as a client sees it: connect, walk in, move between rooms, and take
|
|
4
|
+
part in the call.
|
|
5
|
+
|
|
6
|
+
It holds the office state, applies diffs, notices a gap in the sequence and
|
|
7
|
+
resyncs, and reconnects without the person having to do anything. It carries no
|
|
8
|
+
DOM, so React Native can use it as it is.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createOfisClient } from '@unityevolv/ofiskit-realtime-client'
|
|
12
|
+
|
|
13
|
+
const client = createOfisClient({ url, deviceId })
|
|
14
|
+
client.subscribe((office) => render(office))
|
|
15
|
+
|
|
16
|
+
await client.enter({ email, name })
|
|
17
|
+
await client.joinRoom(roomId)
|
|
18
|
+
await client.joinCall({ audio: true, video: false })
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The RTC half is an adapter too. The built-in one is a peer-to-peer mesh with
|
|
22
|
+
audio, video and screen share; another provider's SDK goes behind the same
|
|
23
|
+
interface, and the UI never learns which is in use.
|
|
24
|
+
|
|
25
|
+
`connect` takes any socket-like object, so the client can run over something
|
|
26
|
+
other than Socket.IO — which is how the browser-only demo puts the whole office
|
|
27
|
+
in a tab.
|
|
28
|
+
|
|
29
|
+
## Licence
|
|
30
|
+
|
|
31
|
+
AGPL-3.0-only. The interface packages (`ofiskit-template`,
|
|
32
|
+
`ofiskit-presence-store` and `ofiskit-adapters`) are Apache-2.0, so building
|
|
33
|
+
against the interfaces is not a licensing decision even though extending this is.
|
|
34
|
+
|
|
35
|
+
Part of [ofiskit](https://github.com/UnityEvolv/ofis-kit): a virtual office you
|
|
36
|
+
can run yourself. [Try the demo](https://unityevolv.com/ofis-kit/).
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { Ack, CallJoinResponse, CustomStatus, DeviceKind, ManualStatus, OfficeSnapshot } from '@unityevolv/ofiskit-realtime-core/protocol';
|
|
2
|
+
import { type OfficeState } from './office-state.js';
|
|
3
|
+
import type { RtcClientAdapter, RtcEvent, Signaller } from './rtc/adapter.js';
|
|
4
|
+
/**
|
|
5
|
+
* The office, on the client.
|
|
6
|
+
*
|
|
7
|
+
* One object holding the socket and the office state, so a UI subscribes to one
|
|
8
|
+
* thing and renders. Both the web map and a React Native list use this; only the
|
|
9
|
+
* drawing differs, which is why there is no DOM anywhere in this package.
|
|
10
|
+
*/
|
|
11
|
+
export type ConnectionStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'closed';
|
|
12
|
+
/** Things that happen *to* you, which the UI announces rather than only draws. */
|
|
13
|
+
export type ClientEvent =
|
|
14
|
+
/** Somebody is at the door of the room you are in. */
|
|
15
|
+
{
|
|
16
|
+
type: 'knock';
|
|
17
|
+
knockId: string;
|
|
18
|
+
roomId: string;
|
|
19
|
+
userId: string;
|
|
20
|
+
displayName: string;
|
|
21
|
+
photoUrl?: string;
|
|
22
|
+
/** True when everybody inside is on do not disturb: it arrived silently. */
|
|
23
|
+
silent: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** A knock is over, one way or another — including nobody answering. */
|
|
26
|
+
| {
|
|
27
|
+
type: 'knock.resolved';
|
|
28
|
+
knockId: string;
|
|
29
|
+
outcome: 'admitted' | 'declined' | 'expired';
|
|
30
|
+
}
|
|
31
|
+
/** You were let in. An invitation to move, not a reservation. */
|
|
32
|
+
| {
|
|
33
|
+
type: 'admitted';
|
|
34
|
+
roomId: string;
|
|
35
|
+
byUserId: string;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'template.changed';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Somebody in your room reacted.
|
|
41
|
+
*
|
|
42
|
+
* An event rather than state, because a reaction is not state: it floats over
|
|
43
|
+
* whoever sent it for a few seconds and then it is gone, and nothing about it is
|
|
44
|
+
* stored here or on the server. A client that was not listening missed it,
|
|
45
|
+
* which is what happens with a nod in a room.
|
|
46
|
+
*/
|
|
47
|
+
| {
|
|
48
|
+
type: 'reaction';
|
|
49
|
+
roomId: string;
|
|
50
|
+
userId: string;
|
|
51
|
+
/** Which screen it came from, so it floats over the right tile. */
|
|
52
|
+
deviceId: string;
|
|
53
|
+
reaction: string;
|
|
54
|
+
at: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Your own screen share was stopped, by somebody else taking over.
|
|
58
|
+
*
|
|
59
|
+
* Only ever about your own share, because it is the only one this client has a
|
|
60
|
+
* capture to tear down. Everybody else learns a share changed hands from the
|
|
61
|
+
* call, the ordinary way, and needs no event for it.
|
|
62
|
+
*/
|
|
63
|
+
| {
|
|
64
|
+
type: 'share.ended';
|
|
65
|
+
roomId: string;
|
|
66
|
+
reason: 'taken_over';
|
|
67
|
+
byUserId: string;
|
|
68
|
+
}
|
|
69
|
+
/** A move or an action the server refused, with the reason to show. */
|
|
70
|
+
| {
|
|
71
|
+
type: 'refused';
|
|
72
|
+
action: string;
|
|
73
|
+
code: string;
|
|
74
|
+
message: string;
|
|
75
|
+
} | {
|
|
76
|
+
type: 'status';
|
|
77
|
+
status: ConnectionStatus;
|
|
78
|
+
} | {
|
|
79
|
+
type: 'closed';
|
|
80
|
+
code: string;
|
|
81
|
+
message: string;
|
|
82
|
+
}
|
|
83
|
+
/** Everything the provider's client half reports, in one shape. */
|
|
84
|
+
| {
|
|
85
|
+
type: 'rtc';
|
|
86
|
+
event: RtcEvent;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* The little of Socket.IO this client actually uses.
|
|
90
|
+
*
|
|
91
|
+
* Named so the client can be driven without a network, for the same reason the
|
|
92
|
+
* engine takes a `Transport`: the rules worth testing here are what happens to
|
|
93
|
+
* the office state when a diff skips a number or a move is refused, and none of
|
|
94
|
+
* those need a socket to be true. It is also the seam a host would replace to run
|
|
95
|
+
* this over something else entirely.
|
|
96
|
+
*/
|
|
97
|
+
export interface SocketLike {
|
|
98
|
+
on(event: string, handler: (...args: never[]) => void): unknown;
|
|
99
|
+
off(event: string, handler: (...args: never[]) => void): unknown;
|
|
100
|
+
emit(event: string, ...args: unknown[]): unknown;
|
|
101
|
+
disconnect(): unknown;
|
|
102
|
+
/** Socket.IO's manager, which is where reconnection attempts are announced. */
|
|
103
|
+
io: {
|
|
104
|
+
on(event: string, handler: (...args: never[]) => void): unknown;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export interface OfisClientOptions {
|
|
108
|
+
/** Where the socket lives. From configuration; never a literal in the app. */
|
|
109
|
+
url: string;
|
|
110
|
+
path?: string;
|
|
111
|
+
/** Stable per installation, so a reconnect is recognised as the same device. */
|
|
112
|
+
deviceId: string;
|
|
113
|
+
kind?: DeviceKind;
|
|
114
|
+
/** How often to tell the server we are still here. */
|
|
115
|
+
heartbeatMs?: number;
|
|
116
|
+
/**
|
|
117
|
+
* The provider's client half.
|
|
118
|
+
*
|
|
119
|
+
* The built-in mesh unless a host swaps it, which is the other half of what
|
|
120
|
+
* makes a provider replaceable: a plugin on the server and an adapter here, and
|
|
121
|
+
* no UI change between them.
|
|
122
|
+
*/
|
|
123
|
+
rtc?: (signaller: Signaller) => RtcClientAdapter;
|
|
124
|
+
/**
|
|
125
|
+
* How to open the socket. Socket.IO unless somebody says otherwise.
|
|
126
|
+
*
|
|
127
|
+
* Tests pass a fake here. So could a host on a different transport, which is
|
|
128
|
+
* the only reason this is an option rather than a test-only hook.
|
|
129
|
+
*/
|
|
130
|
+
connect?(options: OfisClientOptions): SocketLike;
|
|
131
|
+
}
|
|
132
|
+
export interface OfisClient {
|
|
133
|
+
state(): OfficeState;
|
|
134
|
+
/** Called on every state change. Returns its own unsubscribe. */
|
|
135
|
+
subscribe(listener: (state: OfficeState) => void): () => void;
|
|
136
|
+
on(listener: (event: ClientEvent) => void): () => void;
|
|
137
|
+
status(): ConnectionStatus;
|
|
138
|
+
enter(credentials: unknown): Promise<Ack<{
|
|
139
|
+
snapshot: OfficeSnapshot;
|
|
140
|
+
}>>;
|
|
141
|
+
leave(): Promise<void>;
|
|
142
|
+
close(): void;
|
|
143
|
+
joinRoom(roomId: string): Promise<Ack>;
|
|
144
|
+
leaveRoom(): Promise<Ack>;
|
|
145
|
+
lock(roomId: string): Promise<Ack>;
|
|
146
|
+
unlock(roomId: string): Promise<Ack>;
|
|
147
|
+
knock(roomId: string): Promise<Ack<{
|
|
148
|
+
knockId: string;
|
|
149
|
+
silent: boolean;
|
|
150
|
+
}>>;
|
|
151
|
+
/** Let one person in, without unlocking the room for anybody else. */
|
|
152
|
+
admit(knockId: string): Promise<Ack>;
|
|
153
|
+
decline(knockId: string): Promise<Ack>;
|
|
154
|
+
/** A status the person chose. null puts them back on automatic. */
|
|
155
|
+
setStatus(manual: ManualStatus | null): Promise<Ack>;
|
|
156
|
+
setCustomStatus(custom: CustomStatus | null): Promise<Ack>;
|
|
157
|
+
/** One device's own signals. Never a conclusion about the person. */
|
|
158
|
+
setActivity(activity: {
|
|
159
|
+
idle: boolean;
|
|
160
|
+
foreground: boolean;
|
|
161
|
+
}): void;
|
|
162
|
+
/**
|
|
163
|
+
* Join the call in your room, and start publishing.
|
|
164
|
+
*
|
|
165
|
+
* Two things in one, deliberately: the server's answer carries the credentials
|
|
166
|
+
* and the participant list the adapter needs, and a caller that had to sequence
|
|
167
|
+
* them itself would be a caller that could get the order wrong.
|
|
168
|
+
*/
|
|
169
|
+
joinCall(options: {
|
|
170
|
+
audio: boolean;
|
|
171
|
+
video: boolean;
|
|
172
|
+
secondDevice?: 'move' | 'add';
|
|
173
|
+
}): Promise<Ack<{
|
|
174
|
+
call: CallJoinResponse;
|
|
175
|
+
}>>;
|
|
176
|
+
leaveCall(): Promise<Ack>;
|
|
177
|
+
/**
|
|
178
|
+
* Put your hand up, or take it down.
|
|
179
|
+
*
|
|
180
|
+
* A socket event and not media, so it behaves identically on the built-in
|
|
181
|
+
* provider and on anybody else's — and it loads no provider SDK to do it.
|
|
182
|
+
*/
|
|
183
|
+
raiseHand(raised: boolean): Promise<Ack>;
|
|
184
|
+
/** React, without interrupting. Rate limited by the server, which may refuse. */
|
|
185
|
+
react(reaction: string): Promise<Ack>;
|
|
186
|
+
/** The provider's client half. What the controls bar and the tiles talk to. */
|
|
187
|
+
rtc: RtcClientAdapter;
|
|
188
|
+
}
|
|
189
|
+
export declare function createOfisClient(options: OfisClientOptions): OfisClient;
|
|
190
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { io } from 'socket.io-client';
|
|
2
|
+
import { applyChange, applyDiff, emptyOffice, fromSnapshot, } from './office-state.js';
|
|
3
|
+
import { meshAdapter } from './rtc/mesh.js';
|
|
4
|
+
/** Socket.IO, which is what every real deployment uses. */
|
|
5
|
+
function openSocket(options) {
|
|
6
|
+
const socket = io(options.url, {
|
|
7
|
+
path: options.path ?? '/socket',
|
|
8
|
+
auth: { deviceId: options.deviceId, kind: options.kind ?? 'web' },
|
|
9
|
+
transports: ['websocket', 'polling'],
|
|
10
|
+
});
|
|
11
|
+
return socket;
|
|
12
|
+
}
|
|
13
|
+
export function createOfisClient(options) {
|
|
14
|
+
const socket = (options.connect ?? openSocket)(options);
|
|
15
|
+
let state = emptyOffice();
|
|
16
|
+
let connection = 'idle';
|
|
17
|
+
let credentials = null;
|
|
18
|
+
let heartbeat = null;
|
|
19
|
+
let resyncing = false;
|
|
20
|
+
const listeners = new Set();
|
|
21
|
+
const events = new Set();
|
|
22
|
+
const publish = (next) => {
|
|
23
|
+
state = next;
|
|
24
|
+
// Copied before iterating, because a listener may unsubscribe as it runs.
|
|
25
|
+
for (const listener of [...listeners])
|
|
26
|
+
listener(state);
|
|
27
|
+
};
|
|
28
|
+
const emit = (event) => {
|
|
29
|
+
for (const listener of [...events])
|
|
30
|
+
listener(event);
|
|
31
|
+
};
|
|
32
|
+
const setStatusTo = (next) => {
|
|
33
|
+
if (connection === next)
|
|
34
|
+
return;
|
|
35
|
+
connection = next;
|
|
36
|
+
emit({ type: 'status', status: next });
|
|
37
|
+
};
|
|
38
|
+
/** Every request goes through here, so every one is answered or times out. */
|
|
39
|
+
function ask(event, payload) {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const args = payload === undefined ? [] : [payload];
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
// A request with no answer is not a refusal and must not look like one:
|
|
44
|
+
// the message says to try again, where a refusal would say why not to.
|
|
45
|
+
resolve({
|
|
46
|
+
ok: false,
|
|
47
|
+
code: 'client.timeout',
|
|
48
|
+
message: 'The office did not answer. Check your connection and try again.',
|
|
49
|
+
});
|
|
50
|
+
}, 10_000);
|
|
51
|
+
socket.emit(event, ...args, (result) => {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
resolve(result ?? { ok: false, code: 'client.no_reply', message: 'No answer.' });
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The gap handler.
|
|
59
|
+
*
|
|
60
|
+
* A client that misses a diff must not carry on drawing: it asks for a fresh
|
|
61
|
+
* snapshot. Guarded, so a burst of out-of-order diffs asks once rather than
|
|
62
|
+
* once per diff — and a resync is a full office, which is exactly what the
|
|
63
|
+
* diffs exist to avoid sending.
|
|
64
|
+
*/
|
|
65
|
+
async function resync() {
|
|
66
|
+
if (resyncing)
|
|
67
|
+
return;
|
|
68
|
+
resyncing = true;
|
|
69
|
+
try {
|
|
70
|
+
const result = await ask('office:resync');
|
|
71
|
+
if (result.ok)
|
|
72
|
+
publish(fromSnapshot(result.snapshot));
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
resyncing = false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
socket.on('connect', () => {
|
|
79
|
+
setStatusTo('connected');
|
|
80
|
+
// A reconnect re-enters with the credentials already in hand, so the person
|
|
81
|
+
// is back in the room they were in without touching anything.
|
|
82
|
+
if (credentials !== null)
|
|
83
|
+
void enter(credentials);
|
|
84
|
+
});
|
|
85
|
+
socket.on('disconnect', () => setStatusTo('reconnecting'));
|
|
86
|
+
socket.io.on('reconnect_attempt', () => setStatusTo('reconnecting'));
|
|
87
|
+
socket.on('office:diff', (diff) => {
|
|
88
|
+
const outcome = applyDiff(state, diff);
|
|
89
|
+
if (outcome.kind === 'applied')
|
|
90
|
+
publish(outcome.state);
|
|
91
|
+
else if (outcome.kind === 'resync')
|
|
92
|
+
void resync();
|
|
93
|
+
});
|
|
94
|
+
socket.on('knock:received', (event) => emit({ type: 'knock', ...event }));
|
|
95
|
+
socket.on('knock:resolved', (event) => emit({ type: 'knock.resolved', ...event }));
|
|
96
|
+
socket.on('knock:admitted', (event) => emit({ type: 'admitted', ...event }));
|
|
97
|
+
socket.on('template:changed', () => emit({ type: 'template.changed' }));
|
|
98
|
+
socket.on('call:reaction', (event) => emit({ type: 'reaction', ...event }));
|
|
99
|
+
/**
|
|
100
|
+
* How the adapter reaches the other legs.
|
|
101
|
+
*
|
|
102
|
+
* Over the socket that is already open, which is the whole of the built-in
|
|
103
|
+
* provider's signalling. An external provider's adapter would not use this at
|
|
104
|
+
* all — it would talk to its own servers, and our socket would carry only call
|
|
105
|
+
* state.
|
|
106
|
+
*/
|
|
107
|
+
const signaller = {
|
|
108
|
+
send(message) {
|
|
109
|
+
socket.emit('signal', message);
|
|
110
|
+
},
|
|
111
|
+
receive(handler) {
|
|
112
|
+
const wrapped = (message) => handler(message);
|
|
113
|
+
socket.on('signal', wrapped);
|
|
114
|
+
return () => socket.off('signal', wrapped);
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
const rtc = (options.rtc ?? meshAdapter)(signaller);
|
|
118
|
+
/*
|
|
119
|
+
* The adapter's events are the only thing the UI hears about the call — and
|
|
120
|
+
* three of them have to reach the server as well.
|
|
121
|
+
*
|
|
122
|
+
* Speaking drives the indicators for everybody else, so it cannot stay local.
|
|
123
|
+
* Media state is reported *after* the adapter did it, so a camera that failed to
|
|
124
|
+
* start never shows as on. Quality feeds the host's usage hook, and the relayed
|
|
125
|
+
* flag in it is the number that costs money.
|
|
126
|
+
*/
|
|
127
|
+
rtc.on((event) => {
|
|
128
|
+
if (event.type === 'speaking') {
|
|
129
|
+
socket.emit('call:speaking', { speaking: event.speaking });
|
|
130
|
+
}
|
|
131
|
+
if (event.type === 'state') {
|
|
132
|
+
socket.emit('call:media', {
|
|
133
|
+
muted: event.muted,
|
|
134
|
+
cameraOn: event.cameraOn,
|
|
135
|
+
sharing: event.sharing,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (event.type === 'quality') {
|
|
139
|
+
socket.emit('call:quality', {
|
|
140
|
+
peerDeviceId: event.deviceId,
|
|
141
|
+
relayed: event.relayed,
|
|
142
|
+
packetLoss: event.packetLoss,
|
|
143
|
+
roundTripMs: event.roundTripMs,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
// A call that could not connect at all, reported once so a host can see how
|
|
147
|
+
// often its network requirements are the problem.
|
|
148
|
+
if (event.type === 'failed' && !event.deviceId) {
|
|
149
|
+
socket.emit('call:failed', { reason: event.reason });
|
|
150
|
+
}
|
|
151
|
+
emit({ type: 'rtc', event });
|
|
152
|
+
});
|
|
153
|
+
/**
|
|
154
|
+
* Somebody took the share, so this one stops.
|
|
155
|
+
*
|
|
156
|
+
* The capture is stopped here rather than left to whoever is drawing, because it
|
|
157
|
+
* is not a drawing decision: the operating system is recording a screen, the
|
|
158
|
+
* server has already given the slot to somebody else, and a client that only
|
|
159
|
+
* redrew would go on capturing for nobody. The event is emitted as well, so the
|
|
160
|
+
* person can be told why their share ended without them touching anything.
|
|
161
|
+
*/
|
|
162
|
+
socket.on('call:share_ended', (event) => {
|
|
163
|
+
void rtc.stopScreenShare();
|
|
164
|
+
emit({ type: 'share.ended', ...event });
|
|
165
|
+
});
|
|
166
|
+
socket.on('disconnected', (reason) => {
|
|
167
|
+
// Told why, rather than just going quiet. The client stops retrying, because
|
|
168
|
+
// retrying a revoked credential forever helps nobody.
|
|
169
|
+
credentials = null;
|
|
170
|
+
setStatusTo('closed');
|
|
171
|
+
emit({ type: 'closed', ...reason });
|
|
172
|
+
socket.disconnect();
|
|
173
|
+
});
|
|
174
|
+
async function enter(given) {
|
|
175
|
+
credentials = given;
|
|
176
|
+
setStatusTo('connecting');
|
|
177
|
+
const result = await ask('office:enter', {
|
|
178
|
+
credentials: given,
|
|
179
|
+
deviceId: options.deviceId,
|
|
180
|
+
kind: options.kind ?? 'web',
|
|
181
|
+
});
|
|
182
|
+
if (result.ok) {
|
|
183
|
+
publish(fromSnapshot(result.snapshot));
|
|
184
|
+
setStatusTo('connected');
|
|
185
|
+
if (heartbeat)
|
|
186
|
+
clearInterval(heartbeat);
|
|
187
|
+
heartbeat = setInterval(() => {
|
|
188
|
+
void ask('heartbeat');
|
|
189
|
+
}, options.heartbeatMs ?? 20_000);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
credentials = null;
|
|
193
|
+
setStatusTo('idle');
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
state: () => state,
|
|
199
|
+
subscribe(listener) {
|
|
200
|
+
listeners.add(listener);
|
|
201
|
+
return () => listeners.delete(listener);
|
|
202
|
+
},
|
|
203
|
+
on(listener) {
|
|
204
|
+
events.add(listener);
|
|
205
|
+
return () => events.delete(listener);
|
|
206
|
+
},
|
|
207
|
+
status: () => connection,
|
|
208
|
+
enter,
|
|
209
|
+
async leave() {
|
|
210
|
+
await ask('office:leave');
|
|
211
|
+
credentials = null;
|
|
212
|
+
if (heartbeat)
|
|
213
|
+
clearInterval(heartbeat);
|
|
214
|
+
heartbeat = null;
|
|
215
|
+
publish(emptyOffice());
|
|
216
|
+
},
|
|
217
|
+
close() {
|
|
218
|
+
if (heartbeat)
|
|
219
|
+
clearInterval(heartbeat);
|
|
220
|
+
heartbeat = null;
|
|
221
|
+
socket.disconnect();
|
|
222
|
+
setStatusTo('closed');
|
|
223
|
+
},
|
|
224
|
+
/**
|
|
225
|
+
* Move, optimistically.
|
|
226
|
+
*
|
|
227
|
+
* The avatar moves the moment it is clicked, because waiting a round trip to
|
|
228
|
+
* watch your own click land feels broken. The server's answer is still the
|
|
229
|
+
* authority: a refusal snaps the avatar back and says why, and the next diff
|
|
230
|
+
* overwrites whatever was guessed here either way.
|
|
231
|
+
*
|
|
232
|
+
* Which is also how two devices acting at once resolve. Both guess, one is
|
|
233
|
+
* refused, and the diff that follows is the same office on both.
|
|
234
|
+
*/
|
|
235
|
+
async joinRoom(roomId) {
|
|
236
|
+
const me = state.people.get(state.you.userId);
|
|
237
|
+
const previous = me?.roomId;
|
|
238
|
+
if (me) {
|
|
239
|
+
publish(applyChange(state, {
|
|
240
|
+
kind: 'person.moved',
|
|
241
|
+
userId: me.userId,
|
|
242
|
+
roomId,
|
|
243
|
+
arrivedAt: new Date().toISOString(),
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
const result = await ask('room:join', { roomId });
|
|
247
|
+
if (!result.ok && me && previous) {
|
|
248
|
+
publish(applyChange(state, {
|
|
249
|
+
kind: 'person.moved',
|
|
250
|
+
userId: me.userId,
|
|
251
|
+
roomId: previous,
|
|
252
|
+
arrivedAt: me.arrivedAt,
|
|
253
|
+
}));
|
|
254
|
+
emit({ type: 'refused', action: 'room:join', code: result.code, message: result.message });
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
},
|
|
258
|
+
leaveRoom: () => ask('room:leave'),
|
|
259
|
+
lock: (roomId) => ask('room:lock', { roomId }),
|
|
260
|
+
unlock: (roomId) => ask('room:unlock', { roomId }),
|
|
261
|
+
knock: (roomId) => ask('room:knock', { roomId }),
|
|
262
|
+
admit: (knockId) => ask('knock:admit', { knockId }),
|
|
263
|
+
decline: (knockId) => ask('knock:decline', { knockId }),
|
|
264
|
+
/**
|
|
265
|
+
* Set, or clear, a status the person chose.
|
|
266
|
+
*
|
|
267
|
+
* The choice is recorded here as well as sent, because `you.manual` is what
|
|
268
|
+
* the control reads to decide whether to offer a way back to automatic, and
|
|
269
|
+
* it arrives only in a snapshot. Waiting for the next one would leave the
|
|
270
|
+
* control a step behind its own button.
|
|
271
|
+
*/
|
|
272
|
+
async setStatus(manual) {
|
|
273
|
+
const result = await ask('status:manual', { manual });
|
|
274
|
+
if (result.ok)
|
|
275
|
+
publish({ ...state, you: { ...state.you, manual } });
|
|
276
|
+
return result;
|
|
277
|
+
},
|
|
278
|
+
setCustomStatus: (custom) => ask('status:custom', { custom }),
|
|
279
|
+
setActivity(activity) {
|
|
280
|
+
// No acknowledgement: these arrive constantly and nobody waits on them.
|
|
281
|
+
socket.emit('device:activity', activity);
|
|
282
|
+
},
|
|
283
|
+
async joinCall(wanted) {
|
|
284
|
+
const result = await ask('call:join', wanted);
|
|
285
|
+
if (!result.ok) {
|
|
286
|
+
emit({ type: 'refused', action: 'call:join', code: result.code, message: result.message });
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
// The server's answer first, then the media. The other order would mean
|
|
290
|
+
// publishing to a call that might refuse us.
|
|
291
|
+
await rtc.join({
|
|
292
|
+
callId: result.call.call.roomId,
|
|
293
|
+
deviceId: state.you.deviceId || options.deviceId,
|
|
294
|
+
credentials: result.call.credentials,
|
|
295
|
+
iceServers: result.call.iceServers,
|
|
296
|
+
participants: result.call.participants,
|
|
297
|
+
audio: wanted.audio,
|
|
298
|
+
video: wanted.video,
|
|
299
|
+
});
|
|
300
|
+
return result;
|
|
301
|
+
},
|
|
302
|
+
async leaveCall() {
|
|
303
|
+
// The media first: tearing down locally before telling the server means
|
|
304
|
+
// nobody is left looking at a tile for a camera that has already stopped.
|
|
305
|
+
await rtc.leave();
|
|
306
|
+
return ask('call:leave');
|
|
307
|
+
},
|
|
308
|
+
raiseHand: (raised) => ask('call:hand', { raised }),
|
|
309
|
+
/**
|
|
310
|
+
* React, and say so if it was refused.
|
|
311
|
+
*
|
|
312
|
+
* The refusal is announced like any other, because a reaction that silently
|
|
313
|
+
* does nothing looks like a broken button — and the limit is the reason it
|
|
314
|
+
* would happen.
|
|
315
|
+
*/
|
|
316
|
+
async react(reaction) {
|
|
317
|
+
const result = await ask('call:react', { reaction });
|
|
318
|
+
if (!result.ok) {
|
|
319
|
+
emit({ type: 'refused', action: 'call:react', code: result.code, message: result.message });
|
|
320
|
+
}
|
|
321
|
+
return result;
|
|
322
|
+
},
|
|
323
|
+
rtc,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
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, type DiffOutcome, type OfficeState, type Sharer, } 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, type JoinOptions, type RtcClientAdapter, type RtcEvent, type RtcHandler, type Signaller, } 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, type ScreenSource, type ScreenSourceProvider, type ShareOptions, } 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, type DeviceChoice, type DeviceStorage, type Devices, type PermissionOutcome, } from './rtc/devices.js';
|
|
35
|
+
export { createOfisClient, type ClientEvent, type ConnectionStatus, type OfisClient, type OfisClientOptions, type SocketLike, } 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, type Reaction } from '@unityevolv/ofiskit-realtime-core/protocol';
|
|
47
|
+
/**
|
|
48
|
+
* The wire types, re-exported from the one place they are defined.
|
|
49
|
+
*
|
|
50
|
+
* A UI needs `PublicPresence` to draw a person and `Status` to draw a dot, and
|
|
51
|
+
* making it depend on the engine package for them would mean the map imported
|
|
52
|
+
* the server. There is one definition; this is the door to it.
|
|
53
|
+
*/
|
|
54
|
+
export type { CallJoinRequest, CallJoinResponse, CustomStatus, DeviceKind, ManualStatus, OfficeChange, OfficeDiff, IceServer, OfficeSnapshot, PublicPresence, RoomCall, Status, } from '@unityevolv/ofiskit-realtime-core/protocol';
|
|
55
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","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,EACR,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,MAAM,GACZ,MAAM,mBAAmB,CAAA;AAE1B;;;;;;GAMG;AACH,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,SAAS,GACf,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,EACb,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,YAAY,GAClB,MAAM,iBAAiB,CAAA;AAExB;;;;;;;GAOG;AACH,OAAO,EACL,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,OAAO,EACZ,KAAK,iBAAiB,GACvB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,gBAAgB,EAChB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,UAAU,GAChB,MAAM,aAAa,CAAA;AAEpB;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,4CAA4C,CAAA;AAErF;;;;;;GAMG;AACH,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,SAAS,EACT,cAAc,EACd,cAAc,EACd,QAAQ,EACR,MAAM,GACP,MAAM,4CAA4C,CAAA"}
|