From 90353d04a5cadba3a15a687fc9831a6c28adfe90 Mon Sep 17 00:00:00 2001 From: Daniel Belmes <3631206+DanielBelmes@users.noreply.github.com> Date: Wed, 26 Jun 2024 16:28:50 -0700 Subject: [PATCH 01/10] change grid defaults (#10460) --- .../src/renderer/components/InfiniteGridHelper.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/spatial/src/renderer/components/InfiniteGridHelper.ts b/packages/spatial/src/renderer/components/InfiniteGridHelper.ts index d89d9c98e5..4d17477348 100644 --- a/packages/spatial/src/renderer/components/InfiniteGridHelper.ts +++ b/packages/spatial/src/renderer/components/InfiniteGridHelper.ts @@ -94,13 +94,13 @@ float getGrid(float size) { } float getXAxisLine() { - float lineWidth = 0.02; // Adjust line width if needed + float lineWidth = 0.1; // Adjust line width if needed float xLine = smoothstep(-lineWidth, lineWidth, abs(worldPosition.x)); return 1.0 - xLine; } float getZAxisLine() { - float lineWidth = 0.02; // Adjust line width if needed + float lineWidth = 0.1; // Adjust line width if needed float zLine = smoothstep(-lineWidth, lineWidth, abs(worldPosition.z)); return 1.0 - zLine; } @@ -114,14 +114,13 @@ void main() { float g2 = getGrid(uSize2); float xAxisLine = getXAxisLine(); float zAxisLine = getZAxisLine(); - vec3 xAxisColor = vec3(1.0, 0.0, 0.0); - vec3 zAxisColor = vec3(0.0, 0.0, 1.0); if (xAxisLine > 0.0 || zAxisLine > 0.0) { discard; } else { - gl_FragColor = vec4(uColor.rgb, mix(g2, g1, g1) * pow(d, 3.0)); + gl_FragColor = vec4(uColor.rgb, mix(g2, g1, g1)); gl_FragColor.a = mix(0.5 * gl_FragColor.a, gl_FragColor.a, g2); + gl_FragColor.a *= pow(d, 3.0); } if ( gl_FragColor.a <= 0.0 ) discard; @@ -133,8 +132,8 @@ export const InfiniteGridComponent = defineComponent({ onInit(entity) { return { size: 1, - color: new Color('white'), - distance: 8000 + color: new Color(0x535353), + distance: 200 } }, From 1b9ee3d3f7d276045287087b9f6e538b35b13157 Mon Sep 17 00:00:00 2001 From: Daniel Belmes <3631206+DanielBelmes@users.noreply.github.com> Date: Wed, 26 Jun 2024 16:59:52 -0700 Subject: [PATCH 02/10] Fix fog color and improve colorInput (#10463) --- packages/ui/src/components/editor/properties/fog/index.tsx | 1 + packages/ui/src/primitives/tailwind/Color/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/editor/properties/fog/index.tsx b/packages/ui/src/components/editor/properties/fog/index.tsx index 877d3a3674..692301a7c2 100644 --- a/packages/ui/src/components/editor/properties/fog/index.tsx +++ b/packages/ui/src/components/editor/properties/fog/index.tsx @@ -90,6 +90,7 @@ export const FogSettingsEditor: EditorComponentType = (props) => { updateProperty(FogSettingsComponent, 'color')('#' + val.getHexString())} + onRelease={(val) => commitProperty(FogSettingsComponent, 'color')('#' + val.getHexString())} /> {fogState.type.value === FogType.Linear ? ( diff --git a/packages/ui/src/primitives/tailwind/Color/index.tsx b/packages/ui/src/primitives/tailwind/Color/index.tsx index f74027267e..13adeec042 100644 --- a/packages/ui/src/primitives/tailwind/Color/index.tsx +++ b/packages/ui/src/primitives/tailwind/Color/index.tsx @@ -66,12 +66,12 @@ export function ColorInput({ >
Date: Thu, 27 Jun 2024 01:53:57 -0400 Subject: [PATCH 03/10] remove bottom margin (#10464) --- .../ui/src/components/editor/panels/Files/browserGrid/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/editor/panels/Files/browserGrid/index.tsx b/packages/ui/src/components/editor/panels/Files/browserGrid/index.tsx index 94799894b4..9c886ee9b2 100644 --- a/packages/ui/src/components/editor/panels/Files/browserGrid/index.tsx +++ b/packages/ui/src/components/editor/panels/Files/browserGrid/index.tsx @@ -183,7 +183,7 @@ export const FileGridItem: React.FC = (props) => { color="text-[#375DAF]" />
-
{props.item.fullName}
+
{props.item.fullName}
) } From 4ea6942fc0f39af7dbeacd195942a0b9ca1deaa8 Mon Sep 17 00:00:00 2001 From: Josh Field Date: Thu, 27 Jun 2024 16:34:31 +1000 Subject: [PATCH 04/10] IR-2824-Reinstate-original-publish-workflow-in-studio (#10462) Co-authored-by: Aditya Mitra <55396651+aditya-mitra@users.noreply.github.com> --- .../locations/AddEditLocationModal.tsx | 11 +++++++-- .../src/components/toolbar/Toolbar2.tsx | 23 +++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/client-core/src/admin/components/locations/AddEditLocationModal.tsx b/packages/client-core/src/admin/components/locations/AddEditLocationModal.tsx index 969024a3e3..83deea0895 100644 --- a/packages/client-core/src/admin/components/locations/AddEditLocationModal.tsx +++ b/packages/client-core/src/admin/components/locations/AddEditLocationModal.tsx @@ -49,7 +49,13 @@ const locationTypeOptions = [ { label: 'Showroom', value: 'showroom' } ] -export default function AddEditLocationModal({ location }: { location?: LocationType }) { +export default function AddEditLocationModal({ + location, + sceneID +}: { + location?: LocationType + sceneID?: string | null +}) { const { t } = useTranslation() const locationMutation = useMutation(locationPath) @@ -59,7 +65,8 @@ export default function AddEditLocationModal({ location }: { location?: Location const name = useHookstate(location?.name || '') const maxUsers = useHookstate(location?.maxUsersPerInstance || 20) - const scene = useHookstate(location?.sceneId || '') + + const scene = useHookstate((location ? location.sceneId : sceneID) ?? '') const videoEnabled = useHookstate(location?.locationSetting.videoEnabled || true) const audioEnabled = useHookstate(location?.locationSetting.audioEnabled || true) const screenSharingEnabled = useHookstate(location?.locationSetting.screenSharingEnabled || true) diff --git a/packages/editor/src/components/toolbar/Toolbar2.tsx b/packages/editor/src/components/toolbar/Toolbar2.tsx index a8d03d1b8e..b6c649f3e7 100644 --- a/packages/editor/src/components/toolbar/Toolbar2.tsx +++ b/packages/editor/src/components/toolbar/Toolbar2.tsx @@ -23,13 +23,16 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import AddEditLocationModal from '@etherealengine/client-core/src/admin/components/locations/AddEditLocationModal' import { NotificationService } from '@etherealengine/client-core/src/common/services/NotificationService' import { PopoverState } from '@etherealengine/client-core/src/common/services/PopoverState' import { RouterState } from '@etherealengine/client-core/src/common/services/RouterService' import { useProjectPermissions } from '@etherealengine/client-core/src/user/useUserProjectPermission' import { useUserHasAccessHook } from '@etherealengine/client-core/src/user/userHasAccess' +import { locationPath } from '@etherealengine/common/src/schema.type.module' import { GLTFModifiedState } from '@etherealengine/engine/src/gltf/GLTFDocumentState' import { getMutableState, getState, useHookstate, useMutableState } from '@etherealengine/hyperflux' +import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHooks' import ContextMenu from '@etherealengine/ui/src/components/editor/layout/ContextMenu' import Button from '@etherealengine/ui/src/primitives/tailwind/Button' import { t } from 'i18next' @@ -113,11 +116,13 @@ export default function Toolbar() { const anchorEvent = useHookstate>(null) const anchorPosition = useHookstate({ left: 0, top: 0 }) - const { projectName, sceneName } = useMutableState(EditorState) + const { projectName, sceneName, sceneAssetID } = useMutableState(EditorState) const hasLocationWriteScope = useUserHasAccessHook('location:write') const permission = useProjectPermissions(projectName.value!) const hasPublishAccess = hasLocationWriteScope || permission?.type === 'owner' || permission?.type === 'editor' + const locationQuery = useFind(locationPath, { query: { sceneId: sceneAssetID.value } }) + const currentLocation = locationQuery.data.length === 1 ? locationQuery.data[0] : undefined return ( <> @@ -148,9 +153,19 @@ export default function Toolbar() { / {sceneName.value} - + {sceneAssetID.value && ( + + )} } From bc3cc91bda67567a2963439d9beb057344e69dbe Mon Sep 17 00:00:00 2001 From: Appaji <52322531+CITIZENDOT@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:54:05 +0530 Subject: [PATCH 05/10] IR-2719: Unify & unit-test all regex patterns at one place (#10444) * Move all regex patterns into one module * add license * WIP: Add unit tests * disable eslint for 1st line * Add remaining unit tests * Rename variable * Remove HTTPS_REGEX pattern * Fix `GITHUB_URL_REGEX` usecases * Test for group captures --------- Co-authored-by: Hanzla Mateen --- .../admin/common/Project/ProjectFields.tsx | 6 +- .../project/AddEditProjectModal.tsx | 6 +- .../src/social/services/InviteService.ts | 7 +- .../components/UserMenu/menus/ShareMenu.tsx | 2 +- packages/client/pwa.config.ts | 9 +- packages/common/src/config.ts | 6 +- .../common/src/constants/AvatarConstants.ts | 2 - .../common/src/constants/GitHubConstants.ts | 3 - packages/common/src/constants/IdConstants.ts | 30 - .../src/constants/ProjectKeyConstants.ts | 31 - packages/common/src/regex/index.ts | 135 ++++ .../common/src/utils/validateSceneName.ts | 15 +- packages/common/tests/regex.test.ts | 672 ++++++++++++++++++ .../src/components/element/ElementList.tsx | 4 +- .../hierarchy/HierarchyPanelContainer.tsx | 4 +- .../src/components/prefabs/PrefabList.tsx | 5 +- .../components/properties/ModelNodeEditor.tsx | 6 +- packages/editor/src/functions/exportGLTF.ts | 4 +- .../src/assets/constants/LoaderConstants.ts | 2 - .../gltf/extensions/ImageRoutingExtension.ts | 6 +- .../src/assets/functions/pathResolver.ts | 11 +- .../src/scene/functions/GLTFConversion.ts | 3 +- .../functions/loaders/VariantFunctions.ts | 6 +- .../hooks/project-permission-authenticate.ts | 6 +- .../src/media/storageprovider/s3.storage.ts | 17 +- .../project-permission.hooks.ts | 2 +- .../src/projects/project/github-helper.ts | 11 +- .../src/projects/project/project-helper.ts | 3 +- .../src/projects/project/project.hooks.ts | 10 +- .../client-setting/client-setting.hooks.ts | 11 +- .../src/common/functions/isAbsolutePath.ts | 5 +- .../panels/Properties/elementList/index.tsx | 8 +- .../editor/properties/model/index.tsx | 6 +- .../core/serialization/generateEmbeddedCSS.ts | 15 +- 34 files changed, 903 insertions(+), 166 deletions(-) delete mode 100644 packages/common/src/constants/IdConstants.ts delete mode 100644 packages/common/src/constants/ProjectKeyConstants.ts create mode 100644 packages/common/src/regex/index.ts create mode 100644 packages/common/tests/regex.test.ts diff --git a/packages/client-core/src/admin/common/Project/ProjectFields.tsx b/packages/client-core/src/admin/common/Project/ProjectFields.tsx index 2addc06b39..6bb86a1433 100644 --- a/packages/client-core/src/admin/common/Project/ProjectFields.tsx +++ b/packages/client-core/src/admin/common/Project/ProjectFields.tsx @@ -233,8 +233,8 @@ const ProjectFields = ({ ProjectUpdateService.setSourceProjectName(project.name, '') return } - const valueRegex = new RegExp(`^${value}`, 'g') - let matchingCommit = commitData.find((data) => valueRegex.test(data.commitSHA)) + + let matchingCommit = commitData.find((data) => data.commitSHA.startsWith(value)) if (!matchingCommit) { const commitResponse = (await ProjectService.checkUnfetchedCommit({ url: projectUpdateStatus.value.sourceURL, @@ -251,7 +251,7 @@ const ProjectFields = ({ resolve(null) }, 100) }) - matchingCommit = commitData.find((data) => valueRegex.test(data.commitSHA)) + matchingCommit = commitData.find((data) => data.commitSHA.startsWith(value)) } } ProjectUpdateService.setSourceProjectName(project.name, matchingCommit?.projectName || '') diff --git a/packages/client-core/src/admin/components/project/AddEditProjectModal.tsx b/packages/client-core/src/admin/components/project/AddEditProjectModal.tsx index cde67234ab..f88e8da0a3 100644 --- a/packages/client-core/src/admin/components/project/AddEditProjectModal.tsx +++ b/packages/client-core/src/admin/components/project/AddEditProjectModal.tsx @@ -169,8 +169,8 @@ export default function AddEditProjectModal({ ProjectUpdateService.setSourceProjectName(project.name, '') return } - const valueRegex = new RegExp(`^${commitValue}`, 'g') - let matchingCommit = commitData.find((data) => valueRegex.test(data.commitSHA)) + + let matchingCommit = commitData.find((data) => data.commitSHA.startsWith(commitValue)) if (!matchingCommit) { const commitResponse = (await ProjectService.checkUnfetchedCommit({ url: projectUpdateStatus.value.sourceURL, @@ -187,7 +187,7 @@ export default function AddEditProjectModal({ resolve(null) }, 100) }) - matchingCommit = commitData.find((data) => valueRegex.test(data.commitSHA)) + matchingCommit = commitData.find((data) => data.commitSHA.startsWith(commitValue)) } } ProjectUpdateService.setSourceProjectName(project.name, matchingCommit?.projectName || '') diff --git a/packages/client-core/src/social/services/InviteService.ts b/packages/client-core/src/social/services/InviteService.ts index 3ba40d81b0..b71085fcf8 100644 --- a/packages/client-core/src/social/services/InviteService.ts +++ b/packages/client-core/src/social/services/InviteService.ts @@ -26,12 +26,7 @@ Ethereal Engine. All Rights Reserved. import { Paginated } from '@feathersjs/feathers' import { useEffect } from 'react' -import { - EMAIL_REGEX, - INVITE_CODE_REGEX, - PHONE_REGEX, - USER_ID_REGEX -} from '@etherealengine/common/src/constants/IdConstants' +import { EMAIL_REGEX, INVITE_CODE_REGEX, PHONE_REGEX, USER_ID_REGEX } from '@etherealengine/common/src/regex' import { InviteCode, InviteData, diff --git a/packages/client-core/src/user/components/UserMenu/menus/ShareMenu.tsx b/packages/client-core/src/user/components/UserMenu/menus/ShareMenu.tsx index a3cc5cbd58..0a816ec9a2 100755 --- a/packages/client-core/src/user/components/UserMenu/menus/ShareMenu.tsx +++ b/packages/client-core/src/user/components/UserMenu/menus/ShareMenu.tsx @@ -33,8 +33,8 @@ import InputCheck from '@etherealengine/client-core/src/common/components/InputC import InputText from '@etherealengine/client-core/src/common/components/InputText' import Menu from '@etherealengine/client-core/src/common/components/Menu' import { NotificationService } from '@etherealengine/client-core/src/common/services/NotificationService' -import { EMAIL_REGEX, PHONE_REGEX } from '@etherealengine/common/src/constants/IdConstants' import multiLogger from '@etherealengine/common/src/logger' +import { EMAIL_REGEX, PHONE_REGEX } from '@etherealengine/common/src/regex' import { InviteCode, InviteData } from '@etherealengine/common/src/schema.type.module' import { useMutableState } from '@etherealengine/hyperflux' import { isShareAvailable } from '@etherealengine/spatial/src/common/functions/DetectFeatures' diff --git a/packages/client/pwa.config.ts b/packages/client/pwa.config.ts index 43843bb413..834303a1d3 100644 --- a/packages/client/pwa.config.ts +++ b/packages/client/pwa.config.ts @@ -27,6 +27,9 @@ import { VitePWA } from 'vite-plugin-pwa' import manifest from './manifest.default.json' +const WILDCARD_REGEX = /^\/.*$/ +const LOCAL_FILESYSTEM_REGEX = /^\/@fs\/.*$/ + /** * Creates a new instance of the VitePWA plugin for Vite.js. * @param {Object} clientSetting - An object containing custom settings for the PWA. @@ -75,9 +78,9 @@ const PWA = (clientSetting) => // Allowlist all paths for navigateFallback during development navigateFallbackAllowlist: [ // allow everything - new RegExp('^/.*$'), + WILDCARD_REGEX, // allow @fs - new RegExp('^/@fs/.*$') + LOCAL_FILESYSTEM_REGEX ] }, workbox: { @@ -94,7 +97,7 @@ const PWA = (clientSetting) => // Allowlist all paths for navigateFallback during production navigateFallbackAllowlist: [ // allow everything - new RegExp('^/.*$') + WILDCARD_REGEX ], // Set the glob directory and patterns for the cache globDirectory: process.env.APP_ENV === 'development' ? './public' : './dist', diff --git a/packages/common/src/config.ts b/packages/common/src/config.ts index f569349298..ea8726b97e 100644 --- a/packages/common/src/config.ts +++ b/packages/common/src/config.ts @@ -23,16 +23,14 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { EMAIL_REGEX } from './regex' + /** * Config settings (for client and isomorphic engine usage). */ const localBuildOrDev = globalThis.process.env.APP_ENV === 'development' || globalThis.process.env.VITE_LOCAL_BUILD === 'true' -// https://fightingforalostcause.net/content/misc/2006/compare-email-regex.php -const EMAIL_REGEX = - /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i - export function validateEmail(email: string): boolean { return EMAIL_REGEX.test(email) } diff --git a/packages/common/src/constants/AvatarConstants.ts b/packages/common/src/constants/AvatarConstants.ts index fc5b340c26..490bf6161e 100755 --- a/packages/common/src/constants/AvatarConstants.ts +++ b/packages/common/src/constants/AvatarConstants.ts @@ -33,5 +33,3 @@ export const THUMBNAIL_FILE_ALLOWED_EXTENSIONS = '.png,.jpg' export const THUMBNAIL_WIDTH = 300 export const THUMBNAIL_HEIGHT = 300 export const MAX_ALLOWED_TRIANGLES = 100000 -export const REGEX_VALID_URL = - /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g diff --git a/packages/common/src/constants/GitHubConstants.ts b/packages/common/src/constants/GitHubConstants.ts index 7cc84076b0..3d8eaa44ab 100644 --- a/packages/common/src/constants/GitHubConstants.ts +++ b/packages/common/src/constants/GitHubConstants.ts @@ -23,7 +23,4 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -export const GITHUB_URL_REGEX = /(?:git@|https:\/\/)([a-zA-Z0-9\-]+:[a-zA-Z0-9_]+@)?github.com[:/](.*)[.git]?/ export const GITHUB_PER_PAGE = 100 -export const PUBLIC_SIGNED_REGEX = /https:\/\/[\w\d\s\-_]+:[\w\d\s\-_]+@github.com\/([\w\d\s\-_]+)\/([\w\d\s\-_]+).git/ -export const INSTALLATION_SIGNED_REGEX = /https:\/\/oauth2:[\w\d\s\-_]+@github.com\/([\w\d\s\-_]+)\/([\w\d\s\-_]+).git/ diff --git a/packages/common/src/constants/IdConstants.ts b/packages/common/src/constants/IdConstants.ts deleted file mode 100644 index 0ebaa7a6c0..0000000000 --- a/packages/common/src/constants/IdConstants.ts +++ /dev/null @@ -1,30 +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. -*/ - -export const USER_ID_REGEX = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/ -export const EMAIL_REGEX = - /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/ -export const PHONE_REGEX = /^[0-9]{10}$/ -export const INVITE_CODE_REGEX = /^[0-9a-fA-F]{8}$/ diff --git a/packages/common/src/constants/ProjectKeyConstants.ts b/packages/common/src/constants/ProjectKeyConstants.ts deleted file mode 100644 index 3dc72cc700..0000000000 --- a/packages/common/src/constants/ProjectKeyConstants.ts +++ /dev/null @@ -1,31 +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. -*/ - -//FIXME: Legacy location of public assets, should be deleted and all files' use of this removed once all existing -//projects have had their public assets moved to the /public folder -export const assetsRegex = /projects\/[a-zA-Z0-9-_\/]+\/assets\// -export const projectRegex = /projects\/[a-zA-Z0-9-_\/]+/ -export const projectPublicRegex = /projects\/[a-zA-Z0-9-_\/]+\/public\// -export const projectThumbnailsRegex = /projects\/[a-zA-Z0-9-_\/]+\/thumbnails\// diff --git a/packages/common/src/regex/index.ts b/packages/common/src/regex/index.ts new file mode 100644 index 0000000000..957b4f3140 --- /dev/null +++ b/packages/common/src/regex/index.ts @@ -0,0 +1,135 @@ +/* +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. +*/ + +// eslint-disable-next-line no-control-regex +export const INVALID_FILENAME_REGEX = /[_<>:"/\\|?*\u0000-\u001F]/g +export const WINDOWS_RESERVED_NAME_REGEX = /^(con|prn|aux|nul|com\d|lpt\d)$/i +export const VALID_SCENE_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_\-\s]{1,62}[a-zA-Z0-9_\-]$/ + +/** + * Matches CSS imports & URLS. + * For eg: `@import "styles.css"`, `url(image.png)`. Captures the resource in group 2 or group 3. + */ +export const CSS_URL_REGEX = /(@import\s+["']([^"']+)["']|url\((?!['"]?(?:data):)['"]?([^'"\)]+)['"]?\))/gi + +/** + * Matches absolute URLs. For eg: `http://example.com`, `https://example.com`, `ftp://example.com`, `//example.com`, etc. + * This Does NOT match relative URLs like `example.com` + */ +export const ABSOLUTE_URL_PROTOCOL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/ + +/** + * Captures project name and asset path from a URL. + * For eg: `/path/to/projects/project123/assets/images/logo.png` will capture following groups + * - `project123` => Group 1 + * - `assets/images/logo.png` => Group 2 + */ +export const STATIC_ASSET_REGEX = /^(?:.*\/(?:projects|static-resources)\/([^\/]*)\/((?:assets\/|).*)$)/ + +// ===================================================================== +// ========================= ID Regex Patterns ========================= +// ===================================================================== + +/** + * This regex is used to validate a string that conforms to the UUID version 4 format. It ensures that the string consists of exactly 32 hexadecimal digits arranged in the 8-4-4-4-12 pattern, separated by hyphens. + */ +export const USER_ID_REGEX = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/ + +/** + * Email regex. Source: https://fightingforalostcause.net/content/misc/2006/compare-email-regex.php + */ +export const EMAIL_REGEX = + /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i + +/** + * This regex is useful for validating input that must be exactly 10 digits long, such as a phone number (without any separators or formatting). + */ +export const PHONE_REGEX = /^[0-9]{10}$/ + +/** + * This regex is used to validate strings that should consist of exactly 8 hexadecimal digits. + */ +export const INVITE_CODE_REGEX = /^[0-9a-fA-F]{8}$/ + +// ========================================================================= +// ========================= GitHub Regex Patterns ========================= +// ========================================================================= + +/** + * This regular expression is designed to match GitHub repository URLs in both SSH and HTTPS formats. Group 2 captures the owner and repository name. + * - For eg: `git@github.com:user/repo.git` + * - `user/repo` => Group 1 + */ +export const GITHUB_URL_REGEX = /(?:git@github.com:|https:\/\/github.com\/)([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)(\.git)?$/ + +/** + * This regex is useful for extracting the owner and repository name from GitHub HTTPS URLs that contain authentication credentials in the URL itself. Group 1 captures the owner and Group 2 captures the repository name. + * - For eg: `https://username:password@github.com/owner/repository-name.git` + * - `owner` => Group 1 + * - `repository-name` => Group 2 + */ +export const PUBLIC_SIGNED_REGEX = /https:\/\/[\w\d\s\-_]+:[\w\d\s\-_]+@github.com\/([\w\d\s\-_]+)\/([\w\d\s\-_]+).git$/ + +/** + * This regex is useful for extracting the owner and repository name from GitHub HTTPS URLs that contain OAuth2 token or credentials in the URL itself. Group 1 captures the owner and Group 2 captures the repository name. + * - For eg: `https://oauth2:token123@github.com/owner/repository-name.git` + * - `owner` => Group 1 + * - `repository-name` => Group 2 + */ +export const INSTALLATION_SIGNED_REGEX = /https:\/\/oauth2:[\w\d\s\-_]+@github.com\/([\w\d\s\-_]+)\/([\w\d\s\-_]+).git$/ + +// ============================================================================== +// ========================= Project Key Regex Patterns ========================= +// ============================================================================== + +/** + * This regex is used to match specific file paths or directory structures that start with `projects/`, followed by one or more valid characters (letters, digits, hyphens, underscores, or forward slashes), and end with `/assets/` + */ +export const ASSETS_REGEX = /projects\/[a-zA-Z0-9-_\/]+\/assets\// + +/** + * This regex matches strings that start with `projects/`, followed by one or more characters that can be letters, digits, hyphens, underscores, or forward slashes. + */ +export const PROJECT_REGEX = /projects\/[a-zA-Z0-9-_\/]+/ + +/** + * This regex matches strings that start with `projects/`, followed by one or more characters that can be letters, digits, hyphens, underscores, or forward slashes, and then `/public/`. + */ +export const PROJECT_PUBLIC_REGEX = /projects\/[a-zA-Z0-9-_\/]+\/public\// + +/** + * This regex matches strings that start with `projects/`, followed by one or more characters that can be letters, digits, hyphens, underscores, or forward slashes, and then `/thumbnails/`. + */ +export const PROJECT_THUMBNAIL_REGEX = /projects\/[a-zA-Z0-9-_\/]+\/thumbnails\// + +export const VALID_PROJECT_NAME = /^(?!\s)[\w\-\s]+$/ + +// ========================================================================== +// ========================= Dynamic Regex Patterns ========================= +// ========================================================================== + +export const getCacheRegex = (fileServer: string) => { + return new RegExp(`${fileServer}\/projects`) +} diff --git a/packages/common/src/utils/validateSceneName.ts b/packages/common/src/utils/validateSceneName.ts index 721034d0ab..90b283f97c 100644 --- a/packages/common/src/utils/validateSceneName.ts +++ b/packages/common/src/utils/validateSceneName.ts @@ -23,17 +23,18 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -// eslint-disable-next-line no-control-regex -const invalidFileNameRegex = /[_<>:"/\\|?*\u0000-\u001F]/g -const windowsReservedNameRegex = /^(con|prn|aux|nul|com\d|lpt\d)$/i -const validSceneNameRegex = /^[a-zA-Z0-9][a-zA-Z0-9_\-\s]{1,62}[a-zA-Z0-9_\-]$/ +import { + INVALID_FILENAME_REGEX, + VALID_SCENE_NAME_REGEX, + WINDOWS_RESERVED_NAME_REGEX +} from '@etherealengine/common/src/regex' export default function isValidSceneName(sceneName: string) { return ( sceneName.length >= 3 && sceneName.length <= 64 && - !invalidFileNameRegex.test(sceneName) && - !windowsReservedNameRegex.test(sceneName) && - validSceneNameRegex.test(sceneName) + !INVALID_FILENAME_REGEX.test(sceneName) && + !WINDOWS_RESERVED_NAME_REGEX.test(sceneName) && + VALID_SCENE_NAME_REGEX.test(sceneName) ) } diff --git a/packages/common/tests/regex.test.ts b/packages/common/tests/regex.test.ts new file mode 100644 index 0000000000..d8a503b597 --- /dev/null +++ b/packages/common/tests/regex.test.ts @@ -0,0 +1,672 @@ +/* +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 assert from 'assert' +import { + ABSOLUTE_URL_PROTOCOL_REGEX, + ASSETS_REGEX, + CSS_URL_REGEX, + EMAIL_REGEX, + GITHUB_URL_REGEX, + INSTALLATION_SIGNED_REGEX, + INVALID_FILENAME_REGEX, + INVITE_CODE_REGEX, + PHONE_REGEX, + PROJECT_PUBLIC_REGEX, + PROJECT_REGEX, + PROJECT_THUMBNAIL_REGEX, + PUBLIC_SIGNED_REGEX, + STATIC_ASSET_REGEX, + USER_ID_REGEX, + VALID_PROJECT_NAME, + VALID_SCENE_NAME_REGEX, + WINDOWS_RESERVED_NAME_REGEX +} from '../src/regex' + +describe('regex.test', () => { + describe('INVALID_FILENAME_REGEX', () => { + it('should match invalid filenames', () => { + const invalidFilenames = [ + 'hello_world', + 'file { + assert.ok(INVALID_FILENAME_REGEX.test(filename), `Expected '${filename}' to be invalid`) + INVALID_FILENAME_REGEX.lastIndex = 0 + }) + }) + + it('should not match valid filenames', () => { + const validFilenames = [ + 'helloworld', + 'filename', + 'emailexample.com', + 'pathtofile', + 'backslash', + 'pipesymbol', + 'questionmark', + 'asteriskchar', + 'controlchar', + 'anothercontrol' + ] + validFilenames.forEach((filename) => { + assert.ok(!INVALID_FILENAME_REGEX.test(filename), `Expected '${filename}' to be valid`) + INVALID_FILENAME_REGEX.lastIndex = 0 + }) + }) + }) + + describe('WINDOWS_RESERVED_NAME_REGEX', () => { + it('should match windows reserved names', () => { + const reservedNames = ['CON', 'PrN', 'auX', 'NUL', 'COM0', 'com1', 'Com9', 'LPT0', 'LpT4', 'lPt9'] + reservedNames.forEach((filename) => { + assert.ok(WINDOWS_RESERVED_NAME_REGEX.test(filename), `Expected '${filename}' to be windows reserved name`) + }) + }) + + it('should not match common filenames', () => { + const validFilenames = [ + 'helloworld', + 'filename', + 'emailexample.com', + 'pathtofile', + 'backslash', + 'pipesymbol', + 'questionmark', + 'asteriskchar', + 'controlchar', + 'anothercontrol' + ] + validFilenames.forEach((filename) => { + assert.ok(!WINDOWS_RESERVED_NAME_REGEX.test(filename), `Expected '${filename}' to be valid filename`) + }) + }) + }) + + describe('VALID_SCENE_NAME_REGEX', () => { + it('should match valid scene names', () => { + const validSceneNames = [ + 'A123', + 'file_name', + 'user-name', + '12345', + 'test file', + 'my-file_123', + 'Name123_456-789', + 'a_b_c_d-e_f_g-h_i_j-k_l_m-n_o-p-q_r-s_t-u_v-w_x-y_z0123456789_-' + ] + validSceneNames.forEach((filename) => { + assert.ok(VALID_SCENE_NAME_REGEX.test(filename), `Expected '${filename}' to be valid scene names`) + }) + }) + + it('should not match invalid scene names', () => { + const invalidSceneNames = [ + 'A1', + '_test', + 'invalid!', + 'very_long_string_that_is_definitely_not_going_to_match_the_regex_because_it_is_way_too_long_for_the_pattern', + '--double-hyphen', + '...' + ] + invalidSceneNames.forEach((filename) => { + assert.ok(!VALID_SCENE_NAME_REGEX.test(filename), `Expected '${filename}' to be invalid scene names`) + }) + }) + }) + + describe('CSS_URL_REGEX', () => { + it('should match for CSS sources with valid URLs', () => { + const positiveCases = [ + { + source: '@import url("https://example.com/styles.css");', + urlResource: 'https://example.com/styles.css' + }, + { + source: 'background: url("https://example.com/image.jpg");', + urlResource: 'https://example.com/image.jpg' + }, + { + source: "background: url('https://example.com/image.jpg');", + urlResource: 'https://example.com/image.jpg' + }, + { + source: 'background: url(https://example.com/image.jpg);', + urlResource: 'https://example.com/image.jpg' + } + ] + + positiveCases.forEach(({ source, urlResource }) => { + const match = CSS_URL_REGEX.exec(source) + assert.ok(match, `Expected '${source}' to match CSS_URL_REGEX`) + assert.equal(match?.[2] ?? match?.[3], urlResource, `Expected URL resource: ${urlResource} in '${source}'`) + CSS_URL_REGEX.lastIndex = 0 // reset the regex (important for patterns with global flag) + }) + }) + + it('should not match invalid CSS imports & URLs', () => { + const negativeCases = [ + 'color: #fff;', + 'background: urll(https://example.com/image.jpg);', // Misspelled 'url' + 'url(https://example.com/image', // Missing closing parenthesis + 'background: url();' // Empty URL + ] + + negativeCases.forEach((source) => { + assert.ok(!CSS_URL_REGEX.test(source), `Expected '${source}' to not match CSS_URL_REGEX`) + }) + }) + }) + + describe('ABSOLUTE_URL_REGEX', () => { + it('should match absolute URLs', () => { + const positiveCases = ['http://example.com', 'https://example.com', 'ftp://example.com', '//example.com'] + + positiveCases.forEach((url) => { + assert.match(url, ABSOLUTE_URL_PROTOCOL_REGEX, `Expected '${url}' to match ABSOLUTE_URL_REGEX`) + }) + }) + + it('should not match relative URLs', () => { + assert.doesNotMatch( + 'example.com', + ABSOLUTE_URL_PROTOCOL_REGEX, + `Expected 'example.com' to not match ABSOLUTE_URL_REGEX` + ) + }) + }) + + describe('STATIC_ASSET_REGEX', () => { + it('should match static asset URLs', () => { + const positiveCases = [ + { + url: 'https://example.com/projects/default-project/assets/images/logo.png', + projectName: 'default-project', + assetPath: 'assets/images/logo.png' + }, + { + url: 'https://example.com/static-resources/default-project/assets/images/logo.png', + projectName: 'default-project', + assetPath: 'assets/images/logo.png' + }, + { + url: 'https://example.com/projects/default-project/assets/animations/emotes.glb', + projectName: 'default-project', + assetPath: 'assets/animations/emotes.glb' + }, + { + url: 'https://example.com/projects/default-project/assets/animations/locomotion.glb', + projectName: 'default-project', + assetPath: 'assets/animations/locomotion.glb' + } + ] + positiveCases.forEach(({ url, projectName, assetPath }) => { + const match = STATIC_ASSET_REGEX.exec(url) + assert.ok(match, `Expected '${url}' to match STATIC_ASSET_REGEX`) + assert.equal(match?.[1], projectName, `Expected project name '${projectName}' in '${url}'. Found ${match?.[1]}`) + assert.equal(match?.[2], assetPath, `Expected asset path '${assetPath}' in '${url}'. Found ${match?.[2]}`) + }) + }) + + it('should not match non-static asset URLs', () => { + const negativeCases = [ + 'https://example.com/static-resources/', + 'https://example.com/project/subdir/assets', + 'https://example.com/default-project/assets/animations/emotes.glb' + ] + negativeCases.forEach((url) => { + assert.doesNotMatch(url, STATIC_ASSET_REGEX, `Expected '${url}' to not match STATIC_ASSET_REGEX`) + }) + }) + }) + + describe('USER_ID_REGEX', () => { + it('should match valid user Ids', () => { + const positiveCases = [ + '123e4567-e89b-12d3-a456-426614174000', + 'abcdef01-2345-6789-abcd-ef0123456789', + 'ABCDEF01-2345-6789-ABCD-EF0123456789', + 'a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d', + 'ffffffff-ffff-ffff-ffff-ffffffffffff' + ] + positiveCases.forEach((userId) => { + assert.match(userId, USER_ID_REGEX, `Expected '${userId}' to match USER_ID_REGEX`) + }) + }) + + it('should not match invalid user Ids', () => { + const negativeCases = [ + '123e4567-e89b-12d3-a456-42661417400', + '123e4567e89b12d3a456426614174000', + '123e4567-e89b-12d3-a456-426614174000-', + '-123e4567-e89b-12d3-a456-426614174000', + '123e4567-e89b-12d3-a456-4266141740000', + '123e4567-e89b-12d3-g456-426614174000' + ] + negativeCases.forEach((userId) => { + assert.doesNotMatch(userId, USER_ID_REGEX, `Expected '${userId}' to not match USER_ID_REGEX`) + }) + }) + }) + + describe('EMAIL_REGEX', () => { + it('should match valid emails', () => { + const positiveCases = [ + 'simple@example.com', + 'very.common@example.com', + 'disposable.style.email.with+symbol@example.com', + 'other.email-with-hyphen@example.com', + 'fully-qualified-domain@example.com', + 'user.name+tag+sorting@example.com', + 'x@example.com', + 'example-indeed@strange-example.com', + 'mailhost!username@example.org', + 'user%example.com@example.org', + 'user-@example.org' + ] + + positiveCases.forEach((email) => { + assert.match(email, EMAIL_REGEX, `Expected '${email}' to match EMAIL_REGEX`) + }) + }) + + it('should not match invalid emails', () => { + const negativeCases = [ + 'plainaddress', + '@missingusername.com', + 'username@.com.', + '.username@yahoo.com', + 'username@yahoo.com.', + 'username@yahoo..com', + 'username@yahoo.c', + 'username@yahoo.corporate', + 'username@-example.com', + 'username@example.com-', + 'username@example..com', + 'username@111.222.333.44444', + 'username@example..com', + 'username@-example.com' + ] + negativeCases.forEach((email) => { + assert.doesNotMatch(email, EMAIL_REGEX, `Expected '${email}' to not match EMAIL_REGEX`) + }) + }) + }) + + describe('PHONE_REGEX', () => { + it('should match valid phone numbers', () => { + const positiveCases = ['1234567890', '0987654321', '0000000000', '1111111111', '9999999999'] + + positiveCases.forEach((phoneNo) => { + assert.match(phoneNo, PHONE_REGEX, `Expected '${phoneNo}' to match PHONE_REGEX`) + }) + }) + + it('should not match invalid phone numbers', () => { + const negativeCases = [ + '123456789', + '12345678901', + '12345abcde', + '123 456 7890', + '123-456-7890', + '+1234567890', + '0123456789a', + 'a123456789', + '12345 67890', + `` + ] + negativeCases.forEach((phoneNo) => { + assert.doesNotMatch(phoneNo, PHONE_REGEX, `Expected '${phoneNo}' to not match PHONE_REGEX`) + }) + }) + }) + + describe('INVITE_CODE_REGEX', () => { + it('should match valid invite codes', () => { + const positiveCases = ['1a2b3c4d', 'A1B2C3D4', 'abcdef12', '12345678', 'ABCDEF12'] + + positiveCases.forEach((inviteCode) => { + assert.match(inviteCode, INVITE_CODE_REGEX, `Expected '${inviteCode}' to match INVITE_CODE_REGEX`) + }) + }) + + it('should not match invalid invite codes', () => { + const negativeCases = [ + '1a2b3c4', + '1a2b3c4d5', + '1a2b3c4g', + '1a2b 3c4d', + '1a-2b-3c-4d', + '1234abcd!', + 'GHIJKLMN', + '123' + ] + negativeCases.forEach((inviteCode) => { + assert.doesNotMatch(inviteCode, INVITE_CODE_REGEX, `Expected '${inviteCode}' to not match INVITE_CODE_REGEX`) + }) + }) + }) + + describe('GITHUB_URL_REGEX', () => { + it('should match valid GitHub Repository URLs', () => { + const positiveCases = [ + { + url: 'git@github.com:user/repo.git', + capturedGroup: 'user/repo' + }, + { + url: 'https://github.com/user/repo.git', + capturedGroup: 'user/repo' + }, + { + url: 'git@github.com:user/repo', + capturedGroup: 'user/repo' + }, + { + url: 'https://github.com/user/repo', + capturedGroup: 'user/repo' + }, + { + url: 'git@github.com:username_123/repo-name.git', + capturedGroup: 'username_123/repo-name' + } + ] + + positiveCases.forEach(({ url, capturedGroup }) => { + const match = GITHUB_URL_REGEX.exec(url) + assert.ok(match, `Expected '${url}' to match GITHUB_URL_REGEX`) + assert.equal(match?.[1], capturedGroup, `Expected group '${capturedGroup}' in '${url}'. Found ${match?.[1]}`) + }) + }) + + it('should not match invalid GitHub Repository URLs', () => { + const negativeCases = [ + 'git@bitbucket.org:user/repo.git', // different domain + 'http://github.com/user/repo.git', // uses HTTP instead of HTTPS + 'git@github.comuser/repo.git', // missing colon + 'https://bitbucket.org/user/repo', // different domain + 'git@github.com:/user/repo.git', // extra colon + 'https://github.com:user/repo.git', // colon in HTTPS format + 'git://github.com/user/repo.git', // unsupported protocol + 'git@github.com:username/repo.', // ends with a dot instead of .git + 'https://github.com/username/repo.git/extra', // extra path segment + 'git@github.com:username' // missing repository name + ] + negativeCases.forEach((url) => { + assert.doesNotMatch(url, GITHUB_URL_REGEX, `Expected '${url}' to not match GITHUB_URL_REGEX`) + }) + }) + }) + + describe('PUBLIC_SIGNED_REGEX', () => { + it('should match valid public signed GitHub Repository URLs', () => { + const positiveCases = [ + { + url: 'https://username:password@github.com/owner/repo.git', + owner: 'owner', + repo: 'repo' + }, + { + url: 'https://user-name:pass-word@github.com/owner-name/repo-name.git', + owner: 'owner-name', + repo: 'repo-name' + }, + { + url: 'https://user_name:pass_word@github.com/owner_name/repo_name.git', + owner: 'owner_name', + repo: 'repo_name' + }, + { + url: 'https://user name:pass word@github.com/owner name/repo name.git', + owner: 'owner name', + repo: 'repo name' + }, + { + url: 'https://user123:pass123@github.com/owner123/repo123.git', + owner: 'owner123', + repo: 'repo123' + }, + { + url: 'https://user_name-123:pass_word-123@github.com/owner_name-123/repo_name-123.git', + owner: 'owner_name-123', + repo: 'repo_name-123' + } + ] + + positiveCases.forEach(({ url, owner, repo }) => { + const match = PUBLIC_SIGNED_REGEX.exec(url) + assert.ok(match, `Expected '${url}' to match PUBLIC_SIGNED_REGEX`) + assert.equal(match?.[1], owner, `Expected owner '${owner}' in '${url}'. Found ${match?.[1]}`) + assert.equal(match?.[2], repo, `Expected repo '${repo}' in '${url}'. Found ${match?.[2]}`) + }) + }) + + it('should not match invalid public signed GitHub Repository URLs', () => { + const negativeCases = [ + 'http://username:password@github.com/owner/repo.git', // uses HTTP instead of HTTPS + 'https://username:password@bitbucket.com/owner/repo.git', // different domain + 'https://username:password@github.com/owner/repo', // missing .git suffix + 'https://username:password@github.com/owner/repo/extra.git', // extra path segment + 'https://username:password@github.com//repo.git', // missing owner name + 'https://username:password@github.com/owner/.git', // missing repository name + 'https://username:password@github.com/owner', // missing repository name and .git + 'https://username@github.com/owner/repo.git', // missing password + 'https://:password@github.com/owner/repo.git', // missing username + 'https://username:password@github.com/owner/repo.git/extra' // extra path after .git + ] + negativeCases.forEach((url) => { + assert.doesNotMatch(url, PUBLIC_SIGNED_REGEX, `Expected '${url}' to not match PUBLIC_SIGNED_REGEX`) + }) + }) + }) + + describe('INSTALLATION_SIGNED_REGEX', () => { + it('should match valid Installation signed GitHub Repository URLs', () => { + const positiveCases = [ + { + url: 'https://oauth2:token123@github.com/owner/repo.git', + owner: 'owner', + repo: 'repo' + }, + { + url: 'https://oauth2:my-oauth-token@github.com/user-name/repository-name.git', + owner: 'user-name', + repo: 'repository-name' + }, + { + url: 'https://oauth2:abc_123@github.com/org_name/repo_name.git', + owner: 'org_name', + repo: 'repo_name' + } + ] + + positiveCases.forEach(({ url, owner, repo }) => { + const match = INSTALLATION_SIGNED_REGEX.exec(url) + assert.ok(match, `Expected '${url}' to match INSTALLATION_SIGNED_REGEX`) + assert.equal(match?.[1], owner, `Expected owner '${owner}' in '${url}'. Found ${match?.[1]}`) + assert.equal(match?.[2], repo, `Expected repo '${repo}' in '${url}'. Found ${match?.[2]}`) + }) + }) + + it('should not match invalid Installation signed GitHub Repository URLs', () => { + const negativeCases = [ + 'http://oauth2:token123@github.com/owner/repo.git', // uses HTTP instead of HTTPS + 'https://oauth2:token123@bitbucket.org/owner/repo.git', // different domain + 'https://oauth2:token123@github.com/owner/repo', // missing .git suffix + 'https://oauth2:token123@github.com/owner/repo/extra.git', // extra path segment + 'https://oauth2:token123@github.com//repo.git', // missing owner name + 'https://oauth2:token123@github.com/owner/.git', // missing repository name + 'https://oauth2:token123@github.com/owner', // missing repository name and .git + 'https://oauth2:token123@github.com/owner/repo/.git', // trailing slash after .git + 'https://oauth2:token123@github.com/owner/repo.git/extra' // extra path segment after .git + ] + negativeCases.forEach((url) => { + assert.doesNotMatch(url, INSTALLATION_SIGNED_REGEX, `Expected '${url}' to not match INSTALLATION_SIGNED_REGEX`) + }) + }) + }) + + describe('ASSETS_REGEX', () => { + it('should match assets URLs', () => { + const positiveCases = [ + 'https://example.com/projects/default-project/assets/images/logo.png', + 'https://example.com/projects/default-project/assets/animations/emotes.glb', + 'https://example.com/projects/default-project/assets/animations/locomotion.glb' + ] + positiveCases.forEach((url) => { + assert.match(url, ASSETS_REGEX, `Expected '${url}' to match ASSETS_REGEX`) + }) + }) + + it('should not match non-assets URLs', () => { + const negativeCases = [ + 'https://example.com/projects/default-project/scene.json', + 'https://example.com/projects/default-project/assets', + 'https://example.com/default-project/assets/animations/emotes.glb' + ] + negativeCases.forEach((url) => { + assert.doesNotMatch(url, ASSETS_REGEX, `Expected '${url}' to not match ASSETS_REGEX`) + }) + }) + }) + + describe('PROJECT_REGEX', () => { + it('should match valid project paths', () => { + const positiveCases = [ + 'projects/project123', + 'projects/project-name', + 'projects/project_name', + 'projects/project/123', + 'projects/project/abc_def' + ] + positiveCases.forEach((value) => { + assert.match(value, PROJECT_REGEX, `Expected '${value}' to match PROJECT_REGEX`) + }) + }) + + it('should not match invalid project paths', () => { + const negativeCases = [ + 'projects/', // (missing project name) + 'projects' // (missing trailing slash and project name) + ] + negativeCases.forEach((value) => { + assert.doesNotMatch(value, PROJECT_REGEX, `Expected '${value}' to not match PROJECT_REGEX`) + }) + }) + }) + + describe('PROJECT_PUBLIC_REGEX', () => { + it('should match valid project paths', () => { + const positiveCases = [ + 'projects/project123/public/', + 'projects/project-name/public/', + 'projects/project_name/public/', + 'projects/project/123/public/', + 'projects/project/abc_def/public/' + ] + positiveCases.forEach((value) => { + assert.match(value, PROJECT_PUBLIC_REGEX, `Expected '${value}' to match PROJECT_PUBLIC_REGEX`) + }) + }) + + it('should not match invalid project paths', () => { + const negativeCases = [ + 'projects/project123/public', // (missing trailing slash) + 'projects/project-name/private/', // (incorrect folder private instead of public) + 'projects/project$name/public/', // (contains invalid character $) + 'projects/project-@name/public/', // (contains invalid character @) + 'projects/' // (missing project name and /public/) + ] + negativeCases.forEach((value) => { + assert.doesNotMatch(value, PROJECT_PUBLIC_REGEX, `Expected '${value}' to not match PROJECT_PUBLIC_REGEX`) + }) + }) + }) + + describe('PROJECT_THUMBNAIL_REGEX', () => { + it('should match valid project thumbnail paths', () => { + const positiveCases = [ + 'projects/project123/thumbnails/', + 'projects/project-name/thumbnails/', + 'projects/project_name/thumbnails/', + 'projects/project/123/thumbnails/', + 'projects/project/abc_def/thumbnails/' + ] + positiveCases.forEach((value) => { + assert.match(value, PROJECT_THUMBNAIL_REGEX, `Expected '${value}' to match PROJECT_THUMBNAIL_REGEX`) + }) + }) + + it('should not match invalid project thumbnail paths', () => { + const negativeCases = [ + 'projects/project123/thumbnails', // (missing trailing slash) + 'projects/project-name/private/', // (incorrect folder private instead of public) + 'projects/project$name/thumbnails/', // (contains invalid character $) + 'projects/project-@name/thumbnails/', // (contains invalid character @) + 'projects/' // (missing project name and /thumbnail/) + ] + negativeCases.forEach((value) => { + assert.doesNotMatch(value, PROJECT_THUMBNAIL_REGEX, `Expected '${value}' to not match PROJECT_THUMBNAIL_REGEX`) + }) + }) + }) + + describe('VALID_PROJECT_NAME', () => { + it('should match valid project names', () => { + const positiveCases = [ + 'example', + 'example-example', + 'example example', + 'example123', + 'example-example123', + 'example example 123', + 'example-example_123', + 'example example-123', + 'example-example_123', + 'example example_123' + ] + positiveCases.forEach((name) => { + assert.match(name, VALID_PROJECT_NAME, `Expected '${name}' to match VALID_PROJECT_NAME`) + }) + }) + + it('should not match invalid project names', () => { + const negativeCases = [ + ' word', // (leading space) + 'word@word' // (contains non-word character @) + ] + negativeCases.forEach((name) => { + assert.doesNotMatch(name, VALID_PROJECT_NAME, `Expected '${name}' to not match VALID_PROJECT_NAME`) + }) + }) + }) +}) diff --git a/packages/editor/src/components/element/ElementList.tsx b/packages/editor/src/components/element/ElementList.tsx index c017617733..d5ca7173e5 100644 --- a/packages/editor/src/components/element/ElementList.tsx +++ b/packages/editor/src/components/element/ElementList.tsx @@ -126,11 +126,9 @@ const useComponentShelfCategories = (search: string) => { return Object.entries(getState(ComponentShelfCategoriesState)) } - const searchRegExp = new RegExp(search, 'gi') - return Object.entries(getState(ComponentShelfCategoriesState)) .map(([category, items]) => { - const filteredItems = items.filter((item) => item.name.match(searchRegExp)?.length) + const filteredItems = items.filter((item) => item.name.toLowerCase().includes(search.toLowerCase())) return [category, filteredItems] as [string, Component[]] }) .filter(([_, items]) => !!items.length) diff --git a/packages/editor/src/components/hierarchy/HierarchyPanelContainer.tsx b/packages/editor/src/components/hierarchy/HierarchyPanelContainer.tsx index 01d0aebc96..01ea277fc5 100644 --- a/packages/editor/src/components/hierarchy/HierarchyPanelContainer.tsx +++ b/packages/editor/src/components/hierarchy/HierarchyPanelContainer.tsx @@ -108,9 +108,9 @@ function HierarchyPanelContents(props: { sceneURL: string; rootEntityUUID: Entit const nodeSearch: HeirarchyTreeNodeType[] = [] if (searchHierarchy.value.length > 0) { - const condition = new RegExp(searchHierarchy.value.toLowerCase()) + const searchString = searchHierarchy.value.toLowerCase() entityHierarchy.value.forEach((node) => { - if (node.entity && condition.test(getComponent(node.entity, NameComponent)?.toLowerCase() ?? '')) + if (node.entity && (getComponent(node.entity, NameComponent)?.toLowerCase() ?? '').includes(searchString)) nodeSearch.push(node) }) } diff --git a/packages/editor/src/components/prefabs/PrefabList.tsx b/packages/editor/src/components/prefabs/PrefabList.tsx index 669555166c..167984cde9 100644 --- a/packages/editor/src/components/prefabs/PrefabList.tsx +++ b/packages/editor/src/components/prefabs/PrefabList.tsx @@ -113,9 +113,8 @@ const usePrefabShelfCategories = (search: string) => { return getState(PrefabShelfState) } - const searchRegExp = new RegExp(search, 'gi') - - return getState(PrefabShelfState).filter(({ name }) => !!name.match(searchRegExp)?.length) + const searchString = search.toLowerCase() + return getState(PrefabShelfState).filter(({ name }) => name.toLowerCase().includes(searchString)) } export function PrefabList() { diff --git a/packages/editor/src/components/properties/ModelNodeEditor.tsx b/packages/editor/src/components/properties/ModelNodeEditor.tsx index de1010ca7c..46a9447bd4 100755 --- a/packages/editor/src/components/properties/ModelNodeEditor.tsx +++ b/packages/editor/src/components/properties/ModelNodeEditor.tsx @@ -33,13 +33,13 @@ import { ProjectState } from '@etherealengine/client-core/src/common/services/Pr import config from '@etherealengine/common/src/config' import { pathJoin } from '@etherealengine/common/src/utils/miscUtils' import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions' -import { pathResolver } from '@etherealengine/engine/src/assets/functions/pathResolver' import { updateModelResource } from '@etherealengine/engine/src/assets/functions/resourceLoaderFunctions' import { recursiveHipsLookup } from '@etherealengine/engine/src/avatar/AvatarBoneMatching' import { getEntityErrors } from '@etherealengine/engine/src/scene/components/ErrorComponent' import { ModelComponent } from '@etherealengine/engine/src/scene/components/ModelComponent' import { getState, useState } from '@etherealengine/hyperflux' +import { STATIC_ASSET_REGEX } from '@etherealengine/common/src/regex' import { exportRelativeGLTF } from '../../functions/exportGLTF' import { EditorState } from '../../services/EditorServices' import BooleanInput from '../inputs/BooleanInput' @@ -69,10 +69,10 @@ export const ModelNodeEditor: EditorComponentType = (props) => { const editorState = getState(EditorState) const projectState = getState(ProjectState) const loadedProjects = useState(() => projectState.projects.map((project) => project.name)) - const srcProject = useState(() => pathResolver().exec(modelComponent.src.value)?.[1] ?? editorState.projectName!) + const srcProject = useState(() => STATIC_ASSET_REGEX.exec(modelComponent.src.value)?.[1] ?? editorState.projectName!) const getRelativePath = useCallback(() => { - const relativePath = pathResolver().exec(modelComponent.src.value)?.[2] + const relativePath = STATIC_ASSET_REGEX.exec(modelComponent.src.value)?.[2] if (!relativePath) { return 'assets/new-model' } else { diff --git a/packages/editor/src/functions/exportGLTF.ts b/packages/editor/src/functions/exportGLTF.ts index 57a001a609..c887037612 100644 --- a/packages/editor/src/functions/exportGLTF.ts +++ b/packages/editor/src/functions/exportGLTF.ts @@ -23,14 +23,14 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { STATIC_ASSET_REGEX } from '@etherealengine/common/src/regex' import { Entity } from '@etherealengine/ecs/src/Entity' import exportModelGLTF from '@etherealengine/engine/src/assets/functions/exportModelGLTF' -import { pathResolver } from '@etherealengine/engine/src/assets/functions/pathResolver' import { uploadProjectFiles } from './assetFunctions' export default async function exportGLTF(entity: Entity, path: string) { - const [, pName, fileName] = pathResolver().exec(path)! + const [, pName, fileName] = STATIC_ASSET_REGEX.exec(path)! return exportRelativeGLTF(entity, pName, fileName) } diff --git a/packages/engine/src/assets/constants/LoaderConstants.ts b/packages/engine/src/assets/constants/LoaderConstants.ts index cfd7e1406d..cb344d3766 100755 --- a/packages/engine/src/assets/constants/LoaderConstants.ts +++ b/packages/engine/src/assets/constants/LoaderConstants.ts @@ -29,8 +29,6 @@ export const DEFAULT_LOD_DISTANCES = Object.freeze({ '2': 30 }) -export const LODS_REGEXP = new RegExp(/^(.*)_LOD(\d+)$/) - export const LOADER_STATUS = { LOADED: 0, LOADING: 1, diff --git a/packages/engine/src/assets/exporters/gltf/extensions/ImageRoutingExtension.ts b/packages/engine/src/assets/exporters/gltf/extensions/ImageRoutingExtension.ts index 8af2bc9d39..f9f569dc3e 100644 --- a/packages/engine/src/assets/exporters/gltf/extensions/ImageRoutingExtension.ts +++ b/packages/engine/src/assets/exporters/gltf/extensions/ImageRoutingExtension.ts @@ -28,8 +28,8 @@ import { Material, Object3D, Object3DEventMap, Texture } from 'three' import { pathJoin, relativePathTo } from '@etherealengine/common/src/utils/miscUtils' import { EntityUUID, UUIDComponent, getOptionalComponent } from '@etherealengine/ecs' +import { STATIC_ASSET_REGEX } from '@etherealengine/common/src/regex' import { SourceComponent } from '../../../../scene/components/SourceComponent' -import { pathResolver } from '../../../functions/pathResolver' import { GLTFExporterPlugin, GLTFWriter } from '../GLTFExporter' import { ExporterExtension } from './ExporterExtension' @@ -47,7 +47,7 @@ export default class ImageRoutingExtension extends ExporterExtension implements if (!materialEntity) return const src = getOptionalComponent(materialEntity, SourceComponent) if (!src) return - const resolvedPath = pathResolver().exec(src)! + const resolvedPath = STATIC_ASSET_REGEX.exec(src)! const projectDst = this.writer.options.projectName! let projectSrc = this.writer.options.projectName! let relativeSrc = './assets/' @@ -66,7 +66,7 @@ export default class ImageRoutingExtension extends ExporterExtension implements if (texture.image instanceof ImageBitmap) continue let oldURI = texture.userData.src if (!oldURI) { - const resolved = pathResolver().exec(texture.image.src)! + const resolved = STATIC_ASSET_REGEX.exec(texture.image.src)! const oldProject = resolved[1] const relativeOldURL = resolved[2] if (oldProject !== projectSrc) { diff --git a/packages/engine/src/assets/functions/pathResolver.ts b/packages/engine/src/assets/functions/pathResolver.ts index f311a7c9cc..8e55bef4ef 100644 --- a/packages/engine/src/assets/functions/pathResolver.ts +++ b/packages/engine/src/assets/functions/pathResolver.ts @@ -23,12 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -export function pathResolver() { - //const cacheRe = new RegExp(`(https://[^\\/]+)/projects/([^/]+)/(.*$)`) - const cacheRe = new RegExp(`.*/(?:projects|static-resources)/([^/]*)/((?:assets/|).*)`) - // 1: project name -- 2: internal path - return cacheRe -} +import { STATIC_ASSET_REGEX } from '@etherealengine/common/src/regex' export function getBasePath(path: string) { const regex = new RegExp(`(.*/(?:projects|static-resources)/[^/]*)`) @@ -40,11 +35,11 @@ export function getFileName(path: string) { } export function getRelativeURI(path: string) { - return pathResolver().exec(path)?.[2] ?? '' + return STATIC_ASSET_REGEX.exec(path)?.[2] ?? '' } export function getProjectName(path: string) { - return pathResolver().exec(path)?.[1] ?? '' + return STATIC_ASSET_REGEX.exec(path)?.[1] ?? '' } export function modelResourcesPath(modelName: string) { diff --git a/packages/engine/src/scene/functions/GLTFConversion.ts b/packages/engine/src/scene/functions/GLTFConversion.ts index f46ddf7119..8ddef230fa 100644 --- a/packages/engine/src/scene/functions/GLTFConversion.ts +++ b/packages/engine/src/scene/functions/GLTFConversion.ts @@ -31,6 +31,7 @@ import { sceneRelativePathIdentifier } from '@etherealengine/common/src/utils/pa import { EntityUUID, generateEntityUUID, SerializedComponentType, UUIDComponent } from '@etherealengine/ecs' import { TransformComponent } from '@etherealengine/spatial' +import { getCacheRegex } from '@etherealengine/common/src/regex' import { EntityJsonType, SceneJsonType } from '../types/SceneTypes' export const nodeToEntityJson = (node: any): EntityJsonType => { @@ -81,7 +82,7 @@ export const gltfToSceneJson = (gltf: any): SceneJsonType => { * @param mode 'encode' or 'decode' */ export const handleScenePaths = (gltf: GLTF.IGLTF, mode: 'encode' | 'decode') => { - const cacheRe = new RegExp(`${config.client.fileServer}\/projects`) + const cacheRe = getCacheRegex(config.client.fileServer) const symbolRe = /__\$project\$__/ const frontier = [...(gltf.scenes ?? []), ...(gltf.nodes ?? [])] while (frontier.length > 0) { diff --git a/packages/engine/src/scene/functions/loaders/VariantFunctions.ts b/packages/engine/src/scene/functions/loaders/VariantFunctions.ts index 117bc147c5..1516505683 100644 --- a/packages/engine/src/scene/functions/loaders/VariantFunctions.ts +++ b/packages/engine/src/scene/functions/loaders/VariantFunctions.ts @@ -23,7 +23,7 @@ import { DistanceFromCameraComponent } from '@etherealengine/spatial/src/transfo import { TransformComponent } from '@etherealengine/spatial/src/transform/components/TransformComponent' import { isMobileXRHeadset } from '@etherealengine/spatial/src/xr/XRState' -import { pathResolver } from '../../../assets/functions/pathResolver' +import { STATIC_ASSET_REGEX } from '@etherealengine/common/src/regex' import { getGLTFAsync } from '../../../assets/functions/resourceLoaderHooks' import { InstancingComponent } from '../../components/InstancingComponent' import { ModelComponent } from '../../components/ModelComponent' @@ -66,8 +66,8 @@ export function updateModelVariant( const levelIndex = variantComponent.levels.findIndex((level) => level.metadata['device'] === targetDevice) if (levelIndex < 0) return const deviceVariant = variantComponent.levels[levelIndex] - const modelRelativePath = pathResolver().exec(modelComponent.src.value)?.[2] - const deviceRelativePath = deviceVariant ? pathResolver().exec(deviceVariant.src.value)?.[2] : '' + const modelRelativePath = STATIC_ASSET_REGEX.exec(modelComponent.src.value)?.[2] + const deviceRelativePath = deviceVariant ? STATIC_ASSET_REGEX.exec(deviceVariant.src.value)?.[2] : '' if (deviceVariant && modelRelativePath !== deviceRelativePath) { variantComponent.currentLevel.set(levelIndex) } diff --git a/packages/server-core/src/hooks/project-permission-authenticate.ts b/packages/server-core/src/hooks/project-permission-authenticate.ts index d526eb4222..1e7e8a332e 100755 --- a/packages/server-core/src/hooks/project-permission-authenticate.ts +++ b/packages/server-core/src/hooks/project-permission-authenticate.ts @@ -26,7 +26,7 @@ Ethereal Engine. All Rights Reserved. import { BadRequest, Forbidden } from '@feathersjs/errors' import { HookContext, Paginated } from '@feathersjs/feathers' -import { GITHUB_URL_REGEX } from '@etherealengine/common/src/constants/GitHubConstants' +import { GITHUB_URL_REGEX } from '@etherealengine/common/src/regex' import { projectPermissionPath, ProjectPermissionType @@ -88,9 +88,9 @@ export default (writeAccess) => { if (githubIdentityProvider.data.length === 0) throw new Forbidden('You are not authorized to access this project') const githubPathRegexExec = GITHUB_URL_REGEX.exec(projectRepoPath) if (!githubPathRegexExec) throw new BadRequest('Invalid project URL') - const split = githubPathRegexExec[2].split('/') + const split = githubPathRegexExec[1].split('/') const owner = split[0] - const repo = split[1].replace('.git', '') + const repo = split[1] const userRepoWriteStatus = await checkUserRepoWriteStatus(owner, repo, githubIdentityProvider.data[0].oauthToken) if (userRepoWriteStatus !== 200) throw new Forbidden('You are not authorized to access this project') } diff --git a/packages/server-core/src/media/storageprovider/s3.storage.ts b/packages/server-core/src/media/storageprovider/s3.storage.ts index d821d3d101..c88adce0fc 100755 --- a/packages/server-core/src/media/storageprovider/s3.storage.ts +++ b/packages/server-core/src/media/storageprovider/s3.storage.ts @@ -66,12 +66,14 @@ import S3BlobStore from 's3-blob-store' import { PassThrough, Readable } from 'stream' import { MULTIPART_CHUNK_SIZE, MULTIPART_CUTOFF_SIZE } from '@etherealengine/common/src/constants/FileSizeConstants' + import { - assetsRegex, - projectPublicRegex, - projectRegex, - projectThumbnailsRegex -} from '@etherealengine/common/src/constants/ProjectKeyConstants' + ASSETS_REGEX, + PROJECT_PUBLIC_REGEX, + PROJECT_REGEX, + PROJECT_THUMBNAIL_REGEX +} from '@etherealengine/common/src/regex' + import { FileBrowserContentType } from '@etherealengine/common/src/schemas/media/file-browser.schema' import config from '../../appconfig' @@ -112,7 +114,10 @@ const awsPath = './.aws/s3' const credentialsPath = `${awsPath}/credentials` export const getACL = (key: string) => - projectRegex.test(key) && !projectPublicRegex.test(key) && !projectThumbnailsRegex.test(key) && !assetsRegex.test(key) + PROJECT_REGEX.test(key) && + !PROJECT_PUBLIC_REGEX.test(key) && + !PROJECT_THUMBNAIL_REGEX.test(key) && + !ASSETS_REGEX.test(key) ? ObjectCannedACL.private : ObjectCannedACL.public_read diff --git a/packages/server-core/src/projects/project-permission/project-permission.hooks.ts b/packages/server-core/src/projects/project-permission/project-permission.hooks.ts index 3fc1c83f09..181f1d35bb 100644 --- a/packages/server-core/src/projects/project-permission/project-permission.hooks.ts +++ b/packages/server-core/src/projects/project-permission/project-permission.hooks.ts @@ -23,7 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { INVITE_CODE_REGEX, USER_ID_REGEX } from '@etherealengine/common/src/constants/IdConstants' +import { INVITE_CODE_REGEX, USER_ID_REGEX } from '@etherealengine/common/src/regex' import { ProjectPermissionData, ProjectPermissionPatch, diff --git a/packages/server-core/src/projects/project/github-helper.ts b/packages/server-core/src/projects/project/github-helper.ts index 4ced03244d..2dc62fde52 100755 --- a/packages/server-core/src/projects/project/github-helper.ts +++ b/packages/server-core/src/projects/project/github-helper.ts @@ -31,7 +31,8 @@ import fs from 'fs' import fetch from 'node-fetch' import path from 'path' -import { GITHUB_PER_PAGE, GITHUB_URL_REGEX } from '@etherealengine/common/src/constants/GitHubConstants' +import { GITHUB_PER_PAGE } from '@etherealengine/common/src/constants/GitHubConstants' +import { GITHUB_URL_REGEX } from '@etherealengine/common/src/regex' import { apiJobPath } from '@etherealengine/common/src/schemas/cluster/api-job.schema' import { ProjectType, projectPath } from '@etherealengine/common/src/schemas/projects/project.schema' import { @@ -206,9 +207,9 @@ export const pushProject = async ( const githubPathRegexExec = GITHUB_URL_REGEX.exec(repoPath) if (!githubPathRegexExec) throw new BadRequest('Invalid Github URL') - const split = githubPathRegexExec[2].split('/') + const split = githubPathRegexExec[1].split('/') const owner = split[0] - const repo = split[1].replace('.git', '') + const repo = split[1] if (githubIdentityProvider.data.length === 0 || !githubIdentityProvider.data[0].oauthToken) throw new Forbidden('You must log out and log back in with Github to refresh the token, and then try again.') @@ -465,14 +466,14 @@ export const getGithubOwnerRepo = (url: string) => { error: 'invalidUrl', text: 'Project URL is not a valid GitHub URL, or the GitHub repo is private' } - const split = githubPathRegexExec[2].split('/') + const split = githubPathRegexExec[1].split('/') if (!split[0] || !split[1]) return { error: 'invalidUrl', text: 'Project URL is not a valid GitHub URL, or the GitHub repo is private' } const owner = split[0] - const repo = split[1].replace('.git', '') + const repo = split[1] return { owner, repo diff --git a/packages/server-core/src/projects/project/project-helper.ts b/packages/server-core/src/projects/project/project-helper.ts index 1a670ab80d..8fdc0ca5ca 100644 --- a/packages/server-core/src/projects/project/project-helper.ts +++ b/packages/server-core/src/projects/project/project-helper.ts @@ -45,7 +45,8 @@ import { promisify } from 'util' import { v4 as uuidv4 } from 'uuid' import { AssetType } from '@etherealengine/common/src/constants/AssetType' -import { INSTALLATION_SIGNED_REGEX, PUBLIC_SIGNED_REGEX } from '@etherealengine/common/src/constants/GitHubConstants' +import { INSTALLATION_SIGNED_REGEX, PUBLIC_SIGNED_REGEX } from '@etherealengine/common/src/regex' + import { ManifestJson } from '@etherealengine/common/src/interfaces/ManifestJson' import { ProjectPackageJsonType } from '@etherealengine/common/src/interfaces/ProjectPackageJsonType' import { ResourcesJson } from '@etherealengine/common/src/interfaces/ResourcesJson' diff --git a/packages/server-core/src/projects/project/project.hooks.ts b/packages/server-core/src/projects/project/project.hooks.ts index 81138473ea..b3673f5f7b 100644 --- a/packages/server-core/src/projects/project/project.hooks.ts +++ b/packages/server-core/src/projects/project/project.hooks.ts @@ -31,8 +31,8 @@ import fs from 'fs' import { Knex } from 'knex' import path from 'path' -import { GITHUB_URL_REGEX } from '@etherealengine/common/src/constants/GitHubConstants' import { ManifestJson } from '@etherealengine/common/src/interfaces/ManifestJson' +import { GITHUB_URL_REGEX } from '@etherealengine/common/src/regex' import { apiJobPath } from '@etherealengine/common/src/schemas/cluster/api-job.schema' import { staticResourcePath, StaticResourceType } from '@etherealengine/common/src/schemas/media/static-resource.schema' import { ProjectBuildUpdateItemType } from '@etherealengine/common/src/schemas/projects/project-build.schema' @@ -165,7 +165,7 @@ const ensurePushStatus = async (context: HookContext) => { allowedProjectGithubRepos.map(async (project) => { const regexExec = GITHUB_URL_REGEX.exec(project.repositoryPath) if (!regexExec) return { repositoryPath: '', name: '' } - const split = regexExec[2].split('/') + const split = regexExec[1].split('/') project.repositoryPath = `https://github.com/${split[0]}/${split[1]}` return project }) @@ -184,7 +184,7 @@ const ensurePushStatus = async (context: HookContext) => { repositoryPaths.push(`${url}.git`) const regexExec = GITHUB_URL_REGEX.exec(url) if (regexExec) { - const split = regexExec[2].split('/') + const split = regexExec[1].split('/') repositoryPaths.push(`git@github.com:${split[0]}/${split[1]}`) repositoryPaths.push(`git@github.com:${split[0]}/${split[1]}.git`) } @@ -360,9 +360,9 @@ const linkGithubToProject = async (context: HookContext) => { if (!githubPathRegexExec) throw new BadRequest('Invalid Github URL') if (githubIdentityProvider.data.length === 0) throw new Error('Must be logged in with GitHub to link a project to a GitHub repo') - const split = githubPathRegexExec[2].split('/') + const split = githubPathRegexExec[1].split('/') const org = split[0] - const repo = split[1].replace('.git', '') + const repo = split[1] const appOrgAccess = await checkAppOrgStatus(org, githubIdentityProvider.data[0].oauthToken) if (!appOrgAccess) throw new Forbidden( diff --git a/packages/server-core/src/setting/client-setting/client-setting.hooks.ts b/packages/server-core/src/setting/client-setting/client-setting.hooks.ts index 3ec73cd019..6a4b52b960 100644 --- a/packages/server-core/src/setting/client-setting/client-setting.hooks.ts +++ b/packages/server-core/src/setting/client-setting/client-setting.hooks.ts @@ -29,6 +29,7 @@ import { iff, isProvider } from 'feathers-hooks-common' import path from 'path' import { invalidationPath } from '@etherealengine/common/src/schemas/media/invalidation.schema' + import { ClientSettingData, clientSettingDataValidator, @@ -69,11 +70,11 @@ const updateWebManifest = async (context: HookContext) => try { const webmanifestResponse = await storageProvider.getObject(webmanifestPath) const webmanifest = JSON.parse(webmanifestResponse.Body.toString('utf-8')) - context.data![0].startPath = data![0].startPath?.replace(/https:\/\//, '/') - const icon192px = /https:\/\//.test(data![0].icon192px!) + context.data![0].startPath = data![0].startPath?.replace('https://', '/') + const icon192px = data![0].icon192px!.startsWith('https://') ? data![0].icon192px : path.join('client', data![0].icon192px!) - const icon512px = /https:\/\//.test(data![0].icon512px!) + const icon512px = data![0].icon512px!.startsWith('https://') ? data![0].icon512px : path.join('client', data![0].icon512px!) webmanifest.name = data![0].title @@ -87,7 +88,7 @@ const updateWebManifest = async (context: HookContext) => const cacheDomain = storageProvider.getCacheDomain() webmanifest.icons = [ { - src: /https:\/\//.test(icon192px!) + src: icon192px!.startsWith('https://') ? icon192px : cacheDomain[cacheDomain.length - 1] === '/' && icon192px![0] === '/' ? `https://${cacheDomain}${icon192px?.slice(1)}` @@ -98,7 +99,7 @@ const updateWebManifest = async (context: HookContext) => type: getContentType(icon192px!) }, { - src: /https:\/\//.test(icon512px!) + src: icon512px!.startsWith('https://') ? icon512px : cacheDomain[cacheDomain.length - 1] === '/' && icon512px![0] === '/' ? `https://${cacheDomain}${icon512px?.slice(1)}` diff --git a/packages/spatial/src/common/functions/isAbsolutePath.ts b/packages/spatial/src/common/functions/isAbsolutePath.ts index cc6038ba8b..0d5520c2aa 100755 --- a/packages/spatial/src/common/functions/isAbsolutePath.ts +++ b/packages/spatial/src/common/functions/isAbsolutePath.ts @@ -23,7 +23,8 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -const isAbsolute = new RegExp(`(?:^[a-z][a-z0-9+.-]*:|\/\/)`) +import { ABSOLUTE_URL_PROTOCOL_REGEX } from '@etherealengine/common/src/regex' + export const isAbsolutePath = (path) => { - return isAbsolute.test(path) + return ABSOLUTE_URL_PROTOCOL_REGEX.test(path) } diff --git a/packages/ui/src/components/editor/panels/Properties/elementList/index.tsx b/packages/ui/src/components/editor/panels/Properties/elementList/index.tsx index 5b4dab143c..808129520d 100644 --- a/packages/ui/src/components/editor/panels/Properties/elementList/index.tsx +++ b/packages/ui/src/components/editor/panels/Properties/elementList/index.tsx @@ -160,11 +160,11 @@ const useComponentShelfCategories = (search: string) => { return Object.entries(getState(ComponentShelfCategoriesState)) } - const searchRegExp = new RegExp(search, 'gi') + const searchString = search.toLowerCase() return Object.entries(getState(ComponentShelfCategoriesState)) .map(([category, items]) => { - const filteredItems = items.filter((item) => item.name.match(searchRegExp)?.length) + const filteredItems = items.filter((item) => item.name.toLowerCase().includes(searchString)) return [category, filteredItems] as [string, Component[]] }) .filter(([_, items]) => !!items.length) @@ -185,11 +185,11 @@ const usePrefabShelfCategories = (search: string): [string, PrefabShelfItem[]][] return Object.entries(prefabShelves) } - const searchRegExp = new RegExp(search, 'gi') + const searchString = search.toLowerCase() return Object.entries(prefabShelves) .map(([category, items]) => { - const filteredItems = items.filter((item) => item.name.match(searchRegExp)?.length) + const filteredItems = items.filter((item) => item.name.toLowerCase().includes(searchString)) return [category, filteredItems] as [string, PrefabShelfItem[]] }) .filter(([_, items]) => !!items.length) diff --git a/packages/ui/src/components/editor/properties/model/index.tsx b/packages/ui/src/components/editor/properties/model/index.tsx index 5b57f00647..e84a291d8a 100644 --- a/packages/ui/src/components/editor/properties/model/index.tsx +++ b/packages/ui/src/components/editor/properties/model/index.tsx @@ -31,13 +31,13 @@ import { Object3D, Scene } from 'three' import { ProjectState } from '@etherealengine/client-core/src/common/services/ProjectService' import config from '@etherealengine/common/src/config' +import { STATIC_ASSET_REGEX } from '@etherealengine/common/src/regex' import { pathJoin } from '@etherealengine/common/src/utils/miscUtils' import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions' import ErrorPopUp from '@etherealengine/editor/src/components/popup/ErrorPopUp' import { EditorComponentType, commitProperty } from '@etherealengine/editor/src/components/properties/Util' import { exportRelativeGLTF } from '@etherealengine/editor/src/functions/exportGLTF' import { EditorState } from '@etherealengine/editor/src/services/EditorServices' -import { pathResolver } from '@etherealengine/engine/src/assets/functions/pathResolver' import { updateModelResource } from '@etherealengine/engine/src/assets/functions/resourceLoaderFunctions' import { recursiveHipsLookup } from '@etherealengine/engine/src/avatar/AvatarBoneMatching' import { getEntityErrors } from '@etherealengine/engine/src/scene/components/ErrorComponent' @@ -67,10 +67,10 @@ export const ModelNodeEditor: EditorComponentType = (props) => { const editorState = getState(EditorState) const projectState = getState(ProjectState) const loadedProjects = useState(() => projectState.projects.map((project) => project.name)) - const srcProject = useState(() => pathResolver().exec(modelComponent.src.value)?.[1] ?? editorState.projectName!) + const srcProject = useState(() => STATIC_ASSET_REGEX.exec(modelComponent.src.value)?.[1] ?? editorState.projectName!) const getRelativePath = useCallback(() => { - const relativePath = pathResolver().exec(modelComponent.src.value)?.[2] + const relativePath = STATIC_ASSET_REGEX.exec(modelComponent.src.value)?.[2] if (!relativePath) { return 'assets/new-model' } else { diff --git a/packages/xrui/core/serialization/generateEmbeddedCSS.ts b/packages/xrui/core/serialization/generateEmbeddedCSS.ts index 08e5186ae8..cd3f1ea0a5 100644 --- a/packages/xrui/core/serialization/generateEmbeddedCSS.ts +++ b/packages/xrui/core/serialization/generateEmbeddedCSS.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 { CSS_URL_REGEX } from '@etherealengine/common/src/regex/index' import { WebRenderer } from '../WebRenderer' import { getEmbeddedDataURL } from './getEmbeddedDataURL' @@ -39,14 +40,12 @@ export async function generateEmbeddedCSS(url: string, css: string): Promise[] // Add classes for psuedo-classes - css = css.replace(new RegExp(':hover', 'g'), WebRenderer.attributeCSS(WebRenderer.HOVER_ATTRIBUTE)) - css = css.replace(new RegExp(':active', 'g'), WebRenderer.attributeCSS(WebRenderer.ACTIVE_ATTRIBUTE)) - css = css.replace(new RegExp(':focus', 'g'), WebRenderer.attributeCSS(WebRenderer.FOCUS_ATTRIBUTE)) - css = css.replace(new RegExp(':target', 'g'), WebRenderer.attributeCSS(WebRenderer.TARGET_ATTRIBUTE)) - - // Replace all urls in the css - const regEx = RegExp(/(@import.*?["']([^"']+)["'].*?|url\((?!['"]?(?:data):)['"]?([^'"\)]*)['"]?\))/gi) - while ((found = regEx.exec(css))) { + css = css.replaceAll(':hover', WebRenderer.attributeCSS(WebRenderer.HOVER_ATTRIBUTE)) + css = css.replaceAll(':active', WebRenderer.attributeCSS(WebRenderer.ACTIVE_ATTRIBUTE)) + css = css.replaceAll(':focus', WebRenderer.attributeCSS(WebRenderer.FOCUS_ATTRIBUTE)) + css = css.replaceAll(':target', WebRenderer.attributeCSS(WebRenderer.TARGET_ATTRIBUTE)) + + while ((found = CSS_URL_REGEX.exec(css))) { const isCSSImport = !!found[2] const accept = isCSSImport ? 'type/css' : undefined const resourceURL = found[2] || found[3] From edb17c5bd894d0947bb3e43016c694d652b710d4 Mon Sep 17 00:00:00 2001 From: Josh Field Date: Thu, 27 Jun 2024 22:00:48 +1000 Subject: [PATCH 06/10] Optimize-routing (#10465) * optimize routing and client settings state cleanup * license * format * fix index not loading, optimize and fix auth routes * fixes * log --- .../admin/components/settings/tabs/client.tsx | 82 +++++++---------- .../services/Setting/ClientSettingService.ts | 74 ---------------- .../src/common/services/AppThemeState.ts | 5 -- .../src/common/services/RouterService.tsx | 10 ++- .../src/components/LoadWebappInjection.tsx | 12 ++- .../src/components/UserMediaWindow/index.tsx | 11 +-- .../src/systems/LoadingUISystem.tsx | 6 +- .../src/transports/MediaStreams.ts | 7 +- .../transports/SocketWebRTCClientFunctions.ts | 15 ++-- .../components/UserMenu/menus/SettingMenu.tsx | 16 ++-- .../src/user/services/AuthService.ts | 2 +- packages/client-core/src/world/Location.tsx | 15 +++- packages/client/src/main.tsx | 35 ++++++-- packages/client/src/pages/503.tsx | 11 +-- .../src/pages/{_app_tw.tsx => AppPage.tsx} | 43 +++------ packages/client/src/pages/_app.tsx | 88 ------------------- packages/client/src/pages/index.tsx | 25 +++--- packages/client/src/pages/room.tsx | 16 ++-- .../{route/public.tsx => pages/tailwind.tsx} | 41 ++------- packages/client/src/pages/themeContext.tsx | 18 ++-- .../route/{public_tw.tsx => CustomRouter.tsx} | 30 ++++--- packages/client/src/route/customRoutes.tsx | 63 ------------- packages/common/src/config.ts | 2 + .../src/components/panels/ViewportPanel.tsx | 11 +-- .../src/components/projects/EditorNavbar.tsx | 11 +-- .../default-project/xrengine.config.ts | 3 - .../server-core/src/route/route/route.seed.ts | 4 - .../panels/Viewport/container/index.tsx | 9 +- .../ui/src/pages/Capture/index.stories.tsx | 2 +- 29 files changed, 203 insertions(+), 464 deletions(-) delete mode 100644 packages/client-core/src/admin/services/Setting/ClientSettingService.ts rename packages/client/src/pages/{_app_tw.tsx => AppPage.tsx} (73%) delete mode 100755 packages/client/src/pages/_app.tsx rename packages/client/src/{route/public.tsx => pages/tailwind.tsx} (52%) rename packages/client/src/route/{public_tw.tsx => CustomRouter.tsx} (74%) delete mode 100644 packages/client/src/route/customRoutes.tsx diff --git a/packages/client-core/src/admin/components/settings/tabs/client.tsx b/packages/client-core/src/admin/components/settings/tabs/client.tsx index bfdfdc4ac4..9b2d6d625e 100644 --- a/packages/client-core/src/admin/components/settings/tabs/client.tsx +++ b/packages/client-core/src/admin/components/settings/tabs/client.tsx @@ -27,8 +27,8 @@ import React, { forwardRef, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { HiMinus, HiPlusSmall } from 'react-icons/hi2' -import { ClientSettingType } from '@etherealengine/common/src/schema.type.module' -import { getMutableState, NO_PROXY, none, useHookstate, useMutableState } from '@etherealengine/hyperflux' +import { clientSettingPath, ClientSettingType } from '@etherealengine/common/src/schema.type.module' +import { NO_PROXY, State, useHookstate } from '@etherealengine/hyperflux' import Accordion from '@etherealengine/ui/src/primitives/tailwind/Accordion' import Button from '@etherealengine/ui/src/primitives/tailwind/Button' import Input from '@etherealengine/ui/src/primitives/tailwind/Input' @@ -37,8 +37,8 @@ import Select from '@etherealengine/ui/src/primitives/tailwind/Select' import Text from '@etherealengine/ui/src/primitives/tailwind/Text' import Toggle from '@etherealengine/ui/src/primitives/tailwind/Toggle' -import { AuthState } from '../../../../user/services/AuthService' -import { AdminClientSettingsState, ClientSettingService } from '../../../services/Setting/ClientSettingService' +import { Engine } from '@etherealengine/ecs' +import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHooks' const ClientTab = forwardRef(({ open }: { open: boolean }, ref: React.MutableRefObject) => { const { t } = useTranslation() @@ -47,51 +47,25 @@ const ClientTab = forwardRef(({ open }: { open: boolean }, ref: React.MutableRef loading: false, errorMessage: '' }) - const user = useHookstate(getMutableState(AuthState).user) - const clientSettingState = useMutableState(AdminClientSettingsState) - const [clientSetting] = clientSettingState?.client?.get({ noproxy: true }) || [] - const id = clientSetting?.id + const clientSettingQuery = useFind(clientSettingPath) + const clientSettings = clientSettingQuery.data[0] ?? null + const id = clientSettings?.id - const settings = useHookstate(clientSetting) + const settingsState = useHookstate(null as null | ClientSettingType) useEffect(() => { - if (user?.id?.value != null && clientSettingState?.updateNeeded?.value === true) { - ClientSettingService.fetchClientSettings() + if (clientSettingQuery) { + state.set({ loading: clientSettingQuery.status === 'pending', errorMessage: clientSettingQuery.error }) } - }, [user?.id?.value, clientSettingState?.updateNeeded?.value]) + }, [clientSettingQuery]) useEffect(() => { - if (clientSetting) { - settings.merge({ - logo: clientSetting?.logo, - title: clientSetting?.title, - shortTitle: clientSetting?.shortTitle, - startPath: clientSetting?.startPath || '/', - appTitle: clientSetting?.appTitle, - appSubtitle: clientSetting?.appSubtitle, - appDescription: clientSetting?.appDescription, - appBackground: clientSetting?.appBackground, - appSocialLinks: JSON.parse(JSON.stringify(clientSetting?.appSocialLinks)) || [], - appleTouchIcon: clientSetting?.appleTouchIcon, - icon192px: clientSetting?.icon192px, - icon512px: clientSetting?.icon512px, - siteManifest: clientSetting?.siteManifest, - safariPinnedTab: clientSetting?.safariPinnedTab, - favicon: clientSetting?.favicon, - webmanifestLink: clientSetting?.webmanifestLink, - swScriptLink: clientSetting?.swScriptLink, - favicon16px: clientSetting?.favicon16px, - favicon32px: clientSetting?.favicon32px, - siteDescription: clientSetting?.siteDescription, - key8thWall: clientSetting?.key8thWall, - privacyPolicy: clientSetting?.privacyPolicy, - homepageLinkButtonEnabled: clientSetting?.homepageLinkButtonEnabled, - homepageLinkButtonRedirect: clientSetting?.homepageLinkButtonRedirect, - homepageLinkButtonText: clientSetting?.homepageLinkButtonText - }) + if (clientSettings) { + settingsState.set(clientSettings) + state.set({ loading: false, errorMessage: '' }) } - }, [clientSettingState?.updateNeeded?.value]) + }, [clientSettings]) const codecMenu = [ { @@ -130,14 +104,17 @@ const ClientTab = forwardRef(({ open }: { open: boolean }, ref: React.MutableRef const handleSubmit = (event) => { state.loading.set(true) event.preventDefault() - console.log(settings.get(NO_PROXY)) - settings.merge({ - createdAt: none, - updatedAt: none - }) - ClientSettingService.patchClientSetting(settings.value as ClientSettingType, id) + const newSettings = { + ...settingsState.get(NO_PROXY), + createdAt: undefined!, + updatedAt: undefined! + } as any as ClientSettingType + Engine.instance.api + .service(clientSettingPath) + .patch(id, newSettings) .then(() => { state.set({ loading: false, errorMessage: '' }) + clientSettingQuery.refetch() }) .catch((e) => { state.set({ loading: false, errorMessage: e.message }) @@ -145,9 +122,14 @@ const ClientTab = forwardRef(({ open }: { open: boolean }, ref: React.MutableRef } const handleCancel = () => { - settings.set(clientSetting) + settingsState.set(clientSettings) } + if (!settingsState.value) + return + + const settings = settingsState as State + return ( diff --git a/packages/client-core/src/admin/services/Setting/ClientSettingService.ts b/packages/client-core/src/admin/services/Setting/ClientSettingService.ts deleted file mode 100644 index 459a78eea3..0000000000 --- a/packages/client-core/src/admin/services/Setting/ClientSettingService.ts +++ /dev/null @@ -1,74 +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 { Paginated } from '@feathersjs/feathers' - -import config from '@etherealengine/common/src/config' -import multiLogger from '@etherealengine/common/src/logger' -import { ClientSettingPatch, clientSettingPath, ClientSettingType } from '@etherealengine/common/src/schema.type.module' -import { Engine } from '@etherealengine/ecs/src/Engine' -import { defineState, getMutableState } from '@etherealengine/hyperflux' - -import { NotificationService } from '../../../common/services/NotificationService' -import waitForClientAuthenticated from '../../../util/wait-for-client-authenticated' - -const logger = multiLogger.child({ component: 'client-core:ClientSettingService' }) - -export const AdminClientSettingsState = defineState({ - name: 'AdminClientSettingsState', - initial: () => ({ - client: [] as Array, - updateNeeded: true - }) -}) - -export const ClientSettingService = { - fetchClientSettings: async () => { - try { - await waitForClientAuthenticated() - const clientSettings = (await Engine.instance.api - .service(clientSettingPath) - .find()) as Paginated - - if (clientSettings.data[0].key8thWall) { - config.client.key8thWall = clientSettings.data[0].key8thWall - } - - getMutableState(AdminClientSettingsState).merge({ client: clientSettings.data, updateNeeded: false }) - } catch (err) { - logger.error(err) - NotificationService.dispatchNotify(err.message, { variant: 'error' }) - } - }, - patchClientSetting: async (data: ClientSettingPatch, id: string) => { - try { - await Engine.instance.api.service(clientSettingPath).patch(id, data) - getMutableState(AdminClientSettingsState).merge({ updateNeeded: true }) - } catch (err) { - logger.error(err) - NotificationService.dispatchNotify(err.message, { variant: 'error' }) - } - } -} diff --git a/packages/client-core/src/common/services/AppThemeState.ts b/packages/client-core/src/common/services/AppThemeState.ts index 27ccf2e3d4..c621b0f922 100644 --- a/packages/client-core/src/common/services/AppThemeState.ts +++ b/packages/client-core/src/common/services/AppThemeState.ts @@ -27,7 +27,6 @@ import { defaultThemeSettings, getCurrentTheme } from '@etherealengine/common/sr import { ClientThemeOptionsType } from '@etherealengine/common/src/schema.type.module' import { defineState, getMutableState, getState, useMutableState } from '@etherealengine/hyperflux' -import { AdminClientSettingsState } from '../../admin/services/Setting/ClientSettingService' import { AuthState } from '../../user/services/AuthService' /** @deprected this is the thene for mui pages, it will be replaced with ThemeService / ThemeState */ @@ -70,9 +69,5 @@ export const getAppTheme = () => { const authState = getState(AuthState) const theme = getCurrentTheme(authState.user?.userSetting?.themeModes) - const clientSettingState = getState(AdminClientSettingsState) - const themeSettings = clientSettingState?.client?.[0]?.themeSettings - if (themeSettings) return themeSettings[theme] - return defaultThemeSettings[theme] } diff --git a/packages/client-core/src/common/services/RouterService.tsx b/packages/client-core/src/common/services/RouterService.tsx index 432ec2edf6..e917a4b207 100644 --- a/packages/client-core/src/common/services/RouterService.tsx +++ b/packages/client-core/src/common/services/RouterService.tsx @@ -70,7 +70,13 @@ export const RouterState = defineState({ export type CustomRoute = { route: string component: ReturnType - props: any + componentProps?: { + [x: string]: any + } + props?: { + [x: string]: any + exact?: boolean + } } /** @@ -110,5 +116,5 @@ export const useCustomRoutes = () => { }) }, []) - return customRoutes.get(NO_PROXY) + return customRoutes.get(NO_PROXY) as CustomRoute[] } diff --git a/packages/client-core/src/components/LoadWebappInjection.tsx b/packages/client-core/src/components/LoadWebappInjection.tsx index 81e4d284e6..d11d12afe8 100644 --- a/packages/client-core/src/components/LoadWebappInjection.tsx +++ b/packages/client-core/src/components/LoadWebappInjection.tsx @@ -23,16 +23,26 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import config from '@etherealengine/common/src/config' +import { clientSettingPath } from '@etherealengine/common/src/schema.type.module' import { NO_PROXY } from '@etherealengine/hyperflux' import { loadWebappInjection } from '@etherealengine/projects/loadWebappInjection' +import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHooks' import LoadingView from '@etherealengine/ui/src/primitives/tailwind/LoadingView' import { useHookstate } from '@hookstate/core' import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' -export const LoadWebappInjection = (props) => { +export const LoadWebappInjection = (props: { children: React.ReactNode }) => { const { t } = useTranslation() + const clientSettingQuery = useFind(clientSettingPath) + const clientSettings = clientSettingQuery.data[0] ?? null + useEffect(() => { + config.client.key8thWall = clientSettings?.key8thWall + config.client.mediaSettings = clientSettings?.mediaSettings + }, [clientSettings]) + const projectComponents = useHookstate(null as null | any[]) useEffect(() => { diff --git a/packages/client-core/src/components/UserMediaWindow/index.tsx b/packages/client-core/src/components/UserMediaWindow/index.tsx index ff4a2568e5..d27437d8ef 100755 --- a/packages/client-core/src/components/UserMediaWindow/index.tsx +++ b/packages/client-core/src/components/UserMediaWindow/index.tsx @@ -42,7 +42,7 @@ import { toggleWebcamPaused } from '@etherealengine/client-core/src/transports/SocketWebRTCClientFunctions' import { AuthState } from '@etherealengine/client-core/src/user/services/AuthService' -import { UserName, userPath } from '@etherealengine/common/src/schema.type.module' +import { UserName, clientSettingPath, userPath } from '@etherealengine/common/src/schema.type.module' import { useExecute } from '@etherealengine/ecs' import { Engine } from '@etherealengine/ecs/src/Engine' import { AudioState } from '@etherealengine/engine/src/audio/AudioState' @@ -59,7 +59,7 @@ import { useMutableState } from '@etherealengine/hyperflux' import { NetworkState, VideoConstants } from '@etherealengine/network' -import { useGet } from '@etherealengine/spatial/src/common/functions/FeathersHooks' +import { useFind, useGet } from '@etherealengine/spatial/src/common/functions/FeathersHooks' import { isMobile } from '@etherealengine/spatial/src/common/functions/isMobile' import { drawPoseToCanvas } from '@etherealengine/ui/src/pages/Capture' import Icon from '@etherealengine/ui/src/primitives/mui/Icon' @@ -68,7 +68,6 @@ import Slider from '@etherealengine/ui/src/primitives/mui/Slider' import Tooltip from '@etherealengine/ui/src/primitives/mui/Tooltip' import Canvas from '@etherealengine/ui/src/primitives/tailwind/Canvas' -import { AdminClientSettingsState } from '../../admin/services/Setting/ClientSettingService' import { MediaStreamState } from '../../transports/MediaStreams' import { PeerMediaChannelState, PeerMediaStreamInterface } from '../../transports/PeerMediaChannelState' import { ConsumerExtension, SocketWebRTCClientNetwork } from '../../transports/SocketWebRTCClientFunctions' @@ -491,6 +490,9 @@ export const UserMediaWindow = ({ peerID, type }: Props): JSX.Element => { const canvasRef = useRef(null) const canvasCtxRef = useRef() + const clientSettingQuery = useFind(clientSettingPath) + const clientSetting = clientSettingQuery.data[0] + useDrawMocapLandmarks(videoElement, canvasCtxRef, canvasRef, peerID) useEffect(() => { @@ -517,8 +519,7 @@ export const UserMediaWindow = ({ peerID, type }: Props): JSX.Element => { const encodings = videoStream.rtpParameters.encodings const immersiveMedia = getMutableState(MediaSettingsState).immersiveMedia - const clientSettingState = getMutableState(AdminClientSettingsState) - const { maxResolution } = clientSettingState.client[0].mediaSettings.video.value + const { maxResolution } = clientSetting.mediaSettings.video const resolution = VideoConstants.VIDEO_CONSTRAINTS[maxResolution] || VideoConstants.VIDEO_CONSTRAINTS.hd if (isPiP || immersiveMedia.value) { let maxLayer diff --git a/packages/client-core/src/systems/LoadingUISystem.tsx b/packages/client-core/src/systems/LoadingUISystem.tsx index ddda911b09..efadfd8cc8 100755 --- a/packages/client-core/src/systems/LoadingUISystem.tsx +++ b/packages/client-core/src/systems/LoadingUISystem.tsx @@ -70,7 +70,6 @@ import { ObjectFitFunctions } from '@etherealengine/spatial/src/xrui/functions/O import type { WebLayer3D } from '@etherealengine/xrui' import { EngineState } from '@etherealengine/spatial/src/EngineState' -import { AdminClientSettingsState } from '../admin/services/Setting/ClientSettingService' import { AppThemeState, getAppTheme } from '../common/services/AppThemeState' import { useRemoveEngineCanvas } from '../hooks/useEngineCanvas' import { LocationState } from '../social/services/LocationService' @@ -339,9 +338,6 @@ const execute = () => { const Reactor = () => { const themeState = useMutableState(AppThemeState) const themeModes = useHookstate(getMutableState(AuthState).user?.userSetting?.ornull?.themeModes) - const clientSettings = useHookstate( - getMutableState(AdminClientSettingsState)?.client?.[0]?.themeSettings?.clientSettings - ) const locationSceneID = useHookstate(getMutableState(LocationState).currentLocation.location.sceneId).value const sceneEntity = GLTFAssetState.useScene(locationSceneID) const gltfDocumentState = useMutableState(GLTFDocumentState) @@ -349,7 +345,7 @@ const Reactor = () => { useEffect(() => { const theme = getAppTheme() if (theme) defaultColor.set(theme!.textColor) - }, [themeState, themeModes, clientSettings]) + }, [themeState, themeModes]) if (!sceneEntity) return null diff --git a/packages/client-core/src/transports/MediaStreams.ts b/packages/client-core/src/transports/MediaStreams.ts index 01552ded3d..c0c2c751a4 100755 --- a/packages/client-core/src/transports/MediaStreams.ts +++ b/packages/client-core/src/transports/MediaStreams.ts @@ -24,10 +24,10 @@ Ethereal Engine. All Rights Reserved. */ import multiLogger from '@etherealengine/common/src/logger' -import { defineState, getMutableState, getState } from '@etherealengine/hyperflux' +import { defineState, getMutableState } from '@etherealengine/hyperflux' import { VideoConstants } from '@etherealengine/network' -import { AdminClientSettingsState } from '../admin/services/Setting/ClientSettingService' +import config from '@etherealengine/common/src/config' import { ProducerExtension } from './SocketWebRTCClientFunctions' const logger = multiLogger.child({ component: 'client-core:MediaStreams' }) @@ -182,9 +182,8 @@ export const MediaStreamService = { */ async getVideoStream() { const state = getMutableState(MediaStreamState) - const clientSettingState = getState(AdminClientSettingsState) try { - const { maxResolution } = clientSettingState.client[0].mediaSettings.video + const { maxResolution } = config.client.mediaSettings!.video const constraints = { video: VideoConstants.VIDEO_CONSTRAINTS[maxResolution] || VideoConstants.VIDEO_CONSTRAINTS.hd } diff --git a/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts b/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts index f24a406231..55176c1348 100755 --- a/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts +++ b/packages/client-core/src/transports/SocketWebRTCClientFunctions.ts @@ -92,7 +92,6 @@ import { webcamVideoDataChannelType } from '@etherealengine/network' -import { AdminClientSettingsState } from '../admin/services/Setting/ClientSettingService' import { LocationInstanceState } from '../common/services/LocationInstanceConnectionService' import { MediaInstanceState } from '../common/services/MediaInstanceConnectionService' import { @@ -797,9 +796,8 @@ export async function configureMediaTransports(mediaTypes: string[]): Promise { - const clientSettingState = getState(AdminClientSettingsState).client[0] - const settings = - service === 'video' ? clientSettingState.mediaSettings.video : clientSettingState.mediaSettings.screenshare + const mediaSettings = config.client.mediaSettings + const settings = service === 'video' ? mediaSettings.video : mediaSettings.screenshare let codec, encodings if (settings) { switch (settings.codec) { @@ -869,7 +867,6 @@ export async function createCamVideoProducer(network: SocketWebRTCClientNetwork) export async function createCamAudioProducer(network: SocketWebRTCClientNetwork): Promise { const channelConnectionState = getState(MediaInstanceState) - const clientSettingState = getState(AdminClientSettingsState) const currentChannelInstanceConnection = channelConnectionState.instances[network.id] const channelId = currentChannelInstanceConnection.channelId const mediaStreamState = getMutableState(MediaStreamState) @@ -893,8 +890,8 @@ export async function createCamAudioProducer(network: SocketWebRTCClientNetwork) try { const codecOptions = { ...VideoConstants.CAM_AUDIO_CODEC_OPTIONS } - if (clientSettingState.client?.[0]?.mediaSettings?.audio) - codecOptions.opusMaxAverageBitrate = clientSettingState.client[0].mediaSettings.audio.maxBitrate * 1000 + const mediaSettings = config.client.mediaSettings + if (mediaSettings?.audio) codecOptions.opusMaxAverageBitrate = mediaSettings.audio.maxBitrate * 1000 // Create a new transport for audio and start producing let produceInProgress = false @@ -1183,8 +1180,8 @@ export const startScreenshare = async (network: SocketWebRTCClientNetwork) => { ) const channelConnectionState = getState(MediaInstanceState) - const clientSettingState = getState(AdminClientSettingsState).client[0] - const screenshareSettings = clientSettingState.mediaSettings.screenshare + const mediaSettings = config.client.mediaSettings + const screenshareSettings = mediaSettings.screenshare const currentChannelInstanceConnection = channelConnectionState.instances[network.id] const channelId = currentChannelInstanceConnection.channelId diff --git a/packages/client-core/src/user/components/UserMenu/menus/SettingMenu.tsx b/packages/client-core/src/user/components/UserMenu/menus/SettingMenu.tsx index 0cf3e59959..c29107515a 100755 --- a/packages/client-core/src/user/components/UserMenu/menus/SettingMenu.tsx +++ b/packages/client-core/src/user/components/UserMenu/menus/SettingMenu.tsx @@ -34,7 +34,7 @@ import Tabs from '@etherealengine/client-core/src/common/components/Tabs' import Text from '@etherealengine/client-core/src/common/components/Text' import { AuthService, AuthState } from '@etherealengine/client-core/src/user/services/AuthService' import { defaultThemeModes, defaultThemeSettings } from '@etherealengine/common/src/constants/DefaultThemeSettings' -import { UserSettingPatch } from '@etherealengine/common/src/schema.type.module' +import { UserSettingPatch, clientSettingPath } from '@etherealengine/common/src/schema.type.module' import capitalizeFirstLetter from '@etherealengine/common/src/utils/capitalizeFirstLetter' import { AudioState } from '@etherealengine/engine/src/audio/AudioState' import { @@ -43,7 +43,6 @@ import { } from '@etherealengine/engine/src/avatar/state/AvatarInputSettingsState' import { getMutableState, useHookstate, useMutableState } from '@etherealengine/hyperflux' import { isMobile } from '@etherealengine/spatial/src/common/functions/isMobile' -import { EngineState } from '@etherealengine/spatial/src/EngineState' import { InputState } from '@etherealengine/spatial/src/input/state/InputState' import { RendererState } from '@etherealengine/spatial/src/renderer/RendererState' import { XRState } from '@etherealengine/spatial/src/xr/XRState' @@ -51,11 +50,11 @@ import Box from '@etherealengine/ui/src/primitives/mui/Box' import Grid from '@etherealengine/ui/src/primitives/mui/Grid' import Icon from '@etherealengine/ui/src/primitives/mui/Icon' -import { AdminClientSettingsState } from '../../../../admin/services/Setting/ClientSettingService' -import { userHasAccess } from '../../../userHasAccess' +import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHooks' import { UserMenus } from '../../../UserUISystem' -import styles from '../index.module.scss' +import { userHasAccess } from '../../../userHasAccess' import { PopupMenuServices } from '../PopupMenuService' +import styles from '../index.module.scss' export const ShadowMapResolutionOptions: InputMenuItem[] = [ { @@ -104,15 +103,14 @@ const SettingMenu = ({ isPopover }: Props): JSX.Element => { const controlSchemes = Object.entries(AvatarAxesControlScheme) const handOptions = ['left', 'right'] const selectedTab = useHookstate('general') - const engineState = useMutableState(EngineState) - const clientSettingState = useMutableState(AdminClientSettingsState) - const [clientSetting] = clientSettingState?.client?.value || [] + const clientSettingQuery = useFind(clientSettingPath) + const clientSettings = clientSettingQuery.data[0] const userSettings = selfUser.userSetting.value const hasAdminAccess = userHasAccess('admin:admin') const hasEditorAccess = userHasAccess('editor:write') - const themeSettings = { ...defaultThemeSettings, ...clientSetting.themeSettings } + const themeSettings = { ...defaultThemeSettings, ...clientSettings?.themeSettings } const themeModes = { client: userSettings?.themeModes?.client ?? defaultThemeModes.client, studio: userSettings?.themeModes?.studio ?? defaultThemeModes.studio, diff --git a/packages/client-core/src/user/services/AuthService.ts b/packages/client-core/src/user/services/AuthService.ts index fb37abaa6c..5c06153005 100755 --- a/packages/client-core/src/user/services/AuthService.ts +++ b/packages/client-core/src/user/services/AuthService.ts @@ -183,7 +183,7 @@ export const AuthService = { // This would normally cause doLoginAuto to make a guest user, which we do not want. // Instead, just skip it on oauth callbacks, and the callback handler will log them in. // The client and auth settigns will not be needed on these routes - if (/auth\/oauth/.test(location.pathname)) return + if (location.pathname.startsWith('/auth')) return const authState = getMutableState(AuthState) try { const accessToken = !forceClientAuthReset && authState?.authUser?.accessToken?.value diff --git a/packages/client-core/src/world/Location.tsx b/packages/client-core/src/world/Location.tsx index cf06b50242..939838ecd5 100755 --- a/packages/client-core/src/world/Location.tsx +++ b/packages/client-core/src/world/Location.tsx @@ -29,13 +29,16 @@ import { useParams } from 'react-router-dom' import { LocationIcons } from '@etherealengine/client-core/src/components/LocationIcons' import { useLoadLocation, useLoadScene } from '@etherealengine/client-core/src/components/World/LoadLocationScene' import { AuthService } from '@etherealengine/client-core/src/user/services/AuthService' +import { ThemeContextProvider } from '@etherealengine/client/src/pages/themeContext' +import { useMutableState } from '@etherealengine/hyperflux' + +import '@etherealengine/client-core/src/util/GlobalStyle.css' import './LocationModule' import { t } from 'i18next' -import { useMutableState } from '@etherealengine/hyperflux' - +import { StyledEngineProvider } from '@mui/material/styles' import { LoadingCircle } from '../components/LoadingCircle' import { useLoadEngineWithScene, useNetwork } from '../components/World/EngineHooks' import { LoadingUISystemState } from '../systems/LoadingUISystem' @@ -62,8 +65,12 @@ const LocationPage = ({ online }: Props) => { return ( <> - {!ready.value && } - + + + {!ready.value && } + + + ) } diff --git a/packages/client/src/main.tsx b/packages/client/src/main.tsx index ca452d5b1a..3276b867aa 100755 --- a/packages/client/src/main.tsx +++ b/packages/client/src/main.tsx @@ -37,11 +37,16 @@ import './pages/mui.styles.scss' /** @todo Remove when MUI is removed */ // @ts-ignore ;(globalThis as any).process = { env: { ...(import.meta as any).env, APP_ENV: (import.meta as any).env.MODE } } +const $offline = lazy(() => import('@etherealengine/client/src/pages/offline/offline')) +const $location = lazy(() => import('@etherealengine/client/src/pages/location/location')) +const $auth = lazy(() => import('@etherealengine/client/src/pages/auth/authRoutes')) + const Engine = lazy(() => import('./engine')) -/** @deprecated see https://github.com/EtherealEngine/etherealengine/issues/6485 */ -const AppPage = lazy(() => import('./pages/_app')) -const TailwindPage = lazy(() => import('./pages/_app_tw')) +const AppPage = lazy(() => import('./pages/AppPage')) +/** @todo we can import tailwind here immediately once mui is gone */ +const Tailwind = lazy(() => import('./pages/tailwind')) +const Router = lazy(() => import('./route/CustomRouter')) const App = () => { return ( @@ -54,7 +59,9 @@ const App = () => { path="/location/*" element={ }> - + + <$location /> + } /> @@ -63,17 +70,31 @@ const App = () => { path="/offline/*" element={ }> - + + <$offline /> + + + } + /> + {/* This will become redundant and we can embed the AppPage directly */} + }> + <$auth /> } /> - {/* This will become redundant and we can embed the TailwindPage directly */} - + + + + } /> diff --git a/packages/client/src/pages/503.tsx b/packages/client/src/pages/503.tsx index 8e17bfb24d..bf2b863064 100755 --- a/packages/client/src/pages/503.tsx +++ b/packages/client/src/pages/503.tsx @@ -23,15 +23,16 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { AdminClientSettingsState } from '@etherealengine/client-core/src/admin/services/Setting/ClientSettingService' -import { useMutableState } from '@etherealengine/hyperflux' +import { clientSettingPath } from '@etherealengine/common/src/schema.type.module' +import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHooks' import React from 'react' import { useTranslation } from 'react-i18next' export const Custom503 = (): any => { + console.log('503') const { t } = useTranslation() - const clientSettingState = useMutableState(AdminClientSettingsState) - const [clientSetting] = clientSettingState?.client?.value || [] + const clientSettingQuery = useFind(clientSettingPath) + const clientSetting = clientSettingQuery.data[0] return ( <>

{t('503.msg')}

@@ -40,7 +41,7 @@ export const Custom503 = (): any => { height: 'auto', maxWidth: '100%' }} - src={clientSetting.appTitle} + src={clientSetting?.appTitle} /> ) diff --git a/packages/client/src/pages/_app_tw.tsx b/packages/client/src/pages/AppPage.tsx similarity index 73% rename from packages/client/src/pages/_app_tw.tsx rename to packages/client/src/pages/AppPage.tsx index feba56a8c3..2f98be6354 100755 --- a/packages/client/src/pages/_app_tw.tsx +++ b/packages/client/src/pages/AppPage.tsx @@ -28,29 +28,20 @@ Ethereal Engine. All Rights Reserved. import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { - AdminClientSettingsState, - ClientSettingService -} from '@etherealengine/client-core/src/admin/services/Setting/ClientSettingService' import { initGA, logPageView } from '@etherealengine/client-core/src/common/analytics' import { NotificationSnackbar } from '@etherealengine/client-core/src/common/services/NotificationService' import { useThemeProvider } from '@etherealengine/client-core/src/common/services/ThemeService' import Debug from '@etherealengine/client-core/src/components/Debug' +import InviteToast from '@etherealengine/client-core/src/components/InviteToast' import { LoadWebappInjection } from '@etherealengine/client-core/src/components/LoadWebappInjection' +import { useZendesk } from '@etherealengine/client-core/src/hooks/useZendesk' import { useAuthenticated } from '@etherealengine/client-core/src/user/services/AuthService' -import { useMutableState } from '@etherealengine/hyperflux' import LoadingView from '@etherealengine/ui/src/primitives/tailwind/LoadingView' -import { useZendesk } from '@etherealengine/client-core/src/hooks/useZendesk' -import PublicRouter from '../route/public_tw' - -import '../themes/base.css' -import '../themes/components.css' -import '../themes/utilities.css' - -const AppPage = () => { +const AppPage = (props: { children: React.ReactNode }) => { const { t } = useTranslation() const isLoggedIn = useAuthenticated() + useZendesk() useEffect(() => { @@ -58,34 +49,20 @@ const AppPage = () => { logPageView() }, []) - if (!/auth\/oauth/.test(location.pathname) && !isLoggedIn) { + useThemeProvider() + + if (!isLoggedIn) { return } - return ( - <> - - - - - ) -} - -const TailwindPage = () => { - const clientSettingState = useMutableState(AdminClientSettingsState) - useEffect(() => { - if (clientSettingState?.updateNeeded?.value) ClientSettingService.fetchClientSettings() - }, [clientSettingState?.updateNeeded?.value]) - - useThemeProvider() - return ( <> - + {props.children} + ) } -export default TailwindPage +export default AppPage diff --git a/packages/client/src/pages/_app.tsx b/packages/client/src/pages/_app.tsx deleted file mode 100755 index 52ad347f74..0000000000 --- a/packages/client/src/pages/_app.tsx +++ /dev/null @@ -1,88 +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 * as chapiWalletPolyfill from 'credential-handler-polyfill' -import React, { useEffect } from 'react' - -import { initGA, logPageView } from '@etherealengine/client-core/src/common/analytics' -import { NotificationSnackbar } from '@etherealengine/client-core/src/common/services/NotificationService' -import Debug from '@etherealengine/client-core/src/components/Debug' -import InviteToast from '@etherealengine/client-core/src/components/InviteToast' -import { useAuthenticated } from '@etherealengine/client-core/src/user/services/AuthService' - -import '@etherealengine/client-core/src/util/GlobalStyle.css' - -import { StyledEngineProvider, Theme } from '@mui/material/styles' -import { useTranslation } from 'react-i18next' - -import { LoadingCircle } from '@etherealengine/client-core/src/components/LoadingCircle' - -import { LoadWebappInjection } from '@etherealengine/client-core/src/components/LoadWebappInjection' -import RouterComp from '../route/public' -import { ThemeContextProvider } from './themeContext' - -declare module '@mui/styles/defaultTheme' { - // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface DefaultTheme extends Theme {} -} - -/** @deprecated see https://github.com/EtherealEngine/etherealengine/issues/6485 */ -const AppPage = ({ route }: { route: string }) => { - const isLoggedIn = useAuthenticated() - const { t } = useTranslation() - - useEffect(() => { - initGA() - logPageView() - }, []) - - if (!isLoggedIn) { - return - } - - return ( - <> - - - -
- - -
- - - -
-
- - ) -} - -export default AppPage diff --git a/packages/client/src/pages/index.tsx b/packages/client/src/pages/index.tsx index 4b0f951932..c2ed9e98f0 100755 --- a/packages/client/src/pages/index.tsx +++ b/packages/client/src/pages/index.tsx @@ -28,7 +28,6 @@ import { Trans, useTranslation } from 'react-i18next' import { Navigate } from 'react-router-dom' import styles from '@etherealengine/client-core/src/admin/old-styles/admin.module.scss' -import { AdminClientSettingsState } from '@etherealengine/client-core/src/admin/services/Setting/ClientSettingService' import MetaTags from '@etherealengine/client-core/src/common/components/MetaTags' import { NotificationService } from '@etherealengine/client-core/src/common/services/NotificationService' @@ -43,14 +42,16 @@ import { Box, Button } from '@mui/material' import ProfileMenu from '@etherealengine/client-core/src/user/components/UserMenu/menus/ProfileMenu' import { UserMenus } from '@etherealengine/client-core/src/user/UserUISystem' +import { clientSettingPath } from '@etherealengine/common/src/schema.type.module' +import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHooks' import './index.scss' const ROOT_REDIRECT = config.client.rootRedirect export const HomePage = (): any => { const { t } = useTranslation() - const clientSettingState = useMutableState(AdminClientSettingsState) - const [clientSetting] = clientSettingState?.client?.value || [] + const clientSettingQuery = useFind(clientSettingPath) + const clientSetting = clientSettingQuery.data[0] const popupMenuState = useMutableState(PopupMenuState) const popupMenu = getState(PopupMenuState) const Panel = popupMenu.openMenu ? popupMenu.menus[popupMenu.openMenu] : null @@ -92,7 +93,7 @@ export const HomePage = (): any => { height: 'auto', maxWidth: '100%' }} - src={clientSetting.appBackground} + src={clientSetting?.appBackground} alt="" crossOrigin="anonymous" /> @@ -101,26 +102,26 @@ export const HomePage = (): any => {