diff --git a/client/src/components/AssetPreview/index.jsx b/client/src/components/AssetPreview/index.jsx index 17a26f1f..6bfd5e68 100644 --- a/client/src/components/AssetPreview/index.jsx +++ b/client/src/components/AssetPreview/index.jsx @@ -1,9 +1,11 @@ import React from 'react'; import { Link } from 'react-router-dom'; +import createCanonicalLink from '../../../../utils/createCanonicalLink'; -const AssetPreview = ({ defaultThumbnail, claimData: { name, claimId, fileExt, contentType, thumbnail, title } }) => { - const embedUrl = `/${claimId}/${name}.${fileExt}`; - const showUrl = `/${claimId}/${name}`; +const AssetPreview = ({ defaultThumbnail, claimData }) => { + const { name, fileExt, contentType, thumbnail, title } = claimData; + const showUrl = createCanonicalLink({ asset: { ...claimData }}); + const embedUrl = `${showUrl}.${fileExt}`; return ( {(() => { diff --git a/client/src/components/AssetShareButtons/index.js b/client/src/components/AssetShareButtons/index.js index a3557f53..6ac868e5 100644 --- a/client/src/components/AssetShareButtons/index.js +++ b/client/src/components/AssetShareButtons/index.js @@ -1,48 +1,48 @@ import React from 'react'; import SocialShareLink from '@components/SocialShareLink'; -const AssetShareButtons = ({ host, name, shortId }) => { +const AssetShareButtons = ({ assetUrl, name }) => { return ( twitter facebook tumblr reddit mastodon diaspora diff --git a/client/src/containers/AssetInfo/view.jsx b/client/src/containers/AssetInfo/view.jsx index 63d16e10..af742617 100644 --- a/client/src/containers/AssetInfo/view.jsx +++ b/client/src/containers/AssetInfo/view.jsx @@ -7,18 +7,26 @@ import SpaceBetween from '@components/SpaceBetween'; import AssetShareButtons from '@components/AssetShareButtons'; import ClickToCopy from '@components/ClickToCopy'; +import siteConfig from '@config/siteConfig.json'; +const { details: { host } } = siteConfig; +import createCanonicalLink from '../../../../utils/createCanonicalLink'; + class AssetInfo extends React.Component { render () { - const { - asset: { - shortId, - claimData : { - channelName, certificateId, description, name, claimId, fileExt, contentType, thumbnail, host - }, - claimViews, - } - } = this.props; + const { asset } = this.props; + const { claimViews, claimData: { channelName, channelShortId, description, name, fileExt, contentType, thumbnail, host } } = asset; + const canonicalUrl = createCanonicalLink({ asset: { ...asset.claimData, shortId: asset.shortId }}); + const assetCanonicalUrl = `${host}${canonicalUrl}`; + + let channelCanonicalUrl; + if (channelName) { + const channel = { + name: channelName, + shortId: channelShortId, + }; + channelCanonicalUrl = `${createCanonicalLink({channel})}`; + } return (
{channelName && ( @@ -29,7 +37,7 @@ class AssetInfo extends React.Component { } content={ - {channelName} + {channelName} } /> @@ -58,9 +66,8 @@ class AssetInfo extends React.Component { } content={ } /> @@ -74,7 +81,7 @@ class AssetInfo extends React.Component { content={ } /> @@ -90,12 +97,12 @@ class AssetInfo extends React.Component { {(contentType === 'video/mp4') ? ( `} + value={``} /> ) : ( `} + value={``} /> )}
@@ -107,13 +114,13 @@ class AssetInfo extends React.Component { Direct Link Download diff --git a/client/src/containers/NavigationLinks/view.jsx b/client/src/containers/NavigationLinks/view.jsx index 01775688..f6e4e087 100644 --- a/client/src/containers/NavigationLinks/view.jsx +++ b/client/src/containers/NavigationLinks/view.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { NavLink, withRouter } from 'react-router-dom'; import NavBarChannelOptionsDropdown from '@components/NavBarChannelOptionsDropdown'; +import createCanonicalLink from '../../../../utils/createCanonicalLink'; const VIEW = 'VIEW'; const LOGOUT = 'LOGOUT'; @@ -14,6 +15,7 @@ class NavigationLinks extends React.Component { this.props.checkForLoggedInChannel(); } handleSelection (event) { + const { history, channelName: name, channelShortId: shortId } = this.props; const value = event.target.selectedOptions[0].value; switch (value) { case LOGOUT: @@ -21,7 +23,7 @@ class NavigationLinks extends React.Component { break; case VIEW: // redirect to channel page - this.props.history.push(`/${this.props.channelName}:${this.props.channelLongId}`); + history.push(createCanonicalLink({ channel: { name, shortId } })); break; default: break; diff --git a/client/src/containers/SEO/view.jsx b/client/src/containers/SEO/view.jsx index 4f64f9a3..989dacc3 100644 --- a/client/src/containers/SEO/view.jsx +++ b/client/src/containers/SEO/view.jsx @@ -2,12 +2,12 @@ import React from 'react'; import Helmet from 'react-helmet'; import PropTypes from 'prop-types'; -import siteConfig from '@config/siteConfig.json'; import createPageTitle from '../../utils/createPageTitle'; import createMetaTags from '../../utils/createMetaTags'; import oEmbed from '../../utils/oEmbed.js'; -import createCanonicalLink from '../../utils/createCanonicalLink'; +import createCanonicalLink from '../../../../utils/createCanonicalLink'; +import siteConfig from '@config/siteConfig.json'; const { details: { host } } = siteConfig; class SEO extends React.Component { @@ -21,7 +21,11 @@ class SEO extends React.Component { asset, channel, }); - const cannonicalLink = createCanonicalLink(asset, channel, pageUri); + const canonicalLink = `${host}${createCanonicalLink({ + asset: asset ? { ...asset.claimData, shortId: asset.shortId } : undefined, + channel, + page: pageUri, + })}`; // render results return ( ); diff --git a/client/src/utils/createAssetMetaTags.js b/client/src/utils/createAssetMetaTags.js index 531df9e3..980a0df4 100644 --- a/client/src/utils/createAssetMetaTags.js +++ b/client/src/utils/createAssetMetaTags.js @@ -1,6 +1,7 @@ import siteConfig from '@config/siteConfig.json'; import determineContentTypeFromExtension from './determineContentTypeFromExtension'; import createMetaTagsArray from './createMetaTagsArray'; +import createCanonicalLink from '../../../utils/createCanonicalLink'; const { details: { @@ -37,8 +38,10 @@ const determineMediaType = (contentType) => { const createAssetMetaTags = (asset) => { const { claimData } = asset; const { contentType } = claimData; - const showUrl = `${host}/${claimData.claimId}/${claimData.name}`; - const serveUrl = `${host}/${claimData.claimId}/${claimData.name}.${claimData.fileExt}`; + const canonicalLink = createCanonicalLink({ asset: { ...asset.claimData, shortId: asset.shortId }}); + const showUrl = `${host}${canonicalLink}`; + const serveUrl = `${showUrl}.${claimData.fileExt}`; + const ogTitle = claimData.title || claimData.name; const ogDescription = claimData.description || defaultDescription; const ogThumbnailContentType = determineContentTypeFromExtension(claimData.thumbnail); @@ -55,7 +58,7 @@ const createAssetMetaTags = (asset) => { 'fb:app_id' : '1371961932852223', }; if (determineMediaType(contentType) === VIDEO) { - const videoEmbedUrl = `${host}/video-embed/${claimData.name}/${claimData.claimId}`; + const videoEmbedUrl = `${host}/video-embed${canonicalLink}`; // card type tags metaTags['og:type'] = 'video.other'; metaTags['twitter:card'] = 'player'; diff --git a/client/src/utils/createCanonicalLink.js b/client/src/utils/createCanonicalLink.js deleted file mode 100644 index d73e183d..00000000 --- a/client/src/utils/createCanonicalLink.js +++ /dev/null @@ -1,39 +0,0 @@ -import siteConfig from '@config/siteConfig.json'; - -const { - details: { - host, - }, -} = siteConfig; - -const createBasicCanonicalLink = (page) => { - return `${host}/${page}`; -}; - -const createAssetCanonicalLink = (asset) => { - let channelName, certificateId, name, claimId; - if (asset.claimData) { - ({ channelName, certificateId, name, claimId } = asset.claimData); - } - if (channelName) { - return `${host}/${channelName}:${certificateId}/${name}`; - } - return `${host}/${claimId}/${name}`; -}; - -const createChannelCanonicalLink = (channel) => { - const { name, longId } = channel; - return `${host}/${name}:${longId}`; -}; - -const createCanonicalLink = (asset, channel, page) => { - if (asset) { - return createAssetCanonicalLink(asset); - } - if (channel) { - return createChannelCanonicalLink(channel); - } - return createBasicCanonicalLink(page); -}; - -export default createCanonicalLink; diff --git a/client/src/utils/createChannelMetaTags.js b/client/src/utils/createChannelMetaTags.js index fea67ec5..b5f530d6 100644 --- a/client/src/utils/createChannelMetaTags.js +++ b/client/src/utils/createChannelMetaTags.js @@ -1,6 +1,7 @@ import siteConfig from '@config/siteConfig.json'; import determineContentTypeFromExtension from './determineContentTypeFromExtension'; import createMetaTagsArray from './createMetaTagsArray'; +import createCanonicalLink from '../../../utils/createCanonicalLink'; const { details: { @@ -14,7 +15,7 @@ const { } = siteConfig; export const createChannelMetaTags = (channel) => { - const { name, longId } = channel; + const { name, shortId } = channel; const metaTags = { // page detail tags 'og:title' : `${name} on ${siteTitle}`, @@ -22,7 +23,7 @@ export const createChannelMetaTags = (channel) => { 'og:description' : `${name}, a channel on ${siteTitle}`, 'twitter:description': `${name}, a channel on ${siteTitle}`, // url - 'og:url' : `${host}/${name}:${longId}`, + 'og:url' : `${host}/${createCanonicalLink({ channel })}`, // site info 'og:site_name' : siteTitle, 'twitter:site' : twitter, diff --git a/server/controllers/pages/sendVideoEmbedPage.js b/server/controllers/pages/sendVideoEmbedPage.js index 78424d11..4e419e0b 100644 --- a/server/controllers/pages/sendVideoEmbedPage.js +++ b/server/controllers/pages/sendVideoEmbedPage.js @@ -69,12 +69,19 @@ const parseLogoConfigParam = async (rawConfig) => { } const sendVideoEmbedPage = async ({ params }, res) => { - const { + let { claimId, config, name, } = params; + // if channel then swap name and claimId for order + if (name[0] === '@' && name.includes(':')) { + const temp = name; + name = claimId; + claimId = temp; + } + const logoConfig = await parseLogoConfigParam(config); // test setting response headers diff --git a/server/render/build/handleShowRender.js b/server/render/build/handleShowRender.js index 854717dc..277b9630 100644 --- a/server/render/build/handleShowRender.js +++ b/server/render/build/handleShowRender.js @@ -34,6 +34,35 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +var createCanonicalLink = require('../../../utils/createCanonicalLink'); + +var getCanonicalUrlFromShow = function getCanonicalUrlFromShow(show) { + var requestId = show.requestList[show.request.id]; + var requestType = show.request.type; + + switch (requestType) { + case 'ASSET_DETAILS': + var asset = show.assetList[requestId.key]; + return createCanonicalLink({ + asset: _objectSpread({}, asset.claimData, { + shortId: asset.shortId + }) + }); + + case 'CHANNEL': + return createCanonicalLink({ + channel: show.channelList[requestId.key] + }); + + default: + return null; + } +}; + var returnSagaWithParams = function returnSagaWithParams(saga, params) { return ( /*#__PURE__*/ @@ -115,6 +144,14 @@ module.exports = function (req, res) { var boundSaga = returnSagaWithParams(saga, boundAction); // run the saga middleware with the saga call sagaMiddleware.run(boundSaga).done.then(function () { + // redirect if request does not use canonical url + var canonicalUrl = getCanonicalUrlFromShow(store.getState().show); + + if (canonicalUrl && canonicalUrl !== req.originalUrl) { + console.log("redirecting ".concat(req.originalUrl, " to ").concat(canonicalUrl)); + res.redirect(canonicalUrl); + } + return renderPage(store); }); } else { diff --git a/server/render/src/handleShowRender.jsx b/server/render/src/handleShowRender.jsx index 721e0f60..a0640ffa 100644 --- a/server/render/src/handleShowRender.jsx +++ b/server/render/src/handleShowRender.jsx @@ -15,12 +15,27 @@ import App from '@app'; import Sagas from '@sagas'; import Actions from '@actions'; +const createCanonicalLink = require('../../../utils/createCanonicalLink'); + +const getCanonicalUrlFromShow = show => { + const requestId = show.requestList[show.request.id]; + const requestType = show.request.type; + switch (requestType) { + case 'ASSET_DETAILS': + const asset = show.assetList[requestId.key]; + return createCanonicalLink({ asset: { ...asset.claimData, shortId: asset.shortId }}); + case 'CHANNEL': + return createCanonicalLink({ channel: show.channelList[requestId.key] }); + default: + return null; + } +}; + const returnSagaWithParams = (saga, params) => { return function * () { yield call(saga, params); }; }; - module.exports = (req, res) => { let context = {}; @@ -90,7 +105,15 @@ module.exports = (req, res) => { sagaMiddleware .run(boundSaga) .done - .then(() => renderPage(store) ); + .then(() => { + // redirect if request does not use canonical url + const canonicalUrl = getCanonicalUrlFromShow(store.getState().show); + if (canonicalUrl && canonicalUrl !== req.originalUrl) { + console.log(`redirecting ${req.originalUrl} to ${canonicalUrl}`); + res.redirect(canonicalUrl); + } + return renderPage(store) + }); } else { const store = createStore(Reducers); renderPage(store); diff --git a/server/utils/getClaimData.js b/server/utils/getClaimData.js index 68c367e2..f247a4d3 100644 --- a/server/utils/getClaimData.js +++ b/server/utils/getClaimData.js @@ -10,11 +10,17 @@ module.exports = async (data) => { channelName = await chainquery.claim.queries.getClaimChannelName(certificateId).catch(()=>{}); } + let channelShortId = null; + if (certificateId && channelName) { + channelShortId = await chainquery.claim.queries.getShortClaimIdFromLongClaimId(certificateId, channelName).catch(() => null); + } + return ({ name: data.name, title: data.title, certificateId, channelName, + channelShortId, contentType: data.content_type || data.contentType, claimId: data.claim_id || data.claimId, fileExt: data.generated_extension || data.fileExt, diff --git a/utils/createCanonicalLink.js b/utils/createCanonicalLink.js new file mode 100644 index 00000000..210bcd86 --- /dev/null +++ b/utils/createCanonicalLink.js @@ -0,0 +1,25 @@ +const createBasicCanonicalLink = (page) => { + return `/${page}`; +}; + +const createAssetCanonicalLink = (asset) => { + const { channelName, channelShortId, name, claimId, shortId } = asset; + return channelName ? `/${channelName}:${channelShortId}/${name}` : `/${shortId || claimId}/${name}`; +}; + +const createChannelCanonicalLink = (channel) => { + const { name, shortId } = channel; + return `/${name}:${shortId}`; +}; + +const createCanonicalLink = ({asset, channel, page}) => { + if (asset) { + return createAssetCanonicalLink(asset); + } + if (channel) { + return createChannelCanonicalLink(channel); + } + return createBasicCanonicalLink(page); +}; + +module.exports = createCanonicalLink;