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]