From 30023422b8c83ac257e5fb2fa3de138c47586db5 Mon Sep 17 00:00:00 2001 From: Merge Date: Sun, 17 Oct 2021 16:36:14 +0800 Subject: [PATCH] Desktop cherry-pick: "7240 Integrate lbry redux and lbryinc" --- .flowconfig | 9 +- electron/Daemon.js | 2 +- electron/index.js | 2 +- electron/startSandbox.js | 2 +- extras/lbry-first/lbry-first.js | 184 +++ extras/lbryinc/constants/action_types.js | 97 ++ extras/lbryinc/constants/claim.js | 5 + extras/lbryinc/constants/errors.js | 4 + extras/lbryinc/constants/youtube.js | 11 + extras/lbryinc/index.js | 83 ++ extras/lbryinc/lbryio.js | 238 ++++ extras/lbryinc/redux/actions/auth.js | 38 + extras/lbryinc/redux/actions/blacklist.js | 52 + extras/lbryinc/redux/actions/cost_info.js | 35 + extras/lbryinc/redux/actions/filtered.js | 47 + extras/lbryinc/redux/actions/homepage.js | 79 ++ extras/lbryinc/redux/actions/stats.js | 32 + extras/lbryinc/redux/actions/sync.js | 289 +++++ extras/lbryinc/redux/actions/web.js | 12 + extras/lbryinc/redux/reducers/auth.js | 29 + extras/lbryinc/redux/reducers/blacklist.js | 37 + extras/lbryinc/redux/reducers/cost_info.js | 38 + extras/lbryinc/redux/reducers/filtered.js | 34 + extras/lbryinc/redux/reducers/homepage.js | 48 + extras/lbryinc/redux/reducers/stats.js | 55 + extras/lbryinc/redux/reducers/sync.js | 89 ++ extras/lbryinc/redux/reducers/web.js | 62 + extras/lbryinc/redux/selectors/auth.js | 7 + extras/lbryinc/redux/selectors/blacklist.js | 20 + extras/lbryinc/redux/selectors/cost_info.js | 13 + extras/lbryinc/redux/selectors/filtered.js | 20 + extras/lbryinc/redux/selectors/homepage.js | 17 + extras/lbryinc/redux/selectors/stats.js | 20 + extras/lbryinc/redux/selectors/sync.js | 40 + extras/lbryinc/redux/selectors/web.js | 10 + extras/lbryinc/util/redux-utils-delete-me.js | 17 + extras/lbryinc/util/swap-json.js | 10 + extras/lbryinc/util/transifex-upload.js | 78 ++ extras/recsys/index.js | 3 + {ui => extras/recsys}/recsys.js | 6 +- flow-typed/Blocklist.js | 10 + flow-typed/Claim.js | 214 ++++ flow-typed/CoinSwap.js | 29 + flow-typed/Collections.js | 34 + flow-typed/Comment.js | 2 +- flow-typed/Lbry.js | 369 ++++++ flow-typed/LbryFirst.js | 99 ++ flow-typed/Notification.js | 93 ++ flow-typed/Publish.js | 27 + flow-typed/Redux.js | 6 + flow-typed/Reflector.js | 5 + flow-typed/Tags.js | 21 + flow-typed/Transaction.js | 28 + flow-typed/Txo.js | 27 + flow-typed/i18n.js | 2 + flow-typed/lbryURI.js | 21 + flow-typed/publish.js | 2 +- package.json | 4 +- ui/component/abandonedChannelPreview/view.jsx | 2 +- ui/component/app/index.js | 10 +- ui/component/app/view.jsx | 18 +- ui/component/autoplayCountdown/index.js | 2 +- ui/component/channelAbout/index.js | 2 +- ui/component/channelBlockButton/index.js | 2 +- ui/component/channelContent/index.js | 6 +- ui/component/channelDiscussion/index.js | 2 +- ui/component/channelEdit/index.js | 8 +- ui/component/channelEdit/view.jsx | 4 +- .../channelMentionSuggestion/index.js | 2 +- .../channelMentionSuggestions/index.js | 3 +- .../channelMentionSuggestions/view.jsx | 5 +- .../channelMentionTopSuggestion/index.js | 3 +- ui/component/channelSelector/index.js | 2 +- ui/component/channelStakedIndicator/index.js | 2 +- ui/component/channelThumbnail/index.js | 3 +- ui/component/channelThumbnail/view.jsx | 4 +- ui/component/channelTitle/index.js | 2 +- ui/component/claimAbandonButton/index.js | 2 +- ui/component/claimAuthor/index.js | 2 +- ui/component/claimCollectionAdd/index.js | 6 +- .../claimCollectionAddButton/index.js | 3 +- ui/component/claimEffectiveAmount/index.js | 2 +- .../claimInsufficientCredits/index.js | 2 +- ui/component/claimLink/index.js | 3 +- ui/component/claimList/index.js | 3 +- ui/component/claimListDiscover/index.js | 6 +- ui/component/claimListDiscover/view.jsx | 8 +- ui/component/claimListHeader/index.js | 3 +- ui/component/claimListHeader/view.jsx | 2 +- ui/component/claimMenuList/index.js | 14 +- ui/component/claimMenuList/view.jsx | 18 +- ui/component/claimPreview/index.js | 19 +- ui/component/claimPreview/view.jsx | 14 +- ui/component/claimPreviewSubtitle/index.js | 5 +- ui/component/claimPreviewSubtitle/view.jsx | 4 +- ui/component/claimPreviewTile/index.js | 6 +- ui/component/claimPreviewTile/view.jsx | 18 +- ui/component/claimPreviewTitle/index.js | 2 +- ui/component/claimProperties/index.js | 2 +- ui/component/claimRepostAuthor/index.js | 2 +- ui/component/claimSupportButton/index.js | 2 +- ui/component/claimTags/index.js | 2 +- ui/component/claimTilesDiscover/index.js | 12 +- ui/component/claimTilesDiscover/view.jsx | 3 +- ui/component/claimType/index.js | 2 +- ui/component/claimUri/index.js | 2 +- ui/component/collectionActions/index.js | 8 +- .../collectionContentSidebar/index.js | 5 +- .../collectionContentSidebar/view.jsx | 2 +- ui/component/collectionEdit/index.js | 13 +- ui/component/collectionEdit/view.jsx | 5 +- ui/component/collectionMenuList/index.js | 2 +- .../collectionPreviewOverlay/index.js | 8 +- ui/component/collectionPreviewTile/index.js | 11 +- ui/component/collectionSelectItem/index.js | 9 +- ui/component/collectionSelectItem/view.jsx | 7 +- ui/component/collectionsListMine/index.js | 4 +- ui/component/collectionsListMine/view.jsx | 2 +- ui/component/comment/index.js | 2 +- ui/component/comment/view.jsx | 2 +- ui/component/commentCreate/index.js | 4 +- ui/component/commentMenuList/index.js | 6 +- ui/component/commentMenuList/view.jsx | 4 +- ui/component/commentReactions/index.js | 3 +- ui/component/commentsList/index.js | 2 +- ui/component/commentsReplies/index.js | 7 +- ui/component/common/credit-amount.jsx | 4 +- ui/component/creatorAnalytics/index.js | 2 +- ui/component/dateTime/index.js | 3 +- ui/component/emailCollection/index.js | 6 +- ui/component/embedPlayButton/index.js | 4 +- ui/component/errorBoundary/view.jsx | 2 +- ui/component/fileActions/index.js | 7 +- ui/component/fileActions/view.jsx | 5 +- ui/component/fileDescription/index.js | 4 +- ui/component/fileDescription/view.jsx | 2 +- ui/component/fileDetails/index.js | 12 +- ui/component/fileDownloadLink/index.js | 10 +- ui/component/fileDrop/index.js | 9 +- ui/component/filePrice/index.js | 6 +- ui/component/fileReactions/index.js | 2 +- ui/component/fileRender/index.js | 11 +- ui/component/fileRenderFloating/index.js | 11 +- ui/component/fileRenderFloating/view.jsx | 4 +- ui/component/fileRenderInitiator/index.js | 12 +- ui/component/fileRenderInline/index.js | 3 +- ui/component/fileThumbnail/index.js | 3 +- ui/component/fileTitle/index.js | 2 +- ui/component/fileTitleSection/index.js | 2 +- ui/component/fileTitleSection/view.jsx | 2 +- ui/component/fileType/index.js | 3 +- ui/component/fileValues/index.js | 8 +- ui/component/fileViewCount/index.js | 2 +- ui/component/fileViewCountInline/index.js | 2 +- ui/component/fileViewerEmbeddedTitle/index.js | 2 +- ui/component/fileWatchLaterLink/index.js | 12 +- ui/component/fileWatchLaterLink/view.jsx | 2 +- ui/component/header/index.js | 4 +- ui/component/header/view.jsx | 2 +- ui/component/hiddenNsfwClaims/index.js | 8 +- ui/component/inviteNew/index.js | 3 +- ui/component/invited/view.jsx | 25 +- ui/component/livestreamComment/index.js | 2 +- ui/component/livestreamComment/view.jsx | 2 +- ui/component/livestreamComments/index.js | 2 +- ui/component/livestreamLayout/index.js | 6 +- ui/component/livestreamLink/index.js | 2 +- ui/component/livestreamLink/view.jsx | 3 +- ui/component/logo/index.js | 2 +- ui/component/markdownLink/view.jsx | 2 +- ui/component/nagContinueFirstRun/index.js | 2 +- ui/component/navigationHistoryItem/index.js | 12 +- ui/component/notification/view.jsx | 12 +- .../notificationContentChannelMenu/view.jsx | 9 +- ui/component/page/index.js | 2 +- ui/component/page/view.jsx | 2 +- ui/component/postEditor/index.js | 15 +- ui/component/postViewer/index.js | 2 +- ui/component/postViewer/view.jsx | 2 +- ui/component/previewLink/index.js | 8 +- ui/component/previewLink/view.jsx | 2 +- .../previewOverlayProperties/index.js | 9 +- ui/component/privacyAgreement/index.js | 12 +- .../publishAdditionalOptions/index.js | 3 +- ui/component/publishBid/index.js | 7 +- ui/component/publishDescription/index.js | 9 +- ui/component/publishFile/index.js | 12 +- ui/component/publishFile/view.jsx | 2 +- ui/component/publishForm/index.js | 24 +- ui/component/publishForm/view.jsx | 8 +- ui/component/publishFormErrors/index.js | 2 +- ui/component/publishFormErrors/view.jsx | 5 +- ui/component/publishName/index.js | 5 +- ui/component/publishName/name-help-text.jsx | 2 +- ui/component/publishName/view.jsx | 4 +- ui/component/publishPending/index.js | 5 +- ui/component/publishPending/view.jsx | 2 +- ui/component/publishPrice/index.js | 14 +- ui/component/publishReleaseDate/index.js | 3 +- ui/component/recommendedContent/index.js | 2 +- ui/component/reportContent/index.js | 3 +- ui/component/repostCreate/index.js | 15 +- ui/component/repostCreate/view.jsx | 11 +- ui/component/router/index.js | 9 +- ui/component/router/view.jsx | 4 +- ui/component/searchChannelField/view.jsx | 2 +- ui/component/searchTopClaim/index.js | 10 +- ui/component/searchTopClaim/view.jsx | 19 +- ui/component/selectAsset/view.jsx | 4 +- ui/component/selectChannel/index.js | 16 +- ui/component/selectThumbnail/index.js | 10 +- ui/component/selectThumbnail/view.jsx | 3 +- ui/component/settingAccount/index.js | 4 +- ui/component/settingAppearance/index.js | 2 +- ui/component/settingAppearance/view.jsx | 2 +- ui/component/settingAutoLaunch/index.js | 10 +- ui/component/settingClosingBehavior/index.js | 8 +- ui/component/settingCommentsServer/index.js | 2 +- ui/component/settingContent/index.js | 3 +- ui/component/settingContent/view.jsx | 2 +- ui/component/settingSystem/index.js | 3 +- ui/component/settingUnauthenticated/index.js | 2 +- ui/component/settingWalletServer/index.js | 16 +- ui/component/sideNavigation/index.js | 5 +- ui/component/socialShare/index.js | 2 +- ui/component/splash/index.js | 10 +- ui/component/splash/view.jsx | 2 +- ui/component/subscribeButton/index.js | 2 +- ui/component/subscribeButton/view.jsx | 67 +- ui/component/supportsLiquidate/index.js | 10 +- ui/component/syncEnableFlow/index.js | 8 +- ui/component/syncEnableFlow/view.jsx | 16 +- ui/component/syncToggle/index.js | 8 +- ui/component/tag/view.jsx | 5 +- ui/component/themeSelector/index.js | 2 +- ui/component/themeSelector/view.jsx | 2 +- ui/component/transactionListTable/index.js | 6 +- .../transactionListTableItem/index.js | 2 +- .../transactionListTableItem/view.jsx | 4 +- ui/component/txoList/index.js | 6 +- ui/component/txoList/view.jsx | 8 +- ui/component/uriIndicator/index.js | 13 +- ui/component/userChannelFollowIntro/view.jsx | 21 +- ui/component/userEmailNew/index.js | 3 +- ui/component/userFirstChannel/index.js | 7 +- ui/component/userFirstChannel/view.jsx | 6 +- ui/component/userSignUp/index.js | 10 +- ui/component/userSignUp/view.jsx | 2 +- ui/component/videoDuration/index.js | 7 +- ui/component/viewers/appViewer/index.js | 9 +- ui/component/viewers/videoViewer/index.js | 9 +- .../internal/plugins/videojs-recsys/plugin.js | 2 +- ui/component/walletAddress/index.js | 16 +- ui/component/walletBalance/index.js | 7 +- ui/component/walletSend/index.js | 5 +- ui/component/walletSendTip/index.js | 11 +- ui/component/walletSendTip/view.jsx | 4 +- .../walletSpendableBalanceHelp/index.js | 2 +- ui/component/walletSwap/index.js | 3 +- ui/component/walletTipAmountSelector/index.js | 2 +- ui/component/wunderbarSuggestion/index.js | 2 +- ui/component/wunderbarSuggestions/index.js | 2 +- ui/component/wunderbarSuggestions/view.jsx | 2 +- ui/component/wunderbarTopSuggestion/index.js | 6 +- ui/component/youtubeTransferStatus/view.jsx | 2 +- ui/component/yrblWalletEmpty/index.js | 2 +- ui/constants/abandon_states.js | 4 + ui/constants/action_types.js | 136 +++ ui/constants/claim.js | 78 +- ui/constants/collections.js | 19 +- ui/constants/daemon_settings.js | 39 + ui/constants/errors.js | 2 + ui/constants/settings.js | 14 + ui/constants/shape_shift.js | 3 + ui/constants/shared_preferences.js | 31 + ui/constants/sort_options.js | 4 + ui/constants/speech_urls.js | 2 + ui/constants/transaction_list.js | 3 + ui/constants/transaction_types.js | 5 + ui/constants/txo_list.js | 36 + ui/constants/youtube.js | 11 + ui/effects/use-lighthouse.js | 2 +- ui/index.jsx | 7 +- ui/lbry.js | 251 ++++ ui/modal/modalAffirmPurchase/index.js | 8 +- ui/modal/modalAffirmPurchase/view.jsx | 2 +- ui/modal/modalAutoGenerateThumbnail/index.js | 8 +- ui/modal/modalBlockChannel/index.js | 2 +- ui/modal/modalConfirmAge/view.jsx | 2 +- ui/modal/modalConfirmThumbnailUpload/index.js | 2 +- ui/modal/modalConfirmTransaction/index.js | 3 +- ui/modal/modalFileSelection/index.js | 8 +- ui/modal/modalFileTimeout/index.js | 9 +- ui/modal/modalMassTipUnlock/index.js | 5 +- ui/modal/modalPublish/index.js | 3 +- ui/modal/modalPublishPreview/index.js | 11 +- ui/modal/modalRemoveCard/index.js | 7 +- ui/modal/modalRemoveCollection/index.js | 6 +- ui/modal/modalRemoveFile/index.js | 6 +- ui/modal/modalRevokeClaim/index.js | 7 +- ui/modal/modalRouter/index.js | 13 +- ui/modal/modalSupportsLiquidate/index.js | 6 +- ui/modal/modalWalletDecrypt/index.js | 14 +- ui/modal/modalWalletEncrypt/index.js | 14 +- ui/modal/modalWalletUnlock/index.js | 14 +- ui/page/buy/index.js | 5 +- ui/page/channel/index.js | 4 +- ui/page/channel/view.jsx | 4 +- ui/page/channelNew/index.js | 2 +- ui/page/channels/index.js | 4 +- ui/page/channelsFollowing/index.js | 2 +- ui/page/channelsFollowing/view.jsx | 2 +- ui/page/collection/index.js | 18 +- ui/page/collection/view.jsx | 2 +- ui/page/creatorDashboard/index.js | 4 +- ui/page/discover/index.js | 4 +- ui/page/embedWrapper/index.js | 19 +- ui/page/file/index.js | 12 +- ui/page/fileListDownloaded/index.js | 4 +- ui/page/fileListPublished/index.js | 7 +- ui/page/help/view.jsx | 2 +- ui/page/invite/index.js | 6 +- ui/page/invited/index.js | 9 +- ui/page/library/index.js | 12 +- ui/page/listBlocked/index.js | 2 +- ui/page/lists/index.js | 12 +- ui/page/livestream/index.js | 3 +- ui/page/livestream/view.jsx | 2 +- ui/page/livestreamSetup/index.js | 3 +- ui/page/livestreamSetup/view.jsx | 2 +- ui/page/ownComments/index.js | 2 +- ui/page/publish/index.js | 5 +- ui/page/repost/index.js | 8 +- ui/page/search/view.jsx | 5 +- ui/page/send/view.jsx | 4 +- ui/page/settingsCreator/view.jsx | 2 +- ui/page/settingsNotifications/view.jsx | 2 +- ui/page/show/index.js | 15 +- ui/page/show/view.jsx | 9 +- ui/page/tagsFollowing/index.js | 4 +- ui/page/top/index.js | 9 +- ui/page/wallet/index.js | 4 +- ui/page/youtubeSync/view.jsx | 4 +- ui/reducers.js | 9 +- ui/redux/actions/app.js | 30 +- ui/redux/actions/claims.js | 1037 +++++++++++++++++ ui/redux/actions/collections.js | 487 ++++++++ ui/redux/actions/comments.js | 36 +- ui/redux/actions/content.js | 14 +- ui/redux/actions/file.js | 25 +- ui/redux/actions/file_info.js | 73 ++ ui/redux/actions/livestream.js | 3 +- ui/redux/actions/notifications.js | 2 +- ui/redux/actions/publish.js | 501 +++++++- ui/redux/actions/reactions.js | 8 +- ui/redux/actions/rewards.js | 4 +- ui/redux/actions/search.js | 12 +- ui/redux/actions/settings.js | 20 +- ui/redux/actions/subscriptions.js | 18 +- ui/redux/actions/sync.js | 198 +++- ui/redux/actions/user.js | 16 +- ui/redux/actions/wallet.js | 702 +++++++++++ ui/redux/middleware/shared-state.js | 56 + ui/redux/reducers/app.js | 39 +- ui/redux/reducers/blocked.js | 6 +- ui/redux/reducers/claims.js | 894 ++++++++++++++ ui/redux/reducers/coinSwap.js | 6 +- ui/redux/reducers/collections.js | 226 ++++ ui/redux/reducers/comments.js | 2 +- ui/redux/reducers/file_info.js | 158 +++ ui/redux/reducers/publish.js | 134 +++ ui/redux/reducers/rewards.js | 9 +- ui/redux/reducers/settings.js | 13 +- ui/redux/reducers/subscriptions.js | 7 +- ui/redux/reducers/sync.js | 15 +- ui/redux/reducers/tags.js | 4 +- ui/redux/reducers/wallet.js | 549 +++++++++ ui/redux/selectors/app.js | 2 +- ui/redux/selectors/blocked.js | 2 +- ui/redux/selectors/claims.js | 629 ++++++++++ ui/redux/selectors/collections.js | 250 ++++ ui/redux/selectors/comments.js | 4 +- ui/redux/selectors/content.js | 7 +- ui/redux/selectors/file_info.js | 149 ++- ui/redux/selectors/livestream.js | 2 +- ui/redux/selectors/publish.js | 108 +- ui/redux/selectors/reactions.js | 2 +- ui/redux/selectors/search.js | 8 +- ui/redux/selectors/settings.js | 4 +- ui/redux/selectors/subscriptions.js | 5 +- ui/redux/selectors/wallet.js | 273 +++++ ui/rewards.js | 14 +- ui/store.js | 15 +- ui/util/batch-actions.js | 7 + ui/util/buildHomepage.js | 5 +- ui/util/claim.js | 66 ++ ui/util/context-menu.js | 2 +- ui/util/form-validation.js | 4 +- ui/util/format-credits.js | 60 + ui/util/lbryURI.js | 330 ++++++ ui/util/merge-claim.js | 7 + ui/util/remark-lbry.js | 2 +- ui/util/search.js | 2 +- ui/util/sync-settings.js | 4 +- ui/util/transifex-upload.js | 78 ++ ui/util/url.js | 3 +- ui/util/zoomWindow.js | 2 +- web/.env.ody | 92 ++ web/component/ads/index.js | 2 +- .../fileViewerEmbeddedEnded/index.js | 2 +- web/lbry.js | 247 ++++ web/package.json | 2 - web/src/html.js | 4 +- web/src/lbryURI.js | 343 ++++++ web/src/rss.js | 2 +- web/webpack.config.js | 8 +- web/yarn.lock | 29 - webpack.base.config.js | 5 +- yarn.lock | 22 +- 419 files changed, 12085 insertions(+), 1172 deletions(-) create mode 100644 extras/lbry-first/lbry-first.js create mode 100644 extras/lbryinc/constants/action_types.js create mode 100644 extras/lbryinc/constants/claim.js create mode 100644 extras/lbryinc/constants/errors.js create mode 100644 extras/lbryinc/constants/youtube.js create mode 100644 extras/lbryinc/index.js create mode 100644 extras/lbryinc/lbryio.js create mode 100644 extras/lbryinc/redux/actions/auth.js create mode 100644 extras/lbryinc/redux/actions/blacklist.js create mode 100644 extras/lbryinc/redux/actions/cost_info.js create mode 100644 extras/lbryinc/redux/actions/filtered.js create mode 100644 extras/lbryinc/redux/actions/homepage.js create mode 100644 extras/lbryinc/redux/actions/stats.js create mode 100644 extras/lbryinc/redux/actions/sync.js create mode 100644 extras/lbryinc/redux/actions/web.js create mode 100644 extras/lbryinc/redux/reducers/auth.js create mode 100644 extras/lbryinc/redux/reducers/blacklist.js create mode 100644 extras/lbryinc/redux/reducers/cost_info.js create mode 100644 extras/lbryinc/redux/reducers/filtered.js create mode 100644 extras/lbryinc/redux/reducers/homepage.js create mode 100644 extras/lbryinc/redux/reducers/stats.js create mode 100644 extras/lbryinc/redux/reducers/sync.js create mode 100644 extras/lbryinc/redux/reducers/web.js create mode 100644 extras/lbryinc/redux/selectors/auth.js create mode 100644 extras/lbryinc/redux/selectors/blacklist.js create mode 100644 extras/lbryinc/redux/selectors/cost_info.js create mode 100644 extras/lbryinc/redux/selectors/filtered.js create mode 100644 extras/lbryinc/redux/selectors/homepage.js create mode 100644 extras/lbryinc/redux/selectors/stats.js create mode 100644 extras/lbryinc/redux/selectors/sync.js create mode 100644 extras/lbryinc/redux/selectors/web.js create mode 100644 extras/lbryinc/util/redux-utils-delete-me.js create mode 100644 extras/lbryinc/util/swap-json.js create mode 100644 extras/lbryinc/util/transifex-upload.js create mode 100644 extras/recsys/index.js rename {ui => extras/recsys}/recsys.js (97%) create mode 100644 flow-typed/Blocklist.js create mode 100644 flow-typed/Claim.js create mode 100644 flow-typed/CoinSwap.js create mode 100644 flow-typed/Collections.js create mode 100644 flow-typed/Lbry.js create mode 100644 flow-typed/LbryFirst.js create mode 100644 flow-typed/Notification.js create mode 100644 flow-typed/Publish.js create mode 100644 flow-typed/Redux.js create mode 100644 flow-typed/Reflector.js create mode 100644 flow-typed/Tags.js create mode 100644 flow-typed/Transaction.js create mode 100644 flow-typed/Txo.js create mode 100644 flow-typed/i18n.js create mode 100644 flow-typed/lbryURI.js create mode 100644 ui/constants/abandon_states.js create mode 100644 ui/constants/daemon_settings.js create mode 100644 ui/constants/errors.js create mode 100644 ui/constants/shape_shift.js create mode 100644 ui/constants/shared_preferences.js create mode 100644 ui/constants/sort_options.js create mode 100644 ui/constants/speech_urls.js create mode 100644 ui/constants/transaction_list.js create mode 100644 ui/constants/txo_list.js create mode 100644 ui/constants/youtube.js create mode 100644 ui/lbry.js create mode 100644 ui/redux/actions/claims.js create mode 100644 ui/redux/actions/collections.js create mode 100644 ui/redux/actions/file_info.js create mode 100644 ui/redux/actions/wallet.js create mode 100644 ui/redux/middleware/shared-state.js create mode 100644 ui/redux/reducers/claims.js create mode 100644 ui/redux/reducers/collections.js create mode 100644 ui/redux/reducers/file_info.js create mode 100644 ui/redux/reducers/publish.js create mode 100644 ui/redux/reducers/wallet.js create mode 100644 ui/redux/selectors/claims.js create mode 100644 ui/redux/selectors/collections.js create mode 100644 ui/redux/selectors/wallet.js create mode 100644 ui/util/batch-actions.js create mode 100644 ui/util/format-credits.js create mode 100644 ui/util/lbryURI.js create mode 100644 ui/util/merge-claim.js create mode 100644 ui/util/transifex-upload.js create mode 100644 web/.env.ody create mode 100644 web/lbry.js create mode 100644 web/src/lbryURI.js diff --git a/.flowconfig b/.flowconfig index 0ae16f24e..8a6fdca75 100644 --- a/.flowconfig +++ b/.flowconfig @@ -7,13 +7,6 @@ [include] [libs] -./flow-typed -node_modules/lbry-redux/flow-typed/ -node_modules/lbryinc/flow-typed/ - -[untyped] -.*/node_modules/lbry-redux -.*/node_modules/lbryinc [lints] @@ -31,7 +24,7 @@ module.name_mapper='^modal\(.*\)$' -> '/ui/modal\1' module.name_mapper='^app\(.*\)$' -> '/ui/app\1' module.name_mapper='^native\(.*\)$' -> '/ui/native\1' module.name_mapper='^analytics\(.*\)$' -> '/ui/analytics\1' -module.name_mapper='^recsys\(.*\)$' -> '/ui/recsys\1' +module.name_mapper='^recsys\(.*\)$' -> '/extras/recsys\1' module.name_mapper='^rewards\(.*\)$' -> '/ui/rewards\1' module.name_mapper='^i18n\(.*\)$' -> '/ui/i18n\1' module.name_mapper='^effects\(.*\)$' -> '/ui/effects\1' diff --git a/electron/Daemon.js b/electron/Daemon.js index 976423b25..2d11e36a9 100644 --- a/electron/Daemon.js +++ b/electron/Daemon.js @@ -1,7 +1,7 @@ import path from 'path'; import fs from 'fs'; import { spawn, execSync } from 'child_process'; -import { Lbry } from 'lbry-redux'; +import Lbry from 'lbry'; export default class Daemon { static lbrynetPath = diff --git a/electron/index.js b/electron/index.js index ec7cc01d1..be4d11801 100644 --- a/electron/index.js +++ b/electron/index.js @@ -6,7 +6,7 @@ import SemVer from 'semver'; import https from 'https'; import { app, dialog, ipcMain, session, shell } from 'electron'; import { autoUpdater } from 'electron-updater'; -import { Lbry } from 'lbry-redux'; +import Lbry from 'lbry'; import LbryFirstInstance from './LbryFirstInstance'; import Daemon from './Daemon'; import isDev from 'electron-is-dev'; diff --git a/electron/startSandbox.js b/electron/startSandbox.js index 4c6d09e97..d2f1077d9 100644 --- a/electron/startSandbox.js +++ b/electron/startSandbox.js @@ -8,7 +8,7 @@ if (typeof global.fetch === 'object') { global.fetch = global.fetch.default; } -const { Lbry } = require('lbry-redux'); +const Lbry = require('lbry'); delete global.window; diff --git a/extras/lbry-first/lbry-first.js b/extras/lbry-first/lbry-first.js new file mode 100644 index 000000000..a6025b753 --- /dev/null +++ b/extras/lbry-first/lbry-first.js @@ -0,0 +1,184 @@ +// @flow +/* + LBRY FIRST does not work due to api changes + */ +import 'proxy-polyfill'; + +const CHECK_LBRYFIRST_STARTED_TRY_NUMBER = 200; +// +// Basic LBRYFIRST connection config +// Offers a proxy to call LBRYFIRST methods + +// +const LbryFirst: LbryFirstTypes = { + isConnected: false, + connectPromise: null, + lbryFirstConnectionString: 'http://localhost:1337/rpc', + apiRequestHeaders: { 'Content-Type': 'application/json' }, + + // Allow overriding lbryFirst connection string (e.g. to `/api/proxy` for lbryweb) + setLbryFirstConnectionString: (value: string) => { + LbryFirst.lbryFirstConnectionString = value; + }, + + setApiHeader: (key: string, value: string) => { + LbryFirst.apiRequestHeaders = Object.assign(LbryFirst.apiRequestHeaders, { [key]: value }); + }, + + unsetApiHeader: key => { + Object.keys(LbryFirst.apiRequestHeaders).includes(key) && + delete LbryFirst.apiRequestHeaders['key']; + }, + // Allow overriding Lbry methods + overrides: {}, + setOverride: (methodName, newMethod) => { + LbryFirst.overrides[methodName] = newMethod; + }, + getApiRequestHeaders: () => LbryFirst.apiRequestHeaders, + + // LbryFirst Methods + status: (params = {}) => lbryFirstCallWithResult('status', params), + stop: () => lbryFirstCallWithResult('stop', {}), + version: () => lbryFirstCallWithResult('version', {}), + + // Upload to youtube + upload: (params: { title: string, description: string, file_path: ?string } = {}) => { + // Only upload when originally publishing for now + if (!params.file_path) { + return Promise.resolve(); + } + + const uploadParams: { + Title: string, + Description: string, + FilePath: string, + Category: string, + Keywords: string, + } = { + Title: params.title, + Description: params.description, + FilePath: params.file_path, + Category: '', + Keywords: '', + }; + + return lbryFirstCallWithResult('youtube.Upload', uploadParams); + }, + + hasYTAuth: (token: string) => { + const hasYTAuthParams = {}; + hasYTAuthParams.AuthToken = token; + return lbryFirstCallWithResult('youtube.HasAuth', hasYTAuthParams); + }, + + ytSignup: () => { + const emptyParams = {}; + return lbryFirstCallWithResult('youtube.Signup', emptyParams); + }, + + remove: () => { + const emptyParams = {}; + return lbryFirstCallWithResult('youtube.Remove', emptyParams); + }, + + // Connect to lbry-first + connect: () => { + if (LbryFirst.connectPromise === null) { + LbryFirst.connectPromise = new Promise((resolve, reject) => { + let tryNum = 0; + // Check every half second to see if the lbryFirst is accepting connections + function checkLbryFirstStarted() { + tryNum += 1; + LbryFirst.status() + .then(resolve) + .catch(() => { + if (tryNum <= CHECK_LBRYFIRST_STARTED_TRY_NUMBER) { + setTimeout(checkLbryFirstStarted, tryNum < 50 ? 400 : 1000); + } else { + reject(new Error('Unable to connect to LBRY')); + } + }); + } + + checkLbryFirstStarted(); + }); + } + + // Flow thinks this could be empty, but it will always return a promise + // $FlowFixMe + return LbryFirst.connectPromise; + }, +}; + +function checkAndParse(response) { + if (response.status >= 200 && response.status < 300) { + return response.json(); + } + return response.json().then(json => { + let error; + if (json.error) { + const errorMessage = typeof json.error === 'object' ? json.error.message : json.error; + error = new Error(errorMessage); + } else { + error = new Error('Protocol error with unknown response signature'); + } + return Promise.reject(error); + }); +} + +export function apiCall(method: string, params: ?{}, resolve: Function, reject: Function) { + const counter = new Date().getTime(); + const paramsArray = [params]; + const options = { + method: 'POST', + headers: LbryFirst.apiRequestHeaders, + body: JSON.stringify({ + jsonrpc: '2.0', + method, + params: paramsArray, + id: counter, + }), + }; + + return fetch(LbryFirst.lbryFirstConnectionString, options) + .then(checkAndParse) + .then(response => { + const error = response.error || (response.result && response.result.error); + + if (error) { + return reject(error); + } + return resolve(response.result); + }) + .catch(reject); +} + +function lbryFirstCallWithResult(name: string, params: ?{} = {}) { + return new Promise((resolve, reject) => { + apiCall( + name, + params, + result => { + resolve(result); + }, + reject + ); + }); +} + +// This is only for a fallback +// If there is a LbryFirst method that is being called by an app, it should be added to /flow-typed/LbryFirst.js +const lbryFirstProxy = new Proxy(LbryFirst, { + get(target: LbryFirstTypes, name: string) { + if (name in target) { + return target[name]; + } + + return (params = {}) => + new Promise((resolve, reject) => { + apiCall(name, params, resolve, reject); + }); + }, +}); + +export default lbryFirstProxy; diff --git a/extras/lbryinc/constants/action_types.js b/extras/lbryinc/constants/action_types.js new file mode 100644 index 000000000..65723007d --- /dev/null +++ b/extras/lbryinc/constants/action_types.js @@ -0,0 +1,97 @@ +// Claims +export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED'; +export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED'; +export const FETCH_TRENDING_CONTENT_STARTED = 'FETCH_TRENDING_CONTENT_STARTED'; +export const FETCH_TRENDING_CONTENT_COMPLETED = 'FETCH_TRENDING_CONTENT_COMPLETED'; +export const RESOLVE_URIS_STARTED = 'RESOLVE_URIS_STARTED'; +export const RESOLVE_URIS_COMPLETED = 'RESOLVE_URIS_COMPLETED'; +export const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED'; +export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED'; +export const FETCH_CHANNEL_CLAIM_COUNT_STARTED = 'FETCH_CHANNEL_CLAIM_COUNT_STARTED'; +export const FETCH_CHANNEL_CLAIM_COUNT_COMPLETED = 'FETCH_CHANNEL_CLAIM_COUNT_COMPLETED'; +export const FETCH_CLAIM_LIST_MINE_STARTED = 'FETCH_CLAIM_LIST_MINE_STARTED'; +export const FETCH_CLAIM_LIST_MINE_COMPLETED = 'FETCH_CLAIM_LIST_MINE_COMPLETED'; +export const ABANDON_CLAIM_STARTED = 'ABANDON_CLAIM_STARTED'; +export const ABANDON_CLAIM_SUCCEEDED = 'ABANDON_CLAIM_SUCCEEDED'; +export const FETCH_CHANNEL_LIST_STARTED = 'FETCH_CHANNEL_LIST_STARTED'; +export const FETCH_CHANNEL_LIST_COMPLETED = 'FETCH_CHANNEL_LIST_COMPLETED'; +export const CREATE_CHANNEL_STARTED = 'CREATE_CHANNEL_STARTED'; +export const CREATE_CHANNEL_COMPLETED = 'CREATE_CHANNEL_COMPLETED'; +export const PUBLISH_STARTED = 'PUBLISH_STARTED'; +export const PUBLISH_COMPLETED = 'PUBLISH_COMPLETED'; +export const PUBLISH_FAILED = 'PUBLISH_FAILED'; +export const SET_PLAYING_URI = 'SET_PLAYING_URI'; +export const SET_CONTENT_POSITION = 'SET_CONTENT_POSITION'; +export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED'; +export const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI'; +export const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL'; + +// Subscriptions +export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE'; +export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE'; +export const CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS = + 'CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS'; +export const CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS = + 'CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS'; +export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS'; +export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST'; +export const UPDATE_SUBSCRIPTION_UNREADS = 'UPDATE_SUBSCRIPTION_UNREADS'; +export const REMOVE_SUBSCRIPTION_UNREADS = 'REMOVE_SUBSCRIPTION_UNREADS'; +export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED'; +export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED'; +export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE'; +export const FETCH_SUBSCRIPTIONS_START = 'FETCH_SUBSCRIPTIONS_START'; +export const FETCH_SUBSCRIPTIONS_FAIL = 'FETCH_SUBSCRIPTIONS_FAIL'; +export const FETCH_SUBSCRIPTIONS_SUCCESS = 'FETCH_SUBSCRIPTIONS_SUCCESS'; +export const SET_VIEW_MODE = 'SET_VIEW_MODE'; +export const GET_SUGGESTED_SUBSCRIPTIONS_START = 'GET_SUGGESTED_SUBSCRIPTIONS_START'; +export const GET_SUGGESTED_SUBSCRIPTIONS_SUCCESS = 'GET_SUGGESTED_SUBSCRIPTIONS_SUCCESS'; +export const GET_SUGGESTED_SUBSCRIPTIONS_FAIL = 'GET_SUGGESTED_SUBSCRIPTIONS_FAIL'; +export const SUBSCRIPTION_FIRST_RUN_COMPLETED = 'SUBSCRIPTION_FIRST_RUN_COMPLETED'; +export const VIEW_SUGGESTED_SUBSCRIPTIONS = 'VIEW_SUGGESTED_SUBSCRIPTIONS'; + +// Blacklist +export const FETCH_BLACK_LISTED_CONTENT_STARTED = 'FETCH_BLACK_LISTED_CONTENT_STARTED'; +export const FETCH_BLACK_LISTED_CONTENT_COMPLETED = 'FETCH_BLACK_LISTED_CONTENT_COMPLETED'; +export const FETCH_BLACK_LISTED_CONTENT_FAILED = 'FETCH_BLACK_LISTED_CONTENT_FAILED'; +export const BLACK_LISTED_CONTENT_SUBSCRIBE = 'BLACK_LISTED_CONTENT_SUBSCRIBE'; + +// Filtered list +export const FETCH_FILTERED_CONTENT_STARTED = 'FETCH_FILTERED_CONTENT_STARTED'; +export const FETCH_FILTERED_CONTENT_COMPLETED = 'FETCH_FILTERED_CONTENT_COMPLETED'; +export const FETCH_FILTERED_CONTENT_FAILED = 'FETCH_FILTERED_CONTENT_FAILED'; +export const FILTERED_CONTENT_SUBSCRIBE = 'FILTERED_CONTENT_SUBSCRIBE'; + +// Cost Info +export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED'; +export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED'; + +// Stats +export const FETCH_VIEW_COUNT_STARTED = 'FETCH_VIEW_COUNT_STARTED'; +export const FETCH_VIEW_COUNT_FAILED = 'FETCH_VIEW_COUNT_FAILED'; +export const FETCH_VIEW_COUNT_COMPLETED = 'FETCH_VIEW_COUNT_COMPLETED'; +export const FETCH_SUB_COUNT_STARTED = 'FETCH_SUB_COUNT_STARTED'; +export const FETCH_SUB_COUNT_FAILED = 'FETCH_SUB_COUNT_FAILED'; +export const FETCH_SUB_COUNT_COMPLETED = 'FETCH_SUB_COUNT_COMPLETED'; + +// Cross-device Sync +export const GET_SYNC_STARTED = 'GET_SYNC_STARTED'; +export const GET_SYNC_COMPLETED = 'GET_SYNC_COMPLETED'; +export const GET_SYNC_FAILED = 'GET_SYNC_FAILED'; +export const SET_SYNC_STARTED = 'SET_SYNC_STARTED'; +export const SET_SYNC_FAILED = 'SET_SYNC_FAILED'; +export const SET_SYNC_COMPLETED = 'SET_SYNC_COMPLETED'; +export const SET_DEFAULT_ACCOUNT = 'SET_DEFAULT_ACCOUNT'; +export const SYNC_APPLY_STARTED = 'SYNC_APPLY_STARTED'; +export const SYNC_APPLY_COMPLETED = 'SYNC_APPLY_COMPLETED'; +export const SYNC_APPLY_FAILED = 'SYNC_APPLY_FAILED'; +export const SYNC_APPLY_BAD_PASSWORD = 'SYNC_APPLY_BAD_PASSWORD'; +export const SYNC_RESET = 'SYNC_RESET'; + +// Lbry.tv +export const UPDATE_UPLOAD_PROGRESS = 'UPDATE_UPLOAD_PROGRESS'; + +// User +export const GENERATE_AUTH_TOKEN_FAILURE = 'GENERATE_AUTH_TOKEN_FAILURE'; +export const GENERATE_AUTH_TOKEN_STARTED = 'GENERATE_AUTH_TOKEN_STARTED'; +export const GENERATE_AUTH_TOKEN_SUCCESS = 'GENERATE_AUTH_TOKEN_SUCCESS'; diff --git a/extras/lbryinc/constants/claim.js b/extras/lbryinc/constants/claim.js new file mode 100644 index 000000000..4cf33ce09 --- /dev/null +++ b/extras/lbryinc/constants/claim.js @@ -0,0 +1,5 @@ +export const MINIMUM_PUBLISH_BID = 0.00000001; + +export const CHANNEL_ANONYMOUS = 'anonymous'; +export const CHANNEL_NEW = 'new'; +export const PAGE_SIZE = 20; diff --git a/extras/lbryinc/constants/errors.js b/extras/lbryinc/constants/errors.js new file mode 100644 index 000000000..90444632a --- /dev/null +++ b/extras/lbryinc/constants/errors.js @@ -0,0 +1,4 @@ +export const ALREADY_CLAIMED = + 'once the invite reward has been claimed the referrer cannot be changed'; +export const REFERRER_NOT_FOUND = + 'A lbry.tv account could not be found for the referrer you provided.'; diff --git a/extras/lbryinc/constants/youtube.js b/extras/lbryinc/constants/youtube.js new file mode 100644 index 000000000..f2db86ab1 --- /dev/null +++ b/extras/lbryinc/constants/youtube.js @@ -0,0 +1,11 @@ +export const YOUTUBE_SYNC_NOT_TRANSFERRED = 'not_transferred'; +export const YOUTUBE_SYNC_PENDING = 'pending'; +export const YOUTUBE_SYNC_PENDING_EMAIL = 'pendingemail'; +export const YOUTUBE_SYNC_PENDING_TRANSFER = 'pending_transfer'; +export const YOUTUBE_SYNC_COMPLETED_TRANSFER = 'completed_transfer'; +export const YOUTUBE_SYNC_QUEUED = 'queued'; +export const YOUTUBE_SYNC_SYNCING = 'syncing'; +export const YOUTUBE_SYNC_SYNCED = 'synced'; +export const YOUTUBE_SYNC_FAILED = 'failed'; +export const YOUTUBE_SYNC_PENDINGUPGRADE = 'pendingupgrade'; +export const YOUTUBE_SYNC_ABANDONDED = 'abandoned'; diff --git a/extras/lbryinc/index.js b/extras/lbryinc/index.js new file mode 100644 index 000000000..b68d006e6 --- /dev/null +++ b/extras/lbryinc/index.js @@ -0,0 +1,83 @@ +import * as LBRYINC_ACTIONS from 'constants/action_types'; +import * as YOUTUBE_STATUSES from 'constants/youtube'; +import * as ERRORS from 'constants/errors'; +import Lbryio from './lbryio'; + +export { Lbryio }; + +export function testTheThing() { + console.log('tested'); +} + +// constants +export { LBRYINC_ACTIONS, YOUTUBE_STATUSES, ERRORS }; + +// utils +export { doTransifexUpload } from 'util/transifex-upload'; + +// actions +export { doGenerateAuthToken } from './redux/actions/auth'; +export { doFetchCostInfoForUri } from './redux/actions/cost_info'; +export { doBlackListedOutpointsSubscribe } from './redux/actions/blacklist'; +export { doFilteredOutpointsSubscribe } from './redux/actions/filtered'; +// export { doFetchFeaturedUris, doFetchTrendingUris } from './redux/actions/homepage'; +export { doFetchViewCount, doFetchSubCount } from './redux/actions/stats'; +export { + doCheckSync, + doGetSync, + doSetSync, + doSetDefaultAccount, + doSyncApply, + doResetSync, + doSyncEncryptAndDecrypt, +} from 'redux/actions/sync'; +export { doUpdateUploadProgress } from './redux/actions/web'; + +// reducers +export { authReducer } from './redux/reducers/auth'; +export { costInfoReducer } from './redux/reducers/cost_info'; +export { blacklistReducer } from './redux/reducers/blacklist'; +export { filteredReducer } from './redux/reducers/filtered'; +// export { homepageReducer } from './redux/reducers/homepage'; +export { statsReducer } from './redux/reducers/stats'; +export { syncReducer } from './redux/reducers/sync'; +export { webReducer } from './redux/reducers/web'; + +// selectors +export { selectAuthToken, selectIsAuthenticating } from './redux/selectors/auth'; +export { + makeSelectFetchingCostInfoForUri, + makeSelectCostInfoForUri, + selectAllCostInfoByUri, + selectFetchingCostInfo, +} from './redux/selectors/cost_info'; +export { + selectBlackListedOutpoints, + selectBlacklistedOutpointMap, +} from './redux/selectors/blacklist'; +export { selectFilteredOutpoints, selectFilteredOutpointMap } from './redux/selectors/filtered'; +// export { +// selectFeaturedUris, +// selectFetchingFeaturedUris, +// selectTrendingUris, +// selectFetchingTrendingUris, +// } from './redux/selectors/homepage'; +export { + selectViewCount, + makeSelectViewCountForUri, + makeSelectSubCountForUri, +} from './redux/selectors/stats'; +export { + selectHasSyncedWallet, + selectSyncData, + selectSyncHash, + selectSetSyncErrorMessage, + selectGetSyncErrorMessage, + selectGetSyncIsPending, + selectSetSyncIsPending, + selectSyncApplyIsPending, + selectHashChanged, + selectSyncApplyErrorMessage, + selectSyncApplyPasswordError, +} from './redux/selectors/sync'; +export { selectCurrentUploads, selectUploadCount } from './redux/selectors/web'; diff --git a/extras/lbryinc/lbryio.js b/extras/lbryinc/lbryio.js new file mode 100644 index 000000000..89c0536e9 --- /dev/null +++ b/extras/lbryinc/lbryio.js @@ -0,0 +1,238 @@ +import * as ACTIONS from 'constants/action_types'; +import Lbry from 'lbry'; +import querystring from 'querystring'; + +const Lbryio = { + enabled: true, + authenticationPromise: null, + exchangePromise: null, + exchangeLastFetched: null, + CONNECTION_STRING: 'https://api.lbry.com/', +}; + +const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000; +const INTERNAL_APIS_DOWN = 'internal_apis_down'; + +// We can't use env's because they aren't passed into node_modules +Lbryio.setLocalApi = endpoint => { + Lbryio.CONNECTION_STRING = endpoint.replace(/\/*$/, '/'); // exactly one slash at the end; +}; + +Lbryio.call = (resource, action, params = {}, method = 'get') => { + if (!Lbryio.enabled) { + return Promise.reject(new Error(__('LBRY internal API is disabled'))); + } + + if (!(method === 'get' || method === 'post')) { + return Promise.reject(new Error(__('Invalid method'))); + } + + function checkAndParse(response) { + if (response.status >= 200 && response.status < 300) { + return response.json(); + } + + if (response.status === 500) { + return Promise.reject(INTERNAL_APIS_DOWN); + } + + if (response) + return response.json().then(json => { + let error; + if (json.error) { + error = new Error(json.error); + } else { + error = new Error('Unknown API error signature'); + } + error.response = response; // This is primarily a hack used in actions/user.js + return Promise.reject(error); + }); + } + + function makeRequest(url, options) { + return fetch(url, options).then(checkAndParse); + } + + return Lbryio.getAuthToken().then(token => { + const fullParams = { auth_token: token, ...params }; + Object.keys(fullParams).forEach(key => { + const value = fullParams[key]; + if (typeof value === 'object') { + fullParams[key] = JSON.stringify(value); + } + }); + + const qs = querystring.stringify(fullParams); + let url = `${Lbryio.CONNECTION_STRING}${resource}/${action}?${qs}`; + + let options = { + method: 'GET', + }; + + if (method === 'post') { + options = { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: qs, + }; + url = `${Lbryio.CONNECTION_STRING}${resource}/${action}`; + } + + return makeRequest(url, options).then(response => response.data); + }); +}; + +Lbryio.authToken = null; + +Lbryio.getAuthToken = () => + new Promise(resolve => { + if (Lbryio.authToken) { + resolve(Lbryio.authToken); + } else if (Lbryio.overrides.getAuthToken) { + Lbryio.overrides.getAuthToken().then(token => { + resolve(token); + }); + } else if (typeof window !== 'undefined') { + const { store } = window; + if (store) { + const state = store.getState(); + const token = state.auth ? state.auth.authToken : null; + Lbryio.authToken = token; + resolve(token); + } + + resolve(null); + } else { + resolve(null); + } + }); + +Lbryio.getCurrentUser = () => Lbryio.call('user', 'me'); + +Lbryio.authenticate = (domain, language) => { + if (!Lbryio.enabled) { + const params = { + id: 1, + primary_email: 'disabled@lbry.io', + has_verified_email: true, + is_identity_verified: true, + is_reward_approved: false, + language: language || 'en', + }; + + return new Promise(resolve => { + resolve(params); + }); + } + + if (Lbryio.authenticationPromise === null) { + Lbryio.authenticationPromise = new Promise((resolve, reject) => { + Lbryio.getAuthToken() + .then(token => { + if (!token || token.length > 60) { + return false; + } + + // check that token works + return Lbryio.getCurrentUser() + .then(user => user) + .catch(error => { + if (error === INTERNAL_APIS_DOWN) { + throw new Error('Internal APIS down'); + } + + return false; + }); + }) + .then(user => { + if (user) { + return user; + } + + return Lbry.status() + .then( + status => + new Promise((res, rej) => { + const appId = + domain && domain !== 'lbry.tv' + ? (domain.replace(/[.]/gi, '') + status.installation_id).slice(0, 66) + : status.installation_id; + Lbryio.call( + 'user', + 'new', + { + auth_token: '', + language: language || 'en', + app_id: appId, + }, + 'post' + ) + .then(response => { + if (!response.auth_token) { + throw new Error('auth_token was not set in the response'); + } + + const { store } = window; + if (Lbryio.overrides.setAuthToken) { + Lbryio.overrides.setAuthToken(response.auth_token); + } + + if (store) { + store.dispatch({ + type: ACTIONS.GENERATE_AUTH_TOKEN_SUCCESS, + data: { authToken: response.auth_token }, + }); + } + Lbryio.authToken = response.auth_token; + return res(response); + }) + .catch(error => rej(error)); + }) + ) + .then(newUser => { + if (!newUser) { + return Lbryio.getCurrentUser(); + } + return newUser; + }); + }) + .then(resolve, reject); + }); + } + + return Lbryio.authenticationPromise; +}; + +Lbryio.getStripeToken = () => + Lbryio.CONNECTION_STRING.startsWith('http://localhost:') + ? 'pk_test_NoL1JWL7i1ipfhVId5KfDZgo' + : 'pk_live_e8M4dRNnCCbmpZzduEUZBgJO'; + +Lbryio.getExchangeRates = () => { + if ( + !Lbryio.exchangeLastFetched || + Date.now() - Lbryio.exchangeLastFetched > EXCHANGE_RATE_TIMEOUT + ) { + Lbryio.exchangePromise = new Promise((resolve, reject) => { + Lbryio.call('lbc', 'exchange_rate', {}, 'get', true) + .then(({ lbc_usd: LBC_USD, lbc_btc: LBC_BTC, btc_usd: BTC_USD }) => { + const rates = { LBC_USD, LBC_BTC, BTC_USD }; + resolve(rates); + }) + .catch(reject); + }); + Lbryio.exchangeLastFetched = Date.now(); + } + return Lbryio.exchangePromise; +}; + +// Allow overriding lbryio methods +// The desktop app will need to use it for getAuthToken because we use electron's ipcRenderer +Lbryio.overrides = {}; +Lbryio.setOverride = (methodName, newMethod) => { + Lbryio.overrides[methodName] = newMethod; +}; + +export default Lbryio; diff --git a/extras/lbryinc/redux/actions/auth.js b/extras/lbryinc/redux/actions/auth.js new file mode 100644 index 000000000..743991a35 --- /dev/null +++ b/extras/lbryinc/redux/actions/auth.js @@ -0,0 +1,38 @@ +import * as ACTIONS from 'constants/action_types'; +import { Lbryio } from 'lbryinc'; + +export function doGenerateAuthToken(installationId) { + return dispatch => { + dispatch({ + type: ACTIONS.GENERATE_AUTH_TOKEN_STARTED, + }); + + Lbryio.call( + 'user', + 'new', + { + auth_token: '', + language: 'en', + app_id: installationId, + }, + 'post' + ) + .then(response => { + if (!response.auth_token) { + dispatch({ + type: ACTIONS.GENERATE_AUTH_TOKEN_FAILURE, + }); + } else { + dispatch({ + type: ACTIONS.GENERATE_AUTH_TOKEN_SUCCESS, + data: { authToken: response.auth_token }, + }); + } + }) + .catch(() => { + dispatch({ + type: ACTIONS.GENERATE_AUTH_TOKEN_FAILURE, + }); + }); + }; +} diff --git a/extras/lbryinc/redux/actions/blacklist.js b/extras/lbryinc/redux/actions/blacklist.js new file mode 100644 index 000000000..4e0008691 --- /dev/null +++ b/extras/lbryinc/redux/actions/blacklist.js @@ -0,0 +1,52 @@ +import { Lbryio } from 'lbryinc'; +import * as ACTIONS from 'constants/action_types'; + +const CHECK_BLACK_LISTED_CONTENT_INTERVAL = 60 * 60 * 1000; + +export function doFetchBlackListedOutpoints() { + return dispatch => { + dispatch({ + type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_STARTED, + }); + + const success = ({ outpoints }) => { + const splitOutpoints = []; + if (outpoints) { + outpoints.forEach((outpoint, index) => { + const [txid, nout] = outpoint.split(':'); + + splitOutpoints[index] = { txid, nout: Number.parseInt(nout, 10) }; + }); + } + + dispatch({ + type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_COMPLETED, + data: { + outpoints: splitOutpoints, + success: true, + }, + }); + }; + + const failure = ({ message: error }) => { + dispatch({ + type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_FAILED, + data: { + error, + success: false, + }, + }); + }; + + Lbryio.call('file', 'list_blocked', { + auth_token: '', + }).then(success, failure); + }; +} + +export function doBlackListedOutpointsSubscribe() { + return dispatch => { + dispatch(doFetchBlackListedOutpoints()); + setInterval(() => dispatch(doFetchBlackListedOutpoints()), CHECK_BLACK_LISTED_CONTENT_INTERVAL); + }; +} diff --git a/extras/lbryinc/redux/actions/cost_info.js b/extras/lbryinc/redux/actions/cost_info.js new file mode 100644 index 000000000..ca92a364b --- /dev/null +++ b/extras/lbryinc/redux/actions/cost_info.js @@ -0,0 +1,35 @@ +import * as ACTIONS from 'constants/action_types'; +import { Lbryio } from 'lbryinc'; +import { selectClaimsByUri } from 'redux/selectors/claims'; + +// eslint-disable-next-line import/prefer-default-export +export function doFetchCostInfoForUri(uri) { + return (dispatch, getState) => { + const state = getState(); + const claim = selectClaimsByUri(state)[uri]; + + if (!claim) return; + + function resolve(costInfo) { + dispatch({ + type: ACTIONS.FETCH_COST_INFO_COMPLETED, + data: { + uri, + costInfo, + }, + }); + } + + const fee = claim.value ? claim.value.fee : undefined; + + if (fee === undefined) { + resolve({ cost: 0, includesData: true }); + } else if (fee.currency === 'LBC') { + resolve({ cost: fee.amount, includesData: true }); + } else { + Lbryio.getExchangeRates().then(({ LBC_USD }) => { + resolve({ cost: fee.amount / LBC_USD, includesData: true }); + }); + } + }; +} diff --git a/extras/lbryinc/redux/actions/filtered.js b/extras/lbryinc/redux/actions/filtered.js new file mode 100644 index 000000000..4777c591d --- /dev/null +++ b/extras/lbryinc/redux/actions/filtered.js @@ -0,0 +1,47 @@ +import { Lbryio } from 'lbryinc'; +import * as ACTIONS from 'constants/action_types'; + +const CHECK_FILTERED_CONTENT_INTERVAL = 60 * 60 * 1000; + +export function doFetchFilteredOutpoints() { + return dispatch => { + dispatch({ + type: ACTIONS.FETCH_FILTERED_CONTENT_STARTED, + }); + + const success = ({ outpoints }) => { + let formattedOutpoints = []; + if (outpoints) { + formattedOutpoints = outpoints.map(outpoint => { + const [txid, nout] = outpoint.split(':'); + return { txid, nout: Number.parseInt(nout, 10) }; + }); + } + + dispatch({ + type: ACTIONS.FETCH_FILTERED_CONTENT_COMPLETED, + data: { + outpoints: formattedOutpoints, + }, + }); + }; + + const failure = ({ error }) => { + dispatch({ + type: ACTIONS.FETCH_FILTERED_CONTENT_FAILED, + data: { + error, + }, + }); + }; + + Lbryio.call('file', 'list_filtered', { auth_token: '' }).then(success, failure); + }; +} + +export function doFilteredOutpointsSubscribe() { + return dispatch => { + dispatch(doFetchFilteredOutpoints()); + setInterval(() => dispatch(doFetchFilteredOutpoints()), CHECK_FILTERED_CONTENT_INTERVAL); + }; +} diff --git a/extras/lbryinc/redux/actions/homepage.js b/extras/lbryinc/redux/actions/homepage.js new file mode 100644 index 000000000..41de4a2bd --- /dev/null +++ b/extras/lbryinc/redux/actions/homepage.js @@ -0,0 +1,79 @@ +import { Lbryio } from 'lbryinc'; +import { batchActions } from 'util/batch-actions'; +import { doResolveUris } from 'util/lbryURI'; +import * as ACTIONS from 'constants/action_types'; + +export function doFetchFeaturedUris(offloadResolve = false) { + return dispatch => { + dispatch({ + type: ACTIONS.FETCH_FEATURED_CONTENT_STARTED, + }); + + const success = ({ Uris }) => { + let urisToResolve = []; + Object.keys(Uris).forEach(category => { + urisToResolve = [...urisToResolve, ...Uris[category]]; + }); + + const actions = [ + { + type: ACTIONS.FETCH_FEATURED_CONTENT_COMPLETED, + data: { + uris: Uris, + success: true, + }, + }, + ]; + if (urisToResolve.length && !offloadResolve) { + actions.push(doResolveUris(urisToResolve)); + } + + dispatch(batchActions(...actions)); + }; + + const failure = () => { + dispatch({ + type: ACTIONS.FETCH_FEATURED_CONTENT_COMPLETED, + data: { + uris: {}, + }, + }); + }; + + Lbryio.call('file', 'list_homepage').then(success, failure); + }; +} + +export function doFetchTrendingUris() { + return dispatch => { + dispatch({ + type: ACTIONS.FETCH_TRENDING_CONTENT_STARTED, + }); + + const success = data => { + const urisToResolve = data.map(uri => uri.url); + const actions = [ + doResolveUris(urisToResolve), + { + type: ACTIONS.FETCH_TRENDING_CONTENT_COMPLETED, + data: { + uris: data, + success: true, + }, + }, + ]; + dispatch(batchActions(...actions)); + }; + + const failure = () => { + dispatch({ + type: ACTIONS.FETCH_TRENDING_CONTENT_COMPLETED, + data: { + uris: [], + }, + }); + }; + + Lbryio.call('file', 'list_trending').then(success, failure); + }; +} diff --git a/extras/lbryinc/redux/actions/stats.js b/extras/lbryinc/redux/actions/stats.js new file mode 100644 index 000000000..9d2a12178 --- /dev/null +++ b/extras/lbryinc/redux/actions/stats.js @@ -0,0 +1,32 @@ +// @flow +import { Lbryio } from 'lbryinc'; +import * as ACTIONS from 'constants/action_types'; + +export const doFetchViewCount = (claimIdCsv: string) => (dispatch: Dispatch) => { + dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_STARTED }); + + return Lbryio.call('file', 'view_count', { claim_id: claimIdCsv }) + .then((result: Array) => { + const viewCounts = result; + dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_COMPLETED, data: { claimIdCsv, viewCounts } }); + }) + .catch(error => { + dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_FAILED, data: error }); + }); +}; + +export const doFetchSubCount = (claimId: string) => (dispatch: Dispatch) => { + dispatch({ type: ACTIONS.FETCH_SUB_COUNT_STARTED }); + + return Lbryio.call('subscription', 'sub_count', { claim_id: claimId }) + .then((result: Array) => { + const subCount = result[0]; + dispatch({ + type: ACTIONS.FETCH_SUB_COUNT_COMPLETED, + data: { claimId, subCount }, + }); + }) + .catch(error => { + dispatch({ type: ACTIONS.FETCH_SUB_COUNT_FAILED, data: error }); + }); +}; diff --git a/extras/lbryinc/redux/actions/sync.js b/extras/lbryinc/redux/actions/sync.js new file mode 100644 index 000000000..9550f3e85 --- /dev/null +++ b/extras/lbryinc/redux/actions/sync.js @@ -0,0 +1,289 @@ +import * as ACTIONS from 'constants/action_types'; +import { Lbryio } from 'lbryinc'; +import Lbry from 'lbry'; +import { doWalletEncrypt, doWalletDecrypt } from 'redux/actions/wallet'; + +const NO_WALLET_ERROR = 'no wallet found for this user'; + +export function doSetDefaultAccount(success, failure) { + return dispatch => { + dispatch({ + type: ACTIONS.SET_DEFAULT_ACCOUNT, + }); + + Lbry.account_list() + .then(accountList => { + const { lbc_mainnet: accounts } = accountList; + let defaultId; + for (let i = 0; i < accounts.length; ++i) { + if (accounts[i].satoshis > 0) { + defaultId = accounts[i].id; + break; + } + } + + // In a case where there's no balance on either account + // assume the second (which is created after sync) as default + if (!defaultId && accounts.length > 1) { + defaultId = accounts[1].id; + } + + // Set the default account + if (defaultId) { + Lbry.account_set({ account_id: defaultId, default: true }) + .then(() => { + if (success) { + success(); + } + }) + .catch(err => { + if (failure) { + failure(err); + } + }); + } else if (failure) { + // no default account to set + failure('Could not set a default account'); // fail + } + }) + .catch(err => { + if (failure) { + failure(err); + } + }); + }; +} + +export function doSetSync(oldHash, newHash, data) { + return dispatch => { + dispatch({ + type: ACTIONS.SET_SYNC_STARTED, + }); + + return Lbryio.call('sync', 'set', { old_hash: oldHash, new_hash: newHash, data }, 'post') + .then(response => { + if (!response.hash) { + throw Error('No hash returned for sync/set.'); + } + + return dispatch({ + type: ACTIONS.SET_SYNC_COMPLETED, + data: { syncHash: response.hash }, + }); + }) + .catch(error => { + dispatch({ + type: ACTIONS.SET_SYNC_FAILED, + data: { error }, + }); + }); + }; +} + +export function doGetSync(passedPassword, callback) { + const password = passedPassword === null || passedPassword === undefined ? '' : passedPassword; + + function handleCallback(error, hasNewData) { + if (callback) { + if (typeof callback !== 'function') { + throw new Error('Second argument passed to "doGetSync" must be a function'); + } + + callback(error, hasNewData); + } + } + + return dispatch => { + dispatch({ + type: ACTIONS.GET_SYNC_STARTED, + }); + + const data = {}; + + Lbry.wallet_status() + .then(status => { + if (status.is_locked) { + return Lbry.wallet_unlock({ password }); + } + + // Wallet is already unlocked + return true; + }) + .then(isUnlocked => { + if (isUnlocked) { + return Lbry.sync_hash(); + } + data.unlockFailed = true; + throw new Error(); + }) + .then(hash => Lbryio.call('sync', 'get', { hash }, 'post')) + .then(response => { + const syncHash = response.hash; + data.syncHash = syncHash; + data.syncData = response.data; + data.changed = response.changed; + data.hasSyncedWallet = true; + + if (response.changed) { + return Lbry.sync_apply({ password, data: response.data, blocking: true }); + } + }) + .then(response => { + if (!response) { + dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data }); + handleCallback(null, data.changed); + return; + } + + const { hash: walletHash, data: walletData } = response; + + if (walletHash !== data.syncHash) { + // different local hash, need to synchronise + dispatch(doSetSync(data.syncHash, walletHash, walletData)); + } + + dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data }); + handleCallback(null, data.changed); + }) + .catch(syncAttemptError => { + if (data.unlockFailed) { + dispatch({ type: ACTIONS.GET_SYNC_FAILED, data: { error: syncAttemptError } }); + + if (password !== '') { + dispatch({ type: ACTIONS.SYNC_APPLY_BAD_PASSWORD }); + } + + handleCallback(syncAttemptError); + } else if (data.hasSyncedWallet) { + const error = + (syncAttemptError && syncAttemptError.message) || 'Error getting synced wallet'; + dispatch({ + type: ACTIONS.GET_SYNC_FAILED, + data: { + error, + }, + }); + + // Temp solution until we have a bad password error code + // Don't fail on blank passwords so we don't show a "password error" message + // before users have ever entered a password + if (password !== '') { + dispatch({ type: ACTIONS.SYNC_APPLY_BAD_PASSWORD }); + } + + handleCallback(error); + } else { + // user doesn't have a synced wallet + dispatch({ + type: ACTIONS.GET_SYNC_COMPLETED, + data: { hasSyncedWallet: false, syncHash: null }, + }); + + // call sync_apply to get data to sync + // first time sync. use any string for old hash + if (syncAttemptError.message === NO_WALLET_ERROR) { + Lbry.sync_apply({ password }) + .then(({ hash: walletHash, data: syncApplyData }) => { + dispatch(doSetSync('', walletHash, syncApplyData, password)); + handleCallback(); + }) + .catch(syncApplyError => { + handleCallback(syncApplyError); + }); + } + } + }); + }; +} + +export function doSyncApply(syncHash, syncData, password) { + return dispatch => { + dispatch({ + type: ACTIONS.SYNC_APPLY_STARTED, + }); + + Lbry.sync_apply({ password, data: syncData }) + .then(({ hash: walletHash, data: walletData }) => { + dispatch({ + type: ACTIONS.SYNC_APPLY_COMPLETED, + }); + + if (walletHash !== syncHash) { + // different local hash, need to synchronise + dispatch(doSetSync(syncHash, walletHash, walletData)); + } + }) + .catch(() => { + dispatch({ + type: ACTIONS.SYNC_APPLY_FAILED, + data: { + error: + 'Invalid password specified. Please enter the password for your previously synchronised wallet.', + }, + }); + }); + }; +} + +export function doCheckSync() { + return dispatch => { + dispatch({ + type: ACTIONS.GET_SYNC_STARTED, + }); + + Lbry.sync_hash().then(hash => { + Lbryio.call('sync', 'get', { hash }, 'post') + .then(response => { + const data = { + hasSyncedWallet: true, + syncHash: response.hash, + syncData: response.data, + hashChanged: response.changed, + }; + dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data }); + }) + .catch(() => { + // user doesn't have a synced wallet + dispatch({ + type: ACTIONS.GET_SYNC_COMPLETED, + data: { hasSyncedWallet: false, syncHash: null }, + }); + }); + }); + }; +} + +export function doResetSync() { + return dispatch => + new Promise(resolve => { + dispatch({ type: ACTIONS.SYNC_RESET }); + resolve(); + }); +} + +export function doSyncEncryptAndDecrypt(oldPassword, newPassword, encrypt) { + return dispatch => { + const data = {}; + return Lbry.sync_hash() + .then(hash => Lbryio.call('sync', 'get', { hash }, 'post')) + .then(syncGetResponse => { + data.oldHash = syncGetResponse.hash; + + return Lbry.sync_apply({ password: oldPassword, data: syncGetResponse.data }); + }) + .then(() => { + if (encrypt) { + dispatch(doWalletEncrypt(newPassword)); + } else { + dispatch(doWalletDecrypt()); + } + }) + .then(() => Lbry.sync_apply({ password: newPassword })) + .then(syncApplyResponse => { + if (syncApplyResponse.hash !== data.oldHash) { + return dispatch(doSetSync(data.oldHash, syncApplyResponse.hash, syncApplyResponse.data)); + } + }) + .catch(console.error); + }; +} diff --git a/extras/lbryinc/redux/actions/web.js b/extras/lbryinc/redux/actions/web.js new file mode 100644 index 000000000..1bcce69a5 --- /dev/null +++ b/extras/lbryinc/redux/actions/web.js @@ -0,0 +1,12 @@ +// @flow +import * as ACTIONS from 'constants/action_types'; + +export const doUpdateUploadProgress = ( + progress: string, + params: { [key: string]: any }, + xhr: any +) => (dispatch: Dispatch) => + dispatch({ + type: ACTIONS.UPDATE_UPLOAD_PROGRESS, + data: { progress, params, xhr }, + }); diff --git a/extras/lbryinc/redux/reducers/auth.js b/extras/lbryinc/redux/reducers/auth.js new file mode 100644 index 000000000..6de9a2c9d --- /dev/null +++ b/extras/lbryinc/redux/reducers/auth.js @@ -0,0 +1,29 @@ +import * as ACTIONS from 'constants/action_types'; + +const reducers = {}; +const defaultState = { + authenticating: false, +}; + +reducers[ACTIONS.GENERATE_AUTH_TOKEN_FAILURE] = state => + Object.assign({}, state, { + authToken: null, + authenticating: false, + }); + +reducers[ACTIONS.GENERATE_AUTH_TOKEN_STARTED] = state => + Object.assign({}, state, { + authenticating: true, + }); + +reducers[ACTIONS.GENERATE_AUTH_TOKEN_SUCCESS] = (state, action) => + Object.assign({}, state, { + authToken: action.data.authToken, + authenticating: false, + }); + +export function authReducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/extras/lbryinc/redux/reducers/blacklist.js b/extras/lbryinc/redux/reducers/blacklist.js new file mode 100644 index 000000000..48d74b1ff --- /dev/null +++ b/extras/lbryinc/redux/reducers/blacklist.js @@ -0,0 +1,37 @@ +import * as ACTIONS from 'constants/action_types'; +import { handleActions } from 'util/redux-utils'; + +const defaultState = { + fetchingBlackListedOutpoints: false, + fetchingBlackListedOutpointsSucceed: undefined, + blackListedOutpoints: undefined, +}; + +export const blacklistReducer = handleActions( + { + [ACTIONS.FETCH_BLACK_LISTED_CONTENT_STARTED]: state => ({ + ...state, + fetchingBlackListedOutpoints: true, + }), + [ACTIONS.FETCH_BLACK_LISTED_CONTENT_COMPLETED]: (state, action) => { + const { outpoints, success } = action.data; + return { + ...state, + fetchingBlackListedOutpoints: false, + fetchingBlackListedOutpointsSucceed: success, + blackListedOutpoints: outpoints, + }; + }, + [ACTIONS.FETCH_BLACK_LISTED_CONTENT_FAILED]: (state, action) => { + const { error, success } = action.data; + + return { + ...state, + fetchingBlackListedOutpoints: false, + fetchingBlackListedOutpointsSucceed: success, + fetchingBlackListedOutpointsError: error, + }; + }, + }, + defaultState +); diff --git a/extras/lbryinc/redux/reducers/cost_info.js b/extras/lbryinc/redux/reducers/cost_info.js new file mode 100644 index 000000000..c6dd6b1b2 --- /dev/null +++ b/extras/lbryinc/redux/reducers/cost_info.js @@ -0,0 +1,38 @@ +import { handleActions } from 'util/redux-utils'; +import * as ACTIONS from 'constants/action_types'; + +const defaultState = { + fetching: {}, + byUri: {}, +}; + +export const costInfoReducer = handleActions( + { + [ACTIONS.FETCH_COST_INFO_STARTED]: (state, action) => { + const { uri } = action.data; + const newFetching = Object.assign({}, state.fetching); + newFetching[uri] = true; + + return { + ...state, + fetching: newFetching, + }; + }, + + [ACTIONS.FETCH_COST_INFO_COMPLETED]: (state, action) => { + const { uri, costInfo } = action.data; + const newByUri = Object.assign({}, state.byUri); + const newFetching = Object.assign({}, state.fetching); + + newByUri[uri] = costInfo; + delete newFetching[uri]; + + return { + ...state, + byUri: newByUri, + fetching: newFetching, + }; + }, + }, + defaultState +); diff --git a/extras/lbryinc/redux/reducers/filtered.js b/extras/lbryinc/redux/reducers/filtered.js new file mode 100644 index 000000000..02147156d --- /dev/null +++ b/extras/lbryinc/redux/reducers/filtered.js @@ -0,0 +1,34 @@ +import * as ACTIONS from 'constants/action_types'; +import { handleActions } from 'util/redux-utils'; + +const defaultState = { + loading: false, + filteredOutpoints: undefined, +}; + +export const filteredReducer = handleActions( + { + [ACTIONS.FETCH_FILTERED_CONTENT_STARTED]: state => ({ + ...state, + loading: true, + }), + [ACTIONS.FETCH_FILTERED_CONTENT_COMPLETED]: (state, action) => { + const { outpoints } = action.data; + return { + ...state, + loading: false, + filteredOutpoints: outpoints, + }; + }, + [ACTIONS.FETCH_FILTERED_CONTENT_FAILED]: (state, action) => { + const { error } = action.data; + + return { + ...state, + loading: false, + fetchingFilteredOutpointsError: error, + }; + }, + }, + defaultState +); diff --git a/extras/lbryinc/redux/reducers/homepage.js b/extras/lbryinc/redux/reducers/homepage.js new file mode 100644 index 000000000..912bf4f32 --- /dev/null +++ b/extras/lbryinc/redux/reducers/homepage.js @@ -0,0 +1,48 @@ +import { handleActions } from 'util/redux-utils'; +import * as ACTIONS from 'constants/action_types'; + +const defaultState = { + fetchingFeaturedContent: false, + fetchingFeaturedContentFailed: false, + featuredUris: undefined, + fetchingTrendingContent: false, + fetchingTrendingContentFailed: false, + trendingUris: undefined, +}; + +export const homepageReducer = handleActions( + { + [ACTIONS.FETCH_FEATURED_CONTENT_STARTED]: state => ({ + ...state, + fetchingFeaturedContent: true, + }), + + [ACTIONS.FETCH_FEATURED_CONTENT_COMPLETED]: (state, action) => { + const { uris, success } = action.data; + + return { + ...state, + fetchingFeaturedContent: false, + fetchingFeaturedContentFailed: !success, + featuredUris: uris, + }; + }, + + [ACTIONS.FETCH_TRENDING_CONTENT_STARTED]: state => ({ + ...state, + fetchingTrendingContent: true, + }), + + [ACTIONS.FETCH_TRENDING_CONTENT_COMPLETED]: (state, action) => { + const { uris, success } = action.data; + + return { + ...state, + fetchingTrendingContent: false, + fetchingTrendingContentFailed: !success, + trendingUris: uris, + }; + }, + }, + defaultState +); diff --git a/extras/lbryinc/redux/reducers/stats.js b/extras/lbryinc/redux/reducers/stats.js new file mode 100644 index 000000000..22fc9ce16 --- /dev/null +++ b/extras/lbryinc/redux/reducers/stats.js @@ -0,0 +1,55 @@ +import { handleActions } from 'util/redux-utils'; +import * as ACTIONS from 'constants/action_types'; + +const defaultState = { + fetchingViewCount: false, + viewCountError: undefined, + viewCountById: {}, + fetchingSubCount: false, + subCountError: undefined, + subCountById: {}, +}; + +export const statsReducer = handleActions( + { + [ACTIONS.FETCH_VIEW_COUNT_STARTED]: state => ({ ...state, fetchingViewCount: true }), + [ACTIONS.FETCH_VIEW_COUNT_FAILED]: (state, action) => ({ + ...state, + viewCountError: action.data, + }), + [ACTIONS.FETCH_VIEW_COUNT_COMPLETED]: (state, action) => { + const { claimIdCsv, viewCounts } = action.data; + + const viewCountById = Object.assign({}, state.viewCountById); + const claimIds = claimIdCsv.split(','); + + if (claimIds.length === viewCounts.length) { + claimIds.forEach((claimId, index) => { + viewCountById[claimId] = viewCounts[index]; + }); + } + + return { + ...state, + fetchingViewCount: false, + viewCountById, + }; + }, + [ACTIONS.FETCH_SUB_COUNT_STARTED]: state => ({ ...state, fetchingSubCount: true }), + [ACTIONS.FETCH_SUB_COUNT_FAILED]: (state, action) => ({ + ...state, + subCountError: action.data, + }), + [ACTIONS.FETCH_SUB_COUNT_COMPLETED]: (state, action) => { + const { claimId, subCount } = action.data; + + const subCountById = { ...state.subCountById, [claimId]: subCount }; + return { + ...state, + fetchingSubCount: false, + subCountById, + }; + }, + }, + defaultState +); diff --git a/extras/lbryinc/redux/reducers/sync.js b/extras/lbryinc/redux/reducers/sync.js new file mode 100644 index 000000000..497e58107 --- /dev/null +++ b/extras/lbryinc/redux/reducers/sync.js @@ -0,0 +1,89 @@ +import * as ACTIONS from 'constants/action_types'; + +const reducers = {}; +const defaultState = { + hasSyncedWallet: false, + syncHash: null, + syncData: null, + setSyncErrorMessage: null, + getSyncErrorMessage: null, + syncApplyErrorMessage: '', + syncApplyIsPending: false, + syncApplyPasswordError: false, + getSyncIsPending: false, + setSyncIsPending: false, + hashChanged: false, +}; + +reducers[ACTIONS.GET_SYNC_STARTED] = state => + Object.assign({}, state, { + getSyncIsPending: true, + getSyncErrorMessage: null, + }); + +reducers[ACTIONS.GET_SYNC_COMPLETED] = (state, action) => + Object.assign({}, state, { + syncHash: action.data.syncHash, + syncData: action.data.syncData, + hasSyncedWallet: action.data.hasSyncedWallet, + getSyncIsPending: false, + hashChanged: action.data.hashChanged, + }); + +reducers[ACTIONS.GET_SYNC_FAILED] = (state, action) => + Object.assign({}, state, { + getSyncIsPending: false, + getSyncErrorMessage: action.data.error, + }); + +reducers[ACTIONS.SET_SYNC_STARTED] = state => + Object.assign({}, state, { + setSyncIsPending: true, + setSyncErrorMessage: null, + }); + +reducers[ACTIONS.SET_SYNC_FAILED] = (state, action) => + Object.assign({}, state, { + setSyncIsPending: false, + setSyncErrorMessage: action.data.error, + }); + +reducers[ACTIONS.SET_SYNC_COMPLETED] = (state, action) => + Object.assign({}, state, { + setSyncIsPending: false, + setSyncErrorMessage: null, + hasSyncedWallet: true, // sync was successful, so the user has a synced wallet at this point + syncHash: action.data.syncHash, + }); + +reducers[ACTIONS.SYNC_APPLY_STARTED] = state => + Object.assign({}, state, { + syncApplyPasswordError: false, + syncApplyIsPending: true, + syncApplyErrorMessage: '', + }); + +reducers[ACTIONS.SYNC_APPLY_COMPLETED] = state => + Object.assign({}, state, { + syncApplyIsPending: false, + syncApplyErrorMessage: '', + }); + +reducers[ACTIONS.SYNC_APPLY_FAILED] = (state, action) => + Object.assign({}, state, { + syncApplyIsPending: false, + syncApplyErrorMessage: action.data.error, + }); + +reducers[ACTIONS.SYNC_APPLY_BAD_PASSWORD] = state => + Object.assign({}, state, { + syncApplyPasswordError: true, + }); + +reducers[ACTIONS.SYNC_RESET] = () => defaultState; + +export function syncReducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/extras/lbryinc/redux/reducers/web.js b/extras/lbryinc/redux/reducers/web.js new file mode 100644 index 000000000..7cb9f674d --- /dev/null +++ b/extras/lbryinc/redux/reducers/web.js @@ -0,0 +1,62 @@ +// @flow +import * as ACTIONS from 'constants/action_types'; + +/* +test mock: + currentUploads: { + 'test#upload': { + progress: 50, + params: { + name: 'steve', + thumbnail_url: 'https://dev2.spee.ch/4/KMNtoSZ009fawGz59VG8PrID.jpeg', + }, + }, + }, + */ + +export type Params = { + channel?: string, + name: string, + thumbnail_url: ?string, + title: ?string, +}; + +export type UploadItem = { + progess: string, + params: Params, + xhr?: any, +}; + +export type TvState = { + currentUploads: { [key: string]: UploadItem }, +}; + +const reducers = {}; + +const defaultState: TvState = { + currentUploads: {}, +}; + +reducers[ACTIONS.UPDATE_UPLOAD_PROGRESS] = (state: TvState, action) => { + const { progress, params, xhr } = action.data; + const key = params.channel ? `${params.name}#${params.channel}` : `${params.name}#anonymous`; + let currentUploads; + if (!progress) { + currentUploads = Object.assign({}, state.currentUploads); + Object.keys(currentUploads).forEach(k => { + if (k === key) { + delete currentUploads[key]; + } + }); + } else { + currentUploads = Object.assign({}, state.currentUploads); + currentUploads[key] = { progress, params, xhr }; + } + return { ...state, currentUploads }; +}; + +export function webReducer(state: TvState = defaultState, action: any) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/extras/lbryinc/redux/selectors/auth.js b/extras/lbryinc/redux/selectors/auth.js new file mode 100644 index 000000000..819ee5286 --- /dev/null +++ b/extras/lbryinc/redux/selectors/auth.js @@ -0,0 +1,7 @@ +import { createSelector } from 'reselect'; + +const selectState = state => state.auth || {}; + +export const selectAuthToken = createSelector(selectState, state => state.authToken); + +export const selectIsAuthenticating = createSelector(selectState, state => state.authenticating); diff --git a/extras/lbryinc/redux/selectors/blacklist.js b/extras/lbryinc/redux/selectors/blacklist.js new file mode 100644 index 000000000..ed0d8ccb1 --- /dev/null +++ b/extras/lbryinc/redux/selectors/blacklist.js @@ -0,0 +1,20 @@ +import { createSelector } from 'reselect'; + +export const selectState = state => state.blacklist || {}; + +export const selectBlackListedOutpoints = createSelector( + selectState, + state => state.blackListedOutpoints +); + +export const selectBlacklistedOutpointMap = createSelector( + selectBlackListedOutpoints, + outpoints => + outpoints + ? outpoints.reduce((acc, val) => { + const outpoint = `${val.txid}:${val.nout}`; + acc[outpoint] = 1; + return acc; + }, {}) + : {} +); diff --git a/extras/lbryinc/redux/selectors/cost_info.js b/extras/lbryinc/redux/selectors/cost_info.js new file mode 100644 index 000000000..b510c7c84 --- /dev/null +++ b/extras/lbryinc/redux/selectors/cost_info.js @@ -0,0 +1,13 @@ +import { createSelector } from 'reselect'; + +export const selectState = state => state.costInfo || {}; + +export const selectAllCostInfoByUri = createSelector(selectState, state => state.byUri || {}); + +export const makeSelectCostInfoForUri = uri => + createSelector(selectAllCostInfoByUri, costInfos => costInfos && costInfos[uri]); + +export const selectFetchingCostInfo = createSelector(selectState, state => state.fetching || {}); + +export const makeSelectFetchingCostInfoForUri = uri => + createSelector(selectFetchingCostInfo, fetchingByUri => fetchingByUri && fetchingByUri[uri]); diff --git a/extras/lbryinc/redux/selectors/filtered.js b/extras/lbryinc/redux/selectors/filtered.js new file mode 100644 index 000000000..46ea2b1fc --- /dev/null +++ b/extras/lbryinc/redux/selectors/filtered.js @@ -0,0 +1,20 @@ +import { createSelector } from 'reselect'; + +export const selectState = state => state.filtered || {}; + +export const selectFilteredOutpoints = createSelector( + selectState, + state => state.filteredOutpoints +); + +export const selectFilteredOutpointMap = createSelector( + selectFilteredOutpoints, + outpoints => + outpoints + ? outpoints.reduce((acc, val) => { + const outpoint = `${val.txid}:${val.nout}`; + acc[outpoint] = 1; + return acc; + }, {}) + : {} +); diff --git a/extras/lbryinc/redux/selectors/homepage.js b/extras/lbryinc/redux/selectors/homepage.js new file mode 100644 index 000000000..710058cbc --- /dev/null +++ b/extras/lbryinc/redux/selectors/homepage.js @@ -0,0 +1,17 @@ +import { createSelector } from 'reselect'; + +const selectState = state => state.homepage || {}; + +export const selectFeaturedUris = createSelector(selectState, state => state.featuredUris); + +export const selectFetchingFeaturedUris = createSelector( + selectState, + state => state.fetchingFeaturedContent +); + +export const selectTrendingUris = createSelector(selectState, state => state.trendingUris); + +export const selectFetchingTrendingUris = createSelector( + selectState, + state => state.fetchingTrendingContent +); diff --git a/extras/lbryinc/redux/selectors/stats.js b/extras/lbryinc/redux/selectors/stats.js new file mode 100644 index 000000000..2b2065285 --- /dev/null +++ b/extras/lbryinc/redux/selectors/stats.js @@ -0,0 +1,20 @@ +import { createSelector } from 'reselect'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; + +const selectState = state => state.stats || {}; +export const selectViewCount = createSelector(selectState, state => state.viewCountById); +export const selectSubCount = createSelector(selectState, state => state.subCountById); + +export const makeSelectViewCountForUri = uri => + createSelector( + makeSelectClaimForUri(uri), + selectViewCount, + (claim, viewCountById) => (claim ? viewCountById[claim.claim_id] || 0 : 0) + ); + +export const makeSelectSubCountForUri = uri => + createSelector( + makeSelectClaimForUri(uri), + selectSubCount, + (claim, subCountById) => (claim ? subCountById[claim.claim_id] || 0 : 0) + ); diff --git a/extras/lbryinc/redux/selectors/sync.js b/extras/lbryinc/redux/selectors/sync.js new file mode 100644 index 000000000..4448c6c49 --- /dev/null +++ b/extras/lbryinc/redux/selectors/sync.js @@ -0,0 +1,40 @@ +import { createSelector } from 'reselect'; + +const selectState = state => state.sync || {}; + +export const selectHasSyncedWallet = createSelector(selectState, state => state.hasSyncedWallet); + +export const selectSyncHash = createSelector(selectState, state => state.syncHash); + +export const selectSyncData = createSelector(selectState, state => state.syncData); + +export const selectSetSyncErrorMessage = createSelector( + selectState, + state => state.setSyncErrorMessage +); + +export const selectGetSyncErrorMessage = createSelector( + selectState, + state => state.getSyncErrorMessage +); + +export const selectGetSyncIsPending = createSelector(selectState, state => state.getSyncIsPending); + +export const selectSetSyncIsPending = createSelector(selectState, state => state.setSyncIsPending); + +export const selectHashChanged = createSelector(selectState, state => state.hashChanged); + +export const selectSyncApplyIsPending = createSelector( + selectState, + state => state.syncApplyIsPending +); + +export const selectSyncApplyErrorMessage = createSelector( + selectState, + state => state.syncApplyErrorMessage +); + +export const selectSyncApplyPasswordError = createSelector( + selectState, + state => state.syncApplyPasswordError +); diff --git a/extras/lbryinc/redux/selectors/web.js b/extras/lbryinc/redux/selectors/web.js new file mode 100644 index 000000000..54ac3ec4e --- /dev/null +++ b/extras/lbryinc/redux/selectors/web.js @@ -0,0 +1,10 @@ +import { createSelector } from 'reselect'; + +const selectState = state => state.web || {}; + +export const selectCurrentUploads = createSelector(selectState, state => state.currentUploads); + +export const selectUploadCount = createSelector( + selectCurrentUploads, + currentUploads => currentUploads && Object.keys(currentUploads).length +); diff --git a/extras/lbryinc/util/redux-utils-delete-me.js b/extras/lbryinc/util/redux-utils-delete-me.js new file mode 100644 index 000000000..bf9c17158 --- /dev/null +++ b/extras/lbryinc/util/redux-utils-delete-me.js @@ -0,0 +1,17 @@ +// util for creating reducers +// based off of redux-actions +// https://redux-actions.js.org/docs/api/handleAction.html#handleactions + +// eslint-disable-next-line import/prefer-default-export +export const handleActions = (actionMap, defaultState) => (state = defaultState, action) => { + const handler = actionMap[action.type]; + + if (handler) { + const newState = handler(state, action); + return Object.assign({}, state, newState); + } + + // just return the original state if no handler + // returning a copy here breaks redux-persist + return state; +}; diff --git a/extras/lbryinc/util/swap-json.js b/extras/lbryinc/util/swap-json.js new file mode 100644 index 000000000..6b52a9a64 --- /dev/null +++ b/extras/lbryinc/util/swap-json.js @@ -0,0 +1,10 @@ +export function swapKeyAndValue(dict) { + const ret = {}; + // eslint-disable-next-line no-restricted-syntax + for (const key in dict) { + if (dict.hasOwnProperty(key)) { + ret[dict[key]] = key; + } + } + return ret; +} diff --git a/extras/lbryinc/util/transifex-upload.js b/extras/lbryinc/util/transifex-upload.js new file mode 100644 index 000000000..94a83a866 --- /dev/null +++ b/extras/lbryinc/util/transifex-upload.js @@ -0,0 +1,78 @@ +const apiBaseUrl = 'https://www.transifex.com/api/2/project'; +const resource = 'app-strings'; + +export function doTransifexUpload(contents, project, token, success, fail) { + const url = `${apiBaseUrl}/${project}/resources/`; + const updateUrl = `${apiBaseUrl}/${project}/resource/${resource}/content/`; + const headers = { + Authorization: `Basic ${Buffer.from(`api:${token}`).toString('base64')}`, + 'Content-Type': 'application/json', + }; + + const req = { + accept_translations: true, + i18n_type: 'KEYVALUEJSON', + name: resource, + slug: resource, + content: contents, + }; + + function handleResponse(text) { + let json; + try { + // transifex api returns Python dicts for some reason. + // Any way to get the api to return valid JSON? + json = JSON.parse(text); + } catch (e) { + // ignore + } + + if (success) { + success(json || text); + } + } + + function handleError(err) { + if (fail) { + fail(err.message ? err.message : 'Could not upload strings resource to Transifex'); + } + } + + // check if the resource exists + fetch(updateUrl, { headers }) + .then(response => response.json()) + .then(() => { + // perform an update + fetch(updateUrl, { + method: 'PUT', + headers, + body: JSON.stringify({ content: contents }), + }) + .then(response => { + if (response.status !== 200 && response.status !== 201) { + throw new Error('failed to update transifex'); + } + + return response.text(); + }) + .then(handleResponse) + .catch(handleError); + }) + .catch(() => { + // resource doesn't exist, create a fresh resource + fetch(url, { + method: 'POST', + headers, + body: JSON.stringify(req), + }) + .then(response => { + if (response.status !== 200 && response.status !== 201) { + throw new Error('failed to upload to transifex'); + } + + return response.text(); + }) + .then(handleResponse) + .catch(handleError); + }); +} diff --git a/extras/recsys/index.js b/extras/recsys/index.js new file mode 100644 index 000000000..26572300d --- /dev/null +++ b/extras/recsys/index.js @@ -0,0 +1,3 @@ +import Recsys from './recsys'; + +export default Recsys; diff --git a/ui/recsys.js b/extras/recsys/recsys.js similarity index 97% rename from ui/recsys.js rename to extras/recsys/recsys.js index 7b020dbf9..a9cd5a2f9 100644 --- a/ui/recsys.js +++ b/extras/recsys/recsys.js @@ -1,10 +1,12 @@ import { selectUser } from 'redux/selectors/user'; import { makeSelectRecommendedRecsysIdForClaimId } from 'redux/selectors/search'; import { v4 as Uuidv4 } from 'uuid'; -import { parseURI, SETTINGS, makeSelectClaimForUri } from 'lbry-redux'; +import { parseURI } from 'util/lbryURI'; +import * as SETTINGS from 'constants/settings'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; import { selectPlayingUri, selectPrimaryUri } from 'redux/selectors/content'; import { makeSelectClientSetting, selectDaemonSettings } from 'redux/selectors/settings'; -import { history } from './store'; +import { history } from 'ui/store'; const recsysEndpoint = 'https://clickstream.odysee.com/log/video/view'; const recsysId = 'lighthouse-v0'; diff --git a/flow-typed/Blocklist.js b/flow-typed/Blocklist.js new file mode 100644 index 000000000..454a7142f --- /dev/null +++ b/flow-typed/Blocklist.js @@ -0,0 +1,10 @@ +declare type BlocklistState = { + blockedChannels: Array +}; + +declare type BlocklistAction = { + type: string, + data: { + uri: string, + }, +}; diff --git a/flow-typed/Claim.js b/flow-typed/Claim.js new file mode 100644 index 000000000..e03eeae81 --- /dev/null +++ b/flow-typed/Claim.js @@ -0,0 +1,214 @@ +// @flow + +declare type Claim = StreamClaim | ChannelClaim | CollectionClaim; + +declare type ChannelClaim = GenericClaim & { + value: ChannelMetadata, +}; + +declare type CollectionClaim = GenericClaim & { + value: CollectionMetadata, +}; + +declare type StreamClaim = GenericClaim & { + value: StreamMetadata, +}; + +declare type GenericClaim = { + address: string, // address associated with tx + amount: string, // bid amount at time of tx + canonical_url: string, // URL with short id, includes channel with short id + claim_id: string, // unique claim identifier + claim_sequence: number, // not being used currently + claim_op: 'create' | 'update', + confirmations: number, + decoded_claim: boolean, // Not available currently https://github.com/lbryio/lbry/issues/2044 + timestamp?: number, // date of last transaction + height: number, // block height the tx was confirmed + is_channel_signature_valid?: boolean, + is_my_output: boolean, + name: string, + normalized_name: string, // `name` normalized via unicode NFD spec, + nout: number, // index number for an output of a tx + permanent_url: string, // name + claim_id + short_url: string, // permanent_url with short id, no channel + txid: string, // unique tx id + type: 'claim' | 'update' | 'support', + value_type: 'stream' | 'channel' | 'collection', + signing_channel?: ChannelClaim, + reposted_claim?: GenericClaim, + repost_channel_url?: string, + repost_url?: string, + repost_bid_amount?: string, + purchase_receipt?: PurchaseReceipt, + meta: { + activation_height: number, + claims_in_channel?: number, + creation_height: number, + creation_timestamp: number, + effective_amount: string, + expiration_height: number, + is_controlling: boolean, + support_amount: string, + reposted: number, + trending_global: number, + trending_group: number, + trending_local: number, + trending_mixed: number, + }, +}; + +declare type GenericMetadata = { + title?: string, + description?: string, + thumbnail?: { + url?: string, + }, + languages?: Array, + tags?: Array, + locations?: Array, +}; + +declare type ChannelMetadata = GenericMetadata & { + public_key: string, + public_key_id: string, + cover_url?: string, + email?: string, + website_url?: string, + featured?: Array, +}; + +declare type CollectionMetadata = GenericMetadata & { + claims: Array, +} + +declare type StreamMetadata = GenericMetadata & { + license?: string, // License "title" ex: Creative Commons, Custom copyright + license_url?: string, // Link to full license + release_time?: number, // linux timestamp + author?: string, + + source: { + sd_hash: string, + media_type?: string, + hash?: string, + name?: string, // file name + size?: number, // size of file in bytes + }, + + // Only exists if a stream has a fee + fee?: Fee, + + stream_type: 'video' | 'audio' | 'image' | 'software', + // Below correspond to `stream_type` + video?: { + duration: number, + height: number, + width: number, + }, + audio?: { + duration: number, + }, + image?: { + height: number, + width: number, + }, + software?: { + os: string, + }, +}; + +declare type Location = { + latitude?: number, + longitude?: number, + country?: string, + state?: string, + city?: string, + code?: string, +}; + +declare type Fee = { + amount: string, + currency: string, + address: string, +}; + +declare type PurchaseReceipt = { + address: string, + amount: string, + claim_id: string, + confirmations: number, + height: number, + nout: number, + timestamp: number, + txid: string, + type: 'purchase', +}; + +declare type ClaimActionResolveInfo = { + [string]: { + stream: ?StreamClaim, + channel: ?ChannelClaim, + claimsInChannel: ?number, + collection: ?CollectionClaim, + }, +} + +declare type ChannelUpdateParams = { + claim_id: string, + bid?: string, + title?: string, + cover_url?: string, + thumbnail_url?: string, + description?: string, + website_url?: string, + email?: string, + tags?: Array, + replace?: boolean, + languages?: Array, + locations?: Array, + blocking?: boolean, +} + +declare type ChannelPublishParams = { + name: string, + bid: string, + blocking?: true, + title?: string, + cover_url?: string, + thumbnail_url?: string, + description?: string, + website_url?: string, + email?: string, + tags?: Array, + languages?: Array, +} + +declare type CollectionUpdateParams = { + claim_id: string, + claim_ids?: Array, + bid?: string, + title?: string, + cover_url?: string, + thumbnail_url?: string, + description?: string, + website_url?: string, + email?: string, + tags?: Array, + replace?: boolean, + languages?: Array, + locations?: Array, + blocking?: boolean, +} + +declare type CollectionPublishParams = { + name: string, + bid: string, + claim_ids: Array, + blocking?: true, + title?: string, + thumbnail_url?: string, + description?: string, + tags?: Array, + languages?: Array, +} diff --git a/flow-typed/CoinSwap.js b/flow-typed/CoinSwap.js new file mode 100644 index 000000000..a41c57196 --- /dev/null +++ b/flow-typed/CoinSwap.js @@ -0,0 +1,29 @@ +declare type CoinSwapInfo = { + chargeCode: string, + coins: Array, + sendAddresses: { [string]: string}, + sendAmounts: { [string]: any }, + lbcAmount: number, + status?: { + status: string, + receiptCurrency: string, + receiptTxid: string, + lbcTxid: string, + }, +} + +declare type CoinSwapState = { + coinSwaps: Array, +}; + +declare type CoinSwapAddAction = { + type: string, + data: CoinSwapInfo, +}; + +declare type CoinSwapRemoveAction = { + type: string, + data: { + chargeCode: string, + }, +}; diff --git a/flow-typed/Collections.js b/flow-typed/Collections.js new file mode 100644 index 000000000..f70825a4f --- /dev/null +++ b/flow-typed/Collections.js @@ -0,0 +1,34 @@ +declare type Collection = { + id: string, + items: Array, + name: string, + type: string, + updatedAt: number, + totalItems?: number, + sourceId?: string, // if copied, claimId of original collection +}; + +declare type CollectionState = { + unpublished: CollectionGroup, + resolved: CollectionGroup, + pending: CollectionGroup, + edited: CollectionGroup, + builtin: CollectionGroup, + saved: Array, + isResolvingCollectionById: { [string]: boolean }, + error?: string | null, +}; + +declare type CollectionGroup = { + [string]: Collection, +} + +declare type CollectionEditParams = { + claims?: Array, + remove?: boolean, + claimIds?: Array, + replace?: boolean, + order?: { from: number, to: number }, + type?: string, + name?: string, +} diff --git a/flow-typed/Comment.js b/flow-typed/Comment.js index 57a666683..c851d297d 100644 --- a/flow-typed/Comment.js +++ b/flow-typed/Comment.js @@ -177,7 +177,7 @@ declare type CommentCreateParams = { claim_id: string, parent_id?: string, signature: string, - signing_ts: number, + signing_ts: string, support_tx_id?: string, }; diff --git a/flow-typed/Lbry.js b/flow-typed/Lbry.js new file mode 100644 index 000000000..f913e15cc --- /dev/null +++ b/flow-typed/Lbry.js @@ -0,0 +1,369 @@ +// @flow +declare type StatusResponse = { + blob_manager: { + finished_blobs: number, + }, + blockchain_headers: { + download_progress: number, + downloading_headers: boolean, + }, + dht: { + node_id: string, + peers_in_routing_table: number, + }, + hash_announcer: { + announce_queue_size: number, + }, + installation_id: string, + is_running: boolean, + skipped_components: Array, + startup_status: { + blob_manager: boolean, + blockchain_headers: boolean, + database: boolean, + dht: boolean, + exchange_rate_manager: boolean, + hash_announcer: boolean, + peer_protocol_server: boolean, + stream_manager: boolean, + upnp: boolean, + wallet: boolean, + }, + stream_manager: { + managed_files: number, + }, + upnp: { + aioupnp_version: string, + dht_redirect_set: boolean, + external_ip: string, + gateway: string, + peer_redirect_set: boolean, + redirects: {}, + }, + wallet: ?{ + connected: string, + best_blockhash: string, + blocks: number, + blocks_behind: number, + is_encrypted: boolean, + is_locked: boolean, + headers_synchronization_progress: number, + available_servers: number, + }, +}; + +declare type VersionResponse = { + build: string, + lbrynet_version: string, + os_release: string, + os_system: string, + platform: string, + processor: string, + python_version: string, +}; + +declare type BalanceResponse = { + available: string, + reserved: string, + reserved_subtotals: ?{ + claims: string, + supports: string, + tips: string, + }, + total: string, +}; + +declare type ResolveResponse = { + // Keys are the url(s) passed to resolve + [string]: { error?: {}, stream?: StreamClaim, channel?: ChannelClaim, collection?: CollectionClaim, claimsInChannel?: number }, +}; + +declare type GetResponse = FileListItem & { error?: string }; + +declare type GenericTxResponse = { + height: number, + hex: string, + inputs: Array<{}>, + outputs: Array<{}>, + total_fee: string, + total_input: string, + total_output: string, + txid: string, +}; + +declare type PublishResponse = GenericTxResponse & { + // Only first value in outputs is a claim + // That's the only value we care about + outputs: Array, +}; + +declare type ClaimSearchResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type ClaimListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type ChannelCreateResponse = GenericTxResponse & { + outputs: Array, +}; + +declare type ChannelUpdateResponse = GenericTxResponse & { + outputs: Array, +}; + +declare type CommentCreateResponse = Comment; +declare type CommentUpdateResponse = Comment; + +declare type MyReactions = { + // Keys are the commentId + [string]: Array, +}; + +declare type OthersReactions = { + // Keys are the commentId + [string]: { + // Keys are the reaction_type, e.g. 'like' + [string]: number, + }, +}; + +declare type CommentReactListResponse = { + my_reactions: Array, + others_reactions: Array, +}; + +declare type CommentHideResponse = { + // keyed by the CommentIds entered + [string]: { hidden: boolean }, +}; + +declare type CommentPinResponse = { + // keyed by the CommentIds entered + items: Comment, +}; + +declare type CommentAbandonResponse = { + // keyed by the CommentId given + abandoned: boolean, +}; + +declare type ChannelListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type ChannelSignResponse = { + signature: string, + signing_ts: string, +}; + +declare type CollectionCreateResponse = { + outputs: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +} + +declare type CollectionListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type CollectionResolveResponse = { + items: Array, + total_items: number, +}; + +declare type CollectionResolveOptions = { + claim_id: string, +}; + +declare type CollectionListOptions = { + page: number, + page_size: number, + resolve?: boolean, +}; + +declare type FileListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type TxListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type SupportListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type BlobListResponse = { items: Array }; + +declare type WalletListResponse = Array<{ + id: string, + name: string, +}>; + +declare type WalletStatusResponse = { + is_encrypted: boolean, + is_locked: boolean, + is_syncing: boolean, +}; + +declare type SyncApplyResponse = { + hash: string, + data: string, +}; + +declare type SupportAbandonResponse = GenericTxResponse; + +declare type StreamListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type StreamRepostOptions = { + name: string, + bid: string, + claim_id: string, + channel_id?: string, +}; + +declare type StreamRepostResponse = GenericTxResponse; + +declare type PurchaseListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type PurchaseListOptions = { + page: number, + page_size: number, + resolve: boolean, + claim_id?: string, + channel_id?: string, +}; + +// +// Types used in the generic Lbry object that is exported +// +declare type LbryTypes = { + isConnected: boolean, + connectPromise: any, // null | + connect: () => any, // void | Promise ? + daemonConnectionString: string, + alternateConnectionString: string, + methodsUsingAlternateConnectionString: Array, + apiRequestHeaders: { [key: string]: string }, + setDaemonConnectionString: string => void, + setApiHeader: (string, string) => void, + unsetApiHeader: string => void, + overrides: { [string]: ?Function }, + setOverride: (string, Function) => void, + // getMediaType: (?string, ?string) => string, + + // Lbry Methods + stop: () => Promise, + status: () => Promise, + version: () => Promise, + resolve: (params: {}) => Promise, + get: (params: {}) => Promise, + publish: (params: {}) => Promise, + + claim_search: (params: {}) => Promise, + claim_list: (params: {}) => Promise, + channel_create: (params: {}) => Promise, + channel_update: (params: {}) => Promise, + channel_import: (params: {}) => Promise, + channel_list: (params: {}) => Promise, + channel_sign: (params: {}) => Promise, + stream_abandon: (params: {}) => Promise, + stream_list: (params: {}) => Promise, + channel_abandon: (params: {}) => Promise, + support_create: (params: {}) => Promise, + support_list: (params: {}) => Promise, + support_abandon: (params: {}) => Promise, + stream_repost: (params: StreamRepostOptions) => Promise, + purchase_list: (params: PurchaseListOptions) => Promise, + collection_resolve: (params: CollectionResolveOptions) => Promise, + collection_list: (params: CollectionListOptions) => Promise, + collection_create: (params: {}) => Promise, + collection_update: (params: {}) => Promise, + + // File fetching and manipulation + file_list: (params: {}) => Promise, + file_delete: (params: {}) => Promise, + blob_delete: (params: {}) => Promise, + blob_list: (params: {}) => Promise, + file_set_status: (params: {}) => Promise, + file_reflect: (params: {}) => Promise, + + // Preferences + preference_get: (params?: {}) => Promise, + preference_set: (params: {}) => Promise, + + // Commenting + comment_update: (params: {}) => Promise, + comment_hide: (params: {}) => Promise, + comment_abandon: (params: {}) => Promise, + comment_list: (params: {}) => Promise, + comment_create: (params: {}) => Promise, + + // Wallet utilities + wallet_balance: (params: {}) => Promise, + wallet_decrypt: (prams: {}) => Promise, + wallet_encrypt: (params: {}) => Promise, + wallet_unlock: (params: {}) => Promise, + wallet_list: (params: {}) => Promise, + wallet_send: (params: {}) => Promise, + wallet_status: (params?: {}) => Promise, + address_is_mine: (params: {}) => Promise, + address_unused: (params: {}) => Promise, // New address + address_list: (params: {}) => Promise, + transaction_list: (params: {}) => Promise, + txo_list: (params: {}) => Promise, + account_set: (params: {}) => Promise, + account_list: (params?: {}) => Promise, + + // Sync + sync_hash: (params?: {}) => Promise, + sync_apply: (params: {}) => Promise, + // syncGet + + // The app shouldn't need to do this + utxo_release: () => Promise, +}; diff --git a/flow-typed/LbryFirst.js b/flow-typed/LbryFirst.js new file mode 100644 index 000000000..e0954a496 --- /dev/null +++ b/flow-typed/LbryFirst.js @@ -0,0 +1,99 @@ +// @flow +declare type LbryFirstStatusResponse = { + Version: string, + Message: string, + Running: boolean, + Commit: string, +}; + +declare type LbryFirstVersionResponse = { + build: string, + lbrynet_version: string, + os_release: string, + os_system: string, + platform: string, + processor: string, + python_version: string, +}; +/* SAMPLE UPLOAD RESPONSE (FULL) +"Video": { + "etag": "\"Dn5xIderbhAnUk5TAW0qkFFir0M/xlGLrlTox7VFTRcR8F77RbKtaU4\"", + "id": "8InjtdvVmwE", + "kind": "youtube#video", + "snippet": { + "categoryId": "22", + "channelId": "UCXiVsGTU88fJjheB2rqF0rA", + "channelTitle": "Mark Beamer", + "liveBroadcastContent": "none", + "localized": { + "title": "my title" + }, + "publishedAt": "2020-05-05T04:17:53.000Z", + "thumbnails": { + "default": { + "height": 90, + "url": "https://i9.ytimg.com/vi/8InjtdvVmwE/default.jpg?sqp=CMTQw_UF&rs=AOn4CLB6dlhZMSMrazDlWRsitPgCsn8fVw", + "width": 120 + }, + "high": { + "height": 360, + "url": "https://i9.ytimg.com/vi/8InjtdvVmwE/hqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLB-Je_7l6qvASRAR_bSGWZHaXaJWQ", + "width": 480 + }, + "medium": { + "height": 180, + "url": "https://i9.ytimg.com/vi/8InjtdvVmwE/mqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLCvSnDLqVznRNMKuvJ_0misY_chPQ", + "width": 320 + } + }, + "title": "my title" + }, + "status": { + "embeddable": true, + "license": "youtube", + "privacyStatus": "private", + "publicStatsViewable": true, + "uploadStatus": "uploaded" + } + } + */ +declare type UploadResponse = { + Video: { + id: string, + snippet: { + channelId: string, + }, + status: { + uploadStatus: string, + }, + }, +}; + +declare type HasYTAuthResponse = { + HashAuth: boolean, +}; + +declare type YTSignupResponse = {}; + +// +// Types used in the generic LbryFirst object that is exported +// +declare type LbryFirstTypes = { + isConnected: boolean, + connectPromise: ?Promise, + connect: () => void, + lbryFirstConnectionString: string, + apiRequestHeaders: { [key: string]: string }, + setApiHeader: (string, string) => void, + unsetApiHeader: string => void, + overrides: { [string]: ?Function }, + setOverride: (string, Function) => void, + + // LbryFirst Methods + stop: () => Promise, + status: () => Promise, + version: () => Promise, + upload: any => Promise, + hasYTAuth: string => Promise, + ytSignup: () => Promise, +}; diff --git a/flow-typed/Notification.js b/flow-typed/Notification.js new file mode 100644 index 000000000..f0f63a1a9 --- /dev/null +++ b/flow-typed/Notification.js @@ -0,0 +1,93 @@ +// @flow +import * as ACTIONS from 'constants/action_types'; + +/* + Toasts: + - First-in, first-out queue + - Simple messages that are shown in response to user interactions + - Never saved + - If they are the result of errors, use the isError flag when creating + - For errors that should interrupt user behavior, use Error +*/ +declare type ToastParams = { + message: string, + title?: string, + linkText?: string, + linkTarget?: string, + isError?: boolean, +}; + +declare type Toast = { + id: string, + params: ToastParams, +}; + +declare type DoToast = { + type: ACTIONS.CREATE_TOAST, + data: Toast, +}; + +/* + Notifications: + - List of notifications based on user interactions/app notifications + - Always saved, but can be manually deleted + - Can happen in the background, or because of user interaction (ex: publish confirmed) +*/ +declare type Notification = { + id: string, // Unique id + dateCreated: number, + isRead: boolean, // Used to display "new" notifications that a user hasn't seen yet + source?: string, // The type/area an notification is from. Used for sorting (ex: publishes, transactions) + // We may want to use priority/isDismissed in the future to specify how urgent a notification is + // and if the user should see it immediately + // isDissmied: boolean, + // priority?: number +}; + +declare type DoNotification = { + type: ACTIONS.CREATE_NOTIFICATION, + data: Notification, +}; + +declare type DoEditNotification = { + type: ACTIONS.EDIT_NOTIFICATION, + data: { + notification: Notification, + }, +}; + +declare type DoDeleteNotification = { + type: ACTIONS.DELETE_NOTIFICATION, + data: { + id: string, // The id to delete + }, +}; + +/* + Errors: + - First-in, first-out queue + - Errors that should interupt user behavior + - For errors that can be shown without interrupting a user, use Toast with the isError flag +*/ +declare type ErrorNotification = { + title: string, + text: string, +}; + +declare type DoError = { + type: ACTIONS.CREATE_ERROR, + data: ErrorNotification, +}; + +declare type DoDismissError = { + type: ACTIONS.DISMISS_ERROR, +}; + +/* + NotificationState +*/ +declare type NotificationState = { + notifications: Array, + errors: Array, + toasts: Array, +}; diff --git a/flow-typed/Publish.js b/flow-typed/Publish.js new file mode 100644 index 000000000..87955ae24 --- /dev/null +++ b/flow-typed/Publish.js @@ -0,0 +1,27 @@ +// @flow + +declare type UpdatePublishFormData = { + filePath?: string, + contentIsFree?: boolean, + fee?: { + amount: string, + currency: string, + }, + title?: string, + thumbnail_url?: string, + uploadThumbnailStatus?: string, + thumbnailPath?: string, + description?: string, + language?: string, + channel?: string, + channelId?: string, + name?: string, + nameError?: string, + bid?: string, + bidError?: string, + otherLicenseDescription?: string, + licenseUrl?: string, + licenseType?: string, + uri?: string, + nsfw: boolean, +}; diff --git a/flow-typed/Redux.js b/flow-typed/Redux.js new file mode 100644 index 000000000..addce023d --- /dev/null +++ b/flow-typed/Redux.js @@ -0,0 +1,6 @@ +// @flow +/* eslint-disable no-use-before-define */ +declare type GetState = () => any; +declare type ThunkAction = (dispatch: Dispatch, getState: GetState) => any; +declare type Dispatch = (action: {} | Promise<*> | Array<{}> | ThunkAction) => any; // Need to refer to ThunkAction +/* eslint-enable */ diff --git a/flow-typed/Reflector.js b/flow-typed/Reflector.js new file mode 100644 index 000000000..fc6be97c1 --- /dev/null +++ b/flow-typed/Reflector.js @@ -0,0 +1,5 @@ +declare type ReflectingUpdate = { + fileListItem: FileListItem, + progress: number | boolean, + stalled: boolean, +}; diff --git a/flow-typed/Tags.js b/flow-typed/Tags.js new file mode 100644 index 000000000..01c58ac10 --- /dev/null +++ b/flow-typed/Tags.js @@ -0,0 +1,21 @@ +declare type TagState = { + followedTags: FollowedTags, + knownTags: KnownTags, +}; + +declare type Tag = { + name: string, +}; + +declare type KnownTags = { + [string]: Tag, +}; + +declare type FollowedTags = Array; + +declare type TagAction = { + type: string, + data: { + name: string, + }, +}; diff --git a/flow-typed/Transaction.js b/flow-typed/Transaction.js new file mode 100644 index 000000000..db9e592fb --- /dev/null +++ b/flow-typed/Transaction.js @@ -0,0 +1,28 @@ +// @flow +declare type Transaction = { + amount: number, + claim_id: string, + claim_name: string, + fee: number, + nout: number, + txid: string, + type: string, + date: Date, +}; + +declare type Support = { + address: string, + amount: string, + claim_id: string, + confirmations: number, + height: string, + is_change: string, + is_mine: string, + name: string, + normalized_name: string, + nout: string, + permanent_url: string, + timestamp: number, + txid: string, + type: string, +}; diff --git a/flow-typed/Txo.js b/flow-typed/Txo.js new file mode 100644 index 000000000..c4ab9b6bc --- /dev/null +++ b/flow-typed/Txo.js @@ -0,0 +1,27 @@ +declare type Txo = { + amount: number, + claim_id: string, + normalized_name: string, + nout: number, + txid: string, + type: string, + value_type: string, + timestamp: number, + is_my_output: boolean, + is_my_input: boolean, + is_spent: boolean, + signing_channel?: { + channel_id: string, + }, +}; + +declare type TxoListParams = { + page: number, + page_size: number, + type: string, + is_my_input?: boolean, + is_my_output?: boolean, + is_not_my_input?: boolean, + is_not_my_output?: boolean, + is_spent?: boolean, +}; diff --git a/flow-typed/i18n.js b/flow-typed/i18n.js new file mode 100644 index 000000000..de97c0c86 --- /dev/null +++ b/flow-typed/i18n.js @@ -0,0 +1,2 @@ +// @flow +declare function __(a: string, b?: {}): string; diff --git a/flow-typed/lbryURI.js b/flow-typed/lbryURI.js new file mode 100644 index 000000000..549c99106 --- /dev/null +++ b/flow-typed/lbryURI.js @@ -0,0 +1,21 @@ +// @flow +declare type LbryUrlObj = { + // Path and channel will always exist when calling parseURI + // But they may not exist when code calls buildURI + isChannel?: boolean, + path?: string, + streamName?: string, + streamClaimId?: string, + channelName?: string, + channelClaimId?: string, + primaryClaimSequence?: number, + secondaryClaimSequence?: number, + primaryBidPosition?: number, + secondaryBidPosition?: number, + startTime?: number, + + // Below are considered deprecated and should not be used due to unreliableness with claim.canonical_url + claimName?: string, + claimId?: string, + contentName?: string, +}; diff --git a/flow-typed/publish.js b/flow-typed/publish.js index ca11a6a5d..f237b12c9 100644 --- a/flow-typed/publish.js +++ b/flow-typed/publish.js @@ -25,7 +25,7 @@ declare type UpdatePublishFormData = { licenseType?: string, uri?: string, nsfw: boolean, - isMarkdownPost: boolean, + isMarkdownPost?: boolean, }; declare type PublishParams = { diff --git a/package.json b/package.json index 8c6fe2b01..2b63197ae 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,8 @@ "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": { + "@ungap/from-entries": "^0.2.1", + "proxy-polyfill": "0.1.6", "auto-launch": "^5.0.5", "electron-dl": "^1.11.0", "electron-log": "^2.2.12", @@ -157,8 +159,6 @@ "imagesloaded": "^4.1.4", "json-loader": "^0.5.4", "lbry-format": "https://github.com/lbryio/lbry-format.git", - "lbry-redux": "lbryio/lbry-redux#0f930c4a7bfc7f164e6b3c6044050c1bc73f6ab8", - "lbryinc": "lbryio/lbryinc#0b4e41ef90d6347819dd3453f2f9398a5c1b4f36", "lint-staged": "^7.0.2", "localforage": "^1.7.1", "lodash-es": "^4.17.14", diff --git a/ui/component/abandonedChannelPreview/view.jsx b/ui/component/abandonedChannelPreview/view.jsx index 1fc67c6bd..f02afb3d1 100644 --- a/ui/component/abandonedChannelPreview/view.jsx +++ b/ui/component/abandonedChannelPreview/view.jsx @@ -2,7 +2,7 @@ import React from 'react'; import classnames from 'classnames'; import ChannelThumbnail from 'component/channelThumbnail'; -import { parseURI } from 'lbry-redux'; +import { parseURI } from 'util/lbryURI'; import ChannelBlockButton from 'component/channelBlockButton'; import ChannelMuteButton from 'component/channelMuteButton'; import SubscribeButton from 'component/subscribeButton'; diff --git a/ui/component/app/index.js b/ui/component/app/index.js index 1e3da5da0..457b1886f 100644 --- a/ui/component/app/index.js +++ b/ui/component/app/index.js @@ -5,13 +5,9 @@ import { selectGetSyncErrorMessage, selectSyncFatalError } from 'redux/selectors import { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user'; import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectUnclaimedRewards } from 'redux/selectors/rewards'; -import { - doFetchChannelListMine, - doFetchCollectionListMine, - SETTINGS, - selectMyChannelUrls, - doResolveUris, -} from 'lbry-redux'; +import { doFetchChannelListMine, doFetchCollectionListMine, doResolveUris } from 'redux/actions/claims'; +import { selectMyChannelUrls } from 'redux/selectors/claims'; +import * as SETTINGS from 'constants/settings'; import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { makeSelectClientSetting, diff --git a/ui/component/app/view.jsx b/ui/component/app/view.jsx index 2d90ab3f6..77b4a36e5 100644 --- a/ui/component/app/view.jsx +++ b/ui/component/app/view.jsx @@ -4,7 +4,7 @@ import React, { useEffect, useRef, useState, useLayoutEffect } from 'react'; import { lazyImport } from 'util/lazyImport'; import classnames from 'classnames'; import analytics from 'analytics'; -import { buildURI, parseURI } from 'lbry-redux'; +import { buildURI, parseURI } from 'util/lbryURI'; import { SIMPLE_SITE } from 'config'; import Router from 'component/router/index'; import ReactModal from 'react-modal'; @@ -330,15 +330,15 @@ function App(props: Props) { // Load IMA3 SDK for aniview // @if TARGET='web' useEffect(() => { - const script = document.createElement('script'); - script.src = imaLibraryPath; - script.async = true; + const script = document.createElement('script'); + script.src = imaLibraryPath; + script.async = true; + // $FlowFixMe + document.body.appendChild(script); + return () => { // $FlowFixMe - document.body.appendChild(script); - return () => { - // $FlowFixMe - document.body.removeChild(script); - }; + document.body.removeChild(script); + }; }); // @endif diff --git a/ui/component/autoplayCountdown/index.js b/ui/component/autoplayCountdown/index.js index 8793048ea..7cdccf686 100644 --- a/ui/component/autoplayCountdown/index.js +++ b/ui/component/autoplayCountdown/index.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { makeSelectClaimForUri } from 'lbry-redux'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; import { withRouter } from 'react-router'; import AutoplayCountdown from './view'; import { selectModal } from 'redux/selectors/app'; diff --git a/ui/component/channelAbout/index.js b/ui/component/channelAbout/index.js index 40678c8c7..5770ce7ea 100644 --- a/ui/component/channelAbout/index.js +++ b/ui/component/channelAbout/index.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { makeSelectMetadataItemForUri, makeSelectClaimForUri } from 'lbry-redux'; +import { makeSelectMetadataItemForUri, makeSelectClaimForUri } from 'redux/selectors/claims'; import ChannelAbout from './view'; const select = (state, props) => ({ diff --git a/ui/component/channelBlockButton/index.js b/ui/component/channelBlockButton/index.js index 50ca09e1e..9852404d7 100644 --- a/ui/component/channelBlockButton/index.js +++ b/ui/component/channelBlockButton/index.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { makeSelectClaimIdForUri } from 'lbry-redux'; +import { makeSelectClaimIdForUri } from 'redux/selectors/claims'; import { doCommentModUnBlock, doCommentModBlock, diff --git a/ui/component/channelContent/index.js b/ui/component/channelContent/index.js index 8d3bbc6fb..fc1a349f0 100644 --- a/ui/component/channelContent/index.js +++ b/ui/component/channelContent/index.js @@ -6,9 +6,9 @@ import { makeSelectClaimIsMine, makeSelectTotalPagesInChannelSearch, makeSelectClaimForUri, - doResolveUris, - SETTINGS, -} from 'lbry-redux'; +} from 'redux/selectors/claims'; +import { doResolveUris } from 'redux/actions/claims'; +import * as SETTINGS from 'constants/settings'; import { makeSelectChannelIsMuted } from 'redux/selectors/blocked'; import { withRouter } from 'react-router'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; diff --git a/ui/component/channelDiscussion/index.js b/ui/component/channelDiscussion/index.js index ff198701f..e0f04c8cc 100644 --- a/ui/component/channelDiscussion/index.js +++ b/ui/component/channelDiscussion/index.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux'; import { withRouter } from 'react-router'; import { DISABLE_COMMENTS_TAG } from 'constants/tags'; import ChannelDiscussion from './view'; -import { makeSelectTagInClaimOrChannelForUri } from 'lbry-redux'; +import { makeSelectTagInClaimOrChannelForUri } from 'redux/selectors/claims'; const select = (state, props) => { const { search } = props.location; diff --git a/ui/component/channelEdit/index.js b/ui/component/channelEdit/index.js index 9a195039b..43e054f7e 100644 --- a/ui/component/channelEdit/index.js +++ b/ui/component/channelEdit/index.js @@ -4,17 +4,15 @@ import { makeSelectThumbnailForUri, makeSelectCoverForUri, makeSelectMetadataItemForUri, - doUpdateChannel, - doCreateChannel, makeSelectAmountForUri, makeSelectClaimForUri, selectUpdateChannelError, selectUpdatingChannel, selectCreateChannelError, selectCreatingChannel, - selectBalance, - doClearChannelErrors, -} from 'lbry-redux'; +} from 'redux/selectors/claims'; +import { selectBalance } from 'redux/selectors/wallet'; +import { doUpdateChannel, doCreateChannel, doClearChannelErrors } from 'redux/actions/claims'; import { doOpenModal } from 'redux/actions/app'; import { doUpdateBlockListForPublishedChannel } from 'redux/actions/comments'; import { doClaimInitialRewards } from 'redux/actions/rewards'; diff --git a/ui/component/channelEdit/view.jsx b/ui/component/channelEdit/view.jsx index 3e84e40ff..c32c584f8 100644 --- a/ui/component/channelEdit/view.jsx +++ b/ui/component/channelEdit/view.jsx @@ -9,7 +9,7 @@ import TagsSearch from 'component/tagsSearch'; import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field'; import ErrorText from 'component/common/error-text'; import ChannelThumbnail from 'component/channelThumbnail'; -import { isNameValid, parseURI } from 'lbry-redux'; +import { isNameValid, parseURI } from 'util/lbryURI'; import ClaimAbandonButton from 'component/claimAbandonButton'; import { useHistory } from 'react-router-dom'; import { MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR, ESTIMATED_FEE } from 'constants/claim'; @@ -253,7 +253,7 @@ function ChannelForm(props: Props) { let nameError; if (!name && name !== undefined) { nameError = __('A name is required for your url'); - } else if (!isNameValid(name, false)) { + } else if (!isNameValid(name)) { nameError = INVALID_NAME_ERROR; } diff --git a/ui/component/channelMentionSuggestion/index.js b/ui/component/channelMentionSuggestion/index.js index 40b1a92ae..afc418ce7 100644 --- a/ui/component/channelMentionSuggestion/index.js +++ b/ui/component/channelMentionSuggestion/index.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { makeSelectClaimForUri, makeSelectIsUriResolving } from 'lbry-redux'; +import { makeSelectClaimForUri, makeSelectIsUriResolving } from 'redux/selectors/claims'; import ChannelMentionSuggestion from './view'; const select = (state, props) => ({ diff --git a/ui/component/channelMentionSuggestions/index.js b/ui/component/channelMentionSuggestions/index.js index c5ba9712d..d6b4af95b 100644 --- a/ui/component/channelMentionSuggestions/index.js +++ b/ui/component/channelMentionSuggestions/index.js @@ -2,7 +2,8 @@ import { connect } from 'react-redux'; import { selectShowMatureContent } from 'redux/selectors/settings'; import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { withRouter } from 'react-router'; -import { doResolveUris, makeSelectClaimForUri } from 'lbry-redux'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; +import { doResolveUris } from 'redux/actions/claims'; import { makeSelectTopLevelCommentsForUri } from 'redux/selectors/comments'; import ChannelMentionSuggestions from './view'; diff --git a/ui/component/channelMentionSuggestions/view.jsx b/ui/component/channelMentionSuggestions/view.jsx index 9529ba717..d3fefd1f9 100644 --- a/ui/component/channelMentionSuggestions/view.jsx +++ b/ui/component/channelMentionSuggestions/view.jsx @@ -1,7 +1,7 @@ // @flow import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList } from '@reach/combobox'; import { Form } from 'component/common/form'; -import { parseURI, regexInvalidURI } from 'lbry-redux'; +import { parseURI, regexInvalidURI } from 'util/lbryURI'; import { SEARCH_OPTIONS } from 'constants/search'; import * as KEYCODES from 'constants/keycodes'; import ChannelMentionSuggestion from 'component/channelMentionSuggestion'; @@ -69,8 +69,9 @@ export default function ChannelMentionSuggestions(props: Props) { const allShownCanonical = [canonicalCreator, ...canonicalSubscriptions, ...canonicalCommentors]; const possibleMatches = allShownUris.filter((uri) => { try { + // yuck a try catch in a filter? const { channelName } = parseURI(uri); - return channelName.toLowerCase().includes(termToMatch); + return channelName && channelName.toLowerCase().includes(termToMatch); } catch (e) {} }); diff --git a/ui/component/channelMentionTopSuggestion/index.js b/ui/component/channelMentionTopSuggestion/index.js index d3a1d55b5..54d1f6786 100644 --- a/ui/component/channelMentionTopSuggestion/index.js +++ b/ui/component/channelMentionTopSuggestion/index.js @@ -1,5 +1,6 @@ import { connect } from 'react-redux'; -import { makeSelectIsUriResolving, doResolveUri } from 'lbry-redux'; +import { makeSelectIsUriResolving } from 'redux/selectors/claims'; +import { doResolveUri } from 'redux/actions/claims'; import { makeSelectWinningUriForQuery } from 'redux/selectors/search'; import ChannelMentionTopSuggestion from './view'; diff --git a/ui/component/channelSelector/index.js b/ui/component/channelSelector/index.js index f56a21d63..3407bce27 100644 --- a/ui/component/channelSelector/index.js +++ b/ui/component/channelSelector/index.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { selectMyChannelClaims } from 'lbry-redux'; +import { selectMyChannelClaims } from 'redux/selectors/claims'; import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app'; import { doSetActiveChannel, doSetIncognito } from 'redux/actions/app'; import ChannelSelector from './view'; diff --git a/ui/component/channelStakedIndicator/index.js b/ui/component/channelStakedIndicator/index.js index 987683f17..d217012a4 100644 --- a/ui/component/channelStakedIndicator/index.js +++ b/ui/component/channelStakedIndicator/index.js @@ -3,7 +3,7 @@ import { makeSelectClaimForUri, makeSelectStakedLevelForChannelUri, makeSelectTotalStakedAmountForChannelUri, -} from 'lbry-redux'; +} from 'redux/selectors/claims'; import ChannelStakedIndicator from './view'; const select = (state, props) => ({ diff --git a/ui/component/channelThumbnail/index.js b/ui/component/channelThumbnail/index.js index 8f99d916c..ebedaff1a 100644 --- a/ui/component/channelThumbnail/index.js +++ b/ui/component/channelThumbnail/index.js @@ -1,5 +1,6 @@ import { connect } from 'react-redux'; -import { makeSelectThumbnailForUri, doResolveUri, makeSelectClaimForUri, makeSelectIsUriResolving } from 'lbry-redux'; +import { makeSelectThumbnailForUri, makeSelectClaimForUri, makeSelectIsUriResolving } from 'redux/selectors/claims'; +import { doResolveUri } from 'redux/actions/claims'; import ChannelThumbnail from './view'; const select = (state, props) => ({ diff --git a/ui/component/channelThumbnail/view.jsx b/ui/component/channelThumbnail/view.jsx index 572a36028..c5bc3dd02 100644 --- a/ui/component/channelThumbnail/view.jsx +++ b/ui/component/channelThumbnail/view.jsx @@ -1,6 +1,6 @@ // @flow import React from 'react'; -import { parseURI } from 'lbry-redux'; +import { parseURI } from 'util/lbryURI'; import classnames from 'classnames'; import Gerbil from './gerbil.png'; import FreezeframeWrapper from 'component/fileThumbnail/FreezeframeWrapper'; @@ -10,7 +10,7 @@ import { AVATAR_DEFAULT } from 'config'; type Props = { thumbnail: ?string, - uri: ?string, + uri: string, className?: string, thumbnailPreview: ?string, obscure?: boolean, diff --git a/ui/component/channelTitle/index.js b/ui/component/channelTitle/index.js index 502068855..d98e83683 100644 --- a/ui/component/channelTitle/index.js +++ b/ui/component/channelTitle/index.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { makeSelectClaimForUri, makeSelectTitleForUri } from 'lbry-redux'; +import { makeSelectClaimForUri, makeSelectTitleForUri } from 'redux/selectors/claims'; import ChannelTitle from './view'; const select = (state, props) => ({ diff --git a/ui/component/claimAbandonButton/index.js b/ui/component/claimAbandonButton/index.js index e32042d7e..8f706b679 100644 --- a/ui/component/claimAbandonButton/index.js +++ b/ui/component/claimAbandonButton/index.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux'; import { doOpenModal } from 'redux/actions/app'; import ClaimAbandonButton from './view'; -import { makeSelectClaimForUri } from 'lbry-redux'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; const select = (state, props) => ({ claim: props.uri && makeSelectClaimForUri(props.uri)(state), diff --git a/ui/component/claimAuthor/index.js b/ui/component/claimAuthor/index.js index 7119e395b..736bff290 100644 --- a/ui/component/claimAuthor/index.js +++ b/ui/component/claimAuthor/index.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { makeSelectChannelForClaimUri } from 'lbry-redux'; +import { makeSelectChannelForClaimUri } from 'redux/selectors/claims'; import ClaimAuthor from './view'; const select = (state, props) => ({ diff --git a/ui/component/claimCollectionAdd/index.js b/ui/component/claimCollectionAdd/index.js index 896af365b..e1a43261b 100644 --- a/ui/component/claimCollectionAdd/index.js +++ b/ui/component/claimCollectionAdd/index.js @@ -2,12 +2,12 @@ import { connect } from 'react-redux'; import ClaimCollectionAdd from './view'; import { withRouter } from 'react-router'; import { - makeSelectClaimForUri, - doLocalCollectionCreate, selectBuiltinCollections, selectMyPublishedCollections, selectMyUnpublishedCollections, -} from 'lbry-redux'; +} from 'redux/selectors/collections'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; +import { doLocalCollectionCreate } from 'redux/actions/collections'; const select = (state, props) => ({ claim: makeSelectClaimForUri(props.uri)(state), diff --git a/ui/component/claimCollectionAddButton/index.js b/ui/component/claimCollectionAddButton/index.js index 57e05a763..fabd9421a 100644 --- a/ui/component/claimCollectionAddButton/index.js +++ b/ui/component/claimCollectionAddButton/index.js @@ -1,7 +1,8 @@ import { connect } from 'react-redux'; import { doOpenModal } from 'redux/actions/app'; import CollectionAddButton from './view'; -import { makeSelectClaimForUri, makeSelectClaimUrlInCollection } from 'lbry-redux'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; +import { makeSelectClaimUrlInCollection } from 'redux/selectors/collections'; const select = (state, props) => { const claim = makeSelectClaimForUri(props.uri)(state); diff --git a/ui/component/claimEffectiveAmount/index.js b/ui/component/claimEffectiveAmount/index.js index 7601e0eca..0325d9e17 100644 --- a/ui/component/claimEffectiveAmount/index.js +++ b/ui/component/claimEffectiveAmount/index.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { makeSelectClaimForUri } from 'lbry-redux'; +import { makeSelectClaimForUri } from 'redux/selectors/claims'; import ClaimEffectiveAmount from './view'; const select = (state, props) => ({ diff --git a/ui/component/claimInsufficientCredits/index.js b/ui/component/claimInsufficientCredits/index.js index 1b65245ac..3a0230ea4 100644 --- a/ui/component/claimInsufficientCredits/index.js +++ b/ui/component/claimInsufficientCredits/index.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import { makeSelectInsufficientCreditsForUri } from 'redux/selectors/content'; -import { makeSelectClaimWasPurchased } from 'lbry-redux'; +import { makeSelectClaimWasPurchased } from 'redux/selectors/claims'; import ClaimInsufficientCredits from './view'; const select = (state, props) => ({ diff --git a/ui/component/claimLink/index.js b/ui/component/claimLink/index.js index 6aaed830a..ea4ab51b3 100644 --- a/ui/component/claimLink/index.js +++ b/ui/component/claimLink/index.js @@ -1,5 +1,6 @@ import { connect } from 'react-redux'; -import { doResolveUri, makeSelectClaimForUri, makeSelectIsUriResolving } from 'lbry-redux'; +import { makeSelectClaimForUri, makeSelectIsUriResolving } from 'redux/selectors/claims'; +import { doResolveUri } from 'redux/actions/claims'; import { doSetPlayingUri } from 'redux/actions/content'; import { punctuationMarks } from 'util/remark-lbry'; import { selectBlackListedOutpoints } from 'lbryinc'; diff --git a/ui/component/claimList/index.js b/ui/component/claimList/index.js index d9d20509c..086af2607 100644 --- a/ui/component/claimList/index.js +++ b/ui/component/claimList/index.js @@ -1,6 +1,7 @@ import { connect } from 'react-redux'; import ClaimList from './view'; -import { SETTINGS } from 'lbry-redux'; +import * as SETTINGS from 'constants/settings'; + import { makeSelectClientSetting } from 'redux/selectors/settings'; const select = (state) => ({ diff --git a/ui/component/claimListDiscover/index.js b/ui/component/claimListDiscover/index.js index f1d93ca49..eac963d15 100644 --- a/ui/component/claimListDiscover/index.js +++ b/ui/component/claimListDiscover/index.js @@ -1,12 +1,12 @@ import { connect } from 'react-redux'; import { - doClaimSearch, selectClaimsByUri, selectClaimSearchByQuery, selectClaimSearchByQueryLastPageReached, selectFetchingClaimSearch, - SETTINGS, -} from 'lbry-redux'; +} from 'redux/selectors/claims'; +import { doClaimSearch } from 'redux/actions/claims'; +import * as SETTINGS from 'constants/settings'; import { selectFollowedTags } from 'redux/selectors/tags'; import { selectMutedChannels } from 'redux/selectors/blocked'; import { doToggleTagFollowDesktop } from 'redux/actions/tags'; diff --git a/ui/component/claimListDiscover/view.jsx b/ui/component/claimListDiscover/view.jsx index c244b4206..90e66d24e 100644 --- a/ui/component/claimListDiscover/view.jsx +++ b/ui/component/claimListDiscover/view.jsx @@ -5,7 +5,9 @@ import * as CS from 'constants/claim_search'; import React from 'react'; import usePersistedState from 'effects/use-persisted-state'; import { withRouter } from 'react-router'; -import { createNormalizedClaimSearchKey, MATURE_TAGS, splitBySeparator } from 'lbry-redux'; +import { MATURE_TAGS } from 'constants/tags'; +import { createNormalizedClaimSearchKey } from 'util/claim'; +import { splitBySeparator } from 'util/lbryURI'; import Button from 'component/button'; import moment from 'moment'; import ClaimList from 'component/claimList'; @@ -450,7 +452,9 @@ function ClaimListDiscover(props: Props) {

, + contact_support: ( +