diff --git a/package.json b/package.json index 9c2738dab4..a7c95c0d0c 100755 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "make-user-admin": "cross-env ts-node --swc scripts/make-user-admin.ts", "migrate": "cd packages/server-core && npm run migrate", "migrate:rollback": "cd packages/server-core && npm run migrate:rollback", + "migrate:unlock": "cd packages/server-core && npm run migrate:unlock", "postinstall": "patch-package --exclude ./node_modules/@mui/", "precommit": "no-master-commits -b master", "prepare-database": "cross-env APP_ENV=production PREPARE_DATABASE=true EXIT_ON_DB_INIT=true ts-node --swc packages/server/src/index.ts", diff --git a/packages/client-core/i18n/en/admin.json b/packages/client-core/i18n/en/admin.json index 6584f152b0..928cb451c4 100755 --- a/packages/client-core/i18n/en/admin.json +++ b/packages/client-core/i18n/en/admin.json @@ -355,6 +355,7 @@ "homepageLinkButtonEnabled": "Show link button on homepage", "homepageLinkButtonRedirect": "URL of homepage link button", "homepageLinkButtonText": "Text of homepage link button", + "privacyPolicy": "Privacy Policy", "taskServer": { "taskServer": "Task Server", "port": "Port", diff --git a/packages/client-core/i18n/en/editor.json b/packages/client-core/i18n/en/editor.json index 7ed08ab631..f797e332c8 100755 --- a/packages/client-core/i18n/en/editor.json +++ b/packages/client-core/i18n/en/editor.json @@ -99,6 +99,36 @@ "toolbar": { "lbl-publish": "Publish", "lbl-published": "Published", + "gizmo": { + "description": "Transform Gizmo", + "translate": "[T] Translate", + "rotate": "[R] Rotate", + "scale": "[S] Scale" + }, + "transformSpace": { + "description": "Sets your transform control to be oriented to the object(selection) or world", + "lbl-world": "World", + "info-world": "World space relates to the entire scene’s orientation", + "lbl-selection": "Object", + "info-selection": "Object Space (your selection) relates to transforms made to a specific object in relationship to the world space", + "lbl-toggleTransformSpace": "[Z] Toggle Transform Space" + }, + "transformPivot": { + "toggleTransformPivot" : "[X] Toggle Transform Pivot", + "lbl-selection": "Selection", + "info-selection": "The center pivot of the final asset you selected in the sequence.", + "lbl-center": "Center", + "info-center": "A pivot that sits at an equal distance between all selections.", + "lbl-bottom": "Bottom", + "info-bottom": "A pivot that sits equally between all selections and at the bottom of your final selection in the sequence.", + "lbl-origin": "Origin", + "info-origin": "Sets pivot mode to the world origin (0,0,0)" + }, + "transformSnapTool": { + "toggleSnapMode": "[C] Toggle Snap Mode", + "info-translate": "Translate objects by a unit of measurement.", + "info-rotate": "Rotate objects by a specific degrees." + }, "command": { "translate": "Translate", "rotate": "Rotate", @@ -109,8 +139,10 @@ "toggleGrid": "Toggle Grid Visibility", "increaseGridSize": "Increase Grid Size", "descreaseGridSize": "Decrease Grid Size", - "stopPreview": "Stop Previewing Scene", - "playPreview": "Preview Scene" + "lbl-stopPreview": "Stop Previewing Scene", + "info-stopPreview": "Remove your avatar from scene", + "lbl-playPreview": "Preview Scene", + "info-playPreview": "Spawns you into the scene" }, "instance": { "none": "None", @@ -118,6 +150,8 @@ "user": "User" }, "render-settings":{ + "lbl": "Render Settings", + "info": "How you view materials in the Engine", "lbl-usePostProcessing":"Use Post Processing", "info-usePostProcessing":"Enables post processing", "lbl-shadowMapResolution":"Shadow Map Resolution", @@ -126,12 +160,29 @@ "info-renderMode":"Choose render mode" }, "grid": { + "info-toggleGridVisibility": "Toggle Grid Visibility", + "info-gridSpacing": "Set grid spacing meters", "info-incrementHeight": "Increment Grid Height", "info-decrementHeight": "Decrement Grid Height" + }, + "stats": { + "lbl": "Toggle Stats", + "info": "Show stats about the scene and gives you a clue as to how optimized your scene is." + }, + "helpersToggle": { + "lbl-helpers": "Toggle Helpers", + "info-helpers": "View hidden information about your scene, ie: colliders", + "lbl-nodeHelpers": "Toggle Node Helpers", + "info-nodeHelpers": "Helper geometry that helps components have visibility in the scene when inactive." + }, + "sceneScreenshot": { + "lbl": "Screenshot", + "info": "Takes a screenshot of your scene at the current view." } }, "properties": { "title": "Properties", + "info": "Propeties let you access and edit detailed information about objects in your scene.", "lbl-visible": "Visible", "lbl-preventBake": "Prevent Bake", "lbl-dynamicLoad": "Load Children Dynamically", @@ -746,7 +797,33 @@ "placeObjectAtOrigin": "Place Object at Origin", "copyURL": "Copy URL", "openInNewTab": "Open URL in New Tab", - "deleteAsset": "Delete Asset" + "deleteAsset": "Delete Asset", + "tooltip": { + "EE_model": "Creates objects in the hierarchy. Drag a model from the assets folder into the URL box or drag assets directly from project files into the hierarchy.", + "EE_volumetric": "Import volumetric files. Accepts DRCS, UVOL, or Manifest Files. Links to cloud hosting.", + "EE_video": "Imports a 2D plane that accepts .mp4, .mkv, .avi", + "EE_positionalAudio": "Import audio clips, .mp3, .flac, .ogg, .wav, .m4a", + "EE_image": "Imports an image into the scene", + "GroundPlaneComponent": "Create collision ground plane.", + "GroupComponent": "Collection of models or assets.", + "PrefabComponent": "Create prefabs from groups or objects that are saved to the assets folder. Saving requires specific naming: 'assetName'.xre.gltf", + "ColliderComponent": "Creates a collision ball, cuboid, capsule, or cylinder to be manually placement.", + "SpawnPointComponent": "A point where people will appear when they enter your scene.", + "PortalComponent": "A portal to teleport a player to a port in a different location.", + "AmbientLightComponent": "A combination of direct and indirect light, provides general lighting to all assets.", + "PointLightComponent": "A light which emits in all directions from a single point.", + "SpotLightComponent": "Creates a light that shines in a specific direction.", + "DirectionalLightComponent": "Creates a light that emits evenly in a single direction.", + "HemisphereLightComponent": "A light which illuminates the scene from directly overhead.", + "EE_ParticleSystem": "Creates a particle emitter.", + "SystemComponent": "Inserts code into the scene by creating a new Entity Component System based on the provided .ts file", + "EE_behaveGraph": "Customizes state and behavior of entities through a node graph connection.", + "EnvMapBakeComponent": "Add Env Mapbake to your scene.", + "EE_scenePreviewCamera": "A preview camera which generates a scene thumbnail and the starting position.", + "SkyboxComponent": "Sets the area outside your map (skybox) with a specific sky type.", + "SplineTrackComponent": "Creates a spline track.", + "SplineComponent": "Create and customize curves." + } }, "filebrowser": { "addNewFolder": "Add New Folder", @@ -770,6 +847,8 @@ } }, "hierarchy": { + "lbl": "Hierarchy", + "info": "The scene Hierarchy contains all element currently in your scene (assets, lighting, items from the tool menu, etc).", "lbl-rename": "Rename", "lbl-duplicate": "Duplicate", "lbl-group": "Group", @@ -777,8 +856,13 @@ "lbl-expandAll": "Expand All", "lbl-collapseAll": "Collapse All", "lbl-explode": "Explode Objects", + "lbl-addEntity": "Add Entity", "isseus": "Issues:" }, + "materialLibrary": { + "lbl": "Material Library", + "info": "Location of an assets materials and where you can select and edit them." + }, "dnd": { "nodes": "{{count}} Nodes Selected", "models": "{{count}} Models Selected", diff --git a/packages/client-core/i18n/en/user.json b/packages/client-core/i18n/en/user.json index 5b7890d76f..2da196b7e3 100755 --- a/packages/client-core/i18n/en/user.json +++ b/packages/client-core/i18n/en/user.json @@ -257,7 +257,8 @@ }, "userIdCopied": "User ID copied", "apiKeyCopied": "API Key copied", - "refreshApiKey": "Refresh API Key" + "refreshApiKey": "Refresh API Key", + "privacyPolicy": "Privacy Policy" }, "oauth": { "authenticating": "Authenticating...", diff --git a/packages/client-core/src/admin/common/variables/location.ts b/packages/client-core/src/admin/common/variables/location.ts index d28353c175..1f262740d8 100644 --- a/packages/client-core/src/admin/common/variables/location.ts +++ b/packages/client-core/src/admin/common/variables/location.ts @@ -23,6 +23,8 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' + export interface LocationColumn { id: 'sceneId' | 'maxUsersPerInstance' | 'scene' | 'name' | 'locationType' | 'tags' | 'videoEnabled' | 'action' label: string @@ -77,6 +79,6 @@ export interface LocationData { status: string location: string inviteCode: string - instanceId: string + instanceId: InstanceID action: any } diff --git a/packages/client-core/src/admin/components/Bots/CreateBot.tsx b/packages/client-core/src/admin/components/Bots/CreateBot.tsx index 9a7c9dcee7..fd3b934723 100644 --- a/packages/client-core/src/admin/components/Bots/CreateBot.tsx +++ b/packages/client-core/src/admin/components/Bots/CreateBot.tsx @@ -43,6 +43,7 @@ import Typography from '@etherealengine/ui/src/primitives/mui/Typography' import { useFind, useMutation } from '@etherealengine/engine/src/common/functions/FeathersHooks' import { BotData, botPath } from '@etherealengine/engine/src/schemas/bot/bot.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { locationPath } from '@etherealengine/engine/src/schemas/social/location.schema' import { NotificationService } from '../../../common/services/NotificationService' import { AuthState } from '../../../user/services/AuthService' @@ -68,7 +69,7 @@ const CreateBot = () => { const state = useHookstate({ name: '', description: '', - instance: '', + instance: '' as InstanceID, location: '' }) const user = useHookstate(getMutableState(AuthState).user) @@ -107,7 +108,7 @@ const CreateBot = () => { useEffect(() => { const instanceFilter = data.filter((el) => el.locationId === state.location.value) if (instanceFilter.length > 0) { - state.merge({ instance: '' }) + state.merge({ instance: '' as InstanceID }) currentInstance.set(instanceFilter) } else { currentInstance.set([]) @@ -117,7 +118,7 @@ const CreateBot = () => { const handleSubmit = () => { const data: BotData = { name: state.name.value, - instanceId: state.instance.value || '', + instanceId: state.instance.value || ('' as InstanceID), userId: user.id.value, botCommands: commandData.get({ noproxy: true }), description: state.description.value, @@ -132,7 +133,7 @@ const CreateBot = () => { if (validateForm(state.value, formErrors.value)) { createBotData(data) - state.set({ name: '', description: '', instance: '', location: '' }) + state.set({ name: '', description: '', instance: '' as InstanceID, location: '' }) commandData.set([]) currentInstance.set([]) } else { diff --git a/packages/client-core/src/admin/components/Bots/UpdateBot.tsx b/packages/client-core/src/admin/components/Bots/UpdateBot.tsx index 6529a2c09f..189a73bb17 100644 --- a/packages/client-core/src/admin/components/Bots/UpdateBot.tsx +++ b/packages/client-core/src/admin/components/Bots/UpdateBot.tsx @@ -40,6 +40,7 @@ import IconButton from '@etherealengine/ui/src/primitives/mui/IconButton' import { useFind, useMutation } from '@etherealengine/engine/src/common/functions/FeathersHooks' import { BotPatch, BotType, botPath } from '@etherealengine/engine/src/schemas/bot/bot.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { locationPath } from '@etherealengine/engine/src/schemas/social/location.schema' import { NotificationService } from '../../../common/services/NotificationService' import { AuthState } from '../../../user/services/AuthService' @@ -57,7 +58,7 @@ const UpdateBot = ({ open, bot, onClose }: Props) => { const state = useHookstate({ name: '', description: '', - instance: '', + instance: '' as InstanceID, location: '' }) const formErrors = useHookstate({ @@ -81,7 +82,7 @@ const UpdateBot = ({ open, bot, onClose }: Props) => { state.set({ name: bot?.name, description: bot?.description, - instance: bot?.instance?.id || '', + instance: bot?.instance?.id || ('' as InstanceID), location: bot?.location?.id || '' }) } @@ -127,18 +128,18 @@ const UpdateBot = ({ open, bot, onClose }: Props) => { useEffect(() => { const instanceFilter = data.filter((el) => el.locationId === state.location.value) if (instanceFilter.length > 0) { - state.merge({ instance: state.instance.value || '' }) + state.merge({ instance: state.instance.value || ('' as InstanceID) }) currentInstance.set(instanceFilter) } else { currentInstance.set([]) - state.merge({ instance: '' }) + state.merge({ instance: '' as InstanceID }) } }, [state.location.value, instancesData]) const handleUpdate = () => { const data: BotPatch = { name: state.name.value, - instanceId: state.instance.value || '', + instanceId: state.instance.value || ('' as InstanceID), userId: user.id.value, description: state.description.value, locationId: state.location.value @@ -152,7 +153,7 @@ const UpdateBot = ({ open, bot, onClose }: Props) => { if (validateForm(state.value, formErrors.value) && bot) { updateBot(bot.id, data) - state.set({ name: '', description: '', instance: '', location: '' }) + state.set({ name: '', description: '', instance: '' as InstanceID, location: '' }) currentInstance.set([]) onClose() } else { @@ -221,7 +222,7 @@ const UpdateBot = ({ open, bot, onClose }: Props) => { disableElevation type="submit" onClick={() => { - state.set({ name: '', description: '', instance: '', location: '' }) + state.set({ name: '', description: '', instance: '' as InstanceID, location: '' }) formErrors.set({ name: '', description: '', location: '' }) onClose() }} diff --git a/packages/client-core/src/admin/components/Instance/InstanceDrawer.tsx b/packages/client-core/src/admin/components/Instance/InstanceDrawer.tsx index 12c9a23299..2d992cd0e0 100644 --- a/packages/client-core/src/admin/components/Instance/InstanceDrawer.tsx +++ b/packages/client-core/src/admin/components/Instance/InstanceDrawer.tsx @@ -37,9 +37,10 @@ import Grid from '@etherealengine/ui/src/primitives/mui/Grid' import { useFind, useMutation } from '@etherealengine/engine/src/common/functions/FeathersHooks' import { instanceAttendancePath } from '@etherealengine/engine/src/schemas/networking/instance-attendance.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { userKickPath } from '@etherealengine/engine/src/schemas/user/user-kick.schema' import { UserID, userPath } from '@etherealengine/engine/src/schemas/user/user.schema' -import { toDateTimeSql } from '@etherealengine/server-core/src/util/get-datetime-sql' +import { toDateTimeSql } from '@etherealengine/server-core/src/util/datetime-sql' import ConfirmDialog from '../../../common/components/ConfirmDialog' import { NotificationService } from '../../../common/services/NotificationService' import DrawerView from '../../common/DrawerView' @@ -57,7 +58,7 @@ const INFINITY = 'INFINITY' const INSTANCE_USERS_PAGE_LIMIT = 10 -const useUsersInInstance = (instanceId: string) => { +const useUsersInInstance = (instanceId: InstanceID) => { const instanceAttendances = useFind(instanceAttendancePath, { query: { instanceId @@ -99,11 +100,11 @@ const InstanceDrawer = ({ open, selectedInstance, onClose }: Props) => { const openKickDialog = useHookstate(false) const kickData = useHookstate({ userId: '' as UserID, - instanceId: '', + instanceId: '' as InstanceID, duration: '8' }) - const instanceUsersQuery = useUsersInInstance(selectedInstance?.id ?? '') + const instanceUsersQuery = useUsersInInstance(selectedInstance?.id ?? ('' as InstanceID)) const kickUser = useKickUser() const createData = (id: UserID, name: string) => ({ diff --git a/packages/client-core/src/admin/components/Invite/AdminInvites.tsx b/packages/client-core/src/admin/components/Invite/AdminInvites.tsx index d1b963513f..395026c5e9 100755 --- a/packages/client-core/src/admin/components/Invite/AdminInvites.tsx +++ b/packages/client-core/src/admin/components/Invite/AdminInvites.tsx @@ -34,7 +34,7 @@ import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' import { useFind, useMutation } from '@etherealengine/engine/src/common/functions/FeathersHooks' import { InviteType, invitePath } from '@etherealengine/engine/src/schemas/social/invite.schema' -import { toDateTimeSql } from '@etherealengine/server-core/src/util/get-datetime-sql' +import { toDateTimeSql } from '@etherealengine/server-core/src/util/datetime-sql' import { INVITE_PAGE_LIMIT } from '../../../social/services/InviteService' import TableComponent from '../../common/Table' import { InviteColumn, inviteColumns } from '../../common/variables/invite' diff --git a/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx b/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx index 4b99172ce5..a988de0c98 100755 --- a/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx +++ b/packages/client-core/src/admin/components/Invite/CreateInviteModal.tsx @@ -50,7 +50,7 @@ import { useFind } from '@etherealengine/engine/src/common/functions/FeathersHoo import { InviteData } from '@etherealengine/engine/src/schemas/social/invite.schema' import { locationPath } from '@etherealengine/engine/src/schemas/social/location.schema' import { userPath } from '@etherealengine/engine/src/schemas/user/user.schema' -import { toDateTimeSql } from '@etherealengine/server-core/src/util/get-datetime-sql' +import { toDateTimeSql } from '@etherealengine/server-core/src/util/datetime-sql' import { NotificationService } from '../../../common/services/NotificationService' import { InviteService } from '../../../social/services/InviteService' import DrawerView from '../../common/DrawerView' diff --git a/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx b/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx index a03999ba3b..9c735e45a9 100755 --- a/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx +++ b/packages/client-core/src/admin/components/Invite/UpdateInviteModal.tsx @@ -51,7 +51,7 @@ import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' import { InvitePatch, InviteType, invitePath } from '@etherealengine/engine/src/schemas/social/invite.schema' import { locationPath } from '@etherealengine/engine/src/schemas/social/location.schema' import { userPath } from '@etherealengine/engine/src/schemas/user/user.schema' -import { toDateTimeSql } from '@etherealengine/server-core/src/util/get-datetime-sql' +import { toDateTimeSql } from '@etherealengine/server-core/src/util/datetime-sql' import { Id } from '@feathersjs/feathers' import { NotificationService } from '../../../common/services/NotificationService' import DrawerView from '../../common/DrawerView' diff --git a/packages/client-core/src/admin/components/Invite/index.tsx b/packages/client-core/src/admin/components/Invite/index.tsx index 793bddeaa4..6c8fe345c7 100755 --- a/packages/client-core/src/admin/components/Invite/index.tsx +++ b/packages/client-core/src/admin/components/Invite/index.tsx @@ -82,8 +82,7 @@ const InvitesConsole = () => { > {t('admin:components.invite.create')} - - {selectedInviteIds.value.size && ( + {selectedInviteIds.size.value > 0 && ( { const homepageLinkButtonEnabled = useHookstate(clientSetting?.homepageLinkButtonEnabled) const homepageLinkButtonRedirect = useHookstate(clientSetting?.homepageLinkButtonRedirect) const homepageLinkButtonText = useHookstate(clientSetting?.homepageLinkButtonText) + const privacyPolicyLink = useHookstate(clientSetting?.privacyPolicy) useEffect(() => { if (user?.id?.value != null && clientSettingState?.updateNeeded?.value === true) { @@ -97,6 +98,7 @@ const Client = () => { favicon32px.set(clientSetting?.favicon32px) siteDescription.set(clientSetting?.siteDescription) key8thWall.set(clientSetting?.key8thWall) + privacyPolicyLink.set(clientSetting?.privacyPolicy) homepageLinkButtonEnabled.set(clientSetting?.homepageLinkButtonEnabled) homepageLinkButtonRedirect.set(clientSetting?.homepageLinkButtonRedirect) homepageLinkButtonText.set(clientSetting?.homepageLinkButtonText) @@ -152,6 +154,7 @@ const Client = () => { themeSettings: clientSetting?.themeSettings, themeModes: clientSetting?.themeModes, key8thWall: key8thWall.value, + privacyPolicy: privacyPolicyLink.value, homepageLinkButtonEnabled: homepageLinkButtonEnabled.value, homepageLinkButtonRedirect: homepageLinkButtonRedirect.value, homepageLinkButtonText: homepageLinkButtonText.value @@ -379,6 +382,13 @@ const Client = () => { disabled /> + privacyPolicyLink.set(e.target.value)} + /> + ({ - instances: {} as { [id: string]: InstanceState } + instances: {} as { [id: InstanceID]: InstanceState } }) }) @@ -115,7 +115,7 @@ export const LocationInstanceConnectionService = { }, 1000) } }, - provisionExistingServer: async (locationId: string, instanceId: string, sceneId: string) => { + provisionExistingServer: async (locationId: string, instanceId: InstanceID, sceneId: string) => { logger.info({ locationId, instanceId, sceneId }, 'Provision Existing World Server') const token = getState(AuthState).authUser.accessToken const instance = (await API.instance.client.service('instance').find({ @@ -198,13 +198,13 @@ export const LocationInstanceConnectionService = { console.warn('Failed to connect to expected existing instance') } }, - changeActiveConnectionHostId: (currentInstanceId: UserID, newInstanceId: UserID) => { + changeActiveConnectionID: (currentInstanceId: InstanceID, newInstanceId: InstanceID) => { const state = getMutableState(LocationInstanceState) const currentNetwork = state.instances[currentInstanceId].get({ noproxy: true }) const networkState = getMutableState(NetworkState) const currentNework = getState(NetworkState).networks[currentInstanceId] updateNetworkID(currentNework as SocketWebRTCClientNetwork, newInstanceId) - networkState.hostIds.media.set(newInstanceId as UserID) + networkState.hostIds.media.set(newInstanceId) state.instances.merge({ [newInstanceId]: currentNetwork }) state.instances[currentInstanceId].set(none) }, diff --git a/packages/client-core/src/common/services/MediaInstanceConnectionService.ts b/packages/client-core/src/common/services/MediaInstanceConnectionService.ts index 3f6533ff22..57a660278d 100755 --- a/packages/client-core/src/common/services/MediaInstanceConnectionService.ts +++ b/packages/client-core/src/common/services/MediaInstanceConnectionService.ts @@ -31,6 +31,7 @@ import { NetworkState } from '@etherealengine/engine/src/networking/NetworkState import { defineState, getMutableState, getState, State, useState } from '@etherealengine/hyperflux' import { ChannelID } from '@etherealengine/common/src/dbmodels/Channel' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { SocketWebRTCClientNetwork } from '../../transports/SocketWebRTCClientFunctions' import { AuthState } from '../../user/services/AuthService' @@ -47,7 +48,7 @@ type InstanceState = { export const MediaInstanceState = defineState({ name: 'MediaInstanceState', initial: () => ({ - instances: {} as { [id: string]: InstanceState } + instances: {} as { [id: InstanceID]: InstanceState } }) }) diff --git a/packages/client-core/src/components/ConferenceMode/index.tsx b/packages/client-core/src/components/ConferenceMode/index.tsx index e26218cf05..117eaa107a 100644 --- a/packages/client-core/src/components/ConferenceMode/index.tsx +++ b/packages/client-core/src/components/ConferenceMode/index.tsx @@ -41,10 +41,10 @@ const ConferenceMode = (): JSX.Element => { const authState = useHookstate(getMutableState(AuthState)) const channelConnectionState = useHookstate(getMutableState(MediaInstanceState)) const network = Engine.instance.mediaNetwork - const currentChannelInstanceConnection = network && channelConnectionState.instances[network.hostId].ornull + const currentChannelInstanceConnection = network && channelConnectionState.instances[network.id].ornull const displayedUsers = - network?.hostId && currentChannelInstanceConnection - ? Array.from(network.peers.values()).filter( + network?.id && currentChannelInstanceConnection + ? Object.values(network.peers).filter( (peer) => peer.peerID !== 'server' && peer.userId !== authState.user.id.value ) || [] : [] @@ -67,7 +67,7 @@ const ConferenceMode = (): JSX.Element => { for (let user of displayedUsers) { totalScreens += 1 - const peerID = Array.from(network.peers.values()).find((peer) => peer.userId === user.userId)?.peerID + const peerID = Object.values(network.peers).find((peer) => peer.userId === user.userId)?.peerID if (screenShareConsumers.find((consumer) => consumer.peerID.value === peerID)) { totalScreens += 1 } diff --git a/packages/client-core/src/components/InstanceChat/index.tsx b/packages/client-core/src/components/InstanceChat/index.tsx index 7b77585fb0..53f33cb011 100755 --- a/packages/client-core/src/components/InstanceChat/index.tsx +++ b/packages/client-core/src/components/InstanceChat/index.tsx @@ -145,7 +145,7 @@ export const useChatHooks = ({ chatWindowOpen, setUnreadMessages, messageRefInpu } const packageMessage = (): void => { - const instanceId = Engine.instance.worldNetwork.hostId + const instanceId = Engine.instance.worldNetwork.id if (composingMessage?.value?.length && instanceId) { if (usersTyping) { dispatchAction( @@ -460,7 +460,7 @@ export const InstanceChatWrapper = () => { if (worldNetwork?.connected?.value) { ChannelService.getInstanceChannel() } - }, [worldNetwork?.connected]) + }, [worldNetwork?.connected?.value]) return ( <> diff --git a/packages/client-core/src/components/LocationIcons/index.tsx b/packages/client-core/src/components/LocationIcons/index.tsx index 3e6fe3f588..f2d069fa51 100755 --- a/packages/client-core/src/components/LocationIcons/index.tsx +++ b/packages/client-core/src/components/LocationIcons/index.tsx @@ -28,7 +28,6 @@ import React from 'react' import { TouchGamepad } from '@etherealengine/client-core/src/common/components/TouchGamepad' import { UserMenu } from '@etherealengine/client-core/src/user/components/UserMenu' import { iOS } from '@etherealengine/engine/src/common/functions/isMobile' -import { EngineState } from '@etherealengine/engine/src/ecs/classes/EngineState' import { getCameraMode, XRState } from '@etherealengine/engine/src/xr/XRState' import { getMutableState, useHookstate } from '@etherealengine/hyperflux' @@ -44,11 +43,9 @@ import styles from './index.module.scss' export const LocationIcons = () => { const loadingScreenOpacity = useHookstate(getMutableState(LoadingSystemState).loadingScreenOpacity) - const isEngineInitialized = useHookstate(getMutableState(EngineState).isEngineInitialized) useHookstate(getMutableState(XRState)) const cameraMode = getCameraMode() - if (!isEngineInitialized.value) return <> return ( <> diff --git a/packages/client-core/src/components/MediaIconsBox/index.tsx b/packages/client-core/src/components/MediaIconsBox/index.tsx index 583c6cffea..ca40e5fdf2 100755 --- a/packages/client-core/src/components/MediaIconsBox/index.tsx +++ b/packages/client-core/src/components/MediaIconsBox/index.tsx @@ -73,9 +73,9 @@ export const MediaIconsBox = () => { const channelConnectionState = useHookstate(getMutableState(MediaInstanceState)) const networkState = useHookstate(getMutableState(NetworkState)) const mediaNetworkState = useMediaNetwork() - const mediaHostId = Engine.instance.mediaNetwork?.hostId + const mediaNetworkID = Engine.instance.mediaNetwork?.id const mediaNetworkReady = mediaNetworkState?.ready?.value - const currentChannelInstanceConnection = mediaHostId && channelConnectionState.instances[mediaHostId].ornull + const currentChannelInstanceConnection = mediaNetworkID && channelConnectionState.instances[mediaNetworkID].ornull const videoEnabled = currentLocation?.locationSetting?.value ? currentLocation?.locationSetting?.videoEnabled?.value : false diff --git a/packages/client-core/src/components/UserMediaWindow/index.tsx b/packages/client-core/src/components/UserMediaWindow/index.tsx index a58d2d3b75..87bff8544f 100755 --- a/packages/client-core/src/components/UserMediaWindow/index.tsx +++ b/packages/client-core/src/components/UserMediaWindow/index.tsx @@ -111,11 +111,11 @@ export const useUserMediaWindowHook = ({ peerID, type }: Props) => { !mediaNetwork || peerID === Engine.instance.peerID || (mediaNetwork?.peers && - Array.from(mediaNetwork.peers.values()).find((peer) => peer.userId === selfUser.id)?.peerID === peerID) || + Object.values(mediaNetwork.peers).find((peer) => peer.userId === selfUser.id)?.peerID === peerID) || peerID === 'self' const volume = isSelf ? audioState.microphoneGain.value : _volume.value const isScreen = type === 'screen' - const userId = isSelf ? selfUser?.id : mediaNetwork?.peers?.get(peerID!)?.userId + const userId = isSelf ? selfUser?.id : mediaNetwork?.peers?.[peerID]?.userId const mediaStreamState = useHookstate(getMutableState(MediaStreamState)) const mediaSettingState = useHookstate(getMutableState(MediaSettingsState)) diff --git a/packages/client-core/src/components/UserMediaWindows/index.tsx b/packages/client-core/src/components/UserMediaWindows/index.tsx index 15b6b94925..a9a663c2a4 100755 --- a/packages/client-core/src/components/UserMediaWindows/index.tsx +++ b/packages/client-core/src/components/UserMediaWindows/index.tsx @@ -66,7 +66,7 @@ export const useMediaWindows = () => { (cam.audioStream && !cam.audioProducerPaused && !cam.audioStreamPaused) const userPeers: Array<[UserID, PeerID[]]> = mediaNetworkConnected - ? Array.from(mediaNetwork.users.entries()) + ? (Object.entries(mediaNetwork.users) as Array<[UserID, PeerID[]]>) : [[selfUserID, [selfPeerID]]] // reduce all userPeers to an array 'windows' of { peerID, type } objects, displaying screens first, then cams. if a user has no cameras, only include one peerID for that user @@ -98,20 +98,28 @@ export const useMediaWindows = () => { .sort(sortScreensBeforeCameras) .filter(({ peerID }) => peerMediaChannelState[peerID].value) + // if window doesnt exist for self, add it + if (!windows.find(({ peerID }) => peerID === selfPeerID)) { + windows.unshift({ peerID: selfPeerID, type: 'cam' }) + } + return windows } export const UserMediaWindows = () => { const { topShelfStyle } = useShelfStyles() + const peerMediaChannelState = useHookstate(getMutableState(PeerMediaChannelState)) const windows = useMediaWindows() return (
- {windows.map(({ peerID, type }) => ( - - ))} + {windows + .filter(({ peerID }) => peerMediaChannelState[peerID].value) + .map(({ peerID, type }) => ( + + ))}
) @@ -151,7 +159,7 @@ export const UserMediaWindowsWidget = () => { const mediaNetwork = Engine.instance.mediaNetwork // if window doesnt exist for self, add it - if (!mediaNetwork || !windows.find(({ peerID }) => mediaNetwork.peers.get(peerID)?.userId === selfUserID)) { + if (!mediaNetwork || !windows.find(({ peerID }) => mediaNetwork.peers[peerID]?.userId === selfUserID)) { windows.unshift({ peerID: selfPeerID, type: 'cam' }) } diff --git a/packages/client-core/src/components/World/EngineHooks.tsx b/packages/client-core/src/components/World/EngineHooks.tsx index cf5f8f3582..50aca9b509 100755 --- a/packages/client-core/src/components/World/EngineHooks.tsx +++ b/packages/client-core/src/components/World/EngineHooks.tsx @@ -54,6 +54,7 @@ import { addOutgoingTopicIfNecessary, dispatchAction, getMutableState } from '@e import { loadEngineInjection } from '@etherealengine/projects/loadEngineInjection' import { UndefinedEntity } from '@etherealengine/engine/src/ecs/classes/Entity' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { NotificationService } from '../../common/services/NotificationService' import { RouterService } from '../../common/services/RouterService' import { LocationState } from '../../social/services/LocationService' @@ -230,8 +231,8 @@ export const useOfflineNetwork = (props?: { spectate?: boolean }) => { const peerIndex = 1 const networkState = getMutableState(NetworkState) - networkState.hostIds.world.set(userId) - addNetwork(createNetwork(userId, userId, NetworkTopics.world)) + networkState.hostIds.world.set(userId as any as InstanceID) + addNetwork(createNetwork(userId as any as InstanceID, userId, NetworkTopics.world)) addOutgoingTopicIfNecessary(NetworkTopics.world) NetworkPeerFunctions.createPeer( diff --git a/packages/client-core/src/media/PeerMedia.tsx b/packages/client-core/src/media/PeerMedia.tsx index c08e67b688..3681de8350 100644 --- a/packages/client-core/src/media/PeerMedia.tsx +++ b/packages/client-core/src/media/PeerMedia.tsx @@ -40,7 +40,8 @@ import { MediasoupMediaProducerConsumerState, MediasoupMediaProducersConsumersObjectsState } from '@etherealengine/engine/src/networking/systems/MediasoupMediaProducerConsumerState' -import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' +import { useMediaNetwork } from '../common/services/MediaInstanceConnectionService' import { MediaStreamState } from '../transports/MediaStreams' import { PeerMediaChannelState, @@ -52,7 +53,7 @@ import { SocketWebRTCClientNetwork } from '../transports/SocketWebRTCClientFunct /** * Sets media stream state for a peer */ -const PeerMedia = (props: { consumerID: string; networkID: UserID }) => { +const PeerMedia = (props: { consumerID: string; networkID: InstanceID }) => { const consumerState = useHookstate( getMutableState(MediasoupMediaProducerConsumerState)[props.networkID].consumers[props.consumerID] ) @@ -133,47 +134,15 @@ const SelfMedia = () => { return null } -export const PeerMediaChannels = (props: { networkID: UserID }) => { - const mediaStreamState = useHookstate(getMutableState(MediaStreamState)) - const mediaNetworkState = useHookstate(getMutableState(NetworkState).networks[props.networkID]) - const consumerState = useHookstate(getMutableState(MediasoupMediaProducerConsumerState)[props.networkID].consumers) - - // create a peer media stream for each peer with a consumer - useEffect(() => { - const mediaNetwork = Engine.instance.mediaNetwork as SocketWebRTCClientNetwork - if (!mediaNetwork) return - const peerMediaChannels = getState(PeerMediaChannelState) - const mediaChannelPeers = Array.from(mediaNetwork.peers.keys()).filter((peerID) => peerID !== 'server') - for (const peerID of mediaChannelPeers) { - if (!peerMediaChannels[peerID]) { - createPeerMediaChannels(peerID) - } - } - for (const peerID of Object.keys(peerMediaChannels)) { - const peerConsumers = mediaChannelPeers.filter((peer) => peer === peerID) - if (peerConsumers.length === 0) { - removePeerMediaChannels(peerID as PeerID) - } - } - }, [ - mediaNetworkState?.peers?.size, - consumerState.keys.length, - mediaStreamState.videoStream, - mediaStreamState.audioStream, - mediaStreamState.screenAudioProducer, - mediaStreamState.screenVideoProducer - ]) - - return null -} - -export const NetworkProducer = (props: { networkID: UserID; producerID: string }) => { +export const NetworkProducer = (props: { networkID: InstanceID; producerID: string }) => { const { networkID, producerID } = props const producerState = useHookstate( getMutableState(MediasoupMediaProducerConsumerState)[networkID].producers[producerID] ) + const networkState = useHookstate(getMutableState(NetworkState).networks[networkID]) useEffect(() => { + if (!networkState.ready.value) return const peerID = producerState.peerID.value const mediaTag = producerState.mediaTag.value const channelID = producerState.channelID.value @@ -189,18 +158,17 @@ export const NetworkProducer = (props: { networkID: UserID; producerID: string } $to: network.hostPeerID }) ) - }, []) + }, [networkState.ready]) return null } -const NetworkConsumers = (props: { networkID: UserID }) => { +const NetworkConsumers = (props: { networkID: InstanceID }) => { const { networkID } = props const consumers = useHookstate(getMutableState(MediasoupMediaProducerConsumerState)[networkID].consumers) const producers = useHookstate(getMutableState(MediasoupMediaProducerConsumerState)[networkID].producers) return ( <> - {producers.keys.map((producerID: string) => ( ))} @@ -211,14 +179,46 @@ const NetworkConsumers = (props: { networkID: UserID }) => { ) } +export const PeerMediaChannel = (props: { peerID: PeerID }) => { + useEffect(() => { + createPeerMediaChannels(props.peerID) + return () => { + removePeerMediaChannels(props.peerID) + } + }, []) + return null +} + +export const PeerMediaChannels = () => { + const mediaNetwork = useMediaNetwork() + + const mediaPeers = useHookstate([] as PeerID[]) + + useEffect(() => { + const mediaChannelPeers = mediaNetwork?.peers?.keys?.length + ? Array.from(mediaNetwork.peers.keys as PeerID[]).filter((peerID) => peerID !== 'server') + : [Engine.instance.peerID] + mediaPeers.set(mediaChannelPeers) + }, [mediaNetwork?.peers?.keys?.length]) + + return ( + <> + {mediaPeers.value.map((peerID) => ( + + ))} + + ) +} + export const PeerMediaConsumers = () => { const networkIDs = useHookstate(getMutableState(MediasoupMediaProducerConsumerState)) const selfPeerMediaChannelState = useHookstate(getMutableState(PeerMediaChannelState)[Engine.instance.peerID]) return ( <> + {selfPeerMediaChannelState.value && } - {networkIDs.keys.map((hostId: UserID) => ( - + {networkIDs.keys.map((id: InstanceID) => ( + ))} ) diff --git a/packages/client-core/src/networking/ClientNetworkingSystem.tsx b/packages/client-core/src/networking/ClientNetworkingSystem.tsx index 203a05462b..baf593e04f 100644 --- a/packages/client-core/src/networking/ClientNetworkingSystem.tsx +++ b/packages/client-core/src/networking/ClientNetworkingSystem.tsx @@ -36,6 +36,7 @@ import { useHookstate } from '@etherealengine/hyperflux' +import { PeerID } from '@etherealengine/common/src/interfaces/PeerID' import { NetworkActions, NetworkState } from '@etherealengine/engine/src/networking/NetworkState' import { NetworkPeerFunctions } from '@etherealengine/engine/src/networking/functions/NetworkPeerFunctions' import { MediasoupMediaConsumerActions } from '@etherealengine/engine/src/networking/systems/MediasoupMediaProducerConsumerState' @@ -44,7 +45,7 @@ import { MediasoupTransportObjectsState, MediasoupTransportState } from '@etherealengine/engine/src/networking/systems/MediasoupTransportState' -import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { PeerMediaConsumers } from '../media/PeerMedia' import { FriendServiceReceptor } from '../social/services/FriendService' import { @@ -70,14 +71,14 @@ const execute = () => { for (const peer of action.peers) { NetworkPeerFunctions.createPeer(network, peer.peerID, peer.peerIndex, peer.userID, peer.userIndex, peer.name) } - for (const [peerID, peer] of network.peers) + for (const [peerID, peer] of Object.entries(network.peers)) if (!action.peers.find((p) => p.peerID === peerID)) { - NetworkPeerFunctions.destroyPeer(network, peerID) + NetworkPeerFunctions.destroyPeer(network, peerID as PeerID) } } } -const NetworkConnectionReactor = (props: { networkID: UserID }) => { +const NetworkConnectionReactor = (props: { networkID: InstanceID }) => { const networkState = getMutableState(NetworkState).networks[props.networkID] as State const transportState = useHookstate(getMutableState(MediasoupTransportObjectsState)) @@ -105,8 +106,8 @@ const reactor = () => { return ( <> - {networkIDs.map((hostId: UserID) => ( - + {networkIDs.map((id: InstanceID) => ( + ))} diff --git a/packages/client-core/src/networking/DataChannelSystem.tsx b/packages/client-core/src/networking/DataChannelSystem.tsx index f6f97bc64f..6bd6c1f771 100644 --- a/packages/client-core/src/networking/DataChannelSystem.tsx +++ b/packages/client-core/src/networking/DataChannelSystem.tsx @@ -37,7 +37,7 @@ import { MediasoupTransportObjectsState, MediasoupTransportState } from '@etherealengine/engine/src/networking/systems/MediasoupTransportState' -import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { defineActionQueue, dispatchAction, getMutableState, getState } from '@etherealengine/hyperflux' import { none, useHookstate } from '@hookstate/core' import { DataProducer, DataProducerOptions } from 'mediasoup-client/lib/DataProducer' @@ -143,7 +143,7 @@ const execute = () => { } } -export const DataChannel = (props: { networkID: UserID; dataChannelType: DataChannelType }) => { +export const DataChannel = (props: { networkID: InstanceID; dataChannelType: DataChannelType }) => { const { networkID, dataChannelType } = props const transportState = useHookstate(getMutableState(MediasoupTransportObjectsState)) @@ -164,7 +164,7 @@ export const DataChannel = (props: { networkID: UserID; dataChannelType: DataCha return null } -const NetworkReactor = (props: { networkID: UserID }) => { +const NetworkReactor = (props: { networkID: InstanceID }) => { const { networkID } = props const dataChannelRegistry = useHookstate(getMutableState(DataChannelRegistryState)) return ( @@ -182,8 +182,8 @@ export const DataChannels = () => { .map(([networkID, network]) => networkID) return ( <> - {networkIDs.map((hostId: UserID) => ( - + {networkIDs.map((id: InstanceID) => ( + ))} ) diff --git a/packages/client-core/src/networking/NetworkInstanceProvisioning.tsx b/packages/client-core/src/networking/NetworkInstanceProvisioning.tsx index 56c84c5573..f72117f419 100644 --- a/packages/client-core/src/networking/NetworkInstanceProvisioning.tsx +++ b/packages/client-core/src/networking/NetworkInstanceProvisioning.tsx @@ -44,7 +44,7 @@ import { getMutableState, none, useHookstate } from '@etherealengine/hyperflux' import { Groups } from '@mui/icons-material' -import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { useTranslation } from 'react-i18next' import { FriendService } from '../social/services/FriendService' import { connectToNetwork } from '../transports/SocketWebRTCClientFunctions' @@ -129,14 +129,14 @@ export const WorldInstanceProvisioning = () => { return ( <> - {locationInstance.instances.keys.map((instanceId: UserID) => ( + {locationInstance.instances.keys.map((instanceId: InstanceID) => ( ))} ) } -export const WorldInstance = ({ id }: { id: UserID }) => { +export const WorldInstance = ({ id }: { id: InstanceID }) => { const worldInstance = useHookstate(getMutableState(LocationInstanceState).instances[id]) useEffect(() => { @@ -156,7 +156,7 @@ export const WorldInstance = ({ id }: { id: UserID }) => { export const MediaInstanceProvisioning = () => { const channelState = useHookstate(getMutableState(ChannelState)) - const worldNetworkHostId = Engine.instance.worldNetwork?.hostId + const worldNetworkId = Engine.instance.worldNetwork?.id const worldNetwork = useWorldNetwork() MediaInstanceConnectionService.useAPIListeners() @@ -168,7 +168,7 @@ export const MediaInstanceProvisioning = () => { if (channelState.channels.channels?.value.length) { const currentChannel = channelState.targetChannelId.value === '' - ? channelState.channels.channels.value.find((channel) => channel.instanceId === worldNetworkHostId)?.id + ? channelState.channels.channels.value.find((channel) => channel.instanceId === worldNetworkId)?.id : channelState.targetChannelId.value if (currentChannel) MediaInstanceConnectionService.provisionServer(currentChannel, true) } @@ -181,14 +181,14 @@ export const MediaInstanceProvisioning = () => { return ( <> - {mediaInstance.instances.keys.map((instanceId: UserID) => ( + {mediaInstance.instances.keys.map((instanceId: InstanceID) => ( ))} ) } -export const MediaInstance = ({ id }: { id: UserID }) => { +export const MediaInstance = ({ id }: { id: InstanceID }) => { const worldInstance = useHookstate(getMutableState(MediaInstanceState).instances[id]) useEffect(() => { diff --git a/packages/client-core/src/social/services/ChannelService.ts b/packages/client-core/src/social/services/ChannelService.ts index 5aad6dd8f2..b4af7a74d4 100755 --- a/packages/client-core/src/social/services/ChannelService.ts +++ b/packages/client-core/src/social/services/ChannelService.ts @@ -72,7 +72,7 @@ export const ChannelService = { try { const channelResult = (await Engine.instance.api.service('channel').find({ query: { - instanceId: Engine.instance.worldNetwork.hostId + instanceId: Engine.instance.worldNetwork.id } })) as Channel[] if (!channelResult.length) return setTimeout(() => ChannelService.getInstanceChannel(), 2000) diff --git a/packages/client-core/src/systems/AvatarUISystem.tsx b/packages/client-core/src/systems/AvatarUISystem.tsx index 4db19e3b66..d68c77252b 100644 --- a/packages/client-core/src/systems/AvatarUISystem.tsx +++ b/packages/client-core/src/systems/AvatarUISystem.tsx @@ -152,7 +152,6 @@ const onSecondaryClick = () => { const execute = () => { const engineState = getState(EngineState) - if (!engineState.isEngineInitialized) return const nonCapturedInputSource = InputSourceComponent.nonCapturedInputSourceQuery()[0] if (!nonCapturedInputSource) return @@ -220,7 +219,7 @@ const execute = () => { if (mediaNetwork) if (immersiveMedia && videoPreviewTimer === 0) { const { ownerId } = getComponent(userEntity, NetworkObjectComponent) - const peers = mediaNetwork.peers ? Array.from(mediaNetwork.peers.values()) : [] + const peers = mediaNetwork.peers ? Object.values(mediaNetwork.peers) : [] const peer = peers.find((peer) => { return peer.userId === ownerId }) diff --git a/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx b/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx index 79b922cc05..8a11fb1db4 100644 --- a/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx +++ b/packages/client-core/src/systems/ui/AvatarDetailView/index.tsx @@ -59,7 +59,7 @@ const AvatarDetailView = () => { const { t } = useTranslation() const detailState = useXRUIState() const user = Engine.instance.worldNetworkState?.peers - ? Array.from(Engine.instance.worldNetwork.peers.values()).find((peer) => peer.userId === detailState.id.value) + ? Object.values(Engine.instance.worldNetwork.peers).find((peer) => peer.userId === detailState.id.value) : undefined const worldState = useHookstate(getMutableState(WorldState)).get({ noproxy: true }) const usersTypingState = useHookstate(getMutableState(AvatarUIState).usersTyping) diff --git a/packages/client-core/src/systems/ui/RecordingsWidgetUI.tsx b/packages/client-core/src/systems/ui/RecordingsWidgetUI.tsx index 5ed02edd7f..f7874921d2 100755 --- a/packages/client-core/src/systems/ui/RecordingsWidgetUI.tsx +++ b/packages/client-core/src/systems/ui/RecordingsWidgetUI.tsx @@ -209,7 +209,7 @@ export const RecordingPeerList = () => { useEffect(() => { if (!mediaNetworkState?.users) return - peerIDs.set(mediaNetworkState.users.value.get(Engine.instance.userID) ?? []) + peerIDs.set(mediaNetworkState.users.get({ noproxy: true })[Engine.instance.userID] ?? []) }, [mediaNetworkState?.peers, mediaNetworkState?.users]) return ( diff --git a/packages/client-core/src/systems/ui/UserMenuView/index.tsx b/packages/client-core/src/systems/ui/UserMenuView/index.tsx index 12ca8119be..fd87126c58 100644 --- a/packages/client-core/src/systems/ui/UserMenuView/index.tsx +++ b/packages/client-core/src/systems/ui/UserMenuView/index.tsx @@ -66,8 +66,10 @@ const AvatarContextMenu = () => { const authState = useHookstate(getMutableState(AuthState)) const selfId = authState.user.id?.value ?? '' - const peers = (Engine.instance.worldNetworkState.peers?.get({ noproxy: true }) || []).values() - const user = peers ? Array.from(peers).find((peer) => peer.userId === detailState.id.value) || undefined : undefined + const peers = Engine.instance.worldNetwork.peers + const user = peers + ? Object.values(peers).find((peer) => peer.userId === detailState.id.value) || undefined + : undefined const { t } = useTranslation() const isFriend = friendState.relationships.value.find( @@ -92,9 +94,9 @@ const AvatarContextMenu = () => { useEffect(() => { if (detailState.id.value !== '') { - const tappedUser = Array.from( - (Engine.instance.worldNetworkState.peers?.get({ noproxy: true }) || []).values() - ).find((peer) => peer.userId === detailState.id.value) + const tappedUser = Object.values(Engine.instance.worldNetwork.peers).find( + (peer) => peer.userId === detailState.id.value + ) dispatchAction(PopupMenuActions.showPopupMenu({ id: AvatarMenus.AvatarContext, params: { user: tappedUser } })) } }, [detailState.id]) diff --git a/packages/client-core/src/transports/FilteredUsersSystem.ts b/packages/client-core/src/transports/FilteredUsersSystem.ts index 7bb4bc90ae..69d873c9f8 100755 --- a/packages/client-core/src/transports/FilteredUsersSystem.ts +++ b/packages/client-core/src/transports/FilteredUsersSystem.ts @@ -45,10 +45,10 @@ export const FilteredUsersService = { if (!Engine.instance.worldNetwork) return const mediaState = getMutableState(FilteredUsersState) const selfUserId = getMutableState(AuthState).user.id.value - const peers = Engine.instance.worldNetwork.peers ? Array.from(Engine.instance.worldNetwork.peers.values()) : [] + const peers = Object.values(Engine.instance.worldNetwork.peers) const worldUserIds = peers - ? peers.filter((peer) => peer.peerID !== 'server' && peer.userId !== selfUserId).map((peer) => peer.userId) - : [] + .filter((peer) => peer.peerID !== 'server' && peer.userId !== selfUserId) + .map((peer) => peer.userId) const nearbyUsers = getNearbyUsers(Engine.instance.userID, worldUserIds) mediaState.nearbyLayerUsers.set(nearbyUsers) } @@ -61,7 +61,7 @@ export const updateNearbyAvatars = () => { FilteredUsersService.updateNearbyLayerUsers() const channelConnectionState = getState(MediaInstanceState) - const currentChannelInstanceConnection = channelConnectionState.instances[network.hostId] + const currentChannelInstanceConnection = channelConnectionState.instances[network.id] if (!currentChannelInstanceConnection) return const filteredUsersState = getState(FilteredUsersState) diff --git a/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts b/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts index 744b158c0c..f21f870540 100755 --- a/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts +++ b/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts @@ -90,6 +90,7 @@ import { MediasoupTransportObjectsState, MediasoupTransportState } from '@etherealengine/engine/src/networking/systems/MediasoupTransportState' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { MathUtils } from 'three' import { LocationInstanceState } from '../common/services/LocationInstanceConnectionService' import { MediaInstanceState } from '../common/services/MediaInstanceConnectionService' @@ -138,7 +139,7 @@ export const promisedRequest = (network: SocketWebRTCClientNetwork, type: any, d }) } -const handleFailedConnection = (topic: Topic, instanceID: UserID) => { +const handleFailedConnection = (topic: Topic, instanceID: InstanceID) => { console.log('handleFailedConnection', topic, instanceID) if (topic === NetworkTopics.world) { const locationInstanceConnectionState = getMutableState(LocationInstanceState) @@ -167,7 +168,7 @@ export const closeNetwork = (network: SocketWebRTCClientNetwork) => { networkState.transport.primus.set(null!) } -export const initializeNetwork = (id: string, hostId: UserID, topic: Topic) => { +export const initializeNetwork = (id: InstanceID, hostId: UserID, topic: Topic) => { const mediasoupDevice = new mediasoupClient.Device( getMutableState(EngineState).isBot.value ? { handlerName: 'Chrome74' } : undefined ) @@ -207,7 +208,7 @@ export const initializeNetwork = (id: string, hostId: UserID, topic: Topic) => { export type SocketWebRTCClientNetwork = ReturnType export const connectToNetwork = async ( - instanceID: UserID, + instanceID: InstanceID, ipAddress: string, port: string, locationId?: string | null, @@ -224,7 +225,7 @@ export const connectToNetwork = async ( roomCode, token } as { - instanceID: string + instanceID: InstanceID locationId?: string channelId?: ChannelID roomCode?: string @@ -267,7 +268,7 @@ export const connectToNetwork = async ( const onConnect = () => { const topic = locationId ? NetworkTopics.world : NetworkTopics.media getMutableState(NetworkState).hostIds[topic].set(instanceID) - const network = initializeNetwork(instanceID, instanceID, topic) + const network = initializeNetwork(instanceID, instanceID as any, topic) addNetwork(network) const networkState = getMutableState(NetworkState).networks[network.id] as State @@ -296,7 +297,7 @@ export const connectToNetwork = async ( export const getChannelIdFromTransport = (network: SocketWebRTCClientNetwork) => { const channelConnectionState = getState(MediaInstanceState) const mediaNetwork = Engine.instance.mediaNetwork - const currentChannelInstanceConnection = mediaNetwork && channelConnectionState.instances[mediaNetwork.hostId] + const currentChannelInstanceConnection = mediaNetwork && channelConnectionState.instances[mediaNetwork.id] const isWorldConnection = network.topic === NetworkTopics.world return isWorldConnection ? null : currentChannelInstanceConnection?.channelId } @@ -452,8 +453,6 @@ export const onTransportCreated = async (action: typeof MediasoupTransportAction transport = await network.transport.mediasoupDevice.createSendTransport(transportOptions) } else throw new Error(`bad transport 'direction': ${direction}`) - getMutableState(MediasoupTransportObjectsState)[transportID].set(transport) - // mediasoup-client will emit a connect event when media needs to // start flowing for the first time. send dtlsParameters to the // server, then call callback() on success or errback() on failure. @@ -596,6 +595,7 @@ export const onTransportCreated = async (action: typeof MediasoupTransportAction errback: (error: Error) => void ) => { const { sctpStreamParameters, label, protocol, appData } = parameters + if (label === '__CONNECT__') return callback({ id: '__CONNECT__' }) const requestID = MathUtils.generateUUID() dispatchAction( @@ -676,6 +676,51 @@ export const onTransportCreated = async (action: typeof MediasoupTransportAction }, 5000) } }) + + /** + * Since mediasoup only connects the transport upon a consumer or producer being created, + * we need to create a dummy consumer/producer to trigger the transport to connect. + */ + try { + if (direction === 'recv') { + const consumer = await transport.consumeData({ + id: '', + dataProducerId: '', + sctpStreamParameters: { + streamId: 0, + ordered: true, + maxPacketLifeTime: 0 + }, + label: '__CONNECT__' + }) + consumer.close() + } else { + const producer = await transport.produceData({ + label: '__CONNECT__' + }) + producer.close() + } + } catch (e) { + // no-op + } + + // /** + // * Since mediasoup only connects the transport upon a consumer or producer being created, + // * we need to manually dive in and call it's internal implementation. + // * - NOTE this does not work for Edge11 + // */ + // const handler = (transport as any)._handler + // const offer = await handler._pc.createOffer() + // const localSdpObject = sdpTransform.parse(offer.sdp) + // const _dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }) + // _dtlsParameters.role = handler._forcedLocalDtlsRole + // handler._remoteSdp!.updateDtlsRole(handler._forcedLocalDtlsRole === 'client' ? 'server' : 'client') + // await new Promise((resolve, reject) => { + // transport.safeEmit('connect', { dtlsParameters: _dtlsParameters }, resolve, reject) + // }) + // handler._transportReady = true + + getMutableState(MediasoupTransportObjectsState)[transportID].set(transport) } export async function configureMediaTransports(mediaTypes: string[]): Promise { @@ -708,7 +753,7 @@ export async function configureMediaTransports(mediaTypes: string[]): Promise { const channelConnectionState = getState(MediaInstanceState) - const currentChannelInstanceConnection = channelConnectionState.instances[network.hostId] + const currentChannelInstanceConnection = channelConnectionState.instances[network.id] const channelId = currentChannelInstanceConnection.channelId const mediaStreamState = getMutableState(MediaStreamState) if (mediaStreamState.videoStream.value !== null) { @@ -749,7 +794,7 @@ export async function createCamVideoProducer(network: SocketWebRTCClientNetwork) export async function createCamAudioProducer(network: SocketWebRTCClientNetwork): Promise { const channelConnectionState = getState(MediaInstanceState) - const currentChannelInstanceConnection = channelConnectionState.instances[network.hostId] + const currentChannelInstanceConnection = channelConnectionState.instances[network.id] const channelId = currentChannelInstanceConnection.channelId const mediaStreamState = getMutableState(MediaStreamState) if (mediaStreamState.audioStream.value !== null) { @@ -802,6 +847,7 @@ export async function createCamAudioProducer(network: SocketWebRTCClientNetwork) } } +/** @todo this is unused, see if it's ever needed to add these checks */ export async function subscribeToTrack( network: SocketWebRTCClientNetwork, peerID: PeerID, @@ -816,7 +862,7 @@ export async function subscribeToTrack( const selfProducerIds = [mediaStreamState.camVideoProducer?.id, mediaStreamState.camAudioProducer?.id] const channelConnectionState = getState(MediaInstanceState) - const currentChannelInstanceConnection = channelConnectionState.instances[network.hostId] + const currentChannelInstanceConnection = channelConnectionState.instances[network.id] const existingConsumer = MediasoupMediaProducerConsumerState.getConsumerByPeerIdAndMediaTag( network.id, @@ -1113,7 +1159,7 @@ export const startScreenshare = async (network: SocketWebRTCClientNetwork) => { ) const channelConnectionState = getState(MediaInstanceState) - const currentChannelInstanceConnection = channelConnectionState.instances[network.hostId] + const currentChannelInstanceConnection = channelConnectionState.instances[network.id] const channelId = currentChannelInstanceConnection.channelId await waitForTransports(network) diff --git a/packages/client-core/src/user/components/MagicLink/AuthMagicLink.tsx b/packages/client-core/src/user/components/MagicLink/AuthMagicLink.tsx index 185bc36162..8dfa36d917 100755 --- a/packages/client-core/src/user/components/MagicLink/AuthMagicLink.tsx +++ b/packages/client-core/src/user/components/MagicLink/AuthMagicLink.tsx @@ -33,6 +33,7 @@ import Box from '@etherealengine/ui/src/primitives/mui/Box' import Container from '@etherealengine/ui/src/primitives/mui/Container' import Typography from '@etherealengine/ui/src/primitives/mui/Typography' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { AuthService, AuthState } from '../../services/AuthService' import { VerifyEmail } from '../Auth/VerifyEmail' @@ -40,7 +41,7 @@ interface Props { //auth: any type: string token: string - instanceId: string + instanceId: InstanceID path: string } diff --git a/packages/client-core/src/user/components/UserMenu/index.module.scss b/packages/client-core/src/user/components/UserMenu/index.module.scss index 4188f5b4c2..aff9957cd8 100755 --- a/packages/client-core/src/user/components/UserMenu/index.module.scss +++ b/packages/client-core/src/user/components/UserMenu/index.module.scss @@ -117,3 +117,9 @@ } } } + +.center { + display: flex; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/packages/client-core/src/user/components/UserMenu/menus/FriendsMenu.tsx b/packages/client-core/src/user/components/UserMenu/menus/FriendsMenu.tsx index 960dee16a2..030731e174 100755 --- a/packages/client-core/src/user/components/UserMenu/menus/FriendsMenu.tsx +++ b/packages/client-core/src/user/components/UserMenu/menus/FriendsMenu.tsx @@ -164,8 +164,8 @@ const FriendsMenu = ({ defaultSelectedTab }: Props): JSX.Element => { .map((item) => ({ id: item.relatedUserId, name: item.relatedUser.name, relationType: 'blocking' as const })) displayList.push(...blockingList) } else if (selectedTab.value === 'find') { - const layerPeers = Engine.instance.worldNetworkState?.peers - ? Array.from(Engine.instance.worldNetworkState.peers.get({ noproxy: true }).values()).filter( + const layerPeers = Engine.instance.worldNetwork + ? Object.values(Engine.instance.worldNetwork.peers).filter( (peer) => peer.peerID !== 'server' && peer.userId !== userId && diff --git a/packages/client-core/src/user/components/UserMenu/menus/ProfileMenu.tsx b/packages/client-core/src/user/components/UserMenu/menus/ProfileMenu.tsx index 84d4122ed3..1af8083ca3 100755 --- a/packages/client-core/src/user/components/UserMenu/menus/ProfileMenu.tsx +++ b/packages/client-core/src/user/components/UserMenu/menus/ProfileMenu.tsx @@ -42,7 +42,6 @@ import Menu from '@etherealengine/client-core/src/common/components/Menu' import Text from '@etherealengine/client-core/src/common/components/Text' import { validateEmail, validatePhoneNumber } from '@etherealengine/common/src/config' import { useFind } from '@etherealengine/engine/src/common/functions/FeathersHooks' -import { EngineState } from '@etherealengine/engine/src/ecs/classes/EngineState' import { getMutableState, useHookstate } from '@etherealengine/hyperflux' import Box from '@etherealengine/ui/src/primitives/mui/Box' import CircularProgress from '@etherealengine/ui/src/primitives/mui/CircularProgress' @@ -50,6 +49,7 @@ import Icon from '@etherealengine/ui/src/primitives/mui/Icon' import IconButton from '@etherealengine/ui/src/primitives/mui/IconButton' import { authenticationSettingPath } from '@etherealengine/engine/src/schemas/setting/authentication-setting.schema' +import { clientSettingPath } from '@etherealengine/engine/src/schemas/setting/client-setting.schema' import { initialAuthState, initialOAuthConnectedState } from '../../../../common/initialAuthState' import { NotificationService } from '../../../../common/services/NotificationService' import { useUserAvatarThumbnail } from '../../../functions/useUserAvatarThumbnail' @@ -81,8 +81,8 @@ const ProfileMenu = ({ hideLogin, onClose, isPopover }: Props): JSX.Element => { const oauthConnectedState = useHookstate(Object.assign({}, initialOAuthConnectedState)) const authState = useHookstate(initialAuthState) - const engineInitialized = useHookstate(getMutableState(EngineState).isEngineInitialized) const authSetting = useFind(authenticationSettingPath).data.at(0) + const clientSetting = useFind(clientSettingPath).data.at(0) const loading = useHookstate(getMutableState(AuthState).isProcessing) const userId = selfUser.id.value const apiKey = selfUser.apiKey?.token?.value @@ -355,7 +355,7 @@ const ProfileMenu = ({ hideLogin, onClose, isPopover }: Props): JSX.Element => { PopupMenuServices.showPopupMenu(UserMenus.AvatarSelect)} /> @@ -638,6 +638,9 @@ const ProfileMenu = ({ hideLogin, onClose, isPopover }: Props): JSX.Element => { )} )} + ) diff --git a/packages/client-core/src/user/functions/userPatched.ts b/packages/client-core/src/user/functions/userPatched.ts index d8f1f26098..a330919bc8 100644 --- a/packages/client-core/src/user/functions/userPatched.ts +++ b/packages/client-core/src/user/functions/userPatched.ts @@ -28,7 +28,8 @@ import { WorldState } from '@etherealengine/engine/src/networking/interfaces/Wor import { NetworkState } from '@etherealengine/engine/src/networking/NetworkState' import { getMutableState, getState } from '@etherealengine/hyperflux' -import { UserID, UserType } from '@etherealengine/engine/src/schemas/user/user.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' +import { UserType } from '@etherealengine/engine/src/schemas/user/user.schema' import { LocationInstanceConnectionService } from '../../common/services/LocationInstanceConnectionService' import { AuthState } from '../services/AuthService' @@ -49,9 +50,9 @@ export const userPatched = (user: UserType) => { if (selfUser.id.value === patchedUser.id) { getMutableState(AuthState).merge({ user: patchedUser }) const currentInstanceId = patchedUser.instanceAttendance?.find((attendance) => !attendance.isChannel) - ?.instanceId as UserID + ?.instanceId as InstanceID if (worldHostID && currentInstanceId && worldHostID !== currentInstanceId) { - LocationInstanceConnectionService.changeActiveConnectionHostId(worldHostID, currentInstanceId) + LocationInstanceConnectionService.changeActiveConnectionID(worldHostID, currentInstanceId) } } } diff --git a/packages/client/src/pages/capture/capture.tsx b/packages/client/src/pages/capture/capture.tsx index f659544908..2beba1d1eb 100755 --- a/packages/client/src/pages/capture/capture.tsx +++ b/packages/client/src/pages/capture/capture.tsx @@ -42,7 +42,7 @@ import { NetworkState } from '@etherealengine/engine/src/networking/NetworkState import { MediasoupDataProducerConsumerStateSystem } from '@etherealengine/engine/src/networking/systems/MediasoupDataProducerConsumerState' import { MediasoupMediaProducerConsumerStateSystem } from '@etherealengine/engine/src/networking/systems/MediasoupMediaProducerConsumerState' import { MediasoupTransportStateSystem } from '@etherealengine/engine/src/networking/systems/MediasoupTransportState' -import { dispatchAction, getMutableState, useHookstate } from '@etherealengine/hyperflux' +import { dispatchAction, getMutableState, getState, useHookstate } from '@etherealengine/hyperflux' import { loadEngineInjection } from '@etherealengine/projects/loadEngineInjection' import CaptureUI from '@etherealengine/ui/src/pages/Capture' @@ -63,7 +63,7 @@ const startCaptureSystems = () => { } export const initializeEngineForRecorder = async () => { - if (getMutableState(EngineState).isEngineInitialized.value) return + if (getState(EngineState).isEngineInitialized) return const projects = API.instance.client.service('projects').find() @@ -98,7 +98,7 @@ export const CaptureLocation = () => { const engineState = useHookstate(getMutableState(EngineState)) - if (!engineState.isEngineInitialized.value && !engineState.connectedWorld.value) return <> + if (!engineState.connectedWorld.value) return <> return } diff --git a/packages/common/src/dbmodels/Channel.ts b/packages/common/src/dbmodels/Channel.ts index bcde924721..2e4d9cfe6e 100644 --- a/packages/common/src/dbmodels/Channel.ts +++ b/packages/common/src/dbmodels/Channel.ts @@ -25,6 +25,7 @@ Ethereal Engine. All Rights Reserved. // TODO: Move it to channel schema once moved to feathers 5 and all dependencies on common packages are removed. +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { OpaqueType } from '../interfaces/OpaqueType' export type ChannelID = OpaqueType<'ChannelID'> & string @@ -32,5 +33,5 @@ export type ChannelID = OpaqueType<'ChannelID'> & string export interface ChannelInterface { id: string name: string - instanceId: string + instanceId: InstanceID } diff --git a/packages/common/src/interfaces/Instance.ts b/packages/common/src/interfaces/Instance.ts index 1aa4c5dd87..b8645bf88e 100755 --- a/packages/common/src/interfaces/Instance.ts +++ b/packages/common/src/interfaces/Instance.ts @@ -23,12 +23,13 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { LocationData, LocationType } from '@etherealengine/engine/src/schemas/social/location.schema' import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' import { ChannelID } from '../dbmodels/Channel' export interface Instance { - id: string + id: InstanceID roomCode: string currentUsers: number ipAddress: string @@ -44,7 +45,7 @@ export interface Instance { } export const InstanceSeed: Instance = { - id: '', + id: '' as InstanceID, roomCode: '', ipAddress: '', currentUsers: 0, diff --git a/packages/editor/src/components/element/ElementList.tsx b/packages/editor/src/components/element/ElementList.tsx index 5ea45a0924..019c5c9399 100644 --- a/packages/editor/src/components/element/ElementList.tsx +++ b/packages/editor/src/components/element/ElementList.tsx @@ -59,13 +59,13 @@ import { TransformComponent } from '@etherealengine/engine/src/transform/compone import { NO_PROXY, getState, useState } from '@etherealengine/hyperflux' import MenuItem from '@etherealengine/ui/src/primitives/mui/MenuItem' -import Tooltip from '@etherealengine/ui/src/primitives/mui/Tooltip' import Typography from '@etherealengine/ui/src/primitives/mui/Typography' import { GroupAddOutlined as PlaceHolderIcon } from '@mui/icons-material' import { IconButton, PopoverPosition } from '@mui/material' import { BehaveGraphComponent } from '@etherealengine/engine/src/behave-graph/components/BehaveGraphComponent' +import { EnvmapComponent } from '@etherealengine/engine/src/scene/components/EnvmapComponent' import { ItemTypes } from '../../constants/AssetTypes' import { EntityNodeEditor } from '../../functions/ComponentEditors' import { EditorControlFunctions } from '../../functions/EditorControlFunctions' @@ -73,6 +73,7 @@ import { getSpawnPositionAtCenter } from '../../functions/screenSpaceFunctions' import { Button } from '../inputs/Button' import StringInput from '../inputs/StringInput' import { ContextMenu } from '../layout/ContextMenu' +import { InfoTooltip } from '../layout/Tooltip' import styles from './styles.module.scss' export type SceneElementType = { @@ -99,7 +100,7 @@ export const ComponentShelfCategories: Record = { DirectionalLightComponent, HemisphereLightComponent ], - FX: [ParticleSystemComponent], + FX: [ParticleSystemComponent, EnvmapComponent], Scripting: [SystemComponent, BehaveGraphComponent], Misc: [EnvMapBakeComponent, ScenePreviewCameraComponent, SkyboxComponent, SplineTrackComponent, SplineComponent] } @@ -114,6 +115,8 @@ export const addSceneComponentElement = ( } const SceneElementListItem = ({ item, onClick, onContextMenu }: SceneElementListItemType) => { + const { t } = useTranslation() + const onClickItem = useCallback(() => { onClick?.(item) }, [item, onClick]) @@ -127,7 +130,12 @@ const SceneElementListItem = ({ item, onClick, onContextMenu }: SceneElementList return (
onContextMenu(event, item)}> - + } /> - +
) } @@ -202,7 +210,11 @@ export function ElementList() {
- + searchBarState.set(event?.target.value)} + placeholder={t('Search...')} + /> {Object.entries(validElements.get(NO_PROXY)).map(([category, items]) => ( diff --git a/packages/editor/src/components/graph/BehaveFlow.tsx b/packages/editor/src/components/graph/BehaveFlow.tsx index 71667adda5..57ada1815d 100644 --- a/packages/editor/src/components/graph/BehaveFlow.tsx +++ b/packages/editor/src/components/graph/BehaveFlow.tsx @@ -26,13 +26,18 @@ Ethereal Engine. All Rights Reserved. import { useForceUpdate } from '@etherealengine/common/src/utils/useForceUpdate' import { BehaveGraphComponent } from '@etherealengine/engine/src/behave-graph/components/BehaveGraphComponent' import { BehaveGraphState } from '@etherealengine/engine/src/behave-graph/state/BehaveGraphState' -import { UndefinedEntity } from '@etherealengine/engine/src/ecs/classes/Entity' -import { getMutableComponent, hasComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' +import { Entity, UndefinedEntity } from '@etherealengine/engine/src/ecs/classes/Entity' +import { + getMutableComponent, + hasComponent, + setComponent +} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' import { getMutableState, getState, useHookstate } from '@etherealengine/hyperflux' import React, { useEffect } from 'react' import AutoSizer from 'react-virtualized-auto-sizer' import 'reactflow/dist/style.css' import { SelectionState } from '../../services/SelectionServices' +import { PropertiesPanelButton } from '../inputs/Button' import { Flow } from './ee-flow' import './ee-flow/styles.css' @@ -44,7 +49,10 @@ const BehaveFlow = () => { const graphState = getMutableComponent(validEntity ? entity : UndefinedEntity, BehaveGraphComponent) const forceUpdate = useForceUpdate() const registry = getState(BehaveGraphState).registry - + const addGraph = () => { + setComponent(entity as Entity, BehaveGraphComponent) + forceUpdate() + } useEffect(() => { forceUpdate() }, [selectionState.objectChangeCounter]) @@ -53,6 +61,22 @@ const BehaveFlow = () => { {({ width, height }) => (
+ {!validEntity && ( + { + addGraph() + }} + > + {' '} + Add Graph + + )} {validEntity && ( , areEqual @@ -532,6 +536,20 @@ export default function HierarchyPanel({ {HierarchyList}
)} +
onRenameNode(contextSelectedItem!)}>{t('editor:hierarchy.lbl-rename')} diff --git a/packages/editor/src/components/hierarchy/HierarchyPanelTitle.tsx b/packages/editor/src/components/hierarchy/HierarchyPanelTitle.tsx index 5ecdd0c3ae..04ad7c4046 100644 --- a/packages/editor/src/components/hierarchy/HierarchyPanelTitle.tsx +++ b/packages/editor/src/components/hierarchy/HierarchyPanelTitle.tsx @@ -27,15 +27,23 @@ import React from 'react' import AccountTreeIcon from '@mui/icons-material/AccountTree' +import { useTranslation } from 'react-i18next' import { PanelDragContainer, PanelIcon, PanelTitle } from '../layout/Panel' +import { InfoTooltip } from '../layout/Tooltip' import styles from '../styles.module.scss' export const HierarchyPanelTitle = () => { + const { t } = useTranslation() + return (
- Hierarchy + + + {t('editor:hierarchy.lbl')} + +
) diff --git a/packages/editor/src/components/inputs/InputGroup.css b/packages/editor/src/components/inputs/InputGroup.css index 82adc64be7..5db8000148 100755 --- a/packages/editor/src/components/inputs/InputGroup.css +++ b/packages/editor/src/components/inputs/InputGroup.css @@ -12,7 +12,7 @@ opacity: 0.3; } -.tooltip { +.label { color: var(--textColor); overflow: hidden; text-overflow: ellipsis; diff --git a/packages/editor/src/components/inputs/InputGroup.tsx b/packages/editor/src/components/inputs/InputGroup.tsx index 609127f7db..699b1e47df 100755 --- a/packages/editor/src/components/inputs/InputGroup.tsx +++ b/packages/editor/src/components/inputs/InputGroup.tsx @@ -133,9 +133,7 @@ export function InputGroup({ name, children, disabled, info, label, ...rest }: I - - - + {info && ( diff --git a/packages/editor/src/components/inputs/NumericInputGroup.tsx b/packages/editor/src/components/inputs/NumericInputGroup.tsx index a3b6e5033f..0098964fdc 100755 --- a/packages/editor/src/components/inputs/NumericInputGroup.tsx +++ b/packages/editor/src/components/inputs/NumericInputGroup.tsx @@ -54,13 +54,15 @@ export interface NumericInputGroupProp { } function BaseNumericInputGroup({ name, className, label, ...rest }: NumericInputGroupProp) { - const { displayPrecision, ...scrubberProps } = rest + const { displayPrecision, onChange, value, ...scrubberProps } = rest return ( - {label} + + {label} + diff --git a/packages/editor/src/components/inputs/Scrubber.css b/packages/editor/src/components/inputs/Scrubber.css index 6b87c3c72c..9267d93b70 100755 --- a/packages/editor/src/components/inputs/Scrubber.css +++ b/packages/editor/src/components/inputs/Scrubber.css @@ -1,8 +1,3 @@ -.ScrubberContainer { - cursor: ew-resize; - user-select: none; -} - .Cursor { position: absolute; width: 20px; diff --git a/packages/editor/src/components/inputs/Scrubber.tsx b/packages/editor/src/components/inputs/Scrubber.tsx index 186fd9fc5c..9a039fff50 100755 --- a/packages/editor/src/components/inputs/Scrubber.tsx +++ b/packages/editor/src/components/inputs/Scrubber.tsx @@ -37,18 +37,20 @@ import { useHookstate } from '@etherealengine/hyperflux' import Portal from '../layout/Portal' type ScrubberContainerProps = { - tag?: keyof JSX.IntrinsicElements + tag?: any children?: ReactNode onMouseDown: any } -const ScrubberContainer = React.forwardRef(({ tag: Component = 'div', children, ...rest }: ScrubberContainerProps) => { - return ( - - {children} - - ) -}) +const ScrubberContainer = React.forwardRef( + ({ tag: Component = 'div', children, ...rest }: ScrubberContainerProps, ref: React.Ref) => { + return ( + + {children} + + ) + } +) type CursorProps = { x: number @@ -76,7 +78,7 @@ type ScrubberProps = { onCommit?: (value: any) => void } -const Scrubber = ({ +const Scrubber: React.FC = ({ tag, children, smallStep, @@ -92,7 +94,7 @@ const Scrubber = ({ onChange, onCommit, ...rest -}: ScrubberProps) => { +}) => { const state = useHookstate({ isDragging: false, startValue: null as number | null, @@ -110,7 +112,7 @@ const Scrubber = ({ const nextDelta = state.delta.value + event.movementX const stepSize = getStepSize(event, smallStep, mediumStep, largeStep) const nextValue = (state.startValue.value as number) + Math.round(nextDelta / (sensitivity || 1)) * stepSize - const clampedValue = min != null && max != null ? clamp(nextValue, min, max) : nextValue + const clampedValue = clamp(nextValue, min ?? -Infinity, max ?? Infinity) const roundedValue = precision ? toPrecision(clampedValue, precision) : clampedValue const finalValue = convertTo(roundedValue) onChange(finalValue) @@ -153,9 +155,7 @@ const Scrubber = ({ state.delta.set(0) state.mouseX.set(event.clientX) state.mouseY.set(event.clientY) - scrubberEl?.current?.requestPointerLock() - window.addEventListener('mousemove', handleMouseMove) window.addEventListener('mouseup', handleMouseUp) } diff --git a/packages/editor/src/components/inputs/SelectInput.tsx b/packages/editor/src/components/inputs/SelectInput.tsx index 19eedd989f..21ec9f6cbc 100755 --- a/packages/editor/src/components/inputs/SelectInput.tsx +++ b/packages/editor/src/components/inputs/SelectInput.tsx @@ -30,11 +30,12 @@ import FormControl from '@mui/material/FormControl' import MenuItem from '@mui/material/MenuItem' import Select, { SelectChangeEvent } from '@mui/material/Select' +import { InfoTooltip } from '../layout/Tooltip' import styles from './selectInput.module.scss' interface SelectInputProp { value: T | string - options: Array<{ label: string; value: T }> + options: Array<{ label: string; value: T; info?: string }> onChange?: (value: T | string) => void placeholder?: string disabled?: boolean @@ -42,6 +43,7 @@ interface SelectInputProp { className?: string isSearchable?: boolean } + export function SelectInput | number | undefined>({ value, options, @@ -124,8 +126,14 @@ export function SelectInput | number | IconComponent={ExpandMoreIcon} > {options.map((option, index) => ( - - {option.label} + + {option.info ? ( + + {option.label} + + ) : ( + option.label + )} ))} diff --git a/packages/editor/src/components/layout/Tooltip.tsx b/packages/editor/src/components/layout/Tooltip.tsx index 264a018e18..d14154e807 100755 --- a/packages/editor/src/components/layout/Tooltip.tsx +++ b/packages/editor/src/components/layout/Tooltip.tsx @@ -37,19 +37,26 @@ const useStyles = makeStyles((theme: any) => { }) /** - * - * @param {any} info - * @param {any} children - * @param {any} rest - * @returns + * @param {Object} props + * @param {string} props.info additional info added to the tooltip label */ -export function InfoTooltip(props: TooltipProps) { +export function InfoTooltip(props: TooltipProps & { info?: string }) { if (!props.title) return <>{props.children} + const title = props.info ? ( +

+ {props.title} +


+ {props.info} +

+ ) : ( + props.title + ) + const styles = useStyles({}) return ( - + {/* Span is required to trigger events like hover in safari for disabled elements */} {props.children} diff --git a/packages/editor/src/components/materials/MaterialLibraryPanelTitle.tsx b/packages/editor/src/components/materials/MaterialLibraryPanelTitle.tsx index b4c28b84ec..f54ade0038 100644 --- a/packages/editor/src/components/materials/MaterialLibraryPanelTitle.tsx +++ b/packages/editor/src/components/materials/MaterialLibraryPanelTitle.tsx @@ -27,15 +27,23 @@ import React from 'react' import MaterialLibraryIcon from '@mui/icons-material/Yard' +import { useTranslation } from 'react-i18next' import { PanelDragContainer, PanelIcon, PanelTitle } from '../layout/Panel' +import { InfoTooltip } from '../layout/Tooltip' import styles from '../styles.module.scss' export const MaterialLibraryPanelTitle = () => { + const { t } = useTranslation() + return (
- Material Library + + + {t('editor:materialLibrary.lbl')} + +
) diff --git a/packages/editor/src/components/properties/EnvMapEditor.tsx b/packages/editor/src/components/properties/EnvMapEditor.tsx index 7ac0c1eb53..24225827d5 100644 --- a/packages/editor/src/components/properties/EnvMapEditor.tsx +++ b/packages/editor/src/components/properties/EnvMapEditor.tsx @@ -43,6 +43,8 @@ import SelectInput from '../inputs/SelectInput' import NodeEditor from './NodeEditor' import { EditorComponentType, updateProperties, updateProperty } from './Util' +import { SportsBarTwoTone } from '@mui/icons-material' + /** * EnvMapSourceOptions array containing SourceOptions for Envmap */ @@ -157,5 +159,5 @@ export const EnvMapEditor: EditorComponentType = (props) => { ) } - +EnvMapEditor.iconComponent = SportsBarTwoTone export default EnvMapEditor diff --git a/packages/editor/src/components/properties/NameInputGroup.tsx b/packages/editor/src/components/properties/NameInputGroup.tsx index e888b5d3f9..cb5b96bf12 100755 --- a/packages/editor/src/components/properties/NameInputGroup.tsx +++ b/packages/editor/src/components/properties/NameInputGroup.tsx @@ -23,7 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import React, { useEffect, useState } from 'react' +import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { @@ -63,8 +63,8 @@ export const NameInputGroup: EditorComponentType = (props) => { const nodeName = useComponent(props.entity, NameComponent) // temp name is used to store the name of the entity, which is then updated upon onBlur event - const [tempName, setTempName] = useState(nodeName.value) - const [focusedNode, setFocusedNode] = useState() + const tempName = useHookstate(nodeName.value) + const focusedNode = useHookstate(undefined) const { t } = useTranslation() useEffect(() => { @@ -72,32 +72,32 @@ export const NameInputGroup: EditorComponentType = (props) => { }, [selectionState.objectChangeCounter]) const onObjectChange = (propertyName: string) => { - if (propertyName === 'name') setTempName(getComponent(props.entity, NameComponent)) + if (propertyName === 'name') tempName.set(getComponent(props.entity, NameComponent)) } //function to handle change in name property const updateName = () => { - setComponent(props.entity, NameComponent, tempName) + setComponent(props.entity, NameComponent, tempName.value) const group = getOptionalComponent(props.entity, GroupComponent) - if (group) for (const obj3d of group) obj3d.name = tempName + if (group) for (const obj3d of group) obj3d.name = tempName.value } //function called when element get focused const onFocus = () => { - setFocusedNode(props.entity) - setTempName(nodeName.value) + focusedNode.set(props.entity) + tempName.set(nodeName.value) } // function to handle onBlur event on name property const onBlurName = () => { // Check that the focused node is current node before setting the property. // This can happen when clicking on another node in the HierarchyPanel - if (nodeName.value !== tempName && props.entity === focusedNode) { + if (nodeName.value !== tempName.value && props.entity === focusedNode.value) { updateName() } - setFocusedNode(undefined) + focusedNode.set(undefined) } //function to handle keyUp event on name property @@ -111,8 +111,8 @@ export const NameInputGroup: EditorComponentType = (props) => { return ( tempName.set(event?.target.value)} onFocus={onFocus} onBlur={onBlurName} onKeyUp={onKeyUpName} diff --git a/packages/editor/src/components/properties/PropertiesPanelTitle.tsx b/packages/editor/src/components/properties/PropertiesPanelTitle.tsx index 7f5af81039..9cee6b2753 100644 --- a/packages/editor/src/components/properties/PropertiesPanelTitle.tsx +++ b/packages/editor/src/components/properties/PropertiesPanelTitle.tsx @@ -27,15 +27,23 @@ import React from 'react' import TuneIcon from '@mui/icons-material/Tune' +import { useTranslation } from 'react-i18next' import { PanelDragContainer, PanelIcon, PanelTitle } from '../layout/Panel' +import { InfoTooltip } from '../layout/Tooltip' import styles from '../styles.module.scss' export const PropertiesPanelTitle = () => { + const { t } = useTranslation() + return (
- Properties + + + {t('editor:properties.title')} + +
) diff --git a/packages/editor/src/components/realtime/EditorActiveInstanceService.ts b/packages/editor/src/components/realtime/EditorActiveInstanceService.ts index 64818ec1a7..553af95742 100644 --- a/packages/editor/src/components/realtime/EditorActiveInstanceService.ts +++ b/packages/editor/src/components/realtime/EditorActiveInstanceService.ts @@ -28,10 +28,11 @@ import { AuthState } from '@etherealengine/client-core/src/user/services/AuthSer import logger from '@etherealengine/common/src/logger' import { Validator, matches } from '@etherealengine/engine/src/common/functions/MatchesUtils' import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { defineAction, defineState, dispatchAction, getMutableState, getState } from '@etherealengine/hyperflux' export type ActiveInstance = { - id: string + id: InstanceID location: string currentUsers: number // todo: assignedAt so we can sort by most recent? @@ -58,7 +59,7 @@ export const EditorActiveInstanceServiceReceptor = (action): any => { //Service export const EditorActiveInstanceService = { - provisionServer: async (locationId: string, instanceId: string, sceneId: string) => { + provisionServer: async (locationId: string, instanceId: InstanceID, sceneId: string) => { logger.info({ locationId, instanceId, sceneId }, 'Provision World Server Editor') const token = getState(AuthState).authUser.accessToken const provisionResult = await Engine.instance.api.service('instance-provision').find({ diff --git a/packages/editor/src/components/realtime/WorldInstanceConnection.tsx b/packages/editor/src/components/realtime/WorldInstanceConnection.tsx index dcef1863bc..0d74face5a 100644 --- a/packages/editor/src/components/realtime/WorldInstanceConnection.tsx +++ b/packages/editor/src/components/realtime/WorldInstanceConnection.tsx @@ -79,7 +79,7 @@ export const WorldInstanceConnection = () => { // const decrementPage = () => { } // const incrementPage = () => { } - const worldNetworkHostId = Engine.instance.worldNetwork?.hostId + const worldNetworkHostId = Engine.instance.worldNetwork?.id const networkState = useWorldNetwork() const getIcon = () => { diff --git a/packages/editor/src/components/toolbar/tools/GridTool.tsx b/packages/editor/src/components/toolbar/tools/GridTool.tsx index 4d76326c5f..ebb26f5ecf 100644 --- a/packages/editor/src/components/toolbar/tools/GridTool.tsx +++ b/packages/editor/src/components/toolbar/tools/GridTool.tsx @@ -50,7 +50,7 @@ const GridTool = () => { return (
- + - + +
+ +
+
) } diff --git a/packages/editor/src/components/toolbar/tools/HelperToggleTool.tsx b/packages/editor/src/components/toolbar/tools/HelperToggleTool.tsx index b7b7549fcc..649d09ccc5 100644 --- a/packages/editor/src/components/toolbar/tools/HelperToggleTool.tsx +++ b/packages/editor/src/components/toolbar/tools/HelperToggleTool.tsx @@ -31,10 +31,13 @@ import { getMutableState, useHookstate } from '@etherealengine/hyperflux' import SelectAllIcon from '@mui/icons-material/SelectAll' import SquareFootIcon from '@mui/icons-material/SquareFoot' +import { useTranslation } from 'react-i18next' import { InfoTooltip } from '../../layout/Tooltip' import * as styles from '../styles.module.scss' export const HelperToggleTool = () => { + const { t } = useTranslation() + const rendererState = useHookstate(getMutableState(RendererState)) const toggleDebug = () => { @@ -48,7 +51,10 @@ export const HelperToggleTool = () => { return ( <>
- +
- + diff --git a/packages/editor/src/components/toolbar/tools/StatsTool.tsx b/packages/editor/src/components/toolbar/tools/StatsTool.tsx index 5ca74bc9d8..77098ab269 100644 --- a/packages/editor/src/components/toolbar/tools/StatsTool.tsx +++ b/packages/editor/src/components/toolbar/tools/StatsTool.tsx @@ -56,7 +56,7 @@ const StatsTool = () => { return ( <>
- + diff --git a/packages/editor/src/components/toolbar/tools/TransformPivotTool.tsx b/packages/editor/src/components/toolbar/tools/TransformPivotTool.tsx index 43c0f67fe0..6eef710acf 100644 --- a/packages/editor/src/components/toolbar/tools/TransformPivotTool.tsx +++ b/packages/editor/src/components/toolbar/tools/TransformPivotTool.tsx @@ -31,6 +31,8 @@ import { getMutableState } from '@etherealengine/hyperflux' import AdjustIcon from '@mui/icons-material/Adjust' +import { t } from 'i18next' +import { useTranslation } from 'react-i18next' import { setTransformPivot, toggleTransformPivot } from '../../../functions/transformFunctions' import { EditorHelperState } from '../../../services/EditorHelperState' import SelectInput from '../../inputs/SelectInput' @@ -38,18 +40,36 @@ import { InfoTooltip } from '../../layout/Tooltip' import * as styles from '../styles.module.scss' const transformPivotOptions = [ - { label: 'Selection', value: TransformPivot.Selection }, - { label: 'Center', value: TransformPivot.Center }, - { label: 'Bottom', value: TransformPivot.Bottom }, - { label: 'Origin', value: TransformPivot.Origin } + { + label: t('editor:toolbar.transformPivot.lbl-selection'), + info: t('editor:toolbar.transformPivot.info-selection'), + value: TransformPivot.Selection + }, + { + label: t('editor:toolbar.transformPivot.lbl-center'), + info: t('editor:toolbar.transformPivot.info-center'), + value: TransformPivot.Center + }, + { + label: t('editor:toolbar.transformPivot.lbl-bottom'), + info: t('editor:toolbar.transformPivot.info-bottom'), + value: TransformPivot.Bottom + }, + { + label: t('editor:toolbar.transformPivot.lbl-origin'), + info: t('editor:toolbar.transformPivot.info-origin'), + value: TransformPivot.Origin + } ] const TransformPivotTool = () => { + const { t } = useTranslation() + const editorHelperState = useHookstate(getMutableState(EditorHelperState)) return (
- + diff --git a/packages/editor/src/components/toolbar/tools/TransformSnapTool.tsx b/packages/editor/src/components/toolbar/tools/TransformSnapTool.tsx index 2f78fdcdd6..78366ad9de 100644 --- a/packages/editor/src/components/toolbar/tools/TransformSnapTool.tsx +++ b/packages/editor/src/components/toolbar/tools/TransformSnapTool.tsx @@ -31,6 +31,7 @@ import { dispatchAction, getMutableState, useHookstate } from '@etherealengine/h import AttractionsIcon from '@mui/icons-material/Attractions' +import { useTranslation } from 'react-i18next' import { toggleSnapMode } from '../../../functions/transformFunctions' import { EditorHelperAction, EditorHelperState } from '../../../services/EditorHelperState' import SelectInput from '../../inputs/SelectInput' @@ -58,6 +59,8 @@ const rotationSnapOptions = [ ] const TransformSnapTool = () => { + const { t } = useTranslation() + const editorHelperState = useHookstate(getMutableState(EditorHelperState)) const onChangeTranslationSnap = (snapValue: number) => { @@ -85,7 +88,7 @@ const TransformSnapTool = () => { return (
- + - - + +
+ +
+
+ +
+ +
+
) } diff --git a/packages/editor/src/components/toolbar/tools/TransformSpaceTool.tsx b/packages/editor/src/components/toolbar/tools/TransformSpaceTool.tsx index a012163b31..f3b68b44af 100644 --- a/packages/editor/src/components/toolbar/tools/TransformSpaceTool.tsx +++ b/packages/editor/src/components/toolbar/tools/TransformSpaceTool.tsx @@ -31,6 +31,8 @@ import { getMutableState } from '@etherealengine/hyperflux' import LanguageIcon from '@mui/icons-material/Language' +import { t } from 'i18next' +import { useTranslation } from 'react-i18next' import { setTransformSpace, toggleTransformSpace } from '../../../functions/transformFunctions' import { EditorHelperState } from '../../../services/EditorHelperState' import SelectInput from '../../inputs/SelectInput' @@ -38,30 +40,42 @@ import { InfoTooltip } from '../../layout/Tooltip' import * as styles from '../styles.module.scss' const transformSpaceOptions = [ - { label: 'Selection', value: TransformSpace.Local }, - { label: 'World', value: TransformSpace.World } + { + label: t('editor:toolbar.transformSpace.lbl-selection'), + info: t('editor:toolbar.transformSpace.info-selection'), + value: TransformSpace.Local + }, + { + label: t('editor:toolbar.transformSpace.lbl-world'), + info: t('editor:toolbar.transformSpace.info-world'), + value: TransformSpace.World + } ] const TransformSpaceTool = () => { + const { t } = useTranslation() + const transformSpace = useHookstate(getMutableState(EditorHelperState).transformSpace) return ( -
- - - - -
+ +
+ + + + +
+
) } diff --git a/packages/editor/src/components/toolbar/tools/TransformTool.tsx b/packages/editor/src/components/toolbar/tools/TransformTool.tsx index cec28113fa..eff9a915ec 100644 --- a/packages/editor/src/components/toolbar/tools/TransformTool.tsx +++ b/packages/editor/src/components/toolbar/tools/TransformTool.tsx @@ -32,42 +32,47 @@ import HeightIcon from '@mui/icons-material/Height' import OpenWithIcon from '@mui/icons-material/OpenWith' import SyncIcon from '@mui/icons-material/Sync' +import { useTranslation } from 'react-i18next' import { setTransformMode } from '../../../functions/transformFunctions' import { EditorHelperState } from '../../../services/EditorHelperState' import { InfoTooltip } from '../../layout/Tooltip' import * as styles from '../styles.module.scss' const TransformTool = () => { + const { t } = useTranslation() + const editorHelperState = useHookstate(getMutableState(EditorHelperState)) const transformMode = editorHelperState.transformMode.value return ( -
- - - - - - - - - -
+ +
+ + + + + + + + + +
+
) } diff --git a/packages/editor/src/functions/EditorControlFunctions.ts b/packages/editor/src/functions/EditorControlFunctions.ts index 74f4a76c97..e26a24ae96 100644 --- a/packages/editor/src/functions/EditorControlFunctions.ts +++ b/packages/editor/src/functions/EditorControlFunctions.ts @@ -28,7 +28,6 @@ import { Euler, Material, MathUtils, Matrix4, Mesh, Quaternion, Vector3 } from ' import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' import logger from '@etherealengine/common/src/logger' import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' -import { EngineActions } from '@etherealengine/engine/src/ecs/classes/EngineState' import { Entity } from '@etherealengine/engine/src/ecs/classes/Entity' import { SceneState } from '@etherealengine/engine/src/ecs/classes/Scene' import { @@ -95,8 +94,6 @@ const addOrRemoveComponent = >( else removeComponent(entity, component) } - /** @todo remove when all scene components migrated to reactor pattern #6892 */ - dispatchAction(EngineActions.sceneObjectUpdate({ entities: nodes as Entity[] })) dispatchAction(SelectionAction.changedSceneGraph({})) dispatchAction(EditorHistoryAction.createSnapshot({})) } @@ -117,12 +114,6 @@ const modifyProperty = >( updateComponent(node, component, properties) } - /** @todo remove when all scene components migrated to reactor pattern #6892 */ - dispatchAction( - EngineActions.sceneObjectUpdate({ - entities: nodes.filter((node) => typeof node !== 'string') as Entity[] - }) - ) dispatchAction(SelectionAction.changedSceneGraph({})) dispatchAction(EditorHistoryAction.createSnapshot({})) } diff --git a/packages/engine/src/assets/classes/AssetLoader.ts b/packages/engine/src/assets/classes/AssetLoader.ts index 8c04c7067c..3060d6aa6e 100644 --- a/packages/engine/src/assets/classes/AssetLoader.ts +++ b/packages/engine/src/assets/classes/AssetLoader.ts @@ -244,7 +244,7 @@ const getAssetClass = (assetFileName: string): AssetClass => { return AssetClass.Model } else if (/\.png|jpg|jpeg|tga|ktx2|dds$/.test(assetFileName)) { return AssetClass.Image - } else if (/\.mp4|avi|webm|mkv|mov|m3u8$/.test(assetFileName)) { + } else if (/\.mp4|avi|webm|mkv|mov|m3u8|mpd$/.test(assetFileName)) { return AssetClass.Video } else if (/\.mp3|ogg|m4a|flac|wav$/.test(assetFileName)) { return AssetClass.Audio diff --git a/packages/engine/src/assets/loaders/gltf/extensions/LightMapExtension.ts b/packages/engine/src/assets/loaders/gltf/extensions/LightMapExtension.ts index 5f68c7d74e..cc3f7e2fc8 100644 --- a/packages/engine/src/assets/loaders/gltf/extensions/LightMapExtension.ts +++ b/packages/engine/src/assets/loaders/gltf/extensions/LightMapExtension.ts @@ -88,7 +88,7 @@ export class HubsLightMapExtension extends ImporterExtension implements GLTFLoad if (!mesh?.isMesh) return const material = mesh.material as MeshStandardMaterial | MeshBasicMaterial if (!material.lightMap) return - if (!mesh.geometry.hasAttribute('uv2')) { + if (!mesh.geometry.hasAttribute('uv1')) { console.warn('Mesh has lightmap but no uv2 attribute', mesh) material.lightMap = null } diff --git a/packages/engine/src/audio/systems/PositionalAudioSystem.tsx b/packages/engine/src/audio/systems/PositionalAudioSystem.tsx index 2f06d440f6..3674b9bb07 100755 --- a/packages/engine/src/audio/systems/PositionalAudioSystem.tsx +++ b/packages/engine/src/audio/systems/PositionalAudioSystem.tsx @@ -92,7 +92,7 @@ const execute = () => { if (!network) continue const networkObject = getComponent(entity, NetworkObjectComponent) const ownerID = networkObject.ownerId - const peers = Array.from(network.peers.values()).filter((peer) => peer.userId === ownerID) + const peers = Object.values(network.peers).filter((peer) => peer.userId === ownerID) const consumers = getState(MediasoupMediaProducerConsumerState)[network.id]?.consumers if (!consumers) continue diff --git a/packages/engine/src/avatar/systems/AvatarInputSystem.ts b/packages/engine/src/avatar/systems/AvatarInputSystem.ts index 850e62c71f..c90b71d941 100755 --- a/packages/engine/src/avatar/systems/AvatarInputSystem.ts +++ b/packages/engine/src/avatar/systems/AvatarInputSystem.ts @@ -30,6 +30,7 @@ import { EngineState } from '@etherealengine/engine/src/ecs/classes/EngineState' import { dispatchAction, getMutableState, getState } from '@etherealengine/hyperflux' import { CameraComponent } from '../../camera/components/CameraComponent' +import { FollowCameraComponent } from '../../camera/components/FollowCameraComponent' import { V_000, V_010 } from '../../common/constants/MathConstants' import { Engine } from '../../ecs/classes/Engine' import { EngineActions } from '../../ecs/classes/EngineState' @@ -171,6 +172,9 @@ const secondClickTimeout = 0.2 let secondClickTimer = 0 const getAvatarDoubleClick = (buttons): boolean => { + const followComponent = getOptionalComponent(Engine.instance.cameraEntity, FollowCameraComponent) + if (followComponent && followComponent.zoomLevel < 1) return false + if (getState(XRState).sessionActive) return false if (buttons.PrimaryClick?.up) { if (!isAvatarClicked()) { diff --git a/packages/engine/src/ecs/classes/EngineState.ts b/packages/engine/src/ecs/classes/EngineState.ts index 9bd82f9391..dd8ade4172 100644 --- a/packages/engine/src/ecs/classes/EngineState.ts +++ b/packages/engine/src/ecs/classes/EngineState.ts @@ -26,7 +26,6 @@ Ethereal Engine. All Rights Reserved. import { defineAction, defineState, getMutableState } from '@etherealengine/hyperflux' import { matches, matchesEntity, Validator } from '../../common/functions/MatchesUtils' -import { Entity } from './Entity' // TODO: #6016 Refactor EngineState into multiple state objects: timer, scene, world, xr, etc. export const EngineState = defineState({ @@ -42,13 +41,13 @@ export const EngineState = defineState({ physicsSubsteps: 1, + /** @deprecated */ isEngineInitialized: false, sceneLoading: false, sceneLoaded: false, loadingProgress: 0, connectedWorld: false, isTeleporting: false, - socketInstance: false, spectating: false, avatarLoadingEffect: true, /** @@ -67,12 +66,6 @@ export const EngineState = defineState({ export function EngineEventReceptor(a) { const s = getMutableState(EngineState) matches(a) - .when(EngineActions.browserNotSupported.matches, (action) => {}) - .when(EngineActions.resetEngine.matches, (action) => - s.merge({ - socketInstance: action.instance - }) - ) .when(EngineActions.initializeEngine.matches, (action) => s.merge({ isEngineInitialized: action.initialised })) .when(EngineActions.sceneUnloaded.matches, (action) => s.merge({ sceneLoaded: false })) .when(EngineActions.sceneLoaded.matches, (action) => s.merge({ sceneLoading: false, sceneLoaded: true })) @@ -86,11 +79,7 @@ export class EngineActions { isTeleporting: matches.boolean }) - static resetEngine = defineAction({ - type: 'xre.engine.Engine.RESET_ENGINE' as const, - instance: matches.boolean - }) - + /** @deprecated */ static initializeEngine = defineAction({ type: 'xre.engine.Engine.INITIALIZED_ENGINE' as const, initialised: matches.boolean @@ -106,20 +95,6 @@ export class EngineActions { type: 'xre.engine.Engine.SCENE_UNLOADED' as const }) - static browserNotSupported = defineAction({ - type: 'xre.engine.Engine.BROWSER_NOT_SUPPORTED' as const, - msg: matches.string - }) - - static setUserHasInteracted = defineAction({ - type: 'xre.engine.Engine.SET_USER_HAS_INTERACTED' as const - }) - - static setupAnimation = defineAction({ - type: 'xre.engine.Engine.SETUP_ANIMATION' as const, - entity: matches.number - }) - static spectateUser = defineAction({ type: 'xre.engine.Engine.SPECTATE_USER' as const, user: matches.string.optional() @@ -129,26 +104,12 @@ export class EngineActions { type: 'xre.engine.Engine.EXIT_SPECTATE' as const }) - static avatarAlreadyInWorld = defineAction({ - type: 'xre.world.AVATAR_ALREADY_IN_WORLD' - }) - static interactedWithObject = defineAction({ type: 'xre.engine.Engine.INTERACTED_WITH_OBJECT' as const, targetEntity: matchesEntity.optional(), handedness: matches.string as Validator }) - /** - * Dispatched whenever an otherwise unchanging scene object has it's properties changed, - * such as making changes from the editor. - * @deprecated - **/ - static sceneObjectUpdate = defineAction({ - type: 'xre.engine.Engine.SCENE_OBJECT_UPDATE' as const, - entities: matches.any as Validator - }) - static avatarModelChanged = defineAction({ type: 'xre.engine.Engine.AVATAR_MODEL_CHANGED' as const, entity: matchesEntity diff --git a/packages/engine/src/initializeBrowser.ts b/packages/engine/src/initializeBrowser.ts index 059d5a5c84..b0f5e5f0d9 100644 --- a/packages/engine/src/initializeBrowser.ts +++ b/packages/engine/src/initializeBrowser.ts @@ -24,13 +24,13 @@ Ethereal Engine. All Rights Reserved. */ import { BotUserAgent } from '@etherealengine/common/src/constants/BotUserAgent' -import { dispatchAction, getMutableState } from '@etherealengine/hyperflux' +import { getMutableState } from '@etherealengine/hyperflux' import { WebLayerManager } from '@etherealengine/xrui' import { AudioState } from './audio/AudioState' import { CameraComponent } from './camera/components/CameraComponent' import { Engine } from './ecs/classes/Engine' -import { EngineActions, EngineState } from './ecs/classes/EngineState' +import { EngineState } from './ecs/classes/EngineState' import { getComponent } from './ecs/functions/ComponentFunctions' import { EngineRenderer } from './renderer/WebGLRendererSystem' import { ObjectLayers } from './scene/constants/ObjectLayers' @@ -75,7 +75,6 @@ export const initializeBrowser = () => { const setupInitialClickListener = () => { const canvas = EngineRenderer.instance.renderer.domElement const initialClickListener = () => { - dispatchAction(EngineActions.setUserHasInteracted({})) window.removeEventListener('click', initialClickListener) window.removeEventListener('touchend', initialClickListener) canvas.removeEventListener('click', initialClickListener) diff --git a/packages/engine/src/interaction/systems/GrabbableSystem.test.ts b/packages/engine/src/interaction/systems/GrabbableSystem.test.ts index e91045f4a6..a02e1cb8a2 100644 --- a/packages/engine/src/interaction/systems/GrabbableSystem.test.ts +++ b/packages/engine/src/interaction/systems/GrabbableSystem.test.ts @@ -131,12 +131,12 @@ describe.skip('EquippableSystem Integration Tests', () => { ;(Engine.instance.worldNetwork as Network).hostId = hostUserId const hostIndex = 0 - Engine.instance.worldNetwork.peers.set(hostUserId, { + Engine.instance.worldNetwork.peers[hostUserId] = { peerID: hostUserId, peerIndex: hostIndex, userId: hostUserId, userIndex: hostIndex - }) + } const userId = 'user id' as UserID const userName = 'user name' diff --git a/packages/engine/src/interaction/systems/GrabbableSystem.tsx b/packages/engine/src/interaction/systems/GrabbableSystem.tsx index cbc23e0522..0c0a248651 100644 --- a/packages/engine/src/interaction/systems/GrabbableSystem.tsx +++ b/packages/engine/src/interaction/systems/GrabbableSystem.tsx @@ -168,7 +168,7 @@ export function transferAuthorityOfObjectReceptor( if (action.newAuthority !== Engine.instance.peerID) return const grabbableEntity = NetworkObjectComponent.getNetworkObject(action.ownerId, action.networkId)! if (hasComponent(grabbableEntity, GrabbableComponent)) { - const grabberUserId = Engine.instance.worldNetwork.peers.get(action.newAuthority)?.userId! + const grabberUserId = Engine.instance.worldNetwork.peers[action.newAuthority]?.userId dispatchAction( GrabbableNetworkAction.setGrabbedObject({ entityUUID: getComponent(grabbableEntity, UUIDComponent), diff --git a/packages/engine/src/mocap/MotionCaptureSystem.ts b/packages/engine/src/mocap/MotionCaptureSystem.ts index 8939db2f64..5828f59844 100644 --- a/packages/engine/src/mocap/MotionCaptureSystem.ts +++ b/packages/engine/src/mocap/MotionCaptureSystem.ts @@ -39,8 +39,13 @@ import { NetworkObjectComponent } from '../networking/components/NetworkObjectCo import { Landmark, Results } from '@mediapipe/holistic' +import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' +import { isClient } from '../common/functions/getEnvironment' +import { removeEntity } from '../ecs/functions/EntityFunctions' import { addDataChannelHandler, removeDataChannelHandler } from '../networking/systems/DataChannelRegistry' +import { UUIDComponent } from '../scene/components/UUIDComponent' import UpdateAvatar from './UpdateAvatar' +import { motionCaptureHeadSuffix, motionCaptureLeftHandSuffix, motionCaptureRightHandSuffix } from './UpdateIkHand' export interface MotionCaptureStream extends Results { za: Landmark[] @@ -94,15 +99,33 @@ const timeSeriesMocapLastSeen = new Map() const execute = () => { const network = Engine.instance.worldNetwork for (const [peerID, mocapData] of timeSeriesMocapData) { - if (!network?.peers?.has(peerID) || timeSeriesMocapLastSeen.get(peerID)! < Date.now() - 1000) { + if (!network?.peers?.[peerID] || timeSeriesMocapLastSeen.get(peerID)! < Date.now() - 1000) { timeSeriesMocapData.delete(peerID) timeSeriesMocapLastSeen.delete(peerID) } } + + const userPeers = network?.users?.[Engine.instance.userID] + + // Stop mocap by removing entities if data doesnt exist + if (isClient && !userPeers?.find((peerID) => timeSeriesMocapData.has(peerID))) { + const headUUID = (Engine.instance.userID + motionCaptureHeadSuffix) as EntityUUID + const leftHandUUID = (Engine.instance.userID + motionCaptureLeftHandSuffix) as EntityUUID + const rightHandUUID = (Engine.instance.userID + motionCaptureRightHandSuffix) as EntityUUID + + const ikTargetHead = UUIDComponent.entitiesByUUID[headUUID] + const ikTargetLeftHand = UUIDComponent.entitiesByUUID[leftHandUUID] + const ikTargetRightHand = UUIDComponent.entitiesByUUID[rightHandUUID] + + if (ikTargetHead) removeEntity(ikTargetHead) + if (ikTargetLeftHand) removeEntity(ikTargetLeftHand) + if (ikTargetRightHand) removeEntity(ikTargetRightHand) + } + for (const [peerID, mocapData] of timeSeriesMocapData) { const data = mocapData.popLast() timeSeriesMocapLastSeen.set(peerID, Date.now()) - const userID = network.peers.get(peerID)!.userId + const userID = network.peers[peerID]!.userId const entity = NetworkObjectComponent.getUserAvatarEntity(userID) if (data && entity) { UpdateAvatar(data, userID, entity) diff --git a/packages/engine/src/networking/NetworkState.ts b/packages/engine/src/networking/NetworkState.ts index 2f9aec6c60..231398cefa 100644 --- a/packages/engine/src/networking/NetworkState.ts +++ b/packages/engine/src/networking/NetworkState.ts @@ -24,12 +24,12 @@ Ethereal Engine. All Rights Reserved. */ import { PeerID, PeersUpdateType } from '@etherealengine/common/src/interfaces/PeerID' -import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' import { defineAction, defineState, getMutableState, none } from '@etherealengine/hyperflux' import { ChannelID } from '@etherealengine/common/src/dbmodels/Channel' import { DataChannelType } from '@etherealengine/common/src/interfaces/DataChannelType' import { Validator, matches } from '../common/functions/MatchesUtils' +import { InstanceID } from '../schemas/networking/instance.schema' import { Network } from './classes/Network' import { SerializationSchema } from './serialization/Utils' @@ -44,12 +44,12 @@ export const NetworkState = defineState({ name: 'NetworkState', initial: { hostIds: { - media: null as UserID | null, - world: null as UserID | null + media: null as InstanceID | null, + world: null as InstanceID | null }, // todo - move to Network.schemas networkSchema: {} as { [key: string]: SerializationSchema }, - networks: {} as { [key: UserID]: Network }, + networks: {} as { [key: InstanceID]: Network }, config: { /** Allow connections to a world instance server */ world: false, @@ -109,16 +109,17 @@ export type PeerMediaType = { export type TransportDirection = 'send' | 'receive' export const addNetwork = (network: Network) => { - getMutableState(NetworkState).networks[network.hostId].set(network) + getMutableState(NetworkState).networks[network.id].set(network) } export const removeNetwork = (network: Network) => { - getMutableState(NetworkState).networks[network.hostId].set(none) + getMutableState(NetworkState).networks[network.id].set(none) } -export const updateNetworkID = (network: Network, newHostId: UserID) => { +export const updateNetworkID = (network: Network, newID: InstanceID) => { const state = getMutableState(NetworkState) - state.networks[network.hostId].set(none) - state.networks[newHostId].set(network) - state.networks[newHostId].hostId.set(newHostId) + state.networks[network.id].set(none) + state.networks[newID].set(network) + state.networks[newID].hostId.set(newID as any) + state.networks[newID].id.set(newID) } diff --git a/packages/engine/src/networking/classes/Network.ts b/packages/engine/src/networking/classes/Network.ts index 4c8ff17fd2..b5a27d58f2 100755 --- a/packages/engine/src/networking/classes/Network.ts +++ b/packages/engine/src/networking/classes/Network.ts @@ -29,6 +29,7 @@ import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' import { addOutgoingTopicIfNecessary, Topic } from '@etherealengine/hyperflux/functions/ActionFunctions' import { Engine } from '../../ecs/classes/Engine' +import { InstanceID } from '../../schemas/networking/instance.schema' import { NetworkPeer } from '../interfaces/NetworkPeer' /** @@ -53,8 +54,8 @@ export interface JitterBufferEntry { /** Interface for the Transport. */ export const createNetwork = ( - id: string, - hostId: UserID, + id: InstanceID, + hostId: UserID, // TODO make PeerID, derive user from UserID topic: Topic, transport = { messageToPeer: (peerId: PeerID, data: any) => {}, @@ -66,13 +67,13 @@ export const createNetwork = ( addOutgoingTopicIfNecessary(topic) const network = { /** Connected peers */ - peers: new Map() as Map, + peers: {} as Record, /** Map of numerical peer index to peer IDs */ - peerIndexToPeerID: new Map(), + peerIndexToPeerID: {} as Record, /** Map of peer IDs to numerical peer index */ - peerIDToPeerIndex: new Map(), + peerIDToPeerIndex: {} as Record, /** * The index to increment when a new peer connects @@ -81,17 +82,17 @@ export const createNetwork = ( peerIndexCount: 0, /** Connected users */ - users: new Map() as Map, + users: {} as Record, /** Map of numerical user index to user client IDs */ - userIndexToUserID: new Map(), + userIndexToUserID: {} as Record, /** Map of user client IDs to numerical user index */ - userIDToUserIndex: new Map(), + userIDToUserIndex: {} as Record, /** Gets the host peer */ get hostPeerID() { - const hostPeers = network.users.get(network.hostId) + const hostPeers = network.users[network.id] if (!hostPeers) return undefined! return hostPeers[0] }, @@ -106,6 +107,7 @@ export const createNetwork = ( * The UserID of the host * - will either be a user's UserID, or an instance server's InstanceId * @todo rename to hostUserID to differentiate better from hostPeerID + * @todo change from UserID to PeerID and change "get hostPeerID()" to "get hostUserID()" */ hostId, diff --git a/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts b/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts index 2c8a3d4b1d..9d41fcd179 100644 --- a/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts +++ b/packages/engine/src/networking/functions/NetworkPeerFunctions.test.ts @@ -37,6 +37,7 @@ import { setComponent } from '../../ecs/functions/ComponentFunctions' import { createEntity } from '../../ecs/functions/EntityFunctions' import { createEngine } from '../../initializeEngine' import { UUIDComponent } from '../../scene/components/UUIDComponent' +import { InstanceID } from '../../schemas/networking/instance.schema' import { Network } from '../classes/Network' import { NetworkObjectComponent } from '../components/NetworkObjectComponent' import { WorldState } from '../interfaces/WorldState' @@ -70,16 +71,16 @@ describe('NetworkPeerFunctions', () => { const worldState = getMutableState(WorldState) - assert(network.peers.get(peerID)) - assert.equal(network.peers.get(peerID)?.userId, userId) - assert.equal(network.peers.get(peerID)?.userIndex, userIndex) - assert.equal(network.peers.get(peerID)?.peerID, peerID) - assert.equal(network.peers.get(peerID)?.peerIndex, peerIndex) + assert(network.peers[peerID]) + assert.equal(network.peers[peerID]?.userId, userId) + assert.equal(network.peers[peerID]?.userIndex, userIndex) + assert.equal(network.peers[peerID]?.peerID, peerID) + assert.equal(network.peers[peerID]?.peerIndex, peerIndex) assert.equal(worldState.userNames[userId]?.value, userName) - assert.equal(network.userIndexToUserID.get(userIndex), userId) - assert.equal(network.userIDToUserIndex.get(userId), userIndex) - assert.equal(network.peerIndexToPeerID.get(peerIndex), peerID) - assert.equal(network.peerIDToPeerIndex.get(peerID), peerIndex) + assert.equal(network.userIndexToUserID[userIndex], userId) + assert.equal(network.userIDToUserIndex[userId], userIndex) + assert.equal(network.peerIndexToPeerID[peerIndex], peerID) + assert.equal(network.peerIDToPeerIndex[peerID], peerIndex) }) it('should update peer if it already exists', () => { @@ -98,17 +99,17 @@ describe('NetworkPeerFunctions', () => { const worldState = getMutableState(WorldState) NetworkPeerFunctions.createPeer(network, peerID, peerIndex, userId, userIndex, userName) - assert.equal(network.peers.get(peerID)!.userId, userId) - assert.equal(network.peers.get(peerID)!.userIndex, userIndex) - assert.equal(network.peers.get(peerID)!.peerID, peerID) - assert.equal(network.peers.get(peerID)!.peerIndex, peerIndex) + assert.equal(network.peers[peerID]!.userId, userId) + assert.equal(network.peers[peerID]!.userIndex, userIndex) + assert.equal(network.peers[peerID]!.peerID, peerID) + assert.equal(network.peers[peerID]!.peerIndex, peerIndex) assert.equal(worldState.userNames[userId].value, userName) NetworkPeerFunctions.createPeer(network, peerID, peerIndex2, userId, userIndex2, userName2) - assert.equal(network.peers.get(peerID)!.userId, userId) - assert.equal(network.peers.get(peerID)!.userIndex, userIndex2) - assert.equal(network.peers.get(peerID)!.peerID, peerID) - assert.equal(network.peers.get(peerID)!.peerIndex, peerIndex2) + assert.equal(network.peers[peerID]!.userId, userId) + assert.equal(network.peers[peerID]!.userIndex, userIndex2) + assert.equal(network.peers[peerID]!.peerID, peerID) + assert.equal(network.peers[peerID]!.peerIndex, peerIndex2) assert.equal(worldState.userNames[userId].value, userName2) }) }) @@ -127,12 +128,12 @@ describe('NetworkPeerFunctions', () => { NetworkPeerFunctions.createPeer(network, peerID, peerIndex, userId, userIndex, userName) NetworkPeerFunctions.destroyPeer(network, peerID) - assert(!network.peers.get(peerID)) + assert(!network.peers[peerID]) - assert.equal(network.userIndexToUserID.get(userIndex), undefined) - assert.equal(network.userIDToUserIndex.get(userId), undefined) - assert.equal(network.peerIndexToPeerID.get(peerIndex), undefined) - assert.equal(network.peerIDToPeerIndex.get(peerID), undefined) + assert.equal(network.userIndexToUserID[userIndex], undefined) + assert.equal(network.userIDToUserIndex[userId], undefined) + assert.equal(network.peerIndexToPeerID[peerIndex], undefined) + assert.equal(network.peerIDToPeerIndex[peerID], undefined) }) it('should not remove self peer', () => { @@ -148,16 +149,16 @@ describe('NetworkPeerFunctions', () => { NetworkPeerFunctions.createPeer(network, peerID, peerIndex, userId, userIndex, userName) NetworkPeerFunctions.destroyPeer(network, peerID) - assert(network.peers.get(peerID)) + assert(network.peers[peerID]) - assert.equal(network.userIndexToUserID.get(userIndex), userId) - assert.equal(network.userIDToUserIndex.get(userId), userIndex) - assert.equal(network.peerIndexToPeerID.get(peerIndex), peerID) - assert.equal(network.peerIDToPeerIndex.get(peerID), peerIndex) + assert.equal(network.userIndexToUserID[userIndex], userId) + assert.equal(network.userIDToUserIndex[userId], userIndex) + assert.equal(network.peerIndexToPeerID[peerIndex], peerID) + assert.equal(network.peerIDToPeerIndex[peerID], peerIndex) }) it('should remove peer and owned network objects', () => { - const userId = 'world' as UserID + const userId = 'world' as UserID & InstanceID const peerID = 'peer id' as PeerID Engine.instance.userID = 'another user id' as UserID Engine.instance.peerID = 'another peer id' as PeerID diff --git a/packages/engine/src/networking/functions/NetworkPeerFunctions.ts b/packages/engine/src/networking/functions/NetworkPeerFunctions.ts index 9311c30c55..e39cdfb2aa 100644 --- a/packages/engine/src/networking/functions/NetworkPeerFunctions.ts +++ b/packages/engine/src/networking/functions/NetworkPeerFunctions.ts @@ -27,7 +27,7 @@ import { Validator } from 'ts-matches' import { PeerID } from '@etherealengine/common/src/interfaces/PeerID' import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' -import { dispatchAction, getMutableState } from '@etherealengine/hyperflux' +import { dispatchAction, getMutableState, none } from '@etherealengine/hyperflux' import { Action, ResolvedActionType } from '@etherealengine/hyperflux/functions/ActionFunctions' import { AvatarNetworkAction } from '../../avatar/state/AvatarNetworkActions' @@ -48,22 +48,26 @@ function createPeer( userIndex: number, name: string ) { - network.userIDToUserIndex.set(userID, userIndex) - network.userIndexToUserID.set(userIndex, userID) - network.peerIDToPeerIndex.set(peerID, peerIndex) - network.peerIndexToPeerID.set(peerIndex, peerID) - - network.peers.set(peerID, { - peerID, - peerIndex, - userId: userID, - userIndex + const networkState = getMutableState(NetworkState).networks[network.id] + + networkState.userIDToUserIndex[userID].set(userIndex) + networkState.userIndexToUserID[userIndex].set(userID) + networkState.peerIDToPeerIndex[peerID].set(peerIndex) + networkState.peerIndexToPeerID[peerIndex].set(peerID) + + networkState.peers.merge({ + [peerID]: { + peerID, + peerIndex, + userId: userID, + userIndex + } }) - if (!network.users.has(userID)) { - network.users.set(userID, [peerID]) + if (!network.users[userID]) { + networkState.users.merge({ [userID]: [peerID] }) } else { - if (!network.users.get(userID)!.includes(peerID)) network.users.get(userID)!.push(peerID) + if (!network.users[userID]!.includes(peerID)) networkState.users[userID].merge([peerID]) } // TODO: we probably want an explicit config for detecting a non-user peer @@ -71,38 +75,32 @@ function createPeer( const worldState = getMutableState(WorldState) worldState.userNames[userID].set(name) } - - // reactively set - const networkState = getMutableState(NetworkState).networks[network.id] - networkState.peers.set(network.peers) } function destroyPeer(network: Network, peerID: PeerID) { - if (!network.peers.has(peerID)) + if (!network.peers[peerID]) return console.warn(`[NetworkPeerFunctions]: tried to remove client with peerID ${peerID} that doesn't exit`) if (peerID === Engine.instance.peerID) return console.warn(`[NetworkPeerFunctions]: tried to remove local client`) - const userID = network.peers.get(peerID)!.userId + // reactively set + const userID = network.peers[peerID]!.userId - network.peers.delete(peerID) + const networkState = getMutableState(NetworkState).networks[network.id] + networkState.peers[peerID].set(none) - const userIndex = network.userIDToUserIndex.get(userID)! - network.userIDToUserIndex.delete(userID) - network.userIndexToUserID.delete(userIndex) + const userIndex = network.userIDToUserIndex[userID]! + networkState.userIDToUserIndex[userID].set(none) + networkState.userIndexToUserID[userIndex].set(none) - const peerIndex = network.peerIDToPeerIndex.get(peerID)! - network.peerIDToPeerIndex.delete(peerID) - network.peerIndexToPeerID.delete(peerIndex) + const peerIndex = network.peerIDToPeerIndex[peerID]! + networkState.peerIDToPeerIndex[peerID].set(none) + networkState.peerIndexToPeerID[peerIndex].set(none) - const userPeers = network.users.get(userID)! + const userPeers = network.users[userID]! const peerIndexInUserPeers = userPeers.indexOf(peerID) userPeers.splice(peerIndexInUserPeers, 1) - if (!userPeers.length) network.users.delete(userID) - - // reactively set - const networkState = getMutableState(NetworkState).networks[network.id] - networkState.peers.set(network.peers) + if (!userPeers.length) networkState.users[userID].set(none) /** * if no other connections exist for this user, and this action is occurring on the world network, @@ -116,7 +114,7 @@ function destroyPeer(network: Network, peerID: PeerID) { // }) // .filter((peer) => !!peer) // console.log({remainingPeersForDisconnectingUser}) - if (!network.users.has(userID) && network.isHosting) { + if (!network.users[userID] && network.isHosting) { // Engine.instance.store.actions.cached = Engine.instance.store.actions.cached.filter((a) => a.$from !== userID) for (const eid of NetworkObjectComponent.getOwnedNetworkObjects(userID)) { const networkObject = getComponent(eid, NetworkObjectComponent) @@ -133,7 +131,7 @@ function destroyPeer(network: Network, peerID: PeerID) { } const destroyAllPeers = (network: Network) => { - for (const [userId] of network.peers) NetworkPeerFunctions.destroyPeer(network, userId) + for (const [peerID] of Object.entries(network.peers)) NetworkPeerFunctions.destroyPeer(network, peerID as PeerID) } function clearActionsHistoryForUser(userId: UserID) { diff --git a/packages/engine/src/networking/serialization/DataReader.test.ts b/packages/engine/src/networking/serialization/DataReader.test.ts index 4d94989f59..c2f7ebfec2 100644 --- a/packages/engine/src/networking/serialization/DataReader.test.ts +++ b/packages/engine/src/networking/serialization/DataReader.test.ts @@ -485,10 +485,10 @@ describe('DataReader', () => { NetworkObjectComponent.networkId[entity] = networkId const network = Engine.instance.worldNetwork as Network - network.userIndexToUserID = new Map([[userIndex, userId]]) - network.userIDToUserIndex = new Map([[userId, userIndex]]) - network.peerIndexToPeerID = new Map([[userIndex, peerId]]) - network.peerIDToPeerIndex = new Map([[peerId, userIndex]]) + network.userIndexToUserID[userIndex] = userId + network.userIDToUserIndex[userId] = userIndex + network.peerIndexToPeerID[userIndex] = peerId + network.peerIDToPeerIndex[peerId] = userIndex // construct values for a valid quaternion const [a, b, c] = [0.167, 0.167, 0.167] @@ -560,8 +560,8 @@ describe('DataReader', () => { NetworkObjectComponent.networkId[entity] = networkId const network = Engine.instance.worldNetwork as Network - network.userIndexToUserID = new Map([[userIndex, userId]]) - network.userIDToUserIndex = new Map([[userId, userIndex]]) + network.userIndexToUserID[userIndex] = userId + network.userIDToUserIndex[userId] = userIndex const [x, y, z, w] = [1.5, 2.5, 3.5, 4.5] @@ -632,8 +632,8 @@ describe('DataReader', () => { const userIndex = 0 const network = Engine.instance.worldNetwork as Network - network.userIndexToUserID = new Map([[userIndex, userId]]) - network.userIDToUserIndex = new Map([[userId, userIndex]]) + network.userIndexToUserID[userIndex] = userId + network.userIDToUserIndex[userId] = userIndex const [x, y, z, w] = [1.5, 2.5, 3.5, 4.5] @@ -717,10 +717,10 @@ describe('DataReader', () => { authorityPeerID: peerID, ownerId: userId }) - network.userIndexToUserID.set(userIndex, userId) - network.userIDToUserIndex.set(userId, userIndex) - network.peerIndexToPeerID.set(peerIndex, peerID) - network.peerIDToPeerIndex.set(peerID, peerIndex) + network.userIndexToUserID[userIndex] = userId + network.userIDToUserIndex[userId] = userIndex + network.peerIndexToPeerID[peerIndex] = peerID + network.peerIDToPeerIndex[peerID] = peerIndex }) writeEntities(writeView, network, entities) @@ -766,10 +766,10 @@ describe('DataReader', () => { const peerID = 'peerID' as PeerID const userIndex = 0 const peerIndex = 0 - network.userIndexToUserID.set(userIndex, userId) - network.userIDToUserIndex.set(userId, userIndex) - network.peerIDToPeerIndex.set(peerID, peerIndex) - network.peerIndexToPeerID.set(peerIndex, peerID) + network.userIndexToUserID[userIndex] = userId + network.userIDToUserIndex[userId] = userIndex + network.peerIDToPeerIndex[peerID] = peerIndex + network.peerIndexToPeerID[peerIndex] = peerID const n = 10 const entities: Entity[] = Array(n) @@ -855,7 +855,7 @@ describe('DataReader', () => { } const view = createViewCursor(packet) - const fromUserID = network.userIndexToUserID.get(userIndex)! + const fromUserID = network.userIndexToUserID[userIndex]! readMetadata(view) readEntities(view, network, packet.byteLength, fromUserID) @@ -901,8 +901,8 @@ describe('DataReader', () => { authorityPeerID: userId, ownerId: userId }) - network.userIndexToUserID.set(userIndex, userId) - network.userIDToUserIndex.set(userId, userIndex) + network.userIndexToUserID[userIndex] = userId + network.userIDToUserIndex[userId] = userIndex }) const packet = write(network, Engine.instance.userID, peerID, entities) @@ -983,8 +983,8 @@ describe('DataReader', () => { authorityPeerID: userId, ownerId: userId }) - network.userIndexToUserID.set(userIndex, userId) - network.userIDToUserIndex.set(userId, userIndex) + network.userIndexToUserID[userIndex] = userId + network.userIDToUserIndex[userId] = userIndex }) let packet = write(network, Engine.instance.userID, Engine.instance.peerID, entities) diff --git a/packages/engine/src/networking/serialization/DataReader.ts b/packages/engine/src/networking/serialization/DataReader.ts index 26b24e7a3a..ae3005e7d7 100644 --- a/packages/engine/src/networking/serialization/DataReader.ts +++ b/packages/engine/src/networking/serialization/DataReader.ts @@ -216,7 +216,7 @@ export const readEntity = ( const ownerIndex = readUint32(v) as NetworkId const changeMask = readUint8(v) - const ownerId = network.userIndexToUserID.get(ownerIndex)! + const ownerId = network.userIndexToUserID[ownerIndex]! let entity = NetworkObjectComponent.getNetworkObject(ownerId, netId) if (entity && hasComponent(entity, NetworkObjectAuthorityTag)) entity = UndefinedEntity @@ -249,8 +249,8 @@ export const readMetadata = (v: ViewCursor) => { export const readDataPacket = (network: Network, packet: ArrayBuffer, jitterBufferTaskList: JitterBufferEntry[]) => { const view = createViewCursor(packet) const { userIndex, peerIndex, simulationTime } = readMetadata(view) - const fromUserID = network.userIndexToUserID.get(userIndex) - const fromPeerID = network.peerIndexToPeerID.get(peerIndex) + const fromUserID = network.userIndexToUserID[userIndex] + const fromPeerID = network.peerIndexToPeerID[peerIndex] const isLoopback = !!fromPeerID && fromPeerID === Engine.instance.peerID if (!fromUserID || isLoopback) return jitterBufferTaskList.push({ diff --git a/packages/engine/src/networking/serialization/DataWriter.test.ts b/packages/engine/src/networking/serialization/DataWriter.test.ts index 9a750478ad..4a39a0e006 100644 --- a/packages/engine/src/networking/serialization/DataWriter.test.ts +++ b/packages/engine/src/networking/serialization/DataWriter.test.ts @@ -474,8 +474,8 @@ describe('DataWriter', () => { ownerId: userId }) - network.userIndexToUserID.set(userIndex, userId) - network.userIDToUserIndex.set(userId, userIndex) + network.userIndexToUserID[userIndex] = userId + network.userIDToUserIndex[userId] = userIndex }) writeEntities(writeView, network, entities) @@ -568,8 +568,8 @@ describe('DataWriter', () => { ownerId: userId }) - network.userIndexToUserID.set(userIndex, userId) - network.userIDToUserIndex.set(userId, userIndex) + network.userIndexToUserID[userIndex] = userId + network.userIDToUserIndex[userId] = userIndex }) const packet = write(network, Engine.instance.userID, Engine.instance.peerID, entities) diff --git a/packages/engine/src/networking/serialization/DataWriter.ts b/packages/engine/src/networking/serialization/DataWriter.ts index 60364ea658..df7bc0dafe 100644 --- a/packages/engine/src/networking/serialization/DataWriter.ts +++ b/packages/engine/src/networking/serialization/DataWriter.ts @@ -295,7 +295,7 @@ export const writeEntities = (v: ViewCursor, network: Network, entities: Entity[ const entity = entities[i] const networkId = NetworkObjectComponent.networkId[entity] as NetworkId const ownerId = getComponent(entity, NetworkObjectComponent).ownerId - const ownerIndex = network.userIDToUserIndex.get(ownerId)! + const ownerIndex = network.userIDToUserIndex[ownerId]! count += writeEntity(v, networkId, ownerIndex, entity, entitySchema) ? 1 : 0 } @@ -305,8 +305,8 @@ export const writeEntities = (v: ViewCursor, network: Network, entities: Entity[ } export const writeMetadata = (v: ViewCursor, network: Network, userId: UserID, peerID: PeerID) => { - writeUint32(v, network.userIDToUserIndex.get(userId)!) - writeUint32(v, network.peerIDToPeerIndex.get(peerID)!) + writeUint32(v, network.userIDToUserIndex[userId]) + writeUint32(v, network.peerIDToPeerIndex[peerID]) writeFloat64(v, getState(EngineState).simulationTime) } diff --git a/packages/engine/src/networking/systems/IncomingNetworkSystem.ts b/packages/engine/src/networking/systems/IncomingNetworkSystem.ts index 7d1d23a6af..03b2971631 100644 --- a/packages/engine/src/networking/systems/IncomingNetworkSystem.ts +++ b/packages/engine/src/networking/systems/IncomingNetworkSystem.ts @@ -82,7 +82,6 @@ function oldestFirstComparator(a: JitterBufferEntry, b: JitterBufferEntry) { const execute = () => { const engineState = getState(EngineState) - if (!engineState.isEngineInitialized) return const { jitterBufferTaskList, jitterBufferDelay, incomingMessageQueueUnreliable, incomingMessageQueueUnreliableIDs } = getState(IncomingNetworkState) diff --git a/packages/engine/src/networking/systems/MediasoupDataProducerConsumerState.tsx b/packages/engine/src/networking/systems/MediasoupDataProducerConsumerState.tsx index 92e5e00e50..c56f6166e3 100644 --- a/packages/engine/src/networking/systems/MediasoupDataProducerConsumerState.tsx +++ b/packages/engine/src/networking/systems/MediasoupDataProducerConsumerState.tsx @@ -28,6 +28,7 @@ import { defineAction, defineState, getState, none, receiveActions } from '@ethe import { Validator, matches, matchesPeerID } from '../../common/functions/MatchesUtils' import { Engine } from '../../ecs/classes/Engine' import { defineSystem } from '../../ecs/functions/SystemFunctions' +import { InstanceID } from '../../schemas/networking/instance.schema' export class MediasoupDataProducerActions { static requestProducer = defineAction({ @@ -76,6 +77,7 @@ export class MediasoupDataConsumerActions { consumerID: matches.string, peerID: matchesPeerID, producerID: matches.string, + transportID: matches.string, dataChannel: matches.string as Validator, sctpStreamParameters: matches.object, appData: matches.object as Validator, @@ -101,12 +103,12 @@ export const MediasoupDataProducerConsumerState = defineState({ name: 'ee.engine.network.mediasoup.DataProducerConsumerState', initial: {} as Record< - string, // NetworkID + InstanceID, { producers: { [producerID: string]: { producerID: string - transportId: string + transportID: string protocol: string sctpStreamParameters: any dataChannel: DataChannelType @@ -116,6 +118,7 @@ export const MediasoupDataProducerConsumerState = defineState({ consumers: { [consumerID: string]: { consumerID: string + transportID: string dataChannel: DataChannelType sctpStreamParameters: any appData: any @@ -125,7 +128,7 @@ export const MediasoupDataProducerConsumerState = defineState({ } >, - getProducerByPeer: (networkID: string, peerID: string) => { + getProducerByPeer: (networkID: InstanceID, peerID: string) => { const state = getState(MediasoupDataProducerConsumerState)[networkID] if (!state) return @@ -135,7 +138,7 @@ export const MediasoupDataProducerConsumerState = defineState({ return getState(MediasoupDataProducersConsumersObjectsState).producers[producer.producerID] }, - getProducerByDataChannel: (networkID: string, dataChannel: DataChannelType) => { + getProducerByDataChannel: (networkID: InstanceID, dataChannel: DataChannelType) => { const state = getState(MediasoupDataProducerConsumerState)[networkID] if (!state) return @@ -149,14 +152,14 @@ export const MediasoupDataProducerConsumerState = defineState({ [ MediasoupDataProducerActions.producerCreated, (state, action: typeof MediasoupDataProducerActions.producerCreated.matches._TYPE) => { - const networkID = action.$network + const networkID = action.$network as InstanceID if (!state.value[networkID]) { state.merge({ [networkID]: { producers: {}, consumers: {} } }) } state[networkID].producers.merge({ [action.producerID]: { producerID: action.producerID, - transportId: action.transportID, + transportID: action.transportID, protocol: action.protocol, sctpStreamParameters: action.sctpStreamParameters as any, dataChannel: action.dataChannel, @@ -180,7 +183,7 @@ export const MediasoupDataProducerConsumerState = defineState({ cachedActions.splice(cachedActions.indexOf(cachedAction), 1) } - const networkID = action.$network + const networkID = action.$network as InstanceID if (!state.value[networkID]) return state[networkID].producers[action.producerID].set(none) @@ -193,13 +196,14 @@ export const MediasoupDataProducerConsumerState = defineState({ [ MediasoupDataConsumerActions.consumerCreated, (state, action: typeof MediasoupDataConsumerActions.consumerCreated.matches._TYPE) => { - const networkID = action.$network + const networkID = action.$network as InstanceID if (!state.value[networkID]) { state.merge({ [networkID]: { producers: {}, consumers: {} } }) } state[networkID].consumers.merge({ [action.consumerID]: { consumerID: action.consumerID, + transportID: action.transportID, dataChannel: action.dataChannel, sctpStreamParameters: action.sctpStreamParameters as any, appData: action.appData as any, @@ -211,7 +215,7 @@ export const MediasoupDataProducerConsumerState = defineState({ [ MediasoupDataConsumerActions.consumerClosed, (state, action: typeof MediasoupDataConsumerActions.consumerClosed.matches._TYPE) => { - const networkID = action.$network + const networkID = action.$network as InstanceID if (!state.value[networkID]) return state[networkID].consumers[action.consumerID].set(none) diff --git a/packages/engine/src/networking/systems/MediasoupMediaProducerConsumerState.tsx b/packages/engine/src/networking/systems/MediasoupMediaProducerConsumerState.tsx index 3821d5f628..ffff02e16e 100644 --- a/packages/engine/src/networking/systems/MediasoupMediaProducerConsumerState.tsx +++ b/packages/engine/src/networking/systems/MediasoupMediaProducerConsumerState.tsx @@ -42,7 +42,7 @@ import { Validator, matches, matchesPeerID } from '../../common/functions/Matche import { isClient } from '../../common/functions/getEnvironment' import { Engine } from '../../ecs/classes/Engine' import { defineSystem } from '../../ecs/functions/SystemFunctions' -import { UserID } from '../../schemas/user/user.schema' +import { InstanceID } from '../../schemas/networking/instance.schema' import { MediaStreamAppData, MediaTagType, NetworkState } from '../NetworkState' export class MediaProducerActions { @@ -66,6 +66,7 @@ export class MediaProducerActions { type: 'ee.engine.network.mediasoup.MEDIA_PRODUCER_CREATED', requestID: matches.string, producerID: matches.string, + transportID: matches.string, peerID: matchesPeerID, mediaTag: matches.string as Validator, channelID: matches.string as Validator, @@ -101,6 +102,7 @@ export class MediasoupMediaConsumerActions { static consumerCreated = defineAction({ type: 'ee.engine.network.mediasoup.MEDIA_CREATED_CONSUMER', consumerID: matches.string, + transportID: matches.string, peerID: matchesPeerID, mediaTag: matches.string as Validator, channelID: matches.string as Validator, @@ -142,13 +144,14 @@ export const MediasoupMediaProducerConsumerState = defineState({ name: 'ee.engine.network.mediasoup.MediasoupMediaProducerConsumerState', initial: {} as Record< - string, // NetworkID + InstanceID, { producers: { [producerID: string]: { producerID: string peerID: PeerID mediaTag: DataChannelType + transportID: string channelID: ChannelID paused?: boolean globalMute?: boolean @@ -159,6 +162,7 @@ export const MediasoupMediaProducerConsumerState = defineState({ consumerID: string peerID: PeerID mediaTag: DataChannelType + transportID: string channelID: ChannelID producerID: string paused?: boolean @@ -170,7 +174,7 @@ export const MediasoupMediaProducerConsumerState = defineState({ } >, - getProducerByPeerIdAndMediaTag: (networkID: string, peerID: string, mediaTag: MediaTagType) => { + getProducerByPeerIdAndMediaTag: (networkID: InstanceID, peerID: string, mediaTag: MediaTagType) => { const state = getState(MediasoupMediaProducerConsumerState)[networkID] if (!state) return @@ -180,7 +184,7 @@ export const MediasoupMediaProducerConsumerState = defineState({ return getState(MediasoupMediaProducersConsumersObjectsState).producers[producer.producerID] }, - getConsumerByPeerIdAndMediaTag: (networkID: string, peerID: string, tag: MediaTagType) => { + getConsumerByPeerIdAndMediaTag: (networkID: InstanceID, peerID: string, tag: MediaTagType) => { const state = getState(MediasoupMediaProducerConsumerState)[networkID] if (!state) return @@ -194,12 +198,13 @@ export const MediasoupMediaProducerConsumerState = defineState({ [ MediaProducerActions.producerCreated, (state, action: typeof MediaProducerActions.producerCreated.matches._TYPE) => { - const networkID = action.$network + const networkID = action.$network as InstanceID if (!state.value[networkID]) { state.merge({ [networkID]: { producers: {}, consumers: {} } }) } state[networkID].producers.merge({ [action.producerID]: { + transportID: action.transportID, producerID: action.producerID, peerID: action.peerID, mediaTag: action.mediaTag, @@ -224,7 +229,7 @@ export const MediasoupMediaProducerConsumerState = defineState({ cachedActions.splice(cachedActions.indexOf(cachedAction), 1) } - const networkID = action.$network + const networkID = action.$network as InstanceID if (!state.value[networkID]) return state[networkID].producers[action.producerID].set(none) @@ -237,7 +242,7 @@ export const MediasoupMediaProducerConsumerState = defineState({ [ MediaProducerActions.producerPaused, (state, action: typeof MediaProducerActions.producerPaused.matches._TYPE) => { - const networkID = action.$network + const networkID = action.$network as InstanceID if (!state.value[networkID]?.producers[action.producerID]) return const producerState = state[networkID].producers[action.producerID] @@ -252,7 +257,7 @@ export const MediasoupMediaProducerConsumerState = defineState({ const { globalMute, paused } = action const network = getState(NetworkState).networks[networkID] - const media = network.peers.get(peerID)?.media + const media = network.peers[peerID]?.media if (media && media[mediatag]) { media[mediatag].paused = paused media[mediatag].globalMute = globalMute @@ -262,7 +267,7 @@ export const MediasoupMediaProducerConsumerState = defineState({ [ MediasoupMediaConsumerActions.consumerCreated, (state, action: typeof MediasoupMediaConsumerActions.consumerCreated.matches._TYPE) => { - const networkID = action.$network + const networkID = action.$network as InstanceID if (!state.value[networkID]) { state.merge({ [networkID]: { producers: {}, consumers: {} } }) } @@ -271,6 +276,7 @@ export const MediasoupMediaProducerConsumerState = defineState({ consumerID: action.consumerID, peerID: action.peerID, mediaTag: action.mediaTag, + transportID: action.transportID, channelID: action.channelID, producerID: action.producerID, kind: action.kind!, @@ -312,7 +318,7 @@ const execute = () => { receiveActions(MediasoupMediaProducerConsumerState) } -export const NetworkProducer = (props: { networkID: UserID; producerID: string }) => { +export const NetworkProducer = (props: { networkID: InstanceID; producerID: string }) => { const { networkID, producerID } = props const producerState = useHookstate( getMutableState(MediasoupMediaProducerConsumerState)[networkID].producers[producerID] @@ -338,7 +344,7 @@ export const NetworkProducer = (props: { networkID: UserID; producerID: string } const network = getState(NetworkState).networks[networkID] // remove from the peer state - const media = network.peers.get(peerID)?.media + const media = network.peers[peerID]?.media if (media && media[producer.appData.mediaTag]) { delete media[producer.appData.mediaTag] } @@ -386,7 +392,7 @@ export const NetworkProducer = (props: { networkID: UserID; producerID: string } return null } -export const NetworkConsumer = (props: { networkID: UserID; consumerID: string }) => { +export const NetworkConsumer = (props: { networkID: InstanceID; consumerID: string }) => { const { networkID, consumerID } = props const consumerState = useHookstate( getMutableState(MediasoupMediaProducerConsumerState)[networkID].consumers[consumerID] @@ -419,7 +425,7 @@ export const NetworkConsumer = (props: { networkID: UserID; consumerID: string } return null } -const NetworkReactor = (props: { networkID: UserID }) => { +const NetworkReactor = (props: { networkID: InstanceID }) => { const { networkID } = props const networkState = useHookstate(getMutableState(NetworkState).networks[networkID]) const producers = useHookstate(getMutableState(MediasoupMediaProducerConsumerState)[networkID].producers) @@ -428,13 +434,13 @@ const NetworkReactor = (props: { networkID: UserID }) => { useEffect(() => { if (producers?.value) for (const [producerID, producer] of Object.entries(producers.value)) { - if (!networkState.peers.value.get(producer.peerID)) { + if (!networkState.peers.value[producer.peerID]) { producers[producerID].set(none) } } if (consumers?.value) for (const [consumerID, consumer] of Object.entries(consumers.value)) { - if (!networkState.peers.value.get(consumer.peerID)) { + if (!networkState.peers.value[consumer.peerID]) { consumers[consumerID].set(none) } } @@ -456,8 +462,8 @@ const reactor = () => { const networkIDs = useHookstate(getMutableState(MediasoupMediaProducerConsumerState)) return ( <> - {networkIDs.keys.map((hostId: UserID) => ( - + {networkIDs.keys.map((id: InstanceID) => ( + ))} ) diff --git a/packages/engine/src/networking/systems/MediasoupTransportState.tsx b/packages/engine/src/networking/systems/MediasoupTransportState.tsx index 94dd65dab5..9e5509c874 100644 --- a/packages/engine/src/networking/systems/MediasoupTransportState.tsx +++ b/packages/engine/src/networking/systems/MediasoupTransportState.tsx @@ -25,10 +25,11 @@ Ethereal Engine. All Rights Reserved. import { PeerID } from '@etherealengine/common/src/interfaces/PeerID' import { defineAction, defineState, getState, none, receiveActions } from '@etherealengine/hyperflux' -import { matches, matchesPeerID } from '../../common/functions/MatchesUtils' +import { Validator, matches, matchesPeerID } from '../../common/functions/MatchesUtils' import { isClient } from '../../common/functions/getEnvironment' import { Engine } from '../../ecs/classes/Engine' import { defineSystem } from '../../ecs/functions/SystemFunctions' +import { InstanceID } from '../../schemas/networking/instance.schema' import { NetworkState } from '../NetworkState' import { Network } from '../classes/Network' @@ -54,7 +55,13 @@ export class MediasoupTransportActions { sctpParameters: matches.object, iceParameters: matches.object, iceCandidates: matches.arrayOf(matches.object), - dtlsParameters: matches.object + dtlsParameters: matches.object as Validator< + unknown, + { + role?: 'client' | 'server' | 'auto' + fingerprints: { algorithm: string; value: string }[] + } + > }) static requestTransportConnect = defineAction({ @@ -104,7 +111,7 @@ export const MediasoupTransportState = defineState({ >, getTransport: ( - networkID: string, + networkID: InstanceID, direction: 'send' | 'recv', peerID = getState(NetworkState).networks[networkID].hostPeerID ) => { diff --git a/packages/engine/src/networking/systems/OutgoingActionSystem.ts b/packages/engine/src/networking/systems/OutgoingActionSystem.ts index 3ced8aca1c..e04cddccc4 100644 --- a/packages/engine/src/networking/systems/OutgoingActionSystem.ts +++ b/packages/engine/src/networking/systems/OutgoingActionSystem.ts @@ -35,7 +35,7 @@ import { NetworkActions, NetworkState } from '../NetworkState' /** Publish to connected peers that peer information has changed */ export const updatePeers = (network: Network) => { const userNames = getState(WorldState).userNames - const peers = Array.from(network.peers.values()).map((peer) => { + const peers = Object.values(network.peers).map((peer) => { return { peerID: peer.peerID, peerIndex: peer.peerIndex, @@ -76,7 +76,7 @@ export const sendActionsAsHost = (network: Network) => { const outgoing = Engine.instance.store.actions.outgoing - for (const peerID of Array.from(network.peers.keys()) as PeerID[]) { + for (const peerID of Object.keys(network.peers) as PeerID[]) { const arr: Action[] = [] for (const a of [...actions]) { const action = { ...a } diff --git a/packages/engine/src/renderer/WebGLRendererSystem.ts b/packages/engine/src/renderer/WebGLRendererSystem.ts index 6f5eaa193c..55d7443e1d 100644 --- a/packages/engine/src/renderer/WebGLRendererSystem.ts +++ b/packages/engine/src/renderer/WebGLRendererSystem.ts @@ -36,14 +36,14 @@ import { WebGLRendererParameters } from 'three' -import { defineState, dispatchAction, getMutableState, getState, useHookstate } from '@etherealengine/hyperflux' +import { defineState, getMutableState, getState, useHookstate } from '@etherealengine/hyperflux' import { CameraComponent } from '../camera/components/CameraComponent' import { ExponentialMovingAverage } from '../common/classes/ExponentialAverageCurve' import { overrideOnBeforeCompile } from '../common/functions/OnBeforeCompilePlugin' import { nowMilliseconds } from '../common/functions/nowMilliseconds' import { Engine } from '../ecs/classes/Engine' -import { EngineActions, EngineState } from '../ecs/classes/EngineState' +import { EngineState } from '../ecs/classes/EngineState' import { getComponent } from '../ecs/functions/ComponentFunctions' import { defineSystem } from '../ecs/functions/SystemFunctions' import { ObjectLayers } from '../scene/constants/ObjectLayers' @@ -113,11 +113,7 @@ export class EngineRenderer { const context = this.supportWebGL2 ? canvas.getContext('webgl2')! : canvas.getContext('webgl')! if (!context) { - dispatchAction( - EngineActions.browserNotSupported({ - msg: 'Your browser does not have WebGL enabled. Please enable WebGL, or try another browser.' - }) as any - ) + /** @todo - add notice for webgl not supported */ } this.renderContext = context! diff --git a/packages/engine/src/scene/SceneClientModule.ts b/packages/engine/src/scene/SceneClientModule.ts index dd2d30d3bf..23f0e7add4 100644 --- a/packages/engine/src/scene/SceneClientModule.ts +++ b/packages/engine/src/scene/SceneClientModule.ts @@ -73,7 +73,6 @@ import { ParticleSystem } from './systems/ParticleSystemSystem' import { SceneLoadingSystem } from './systems/SceneLoadingSystem' import { SceneObjectDynamicLoadSystem } from './systems/SceneObjectDynamicLoadSystem' import { SceneObjectSystem } from './systems/SceneObjectSystem' -import { SceneObjectUpdateSystem } from './systems/SceneObjectUpdateSystem' import { VariantSystem } from './systems/VariantSystem' /** This const MUST be kept here, to ensure all components definitions are loaded by the time the scene loading occurs */ @@ -111,7 +110,6 @@ export const SceneComponents = [ SpawnPointComponent, SplineComponent, SystemComponent, - SplineComponent, SpotLightComponent, SystemComponent, VariantComponent, @@ -130,11 +128,5 @@ export const SceneSystemUpdateGroup = defineSystem({ export const SceneSystemLoadGroup = defineSystem({ uuid: 'ee.engine.scene.SceneSystemLoadGroup', - subSystems: [ - SceneLoadingSystem, - VariantSystem, - SceneObjectDynamicLoadSystem, - MaterialLibrarySystem, - SceneObjectUpdateSystem - ] + subSystems: [SceneLoadingSystem, VariantSystem, SceneObjectDynamicLoadSystem, MaterialLibrarySystem] }) diff --git a/packages/engine/src/scene/components/ModelComponent.ts b/packages/engine/src/scene/components/ModelComponent.ts index 46c71cc360..bfe6f7453b 100644 --- a/packages/engine/src/scene/components/ModelComponent.ts +++ b/packages/engine/src/scene/components/ModelComponent.ts @@ -32,6 +32,7 @@ import { AssetLoader } from '../../assets/classes/AssetLoader' import { LoopAnimationComponent } from '../../avatar/components/LoopAnimationComponent' import { EngineState } from '../../ecs/classes/EngineState' import { + ComponentType, defineComponent, getComponent, getMutableComponent, @@ -54,6 +55,19 @@ import { SceneAssetPendingTagComponent } from './SceneAssetPendingTagComponent' import { SceneObjectComponent } from './SceneObjectComponent' import { UUIDComponent } from './UUIDComponent' +function clearMaterials(model: ComponentType) { + if (!model.scene) return + try { + removeMaterialSource({ type: SourceType.MODEL, path: model.scene.userData.src ?? '' }) + } catch (e) { + if (e?.name === 'MaterialNotFound') { + console.warn('could not find material in source ' + model.scene.userData.src) + } else { + throw e + } + } +} + export const ModelComponent = defineComponent({ name: 'EE_model', jsonID: 'gltf-model', @@ -92,6 +106,7 @@ export const ModelComponent = defineComponent({ onRemove: (entity, component) => { if (component.scene.value) { + clearMaterials(component.value) removeObjectFromGroup(entity, component.scene.value) component.scene.set(null) } @@ -116,18 +131,10 @@ function ModelReactor() { // update src useEffect(() => { if (source === model.scene?.userData?.src) return - try { - if (model.scene) - try { - removeMaterialSource({ type: SourceType.MODEL, path: model.scene.userData.src }) - } catch (e) { - if (e?.name === 'MaterialNotFound') { - console.warn('could not find material in source ' + model.scene.userData.src) - } else { - throw e - } - } + if (model.scene) { + clearMaterials(model) + } if (!model.src) return const uuid = getComponent(entity, UUIDComponent) const fileExtension = model.src.split('.').pop()?.toLowerCase() diff --git a/packages/engine/src/scene/components/SplineComponent.ts b/packages/engine/src/scene/components/SplineComponent.ts index befd2c02de..b5a5b20909 100644 --- a/packages/engine/src/scene/components/SplineComponent.ts +++ b/packages/engine/src/scene/components/SplineComponent.ts @@ -53,7 +53,7 @@ const helperMaterial = new MeshLambertMaterial({ color: 'white' }) const lineGeometry = new BufferGeometry() lineGeometry.setAttribute('position', new BufferAttribute(new Float32Array(ARC_SEGMENTS * 3), 3)) -interface ISplineElement { +export interface ISplineElement { position: Vector3 quaternion: Quaternion } diff --git a/packages/engine/src/scene/functions/applyScreenshareToTexture.ts b/packages/engine/src/scene/functions/applyScreenshareToTexture.ts index 19b4741302..56eb17ddc9 100644 --- a/packages/engine/src/scene/functions/applyScreenshareToTexture.ts +++ b/packages/engine/src/scene/functions/applyScreenshareToTexture.ts @@ -63,10 +63,10 @@ export const applyVideoToTexture = ( shader.fragmentShader = shader.fragmentShader.replace('void main() {', `uniform vec4 clipColor;\nvoid main() {\n`) const mapFragment = `#ifdef USE_MAP - vec4 sampledDiffuseColor = texture2D( map, vUv ); + vec4 sampledDiffuseColor = texture2D( map, vMapUv ); // Newly added clipping Logic ///// - if (vUv.x < 0.0 || vUv.x > 1.0 || vUv.y < 0.0 || vUv.y > 1.0) sampledDiffuseColor = clipColor; + if (vMapUv.x < 0.0 || vMapUv.x > 1.0 || vMapUv.y < 0.0 || vMapUv.y > 1.0) sampledDiffuseColor = clipColor; ///////////////////////////// #ifdef DECODE_VIDEO_TEXTURE diff --git a/packages/engine/src/scene/systems/SceneLoadingSystem.tsx b/packages/engine/src/scene/systems/SceneLoadingSystem.tsx index e361f56891..4ea82672d7 100755 --- a/packages/engine/src/scene/systems/SceneLoadingSystem.tsx +++ b/packages/engine/src/scene/systems/SceneLoadingSystem.tsx @@ -411,7 +411,6 @@ const sceneAssetPendingTagQuery = defineQuery([SceneAssetPendingTagComponent]) const reactor = () => { const sceneData = useHookstate(getMutableState(SceneState).sceneData) - const isEngineInitialized = useHookstate(getMutableState(EngineState).isEngineInitialized) const sceneAssetPendingTagQuery = useQuery([SceneAssetPendingTagComponent]) const assetLoadingState = useHookstate(SceneAssetPendingTagComponent.loadingProgress) @@ -437,8 +436,8 @@ const reactor = () => { }, [sceneAssetPendingTagQuery, assetLoadingState]) useEffect(() => { - if (isEngineInitialized.value) updateSceneFromJSON() - }, [sceneData, isEngineInitialized]) + updateSceneFromJSON() + }, [sceneData]) useEffect(() => { addActionReceptor(AppLoadingServiceReceptor) diff --git a/packages/engine/src/scene/systems/SceneObjectSystem.tsx b/packages/engine/src/scene/systems/SceneObjectSystem.tsx index 48d71e39b7..4ba6fca1f7 100644 --- a/packages/engine/src/scene/systems/SceneObjectSystem.tsx +++ b/packages/engine/src/scene/systems/SceneObjectSystem.tsx @@ -42,13 +42,14 @@ import { EngineState } from '../../ecs/classes/EngineState' import { Entity } from '../../ecs/classes/Entity' import { defineQuery, getComponent, hasComponent, useOptionalComponent } from '../../ecs/functions/ComponentFunctions' import { defineSystem } from '../../ecs/functions/SystemFunctions' -import { registerMaterial, unregisterMaterial } from '../../renderer/materials/functions/MaterialLibraryFunctions' import { RendererState } from '../../renderer/RendererState' +import { registerMaterial, unregisterMaterial } from '../../renderer/materials/functions/MaterialLibraryFunctions' import { DistanceFromCameraComponent, FrustumCullCameraComponent } from '../../transform/components/DistanceComponents' import { isMobileXRHeadset } from '../../xr/XRState' import { CallbackComponent } from '../components/CallbackComponent' import { GroupComponent, GroupQueryReactor, Object3DWithEntity } from '../components/GroupComponent' import { ShadowComponent } from '../components/ShadowComponent' +import { SpawnPointComponent } from '../components/SpawnPointComponent' import { UpdatableCallback, UpdatableComponent } from '../components/UpdatableComponent' import { VisibleComponent } from '../components/VisibleComponent' import { EnvironmentSystem } from './EnvironmentSystem' @@ -98,6 +99,7 @@ export function setupObject(obj: Object3DWithEntity, force = false) { const groupQuery = defineQuery([GroupComponent]) const updatableQuery = defineQuery([UpdatableComponent, CallbackComponent]) +const spawnPointQuery = defineQuery([SpawnPointComponent]) function SceneObjectReactor(props: { entity: Entity; obj: Object3DWithEntity }) { const { entity, obj } = props @@ -158,6 +160,8 @@ const execute = () => { ) for (const obj of group) obj.visible = visible } + + for (const entity of spawnPointQuery()) getComponent(entity, SpawnPointComponent).helperBox?.update() } const reactor = () => { diff --git a/packages/engine/src/scene/systems/SceneObjectUpdateSystem.ts b/packages/engine/src/scene/systems/SceneObjectUpdateSystem.ts deleted file mode 100644 index 52345ca2b9..0000000000 --- a/packages/engine/src/scene/systems/SceneObjectUpdateSystem.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ -import { defineActionQueue } from '@etherealengine/hyperflux' - -import { EngineActions } from '../../ecs/classes/EngineState' -import { defineQuery, getComponent, hasComponent } from '../../ecs/functions/ComponentFunctions' -import { defineSystem } from '../../ecs/functions/SystemFunctions' -import { CloudComponent } from '../components/CloudComponent' -import { OceanComponent } from '../components/OceanComponent' -import { SpawnPointComponent } from '../components/SpawnPointComponent' -import { updateCloud } from '../functions/loaders/CloudFunctions' -import { updateOcean } from '../functions/loaders/OceanFunctions' - -const cloudQuery = defineQuery([CloudComponent]) -const oceanQuery = defineQuery([OceanComponent]) -const spawnPointComponent = defineQuery([SpawnPointComponent]) - -const modifyPropertyActionQueue = defineActionQueue(EngineActions.sceneObjectUpdate.matches) - -const execute = () => { - for (const action of modifyPropertyActionQueue()) { - for (const entity of action.entities) { - if (hasComponent(entity, CloudComponent)) updateCloud(entity) - if (hasComponent(entity, OceanComponent)) updateOcean(entity) - } - } - - for (const entity of cloudQuery.enter()) updateCloud(entity) - for (const entity of oceanQuery.enter()) updateOcean(entity) - for (const entity of spawnPointComponent()) getComponent(entity, SpawnPointComponent).helperBox?.update() -} - -export const SceneObjectUpdateSystem = defineSystem({ - uuid: 'ee.engine.SceneObjectUpdateSystem', - execute -}) diff --git a/packages/engine/src/schemas/bot/bot.schema.ts b/packages/engine/src/schemas/bot/bot.schema.ts index 0329350cbd..37abec1517 100644 --- a/packages/engine/src/schemas/bot/bot.schema.ts +++ b/packages/engine/src/schemas/bot/bot.schema.ts @@ -28,7 +28,7 @@ import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' import type { Static } from '@feathersjs/typebox' import { getValidator, querySyntax, Type } from '@feathersjs/typebox' import { TypedString } from '../../common/types/TypeboxUtils' -import { instanceSchema } from '../networking/instance.schema' +import { InstanceID, instanceSchema } from '../networking/instance.schema' import { locationSchema } from '../social/location.schema' import { dataValidator, queryValidator } from '../validators' import { botCommandDataSchema } from './bot-command.schema' @@ -45,7 +45,7 @@ export const botSchema = Type.Object( }), name: Type.String(), description: Type.String(), - instanceId: Type.String({ + instanceId: TypedString({ format: 'uuid' }), locationId: Type.String({ diff --git a/packages/engine/src/schemas/interfaces/Channel.ts b/packages/engine/src/schemas/interfaces/Channel.ts index 50cdc294bf..fd6bd0ba8a 100755 --- a/packages/engine/src/schemas/interfaces/Channel.ts +++ b/packages/engine/src/schemas/interfaces/Channel.ts @@ -24,13 +24,14 @@ Ethereal Engine. All Rights Reserved. */ import { ChannelID } from '@etherealengine/common/src/dbmodels/Channel' +import { InstanceID } from '../networking/instance.schema' import { ChannelUser } from './ChannelUser' import { Message } from './Message' export type Channel = { id: ChannelID name: string - instanceId: string | null + instanceId: InstanceID | null createdAt: string updatedAt: string updateNeeded: boolean diff --git a/packages/engine/src/schemas/interfaces/ChannelUser.ts b/packages/engine/src/schemas/interfaces/ChannelUser.ts index 4ebef082ea..6b9c46d425 100755 --- a/packages/engine/src/schemas/interfaces/ChannelUser.ts +++ b/packages/engine/src/schemas/interfaces/ChannelUser.ts @@ -25,6 +25,7 @@ Ethereal Engine. All Rights Reserved. import { ChannelID } from '@etherealengine/common/src/dbmodels/Channel' import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' +import { InstanceID } from '../networking/instance.schema' import { UserType } from '../user/user.schema' interface ChannelInterface { @@ -32,7 +33,7 @@ interface ChannelInterface { name: string users: UserType[] userIds: UserID[] - instanceId: string + instanceId: InstanceID } export type ChannelUser = { diff --git a/packages/engine/src/schemas/networking/instance-attendance.schema.ts b/packages/engine/src/schemas/networking/instance-attendance.schema.ts index 6491cacf0c..faa5a2f553 100644 --- a/packages/engine/src/schemas/networking/instance-attendance.schema.ts +++ b/packages/engine/src/schemas/networking/instance-attendance.schema.ts @@ -29,6 +29,7 @@ import type { Static } from '@feathersjs/typebox' import { getValidator, querySyntax, Type } from '@feathersjs/typebox' import { TypedString } from '../../common/types/TypeboxUtils' import { dataValidator, queryValidator } from '../validators' +import { InstanceID } from './instance.schema' export const instanceAttendancePath = 'instance-attendance' @@ -43,7 +44,7 @@ export const instanceAttendanceSchema = Type.Object( sceneId: Type.String(), isChannel: Type.Boolean(), ended: Type.Boolean(), - instanceId: Type.String({ + instanceId: TypedString({ format: 'uuid' }), userId: TypedString({ diff --git a/packages/engine/src/schemas/networking/instance-authorized-user.schema.ts b/packages/engine/src/schemas/networking/instance-authorized-user.schema.ts index 6043417765..129afa4dc6 100644 --- a/packages/engine/src/schemas/networking/instance-authorized-user.schema.ts +++ b/packages/engine/src/schemas/networking/instance-authorized-user.schema.ts @@ -29,6 +29,7 @@ import type { Static } from '@feathersjs/typebox' import { getValidator, querySyntax, Type } from '@feathersjs/typebox' import { TypedString } from '../../common/types/TypeboxUtils' import { dataValidator, queryValidator } from '../validators' +import { InstanceID } from './instance.schema' export const instanceAuthorizedUserPath = 'instance-authorized-user' @@ -43,7 +44,7 @@ export const instanceAuthorizedUserSchema = Type.Object( userId: TypedString({ format: 'uuid' }), - instanceId: Type.String({ + instanceId: TypedString({ format: 'uuid' }), createdAt: Type.String({ format: 'date-time' }), diff --git a/packages/engine/src/schemas/networking/instance.schema.ts b/packages/engine/src/schemas/networking/instance.schema.ts index d701e0afa7..e4e436e3a1 100644 --- a/packages/engine/src/schemas/networking/instance.schema.ts +++ b/packages/engine/src/schemas/networking/instance.schema.ts @@ -24,18 +24,22 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html +import { OpaqueType } from '@etherealengine/common/src/interfaces/OpaqueType' import type { Static } from '@feathersjs/typebox' import { getValidator, querySyntax, Type } from '@feathersjs/typebox' +import { TypedString } from '../../common/types/TypeboxUtils' import { dataValidator, queryValidator } from '../validators' export const instancePath = 'instance' export const instanceMethods = ['find', 'create', 'patch', 'remove', 'get'] as const +export type InstanceID = OpaqueType<'InstanceID'> & string + // Main data model schema export const instanceSchema = Type.Object( { - id: Type.String({ + id: TypedString({ format: 'uuid' }), ipAddress: Type.Optional(Type.String()), diff --git a/packages/engine/src/schemas/setting/client-setting.schema.ts b/packages/engine/src/schemas/setting/client-setting.schema.ts index 1b7fc15bda..cc03fea6e9 100644 --- a/packages/engine/src/schemas/setting/client-setting.schema.ts +++ b/packages/engine/src/schemas/setting/client-setting.schema.ts @@ -114,6 +114,7 @@ export const clientSettingSchema = Type.Object( themeSettings: Type.Record(Type.String(), Type.Ref(clientThemeOptionsSchema)), themeModes: Type.Record(Type.String(), Type.String()), key8thWall: Type.String(), + privacyPolicy: Type.String(), homepageLinkButtonEnabled: Type.Boolean(), homepageLinkButtonRedirect: Type.String(), homepageLinkButtonText: Type.String(), @@ -155,6 +156,7 @@ export const clientSettingDataSchema = Type.Pick( 'themeSettings', 'themeModes', 'key8thWall', + 'privacyPolicy', 'homepageLinkButtonEnabled', 'homepageLinkButtonRedirect', 'homepageLinkButtonText' @@ -195,6 +197,7 @@ export const clientSettingQueryProperties = Type.Pick(clientSettingSchema, [ // 'themeSettings', // 'themeModes', 'key8thWall', + 'privacyPolicy', 'homepageLinkButtonEnabled', 'homepageLinkButtonRedirect', 'homepageLinkButtonText' diff --git a/packages/engine/src/schemas/user/user-kick.schema.ts b/packages/engine/src/schemas/user/user-kick.schema.ts index 2f3fba4108..7952978edb 100644 --- a/packages/engine/src/schemas/user/user-kick.schema.ts +++ b/packages/engine/src/schemas/user/user-kick.schema.ts @@ -28,6 +28,7 @@ import { OpaqueType } from '@etherealengine/common/src/interfaces/OpaqueType' import type { Static } from '@feathersjs/typebox' import { getValidator, querySyntax, Type } from '@feathersjs/typebox' import { TypedString } from '../../common/types/TypeboxUtils' +import { InstanceID } from '../networking/instance.schema' import { dataValidator, queryValidator } from '../validators' import { UserID } from './user.schema' @@ -47,7 +48,7 @@ export const userKickSchema = Type.Object( userId: TypedString({ format: 'uuid' }), - instanceId: Type.String({ + instanceId: TypedString({ format: 'uuid' }), createdAt: Type.String({ format: 'date-time' }), diff --git a/packages/engine/src/transform/systems/TransformSystem.ts b/packages/engine/src/transform/systems/TransformSystem.ts index 466b4c83e2..a9b26b2dbe 100755 --- a/packages/engine/src/transform/systems/TransformSystem.ts +++ b/packages/engine/src/transform/systems/TransformSystem.ts @@ -28,13 +28,13 @@ import { useEffect } from 'react' import { Camera, Frustum, Matrix4, Mesh, Skeleton, SkinnedMesh, Vector3 } from 'three' import { insertionSort } from '@etherealengine/common/src/utils/insertionSort' -import { defineActionQueue, getMutableState, getState, none } from '@etherealengine/hyperflux' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' import { AnimationComponent } from '../../avatar/components/AnimationComponent' import { CameraComponent } from '../../camera/components/CameraComponent' import { V_000 } from '../../common/constants/MathConstants' import { Engine } from '../../ecs/classes/Engine' -import { EngineActions, EngineState } from '../../ecs/classes/EngineState' +import { EngineState } from '../../ecs/classes/EngineState' import { Entity } from '../../ecs/classes/Entity' import { defineQuery, getComponent, getOptionalComponent, hasComponent } from '../../ecs/functions/ComponentFunctions' import { defineSystem } from '../../ecs/functions/SystemFunctions' @@ -213,9 +213,6 @@ const getDistanceSquaredFromTarget = (entity: Entity, targetPosition: Vector3) = const _frustum = new Frustum() const _projScreenMatrix = new Matrix4() -/** @deprecated */ -const modifyPropertyActionQueue = defineActionQueue(EngineActions.sceneObjectUpdate.matches) - const originChildEntities = new Set() /** get list of entities that are children of the world origin */ @@ -392,18 +389,6 @@ const execute = () => { for (const entity of staticBoundingBoxQuery.enter()) computeBoundingBox(entity) for (const entity of dynamicBoundingBoxQuery()) updateBoundingBox(entity) - /** @todo - refactor */ - for (const action of modifyPropertyActionQueue()) { - for (const entity of action.entities) { - if ( - hasComponent(entity, BoundingBoxComponent) && - hasComponent(entity, TransformComponent) && - hasComponent(entity, GroupComponent) - ) - updateBoundingBox(entity) - } - } - const cameraPosition = getComponent(Engine.instance.cameraEntity, TransformComponent).position const camera = getComponent(Engine.instance.cameraEntity, CameraComponent) for (const entity of distanceFromCameraQuery()) diff --git a/packages/engine/tests/util/createMockNetwork.ts b/packages/engine/tests/util/createMockNetwork.ts index 3a5f842c35..ab690b91da 100644 --- a/packages/engine/tests/util/createMockNetwork.ts +++ b/packages/engine/tests/util/createMockNetwork.ts @@ -23,14 +23,16 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' import { getMutableState } from '@etherealengine/hyperflux' import { createNetwork, NetworkTopics } from '../../src/networking/classes/Network' import { addNetwork, NetworkState } from '../../src/networking/NetworkState' +import { InstanceID } from '../../src/schemas/networking/instance.schema' +import { UserID } from '../../src/schemas/user/user.schema' export const createMockNetwork = (networkType = NetworkTopics.world) => { - if (networkType === NetworkTopics.world) getMutableState(NetworkState).hostIds.world.set(networkType as any as UserID) - else getMutableState(NetworkState).hostIds.media.set(networkType as any as UserID) - addNetwork(createNetwork(networkType, networkType as any as UserID, networkType)) + if (networkType === NetworkTopics.world) + getMutableState(NetworkState).hostIds.world.set(networkType as any as InstanceID) + else getMutableState(NetworkState).hostIds.media.set(networkType as any as InstanceID) + addNetwork(createNetwork(networkType as any as InstanceID, networkType as any as UserID, networkType)) } diff --git a/packages/instanceserver/src/MediaRecordingFunctions.ts b/packages/instanceserver/src/MediaRecordingFunctions.ts index 6b0c855392..6f9767f77a 100644 --- a/packages/instanceserver/src/MediaRecordingFunctions.ts +++ b/packages/instanceserver/src/MediaRecordingFunctions.ts @@ -212,7 +212,7 @@ export const startMediaRecording = async (recordingID: RecordingID, schema: Reco const mediaStreams = {} as Record for (const [peerID, dataChannels] of Object.entries(schema)) { - const peer = network.peers.get(peerID as PeerID)! + const peer = network.peers[peerID as PeerID]! const peerMedia = Object.entries(peer.media!).filter(([type]) => dataChannels.includes(type as DataChannelType)) if (peerMedia.length) { diff --git a/packages/instanceserver/src/MediasoupServerSystem.tsx b/packages/instanceserver/src/MediasoupServerSystem.tsx index 042f9feb81..a3b81b483b 100644 --- a/packages/instanceserver/src/MediasoupServerSystem.tsx +++ b/packages/instanceserver/src/MediasoupServerSystem.tsx @@ -43,7 +43,7 @@ import { MediasoupTransportActions, MediasoupTransportStateSystem } from '@etherealengine/engine/src/networking/systems/MediasoupTransportState' -import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { defineActionQueue, getMutableState, getState, useHookstate } from '@etherealengine/hyperflux' import { SocketWebRTCServerNetwork } from './SocketWebRTCServerFunctions' import { @@ -98,7 +98,7 @@ const execute = () => { } } -export const DataChannel = (props: { networkID: UserID; dataChannelType: DataChannelType }) => { +export const DataChannel = (props: { networkID: InstanceID; dataChannelType: DataChannelType }) => { const { networkID, dataChannelType } = props useEffect(() => { @@ -113,7 +113,7 @@ export const DataChannel = (props: { networkID: UserID; dataChannelType: DataCha return null } -const NetworkReactor = (props: { networkID: UserID }) => { +const NetworkReactor = (props: { networkID: InstanceID }) => { const { networkID } = props const dataChannelRegistry = useHookstate(getMutableState(DataChannelRegistryState)) return ( @@ -131,8 +131,8 @@ export const reactor = () => { .map(([networkID, network]) => networkID) return ( <> - {networkIDs.map((hostId: UserID) => ( - + {networkIDs.map((id: InstanceID) => ( + ))} ) diff --git a/packages/instanceserver/src/NetworkFunctions.ts b/packages/instanceserver/src/NetworkFunctions.ts index f0bc2e9ec1..bb0e17a670 100755 --- a/packages/instanceserver/src/NetworkFunctions.ts +++ b/packages/instanceserver/src/NetworkFunctions.ts @@ -49,12 +49,13 @@ import { ServerState } from '@etherealengine/server-core/src/ServerState' import getLocalServerIp from '@etherealengine/server-core/src/util/get-local-server-ip' import { NetworkObjectComponent } from '@etherealengine/engine/src/networking/components/NetworkObjectComponent' +import { NetworkState } from '@etherealengine/engine/src/networking/NetworkState' import { MediasoupTransportState } from '@etherealengine/engine/src/networking/systems/MediasoupTransportState' import { instanceAuthorizedUserPath } from '@etherealengine/engine/src/schemas/networking/instance-authorized-user.schema' import { inviteCodeLookupPath } from '@etherealengine/engine/src/schemas/social/invite-code-lookup.schema' import { userKickPath } from '@etherealengine/engine/src/schemas/user/user-kick.schema' import { UserID, UserType } from '@etherealengine/engine/src/schemas/user/user.schema' -import { toDateTimeSql } from '@etherealengine/server-core/src/util/get-datetime-sql' +import { toDateTimeSql } from '@etherealengine/server-core/src/util/datetime-sql' import { InstanceServerState } from './InstanceServerState' import { SocketWebRTCServerNetwork, WebRTCTransportExtension } from './SocketWebRTCServerFunctions' @@ -187,7 +188,7 @@ export const authorizeUserToJoinServer = async (app: Application, instance: Inst } export function getUserIdFromPeerID(network: SocketWebRTCServerNetwork, peerID: PeerID) { - const client = Array.from(network.peers.values()).find((c) => c.peerID === peerID) + const client = Object.values(network.peers).find((c) => c.peerID === peerID) return client?.userId } @@ -202,31 +203,34 @@ export const handleConnectingPeer = ( // Create a new client object // and add to the dictionary - const existingUser = Array.from(network.peers.values()).find((client) => client.userId === userId) + const existingUser = Object.values(network.peers).find((client) => client.userId === userId) const userIndex = existingUser ? existingUser.userIndex : network.userIndexCount++ const peerIndex = network.peerIndexCount++ - network.peers.set(peerID, { - userId, - userIndex: userIndex, - spark: spark, - peerIndex, - peerID, - media: {}, - lastSeenTs: Date.now() + const networkState = getMutableState(NetworkState).networks[network.id] + networkState.peers.merge({ + [peerID]: { + userId, + userIndex: userIndex, + spark: spark, + peerIndex, + peerID, + media: {}, + lastSeenTs: Date.now() + } }) - if (!network.users.has(userId)) { - network.users.set(userId, [peerID]) + if (!network.users[userId]) { + networkState.users.merge({ [userId]: [peerID] }) } else { - network.users.get(userId)!.push(peerID) + network.users[userId]!.push(peerID) } const worldState = getMutableState(WorldState) worldState.userNames[userId].set(user.name) - network.userIDToUserIndex.set(userId, userIndex) - network.userIndexToUserID.set(userIndex, userId) + network.userIDToUserIndex[userId] = userIndex + network.userIndexToUserID[userIndex] = userId updatePeers(network) @@ -241,7 +245,7 @@ export const handleConnectingPeer = ( return { routerRtpCapabilities: network.transport.routers[0].rtpCapabilities, - peerIndex: network.peerIDToPeerIndex.get(peerID)!, + peerIndex: network.peerIDToPeerIndex[peerID]!, cachedActions } } @@ -318,7 +322,7 @@ const getUserSpawnFromInvite = async ( } export const handleIncomingActions = (network: SocketWebRTCServerNetwork, peerID: PeerID) => (message) => { - const networkPeer = network.peers.get(peerID) + const networkPeer = network.peers[peerID] if (!networkPeer) return networkPeer.lastSeenTs = Date.now() @@ -338,7 +342,7 @@ export const handleIncomingActions = (network: SocketWebRTCServerNetwork, peerID export async function handleDisconnect(network: SocketWebRTCServerNetwork, peerID: PeerID): Promise { const userId = getUserIdFromPeerID(network, peerID) as UserID - const disconnectedClient = network.peers.get(peerID) + const disconnectedClient = network.peers[peerID] if (!disconnectedClient) return logger.warn(`Tried to handle disconnect for peer ${peerID} but was not found`) // On local, new connections can come in before the old sockets are disconnected. // The new connection will overwrite the socketID for the user's client. diff --git a/packages/instanceserver/src/ServerHostNetworkSystem.tsx b/packages/instanceserver/src/ServerHostNetworkSystem.tsx index 15ec4edf89..5e000c0ac6 100644 --- a/packages/instanceserver/src/ServerHostNetworkSystem.tsx +++ b/packages/instanceserver/src/ServerHostNetworkSystem.tsx @@ -27,13 +27,14 @@ import { defineSystem } from '@etherealengine/engine/src/ecs/functions/SystemFun import { NetworkPeerFunctions } from '@etherealengine/engine/src/networking/functions/NetworkPeerFunctions' import { updatePeers } from '@etherealengine/engine/src/networking/systems/OutgoingActionSystem' +import { PeerID } from '@etherealengine/common/src/interfaces/PeerID' import { SocketWebRTCServerNetwork } from './SocketWebRTCServerFunctions' export async function validateNetworkObjects(network: SocketWebRTCServerNetwork): Promise { - for (const [peerID, client] of network.peers) { + for (const [peerID, client] of Object.entries(network.peers)) { if (client.userId === Engine.instance.userID) continue if (Date.now() - client.lastSeenTs > 5000) { - NetworkPeerFunctions.destroyPeer(network, peerID) + NetworkPeerFunctions.destroyPeer(network, peerID as PeerID) updatePeers(network) } } diff --git a/packages/instanceserver/src/ServerRecordingSystem.ts b/packages/instanceserver/src/ServerRecordingSystem.ts index 135e8e13ea..f018e65b8e 100644 --- a/packages/instanceserver/src/ServerRecordingSystem.ts +++ b/packages/instanceserver/src/ServerRecordingSystem.ts @@ -388,7 +388,7 @@ export const onStartPlayback = async (action: ReturnType { const network = getServerNetwork(app) for (const [userId, userMap] of Array.from(dataChannelToReplay.entries())) { - if (network.users.has(userId)) + if (network.users[userId]) for (const [dataChannel, chunk] of userMap) { for (const frame of chunk.frames) { if (frame.timecode > Date.now() - chunk.startTime) { network.transport.bufferToAll(dataChannel, encode(frame.data)) - // for (const peerID of network.users.get(userId)!) { + // for (const peerID of network.users[userId]) { // network.transport.bufferToPeer(dataChannel, peerID, encode(frame.data)) // } break diff --git a/packages/instanceserver/src/SocketWebRTCServerFunctions.ts b/packages/instanceserver/src/SocketWebRTCServerFunctions.ts index 537ac34eb3..26a164c53d 100755 --- a/packages/instanceserver/src/SocketWebRTCServerFunctions.ts +++ b/packages/instanceserver/src/SocketWebRTCServerFunctions.ts @@ -37,6 +37,7 @@ import multiLogger from '@etherealengine/server-core/src/ServerLogger' import { DataChannelType } from '@etherealengine/common/src/interfaces/DataChannelType' import { startSystem } from '@etherealengine/engine/src/ecs/functions/SystemFunctions' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { InstanceServerState } from './InstanceServerState' import { MediasoupServerSystem } from './MediasoupServerSystem' import { ServerHostNetworkSystem } from './ServerHostNetworkSystem' @@ -51,7 +52,7 @@ export type WebRTCTransportExtension = Omit & { export type ProducerExtension = Omit & { appData: MediaStreamAppData } export type ConsumerExtension = Omit & { appData: MediaStreamAppData } -export const initializeNetwork = async (app: Application, id: string, hostId: UserID, topic: Topic) => { +export const initializeNetwork = async (app: Application, id: InstanceID, hostId: UserID, topic: Topic) => { const { workers, routers } = await startWebRTC() const outgoingDataTransport = await routers[0].createDirectTransport() @@ -60,12 +61,12 @@ export const initializeNetwork = async (app: Application, id: string, hostId: Us const transport = { messageToPeer: (peerId: PeerID, data: any) => { - const spark = network.peers.get(peerId)?.spark + const spark = network.peers[peerId]?.spark if (spark) spark.write(data) }, messageToAll: (data: any) => { - for (const peer of Array.from(network.peers.values())) peer.spark?.write(data) + for (const peer of Object.values(network.peers)) peer.spark?.write(data) }, bufferToPeer: (dataChannelType: DataChannelType, peerID: PeerID, data: any) => { diff --git a/packages/instanceserver/src/WebRTCFunctions.ts b/packages/instanceserver/src/WebRTCFunctions.ts index 2cb47fc67f..9640ab6202 100755 --- a/packages/instanceserver/src/WebRTCFunctions.ts +++ b/packages/instanceserver/src/WebRTCFunctions.ts @@ -174,7 +174,7 @@ export const handleConsumeData = async (action: typeof MediasoupDataConsumerActi const { $peer: peerID, dataChannel } = action - const peer = network.peers.get(peerID)! + const peer = network.peers[peerID]! if (!peer) { logger.warn('No peer found for peerID: ' + peerID) return @@ -226,7 +226,7 @@ export const handleConsumeData = async (action: typeof MediasoupDataConsumerActi getMutableState(MediasoupInternalWebRTCDataChannelState)[peerID].outgoingDataConsumers[dataChannel].set(none) }) - const peer = network.peers.get(peerID) + const peer = network.peers[peerID] logger.info('Setting data consumer to network state.') if (!peer) { @@ -239,6 +239,7 @@ export const handleConsumeData = async (action: typeof MediasoupDataConsumerActi MediasoupDataConsumerActions.consumerCreated({ consumerID: dataConsumer.id, peerID, + transportID: newTransport.id, producerID: outgoingDataProducer.id, dataChannel, sctpStreamParameters: dataConsumer.sctpStreamParameters as any, @@ -279,14 +280,16 @@ export function transportClosed(network: SocketWebRTCServerNetwork, transport: W // and consumers associated with this transport if (getState(MediasoupDataProducerConsumerState)[network.id]) { const dataProducers = Object.values(getState(MediasoupDataProducerConsumerState)[network.id].producers) - dataProducers.forEach( - (dataProducer) => + dataProducers.forEach((dataProducer) => { + if (dataProducer.transportID === transport.id) dataProducer.producerID && closeDataProducer(network, dataProducer.producerID, dataProducer.appData.peerID) - ) + }) } if (getState(MediasoupMediaProducerConsumerState)[network.id]) { const mediaProducers = Object.values(getState(MediasoupMediaProducerConsumerState)[network.id].producers) - mediaProducers.forEach((producer) => producerClosed(network, producer.producerID)) + mediaProducers.forEach((producer) => { + if (producer.transportID === transport.id) producerClosed(network, producer.producerID) + }) } getMutableState(MediasoupTransportObjectsState)[transport.id].set(none) @@ -500,15 +503,7 @@ export async function handleProduceData( ): Promise { const network = getState(NetworkState).networks[action.$network] as SocketWebRTCServerNetwork - const { - $peer: peerID, - transportID: transportId, - sctpStreamParameters, - dataChannel: label, - protocol, - appData, - requestID - } = action + const { $peer: peerID, transportID, sctpStreamParameters, dataChannel: label, protocol, appData, requestID } = action try { const userId = getUserIdFromPeerID(network, peerID) @@ -531,7 +526,7 @@ export async function handleProduceData( ) } - if (!network.peers.has(peerID)) { + if (!network.peers[peerID]) { const errorMessage = `Client no longer exists for userId "${userId}".` logger.error(errorMessage) return dispatchAction( @@ -546,7 +541,7 @@ export async function handleProduceData( } logger.info(`peerID "${peerID}", Data channel "${label}" %o: `, action) - const transport = getState(MediasoupTransportObjectsState)[transportId] + const transport = getState(MediasoupTransportObjectsState)[transportID] if (!transport) { logger.error('Invalid transport.') return dispatchAction( @@ -564,13 +559,13 @@ export async function handleProduceData( label: label ?? undefined, protocol: protocol ?? undefined, sctpStreamParameters, - appData: { ...(appData || {}), peerID, transportId } + appData: { ...(appData || {}), peerID, transportId: transportID } } logger.info('Data producer params: %o', options) const dataProducer = await transport.produceData(options) logger.info(`User ${userId} producing data on ${label}`) - if (!network.peers.has(peerID)) { + if (!network.peers[peerID]) { logger.error('Client no longer exists.') return dispatchAction( MediasoupDataProducerActions.requestProducerError({ @@ -617,7 +612,7 @@ export async function handleProduceData( }) ) } - if (!network.peers.has(peerID)) { + if (!network.peers[peerID]) { logger.error('Client no longer exists.') return dispatchAction( MediasoupDataProducerActions.requestProducerError({ @@ -635,7 +630,7 @@ export async function handleProduceData( return dispatchAction( MediasoupDataProducerActions.producerCreated({ requestID, - transportID: transportId, + transportID: transportID, producerID: dataProducer.id, dataChannel: label, protocol, @@ -781,8 +776,8 @@ export async function handleRequestProducer(action: typeof MediaProducerActions. logger.info(`New Producer: peerID "${peerID}", Media stream "${appData.mediaTag}"`) - if (userId && network.peers.has(peerID)) { - network.peers.get(peerID)!.media![appData.mediaTag!] = { + if (userId && network.peers[peerID]) { + network.peers[peerID]!.media![appData.mediaTag!] = { paused, producerId: producer.id, globalMute: false, @@ -795,6 +790,7 @@ export async function handleRequestProducer(action: typeof MediaProducerActions. requestID, peerID, mediaTag: appData.mediaTag, + transportID, producerID: producer.id, channelID: appData.channelId, $network: action.$network, @@ -833,13 +829,13 @@ export const handleRequestConsumer = async ( // @todo: the 'any' cast here is because WebRtcTransport.internal is protected - we should see if this is the proper accessor const router = network.transport.routers.find((router) => router.id === transport?.internal.routerId) - if (!producer || !router || !router.canConsume({ producerId: producer.producerID, rtpCapabilities })) { + if (!producer || !router || !transport || !router.canConsume({ producerId: producer.producerID, rtpCapabilities })) { + logger.info('%o', { producer, router, transport }) const msg = `Client cannot consume ${mediaPeerId}:${mediaTag}, ${producer?.producerID}` logger.error(`recv-track: ${forPeerID} ${msg}`) return } - if (!transport) return try { const consumer = (await transport.consume({ producerId: producer.producerID, @@ -886,6 +882,7 @@ export const handleRequestConsumer = async ( channelID, consumerID: consumer.id, peerID: mediaPeerId, + transportID: transport.id, mediaTag, producerID: producer.producerID, kind: consumer.kind, diff --git a/packages/instanceserver/src/channels.ts b/packages/instanceserver/src/channels.ts index d66d1c3865..8fd220df7b 100755 --- a/packages/instanceserver/src/channels.ts +++ b/packages/instanceserver/src/channels.ts @@ -53,6 +53,7 @@ import getLocalServerIp from '@etherealengine/server-core/src/util/get-local-ser import { ChannelID } from '@etherealengine/common/src/dbmodels/Channel' import { ChannelUser } from '@etherealengine/engine/src/schemas/interfaces/ChannelUser' import { instanceAttendancePath } from '@etherealengine/engine/src/schemas/networking/instance-attendance.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { identityProviderPath, IdentityProviderType @@ -73,7 +74,7 @@ interface PrimusConnectionType { socketQuery?: { sceneId: string locationId?: string - instanceID?: string + instanceID?: InstanceID channelId?: string roomCode?: string token: string @@ -81,7 +82,9 @@ interface PrimusConnectionType { transport: string t: string } - instanceId?: string + /** @deprecated - @todo refactor */ + instanceId?: InstanceID + /** @deprecated - @todo refactor */ channelId?: string } @@ -235,7 +238,7 @@ const initializeInstance = async ( const loadEngine = async (app: Application, sceneId: string) => { const instanceServerState = getState(InstanceServerState) - const hostId = instanceServerState.instance.id as UserID + const hostId = instanceServerState.instance.id as UserID & InstanceID Engine.instance.userID = hostId Engine.instance.peerID = uuidv4() as PeerID const topic = instanceServerState.isMediaInstance ? NetworkTopics.media : NetworkTopics.world @@ -257,13 +260,13 @@ const loadEngine = async (app: Application, sceneId: string) => { const projects = await getProjectsList() if (instanceServerState.isMediaInstance) { - getMutableState(NetworkState).hostIds.media.set(hostId as UserID) + getMutableState(NetworkState).hostIds.media.set(hostId) startMediaServerSystems() await loadEngineInjection(projects) dispatchAction(EngineActions.initializeEngine({ initialised: true })) dispatchAction(EngineActions.sceneLoaded({})) } else { - getMutableState(NetworkState).hostIds.world.set(hostId as UserID) + getMutableState(NetworkState).hostIds.world.set(hostId) const [projectName, sceneName] = sceneId.split('/') @@ -425,7 +428,7 @@ const createOrUpdateInstance = async ( } } -const shutdownServer = async (app: Application, instanceId: string) => { +const shutdownServer = async (app: Application, instanceId: InstanceID) => { const instanceServer = getState(InstanceServerState) const serverState = getState(ServerState) @@ -468,7 +471,7 @@ const shutdownServer = async (app: Application, instanceId: string) => { // todo: this could be more elegant const getActiveUsersCount = (app: Application, userToIgnore: UserType) => { - const activeClients = getServerNetwork(app).peers + const activeClients = Object.entries(getServerNetwork(app).peers) const activeUsers = [...activeClients].filter( ([id, client]) => client.userId !== Engine.instance.userID && client.userId !== userToIgnore.id ) @@ -479,7 +482,7 @@ const handleUserDisconnect = async ( app: Application, connection: PrimusConnectionType, user: UserType, - instanceId: string + instanceId: InstanceID ) => { const instanceServerState = getState(InstanceServerState) @@ -514,7 +517,7 @@ const handleUserDisconnect = async ( // check if there are no peers connected (1 being the server, // 0 if the serer was just starting when someone connected and disconnected) - if (network.peers.size <= 1) { + if (Object.keys(network.peers).length <= 1) { logger.info('Shutting down instance server as there are no users present.') await shutdownServer(app, instanceId) } @@ -532,7 +535,7 @@ const handleChannelUserRemoved = (app: Application) => async (params) => { }) if (!channel) return const network = getServerNetwork(app) - const matchingPeer = Array.from(network.peers.values()).find((peer) => peer.userId === params.userId) + const matchingPeer = Object.values(network.peers).find((peer) => peer.userId === params.userId) if (matchingPeer) { matchingPeer.spark?.end() NetworkPeerFunctions.destroyPeer(network, matchingPeer.peerID) @@ -728,12 +731,12 @@ export default (app: Application): void => { logger.info('kicking user id %s', data.userId) - const peerId = Engine.instance.worldNetwork.users.get(data.userId) + const peerId = Engine.instance.worldNetwork.users[data.userId] if (!peerId || !peerId[0]) return logger.info('kicking peerId %o', peerId) - const peer = Engine.instance.worldNetwork.peers.get(peerId[0]) + const peer = Engine.instance.worldNetwork.peers[peerId[0]] if (!peer || !peer.spark) return handleDisconnect(getServerNetwork(app), peer.peerID) diff --git a/packages/server-core/knexfile.ts b/packages/server-core/knexfile.ts index 54259b6c6a..dffac3d1bd 100644 --- a/packages/server-core/knexfile.ts +++ b/packages/server-core/knexfile.ts @@ -23,6 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import appRootPath from 'app-root-path' import fs from 'fs' import type { Knex } from 'knex' import path from 'path' @@ -43,7 +44,20 @@ const parseDirectory = (directoryPath: string) => { } } -parseDirectory('./src') +const currentDirectory = process.cwd() +const currentFolderName = path.basename(path.resolve(process.cwd())) // https://stackoverflow.com/a/53295230/2077741 + +let serverCoreSrc = '../server-core/src' + +if (currentFolderName === 'server-core') { + serverCoreSrc = './src' +} else if (currentDirectory.includes('projects/projects')) { + serverCoreSrc = '../../../server-core/src' +} else { + serverCoreSrc = path.join(appRootPath.path, '/packages/server-core/src') +} + +parseDirectory(serverCoreSrc) const projectsDirectory = '../projects/projects' const projectsExists = fs.existsSync(projectsDirectory) diff --git a/packages/server-core/package.json b/packages/server-core/package.json index ac9953f342..5c6deefe48 100755 --- a/packages/server-core/package.json +++ b/packages/server-core/package.json @@ -40,6 +40,7 @@ "validate": "npm run build && npm run test", "migrate": "knex migrate:latest", "migrate:rollback": "knex migrate:rollback --all", + "migrate:unlock": "knex migrate:unlock", "migrate:make": "knex --migrations-directory ./ migrate:make" }, "dependencies": { diff --git a/packages/server-core/src/analytics/analytics/analytics.resolvers.ts b/packages/server-core/src/analytics/analytics/analytics.resolvers.ts index 5353f8d52a..90a1f4399a 100644 --- a/packages/server-core/src/analytics/analytics/analytics.resolvers.ts +++ b/packages/server-core/src/analytics/analytics/analytics.resolvers.ts @@ -24,15 +24,18 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { AnalyticsQuery, AnalyticsType } from '@etherealengine/engine/src/schemas/analytics/analytics.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const analyticsResolver = resolve({}) +export const analyticsResolver = resolve({ + createdAt: virtual(async (analytics) => fromDateTimeSql(analytics.createdAt)), + updatedAt: virtual(async (analytics) => fromDateTimeSql(analytics.updatedAt)) +}) export const analyticsExternalResolver = resolve({}) diff --git a/packages/server-core/src/analytics/analytics/analytics.seed.ts b/packages/server-core/src/analytics/analytics/analytics.seed.ts index 80d74141a5..42bec8f27a 100644 --- a/packages/server-core/src/analytics/analytics/analytics.seed.ts +++ b/packages/server-core/src/analytics/analytics/analytics.seed.ts @@ -29,7 +29,7 @@ import { v4 } from 'uuid' import { analyticsPath, AnalyticsType } from '@etherealengine/engine/src/schemas/analytics/analytics.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/bot/bot-command/bot-command.resolvers.ts b/packages/server-core/src/bot/bot-command/bot-command.resolvers.ts index 57f6aefd46..fe6a34d5c0 100644 --- a/packages/server-core/src/bot/bot-command/bot-command.resolvers.ts +++ b/packages/server-core/src/bot/bot-command/bot-command.resolvers.ts @@ -24,15 +24,18 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { BotCommandQuery, BotCommandType } from '@etherealengine/engine/src/schemas/bot/bot-command.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const botCommandResolver = resolve({}) +export const botCommandResolver = resolve({ + createdAt: virtual(async (botCommand) => fromDateTimeSql(botCommand.createdAt)), + updatedAt: virtual(async (botCommand) => fromDateTimeSql(botCommand.updatedAt)) +}) export const botCommandExternalResolver = resolve({}) diff --git a/packages/server-core/src/bot/bot/bot.class.ts b/packages/server-core/src/bot/bot/bot.class.ts index 5298b37b1a..72869978c3 100644 --- a/packages/server-core/src/bot/bot/bot.class.ts +++ b/packages/server-core/src/bot/bot/bot.class.ts @@ -30,6 +30,7 @@ import { KnexAdapter } from '@feathersjs/knex' import { BotData, BotPatch, BotQuery, BotType } from '@etherealengine/engine/src/schemas/bot/bot.schema' import { botCommandPath } from '@etherealengine/engine/src/schemas/bot/bot-command.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { Application } from '../../../declarations' import { RootParams } from '../../api/root-params' @@ -58,7 +59,7 @@ export class BotService e } async create(data: BotData, params?: BotParams) { - data.instanceId = data.instanceId ? data.instanceId : '' + data.instanceId = data.instanceId ? data.instanceId : ('' as InstanceID) const dataWithoutExtras = { ...data } as any delete dataWithoutExtras.botCommands diff --git a/packages/server-core/src/bot/bot/bot.resolvers.ts b/packages/server-core/src/bot/bot/bot.resolvers.ts index ee75efa31f..09c1a3981a 100644 --- a/packages/server-core/src/bot/bot/bot.resolvers.ts +++ b/packages/server-core/src/bot/bot/bot.resolvers.ts @@ -37,7 +37,7 @@ import { } from '@etherealengine/engine/src/schemas/bot/bot-command.schema' import { InstanceType, instancePath } from '@etherealengine/engine/src/schemas/networking/instance.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' import { botCommandDataResolver } from '../bot-command/bot-command.resolvers' export const botResolver = resolve({}) @@ -65,7 +65,9 @@ export const botExternalResolver = resolve({ })) as BotCommandType[] return botCommands } - }) + }), + createdAt: virtual(async (bot) => fromDateTimeSql(bot.createdAt)), + updatedAt: virtual(async (bot) => fromDateTimeSql(bot.updatedAt)) }) export const botDataResolver = resolve({ diff --git a/packages/server-core/src/cluster/build-status/build-status.resolvers.ts b/packages/server-core/src/cluster/build-status/build-status.resolvers.ts index b8a42835a6..f8a5fb1411 100644 --- a/packages/server-core/src/cluster/build-status/build-status.resolvers.ts +++ b/packages/server-core/src/cluster/build-status/build-status.resolvers.ts @@ -24,14 +24,17 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { BuildStatusQuery, BuildStatusType } from '@etherealengine/engine/src/schemas/cluster/build-status.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const buildStatusResolver = resolve({}) +export const buildStatusResolver = resolve({ + createdAt: virtual(async (buildStatus) => fromDateTimeSql(buildStatus.createdAt)), + updatedAt: virtual(async (buildStatus) => fromDateTimeSql(buildStatus.updatedAt)) +}) export const buildStatusExternalResolver = resolve({}) diff --git a/packages/server-core/src/hooks/matchmaking-link-match-user-to-match.ts b/packages/server-core/src/hooks/matchmaking-link-match-user-to-match.ts index bb9232c92f..cb1a526ed5 100644 --- a/packages/server-core/src/hooks/matchmaking-link-match-user-to-match.ts +++ b/packages/server-core/src/hooks/matchmaking-link-match-user-to-match.ts @@ -30,11 +30,12 @@ import { matchUserPath } from '@etherealengine/engine/src/schemas/matchmaking/ma import { MatchTicketAssignmentType } from '@etherealengine/matchmaking/src/match-ticket-assignment.schema' import { instanceAuthorizedUserPath } from '@etherealengine/engine/src/schemas/networking/instance-authorized-user.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { identityProviderPath } from '@etherealengine/engine/src/schemas/user/identity-provider.schema' import logger from '../ServerLogger' interface AssignmentResponse extends MatchTicketAssignmentType { - instanceId: string + instanceId: InstanceID locationName: string } diff --git a/packages/server-core/src/matchmaking/match-instance/match-instance.resolvers.ts b/packages/server-core/src/matchmaking/match-instance/match-instance.resolvers.ts index 50394595d5..b33068788a 100644 --- a/packages/server-core/src/matchmaking/match-instance/match-instance.resolvers.ts +++ b/packages/server-core/src/matchmaking/match-instance/match-instance.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -33,9 +33,12 @@ import { } from '@etherealengine/engine/src/schemas/matchmaking/match-instance.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const matchInstanceResolver = resolve({}) +export const matchInstanceResolver = resolve({ + createdAt: virtual(async (matchInstance) => fromDateTimeSql(matchInstance.createdAt)), + updatedAt: virtual(async (matchInstance) => fromDateTimeSql(matchInstance.updatedAt)) +}) export const matchInstanceExternalResolver = resolve({}) diff --git a/packages/server-core/src/matchmaking/match-ticket/match-ticket.resolvers.ts b/packages/server-core/src/matchmaking/match-ticket/match-ticket.resolvers.ts index 01ebfa66df..2015e55ecf 100644 --- a/packages/server-core/src/matchmaking/match-ticket/match-ticket.resolvers.ts +++ b/packages/server-core/src/matchmaking/match-ticket/match-ticket.resolvers.ts @@ -24,15 +24,18 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { MatchTicketQuery, MatchTicketType } from '@etherealengine/matchmaking/src/match-ticket.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const matchTicketResolver = resolve({}) +export const matchTicketResolver = resolve({ + createdAt: virtual(async (matchTicket) => (matchTicket.createdAt ? fromDateTimeSql(matchTicket.createdAt) : '')), + updatedAt: virtual(async (matchTicket) => (matchTicket.updatedAt ? fromDateTimeSql(matchTicket.updatedAt) : '')) +}) export const matchTicketExternalResolver = resolve({}) diff --git a/packages/server-core/src/matchmaking/match-user/match-user.resolvers.ts b/packages/server-core/src/matchmaking/match-user/match-user.resolvers.ts index c14810db68..ae9df8f11f 100644 --- a/packages/server-core/src/matchmaking/match-user/match-user.resolvers.ts +++ b/packages/server-core/src/matchmaking/match-user/match-user.resolvers.ts @@ -24,15 +24,18 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { MatchUserQuery, MatchUserType } from '@etherealengine/engine/src/schemas/matchmaking/match-user.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const matchUserResolver = resolve({}) +export const matchUserResolver = resolve({ + createdAt: virtual(async (matchUser) => fromDateTimeSql(matchUser.createdAt)), + updatedAt: virtual(async (matchUser) => fromDateTimeSql(matchUser.updatedAt)) +}) export const matchUserExternalResolver = resolve({}) diff --git a/packages/server-core/src/media/static-resource/static-resource.resolvers.ts b/packages/server-core/src/media/static-resource/static-resource.resolvers.ts index da67096b49..a52a6c08bf 100644 --- a/packages/server-core/src/media/static-resource/static-resource.resolvers.ts +++ b/packages/server-core/src/media/static-resource/static-resource.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -34,9 +34,7 @@ import { } from '@etherealengine/engine/src/schemas/media/static-resource.schema' import type { HookContext } from '@etherealengine/server-core/declarations' import { nanoid } from 'nanoid' -import { getDateTimeSql } from '../../util/get-datetime-sql' - -export const staticResourceResolver = resolve({}) +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const staticResourceDbToSchema = (rawData: StaticResourceDatabaseType): StaticResourceType => { let metadata = JSON.parse(rawData.metadata) as any @@ -71,6 +69,11 @@ export const staticResourceDbToSchema = (rawData: StaticResourceDatabaseType): S } } +export const staticResourceResolver = resolve({ + createdAt: virtual(async (staticResource) => fromDateTimeSql(staticResource.createdAt)), + updatedAt: virtual(async (staticResource) => fromDateTimeSql(staticResource.updatedAt)) +}) + export const staticResourceExternalResolver = resolve( {}, { diff --git a/packages/server-core/src/networking/instance-attendance/instance-attendance.resolvers.ts b/packages/server-core/src/networking/instance-attendance/instance-attendance.resolvers.ts index 772e2edf21..f7f85607f4 100644 --- a/packages/server-core/src/networking/instance-attendance/instance-attendance.resolvers.ts +++ b/packages/server-core/src/networking/instance-attendance/instance-attendance.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -33,9 +33,12 @@ import { } from '@etherealengine/engine/src/schemas/networking/instance-attendance.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const instanceAttendanceResolver = resolve({}) +export const instanceAttendanceResolver = resolve({ + createdAt: virtual(async (instanceAttendance) => fromDateTimeSql(instanceAttendance.createdAt)), + updatedAt: virtual(async (instanceAttendance) => fromDateTimeSql(instanceAttendance.updatedAt)) +}) export const instanceAttendanceExternalResolver = resolve({}) diff --git a/packages/server-core/src/networking/instance-authorized-user/instance-authorized-user.resolvers.ts b/packages/server-core/src/networking/instance-authorized-user/instance-authorized-user.resolvers.ts index d169d47f46..873862dc6a 100644 --- a/packages/server-core/src/networking/instance-authorized-user/instance-authorized-user.resolvers.ts +++ b/packages/server-core/src/networking/instance-authorized-user/instance-authorized-user.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -33,9 +33,12 @@ import { } from '@etherealengine/engine/src/schemas/networking/instance-authorized-user.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const instanceAuthorizedUserResolver = resolve({}) +export const instanceAuthorizedUserResolver = resolve({ + createdAt: virtual(async (instanceAuthorizedUser) => fromDateTimeSql(instanceAuthorizedUser.createdAt)), + updatedAt: virtual(async (instanceAuthorizedUser) => fromDateTimeSql(instanceAuthorizedUser.updatedAt)) +}) export const instanceAuthorizedUserExternalResolver = resolve({}) diff --git a/packages/server-core/src/networking/instance/instance.service.ts b/packages/server-core/src/networking/instance/instance.service.ts index d8dffc9f3c..500e490019 100755 --- a/packages/server-core/src/networking/instance/instance.service.ts +++ b/packages/server-core/src/networking/instance/instance.service.ts @@ -29,6 +29,7 @@ import { Instance as InstanceInterface } from '@etherealengine/common/src/interf import { locationPath, LocationType } from '@etherealengine/engine/src/schemas/social/location.schema' import { instanceAttendancePath } from '@etherealengine/engine/src/schemas/networking/instance-attendance.schema' +import { InstanceID } from '@etherealengine/engine/src/schemas/networking/instance.schema' import { scopePath, ScopeType } from '@etherealengine/engine/src/schemas/scope/scope.schema' import { userRelationshipPath } from '@etherealengine/engine/src/schemas/user/user-relationship.schema' import { UserID, userPath } from '@etherealengine/engine/src/schemas/user/user.schema' @@ -57,7 +58,7 @@ declare module '@etherealengine/common/declarations' { } type ActiveInstance = { - id: string + id: InstanceID location: string currentUsers: number } diff --git a/packages/server-core/src/projects/project-permission/migrations/20230811103608_project-permission.ts b/packages/server-core/src/projects/project-permission/migrations/20230811103608_project-permission.ts index c41f4c7d2b..30b3076646 100644 --- a/packages/server-core/src/projects/project-permission/migrations/20230811103608_project-permission.ts +++ b/packages/server-core/src/projects/project-permission/migrations/20230811103608_project-permission.ts @@ -48,6 +48,17 @@ export async function up(knex: Knex): Promise { } tableExists = await trx.schema.hasTable(projectPermissionPath) + + if (tableExists) { + const hasIdColum = await trx.schema.hasColumn(projectPermissionPath, 'id') + const hasProjectIdColumn = await trx.schema.hasColumn(projectPermissionPath, 'projectId') + const hasUserIdColumn = await trx.schema.hasColumn(projectPermissionPath, 'userId') + if (!(hasIdColum && hasProjectIdColumn && hasUserIdColumn)) { + await trx.schema.dropTable(projectPermissionPath) + tableExists = false + } + } + if (!tableExists && !oldNamedTableExists) { await trx.schema.createTable(projectPermissionPath, (table) => { //@ts-ignore diff --git a/packages/server-core/src/projects/project-permission/project-permission.resolvers.ts b/packages/server-core/src/projects/project-permission/project-permission.resolvers.ts index bb3d2d195b..0780aa99d4 100644 --- a/packages/server-core/src/projects/project-permission/project-permission.resolvers.ts +++ b/packages/server-core/src/projects/project-permission/project-permission.resolvers.ts @@ -34,7 +34,7 @@ import { import type { HookContext } from '@etherealengine/server-core/declarations' import { userPath } from '@etherealengine/engine/src/schemas/user/user.schema' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const projectPermissionResolver = resolve({ user: virtual(async (projectPermission, context) => { @@ -42,7 +42,9 @@ export const projectPermissionResolver = resolve fromDateTimeSql(projectPermission.createdAt)), + updatedAt: virtual(async (projectPermission) => fromDateTimeSql(projectPermission.updatedAt)) }) export const projectPermissionExternalResolver = resolve({}) diff --git a/packages/server-core/src/projects/project/project.class.ts b/packages/server-core/src/projects/project/project.class.ts index 69a53c6a16..df7a6255d8 100644 --- a/packages/server-core/src/projects/project/project.class.ts +++ b/packages/server-core/src/projects/project/project.class.ts @@ -761,7 +761,7 @@ export class Project extends Service { const projectPermissions = await knexClient .from(projectPermissionPath) .join('project', 'project.id', `${projectPermissionPath}.projectId`) - .where({ userId: params.user!.id }) + .where(`${projectPermissionPath}.userId`, params.user!.id) .select() .options({ nestTables: true }) diff --git a/packages/server-core/src/recording/recording-resource/recording-resource.resolvers.ts b/packages/server-core/src/recording/recording-resource/recording-resource.resolvers.ts index aa28271979..003e7b5b40 100644 --- a/packages/server-core/src/recording/recording-resource/recording-resource.resolvers.ts +++ b/packages/server-core/src/recording/recording-resource/recording-resource.resolvers.ts @@ -34,13 +34,15 @@ import { RecordingResourceQuery, RecordingResourceType } from '@etherealengine/engine/src/schemas/recording/recording-resource.schema' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const recordingResourceResolver = resolve({ staticResource: virtual(async (recordingResource, context) => { const staticResource = await context.app.service(staticResourcePath).get(recordingResource.staticResourceId) return staticResource - }) + }), + createdAt: virtual(async (recordingResource) => fromDateTimeSql(recordingResource.createdAt)), + updatedAt: virtual(async (recordingResource) => fromDateTimeSql(recordingResource.updatedAt)) }) export const recordingResourceExternalResolver = resolve({}) diff --git a/packages/server-core/src/recording/recording/recording.resolvers.ts b/packages/server-core/src/recording/recording/recording.resolvers.ts index 869e3b2531..158c7f8976 100644 --- a/packages/server-core/src/recording/recording/recording.resolvers.ts +++ b/packages/server-core/src/recording/recording/recording.resolvers.ts @@ -38,7 +38,7 @@ import { RecordingType } from '@etherealengine/engine/src/schemas/recording/recording.schema' import { userPath } from '@etherealengine/engine/src/schemas/user/user.schema' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const recordingDbToSchema = (rawData: RecordingDatabaseType): RecordingType => { let schema = JSON.parse(rawData.schema) as RecordingSchemaType @@ -72,7 +72,9 @@ export const recordingResolver = resolve({ const user = await context.app.service(userPath)._get(recording.userId) return user.name - }) + }), + createdAt: virtual(async (recording) => fromDateTimeSql(recording.createdAt)), + updatedAt: virtual(async (recording) => fromDateTimeSql(recording.updatedAt)) }) export const recordingExternalResolver = resolve( diff --git a/packages/server-core/src/route/route/route.resolvers.ts b/packages/server-core/src/route/route/route.resolvers.ts index 1cf5121df2..e7e2c5e1b8 100644 --- a/packages/server-core/src/route/route/route.resolvers.ts +++ b/packages/server-core/src/route/route/route.resolvers.ts @@ -24,15 +24,18 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { RouteQuery, RouteType } from '@etherealengine/engine/src/schemas/route/route.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const routeResolver = resolve({}) +export const routeResolver = resolve({ + createdAt: virtual(async (route) => fromDateTimeSql(route.createdAt)), + updatedAt: virtual(async (route) => fromDateTimeSql(route.updatedAt)) +}) export const routeExternalResolver = resolve({}) diff --git a/packages/server-core/src/route/route/route.seed.ts b/packages/server-core/src/route/route/route.seed.ts index 4f78e8e9a8..afce4fc3f6 100644 --- a/packages/server-core/src/route/route/route.seed.ts +++ b/packages/server-core/src/route/route/route.seed.ts @@ -29,7 +29,7 @@ import { v4 } from 'uuid' import { routePath, RouteType } from '@etherealengine/engine/src/schemas/route/route.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/scope/scope-type/scope-type.resolvers.ts b/packages/server-core/src/scope/scope-type/scope-type.resolvers.ts index b2208658e9..7473b3ca6e 100644 --- a/packages/server-core/src/scope/scope-type/scope-type.resolvers.ts +++ b/packages/server-core/src/scope/scope-type/scope-type.resolvers.ts @@ -24,14 +24,17 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { ScopeTypeQuery, ScopeTypeType } from '@etherealengine/engine/src/schemas/scope/scope-type.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const scopeTypeResolver = resolve({}) +export const scopeTypeResolver = resolve({ + createdAt: virtual(async (scopeType) => fromDateTimeSql(scopeType.createdAt)), + updatedAt: virtual(async (scopeType) => fromDateTimeSql(scopeType.updatedAt)) +}) export const scopeTypeExternalResolver = resolve({}) diff --git a/packages/server-core/src/scope/scope-type/scope-type.seed.ts b/packages/server-core/src/scope/scope-type/scope-type.seed.ts index 67c73ba449..3fd67b8c9a 100644 --- a/packages/server-core/src/scope/scope-type/scope-type.seed.ts +++ b/packages/server-core/src/scope/scope-type/scope-type.seed.ts @@ -28,7 +28,7 @@ import { Knex } from 'knex' import { scopeTypePath, ScopeTypeType } from '@etherealengine/engine/src/schemas/scope/scope-type.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export const scopeTypeSeed = [ { diff --git a/packages/server-core/src/scope/scope/scope.resolvers.ts b/packages/server-core/src/scope/scope/scope.resolvers.ts index a3be99863f..20e3f12187 100644 --- a/packages/server-core/src/scope/scope/scope.resolvers.ts +++ b/packages/server-core/src/scope/scope/scope.resolvers.ts @@ -31,7 +31,7 @@ import { ScopeQuery, ScopeType } from '@etherealengine/engine/src/schemas/scope/ import type { HookContext } from '@etherealengine/server-core/declarations' import { userPath } from '@etherealengine/engine/src/schemas/user/user.schema' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const scopeResolver = resolve({}) @@ -41,7 +41,9 @@ export const scopeExternalResolver = resolve({ const user = await context.app.service(userPath)._get(scope.userId) return user } - }) + }), + createdAt: virtual(async (scope) => fromDateTimeSql(scope.createdAt)), + updatedAt: virtual(async (scope) => fromDateTimeSql(scope.updatedAt)) }) export const scopeDataResolver = resolve({ diff --git a/packages/server-core/src/sequelize.ts b/packages/server-core/src/sequelize.ts index 6fcab69782..03fff95e1a 100755 --- a/packages/server-core/src/sequelize.ts +++ b/packages/server-core/src/sequelize.ts @@ -23,18 +23,43 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { spawn } from 'child_process' import { Sequelize } from 'sequelize' import { isDev } from '@etherealengine/common/src/config' import appConfig from '@etherealengine/server-core/src/appconfig' +import { delay } from '@etherealengine/engine/src/common/functions/delay' +import { Knex } from 'knex' import { Application } from '../declarations' -import { seeder } from './seeder' import multiLogger from './ServerLogger' +import { seeder } from './seeder' +const config = require('../knexfile') const logger = multiLogger.child({ component: 'server-core:sequelize' }) +const checkLock = async (knexClient: Knex, delayInMs: number, promiseReject: (reason?: any) => void) => { + const trx = await knexClient.transaction() + await trx.raw('SET FOREIGN_KEY_CHECKS=0') + + const lockTableExists = await trx.schema.hasTable('knex_migrations_lock') + if (lockTableExists) { + const existingData = await trx('knex_migrations_lock').select() + + if (existingData.length > 0 && existingData[0].is_locked === 1) { + logger.info(`Knex migrations are locked. Waiting for ${delayInMs / 1000} seconds to check again.`) + await delay(delayInMs) + const existingData = await trx('knex_migrations_lock').select() + + if (existingData.length > 0 && existingData[0].is_locked === 1) { + await knexClient.migrate.forceFreeMigrationsLock(config.migrations) + } + } + } + + await trx.raw('SET FOREIGN_KEY_CHECKS=1') + await trx.commit() +} + export default (app: Application): void => { try { const { forceRefresh } = appConfig.db @@ -75,43 +100,13 @@ export default (app: Application): void => { app.setup = async function (...args) { try { + const knexClient: Knex = app.get('knexClient') + if (forceRefresh || appConfig.testEnabled) { // We are running our migration:rollback here, so that tables in db are dropped 1st using knex. // TODO: Once sequelize is removed, we should add migrate:rollback as part of `dev-reinit-db` script in package.json - const initPromise = new Promise((resolve, reject) => { - const initProcess = spawn('npm', ['run', 'migrate:rollback']) - initProcess.once('exit', resolve) - initProcess.once('error', reject) - initProcess.once('disconnect', resolve) - initProcess.stdout.on('data', (data) => console.log(data.toString())) - initProcess.stderr.on('data', (data) => console.error(data.toString())) - }) - .then((exitCode) => { - if (exitCode !== 0) { - throw new Error(`Knex migrate:rollback exited with: ${exitCode}`) - } else { - logger.info('Knex migrate:rollback completed') - } - }) - .catch((err) => { - logger.error('Knex migrate:rollback error') - logger.error(err) - promiseReject() - throw err - }) - - await Promise.race([ - initPromise, - new Promise((resolve) => { - setTimeout( - () => { - console.log('WARNING: Knex migrations took too long to run!') - resolve() - }, - 2 * 60 * 1000 - ) // timeout after 2 minutes - }) - ]) + await checkLock(knexClient, 0, promiseReject) + await knexClient.migrate.rollback(config.migrations, true) } await sequelize.query('SET FOREIGN_KEY_CHECKS = 0') @@ -133,7 +128,7 @@ export default (app: Application): void => { const columnResult = await sequelize.query(`DESCRIBE \`${model}\``) const columns = columnResult[0] const columnKeys = columns.map((column: any) => column.Field) - for (let item in sequelizeModel.rawAttributes) { + for (const item in sequelizeModel.rawAttributes) { const value = sequelizeModel.rawAttributes[item] as any if (columnKeys.indexOf(value.fieldName) < 0) { if (value.oldColumn && columnKeys.indexOf(value.oldColumn) >= 0) @@ -164,40 +159,8 @@ export default (app: Application): void => { // And then knex migrations can be executed. This is because knex migrations will have foreign key dependency // on ta tables that are created using sequelize. // TODO: Once sequelize is removed, we should add migration as part of `dev-reinit-db` script in package.json - const initPromise = new Promise((resolve, reject) => { - const initProcess = spawn('npm', ['run', 'migrate']) - initProcess.once('exit', resolve) - initProcess.once('error', reject) - initProcess.once('disconnect', resolve) - initProcess.stdout.on('data', (data) => console.log(data.toString())) - initProcess.stderr.on('data', (data) => console.error(data.toString())) - }) - .then((exitCode) => { - if (exitCode !== 0) { - throw new Error(`Knex migration exited with: ${exitCode}`) - } else { - logger.info('Knex migration completed') - } - }) - .catch((err) => { - logger.error('Knex migration error') - logger.error(err) - promiseReject() - throw err - }) - - await Promise.race([ - initPromise, - new Promise((resolve) => { - setTimeout( - () => { - console.log('WARNING: Knex migrations took too long to run!') - resolve() - }, - 2 * 60 * 1000 - ) // timeout after 2 minutes - }) - ]) + await checkLock(knexClient, prepareDb ? 25000 : 0, promiseReject) + await knexClient.migrate.latest(config.migrations) } try { diff --git a/packages/server-core/src/setting/authentication-setting/authentication-setting.class.ts b/packages/server-core/src/setting/authentication-setting/authentication-setting.class.ts index 42d83ffbfd..dfb1c61c90 100644 --- a/packages/server-core/src/setting/authentication-setting/authentication-setting.class.ts +++ b/packages/server-core/src/setting/authentication-setting/authentication-setting.class.ts @@ -76,10 +76,12 @@ export class AuthenticationSettingService< id: el.id, entity: el.entity, service: el.service, - authStrategies: el.authStrategies + authStrategies: el.authStrategies, + createdAt: el.createdAt, + updatedAt: el.updatedAt } - const returned = { + return { ...el, authStrategies: el.authStrategies, jwtOptions: el.jwtOptions, @@ -89,7 +91,6 @@ export class AuthenticationSettingService< ...el.oauth } } - return returned }) return { total: auth.total, diff --git a/packages/server-core/src/setting/authentication-setting/authentication-setting.resolvers.ts b/packages/server-core/src/setting/authentication-setting/authentication-setting.resolvers.ts index 3e728d821d..eba01b10dd 100644 --- a/packages/server-core/src/setting/authentication-setting/authentication-setting.resolvers.ts +++ b/packages/server-core/src/setting/authentication-setting/authentication-setting.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -41,11 +41,9 @@ import { } from '@etherealengine/engine/src/schemas/setting/authentication-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' import { AuthenticationSettingPatch } from './../../../../engine/src/schemas/setting/authentication-setting.schema' -export const authenticationSettingResolver = resolve({}) - export const authenticationSettingSchemaToDb = (patch: AuthenticationSettingPatch) => { return { ...patch, @@ -161,6 +159,11 @@ export const authenticationDbToSchema = (rawData: AuthenticationSettingDatabaseT } } +export const authenticationSettingResolver = resolve({ + createdAt: virtual(async (authenticationSetting) => fromDateTimeSql(authenticationSetting.createdAt)), + updatedAt: virtual(async (authenticationSetting) => fromDateTimeSql(authenticationSetting.updatedAt)) +}) + export const authenticationSettingExternalResolver = resolve( {}, { diff --git a/packages/server-core/src/setting/authentication-setting/authentication-setting.seed.ts b/packages/server-core/src/setting/authentication-setting/authentication-setting.seed.ts index dd440ac14f..4f0e93101b 100644 --- a/packages/server-core/src/setting/authentication-setting/authentication-setting.seed.ts +++ b/packages/server-core/src/setting/authentication-setting/authentication-setting.seed.ts @@ -34,7 +34,7 @@ import appConfig from '@etherealengine/server-core/src/appconfig' import { identityProviderPath } from '@etherealengine/engine/src/schemas/user/identity-provider.schema' import config from '../../appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/setting/aws-setting/aws-setting.resolvers.ts b/packages/server-core/src/setting/aws-setting/aws-setting.resolvers.ts index 8971e71252..8d2617dafb 100644 --- a/packages/server-core/src/setting/aws-setting/aws-setting.resolvers.ts +++ b/packages/server-core/src/setting/aws-setting/aws-setting.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -38,9 +38,7 @@ import { } from '@etherealengine/engine/src/schemas/setting/aws-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' - -export const awsSettingResolver = resolve({}) +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const awsDbToSchema = (rawData: AwsSettingDatabaseType): AwsSettingType => { let eks = JSON.parse(rawData.eks || '{}') as AwsEksType @@ -84,6 +82,11 @@ export const awsDbToSchema = (rawData: AwsSettingDatabaseType): AwsSettingType = } } +export const awsSettingResolver = resolve({ + createdAt: virtual(async (awsSetting) => fromDateTimeSql(awsSetting.createdAt)), + updatedAt: virtual(async (awsSetting) => fromDateTimeSql(awsSetting.updatedAt)) +}) + export const awsSettingExternalResolver = resolve( {}, { diff --git a/packages/server-core/src/setting/aws-setting/aws-setting.seed.ts b/packages/server-core/src/setting/aws-setting/aws-setting.seed.ts index c92a393ad5..a2051101d5 100644 --- a/packages/server-core/src/setting/aws-setting/aws-setting.seed.ts +++ b/packages/server-core/src/setting/aws-setting/aws-setting.seed.ts @@ -29,7 +29,7 @@ import { v4 } from 'uuid' import { AwsSettingDatabaseType, awsSettingPath } from '@etherealengine/engine/src/schemas/setting/aws-setting.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/setting/chargebee-setting/chargebee-setting.resolvers.ts b/packages/server-core/src/setting/chargebee-setting/chargebee-setting.resolvers.ts index eb414f6003..5261e8da98 100644 --- a/packages/server-core/src/setting/chargebee-setting/chargebee-setting.resolvers.ts +++ b/packages/server-core/src/setting/chargebee-setting/chargebee-setting.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -33,9 +33,12 @@ import { } from '@etherealengine/engine/src/schemas/setting/chargebee-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const chargebeeSettingResolver = resolve({}) +export const chargebeeSettingResolver = resolve({ + createdAt: virtual(async (chargebeeSetting) => fromDateTimeSql(chargebeeSetting.createdAt)), + updatedAt: virtual(async (chargebeeSetting) => fromDateTimeSql(chargebeeSetting.updatedAt)) +}) export const chargebeeSettingExternalResolver = resolve({}) diff --git a/packages/server-core/src/setting/chargebee-setting/chargebee-setting.seed.ts b/packages/server-core/src/setting/chargebee-setting/chargebee-setting.seed.ts index 377b8efe80..27c6be10a5 100644 --- a/packages/server-core/src/setting/chargebee-setting/chargebee-setting.seed.ts +++ b/packages/server-core/src/setting/chargebee-setting/chargebee-setting.seed.ts @@ -32,7 +32,7 @@ import { } from '@etherealengine/engine/src/schemas/setting/chargebee-setting.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/setting/client-setting/client-setting.resolvers.ts b/packages/server-core/src/setting/client-setting/client-setting.resolvers.ts index d02164ecf6..11be197da8 100644 --- a/packages/server-core/src/setting/client-setting/client-setting.resolvers.ts +++ b/packages/server-core/src/setting/client-setting/client-setting.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -36,9 +36,7 @@ import { } from '@etherealengine/engine/src/schemas/setting/client-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' - -export const clientSettingResolver = resolve({}) +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const clientDbToSchema = (rawData: ClientSettingDatabaseType): ClientSettingType => { let appSocialLinks = JSON.parse(rawData.appSocialLinks) as ClientSocialLinkType[] @@ -73,6 +71,11 @@ export const clientDbToSchema = (rawData: ClientSettingDatabaseType): ClientSett } } +export const clientSettingResolver = resolve({ + createdAt: virtual(async (clientSetting) => fromDateTimeSql(clientSetting.createdAt)), + updatedAt: virtual(async (clientSetting) => fromDateTimeSql(clientSetting.updatedAt)) +}) + export const clientSettingExternalResolver = resolve( {}, { diff --git a/packages/server-core/src/setting/client-setting/client-setting.seed.ts b/packages/server-core/src/setting/client-setting/client-setting.seed.ts index aaccbd922a..b0f2499a45 100644 --- a/packages/server-core/src/setting/client-setting/client-setting.seed.ts +++ b/packages/server-core/src/setting/client-setting/client-setting.seed.ts @@ -33,7 +33,7 @@ import { } from '@etherealengine/engine/src/schemas/setting/client-setting.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig @@ -69,6 +69,7 @@ export async function seed(knex: Knex): Promise { themeSettings: JSON.stringify(defaultThemeSettings), themeModes: JSON.stringify(defaultThemeModes), key8thWall: process.env.VITE_8TH_WALL || '', + privacyPolicy: 'https://www.etherealengine.com/privacy', homepageLinkButtonEnabled: false, homepageLinkButtonRedirect: '', homepageLinkButtonText: '', @@ -102,6 +103,12 @@ export async function seed(knex: Knex): Promise { appleTouchIcon: seedData[0].appleTouchIcon }) } + if (!item.privacyPolicy) { + await knex(clientSettingPath).update({ + ...item, + privacyPolicy: seedData[0].privacyPolicy + }) + } } } } diff --git a/packages/server-core/src/setting/client-setting/migrations/20230829064412_privacyPolicy-clientSetting-column.ts b/packages/server-core/src/setting/client-setting/migrations/20230829064412_privacyPolicy-clientSetting-column.ts new file mode 100644 index 0000000000..d17946a634 --- /dev/null +++ b/packages/server-core/src/setting/client-setting/migrations/20230829064412_privacyPolicy-clientSetting-column.ts @@ -0,0 +1,54 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { clientSettingPath } from '@etherealengine/engine/src/schemas/setting/client-setting.schema' +import type { Knex } from 'knex' + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export async function up(knex: Knex): Promise { + const privacyPolicyColumnExists = await knex.schema.hasColumn(clientSettingPath, 'privacyPolicy') + if (!privacyPolicyColumnExists) { + await knex.schema.alterTable(clientSettingPath, async (table) => { + table.string('privacyPolicy') + }) + } +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export async function down(knex: Knex): Promise { + const privacyPolicyColumnExists = await knex.schema.hasColumn(clientSettingPath, 'privacyPolicy') + + if (privacyPolicyColumnExists) { + await knex.schema.alterTable(clientSettingPath, async (table) => { + table.dropColumn('privacyPolicy') + }) + } +} diff --git a/packages/server-core/src/setting/coil-setting/coil-setting.resolvers.ts b/packages/server-core/src/setting/coil-setting/coil-setting.resolvers.ts index de14142109..55304cac60 100644 --- a/packages/server-core/src/setting/coil-setting/coil-setting.resolvers.ts +++ b/packages/server-core/src/setting/coil-setting/coil-setting.resolvers.ts @@ -24,16 +24,19 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { CoilSettingQuery, CoilSettingType } from '@etherealengine/engine/src/schemas/setting/coil-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' import { UserType } from '@etherealengine/engine/src/schemas/user/user.schema' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const coilSettingResolver = resolve({}) +export const coilSettingResolver = resolve({ + createdAt: virtual(async (coilSetting) => fromDateTimeSql(coilSetting.createdAt)), + updatedAt: virtual(async (coilSetting) => fromDateTimeSql(coilSetting.updatedAt)) +}) const resolveForAdmin = async (value: string | undefined, query: CoilSettingType, context: HookContext) => { const loggedInUser = context!.params.user as UserType diff --git a/packages/server-core/src/setting/coil-setting/coil-setting.seed.ts b/packages/server-core/src/setting/coil-setting/coil-setting.seed.ts index 928f6850a9..b94213506e 100644 --- a/packages/server-core/src/setting/coil-setting/coil-setting.seed.ts +++ b/packages/server-core/src/setting/coil-setting/coil-setting.seed.ts @@ -29,7 +29,7 @@ import { v4 } from 'uuid' import { coilSettingPath, CoilSettingType } from '@etherealengine/engine/src/schemas/setting/coil-setting.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/setting/email-setting/email-setting.resolvers.ts b/packages/server-core/src/setting/email-setting/email-setting.resolvers.ts index 7cad75f82f..910658f920 100644 --- a/packages/server-core/src/setting/email-setting/email-setting.resolvers.ts +++ b/packages/server-core/src/setting/email-setting/email-setting.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -37,9 +37,7 @@ import { } from '@etherealengine/engine/src/schemas/setting/email-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' - -export const emailSettingResolver = resolve({}) +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const emailDbToSchema = (rawData: EmailSettingDatabaseType): EmailSettingType => { let smtp = JSON.parse(rawData.smtp) as EmailSmtpType @@ -70,6 +68,11 @@ export const emailDbToSchema = (rawData: EmailSettingDatabaseType): EmailSetting } } +export const emailSettingResolver = resolve({ + createdAt: virtual(async (emailSetting) => fromDateTimeSql(emailSetting.createdAt)), + updatedAt: virtual(async (emailSetting) => fromDateTimeSql(emailSetting.updatedAt)) +}) + export const emailSettingExternalResolver = resolve( {}, { diff --git a/packages/server-core/src/setting/email-setting/email-setting.seed.ts b/packages/server-core/src/setting/email-setting/email-setting.seed.ts index 4d5c84b439..b9574b38a2 100644 --- a/packages/server-core/src/setting/email-setting/email-setting.seed.ts +++ b/packages/server-core/src/setting/email-setting/email-setting.seed.ts @@ -32,7 +32,7 @@ import { } from '@etherealengine/engine/src/schemas/setting/email-setting.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/setting/helm-setting/helm-setting.resolvers.ts b/packages/server-core/src/setting/helm-setting/helm-setting.resolvers.ts index 052ec0625a..15f12c9b04 100644 --- a/packages/server-core/src/setting/helm-setting/helm-setting.resolvers.ts +++ b/packages/server-core/src/setting/helm-setting/helm-setting.resolvers.ts @@ -24,15 +24,18 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { HelmSettingQuery, HelmSettingType } from '@etherealengine/engine/src/schemas/setting/helm-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const helmSettingResolver = resolve({}) +export const helmSettingResolver = resolve({ + createdAt: virtual(async (helmSetting) => fromDateTimeSql(helmSetting.createdAt)), + updatedAt: virtual(async (helmSetting) => fromDateTimeSql(helmSetting.updatedAt)) +}) export const helmSettingExternalResolver = resolve({}) diff --git a/packages/server-core/src/setting/helm-setting/helm-setting.seed.ts b/packages/server-core/src/setting/helm-setting/helm-setting.seed.ts index 3dc240f9d9..ec48ef99a5 100644 --- a/packages/server-core/src/setting/helm-setting/helm-setting.seed.ts +++ b/packages/server-core/src/setting/helm-setting/helm-setting.seed.ts @@ -32,7 +32,7 @@ import { } from '@etherealengine/engine/src/schemas/setting/helm-setting.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/setting/instance-server-setting/instance-server-setting.resolvers.ts b/packages/server-core/src/setting/instance-server-setting/instance-server-setting.resolvers.ts index 13eabb6a18..d885195f03 100644 --- a/packages/server-core/src/setting/instance-server-setting/instance-server-setting.resolvers.ts +++ b/packages/server-core/src/setting/instance-server-setting/instance-server-setting.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -33,9 +33,12 @@ import { } from '@etherealengine/engine/src/schemas/setting/instance-server-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const instanceServerSettingResolver = resolve({}) +export const instanceServerSettingResolver = resolve({ + createdAt: virtual(async (instanceServerSetting) => fromDateTimeSql(instanceServerSetting.createdAt)), + updatedAt: virtual(async (instanceServerSetting) => fromDateTimeSql(instanceServerSetting.updatedAt)) +}) export const instanceServerSettingExternalResolver = resolve({}) diff --git a/packages/server-core/src/setting/instance-server-setting/instance-server-setting.seed.ts b/packages/server-core/src/setting/instance-server-setting/instance-server-setting.seed.ts index 3343cb81fd..a39a79e950 100644 --- a/packages/server-core/src/setting/instance-server-setting/instance-server-setting.seed.ts +++ b/packages/server-core/src/setting/instance-server-setting/instance-server-setting.seed.ts @@ -32,7 +32,7 @@ import { } from '@etherealengine/engine/src/schemas/setting/instance-server-setting.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/setting/redis-setting/redis-setting.resolvers.ts b/packages/server-core/src/setting/redis-setting/redis-setting.resolvers.ts index 890df7c4fc..94e01d020a 100644 --- a/packages/server-core/src/setting/redis-setting/redis-setting.resolvers.ts +++ b/packages/server-core/src/setting/redis-setting/redis-setting.resolvers.ts @@ -24,15 +24,18 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { RedisSettingQuery, RedisSettingType } from '@etherealengine/engine/src/schemas/setting/redis-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const redisSettingResolver = resolve({}) +export const redisSettingResolver = resolve({ + createdAt: virtual(async (redisSetting) => fromDateTimeSql(redisSetting.createdAt)), + updatedAt: virtual(async (redisSetting) => fromDateTimeSql(redisSetting.updatedAt)) +}) export const redisSettingExternalResolver = resolve({}) diff --git a/packages/server-core/src/setting/redis-setting/redis-setting.seed.ts b/packages/server-core/src/setting/redis-setting/redis-setting.seed.ts index 264510d27c..202f957b33 100644 --- a/packages/server-core/src/setting/redis-setting/redis-setting.seed.ts +++ b/packages/server-core/src/setting/redis-setting/redis-setting.seed.ts @@ -29,7 +29,7 @@ import { v4 } from 'uuid' import { redisSettingPath, RedisSettingType } from '@etherealengine/engine/src/schemas/setting/redis-setting.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/setting/server-setting/server-setting.resolvers.ts b/packages/server-core/src/setting/server-setting/server-setting.resolvers.ts index 689d89540e..b696e4529e 100644 --- a/packages/server-core/src/setting/server-setting/server-setting.resolvers.ts +++ b/packages/server-core/src/setting/server-setting/server-setting.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -35,9 +35,7 @@ import { } from '@etherealengine/engine/src/schemas/setting/server-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' - -export const serverSettingResolver = resolve({}) +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const serverDbToSchema = (rawData: ServerSettingDatabaseType): ServerSettingType => { let hub = JSON.parse(rawData.hub) as ServerHubType @@ -54,6 +52,11 @@ export const serverDbToSchema = (rawData: ServerSettingDatabaseType): ServerSett } } +export const serverSettingResolver = resolve({ + createdAt: virtual(async (serverSetting) => fromDateTimeSql(serverSetting.createdAt)), + updatedAt: virtual(async (serverSetting) => fromDateTimeSql(serverSetting.updatedAt)) +}) + export const serverSettingExternalResolver = resolve( {}, { diff --git a/packages/server-core/src/setting/server-setting/server-setting.seed.ts b/packages/server-core/src/setting/server-setting/server-setting.seed.ts index f0eeb39530..c38745c6d0 100644 --- a/packages/server-core/src/setting/server-setting/server-setting.seed.ts +++ b/packages/server-core/src/setting/server-setting/server-setting.seed.ts @@ -35,7 +35,7 @@ import { } from '@etherealengine/engine/src/schemas/setting/server-setting.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' const kubernetesEnabled = process.env.KUBERNETES === 'true' diff --git a/packages/server-core/src/setting/task-server-setting/task-server-setting.resolvers.ts b/packages/server-core/src/setting/task-server-setting/task-server-setting.resolvers.ts index ae0ac51b07..3d5a3caa16 100644 --- a/packages/server-core/src/setting/task-server-setting/task-server-setting.resolvers.ts +++ b/packages/server-core/src/setting/task-server-setting/task-server-setting.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -33,9 +33,12 @@ import { } from '@etherealengine/engine/src/schemas/setting/task-server-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const taskServerSettingResolver = resolve({}) +export const taskServerSettingResolver = resolve({ + createdAt: virtual(async (taskServerSetting) => fromDateTimeSql(taskServerSetting.createdAt)), + updatedAt: virtual(async (taskServerSetting) => fromDateTimeSql(taskServerSetting.updatedAt)) +}) export const taskServerSettingExternalResolver = resolve({}) diff --git a/packages/server-core/src/setting/task-server-setting/task-server-setting.seed.ts b/packages/server-core/src/setting/task-server-setting/task-server-setting.seed.ts index e3e508f65e..18e4847ce4 100644 --- a/packages/server-core/src/setting/task-server-setting/task-server-setting.seed.ts +++ b/packages/server-core/src/setting/task-server-setting/task-server-setting.seed.ts @@ -32,7 +32,7 @@ import { } from '@etherealengine/engine/src/schemas/setting/task-server-setting.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/social/invite/invite.class.ts b/packages/server-core/src/social/invite/invite.class.ts index e1e2d457fb..3ac994a079 100755 --- a/packages/server-core/src/social/invite/invite.class.ts +++ b/packages/server-core/src/social/invite/invite.class.ts @@ -45,7 +45,6 @@ import { identityProviderPath } from '@etherealengine/engine/src/schemas/user/identity-provider.schema' import { Forbidden } from '@feathersjs/errors' -import { Service } from 'feathers-sequelize' import logger from '../../ServerLogger' import { RootParams } from '../../api/root-params' @@ -113,7 +112,7 @@ export const inviteReceived = async (inviteService: InviteService, query) => { delete query.type delete query.search - return (await Service.prototype.find.call(inviteService, { + return (await inviteService._find({ query: { ...query, $or: [ @@ -153,7 +152,7 @@ export const inviteSent = async (inviteService: InviteService, query: Query) => delete query.type delete query.search - return (await Service.prototype.find.call(inviteService, { + return (await inviteService._find({ query: { ...query, userId: query.userId @@ -187,7 +186,7 @@ export const inviteAll = async (inviteService: InviteService, query: Query, user if (!query.existenceCheck) delete query.userId delete query.existenceCheck delete query.search - return (await Service.prototype.find.call(inviteService, { + return (await inviteService.find({ query: { // userId: query.userId, ...query diff --git a/packages/server-core/src/social/invite/invite.resolvers.ts b/packages/server-core/src/social/invite/invite.resolvers.ts index ea446ef586..e44094467c 100644 --- a/packages/server-core/src/social/invite/invite.resolvers.ts +++ b/packages/server-core/src/social/invite/invite.resolvers.ts @@ -37,9 +37,7 @@ import type { HookContext } from '@etherealengine/server-core/declarations' import { ChannelID } from '@etherealengine/common/src/dbmodels/Channel' import { userPath } from '@etherealengine/engine/src/schemas/user/user.schema' -import { getDateTimeSql } from '../../util/get-datetime-sql' - -export const inviteResolver = resolve({}) +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const inviteDbToSchema = (rawData: InviteDatabaseType): InviteType => { let spawnDetails = JSON.parse(rawData.spawnDetails) as SpawnDetailsType @@ -56,6 +54,13 @@ export const inviteDbToSchema = (rawData: InviteDatabaseType): InviteType => { } } +export const inviteResolver = resolve({ + createdAt: virtual(async (invite) => fromDateTimeSql(invite.createdAt)), + updatedAt: virtual(async (invite) => fromDateTimeSql(invite.updatedAt)), + startTime: virtual(async (invite) => (invite.startTime ? fromDateTimeSql(invite.startTime) : '')), + endTime: virtual(async (invite) => (invite.endTime ? fromDateTimeSql(invite.endTime) : '')) +}) + export const inviteExternalResolver = resolve( { user: virtual(async (invite, context) => { diff --git a/packages/server-core/src/social/invite/invite.test.ts b/packages/server-core/src/social/invite/invite.test.ts index f1a1a54cb7..10dcb0b390 100755 --- a/packages/server-core/src/social/invite/invite.test.ts +++ b/packages/server-core/src/social/invite/invite.test.ts @@ -26,6 +26,7 @@ Ethereal Engine. All Rights Reserved. import { InviteType, invitePath } from '@etherealengine/engine/src/schemas/social/invite.schema' import { identityProviderPath } from '@etherealengine/engine/src/schemas/user/identity-provider.schema' import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' +import { Paginated } from '@feathersjs/feathers' import assert from 'assert' import { v1 } from 'uuid' import { Application } from '../../../declarations' @@ -144,6 +145,19 @@ describe.skip('invite service', () => { assert.ok(item.passcode) }) + it('should find invites with empty search string', async () => { + const items = await app.service('invite').find({ query: { search: '' } }) + assert.ok(items) + assert.equal((items as Paginated).data.length, invites.length) + }) + + it('should find invites with search string present', async () => { + const lastInvite = invites.at(-1) + const item = await app.service('invite').find({ query: { search: invites.passcode } }) + + assert.equal((item as Paginated).data[0].passcode, lastInvite.passcode) + }) + it('should find received invites', async () => { const item = await app.service(invitePath).find({ query: { diff --git a/packages/server-core/src/social/location-admin/location-admin.resolvers.ts b/packages/server-core/src/social/location-admin/location-admin.resolvers.ts index 68b2f9b909..7e108dde4d 100644 --- a/packages/server-core/src/social/location-admin/location-admin.resolvers.ts +++ b/packages/server-core/src/social/location-admin/location-admin.resolvers.ts @@ -24,15 +24,18 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { LocationAdminQuery, LocationAdminType } from '@etherealengine/engine/src/schemas/social/location-admin.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const locationAdminResolver = resolve({}) +export const locationAdminResolver = resolve({ + createdAt: virtual(async (locationAdmin) => fromDateTimeSql(locationAdmin.createdAt)), + updatedAt: virtual(async (locationAdmin) => fromDateTimeSql(locationAdmin.updatedAt)) +}) export const locationAdminExternalResolver = resolve({}) diff --git a/packages/server-core/src/social/location-authorized-user/location-authorized-user.resolvers.ts b/packages/server-core/src/social/location-authorized-user/location-authorized-user.resolvers.ts index 8f20d724e8..9638bf0384 100644 --- a/packages/server-core/src/social/location-authorized-user/location-authorized-user.resolvers.ts +++ b/packages/server-core/src/social/location-authorized-user/location-authorized-user.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -33,9 +33,12 @@ import { } from '@etherealengine/engine/src/schemas/social/location-authorized-user.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const locationAuthorizedUserResolver = resolve({}) +export const locationAuthorizedUserResolver = resolve({ + createdAt: virtual(async (locationAuthorizedUser) => fromDateTimeSql(locationAuthorizedUser.createdAt)), + updatedAt: virtual(async (locationAuthorizedUser) => fromDateTimeSql(locationAuthorizedUser.updatedAt)) +}) export const locationAuthorizedUserExternalResolver = resolve({}) diff --git a/packages/server-core/src/social/location-ban/location-ban.resolvers.ts b/packages/server-core/src/social/location-ban/location-ban.resolvers.ts index 3c090d3a57..f54eb930e1 100644 --- a/packages/server-core/src/social/location-ban/location-ban.resolvers.ts +++ b/packages/server-core/src/social/location-ban/location-ban.resolvers.ts @@ -24,15 +24,18 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { LocationBanQuery, LocationBanType } from '@etherealengine/engine/src/schemas/social/location-ban.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const locationBanResolver = resolve({}) +export const locationBanResolver = resolve({ + createdAt: virtual(async (locationBan) => fromDateTimeSql(locationBan.createdAt)), + updatedAt: virtual(async (locationBan) => fromDateTimeSql(locationBan.updatedAt)) +}) export const locationBanExternalResolver = resolve({}) diff --git a/packages/server-core/src/social/location-setting/location-setting.resolvers.ts b/packages/server-core/src/social/location-setting/location-setting.resolvers.ts index 4cc556e934..2366b98830 100644 --- a/packages/server-core/src/social/location-setting/location-setting.resolvers.ts +++ b/packages/server-core/src/social/location-setting/location-setting.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -33,9 +33,12 @@ import { } from '@etherealengine/engine/src/schemas/social/location-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const locationSettingResolver = resolve({}) +export const locationSettingResolver = resolve({ + createdAt: virtual(async (locationSetting) => fromDateTimeSql(locationSetting.createdAt)), + updatedAt: virtual(async (locationSetting) => fromDateTimeSql(locationSetting.updatedAt)) +}) export const locationSettingExternalResolver = resolve({}) diff --git a/packages/server-core/src/social/location-setting/location-setting.seed.ts b/packages/server-core/src/social/location-setting/location-setting.seed.ts index c1bf80e1f0..4173668dde 100755 --- a/packages/server-core/src/social/location-setting/location-setting.seed.ts +++ b/packages/server-core/src/social/location-setting/location-setting.seed.ts @@ -31,7 +31,7 @@ import { } from '@etherealengine/engine/src/schemas/social/location-setting.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/social/location/location.resolvers.ts b/packages/server-core/src/social/location/location.resolvers.ts index ee892fde7a..d3250ac465 100644 --- a/packages/server-core/src/social/location/location.resolvers.ts +++ b/packages/server-core/src/social/location/location.resolvers.ts @@ -36,7 +36,7 @@ import type { HookContext } from '@etherealengine/server-core/declarations' import { LocationAuthorizedUserType } from '@etherealengine/engine/src/schemas/social/location-authorized-user.schema' import { LocationBanType, locationBanPath } from '@etherealengine/engine/src/schemas/social/location-ban.schema' import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' import { LocationParams } from './location.class' export const locationResolver = resolve({ @@ -89,7 +89,9 @@ export const locationResolver = resolve({ paginate: false })) as LocationBanType[] return locationBan - }) + }), + createdAt: virtual(async (location) => fromDateTimeSql(location.createdAt)), + updatedAt: virtual(async (location) => fromDateTimeSql(location.updatedAt)) }) export const locationExternalResolver = resolve({ diff --git a/packages/server-core/src/social/location/location.seed.ts b/packages/server-core/src/social/location/location.seed.ts index 7800bf5db8..8fd2fe48df 100755 --- a/packages/server-core/src/social/location/location.seed.ts +++ b/packages/server-core/src/social/location/location.seed.ts @@ -28,7 +28,7 @@ import { Knex } from 'knex' import { LocationDatabaseType, locationPath } from '@etherealengine/engine/src/schemas/social/location.schema' import appConfig from '@etherealengine/server-core/src/appconfig' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' export async function seed(knex: Knex): Promise { const { testEnabled } = appConfig diff --git a/packages/server-core/src/user/avatar/avatar.resolvers.ts b/packages/server-core/src/user/avatar/avatar.resolvers.ts index d4bf002460..7025e5d6d4 100644 --- a/packages/server-core/src/user/avatar/avatar.resolvers.ts +++ b/packages/server-core/src/user/avatar/avatar.resolvers.ts @@ -31,7 +31,7 @@ import { staticResourcePath } from '@etherealengine/engine/src/schemas/media/sta import { AvatarDatabaseType, AvatarQuery, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const avatarResolver = resolve({ modelResource: virtual(async (avatar, context) => { @@ -45,7 +45,9 @@ export const avatarResolver = resolve({ const thumbnailStaticResource = await context.app.service(staticResourcePath).get(avatar.thumbnailResourceId) return thumbnailStaticResource } - }) + }), + createdAt: virtual(async (avatar) => fromDateTimeSql(avatar.createdAt)), + updatedAt: virtual(async (avatar) => fromDateTimeSql(avatar.updatedAt)) }) export const avatarExternalResolver = resolve({}) diff --git a/packages/server-core/src/user/github-repo-access/github-repo-access.resolvers.ts b/packages/server-core/src/user/github-repo-access/github-repo-access.resolvers.ts index a4e671c70f..4908a7a9d0 100644 --- a/packages/server-core/src/user/github-repo-access/github-repo-access.resolvers.ts +++ b/packages/server-core/src/user/github-repo-access/github-repo-access.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -33,9 +33,12 @@ import { } from '@etherealengine/engine/src/schemas/user/github-repo-access.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const githubRepoAccessResolver = resolve({}) +export const githubRepoAccessResolver = resolve({ + createdAt: virtual(async (githubRepoAccess) => fromDateTimeSql(githubRepoAccess.createdAt)), + updatedAt: virtual(async (githubRepoAccess) => fromDateTimeSql(githubRepoAccess.updatedAt)) +}) export const githubRepoAccessExternalResolver = resolve({}) diff --git a/packages/server-core/src/user/identity-provider/identity-provider.resolvers.ts b/packages/server-core/src/user/identity-provider/identity-provider.resolvers.ts index f8603f78a8..1993ece654 100644 --- a/packages/server-core/src/user/identity-provider/identity-provider.resolvers.ts +++ b/packages/server-core/src/user/identity-provider/identity-provider.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -34,9 +34,12 @@ import { import type { HookContext } from '@etherealengine/server-core/declarations' import { UserID } from '@etherealengine/engine/src/schemas/user/user.schema' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const identityProviderResolver = resolve({}) +export const identityProviderResolver = resolve({ + createdAt: virtual(async (identityProvider) => fromDateTimeSql(identityProvider.createdAt)), + updatedAt: virtual(async (identityProvider) => fromDateTimeSql(identityProvider.updatedAt)) +}) export const identityProviderExternalResolver = resolve({}) diff --git a/packages/server-core/src/user/login-token/login-token.class.ts b/packages/server-core/src/user/login-token/login-token.class.ts index cce695ac5f..86ed1b01a2 100755 --- a/packages/server-core/src/user/login-token/login-token.class.ts +++ b/packages/server-core/src/user/login-token/login-token.class.ts @@ -39,7 +39,7 @@ import { import { Application } from '../../../declarations' import { RootParams } from '../../api/root-params' -import { toDateTimeSql } from '../../util/get-datetime-sql' +import { toDateTimeSql } from '../../util/datetime-sql' // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface LoginTokenParams extends RootParams {} diff --git a/packages/server-core/src/user/login-token/login-token.resolvers.ts b/packages/server-core/src/user/login-token/login-token.resolvers.ts index 72cbaa5abe..8cc1688a75 100644 --- a/packages/server-core/src/user/login-token/login-token.resolvers.ts +++ b/packages/server-core/src/user/login-token/login-token.resolvers.ts @@ -24,15 +24,19 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { LoginTokenQuery, LoginTokenType } from '@etherealengine/engine/src/schemas/user/login-token.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const loginTokenResolver = resolve({}) +export const loginTokenResolver = resolve({ + expiresAt: virtual(async (loginToken) => fromDateTimeSql(loginToken.expiresAt)), + createdAt: virtual(async (loginToken) => fromDateTimeSql(loginToken.createdAt)), + updatedAt: virtual(async (loginToken) => fromDateTimeSql(loginToken.updatedAt)) +}) export const loginTokenExternalResolver = resolve({}) diff --git a/packages/server-core/src/user/user-api-key/user-api-key.resolvers.ts b/packages/server-core/src/user/user-api-key/user-api-key.resolvers.ts index b010dee7ae..81fb74afc6 100644 --- a/packages/server-core/src/user/user-api-key/user-api-key.resolvers.ts +++ b/packages/server-core/src/user/user-api-key/user-api-key.resolvers.ts @@ -24,15 +24,18 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { UserApiKeyQuery, UserApiKeyType } from '@etherealengine/engine/src/schemas/user/user-api-key.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const userApiKeyResolver = resolve({}) +export const userApiKeyResolver = resolve({ + createdAt: virtual(async (userApiKey) => fromDateTimeSql(userApiKey.createdAt)), + updatedAt: virtual(async (userApiKey) => fromDateTimeSql(userApiKey.updatedAt)) +}) export const userApiKeyExternalResolver = resolve({}) diff --git a/packages/server-core/src/user/user-kick/user-kick.resolvers.ts b/packages/server-core/src/user/user-kick/user-kick.resolvers.ts index 9559a7f94b..7b6df4d374 100644 --- a/packages/server-core/src/user/user-kick/user-kick.resolvers.ts +++ b/packages/server-core/src/user/user-kick/user-kick.resolvers.ts @@ -24,15 +24,19 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { UserKickID, UserKickQuery, UserKickType } from '@etherealengine/engine/src/schemas/user/user-kick.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' -export const userKickResolver = resolve({}) +export const userKickResolver = resolve({ + duration: virtual(async (userKick) => fromDateTimeSql(userKick.duration)), + createdAt: virtual(async (userKick) => fromDateTimeSql(userKick.createdAt)), + updatedAt: virtual(async (userKick) => fromDateTimeSql(userKick.updatedAt)) +}) export const userKickExternalResolver = resolve({}) diff --git a/packages/server-core/src/user/user-relationship/user-relationship.class.ts b/packages/server-core/src/user/user-relationship/user-relationship.class.ts index 3b92858bad..f58f02ef33 100755 --- a/packages/server-core/src/user/user-relationship/user-relationship.class.ts +++ b/packages/server-core/src/user/user-relationship/user-relationship.class.ts @@ -44,7 +44,7 @@ import { import { Knex } from 'knex' import { v4 } from 'uuid' import { RootParams } from '../../api/root-params' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { getDateTimeSql } from '../../util/datetime-sql' // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface UserRelationshipParams extends RootParams {} diff --git a/packages/server-core/src/user/user-relationship/user-relationship.resolvers.ts b/packages/server-core/src/user/user-relationship/user-relationship.resolvers.ts index 8e3a2e7bd2..598e347366 100644 --- a/packages/server-core/src/user/user-relationship/user-relationship.resolvers.ts +++ b/packages/server-core/src/user/user-relationship/user-relationship.resolvers.ts @@ -34,7 +34,7 @@ import { } from '@etherealengine/engine/src/schemas/user/user-relationship.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const userRelationshipResolver = resolve({ user: virtual(async (userRelationship, context) => { @@ -48,7 +48,9 @@ export const userRelationshipResolver = resolve fromDateTimeSql(userRelationship.createdAt)), + updatedAt: virtual(async (userRelationship) => fromDateTimeSql(userRelationship.updatedAt)) }) export const userRelationshipExternalResolver = resolve({}) diff --git a/packages/server-core/src/user/user-setting/user-setting.resolvers.ts b/packages/server-core/src/user/user-setting/user-setting.resolvers.ts index fa568b5763..cdb8d2a812 100644 --- a/packages/server-core/src/user/user-setting/user-setting.resolvers.ts +++ b/packages/server-core/src/user/user-setting/user-setting.resolvers.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html -import { resolve } from '@feathersjs/schema' +import { resolve, virtual } from '@feathersjs/schema' import { v4 } from 'uuid' import { @@ -35,9 +35,7 @@ import { } from '@etherealengine/engine/src/schemas/user/user-setting.schema' import type { HookContext } from '@etherealengine/server-core/declarations' -import { getDateTimeSql } from '../../util/get-datetime-sql' - -export const userSettingResolver = resolve({}) +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const userDbToSchema = (rawData: UserSettingDatabaseType): UserSettingType => { let themeModes = JSON.parse(rawData.themeModes) as Record @@ -59,6 +57,11 @@ export const userDbToSchema = (rawData: UserSettingDatabaseType): UserSettingTyp } } +export const userSettingResolver = resolve({ + createdAt: virtual(async (userSetting) => fromDateTimeSql(userSetting.createdAt)), + updatedAt: virtual(async (userSetting) => fromDateTimeSql(userSetting.updatedAt)) +}) + export const userSettingExternalResolver = resolve( {}, { diff --git a/packages/server-core/src/user/user/migrations/20230731113930_user.ts b/packages/server-core/src/user/user/migrations/20230731113930_user.ts index f49f24f3e0..adf2121329 100644 --- a/packages/server-core/src/user/user/migrations/20230731113930_user.ts +++ b/packages/server-core/src/user/user/migrations/20230731113930_user.ts @@ -31,14 +31,23 @@ import type { Knex } from 'knex' * @returns { Promise } */ export async function up(knex: Knex): Promise { - const tableExists = await knex.schema.hasTable(userPath) + // Added transaction here in order to ensure both below queries run on same pool. + // https://github.com/knex/knex/issues/218#issuecomment-56686210 + const trx = await knex.transaction() + await trx.raw('SET FOREIGN_KEY_CHECKS=0') - if (tableExists === false) { - // Added transaction here in order to ensure both below queries run on same pool. - // https://github.com/knex/knex/issues/218#issuecomment-56686210 - const trx = await knex.transaction() - await trx.raw('SET FOREIGN_KEY_CHECKS=0') + let tableExists = await trx.schema.hasTable(userPath) + + if (tableExists) { + const hasIdColum = await trx.schema.hasColumn(userPath, 'id') + const hasAvatarIdColumn = await trx.schema.hasColumn(userPath, 'avatarId') + if (!(hasIdColum && hasAvatarIdColumn)) { + await trx.schema.dropTable(userPath) + tableExists = false + } + } + if (tableExists === false) { await trx.schema.createTable(userPath, (table) => { //@ts-ignore table.uuid('id').collate('utf8mb4_bin').primary() @@ -53,10 +62,10 @@ export async function up(knex: Knex): Promise { table.foreign('avatarId').references('id').inTable('avatar').onDelete('SET NULL').onUpdate('CASCADE') }) - - await trx.raw('SET FOREIGN_KEY_CHECKS=1') - await trx.commit() } + + await trx.raw('SET FOREIGN_KEY_CHECKS=1') + await trx.commit() } /** diff --git a/packages/server-core/src/user/user/user.resolvers.ts b/packages/server-core/src/user/user/user.resolvers.ts index a4fa9743c1..0979ad5bb3 100644 --- a/packages/server-core/src/user/user/user.resolvers.ts +++ b/packages/server-core/src/user/user/user.resolvers.ts @@ -44,7 +44,7 @@ import { } from '@etherealengine/engine/src/schemas/user/identity-provider.schema' import { UserApiKeyType, userApiKeyPath } from '@etherealengine/engine/src/schemas/user/user-api-key.schema' import { UserSettingType, userSettingPath } from '@etherealengine/engine/src/schemas/user/user-setting.schema' -import { getDateTimeSql } from '../../util/get-datetime-sql' +import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql' export const userResolver = resolve({ avatar: virtual(async (user, context) => { @@ -123,7 +123,9 @@ export const userResolver = resolve({ } return [] - }) + }), + createdAt: virtual(async (user) => fromDateTimeSql(user.createdAt)), + updatedAt: virtual(async (user) => fromDateTimeSql(user.updatedAt)) }) export const userExternalResolver = resolve({ diff --git a/packages/server-core/src/util/get-datetime-sql.ts b/packages/server-core/src/util/datetime-sql.ts similarity index 70% rename from packages/server-core/src/util/get-datetime-sql.ts rename to packages/server-core/src/util/datetime-sql.ts index 1015a7838d..667673e587 100644 --- a/packages/server-core/src/util/get-datetime-sql.ts +++ b/packages/server-core/src/util/datetime-sql.ts @@ -31,3 +31,28 @@ export const getDateTimeSql = async () => { export const toDateTimeSql = (date: Date) => { return date.toISOString().slice(0, 19).replace('T', ' ') } + +// https://stackoverflow.com/a/11150727 +export const fromDateTimeSql = (date: string) => { + let dateObj: Date + if (typeof date === 'string') { + dateObj = new Date(date) + } else { + dateObj = date + } + const dateString = + dateObj.getFullYear() + + '-' + + ('00' + (dateObj.getMonth() + 1)).slice(-2) + + '-' + + ('00' + dateObj.getDate()).slice(-2) + + 'T' + + ('00' + dateObj.getHours()).slice(-2) + + ':' + + ('00' + dateObj.getMinutes()).slice(-2) + + ':' + + ('00' + dateObj.getSeconds()).slice(-2) + + '.000Z' + + return dateString +} diff --git a/packages/server/package.json b/packages/server/package.json index e65a49fcf5..f5088419b8 100755 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -37,8 +37,6 @@ "dev-api-server-nossl": "cross-env NOSSL=true ts-node --swc src/index.ts", "dev-nossl": "concurrently \"cross-env NOSSL=true ts-node --swc src/index.ts\" \"cd ../instanceserver && cross-env NOSSL=true ts-node --swc src/index.ts\"", "dev-reinit-db": "cross-env FORCE_DB_REFRESH=true ts-node --swc src/index.ts", - "migrate": "cd ../server-core && npm run migrate", - "migrate:rollback": "cd ../server-core && npm run migrate:rollback", "test": "echo \"TODO: no test specified\" && exit 0", "validate": "npm run build && npm run test", "serve-local-files": "http-server ./upload --ssl --cert ../../certs/cert.pem --key ../../certs/key.pem --port 8642 --cors=* --brotli --gzip" diff --git a/packages/ui/src/components/Chat/Message.tsx b/packages/ui/src/components/Chat/Message.tsx index 1635d065d1..9763c601cf 100644 --- a/packages/ui/src/components/Chat/Message.tsx +++ b/packages/ui/src/components/Chat/Message.tsx @@ -219,9 +219,9 @@ const MessageHeader = (props: { selectedChannelID: ChannelID }) => { }, []) const mediaNetworkState = useMediaNetwork() - const mediaHostId = Engine.instance.mediaNetwork?.hostId - const mediaConnected = mediaHostId && mediaNetworkState?.connected.value - const connecting = mediaHostId && !mediaNetworkState?.connected?.value + const mediaNetworkID = Engine.instance.mediaNetwork?.id + const mediaConnected = mediaNetworkID && mediaNetworkState?.connected.value + const connecting = mediaNetworkID && !mediaNetworkState?.connected?.value return ( <> @@ -295,8 +295,8 @@ export const MessageContainer = () => { const selectedChannelID = useHookstate(getMutableState(ChatState).selectedChannelID).value const mediaNetworkState = useMediaNetwork() - const mediaHostId = Engine.instance.mediaNetwork?.hostId - const mediaConnected = mediaHostId && mediaNetworkState?.connected.value + const mediaNetworkID = Engine.instance.mediaNetwork?.id + const mediaConnected = mediaNetworkID && mediaNetworkState?.connected.value return ( <> diff --git a/packages/ui/src/components/Chat/VideoCall.tsx b/packages/ui/src/components/Chat/VideoCall.tsx index 43bd6cfcce..3ce8b2f3bf 100644 --- a/packages/ui/src/components/Chat/VideoCall.tsx +++ b/packages/ui/src/components/Chat/VideoCall.tsx @@ -57,12 +57,11 @@ export const UserMedia = (props: { peerID: PeerID; type: 'cam' | 'screen' }) => !mediaNetwork || peerID === Engine.instance.peerID || (mediaNetwork?.peers && - Array.from(mediaNetwork.peers.values()).find((peer) => peer.userId === Engine.instance.userID)?.peerID === - peerID) || + Object.values(mediaNetwork.peers).find((peer) => peer.userId === Engine.instance.userID)?.peerID === peerID) || peerID === 'self' const isScreen = type === 'screen' - const userID = mediaNetwork.peers.get(peerID)?.userId! + const userID = mediaNetwork.peers[peerID]?.userId const userThumbnail = useUserAvatarThumbnail(userID) diff --git a/packages/ui/src/primitives/mui/DashboardItems/index.tsx b/packages/ui/src/primitives/mui/DashboardItems/index.tsx index fc3a56ce5b..ad27a5c0e9 100644 --- a/packages/ui/src/primitives/mui/DashboardItems/index.tsx +++ b/packages/ui/src/primitives/mui/DashboardItems/index.tsx @@ -96,7 +96,7 @@ export const SidebarItems = (allowedRoutes) => [ allowedRoutes.bot && { name: 'user:dashboard.bots', path: '/admin/bots', - icon: + icon: }, allowedRoutes.recording && { name: 'user:dashboard.recordings', diff --git a/packages/ui/src/primitives/mui/Icon/index.stories.tsx b/packages/ui/src/primitives/mui/Icon/index.stories.tsx index c2f32b6636..5ddeabc333 100644 --- a/packages/ui/src/primitives/mui/Icon/index.stories.tsx +++ b/packages/ui/src/primitives/mui/Icon/index.stories.tsx @@ -160,7 +160,8 @@ const argTypes = { 'ChevronRight', 'Sync', 'Download', - 'Save' + 'Save', + 'SmartToy' ] } } diff --git a/packages/ui/src/primitives/mui/Icon/index.tsx b/packages/ui/src/primitives/mui/Icon/index.tsx index 2820290a22..f750a9f49e 100644 --- a/packages/ui/src/primitives/mui/Icon/index.tsx +++ b/packages/ui/src/primitives/mui/Icon/index.tsx @@ -137,6 +137,7 @@ import { Send, Settings, Shuffle, + SmartToy, SportsScore, SquareFoot, StopCircle, @@ -448,6 +449,8 @@ const Icon = ({ type, ...props }: SvgIconProps & { type: string }) => { return case 'Toys': return + case 'SmartToy': + return case 'Phone': return case 'FlipCameraAndroid':