@hcikn/colibri 1.3.2 → 2.0.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 +21 -0
- package/README.md +226 -88
- package/dist/index.cjs +1514 -972
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +235 -22
- package/dist/index.d.ts +235 -22
- package/dist/index.js +1480 -884
- package/dist/index.js.map +1 -1
- package/package.json +98 -41
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 HCI Group University Konstanz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,156 +1,294 @@
|
|
|
1
|
-
# Colibri
|
|
1
|
+
# Colibri Web
|
|
2
|
+
|
|
3
|
+
TypeScript client for Colibri supporting messages between clients, synchronized objects, data store and monitoring.
|
|
4
|
+
Voice chat is currently not supported on the web.
|
|
5
|
+
|
|
6
|
+
Documentation: [colibri.hci.uni-konstanz.de](https://colibri.hci.uni-konstanz.de/web/installation)
|
|
7
|
+
|
|
8
|
+
Upgrading from Colibri 1.x: [Migration Guide](../MIGRATION.md).
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
- Node.js 18 or newer
|
|
13
|
+
- TypeScript 5.0 or newer with standard decorators. Leave `experimentalDecorators` unset or `false` in
|
|
14
|
+
`tsconfig.json`. Vite needs a plugin for them: see
|
|
15
|
+
[Vite](https://colibri.hci.uni-konstanz.de/web/installation#vite).
|
|
16
|
+
- [colibri-server](../colibri-server/README.md#installation) 2.0.0 or newer
|
|
2
17
|
|
|
3
18
|
## Installation
|
|
4
19
|
|
|
5
|
-
|
|
6
|
-
|
|
20
|
+
### npm (recommended)
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install @hcikn/colibri@2
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Keep the `@2`. Without it, npm may install a 1.x release. The peer dependency `rxjs` is installed automatically.
|
|
27
|
+
|
|
28
|
+
### Building from source
|
|
29
|
+
|
|
30
|
+
To build the package from the repository:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
cd colibri-web
|
|
34
|
+
npm ci
|
|
35
|
+
npm run build
|
|
36
|
+
npm pack
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Copy the resulting `hcikn-colibri-<version>.tgz` into your project and install it with
|
|
40
|
+
`npm install ./hcikn-colibri-<version>.tgz`.
|
|
7
41
|
|
|
8
42
|
## Configuration
|
|
9
43
|
|
|
10
|
-
|
|
44
|
+
Create the Colibri client once, when your app starts:
|
|
11
45
|
|
|
12
46
|
```ts
|
|
13
47
|
import { Colibri } from '@hcikn/colibri';
|
|
14
|
-
new Colibri('app_name', 'server_address');
|
|
15
48
|
|
|
16
|
-
|
|
17
|
-
new Colibri('app_name', 'server_address', 9011);
|
|
49
|
+
new Colibri('myAppName', 'colibri.hci.uni-konstanz.de');
|
|
18
50
|
```
|
|
19
51
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
52
|
+
- The first argument is the **app name**. Set it to a unique name that is not used on the server. Clients exchange
|
|
53
|
+
data only if their app names match, web and Unity alike. Do not use `colibri`, which the server's web page uses.
|
|
54
|
+
- The second argument is the **server address**: a host name or IP address, optionally with `http://` or
|
|
55
|
+
`https://`. A public test server is available: `colibri.hci.uni-konstanz.de`. You can leave it out when colibri-server runs on the server that served your page.
|
|
56
|
+
- A second `new Colibri()` throws an error. Elsewhere in your app, use `Colibri.getInstance()`.
|
|
23
57
|
|
|
24
|
-
|
|
58
|
+
## Samples
|
|
25
59
|
|
|
26
|
-
|
|
60
|
+
The [samples](samples/) are in the `colibri-web` folder of the repository. They run in a terminal with Node.js:
|
|
27
61
|
|
|
28
|
-
|
|
62
|
+
```sh
|
|
63
|
+
cd colibri-web
|
|
64
|
+
npm ci
|
|
65
|
+
npm run samples/broadcast
|
|
66
|
+
```
|
|
29
67
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
68
|
+
Each sample asks for the server address and port. Press Enter twice for the public test server. To see two clients
|
|
69
|
+
exchange data, start the same sample in a second terminal, or run it next to a Unity client whose app name is
|
|
70
|
+
`myAppName`, which the samples use.
|
|
33
71
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
72
|
+
| Sample | Shows |
|
|
73
|
+
| ------------------------------------------- | -------------------------------------------------- |
|
|
74
|
+
| [broadcast](samples/broadcast.ts) | Sending and receiving messages |
|
|
75
|
+
| [model-sync](samples/model-sync.ts) | Synchronized objects with `SyncModel` |
|
|
76
|
+
| [remote-logging](samples/remote-logging.ts) | Sending the console log to the server |
|
|
77
|
+
| [rest-api](samples/rest-api.ts) | Saving and loading values in the data store |
|
|
78
|
+
| [sync-transform](samples/sync-transform.ts) | Showing and moving Unity's `SyncTransform` objects |
|
|
38
79
|
|
|
39
|
-
|
|
80
|
+
## Usage
|
|
40
81
|
|
|
41
|
-
###
|
|
82
|
+
### Send/Receive Messages
|
|
42
83
|
|
|
43
|
-
|
|
44
|
-
the executed code, as illustrated with the following simple example of sending a boolean value on a "click" channel:
|
|
84
|
+
For sending simple messages of events and commands you can use `Sync.send*`. Each message has a channel name.
|
|
45
85
|
|
|
46
86
|
```ts
|
|
47
|
-
|
|
48
|
-
Sync.
|
|
87
|
+
const myNumber = 5;
|
|
88
|
+
Sync.sendNumber('myChannel', myNumber);
|
|
49
89
|
```
|
|
50
90
|
|
|
51
|
-
|
|
91
|
+
A client receives the messages of every channel it listens to:
|
|
52
92
|
|
|
53
93
|
```ts
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
|
|
94
|
+
function onMyChannel(myNumber: number) {
|
|
95
|
+
// This code is executed whenever
|
|
96
|
+
// a number is received on 'myChannel'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
Sync.receiveNumber('myChannel', onMyChannel);
|
|
57
100
|
```
|
|
58
101
|
|
|
59
|
-
|
|
102
|
+
A full example:
|
|
60
103
|
|
|
61
104
|
```ts
|
|
62
|
-
Sync
|
|
63
|
-
|
|
105
|
+
import { Sync } from '@hcikn/colibri';
|
|
106
|
+
|
|
107
|
+
function onStartRound(round: number) {
|
|
108
|
+
console.log(`Round ${round} starts`);
|
|
109
|
+
// reset the scores, start the timer, ...
|
|
110
|
+
}
|
|
64
111
|
|
|
65
|
-
|
|
112
|
+
// Register before anything is sent. Messages without a listener are dropped.
|
|
113
|
+
Sync.receiveNumber('startRound', onStartRound);
|
|
66
114
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Sync.
|
|
115
|
+
// Reaches all other clients with the same app name, not this one.
|
|
116
|
+
export function startRound(round: number) {
|
|
117
|
+
Sync.sendNumber('startRound', round);
|
|
118
|
+
onStartRound(round);
|
|
119
|
+
}
|
|
70
120
|
```
|
|
71
121
|
|
|
72
|
-
|
|
122
|
+
The script listens on the channel `startRound` for numbers. One player calls `startRound`, e.g. from a button,
|
|
123
|
+
which sends the round number to all other clients, web and Unity. On each of them, Colibri calls `onStartRound`,
|
|
124
|
+
which starts the round. A client does not receive its own messages, so `startRound` also calls `onStartRound`
|
|
125
|
+
directly to start the round on the sending client too.
|
|
73
126
|
|
|
74
|
-
|
|
127
|
+
- Channel and type must match on both sides.
|
|
128
|
+
- Supported types: `sendBool`, `sendNumber`, `sendString`, `sendVector2`, `sendVector3`, `sendQuaternion`,
|
|
129
|
+
`sendColor`, their array forms such as `sendVector3Array`, and `sendJson` for anything else. Each has a matching
|
|
130
|
+
`receive*` ([details](https://colibri.hci.uni-konstanz.de/web/messages#types)).
|
|
131
|
+
- JavaScript has only one number type, so every number is sent as `float`. Unity must receive it with
|
|
132
|
+
`Sync.Receive<float>`, not `Sync.Receive<int>`.
|
|
133
|
+
- A listener stays registered until `Sync.unregister`.
|
|
75
134
|
|
|
76
|
-
|
|
77
|
-
- Type and channel _must_ match between Listener and Sender (`number` will be converted to float for Unity clients, i.e., use `float` listener on Unity clients for sending numbers!)
|
|
78
|
-
- Remember to unregister your listener where necessary!
|
|
135
|
+
### SyncTransform
|
|
79
136
|
|
|
80
|
-
|
|
137
|
+
`SyncTransform` gives the web the objects of Unity's `SyncTransform` component: their position, rotation, scale
|
|
138
|
+
and visibility, e.g. to show a shared 3D object on a web page or to move it from there.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { RegisterSyncTransforms } from '@hcikn/colibri';
|
|
142
|
+
|
|
143
|
+
const [transforms$] = RegisterSyncTransforms();
|
|
144
|
+
|
|
145
|
+
transforms$.subscribe(transforms => {
|
|
146
|
+
for (const transform of transforms) {
|
|
147
|
+
console.log(transform.id, transform.position, transform.rotation, transform.scale, transform.active);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
```
|
|
81
151
|
|
|
82
|
-
|
|
152
|
+
`RegisterSyncTransforms` returns `transforms$`, an Observable of all `SyncTransform` objects of the app. It emits
|
|
153
|
+
the list whenever an object is added, moved, shown, hidden or deleted on any client.
|
|
83
154
|
|
|
84
|
-
|
|
155
|
+
To move an object, assign a new position to it, e.g. when the user drags it in a 3D view. Colibri sends it to all
|
|
156
|
+
other clients, web and Unity:
|
|
85
157
|
|
|
86
158
|
```ts
|
|
87
|
-
|
|
159
|
+
transform.position = [1, 0, 2];
|
|
160
|
+
```
|
|
88
161
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
162
|
+
- Positions and scales are `[x, y, z]`, rotations `[x, y, z, w]`, in Unity's coordinates: left-handed with Y up.
|
|
163
|
+
three.js and most other web 3D libraries are right-handed. To convert, negate `z` of positions, and `x` and `y` of
|
|
164
|
+
rotations. Scales need no conversion.
|
|
165
|
+
- Assign a new array to change a value. Changing an array in place, e.g. `position[0] = 1`, is not sent.
|
|
166
|
+
- Unity objects with a `ModelId` sync on a channel of their own. Pass it: `RegisterSyncTransforms('<ModelId>')`.
|
|
167
|
+
- Call `RegisterSyncTransforms` once per `ModelId` and share what it returns.
|
|
168
|
+
- Move an object from one client at a time. Concurrent updates make it jitter.
|
|
94
169
|
|
|
95
|
-
|
|
96
|
-
private age = 0;
|
|
170
|
+
### SyncModel
|
|
97
171
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
```
|
|
172
|
+
`SyncModel` synchronizes the properties of your own classes between clients, e.g. a score, a selection or the state
|
|
173
|
+
of an experiment. It is the web counterpart of Unity's `SyncBehaviour`, and both sync with each other.
|
|
102
174
|
|
|
103
|
-
|
|
175
|
+
```ts
|
|
176
|
+
import { RegisterModelSync, SyncModel, Synced } from '@hcikn/colibri';
|
|
104
177
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"experimentalDecorators": true
|
|
109
|
-
}
|
|
178
|
+
class Player extends SyncModel<Player> {
|
|
179
|
+
@Synced()
|
|
180
|
+
accessor score = 0;
|
|
110
181
|
}
|
|
111
182
|
|
|
183
|
+
const [players$, registerPlayer] = RegisterModelSync<Player>({ name: 'player', type: Player });
|
|
184
|
+
|
|
185
|
+
players$.subscribe(players => {
|
|
186
|
+
console.log(players.map(p => `${p.id}: ${p.score}`));
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const me = new Player(`player-${Date.now()}`);
|
|
190
|
+
registerPlayer(me);
|
|
191
|
+
me.score = 10;
|
|
112
192
|
```
|
|
113
193
|
|
|
114
|
-
|
|
194
|
+
`Player` inherits from `SyncModel<Player>`. `@Synced()` marks `score` for syncing: when one client changes it, it
|
|
195
|
+
changes on all other clients. `RegisterModelSync` returns `players$`, an [RxJS](https://rxjs.dev/guide/observable)
|
|
196
|
+
Observable of all players, and `registerPlayer`. `players$` emits the list whenever a player is added, changed or
|
|
197
|
+
deleted, also by another client. `registerPlayer` starts syncing a new player. Its id must be unique across all
|
|
198
|
+
clients.
|
|
115
199
|
|
|
116
|
-
|
|
200
|
+
- Declare each synced property with `accessor`. Without it, `@Synced()` does not compile.
|
|
201
|
+
- Always pass `name`. Without it, the channel is the class name in lower case, which a minified production build
|
|
202
|
+
changes.
|
|
203
|
+
- To sync with a Unity `SyncBehaviour`, set `name` to the Unity class name in lower case, e.g. `'player'` for the
|
|
204
|
+
`Player` in the [Unity README](../colibri-unity/README.md#syncbehaviour). Property names are compared in lower
|
|
205
|
+
case, so `score` matches Unity's `Score` ([details](https://colibri.hci.uni-konstanz.de/web/syncmodel)).
|
|
206
|
+
- `delete()` deletes an object for every client.
|
|
117
207
|
|
|
118
|
-
|
|
119
|
-
import { RegisterModelSync } from '@hcikn/colibri';
|
|
120
|
-
const [ SampleClasses$, registerExampleClass ] = RegisterModelSync<SampleClass>({ type: SampleClass });
|
|
121
|
-
```
|
|
208
|
+
### Monitoring
|
|
122
209
|
|
|
123
|
-
The
|
|
210
|
+
The server's web page at `http://<server-address>:9211` shows the connected clients with their latency and
|
|
211
|
+
throughput, and the synchronized objects. To also see a client's console log there, create a `RemoteLogger` when
|
|
212
|
+
your app starts:
|
|
124
213
|
|
|
125
214
|
```ts
|
|
126
|
-
|
|
127
|
-
|
|
215
|
+
import { RemoteLogger } from '@hcikn/colibri';
|
|
216
|
+
|
|
217
|
+
new RemoteLogger();
|
|
128
218
|
```
|
|
129
219
|
|
|
130
|
-
|
|
220
|
+
From then on, everything logged with `console` also appears on the server's web page. This is the easiest way to
|
|
221
|
+
read the log of a web app on a smartphone or head-mounted display ([details](https://colibri.hci.uni-konstanz.de/web/monitoring)).
|
|
131
222
|
|
|
132
|
-
###
|
|
223
|
+
### Data store
|
|
133
224
|
|
|
134
|
-
|
|
225
|
+
The data store keeps small amounts of data on the server, e.g. the color a player selected, the settings of a game
|
|
226
|
+
or the condition of a study. Unlike messages and synchronized objects, the data stays saved after all clients
|
|
227
|
+
disconnect.
|
|
135
228
|
|
|
136
229
|
```ts
|
|
137
|
-
import {
|
|
230
|
+
import { GetRestApi, PutRestApi, toHexColor, toRgbaColor } from '@hcikn/colibri';
|
|
138
231
|
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
const data = {...};
|
|
232
|
+
const playerName = 'Alex';
|
|
233
|
+
const avatar = document.getElementById('avatar')!;
|
|
142
234
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
235
|
+
async function loadColor() {
|
|
236
|
+
const color = await GetRestApi(`color-${playerName}`);
|
|
237
|
+
if (color) {
|
|
238
|
+
avatar.style.backgroundColor = toHexColor(color);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
146
241
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
242
|
+
// Call this when the player selects a color, e.g. '#ff0000ff'.
|
|
243
|
+
async function selectColor(color: string) {
|
|
244
|
+
avatar.style.backgroundColor = color;
|
|
245
|
+
await PutRestApi(`color-${playerName}`, toRgbaColor(color));
|
|
246
|
+
}
|
|
150
247
|
```
|
|
151
248
|
|
|
152
|
-
|
|
249
|
+
`selectColor` colors the player's avatar and saves the color on the server under the name `color-<player name>`.
|
|
250
|
+
`loadColor` loads the saved color, so the player gets their color back in the next session. `GetRestApi` returns
|
|
251
|
+
`null` if the player has not selected a color yet. The color is saved as `[r, g, b, a]`, the format Unity uses, so
|
|
252
|
+
the same value also works in the [Unity README's](../colibri-unity/README.md#data-store) `PlayerColor` sample. Other
|
|
253
|
+
clients see a new value the next time they call `GetRestApi`. It is not sent to them automatically.
|
|
153
254
|
|
|
154
|
-
|
|
255
|
+
- Values are saved per app name. Each value can be up to 5 MiB.
|
|
256
|
+
- Any JSON value works, e.g. an object, an array, a number or a string.
|
|
257
|
+
- Create the `Colibri` instance first. Without it, both functions return `undefined`.
|
|
258
|
+
|
|
259
|
+
### Voice chat
|
|
260
|
+
|
|
261
|
+
Voice chat is currently not supported on the web. It is available in [colibri-unity](../colibri-unity/README.md#voice-chat).
|
|
262
|
+
|
|
263
|
+
## Advanced Configuration
|
|
264
|
+
|
|
265
|
+
`new Colibri(app, server, port)` takes these arguments:
|
|
266
|
+
|
|
267
|
+
| Argument | Default | Description |
|
|
268
|
+
| -------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
269
|
+
| `app` | required | App name. Must match on all clients that exchange data. |
|
|
270
|
+
| `server` | Host of the page. Required in Node.js. | Host name or IP address, optionally with `http://`, `https://`, `ws://` or `wss://` and `:port`. No path, query or fragment. |
|
|
271
|
+
| `port` | `9211` | The server's web port. Used when `server` has no port. If both have one, they must match. |
|
|
272
|
+
|
|
273
|
+
- For a server with [TLS](https://colibri.hci.uni-konstanz.de/web/configuration#tls), use `https://` or `wss://`. A page served over `https://` can only
|
|
274
|
+
connect this way, because browsers block `http://` and `ws://` there.
|
|
275
|
+
|
|
276
|
+
## Troubleshooting
|
|
277
|
+
|
|
278
|
+
| Symptom | Cause | Fix |
|
|
279
|
+
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
|
|
280
|
+
| `new Colibri()` throws a `ColibriError` | Path such as `/log`, query or fragment in the address, another scheme, a port that conflicts with the third argument, or a second instance | Pass only scheme, host and port. Use `Colibri.getInstance()`. |
|
|
281
|
+
| Clients do not see each other | Different app names or servers, or the listener was not registered and connected at send time | Use the same app name and server. Register listeners first. |
|
|
282
|
+
| Nothing connects from an `https://` page | The browser blocks `ws://` and `http://` (mixed content) | Use `https://` or `wss://` with [TLS](https://colibri.hci.uni-konstanz.de/web/configuration#tls) or a TLS proxy |
|
|
283
|
+
| `Colibri: could not connect to ...` after upgrading from 1.x | The address or `port` names 9011, 1.x's default port. 2.x servers default to 9211. | Use the server's port |
|
|
284
|
+
| `colibri.protocolMismatch` emits with `fatal: true` | The server refused this client and closed the connection | Use 2.x on both sides ([Protocol version](https://colibri.hci.uni-konstanz.de/web/configuration#protocol-version)) |
|
|
285
|
+
| `colibri.protocolMismatch` emits with `fatal: false` | No version announcement within 5 s, typical of a pre-2.0.0 server. The connection stays up. | Upgrade the server to 2.x |
|
|
286
|
+
| `@Synced()` fails to compile or does not sync | `experimentalDecorators` is on, or a member lacks `accessor` | Remove `experimentalDecorators`. Declare synced members with `accessor`. |
|
|
287
|
+
| Models sync in development but not in a production build or with Unity | No `name` in `RegisterModelSync` | Pass `name` |
|
|
288
|
+
| Unity never receives numbers from the web | colibri-web sends every number as `float` | Receive with `Sync.Receive<float>` in Unity, not `Sync.Receive<int>` |
|
|
289
|
+
| Colors arrive as strings or arrays | Unity sends `"#RRGGBBAA"`, colibri-web `[r, g, b, a]` | Normalize with `toHexColor()` or `toRgbaColor()` |
|
|
290
|
+
| `Sync.send*` does nothing | Called before `new Colibri()`, dropped with a console warning | Create the `Colibri` instance first |
|
|
291
|
+
|
|
292
|
+
## License
|
|
155
293
|
|
|
156
|
-
|
|
294
|
+
[MIT](LICENSE)
|