Skip to content

Commit

Permalink
feat!: Refactored topic structure for more granular flow and access (#…
Browse files Browse the repository at this point in the history
…652)

* WIP: topics list

* Set topics as strings for custom String.formatStr

* Remove vestigial vio publishing

* Refactor arena-camera to use SCENE_CAMERA topic

- Move query selects to ready if possible
- Partially init topic on ready, use live camName
- use global ARENA

* Consolidate user camera and user hand topics

* Refactor arena-hand to use SCENE_USER topic

- Consolidate this.arena to just global ARENA

* Refactor mqtt and worker to use defined topics

- Once again simplify this.arena to just use ARENA global
- Pass subscriptions list instead of renderTopic

TODO: Last will is still set to delete just the camera object, we need to
      add support for this new /x/ user presence subtopic keyed on user id

* Refactor out renderTopic, used prev for write check

We now assume that a user who has write access can do so to any public
topic message category.

* Restore config "realm" into topic templates

* Set entity attr for new `private` top level param

* Refactor aabb collider for new topics

- Dispatches event msgs to one of 3 possible destination topics:
  1. public user
  2. private to the collided entity itself
  3. private to the collided entity's program designation

* Refactor gesture-detector for new scene topic

- fix: Use camName not 'my-camera' for object_id
- Init vars for topic, camera pos alias

* Refactor click-listener with new topics

* Refactor collision listener topics

* Refactor ar-hit-test-listener topics

- Set 'object_id' as 'scene'

* Add nonuser offset for renderpriv and debug topics

* Use new ns/scene scoped debug topic

* Refactor UI card, buttons, text-input topics

* Update facetracker topic

TODO: Add face_{idTag} pub perm

* Refactor build3d publish topics

* Cleanup unused outputTopic, etc

* Refactor chat topics

* Add reusable ARENA.topicParams

* Refactor mesh/plane publisher

* Refactor apriltag publish, set priv env topic

* Update runtime topics

* Refactor build topic publishes

* fix: Topic to/from order for scene

- This DIFFERS from chat (pending rework of chat channels)
- The users own id is always token in positional index 5

* fix: var name for sceneObj private

* Add positional tokens to topic constants

* Use defined positional tokens in mqtt topic split

* Add topic uuid validation for json msgs on worker

- only applicable for json msgs
- use `uuid`,  `topicUuid` for consistency
- Remove now redundant main thread validation for create/update/delete

* Don't validate chat topics on worker

- Chat currently has a different positional structure vs scene,
  TBD if its worth declaring separate token sets or just leave this one
  off in the chat system msg handler

* Consolidate camName into idTag

- We already know a camera is a camera by object_type in data, the camera
  is implicitly the "main" object representation of a user
- This removes the duality of maintaining and targeting both `camera_IDTAG`
  object vs the idTag of a user.
- This greatly simplifies topics as result
- We still keep username and display name separate

* Add getUsernameFromIdTag util

* Consolidate chat under scene context

- This constrains chat msgs and presence to scene vs namespace, an
  improvement in privacy
- Chat no longer requires explicit subs, inherits default pub/private
  subscriptions from general scene
- As camName is deprecated, this gives both topic source validation on
  `(objectId === idTag)` as well as `toUid`'s solely limited to `idTag`.
  The corresponding to/from msg fields are no longer explicitly
  required (and no longer spoofable as well)
- Message and queue handler validation now can be customized on field name,
  default is still `object_id`

* Add private scene presence topic

- This should replace the announce-to-all behavior when a
  new user joins (such that announces scale at n vs n^2)

* WIP: start splitting chat and presence

- Set out pub and priv chat and presence topics
- client lastwill now will announce to /x/
- remove interval keepalive
- remove onconnect and disconnect callbacks in chat
- Rename some callbacks names to reflect jitsi

* WIP: Dispatch sceneMsg handler by scenemsg type

- Wire chat, user and scene obj handlers
- TODO: handle /x/ msgs (put in chat.js or...?)

* Add SCENE_MSGTYPES to constants

* Default cameras to TTL in 30s

This should remove ghost clients that fail to lastwill

* Add presence message handling, cleanup chat

- Consolidate liveUserList updates
- Presence 'leave' now triggers object delete/blip
- All arena-cameras now default to 30s ttl
- All camera updates extend presence expiration and displayname
- Remove vestigial scene checks in chat

* fix: various chat/mqtt/components

- Update displayname on setting change
- add object_id -> id remap for user msghandler
- bind callback in init
- delete with delete handler
- specify user types in outgoing presence msg
- move self announce join after username set
- Remove call to deprecated connect in chat
- handle undef global ARENA in topics
- topic constant
- rename callback initialized to isReady
  This collides with AFRAME internal initialized bool for components
  used to track dependency states
- add referenced components as dependencies

* fix: query selector for dep

* Remove btoa suffix on chat idTag

* There is no try, only do

* fix: typod var for scenename

* remove unused imports

* Cleanup ARENA and cameraName refs in jitsi

* Further remove un, consolidate as dn in chat

- username is given by topic object_id
- displayname is what should be text rendered all
  the time

* fix: properly handle userlist update from jitsi

* fix: ttl single property per doc examples

* fix: chat msg payload

* fix: chat touid/displayname

* fix: loadsceneobjs call new scene obj handlers

* revert: don't set component deps
- Based on AFRAME behavior, this assumes these components will
  be set on the same el, which is not intended behavior

* fix: delay topics set for box collide

* Handle program_id object flags

* refactor: all clientEvent message structure

- object_id is always the actor, either the user idTag, or a hand.
- data.source is deprecated, since it's implicit and ACL controlled as the object_id
- data.position is ambiguous and thus deprecated, no longer required field
- data.target is always the target object ID
- data.targetPosition is always the relevant position on the target object
- data.originPosition is always the actor's position

* Update clientEvent handler

* fix: typo

* Add private and program_id to build schema

* fix: consistency of ttl usage

* Use timestamp instead of additl from_time field

* fix: imports for build

* Add private and program_id fields to build schemas

* Add formatStr proto func for build/main

* Update device pub/sub topics

* update client rest urls for new account api

* adding some notes for refactor @EdwardLu2018

* update build ui check with topic template

* update topics todos

* fixed isUsersPermitted using old topics

* fix build3d MutiationObserver this.TopicBase scope crash

* enable chat/message ui with jwt check

* fix errors in user presense permission checks

* also disable chat in build3d

* lint/format: autofix

* fix: typo on privPres pub topic

* Add MQTT_SUBSCRIBED event for sensitive msg timing

* fix: proxied func can't be in obj

* lint update

* fix(build): fixed incorrect ns/scene ids

* fix(build): identify build client on mqtt bus

* Move JITSI_EVENTS.CONNECTED to arena eventmanager

- This should be a one-time thing that happens, which may result
  in a race condition as seen in the chatui

* fix chat display from merge

* fix: ttl schema usage, time-extending behavior

* Add jitter to arena-camera TTL to force update()

* Revert previous ttl schema with nested seconds

- This should always trigger an update from aframe lifecycle
- Removes need to jitter arena-camera ttl value

* Add description note for TTL re: top level prop

* update build subscribe to new schema

* render fusion draft

* update remote render pub/sub

* update clientEvent and it's deprecated fields

* update screenshare for non-owners

* update docs with new topics

* add temp subs for render fusion

* fixes ttl seconds expression, all consumers and producers are expecting ttl: 30 and not ttl: { seconds: 30 }

* object program id tags no longer required

* feat: runtime mngr using new topics

* fix: remove tmp formatStr definition

* cleanup logging

---------

Co-authored-by: mwfarb <mwfarb@andrew.cmu.edu>
Co-authored-by: Nuno Pereira <nampereira@gmail.com>
  • Loading branch information
3 people authored Oct 14, 2024
1 parent fe2eb73 commit 03b9705
Show file tree
Hide file tree
Showing 51 changed files with 1,100 additions and 837 deletions.
10 changes: 5 additions & 5 deletions build/arena-account.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ export default class ARENAUserAccount {
* @return {UserAccountData} object with user account data
*/
static async userAuthState() {
return ARENAUserAccount._makeRequest('GET', `/user/user_state`);
return ARENAUserAccount._makeRequest('GET', `/user/v2/user_state`);
}

/**
* Request scene names which the user has permission to from user database
* @return {[string]} list of scene names
*/
static async userScenes() {
return ARENAUserAccount._makeRequest('GET', '/user/my_scenes');
return ARENAUserAccount._makeRequest('GET', '/user/v2/my_scenes');
}

/**
Expand All @@ -80,15 +80,15 @@ export default class ARENAUserAccount {
static async requestUserNewScene(sceneNamespace, isPublic = false) {
const params = new FormData();
// TODO: add public parameter
return ARENAUserAccount._makeRequest('POST', `/user/scenes/${sceneNamespace}`);
return ARENAUserAccount._makeRequest('POST', `/user/v2/scenes/${sceneNamespace}`);
}

/**
* Request to delete scene permissions from user db
* @param {string} sceneNamespace name of the scene without namespace
*/
static async requestDeleteUserScene(sceneNamespace) {
return ARENAUserAccount._makeRequest('DELETE', `/user/scenes/${sceneNamespace}`);
return ARENAUserAccount._makeRequest('DELETE', `/user/v2/scenes/${sceneNamespace}`);
}

/**
Expand All @@ -105,7 +105,7 @@ export default class ARENAUserAccount {
// pages /scenes and /build should have general permissions for the user's scene objects
const result = await ARENAUserAccount._makeRequest(
'POST',
`/user/mqtt_auth`,
`/user/v2/mqtt_auth`,
params,
'application/x-www-form-urlencoded'
);
Expand Down
15 changes: 13 additions & 2 deletions build/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/* eslint-disable import/extensions */
import * as PersistObjects from './persist-objects.js';
import ARENAUserAccount from './arena-account.js';
import TOPICS from '../src/constants/topics.js';

const Alert = Swal.mixin({
toast: true,
Expand Down Expand Up @@ -853,7 +854,7 @@ window.addEventListener('onauth', async (e) => {
// add scene if it does not exist
if (newScene) await PersistObjects.addNewScene(namespaceinput.value, sceneinput.value, undefined);

await PersistObjects.addObject(obj, `${namespaceinput.value}/${sceneinput.value}`);
await PersistObjects.addObject(obj, namespaceinput.value, sceneinput.value);
setTimeout(async () => {
PersistObjects.populateObjectList(
`${namespaceinput.value}/${sceneinput.value}`,
Expand Down Expand Up @@ -1036,7 +1037,11 @@ Swal.fire({
* @param {string} mqttToken
*/
function updatePublishControlsByToken(namespace, scenename, mqttToken) {
const objectsTopic = `realm/s/${namespace}/${scenename}`;
const objectsTopic = TOPICS.PUBLISH.SCENE_OBJECTS.formatStr({
nameSpace: namespace,
sceneName: scenename,
objectId: '+',
});
const editor = ARENAAUTH.isUserSceneEditor(mqttToken, objectsTopic);
const delButton = document.getElementById('delobj');
const deleteSceneButton = document.getElementById('deletescene');
Expand All @@ -1051,3 +1056,9 @@ function updatePublishControlsByToken(namespace, scenename, mqttToken) {
item.disabled = !editor;
});
}

// eslint-disable-next-line no-extend-native
String.prototype.formatStr = function formatStr(...args) {
const params = arguments.length === 1 && typeof args[0] === 'object' ? args[0] : args;
return this.replace(/\{([^}]+)\}/g, (match, key) => (typeof params[key] !== 'undefined' ? params[key] : match));
};
3 changes: 1 addition & 2 deletions build/mqtt-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ export default class MqttClient {
host: st.host !== undefined ? st.host : 'arena.andrew.cmu.edu',
port: st.port !== undefined ? st.port : 8083,
path: st.path !== undefined ? st.path : '/mqtt/',
clientid:
st.clientid !== undefined ? st.clientid : `build-client-${Math.round(Math.random() * 10000)}`,
clientid: st.clientid !== undefined ? st.clientid : `build-client-${Math.round(Math.random() * 10000)}`,
subscribeTopics: st.subscribeTopics,
onConnectCallback: st.onConnectCallback,
onConnectCallbackContext: st.onConnectCallbackContext,
Expand Down
22 changes: 17 additions & 5 deletions build/persist-objects.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* global Alert, ARENAAUTH, ARENADefaults, Swal, THREE */
/* eslint-disable import/extensions */
import TOPICS from '../src/constants/topics.js';
import MqttClient from './mqtt-client.js';
import ARENAUserAccount from './arena-account.js';
import { TTLCache } from './ttl-cache.js';
Expand Down Expand Up @@ -369,7 +370,10 @@ export function updateSubscribeTopic(scene) {

if (persist.currentScene) persist.mc.unsubscribe(persist.currentScene);
if (scene) {
const topic = `realm/s/${scene}/#`;
const topic = TOPICS.SUBSCRIBE.SCENE_PUBLIC.formatStr({
nameSpace: scene.split('/')[0],
sceneName: scene.split('/')[1],
});
persist.mc.subscribe(topic);
persist.currentScene = scene;
populateProgramInstanceList();
Expand Down Expand Up @@ -556,7 +560,7 @@ export async function addNewScene(ns, sceneName, newObjs) {

// add objects to the new scene
newObjs.forEach((obj) => {
addObject(obj, `${ns}/${sceneName}`);
addObject(obj, ns, sceneName);
});
return exists;
}
Expand Down Expand Up @@ -604,7 +608,11 @@ export function performActionArgObjList(action, scene, objList, json = true) {
scene = `${obj.namespace}/${obj.sceneId}`;
theNewScene = obj.sceneId;
}
const topic = `realm/s/${scene}/${obj.object_id}`;
const topic = TOPICS.PUBLISH.SCENE_OBJECTS.formatStr({
nameSpace: theNewScene.split('/')[0],
sceneName: theNewScene.split('/')[1],
objectId: obj.object_id,
});
console.info(`Publish [ ${topic}]: ${actionObj}`);
try {
persist.mc.publish(topic, actionObj);
Expand Down Expand Up @@ -634,7 +642,7 @@ export function clearSelected() {
}
}

export async function addObject(obj, scene) {
export async function addObject(obj, nameSpace, sceneName) {
let found = false;
if (!persist.mqttConnected) mqttReconnect();

Expand Down Expand Up @@ -674,7 +682,11 @@ export async function addObject(obj, scene) {

const persistAlert = obj.persist === false ? '<br/><strong>Object not persisted.</strong>' : '';
const objJson = JSON.stringify(obj);
const topic = `realm/s/${scene}/${obj.object_id}`;
const topic = TOPICS.PUBLISH.SCENE_OBJECTS.formatStr({
nameSpace,
sceneName,
objectId: obj.object_id,
});
console.info(`Publish [ ${topic}]: ${objJson}`);
try {
persist.mc.publish(topic, objJson);
Expand Down
6 changes: 3 additions & 3 deletions build/schemas/arena-program.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
},
"args": {
"type": "array",
"description": "Command-line arguments (passed in argv). Supports variables: ${scene}, ${mqtth}, ${cameraid}, ${username}, ${runtimeid}, ${moduleid}, ${query-string-key}.",
"description": "Command-line arguments (passed in argv). Supports variables: ${scene}, ${mqtth}, ${userid}, ${username}, ${runtimeid}, ${moduleid}, ${query-string-key}.",
"uniqueItems": false,
"items": {
"type": "string",
Expand All @@ -71,7 +71,7 @@
},
"env": {
"type": "array",
"description": "Environment variables. Supports variables: ${scene}, ${namespace}, ${mqtth}, ${cameraid}, ${username}, ${runtimeid}, ${moduleid}, ${query-string-key}.",
"description": "Environment variables. Supports variables: ${scene}, ${namespace}, ${mqtth}, ${userid}, ${username}, ${runtimeid}, ${moduleid}, ${query-string-key}.",
"uniqueItems": false,
"items": {
"type": "string",
Expand Down Expand Up @@ -136,7 +136,7 @@
"type": "pubsub",
"mode": "rw",
"params": {
"topic": "realm/s/${scene}"
"topic": "realm/s/${scene}/${namespace}"
}
}
]
Expand Down
11 changes: 10 additions & 1 deletion build/schemas/definitions-arena-object.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,16 @@
},
"ttl": {
"description": "When applied to an entity, the entity will remove itself from DOM after the specified number of seconds. Update is allowed, which will reset the timer to start from that moment.",
"type": "integer"
"type": "number"
},
"private": {
"description": "If true, interactions with this object should not be broadcasted to other clients, but rather sent on private topics",
"type": "boolean",
"default": false
},
"program_id": {
"description": "The program_id on private program topics that interactions to be directed to, if the private flag is set true. Ignored if private flag is false.",
"type": "string"
}
}
}
31 changes: 23 additions & 8 deletions build/schemas/event.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,42 @@
"properties": {
"data": {
"properties": {
"source": {
"title": "Source ID",
"description": "The `object_id` of event origination. e.g camera or client program connection id.",
"target": {
"description": "The `object_id` of event destination.",
"type": "string"
},
"position": {
"targetPosition": {
"description": "The event destination position in 3D.",
"format": "grid",
"$ref": "#/definitions/vector3",
"title": "Position"
"$ref": "#/definitions/vector3"
},
"clickPos": {
"originPosition": {
"description": "The event origination position in 3D.",
"format": "grid",
"$ref": "#/definitions/vector3",
"title": "Click Position",
"default": {
"x": 0,
"y": 1.6,
"z": 0
}
},
"source": {
"title": "DEPRECATED (source)",
"description": "DEPRECATED: data.source is deprecated for clientEvent, use data.target instead.",
"type": "string",
"deprecated": true
},
"position": {
"title": "DEPRECATED (position)",
"description": "DEPRECATED: data.position is deprecated for clientEvent, use data.targetPosition instead.",
"type": "object",
"deprecated": true
},
"clickPos": {
"title": "DEPRECATED (clickPos)",
"description": "DEPRECATED: data.clickPos is deprecated for clientEvent, use data.originPosition instead.",
"type": "object",
"deprecated": true
}
},
"title": "Event Data",
Expand Down
2 changes: 1 addition & 1 deletion files/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function getStorePath() {
}

window.addEventListener('onauth', async (e) => {
await ARENAAUTH.makeUserRequest('GET', '/user/storelogin');
await ARENAAUTH.makeUserRequest('GET', '/user/v2/storelogin');
const authToken = ARENAAUTH.getCookie('auth');
loadStoreFront(authToken);
});
8 changes: 4 additions & 4 deletions navbar-old.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ $(document).ready(() => {
// add page header
$('#header').load('/header-old.html', () => {
// update auth state in nav bar
fetch('/user/user_state')
fetch('/user/v2/user_state')
.then((response) => response.json())
.then((data) => {
window.auth = data;
Expand All @@ -16,7 +16,7 @@ $(document).ready(() => {
dropdownMenu.append('<li><a href="/conf/versions.html">Version</a></li>');
if (window.auth.authenticated) {
authDrop.html(`${window.auth.username} <b class='caret'></b>`);
dropdownMenu.append('<li><a href="/user/profile">Profile</a></li>');
dropdownMenu.append('<li><a href="/user/v2/profile">Profile</a></li>');
dropdownMenu.append('<li><a id="show_perms" href="#">Permissions</a></li>');
$('#show_perms').on('click', () => {
const frame = document.getElementsByTagName('iframe');
Expand All @@ -27,10 +27,10 @@ $(document).ready(() => {
alert('No MQTT permissions');
}
});
dropdownMenu.append('<li><a href="/user/logout">Logout</a></li>');
dropdownMenu.append('<li><a href="/user/v2/logout">Logout</a></li>');
} else {
authDrop.html('Login <b class="caret"></b>');
dropdownMenu.append('<li><a href="/user/login">Login</a></li>').on('click', (e) => {
dropdownMenu.append('<li><a href="/user/v2/login">Login</a></li>').on('click', (e) => {
localStorage.setItem('request_uri', window.location.href);
});
}
Expand Down
8 changes: 4 additions & 4 deletions navbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ $(document).ready(() => {
// add page header
$('#header').load('/header.html', () => {
// update auth state in nav bar
fetch('/user/user_state')
fetch('/user/v2/user_state')
.then((response) => response.json())
.then((data) => {
window.auth = data;
Expand All @@ -20,7 +20,7 @@ $(document).ready(() => {
dropdownMenu.append('<li><a class="dropdown-item" href="/conf/versions.html">Version</a></li>');
if (window.auth.authenticated) {
authDrop.html(window.auth.username);
dropdownMenu.append('<li><a class="dropdown-item" href="/user/profile">Profile</a></li>');
dropdownMenu.append('<li><a class="dropdown-item" href="/user/v2/profile">Profile</a></li>');
dropdownMenu.append('<li><a class="dropdown-item" id="show_perms" href="#">Permissions</a></li>');
$('#show_perms').on('click', () => {
const frame = document.getElementsByTagName('iframe');
Expand All @@ -31,11 +31,11 @@ $(document).ready(() => {
window.alert('No MQTT permissions');
}
});
dropdownMenu.append('<li><a class="dropdown-item" href="/user/logout">Logout</a></li>');
dropdownMenu.append('<li><a class="dropdown-item" href="/user/v2/logout">Logout</a></li>');
} else {
authDrop.html('Login');
dropdownMenu
.append('<li><a class="dropdown-item" href="/user/login">Login</a></li>')
.append('<li><a class="dropdown-item" href="/user/v2/login">Login</a></li>')
.on('click', (e) => {
localStorage.setItem('request_uri', window.location.href);
});
Expand Down
6 changes: 3 additions & 3 deletions scenes/scenes.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ window.addEventListener('onauth', async (e) => {
window.userSceneId = _e.target.value;
updateUriBuilderCheckboxes(true);
updateUserSceneUrlBox(`${window.location.origin}/${_e.target.value}`);
scenePermsLink.href = `${window.location.origin}/user/profile/scenes/${_e.target.value}`;
scenePermsLink.href = `${window.location.origin}/user/v2/profile/scenes/${_e.target.value}`;
deleteUserSceneBtn.value = _e.target.value;
toggleUserSceneButtons(true);
} else {
Expand Down Expand Up @@ -229,7 +229,7 @@ window.addEventListener('onauth', async (e) => {
deleteUserSceneBtn.addEventListener('click', () => {
if (confirm(`Are you sure you want to delete ${deleteUserSceneBtn.value}?`)) {
const deletes = [
axios.delete(`/user/scenes/${deleteUserSceneBtn.value}`, {
axios.delete(`/user/v2/scenes/${deleteUserSceneBtn.value}`, {
withCredentials: true,
}),
axios.delete(`/persist/${deleteUserSceneBtn.value}`),
Expand Down Expand Up @@ -327,7 +327,7 @@ window.addEventListener('onauth', async (e) => {
// my_scenes may include 'public' namespaces for staff
// my_scenes may include other editor namespaces that have been granted
axios
.get('/user/my_scenes', {
.get('/user/v2/my_scenes', {
withCredentials: true,
})
.then((res) => {
Expand Down
4 changes: 2 additions & 2 deletions screenshare/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ function onConnectionSuccess() {
});

conference.setDisplayName(
`${(+new Date()).toString(36)} ${window.params.screenSharePrefix}_${window.params.camName}`
`${(+new Date()).toString(36)} ${window.params.screenSharePrefix}_${window.params.idTag}`
);
conference.setLocalParticipantProperty('screenshareDispName', window.params.displayName);
conference.setLocalParticipantProperty('screenshareCamName', window.params.camName);
conference.setLocalParticipantProperty('screenshareidTag', window.params.idTag);
conference.setLocalParticipantProperty('screenshareObjIds', window.params.objectIds);

conference.join();
Expand Down
Loading

0 comments on commit 03b9705

Please sign in to comment.