use same code for handling open links on lbry.tv and desktop

This commit is contained in:
Sean Yesmunt 2019-12-02 12:30:08 -05:00
parent 8839bb08db
commit bf512e8338
21 changed files with 156 additions and 140 deletions

View file

@ -2,7 +2,7 @@ module.exports = api => {
api.cache(false);
return {
presets: ['@babel/env', '@babel/react', '@babel/flow'],
presets: [['@babel/env', { loose: true, modules: false }], '@babel/react', '@babel/flow'],
plugins: [
'import-glob',
'@babel/plugin-transform-runtime',

View file

@ -1,7 +1,7 @@
const config = {
WEBPACK_WEB_PORT: 9090,
WEBPACK_ELECTRON_PORT: 9091,
WEB_SERVER_PORT: 80,
WEB_SERVER_PORT: 1337,
DOMAIN: 'lbry.tv',
URL: 'https://lbry.tv',
SITE_TITLE: 'lbry.tv',

View file

@ -1,5 +1,7 @@
const config = require('../../config');
const PAGES = require('../../ui/constants/pages');
const { formatCustomUrl } = require('../../ui/util/url');
const { parseURI } = require('lbry-redux');
async function redirectMiddleware(ctx, next) {
const requestHost = ctx.host;
@ -16,22 +18,14 @@ async function redirectMiddleware(ctx, next) {
return;
}
if (requestHost === 'open.lbry.com') {
let redirectUrl = config.URL;
if (requestHost === 'open.lbry.com' || requestHost === 'open.lbry.io') {
const openQuery = '?src=open';
const matches = /(\/\?)([a-z]*)(.*)/.exec(url);
let redirectUrl = config.URL + formatCustomUrl(url, openQuery);
if (matches && matches.length) {
[, , page, queryString] = matches;
// This is a lbry app page. Make sure to add the leading `/$/`
if (page && Object.values(PAGES).includes(page)) {
redirectUrl += '/$/' + page;
}
redirectUrl += openQuery + queryString;
if (redirectUrl.includes('?')) {
redirectUrl = redirectUrl.replace('?', `${openQuery}&`);
} else {
redirectUrl += path + openQuery;
redirectUrl += openQuery;
}
ctx.redirect(redirectUrl);

View file

@ -4,7 +4,7 @@ import React, { forwardRef, useRef } from 'react';
import Icon from 'component/common/icon';
import classnames from 'classnames';
import { NavLink } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri';
import { formatLbryUrlForWeb } from 'util/url';
import { OutboundLink } from 'react-ga';
import * as PAGES from 'constants/pages';
import useCombinedRefs from 'effects/use-combined-refs';
@ -108,7 +108,7 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
let path = navigate;
if (path) {
if (path.startsWith('lbry://')) {
path = formatLbryUriForWeb(path);
path = formatLbryUrlForWeb(path);
} else if (!path.startsWith('/')) {
// Force a leading slash so new paths aren't appended on to the current path
path = `/${path}`;

View file

@ -5,7 +5,7 @@ import classnames from 'classnames';
import { parseURI, convertToShareLink } from 'lbry-redux';
import { withRouter } from 'react-router-dom';
import { openCopyLinkMenu } from 'util/context-menu';
import { formatLbryUriForWeb } from 'util/uri';
import { formatLbryUrlForWeb } from 'util/url';
import { isEmpty } from 'util/object';
import CardMedia from 'component/cardMedia';
import UriIndicator from 'component/uriIndicator';
@ -149,7 +149,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
if (onClick) {
onClick(e);
} else if ((isChannel || title) && !pending) {
history.push(formatLbryUriForWeb(claim && claim.canonical_url ? claim.canonical_url : uri));
history.push(formatLbryUrlForWeb(claim && claim.canonical_url ? claim.canonical_url : uri));
}
}

View file

@ -5,7 +5,7 @@ import * as React from 'react';
import { isURIValid } from 'lbry-redux';
import Button from 'component/button';
import ClaimLink from 'component/claimLink';
import { isLBRYDomain } from 'util/uri';
import { isLBRYDomain } from 'util/url';
type Props = {
href: string,

View file

@ -7,7 +7,7 @@ import ImageViewer from 'component/viewers/imageViewer';
import AppViewer from 'component/viewers/appViewer';
import Button from 'component/button';
import { withRouter } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri';
import { formatLbryUrlForWeb } from 'util/url';
// @if TARGET='web'
import { generateStreamUrl } from 'util/lbrytv';
// @endif
@ -73,7 +73,7 @@ class FileRender extends React.PureComponent<Props> {
onEndedCb() {
const { autoplay, nextUnplayed, history } = this.props;
if (autoplay && nextUnplayed) {
history.push(formatLbryUriForWeb(nextUnplayed));
history.push(formatLbryUrlForWeb(nextUnplayed));
}
}

View file

@ -5,7 +5,7 @@ import classnames from 'classnames';
import Button from 'component/button';
import { FormField } from 'component/common/form';
import { withRouter } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri';
import { formatLbryUrlForWeb } from 'util/url';
type Props = {
lastViewed: number,
@ -39,7 +39,7 @@ class NavigationHistoryItem extends React.PureComponent<Props> {
({ title } = claim.value);
}
const navigatePath = formatLbryUriForWeb(uri);
const navigatePath = formatLbryUrlForWeb(uri);
const onClick =
onSelect ||
function() {

View file

@ -4,7 +4,7 @@ import UriIndicator from 'component/uriIndicator';
import TruncatedText from 'component/common/truncated-text';
import MarkdownPreview from 'component/common/markdown-preview';
import { withRouter } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri';
import { formatLbryUrlForWeb } from 'util/url';
type Props = {
uri: string,
@ -17,7 +17,7 @@ type Props = {
class PreviewLink extends React.PureComponent<Props> {
handleClick = () => {
const { uri, history } = this.props;
history.push(formatLbryUriForWeb(uri));
history.push(formatLbryUrlForWeb(uri));
};
render() {

View file

@ -1,6 +1,6 @@
// @flow
import * as PAGES from 'constants/pages';
import React, { useEffect, Suspense } from 'react';
import React, { useEffect } from 'react';
import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
import SettingsPage from 'page/settings';
import HelpPage from 'page/help';
@ -69,37 +69,35 @@ function AppRouter(props: Props) {
}, [currentScroll, pathname]);
return (
<Suspense fallback={<span>LODIFJDSLKJFSLDKJFLDJ</span>}>
<Switch>
<Route path="/" exact component={DiscoverPage} />
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
<Switch>
<Route path="/" exact component={DiscoverPage} />
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
<PrivateRoute {...props} path={`/$/${PAGES.DOWNLOADED}`} component={FileListDownloaded} />
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISHED}`} component={FileListPublished} />
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISH}`} component={PublishPage} />
<PrivateRoute {...props} path={`/$/${PAGES.REPORT}`} component={ReportPage} />
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} component={RewardsPage} />
<PrivateRoute {...props} path={`/$/${PAGES.TRANSACTIONS}`} component={TransactionHistoryPage} />
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
<PrivateRoute {...props} path={`/$/${PAGES.ACCOUNT}`} component={AccountPage} />
<PrivateRoute {...props} path={`/$/${PAGES.FOLLOWING}`} component={FollowingPage} />
<PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} />
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
<PrivateRoute {...props} path={`/$/${PAGES.DOWNLOADED}`} component={FileListDownloaded} />
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISHED}`} component={FileListPublished} />
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISH}`} component={PublishPage} />
<PrivateRoute {...props} path={`/$/${PAGES.REPORT}`} component={ReportPage} />
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} component={RewardsPage} />
<PrivateRoute {...props} path={`/$/${PAGES.TRANSACTIONS}`} component={TransactionHistoryPage} />
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
<PrivateRoute {...props} path={`/$/${PAGES.ACCOUNT}`} component={AccountPage} />
<PrivateRoute {...props} path={`/$/${PAGES.FOLLOWING}`} component={FollowingPage} />
<PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} />
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
{/* Below need to go at the end to make sure we don't match any of our pages first */}
<Route path="/:claimName" exact component={ShowPage} />
<Route path="/:claimName/:streamName" exact component={ShowPage} />
<Route path="/*" component={FourOhFourPage} />
</Switch>
</Suspense>
{/* Below need to go at the end to make sure we don't match any of our pages first */}
<Route path="/:claimName" exact component={ShowPage} />
<Route path="/:claimName/:streamName" exact component={ShowPage} />
<Route path="/*" component={FourOhFourPage} />
</Switch>
);
}

View file

@ -11,7 +11,7 @@ import {
import analytics from 'analytics';
import Wunderbar from './view';
import { withRouter } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri';
import { formatLbryUrlForWeb } from 'util/url';
const select = state => ({
suggestions: selectSearchSuggestions(state),
@ -26,7 +26,7 @@ const perform = (dispatch, ownProps) => ({
analytics.apiLogSearch();
},
onSubmit: uri => {
const path = formatLbryUriForWeb(uri);
const path = formatLbryUrlForWeb(uri);
ownProps.history.push(path);
dispatch(doUpdateSearchQuery(''));
},

View file

@ -13,7 +13,7 @@ import * as MODALS from 'constants/modal_types';
import React, { Fragment, useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { doConditionalAuthNavigate, doDaemonReady, doAutoUpdate, doOpenModal, doHideModal } from 'redux/actions/app';
import { doDaemonReady, doAutoUpdate, doOpenModal, doHideModal } from 'redux/actions/app';
import { Lbry, doToast, isURIValid, setSearchApi, apiCall } from 'lbry-redux';
import { doSetLanguage, doUpdateIsNightAsync } from 'redux/actions/settings';
import {
@ -28,7 +28,7 @@ import pjson from 'package.json';
import app from './app';
import doLogWarningConsoleMessage from './logWarningConsoleMessage';
import { ConnectedRouter, push } from 'connected-react-router';
import { formatLbryUriForWeb } from 'util/uri';
import { formatLbryUrlForWeb, formatCustomUrl } from 'util/url';
import { PersistGate } from 'redux-persist/integration/react';
import analytics from 'analytics';
import { getAuthToken, setAuthToken } from 'util/saved-passwords';
@ -70,7 +70,6 @@ Lbry.setOverride(
const startTime = Date.now();
analytics.startupEvent();
const APPPAGEURL = 'lbry://?';
// @if TARGET='app'
const { autoUpdater } = remote.require('electron-updater');
autoUpdater.logger = remote.require('electron-log');
@ -157,25 +156,29 @@ rewards.setCallback('claimRewardSuccess', () => {
});
// @if TARGET='app'
ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
if (uri && uri.startsWith('lbry://')) {
if (uri.startsWith('lbry://?verify')) {
app.store.dispatch(doConditionalAuthNavigate(newSession));
} else if (uri.startsWith(APPPAGEURL)) {
const navpage = uri.replace(APPPAGEURL, '').toLowerCase();
app.store.dispatch(push(`/$/${navpage}`));
} else if (isURIValid(uri)) {
const formattedUri = formatLbryUriForWeb(uri);
app.store.dispatch(push(formattedUri));
analytics.openUrlEvent(formattedUri);
} else {
app.store.dispatch(
doToast({
message: __('Invalid LBRY URL requested'),
})
);
}
ipcRenderer.on('open-uri-requested', (event, url, newSession) => {
function handleError() {
app.store.dispatch(
doToast({
message: __('Invalid LBRY URL requested'),
})
);
}
const path = url.slice('lbry://'.length);
if (path.startsWith('?')) {
const redirectUrl = formatCustomUrl(path);
return app.store.dispatch(push(redirectUrl));
}
if (isURIValid(url)) {
const formattedUrl = formatLbryUrlForWeb(url);
analytics.openUrlEvent(formattedUrl);
return app.store.dispatch(push(formattedUrl));
}
// If nothing redirected before here the url must be messed up
handleError();
});
ipcRenderer.on('language-set', (event, language) => {

View file

@ -1,7 +1,7 @@
// @flow
import React, { useRef } from 'react';
import { Modal } from 'modal/modal';
import { formatPathForWeb } from 'util/uri';
import { formatFileSystemPath } from 'util/url';
type Props = {
upload: WebFile => void,
@ -15,7 +15,7 @@ function ModalAutoGenerateThumbnail(props: Props) {
const playerRef = useRef();
let videoSrc;
if (typeof filePath === 'string') {
videoSrc = formatPathForWeb(filePath);
videoSrc = formatFileSystemPath(filePath);
} else {
videoSrc = URL.createObjectURL(filePath);
}

View file

@ -1,7 +1,7 @@
// @flow
import React from 'react';
import { Modal } from 'modal/modal';
import { formatPathForWeb } from 'util/uri';
import { formatFileSystemPath } from 'util/url';
// @if TARGET='app'
import { shell } from 'electron';
// @endif
@ -37,7 +37,7 @@ function ModalOpenExternalResource(props: Props) {
if (uri) {
window.open(uri);
} else if (path) {
window.open(formatPathForWeb(path));
window.open(formatFileSystemPath(path));
}
// @endif

View file

@ -8,7 +8,7 @@ import ShareButton from 'component/shareButton';
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
import { withRouter } from 'react-router';
import Button from 'component/button';
import { formatLbryUriForWeb } from 'util/uri';
import { formatLbryUrlForWeb } from 'util/url';
import ChannelContent from 'component/channelContent';
import ChannelAbout from 'component/channelAbout';
import ChannelDiscussion from 'component/channelDiscussion';
@ -93,7 +93,7 @@ function ChannelPage(props: Props) {
const tabIndex = currentView === ABOUT_PAGE || editing ? 1 : currentView === DISCUSSION_PAGE ? 2 : 0;
function onTabChange(newTabIndex) {
let url = formatLbryUriForWeb(uri);
let url = formatLbryUrlForWeb(uri);
let search = '?';
if (newTabIndex === 0) {

View file

@ -6,7 +6,6 @@ import { ipcRenderer, remote } from 'electron';
import path from 'path';
import * as ACTIONS from 'constants/action_types';
import * as MODALS from 'constants/modal_types';
import * as PAGES from 'constants/pages';
import {
Lbry,
doBalanceSubscribe,
@ -36,7 +35,6 @@ import {
} from 'redux/selectors/app';
import { doAuthenticate, doGetSync } from 'lbryinc';
import { lbrySettings as config, version as appVersion } from 'package.json';
import { push } from 'connected-react-router';
import analytics from 'analytics';
import { doSignOutCleanup, deleteSavedPassword, getSavedPassword } from 'util/saved-passwords';
@ -404,17 +402,6 @@ export function doClickCommentButton() {
};
}
export function doConditionalAuthNavigate(newSession) {
return (dispatch, getState) => {
const state = getState();
const modal = selectModal(state);
if (newSession || (modal && modal.id !== MODALS.EMAIL_COLLECTION)) {
dispatch(push(`/$/${PAGES.AUTH}`));
}
};
}
export function doToggleSearchExpanded() {
return {
type: ACTIONS.TOGGLE_SEARCH_EXPANDED,

View file

@ -25,7 +25,7 @@ import {
} from 'lbry-redux';
import { makeSelectCostInfoForUri } from 'lbryinc';
import { makeSelectClientSetting, selectosNotificationsEnabled, selectDaemonSettings } from 'redux/selectors/settings';
import { formatLbryUriForWeb } from 'util/uri';
import { formatLbryUrlForWeb } from 'util/url';
const DOWNLOAD_POLL_INTERVAL = 250;
@ -85,7 +85,7 @@ export function doUpdateLoadStatus(uri: string, outpoint: string) {
silent: false,
});
notif.onclick = () => {
dispatch(push(formatLbryUriForWeb(uri)));
dispatch(push(formatLbryUrlForWeb(uri)));
};
}

View file

@ -6,7 +6,7 @@ import { batchActions, doError, selectMyClaims, doPublish, doCheckPendingPublish
import { selectosNotificationsEnabled } from 'redux/selectors/settings';
import { push } from 'connected-react-router';
import analytics from 'analytics';
import { formatLbryUriForWeb } from 'util/uri';
import { formatLbryUrlForWeb } from 'util/url';
import { doOpenModal } from './app';
export const doPublishDesktop = (filePath: string) => (dispatch: Dispatch, getState: () => {}) => {
@ -81,7 +81,7 @@ export const doCheckPendingPublishesApp = () => (dispatch: Dispatch, getState: G
silent: false,
});
notif.onclick = () => {
dispatch(push(formatLbryUriForWeb(claim.permanent_url)));
dispatch(push(formatLbryUrlForWeb(claim.permanent_url)));
};
}
};

View file

@ -6,6 +6,10 @@
color: var(--color-text-selection);
}
*:focus {
outline: none;
}
html {
@include font-sans;
height: 100%;

View file

@ -1,38 +0,0 @@
// @flow
const LBRY_INC_DOMAINS = ['lbry.io', 'lbry.com', 'lbry.tv', 'lbry.tech', 'lbry.fund', 'spee.ch'];
export const formatLbryUriForWeb = (uri: string) => {
return uri.replace('lbry://', '/').replace(/#/g, ':');
};
export const formatPathForWeb = (path: string) => {
if (!path) {
return;
}
let webUrl = path.replace(/\\/g, '/');
if (webUrl[0] !== '/') {
webUrl = `/${webUrl}`;
}
return encodeURI(`file://${webUrl}`).replace(/[?#]/g, encodeURIComponent);
};
export const isLBRYDomain = (uri: string) => {
if (!uri) {
return;
}
const myURL = new URL(uri);
const hostname = myURL.hostname;
for (let domain of LBRY_INC_DOMAINS) {
if (hostname.endsWith(domain)) {
return true;
}
}
return false;
};

68
ui/util/url.js Normal file
View file

@ -0,0 +1,68 @@
const LBRY_INC_DOMAINS = ['lbry.io', 'lbry.com', 'lbry.tv', 'lbry.tech', 'lbry.fund', 'spee.ch'];
exports.formatLbryUrlForWeb = uri => {
return uri.replace('lbry://', '/').replace(/#/g, ':');
};
exports.formatFileSystemPath = path => {
if (!path) {
return;
}
let webUrl = path.replace(/\\/g, '/');
if (webUrl[0] !== '/') {
webUrl = `/${webUrl}`;
}
return encodeURI(`file://${webUrl}`).replace(/[?#]/g, encodeURIComponent);
};
exports.isLBRYDomain = uri => {
if (!uri) {
return;
}
const myURL = new URL(uri);
const hostname = myURL.hostname;
for (let domain of LBRY_INC_DOMAINS) {
if (hostname.endsWith(domain)) {
return true;
}
}
return false;
};
/*
Function that handles page redirects
ex: lbry://?rewards
ex: open.lbry.com/?rewards
*/
exports.formatCustomUrl = path => {
// Determine if we need to add a leading "/$/" for app pages
const APP_PAGE_REGEX = /(\?)([a-z]*)(.*)/;
const appPageMatches = APP_PAGE_REGEX.exec(path);
if (appPageMatches && appPageMatches.length) {
// Definitely an app page (or it's formatted like one)
const [, , page, queryString] = appPageMatches;
let actualUrl = '/$/' + page;
if (queryString) {
if (queryString.startsWith('?')) {
actualUrl += queryString;
} else if (queryString.startsWith('&')) {
// Replace the leading "&" with a "?" because we must have lost the "?" from the page name
// /?rewards&a=b => /$/rewards?a=b
actualUrl += `?${queryString.slice(1)}`;
}
return actualUrl;
}
}
// Regular claim url
return path;
};