Initial commit for LBRY-First

fix killing daemon

bump lbry-first
This commit is contained in:
Thomas Zarebczan 2020-07-02 18:42:04 -04:00 committed by Sean Yesmunt
parent a8bbb95cc0
commit 4291c36c58
15 changed files with 323 additions and 51 deletions

4
.gitignore vendored
View file

@ -13,6 +13,10 @@ package-lock.json
.transifexrc .transifexrc
.idea/ .idea/
/build/daemon* /build/daemon*
/lbrytv/dist/
/lbrytv/node_modules
/static/lbry-first/
/build/lbryFirst*
/web/dist/* /web/dist/*
/web/node_modules/* /web/node_modules/*
/web/.env /web/.env

View file

@ -0,0 +1,91 @@
const path = require('path');
const fs = require('fs');
const packageJSON = require('../package.json');
const fetch = require('node-fetch');
const decompress = require('decompress');
const os = require('os');
const del = require('del');
const downloadLBRYFirst = targetPlatform =>
new Promise((resolve, reject) => {
const lbryFirstURLTemplate = packageJSON.lbrySettings.LBRYFirstUrlTemplate;
const lbryFirstVersion = packageJSON.lbrySettings.LBRYFirstVersion;
const lbryFirstDir = path.join(__dirname, '..', packageJSON.lbrySettings.LBRYFirstDir);
let lbryFirstFileName = packageJSON.lbrySettings.LBRYFirstFileName;
const currentPlatform = os.platform();
let lbryFirstPlatform = process.env.TARGET || targetPlatform || currentPlatform;
if (lbryFirstPlatform === 'linux') lbryFirstPlatform = 'Linux';
if (lbryFirstPlatform === 'mac' || lbryFirstPlatform === 'darwin') lbryFirstPlatform = 'Darwin';
if (lbryFirstPlatform === 'win32' || lbryFirstPlatform === 'windows') {
lbryFirstPlatform = 'Windows';
lbryFirstFileName += '.exe';
}
const lbryFirstFilePath = path.join(lbryFirstDir, lbryFirstFileName);
const lbryFirstVersionPath = path.join(__dirname, 'lbryFirst.ver');
const tmpZipPath = path.join(__dirname, '..', 'dist', 'lbryFirst.zip');
const lbryFirstURL = lbryFirstURLTemplate.replace(/LBRYFIRSTVER/g, lbryFirstVersion).replace(/OSNAME/g, lbryFirstPlatform);
console.log('URL:', lbryFirstURL);
// If a lbryFirst and lbryFirst.ver exists, check to see if it matches the current lbryFirst version
const hasLbryFirstDownloaded = fs.existsSync(lbryFirstFilePath);
const hasLbryFirstVersion = fs.existsSync(lbryFirstVersionPath);
let downloadedLbryFirstVersion;
if (hasLbryFirstVersion) {
downloadedLbryFirstVersion = fs.readFileSync(lbryFirstVersionPath, 'utf8');
}
if (hasLbryFirstDownloaded && hasLbryFirstVersion && downloadedLbryFirstVersion === lbryFirstVersion) {
console.log('\x1b[34minfo\x1b[0m LbryFirst already downloaded');
resolve('Done');
} else {
console.log('\x1b[34minfo\x1b[0m Downloading lbryFirst...');
fetch(lbryFirstURL, {
method: 'GET',
headers: {
'Content-Type': 'application/zip',
},
})
.then(response => response.buffer())
.then(
result =>
new Promise((newResolve, newReject) => {
const distPath = path.join(__dirname, '..', 'dist');
const hasDistFolder = fs.existsSync(distPath);
if (!hasDistFolder) {
fs.mkdirSync(distPath);
}
fs.writeFile(tmpZipPath, result, error => {
if (error) return newReject(error);
return newResolve();
});
})
)
.then(() => del(`${lbryFirstFilePath}*`))
.then()
.then(() =>
decompress(tmpZipPath, lbryFirstDir, {
filter: file => path.basename(file.path) === lbryFirstFileName,
})
)
.then(() => {
console.log('\x1b[32msuccess\x1b[0m LbryFirst downloaded!');
if (hasLbryFirstVersion) {
del(lbryFirstVersionPath);
}
fs.writeFileSync(lbryFirstVersionPath, lbryFirstVersion, 'utf8');
resolve('Done');
})
.catch(error => {
console.error(`\x1b[31merror\x1b[0m LbryFirst download failed due to: \x1b[35m${error}\x1b[0m`);
reject(error);
});
}
});
downloadLBRYFirst();

View file

@ -20,6 +20,11 @@
"to": "static/daemon/", "to": "static/daemon/",
"filter": ["**/*"] "filter": ["**/*"]
}, },
{
"from": "./static/lbry-first/",
"to": "static/lbry-first/",
"filter": ["**/*"]
},
{ {
"from": "./static/img", "from": "./static/img",
"to": "static/img", "to": "static/img",

View file

@ -0,0 +1,59 @@
import path from 'path';
import { spawn, execSync } from 'child_process';
export default class LbryFirstInstance {
static lbryFirstPath =
process.env.LBRY_FIRST_DAEMON ||
(process.env.NODE_ENV === 'production'
? path.join(process.resourcesPath, 'static/lbry-first', 'lbry-first')
: path.join(__static, 'lbry-first/lbry-first'));
static headersPath =
process.env.LBRY_FIRST_DAEMON ||
(process.env.NODE_ENV === 'production'
? path.join(process.resourcesPath, 'static/lbry-first', 'headers')
: path.join(__static, 'lbry-first/headers'));
subprocess;
handlers;
constructor() {
this.handlers = [];
}
launch() {
let flags = ['serve'];
console.log(`LbryFirst: ${LbryFirstInstance.lbryFirstPath}`);
this.subprocess = spawn(LbryFirstInstance.lbryFirstPath, flags);
this.subprocess.stdout.on('data', data => console.log(`LbryFirst: ${data}`));
this.subprocess.stderr.on('data', data => console.error(`LbryFirst: ${data}`));
this.subprocess.on('exit', () => this.fire('exit'));
this.subprocess.on('error', error => console.error(`LbryFirst error: ${error}`));
}
quit() {
if (process.platform === 'win32') {
try {
execSync(`taskkill /pid ${this.subprocess.pid} /t /f`);
} catch (error) {
console.error(error.message);
}
} else {
this.subprocess.kill();
}
}
// Follows the publish/subscribe pattern
// Subscribe method
on(event, handler, context = handler) {
this.handlers.push({ event, handler: handler.bind(context) });
}
// Publish method
fire(event, args) {
this.handlers.forEach(topic => {
if (topic.event === event) topic.handler(args);
});
}
}

View file

@ -6,7 +6,8 @@ import SemVer from 'semver';
import https from 'https'; import https from 'https';
import { app, dialog, ipcMain, session, shell } from 'electron'; import { app, dialog, ipcMain, session, shell } from 'electron';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
import { Lbry } from 'lbry-redux'; import { Lbry, LbryFirst } from 'lbry-redux';
import LbryFirstInstance from './LbryFirstInstance';
import Daemon from './Daemon'; import Daemon from './Daemon';
import isDev from 'electron-is-dev'; import isDev from 'electron-is-dev';
import createTray from './createTray'; import createTray from './createTray';
@ -41,6 +42,7 @@ let rendererWindow;
let tray; // eslint-disable-line let tray; // eslint-disable-line
let daemon; let daemon;
let lbryFirst;
const appState = {}; const appState = {};
@ -87,6 +89,41 @@ const startDaemon = async () => {
} }
}; };
const startLbryFirst = async () => {
let isLbryFirstRunning = false;
console.log(`LbryFirst: Start LBRY First App`);
try {
await LbryFirst.status();
isLbryFirstRunning = true;
} catch (e) {
console.log('status fails', e);
console.log('Starting LbryFirst');
}
if (!isLbryFirstRunning) {
console.log('start process...');
lbryFirst = new LbryFirstInstance();
lbryFirst.on('exit', () => {
if (!isDev) {
lbryFirst = null;
if (!appState.isQuitting) {
dialog.showErrorBox(
'LbryFirst has Exited',
'The lbryFirst may have encountered an unexpected error, or another lbryFirst instance is already running. \n\n'
);
}
app.quit();
}
});
try {
await lbryFirst.launch();
} catch (e) {
console.log('e', e);
}
}
};
// When we are starting the app, ensure there are no other apps already running // When we are starting the app, ensure there are no other apps already running
const gotSingleInstanceLock = app.requestSingleInstanceLock(); const gotSingleInstanceLock = app.requestSingleInstanceLock();
@ -123,6 +160,7 @@ if (!gotSingleInstanceLock) {
app.on('ready', async () => { app.on('ready', async () => {
await startDaemon(); await startDaemon();
await startLbryFirst();
startSandbox(); startSandbox();
if (isDev) { if (isDev) {
@ -205,6 +243,10 @@ app.on('will-quit', event => {
daemon.quit(); daemon.quit();
event.preventDefault(); event.preventDefault();
} }
if (lbryFirst) {
lbryFirst.quit();
event.preventDefault();
}
if (rendererWindow) { if (rendererWindow) {
rendererWindow = null; rendererWindow = null;

1
flow-typed/user.js vendored
View file

@ -26,4 +26,5 @@ declare type User = {
updated_at: string, updated_at: string,
youtube_channels: ?Array<string>, youtube_channels: ?Array<string>,
device_types: Array<DeviceType>, device_types: Array<DeviceType>,
lbry_first_approved: boolean,
}; };

View file

@ -41,7 +41,7 @@
"precommit": "lint-staged", "precommit": "lint-staged",
"preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc", "preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc",
"postinstall": "cd web && yarn && cd .. && if-env NODE_ENV=production && yarn postinstall:warning || if-env APP_ENV=web && echo 'Done installing deps' || yarn postinstall:electron", "postinstall": "cd web && yarn && cd .. && if-env NODE_ENV=production && yarn postinstall:warning || if-env APP_ENV=web && echo 'Done installing deps' || yarn postinstall:electron",
"postinstall:electron": "electron-builder install-app-deps && node ./build/downloadDaemon.js", "postinstall:electron": "electron-builder install-app-deps && node ./build/downloadDaemon.js && node ./build/downloadLBRYFirst.js",
"postinstall:warning": "echo '\n\nWARNING\n\nNot all node modules were installed because NODE_ENV is set to \"production\".\nThis should only be set after installing dependencies with \"yarn\". The app will not work.\n\n'" "postinstall:warning": "echo '\n\nWARNING\n\nNot all node modules were installed because NODE_ENV is set to \"production\".\nThis should only be set after installing dependencies with \"yarn\". The app will not work.\n\n'"
}, },
"dependencies": { "dependencies": {
@ -136,8 +136,8 @@
"imagesloaded": "^4.1.4", "imagesloaded": "^4.1.4",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git", "lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#906199d866a187015668a27363f010828c15979a", "lbry-redux": "lbryio/lbry-redux#67d8540ccc0fbedf018ae0bdfe3cae17660a5a6c",
"lbryinc": "lbryio/lbryinc#72eee35f5181940eb4a468a27ddb2a2a4e362fb0", "lbryinc": "lbryio/lbryinc#0f6fd2c33812fb1c1e393dd35b1a7560d3ba3fe2",
"lint-staged": "^7.0.2", "lint-staged": "^7.0.2",
"localforage": "^1.7.1", "localforage": "^1.7.1",
"lodash-es": "^4.17.14", "lodash-es": "^4.17.14",
@ -217,6 +217,10 @@
"lbrynetDaemonVersion": "0.77.0", "lbrynetDaemonVersion": "0.77.0",
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip", "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
"lbrynetDaemonDir": "static/daemon", "lbrynetDaemonDir": "static/daemon",
"lbrynetDaemonFileName": "lbrynet" "lbrynetDaemonFileName": "lbrynet",
"LBRYFirstVersion": "0.0.19",
"LBRYFirstUrlTemplate": "https://github.com/lbryio/lbry-first/releases/download/vLBRYFIRSTVER/lbry-first_OSNAME_amd64.zip",
"LBRYFirstDir": "static/lbry-first",
"LBRYFirstFileName": "lbry-first"
} }
} }

View file

@ -1261,34 +1261,5 @@
"Delete Your Channel": "Delete Your Channel", "Delete Your Channel": "Delete Your Channel",
"Remove from Blocked List": "Remove from Blocked List", "Remove from Blocked List": "Remove from Blocked List",
"Are you sure you want to remove this from the list?": "Are you sure you want to remove this from the list?", "Are you sure you want to remove this from the list?": "Are you sure you want to remove this from the list?",
"Cover": "Cover", "Automagically upload to your youtube channel!": "Automagically upload to your youtube channel!"
"A name is required for your url": "A name is required for your url",
"Create Channel": "Create Channel",
"CableTube Escape Artists": "CableTube Escape Artists",
"Advanced Filters from URL": "Advanced Filters from URL",
"General": "General",
"MyAwesomeChannel": "MyAwesomeChannel",
"My Awesome Channel": "My Awesome Channel",
"Increasing your deposit can help your channel be discovered more easily.": "Increasing your deposit can help your channel be discovered more easily.",
"Editing @%channel%": "Editing @%channel%",
"This field cannot be changed.": "This field cannot be changed.",
"Delete Channel": "Delete Channel",
"Edit Thumbnail Image": "Edit Thumbnail Image",
"Choose Image": "Choose Image",
"File to upload": "File to upload",
"Uploading...": "Uploading...",
"Use a URL": "Use a URL",
"Edit Cover Image": "Edit Cover Image",
"Cover Image": "Cover Image",
"(6.25:1)": "(6.25:1)",
"(1:1)": "(1:1)",
"You Followed Your First Channel!": "You Followed Your First Channel!",
"Awesome! You just followed your first first channel.": "Awesome! You just followed your first first channel.",
"After submitting, it will take a few minutes for your changes to be live for everyone.": "After submitting, it will take a few minutes for your changes to be live for everyone.",
"Anything": "Anything",
"Paid": "Paid",
"Start at": "Start at",
"Links": "Links",
"LBRY URL": "LBRY URL",
"Download Link": "Download Link"
} }

View file

@ -1,16 +1,18 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectPublishFormValues, doUpdatePublishForm } from 'lbry-redux'; import { selectPublishFormValues, doUpdatePublishForm } from 'lbry-redux';
import PublishPage from './view'; import PublishPage from './view';
import { selectUser, selectAccessToken } from '../../redux/selectors/user';
import {doFetchAccessToken} from '../../redux/actions/user';
const select = state => ({ const select = state => ({
...selectPublishFormValues(state), ...selectPublishFormValues(state),
accessToken: selectAccessToken(state),
user: selectUser(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
updatePublishForm: value => dispatch(doUpdatePublishForm(value)), updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
fetchAccessToken: () => dispatch(doFetchAccessToken()),
}); });
export default connect( export default connect(select, perform)(PublishPage);
select,
perform
)(PublishPage);

View file

@ -4,10 +4,13 @@ import classnames from 'classnames';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import { LbryFirst } from 'lbry-redux';
import LicenseType from './license-type'; import LicenseType from './license-type';
import Card from 'component/common/card'; import Card from 'component/common/card';
type Props = { type Props = {
user: ?User,
language: ?string, language: ?string,
name: ?string, name: ?string,
licenseType: ?string, licenseType: ?string,
@ -15,15 +18,73 @@ type Props = {
licenseUrl: ?string, licenseUrl: ?string,
disabled: boolean, disabled: boolean,
updatePublishForm: ({}) => void, updatePublishForm: ({}) => void,
useLBRYUploader: boolean,
needsYTAuth: boolean,
fetchAccessToken: () => void,
accessToken: string,
}; };
function PublishAdvanced(props: Props) { function PublishAdditionalOptions(props: Props) {
const { language, name, licenseType, otherLicenseDescription, licenseUrl, updatePublishForm } = props; const {
user,
language,
name,
licenseType,
otherLicenseDescription,
licenseUrl,
updatePublishForm,
useLBRYUploader,
needsYTAuth,
fetchAccessToken,
accessToken,
} = props;
const [hideSection, setHideSection] = usePersistedState('publish-advanced-options', true); const [hideSection, setHideSection] = usePersistedState('publish-advanced-options', true);
const isLBRYFirstUser = user && user.lbry_first_approved;
function toggleHideSection() { function toggleHideSection() {
setHideSection(!hideSection); setHideSection(!hideSection);
} }
function signup() {
updatePublishForm({ ytSignupPending: true });
LbryFirst.ytSignup()
.then(response => {
console.log(response);
updatePublishForm({ needsYTAuth: false, ytSignupPending: false });
})
.catch(error => {
updatePublishForm({ ytSignupPending: false });
console.log(error);
});
}
function unlink() {
LbryFirst.remove()
.then(response => {
console.log(response);
updatePublishForm({ needsYTAuth: true });
})
.catch(error => {
console.log(error);
});
}
React.useEffect(() => {
if (!accessToken) {
fetchAccessToken();
}
}, [accessToken, fetchAccessToken]);
React.useEffect(() => {
if (useLBRYUploader) {
LbryFirst.hasYTAuth(accessToken)
.then(response => {
console.log(response);
updatePublishForm({ needsYTAuth: !response.HasAuth });
})
.catch(error => {
console.log(error);
});
}
}, [accessToken, updatePublishForm, useLBRYUploader]);
return ( return (
<Card <Card
@ -31,6 +92,29 @@ function PublishAdvanced(props: Props) {
<React.Fragment> <React.Fragment>
{!hideSection && ( {!hideSection && (
<div className={classnames({ 'card--disabled': !name })}> <div className={classnames({ 'card--disabled': !name })}>
{!IS_WEB && isLBRYFirstUser && (
<FormField
type="checkbox"
name="use_lbry_uploader_checkbox"
onChange={event => updatePublishForm({ useLBRYUploader: !useLBRYUploader })}
label={
<React.Fragment>
{__('Automagically upload to your youtube channel!')}{' '}
<Button button="link" href="https://lbry.com/faq/lbry-uploader" label={__('Learn More')} />
</React.Fragment>
}
/>
)}
{!IS_WEB && useLBRYUploader && !needsYTAuth && isLBRYFirstUser && (
<div className="card__actions">
<Button button="primary" onClick={unlink} label="Unlink YouTube Channel!" disabled={false} />
</div>
)}
{!IS_WEB && useLBRYUploader && needsYTAuth && isLBRYFirstUser && (
<div className="card__actions">
<Button button="primary" onClick={signup} label="Sign in with YouTube!" disabled={false} />
</div>
)}
<FormField <FormField
label={__('Language')} label={__('Language')}
type="select" type="select"
@ -100,4 +184,4 @@ function PublishAdvanced(props: Props) {
); );
} }
export default PublishAdvanced; export default PublishAdditionalOptions;

View file

@ -54,6 +54,7 @@ type Props = {
otherLicenseDescription: ?string, otherLicenseDescription: ?string,
licenseUrl: ?string, licenseUrl: ?string,
uri: ?string, uri: ?string,
useLBRYUploader: ?boolean,
publishing: boolean, publishing: boolean,
balance: number, balance: number,
isStillEditing: boolean, isStillEditing: boolean,
@ -67,6 +68,7 @@ type Props = {
updatePublishForm: any => void, updatePublishForm: any => void,
checkAvailability: string => void, checkAvailability: string => void,
onChannelChange: string => void, onChannelChange: string => void,
ytSignupPending: boolean,
}; };
function PublishForm(props: Props) { function PublishForm(props: Props) {
@ -92,6 +94,7 @@ function PublishForm(props: Props) {
disabled = false, disabled = false,
checkAvailability, checkAvailability,
onChannelChange, onChannelChange,
ytSignupPending,
} = props; } = props;
const TAGS_LIMIT = 5; const TAGS_LIMIT = 5;
const formDisabled = (!filePath && !editingURI) || publishing; const formDisabled = (!filePath && !editingURI) || publishing;
@ -217,7 +220,9 @@ function PublishForm(props: Props) {
button="primary" button="primary"
onClick={() => publish(filePath)} onClick={() => publish(filePath)}
label={submitLabel} label={submitLabel}
disabled={formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS} disabled={
formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS || ytSignupPending
}
/> />
<Button button="link" onClick={clearPublish} label={__('Cancel')} /> <Button button="link" onClick={clearPublish} label={__('Cancel')} />
</div> </div>

View file

@ -31,7 +31,7 @@
"koa-logger": "^3.2.1", "koa-logger": "^3.2.1",
"koa-send": "^5.0.0", "koa-send": "^5.0.0",
"koa-static": "^5.0.0", "koa-static": "^5.0.0",
"lbry-redux": "lbryio/lbry-redux#f6e5b69e5aa337d50503a2f5ebb5efe4eda4ac57", "lbry-redux": "lbryio/lbry-redux#c107d9cc4c4b2cc31f182f196ab6a033aefe976e",
"lbryinc": "lbryio/lbryinc#6a52f8026cdc7cd56d200fb5c46f852e0139bbeb", "lbryinc": "lbryio/lbryinc#6a52f8026cdc7cd56d200fb5c46f852e0139bbeb",
"mysql": "^2.17.1", "mysql": "^2.17.1",
"node-fetch": "^2.6.0" "node-fetch": "^2.6.0"

View file

@ -3333,9 +3333,9 @@ latest-version@^3.0.0:
dependencies: dependencies:
package-json "^4.0.0" package-json "^4.0.0"
lbry-redux@lbryio/lbry-redux#f6e5b69e5aa337d50503a2f5ebb5efe4eda4ac57: lbry-redux@lbryio/lbry-redux#c107d9cc4c4b2cc31f182f196ab6a033aefe976e:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/f6e5b69e5aa337d50503a2f5ebb5efe4eda4ac57" resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/c107d9cc4c4b2cc31f182f196ab6a033aefe976e"
dependencies: dependencies:
proxy-polyfill "0.1.6" proxy-polyfill "0.1.6"
reselect "^3.0.0" reselect "^3.0.0"

View file

@ -46,7 +46,7 @@ let mainConfig = {
{ {
from: `${STATIC_ROOT}/`, from: `${STATIC_ROOT}/`,
to: `${DIST_ROOT}/electron/static/`, to: `${DIST_ROOT}/electron/static/`,
ignore: ['index-web.html', 'index-electron.html', 'daemon/**/*'], ignore: ['index-web.html', 'index-electron.html', 'daemon/**/*', 'lbry-first/**/*'],
}, },
{ {
from: `${STATIC_ROOT}/index-electron.html`, from: `${STATIC_ROOT}/index-electron.html`,
@ -56,6 +56,10 @@ let mainConfig = {
from: `${STATIC_ROOT}/daemon`, from: `${STATIC_ROOT}/daemon`,
to: `${DIST_ROOT}/electron/daemon`, to: `${DIST_ROOT}/electron/daemon`,
}, },
{
from: `${STATIC_ROOT}/lbry-first`,
to: `${DIST_ROOT}/electron/lbry-first`,
},
]), ]),
], ],
}; };

View file

@ -6383,17 +6383,17 @@ lazy-val@^1.0.4:
yargs "^13.2.2" yargs "^13.2.2"
zstd-codec "^0.1.1" zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#906199d866a187015668a27363f010828c15979a: lbry-redux@lbryio/lbry-redux#67d8540ccc0fbedf018ae0bdfe3cae17660a5a6c:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/906199d866a187015668a27363f010828c15979a" resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/67d8540ccc0fbedf018ae0bdfe3cae17660a5a6c"
dependencies: dependencies:
proxy-polyfill "0.1.6" proxy-polyfill "0.1.6"
reselect "^3.0.0" reselect "^3.0.0"
uuid "^3.3.2" uuid "^3.3.2"
lbryinc@lbryio/lbryinc#72eee35f5181940eb4a468a27ddb2a2a4e362fb0: lbryinc@lbryio/lbryinc#0f6fd2c33812fb1c1e393dd35b1a7560d3ba3fe2:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/72eee35f5181940eb4a468a27ddb2a2a4e362fb0" resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/0f6fd2c33812fb1c1e393dd35b1a7560d3ba3fe2"
dependencies: dependencies:
reselect "^3.0.0" reselect "^3.0.0"