diff --git a/packages/client-core/src/util/config.ts b/packages/client-core/src/util/config.ts
index 5ea0da8f08..19d0233db0 100644
--- a/packages/client-core/src/util/config.ts
+++ b/packages/client-core/src/util/config.ts
@@ -1,5 +1,9 @@
export const localBuildOrDev = process.env.APP_ENV === 'development' || process.env['VITE_LOCAL_BUILD'] === 'true'
+export const clientHost = localBuildOrDev
+ ? `https://${globalThis.process.env['VITE_APP_HOST']}:${globalThis.process.env['VITE_APP_PORT']}`
+ : `https://${globalThis.process.env['VITE_APP_HOST']}`
+
export const serverHost = localBuildOrDev
? `https://${globalThis.process.env['VITE_SERVER_HOST']}:${globalThis.process.env['VITE_SERVER_PORT']}`
: `https://${globalThis.process.env['VITE_SERVER_HOST']}`
diff --git a/packages/client/src/pages/_app.tsx b/packages/client/src/pages/_app.tsx
index 33ff940c47..c7d8cf254e 100755
--- a/packages/client/src/pages/_app.tsx
+++ b/packages/client/src/pages/_app.tsx
@@ -2,7 +2,7 @@ import * as chapiWalletPolyfill from 'credential-handler-polyfill'
import { SnackbarProvider } from 'notistack'
import React, { createRef, useCallback, useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
-import { BrowserRouter } from 'react-router-dom'
+import { BrowserRouter, useLocation } from 'react-router-dom'
import {
ClientSettingService,
@@ -30,6 +30,7 @@ import {
} from '@xrengine/client-core/src/admin/services/Setting/CoilSettingService'
import { API } from '@xrengine/client-core/src/API'
import { NotificationAction, NotificationActions } from '@xrengine/client-core/src/common/services/NotificationService'
+import { clientHost, serverHost } from '@xrengine/client-core/src/util/config'
import { getCurrentTheme } from '@xrengine/common/src/constants/DefaultThemeSettings'
import { AudioEffectPlayer } from '@xrengine/engine/src/audio/systems/MediaSystem'
import { addActionReceptor, removeActionReceptor } from '@xrengine/hyperflux'
@@ -91,9 +92,7 @@ const App = (): any => {
useEffect(() => {
const html = document.querySelector('html')
if (html) {
- const currentTheme = getCurrentTheme(selfUser?.user_setting?.value?.themeModes)
- html.dataset.theme = currentTheme
-
+ html.dataset.theme = getCurrentTheme(selfUser?.user_setting?.value?.themeModes)
updateTheme()
}
}, [selfUser?.user_setting?.value])
@@ -149,6 +148,9 @@ const App = (): any => {
const currentTheme = getCurrentTheme(selfUser?.user_setting?.value?.themeModes)
+ const location = useLocation()
+ const oembedLink = `${serverHost}/oembed?url=${encodeURIComponent(`${clientHost}${location.pathname}`)}&format=json`
+
const updateTheme = () => {
if (clientThemeSettings) {
if (clientThemeSettings?.[currentTheme]) {
@@ -175,6 +177,7 @@ const App = (): any => {
{paymentPointer && }
{favicon16 && }
{favicon32 && }
+ {oembedLink && }
diff --git a/packages/server-core/src/media/oembed/oembed.hooks.ts b/packages/server-core/src/media/oembed/oembed.hooks.ts
new file mode 100755
index 0000000000..57c24befaa
--- /dev/null
+++ b/packages/server-core/src/media/oembed/oembed.hooks.ts
@@ -0,0 +1,37 @@
+import { disallow } from 'feathers-hooks-common'
+
+import logRequest from '@xrengine/server-core/src/hooks/log-request'
+
+// Don't remove this comment. It's needed to format import lines nicely.
+
+export default {
+ before: {
+ all: [logRequest()],
+ find: [],
+ get: [disallow()],
+ create: [disallow()],
+ update: [disallow()],
+ patch: [disallow()],
+ remove: [disallow()]
+ },
+
+ after: {
+ all: [],
+ find: [],
+ get: [],
+ create: [],
+ update: [],
+ patch: [],
+ remove: []
+ },
+
+ error: {
+ all: [logRequest()],
+ find: [],
+ get: [],
+ create: [],
+ update: [],
+ patch: [],
+ remove: []
+ }
+} as any
diff --git a/packages/server-core/src/media/oembed/oembed.service.ts b/packages/server-core/src/media/oembed/oembed.service.ts
new file mode 100755
index 0000000000..bb3c38c59f
--- /dev/null
+++ b/packages/server-core/src/media/oembed/oembed.service.ts
@@ -0,0 +1,68 @@
+import { BadRequest } from '@feathersjs/errors'
+import { Params } from '@feathersjs/feathers'
+import { Paginated } from '@feathersjs/feathers/lib'
+
+import { ClientSetting } from '@xrengine/common/src/interfaces/ClientSetting'
+import { Location } from '@xrengine/common/src/interfaces/Location'
+import { ServerSetting } from '@xrengine/common/src/interfaces/ServerSetting'
+
+import { Application } from '../../../declarations'
+import { getStorageProvider } from '../storageprovider/storageprovider'
+import hooks from './oembed.hooks'
+
+declare module '@xrengine/common/declarations' {
+ interface ServiceTypes {
+ oembed: any
+ }
+}
+
+export default (app: Application): void => {
+ app.use('oembed', {
+ find: async (params: Params) => {
+ const queryURL = params.query?.url
+ if (!queryURL) return new BadRequest('Must provide a valid URL for OEmbed')
+ const url = new URL(queryURL)
+ const isLocation = /^\/location\//.test(url.pathname)
+ const serverSettingsResult = (await app.service('server-setting').find()) as Paginated
+ const clientSettingsResult = (await app.service('client-setting').find()) as Paginated
+ if (serverSettingsResult.total > 0 && clientSettingsResult.total > 0) {
+ const serverSettings = serverSettingsResult.data[0]
+ const clientSettings = clientSettingsResult.data[0]
+ if (serverSettings.clientHost !== url.origin.replace(/https:\/\//, ''))
+ return new BadRequest('OEmbed request was for a different domain')
+ const returned = {
+ version: '1.0',
+ type: isLocation ? 'photo' : 'link',
+ title: `${clientSettings.title} - ${clientSettings.url.replace(/https:\/\//, '')}`,
+ provider_name: `${clientSettings.title}`,
+ provider_url: `${clientSettings.url}`,
+ thumbnail_url:
+ clientSettings.favicon32px[0] === '/'
+ ? `${clientSettings.url}${clientSettings.favicon32px}`
+ : clientSettings.favicon32px,
+ thumbnail_width: 32,
+ thumbnail_height: 32
+ } as any
+
+ if (isLocation) {
+ const locationName = url.pathname.replace(/\/location\//, '')
+ const locationResult = (await app.service('location').find({
+ query: {
+ slugifiedName: locationName
+ }
+ })) as Paginated
+ if (locationResult.total === 0) throw new BadRequest('Invalid location name')
+ const [projectName, sceneName] = locationResult.data[0].sceneId.split('/')
+ const storageProvider = getStorageProvider()
+ returned.url = `https://${storageProvider.cacheDomain}/projects/${projectName}/${sceneName}.thumbnail.jpeg`
+ returned.height = 320
+ returned.width = 512
+ }
+ return returned
+ }
+ }
+ })
+ const service = app.service('oembed')
+
+ ;(service as any).hooks(hooks)
+}
diff --git a/packages/server-core/src/media/services.ts b/packages/server-core/src/media/services.ts
index ab07f9c21b..8851a49630 100755
--- a/packages/server-core/src/media/services.ts
+++ b/packages/server-core/src/media/services.ts
@@ -1,6 +1,7 @@
import FileBrowser from './file-browser/file-browser.service'
+import OEmbed from './oembed/oembed.service'
import StaticResourceType from './static-resource-type/static-resource-type.service'
import StaticResource from './static-resource/static-resource.service'
import Upload from './upload-asset/upload-asset.service'
-export default [FileBrowser, StaticResourceType, StaticResource, Upload]
+export default [FileBrowser, OEmbed, StaticResourceType, StaticResource, Upload]