ts3d-hc-collabserver
v0.6.8
Published
* Textbox plugin updated
Downloads
11
Readme
ts3d-hc-collabserver []
Version Update (0.6.8)
- Textbox plugin updated
Version Update (0.6.7)
- Line support for mesh creation & color
- Increased max message size
Version Update (0.6.3)
- Better Suport for plugins
- (
setMessageReceivedCallback
changed toregisterMessageReceivedCallback
) - Ability to retrieve internal suspend state
- Ability to retrieve user info by id
- (
- Camera and Textbox plugins added
- Various bug fixes
Version Update (0.4.0)
- Support for mesh creation & instancing (beta)
- Support for Desyncing camera and user avatars
- Support for persistent room data
Overview
This library adds real-time collaboration support to any HOOPS Communicator based application by synchronizing a subset of the HOOPS Communicator API calls between multiple clients. This includes camera interaction, selection, markup & measurement, as well as cutting planes and various model attributes (visibility, color, matrices, etc.).
For questions/feedback please send an email to [email protected] or post in our forum. For a 60 day trial of the HOOPS Web Platform go to Web Platform.
GitHub Project
The public github project can be found here:
https://github.com/techsoft3d/ts3d-hc-collabServer
Install
Via GitHub
- Clone above GitHub project and run
npm install
in the root folder. This will install all dependencies. - Client-side libraries can be found in the ./dist folder
Via NPM
Install the server-side library into your existing node project:
npm install ts3d-hc-collabserver
Client-side libraries can be found in the ./dist folder of the installed module
Add client-side collaboration library as well as socket.io to your project with a script tag or use module version of library:
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.1/socket.io.js" integrity="sha512-MgkNs0gNdrnOM7k+0L+wgiRc5aLgl74sJQKbIWegVIMvVGPc1+gc1L2oK9Wf/D9pq58eqIJAxOonYPVE5UwUFA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="./js/hcCollab.min.js"></script>
Usage - Server Side
Standalone
Start server with npm start
. This will start the standalone collaboration server on port 3001. All websocket communication with the client will happen via this port. In this scenario you will either have to provide the url/port in the client during initialization or proxy the websocket connection from your webserver to the collaboration server.
As a module
When running the server from your own node project, simply include this module and pass your server object to it:
const collab = require('ts3d-hc-collabserver');
var server;
server = http.createServer(app);
collab.start(server, {allowUserRooms: true});
server.listen(3000);
See server/standalone.js for an example on how to use the server as a module in your own node project.
Usage - Client Side
- After including the client-side library in your project, a gloabl hcCollab object will be available.
- You should call
hcCollab.init()
to initialize the library as soon as the modelStructureReady callback has triggered in the viewer. The parameter to this call are the webviewer object as well as optionally the default UI object. - An optional callback can be provided that gets triggered when a new collaboration message has been received.
- Finally to establish the connection to the collaboration server call
hcCollab.connect()
. This call takes the name of the room you want to connect to, the username of the client as well as an optional room password.
Below is the minimum code required to establish a connection to the collaboration server. After this block of code is executed the local viewer is synced with all other clients that have connected to the same room.
hcCollab.initialize(hwv, ui);
hcCollab.registerMessageReceivedCallback(hcCollabMessageReceived);
hcCollab.connect("default", "User" + Math.floor(Math.random() * 9999));
Limitations
Currently, this library does not sync ALL webviewer API calls. While mesh and mesh instance creation is partially supported, support is not complete and many other API calls are also not handled. Also, if a new user joins only the camera is synced automatically, though you can provide your own code to sync other client states. Finally, the default HOOPS Communicator UI in some cases manages its own state that might not reflect the state of the webviewer. For now, it is recommended to use your own, custom UI for the collaboration server to avoid those inconsistencies.
Please let us know if you run into any problems or require support for specific functionality with regard to syncing. You can also of course add support yourself, either by using the custom message mechanism of the library or forking the project.
Demo
For a live demo of the this library please check out the HOOPS Communicator 3D Sandbox. Its collaboration feature has been developed with this library. There is also a demo available as part of this project you can run with npm run startdemo
. You can access the demo in the browser at http://localhost:3000/viewer.html. As soon as the demo runs, it will establish a connection to the collaboration server and you can open another browser window to connect another user. Running and looking through the code of this demo will help you understand how to build a UI around this library and some of its more advanced concepts like passing custom messages and using plugins.
The client side code for the demo can be found in the dev/public folder of this project. Please note that this demo is using the development version of the collboration library. If you want to use the library in your own project you should use the minified version, which can be found in the dist folder of this project.
Advanced Concepts
Sending your own custom messages
By default the collaboration library will automatically syncronize webviewer calls across clients. However, in some cases you want to send more high level messages to other users. For example, in the 3D Sandbox when running the code in the editor, this is handled as a single custom message, not as a series of individual webviewer calls. In this scenario you most likely want to temporary suppress any webviewer messages from also getting synchronized. This can be done by calling hcCollab.setSuspendSend(true)
before sending your custom message and then calling hcCollab.setSuspendSend(false)
after your message has been sent. You have to make sure that all clients also call suspendSend when they receive your message.
Below is an example on how custom messages are handled in the callback. In the demo we are syncing interactions with the KinematicsToolkit with other clients. As the calls into those library trigger additional webviewer calls we need to suspend those messages when we send (the code for that can be found in componentMove.js) or receive our custom message.
case "custommessage":
{
switch (msg.customType) {
case "test": {
alert("User " + msg.user + " says " + msg.text);
}
break;
case "componentSet": {
hcCollab.setSuspendSend(true);
let hierachy = KT.KinematicsManager.getHierachyByIndex(msg.hierachyIndex);
let component = hierachy.getComponentById(msg.componentId);
await component.set(msg.value);
componentSetHash[msg.hierachyindex + "@" + msg.componentId] = msg.value;
await component.getHierachy().updateComponents();
hcCollab.setSuspendSend(false);
}
break;
}
}
break;
In addition to sending custom messages, it is also possible to suppress certain standard message types by checking the type and selectively returning false
at the end of the message callback.
Synchronizing initial server state
Currently, the library only synchronizes the current camera automatically when a new user connects to a session. However, you are free to synchronize additional data on connection with other clients. This can be done by adding additional values to the the message objects when a sendInitialState message has been received. This message will be send by the server to only one of the already connected clients.
See below for how the demo synchronizes the state of the KinematicsToolkit in that case:
switch (msg.type) {
case "initialState": {
if (msg.kmValues) {
hcCollab.setSuspendSend(true);
for (let i=0;i<msg.kmValues.length;i++) {
let kmValue = msg.kmValues[i];
let hierachy = KT.KinematicsManager.getHierachyByIndex(kmValue.hierachyIndex);
let component = hierachy.getComponentById(kmValue.componentId);
await component.set(kmValue.value);
componentSetHash[kmValue.hierachyIndex + "@" + kmValue.componentId] = kmValue.value;
await component.getHierachy().updateComponents();
}
hcCollab.setSuspendSend(false);
}
}
break;
case "sendInitialState": {
let values = [];
for (let key in componentSetHash) {
let split = key.split("@");
values.push({ hierachyIndex:parseInt(split[0]),componentId: parseInt(split[1]), value: componentSetHash[key]});
}
msg.kmValues = values;
}
break;
Handling Session Locking
It is possible for a user to lock a session with the lockSession()
command. This will prevent any other clients from sending collaboration messages to the server. However, it does not actually prevent interacting with the webviewer. If you want to disable any interaction for locked clients you have to do this when the lock/unlock message is received. In the demo this is done by simply disabling pointer events on the webviewer div:
switch (msg.type) {
case "lockSession":
{
$("#content").css("pointer-events", "none");
$("#collabLockButton").prop("disabled", true);
}
break;
case "unlockSession":
{
$("#content").css("pointer-events", "all");
$("#collabLockButton").prop("disabled", false);
}
break;
Updating Room State
Each Room has its own persistant state object which can be modified and queried by each connected client which makes it easy to synchronize state not directly related to the webviewer specific functionality (and without the use of custom messages). Below is a simple example that keeps a set of variables in sync between clients.
var data = {posx:0, posy:0}; //reset initial state on startup
if (hcCollab.getActive()) {
await hcCollab.updateRoomData(data);
}
function createCube() {
if (hcCollab.getActive()) {
data = await hcCollab.getRoomData(); //get current state of room
}
//create the cube
//...
data.posx+=50; //update local state
if (data.posx > 500) {
data.posx = 0;
data.posy += 50;
}
if (hcCollab.getActive()) {
await hcCollab.updateRoomData(data); //update room state with local states
}
}
Camera and Selection Syncing
By default the webviewer camera is automatically synced across clients. This can be turned off with hcCollab.setSyncCamera(false)
. It is also possible to disable syncing of the selections with hcCollab.setSyncSelection(false)
.
Handling Collaboration UI
The demo project shows how to populate a list of user as well as handle chat messages by specifiying a callback function to the hcCollab.registerMessageReceivedCallback()
function. Using this callback you can detect if a new user has joined the room, an existing user has disconnected or a new text message has been received.
See below for an example on how to handle those messages:
async function hcCollabMessageReceived(msg) {
switch (msg.type) {
case "userlist":
{
collaboratorTable.clearData();
let users = msg.roomusers;
for (let i = 0; i < users.length; i++) {
var prop;
if (hcCollab.getLocalUser().id == users[i].id)
prop = { name: users[i].username + " (You)", id: users[i].id };
else
prop = { name: users[i].username, id: users[i].id };
collaboratorTable.addData([prop], false);
}
}
break;
case "chatmessage":
{
let text = "";
text += '<div><span style="color:green;">' + msg.user + '</span>: ' + msg.message + '</div>';
$("#chatmessages").append(text);
$("#chatmessages").scrollTop($("#chatmessages").height() + 100);
}
break;
}
Proxy Considerations and running on Port 80/443
It is straightforward to proxy the websocket traffic of the collaboration server to a different port/url if you are running the server standalone and all your traffic has to go through a standard port. However, if you are using our streaming server or have another websocket-based service running on that same standard port, it will not be straightforward to differentiate the traffic and one of those services will most likely fail. In that case, you should either run the collaboration server on different port or on a different IP address altogether. If you are running the collaboration server on a different ip address/port from your webserver you need to specify its URL in the client during initialization.
Using Plugins
Plugins are simply classes or a collection of functions that extend the functionality of the collaboration library. Two plugins are already part of this project, one for displaying camera widgets if cameras are not synchronized, and the other for synchronizing the new TextBox markup type, recently released here. You can find those plugins in the dev/public/js/collabPlugins folder of this project. They are both integrated with the demo application.
To write a plugin you have to register a callback with registerMessageReceivedCallback() and then handle your own custom types, including synchronizing startup state, etc. Looking through the code of the demo and the two existing plugins for cameraWidgets and Textbox markup (which can be found in the collabPlugins directory) should give you a good idea of how to do this.
Disclaimer
This library is not an officially supported part of HOOPS Communicator and provided as-is.
Acknowledgments
Library:
Demo:
API
Server
start
Description
Starts the collabration server
Parameters
- server - The server object.
- config - A Configuration object. (optional)
Return Value
the socket.io server object
Example
const http = require('http');
const collab = require('ts3d-hc-collabserver');
const app = express();
var server;
server = http.createServer(app);
let io = collab.start(server, {allowUserRooms: true});
server.listen(3001);
You can provide an http or https server object to the start function. If you need additional authentication you can leverage the socket.io server object returned by this function to run additional middleware. Please see this page for more information on how to do this.
The second parameter to this function is the JSON configuration object. Currently it only supports the "allowUserRooms" property. If set to true, users can create their own rooms from the client. If set to false, only rooms that have been created by the server admin will be available. The default value is true.
createRoom
Description
Creates a new room on the server.
Parameters
- name - The name of the room
- password - The password for the room. (optional)
Return Value
None
Example
collab.createRoom("default", "1234");
Rooms created on the server are permanent and will be available until deleted by the server, while rooms created from the client will be deleted when no clients are connected.
deleteRoom
Description
Deletes an existing room on the server.
Parameters
- name - The name of the room
Return Value
None
Example
collab.deleteRoom("default");
Client
initialize
Description
Initializes the collaboration library. This function should be called only once as soon as the modelStructureReady callback has triggered in the viewer.
Parameters
- viewer - The webviewer object.
- ui - The standard webviewer ui object (optional).
- url - The url to the collab server (only needed if the collab server is not running on the same host as the client). (optional)
Return Value
None
Example
async function modelStructureReady() {
hcCollab.initialize(hwv, ui);
}
If you provide the UI object, the collaboration library will sync opening and closing the model browser with other clients. More UI specific syncing might be added in a future version of the library.
connect
Description
Connects the local client to the collaboration server.
Parameters
- roomname - The name of the room to connect to. If the room does not exist and client-side room creation is allowed it will be created.
- username - The name of the connecting user.
- password - The password to the room if necessary (optional).
Return Value
None
Example
hcCollab.connect("default", "User", "1234");
disconnect
Description
Disconnects the local client from the collaboration server.
Parameters
None
Return Value
None
Example
hcCollab.disconnect();
registerMessageReceivedCallback
Description
Registers a callback function that will be executed when a new collaboration message has been received.
Parameters
- callback - Callback function that receives collaboration messages
Return Value
None
Example
hcCollab.registerMessageReceivedCallback(function msgReceived(msg) {
console.log(msg);
});
getActive
Description
Returns if the local user is currently connected to the collaboration server.
Parameters
None
Return Value
True if the local user is connected to the collaboration server, false otherwise.
getLocalUser
Description
Returns the name, id and color of the local user.
Parameters
None
Return Value
Local User Object
Example
let localUser = hcCollab.getLocalUser();
console.log(localUser.id + ":" + localUser.name);
getUserInfo
Description
Returns information about a connected user
Parameters
- id - Id of connected user
Return Value
User Object
getUsers
Description
Returns an array with information about all users
Parameters
None
Return Value
Array of User Info
lockSession
Description
Locks a session (if not already locked) and prevents other users from interacting with the model
Parameters
Return Value
None
unlockSession
Description
Unlocks a session (if not already unlocked and locked by the local user) and allows other users to interact with the model
Parameters
Return Value
None
getLockedMaster
Description
Returns true if the local user has locked the session.
Parameters
None
Return Value
True if the local user has locked the session, false otherwise.
getLockedClient
Description
Returns true if the session has been locked by another user.
Parameters
None
Return Value
True if the session has been locked by another user, false otherwise.
submitChat
Description
Submits a chat message to the collaboration server.
Parameters
- text - The text to send to other connected clients.
Return Value
None
Example
hcCollab.submitChat("Hello World");
sendCustomMessage
Description
Sends a custom message to the collaboration server.
Parameters
- message - The message to send to other connected clients.
Return Value
None
Example
hcCollab.sendCustomMessage({ customType: "test",text: "Hello" });
The content of the custom messags is completely arbitrary, any valid JSON object that can be stringified is allowed. Just make sure that you don't define type
and user
which will both be set by the library before the message is send. In the above example, customType
and text
are just examples.
setSuspendSend
Description
Suspend or unsuspends sending of webviewer collaboration messages to the collaboration server.
Parameters
- enable - True to suspend sending, false to unsuspend.
Return Value
None
getSuspendSend
Description
Returns true if sending of webviewer collaboration messages to the collaboration server is currently suspended.
Parameters
None
Return Value
True if sending of webviewer collaboration messages to the collaboration server is currently suspended, false otherwise.
getInternalSuspend
Description
Returns true if the internal suspend state is set
Parameters
None
Return Value
True, if the internal suspend state is set, false otherwise.
getRoomData
Description
Returns the room data JSON object
Parameters
None
Return Value
Room Data JSON Object
updateRoomData
Description
Sets individiual room data properties. Only the specified properties will be replaced.
Parameters
The room data JSON object.
Return Value
None
setSyncCamera
Description
Enables/disables camera syncing between clients.
Parameters
- enable - True to enable camera syncing, false to disable.
Return Value
None
getSyncCamera
Description
Returns true if camera syncing is enabled.
Parameters
None
Return Value
True if camera syncing is enabled, false otherwise.
setSyncSelection
Description
Enables/disables selection syncing between clients.
Parameters
- enable - True to enable selection syncing, false to disable.
Return Value
None
getSyncSelection
Description
Returns true if selection syncing is enabled.
Parameters
None
Return Value
True if selection syncing is enabled, false otherwise.