'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } require('proxy-polyfill'); var reselect = require('reselect'); var uuid = _interopDefault(require('uuid/v4')); const MINIMUM_PUBLISH_BID = 0.00000001; const CHANNEL_ANONYMOUS = 'anonymous'; const CHANNEL_NEW = 'new'; const PAGE_SIZE = 20; var claim = /*#__PURE__*/Object.freeze({ MINIMUM_PUBLISH_BID: MINIMUM_PUBLISH_BID, CHANNEL_ANONYMOUS: CHANNEL_ANONYMOUS, CHANNEL_NEW: CHANNEL_NEW, PAGE_SIZE: PAGE_SIZE }); const WINDOW_FOCUSED = 'WINDOW_FOCUSED'; const DAEMON_READY = 'DAEMON_READY'; const DAEMON_VERSION_MATCH = 'DAEMON_VERSION_MATCH'; const DAEMON_VERSION_MISMATCH = 'DAEMON_VERSION_MISMATCH'; const VOLUME_CHANGED = 'VOLUME_CHANGED'; // Navigation const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH'; const WINDOW_SCROLLED = 'WINDOW_SCROLLED'; const HISTORY_NAVIGATE = 'HISTORY_NAVIGATE'; // Upgrades const UPGRADE_CANCELLED = 'UPGRADE_CANCELLED'; const DOWNLOAD_UPGRADE = 'DOWNLOAD_UPGRADE'; const UPGRADE_DOWNLOAD_STARTED = 'UPGRADE_DOWNLOAD_STARTED'; const UPGRADE_DOWNLOAD_COMPLETED = 'UPGRADE_DOWNLOAD_COMPLETED'; const UPGRADE_DOWNLOAD_PROGRESSED = 'UPGRADE_DOWNLOAD_PROGRESSED'; const CHECK_UPGRADE_AVAILABLE = 'CHECK_UPGRADE_AVAILABLE'; const CHECK_UPGRADE_START = 'CHECK_UPGRADE_START'; const CHECK_UPGRADE_SUCCESS = 'CHECK_UPGRADE_SUCCESS'; const CHECK_UPGRADE_FAIL = 'CHECK_UPGRADE_FAIL'; const CHECK_UPGRADE_SUBSCRIBE = 'CHECK_UPGRADE_SUBSCRIBE'; const UPDATE_VERSION = 'UPDATE_VERSION'; const UPDATE_REMOTE_VERSION = 'UPDATE_REMOTE_VERSION'; const SKIP_UPGRADE = 'SKIP_UPGRADE'; const START_UPGRADE = 'START_UPGRADE'; const AUTO_UPDATE_DECLINED = 'AUTO_UPDATE_DECLINED'; const AUTO_UPDATE_DOWNLOADED = 'AUTO_UPDATE_DOWNLOADED'; const CLEAR_UPGRADE_TIMER = 'CLEAR_UPGRADE_TIMER'; // Wallet const GET_NEW_ADDRESS_STARTED = 'GET_NEW_ADDRESS_STARTED'; const GET_NEW_ADDRESS_COMPLETED = 'GET_NEW_ADDRESS_COMPLETED'; const FETCH_TRANSACTIONS_STARTED = 'FETCH_TRANSACTIONS_STARTED'; const FETCH_TRANSACTIONS_COMPLETED = 'FETCH_TRANSACTIONS_COMPLETED'; const FETCH_SUPPORTS_STARTED = 'FETCH_SUPPORTS_STARTED'; const FETCH_SUPPORTS_COMPLETED = 'FETCH_SUPPORTS_COMPLETED'; const ABANDON_SUPPORT_STARTED = 'ABANDON_SUPPORT_STARTED'; const ABANDON_SUPPORT_COMPLETED = 'ABANDON_SUPPORT_COMPLETED'; const UPDATE_BALANCE = 'UPDATE_BALANCE'; const UPDATE_TOTAL_BALANCE = 'UPDATE_TOTAL_BALANCE'; const CHECK_ADDRESS_IS_MINE_STARTED = 'CHECK_ADDRESS_IS_MINE_STARTED'; const CHECK_ADDRESS_IS_MINE_COMPLETED = 'CHECK_ADDRESS_IS_MINE_COMPLETED'; const SEND_TRANSACTION_STARTED = 'SEND_TRANSACTION_STARTED'; const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED'; const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED'; const SUPPORT_TRANSACTION_STARTED = 'SUPPORT_TRANSACTION_STARTED'; const SUPPORT_TRANSACTION_COMPLETED = 'SUPPORT_TRANSACTION_COMPLETED'; const SUPPORT_TRANSACTION_FAILED = 'SUPPORT_TRANSACTION_FAILED'; const WALLET_ENCRYPT_START = 'WALLET_ENCRYPT_START'; const WALLET_ENCRYPT_COMPLETED = 'WALLET_ENCRYPT_COMPLETED'; const WALLET_ENCRYPT_FAILED = 'WALLET_ENCRYPT_FAILED'; const WALLET_UNLOCK_START = 'WALLET_UNLOCK_START'; const WALLET_UNLOCK_COMPLETED = 'WALLET_UNLOCK_COMPLETED'; const WALLET_UNLOCK_FAILED = 'WALLET_UNLOCK_FAILED'; const WALLET_DECRYPT_START = 'WALLET_DECRYPT_START'; const WALLET_DECRYPT_COMPLETED = 'WALLET_DECRYPT_COMPLETED'; const WALLET_DECRYPT_FAILED = 'WALLET_DECRYPT_FAILED'; const WALLET_LOCK_START = 'WALLET_LOCK_START'; const WALLET_LOCK_COMPLETED = 'WALLET_LOCK_COMPLETED'; const WALLET_LOCK_FAILED = 'WALLET_LOCK_FAILED'; const WALLET_STATUS_START = 'WALLET_STATUS_START'; const WALLET_STATUS_COMPLETED = 'WALLET_STATUS_COMPLETED'; const SET_TRANSACTION_LIST_FILTER = 'SET_TRANSACTION_LIST_FILTER'; const UPDATE_CURRENT_HEIGHT = 'UPDATE_CURRENT_HEIGHT'; const SET_DRAFT_TRANSACTION_AMOUNT = 'SET_DRAFT_TRANSACTION_AMOUNT'; const SET_DRAFT_TRANSACTION_ADDRESS = 'SET_DRAFT_TRANSACTION_ADDRESS'; // Claims const RESOLVE_URIS_STARTED = 'RESOLVE_URIS_STARTED'; const RESOLVE_URIS_COMPLETED = 'RESOLVE_URIS_COMPLETED'; const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED'; const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED'; const FETCH_CLAIM_LIST_MINE_STARTED = 'FETCH_CLAIM_LIST_MINE_STARTED'; const FETCH_CLAIM_LIST_MINE_COMPLETED = 'FETCH_CLAIM_LIST_MINE_COMPLETED'; const ABANDON_CLAIM_STARTED = 'ABANDON_CLAIM_STARTED'; const ABANDON_CLAIM_SUCCEEDED = 'ABANDON_CLAIM_SUCCEEDED'; const FETCH_CHANNEL_LIST_STARTED = 'FETCH_CHANNEL_LIST_STARTED'; const FETCH_CHANNEL_LIST_COMPLETED = 'FETCH_CHANNEL_LIST_COMPLETED'; const CREATE_CHANNEL_STARTED = 'CREATE_CHANNEL_STARTED'; const CREATE_CHANNEL_COMPLETED = 'CREATE_CHANNEL_COMPLETED'; const CREATE_CHANNEL_FAILED = 'CREATE_CHANNEL_FAILED'; const PUBLISH_STARTED = 'PUBLISH_STARTED'; const PUBLISH_COMPLETED = 'PUBLISH_COMPLETED'; const PUBLISH_FAILED = 'PUBLISH_FAILED'; const SET_PLAYING_URI = 'SET_PLAYING_URI'; const SET_CONTENT_POSITION = 'SET_CONTENT_POSITION'; const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED'; const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI'; const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL'; const CLAIM_SEARCH_STARTED = 'CLAIM_SEARCH_STARTED'; const CLAIM_SEARCH_COMPLETED = 'CLAIM_SEARCH_COMPLETED'; const CLAIM_SEARCH_FAILED = 'CLAIM_SEARCH_FAILED'; // Comments const COMMENT_LIST_STARTED = 'COMMENT_LIST_STARTED'; const COMMENT_LIST_COMPLETED = 'COMMENT_LIST_COMPLETED'; const COMMENT_LIST_FAILED = 'COMMENT_LIST_FAILED'; const COMMENT_CREATE_STARTED = 'COMMENT_CREATE_STARTED'; const COMMENT_CREATE_COMPLETED = 'COMMENT_CREATE_COMPLETED'; const COMMENT_CREATE_FAILED = 'COMMENT_CREATE_FAILED'; // Files const FILE_LIST_STARTED = 'FILE_LIST_STARTED'; const FILE_LIST_SUCCEEDED = 'FILE_LIST_SUCCEEDED'; const FETCH_FILE_INFO_STARTED = 'FETCH_FILE_INFO_STARTED'; const FETCH_FILE_INFO_COMPLETED = 'FETCH_FILE_INFO_COMPLETED'; const LOADING_VIDEO_STARTED = 'LOADING_VIDEO_STARTED'; const LOADING_VIDEO_COMPLETED = 'LOADING_VIDEO_COMPLETED'; const LOADING_VIDEO_FAILED = 'LOADING_VIDEO_FAILED'; const DOWNLOADING_STARTED = 'DOWNLOADING_STARTED'; const DOWNLOADING_PROGRESSED = 'DOWNLOADING_PROGRESSED'; const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED'; const DOWNLOADING_CANCELED = 'DOWNLOADING_CANCELED'; const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED'; const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED'; const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED'; const FILE_DELETE = 'FILE_DELETE'; const SET_FILE_LIST_SORT = 'SET_FILE_LIST_SORT'; const PURCHASE_URI_STARTED = 'PURCHASE_URI_STARTED'; const PURCHASE_URI_COMPLETED = 'PURCHASE_URI_COMPLETED'; const PURCHASE_URI_FAILED = 'PURCHASE_URI_FAILED'; const DELETE_PURCHASED_URI = 'DELETE_PURCHASED_URI'; const LOADING_FILE_STARTED = 'LOADING_FILE_STARTED'; const LOADING_FILE_COMPLETED = 'LOADING_FILE_COMPLETED'; const LOADING_FILE_FAILED = 'LOADING_FILE_FAILED'; // Search const SEARCH_START = 'SEARCH_START'; const SEARCH_SUCCESS = 'SEARCH_SUCCESS'; const SEARCH_FAIL = 'SEARCH_FAIL'; const UPDATE_SEARCH_QUERY = 'UPDATE_SEARCH_QUERY'; const UPDATE_SEARCH_OPTIONS = 'UPDATE_SEARCH_OPTIONS'; const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS'; const SEARCH_FOCUS = 'SEARCH_FOCUS'; const SEARCH_BLUR = 'SEARCH_BLUR'; // Settings const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED'; const CLIENT_SETTING_CHANGED = 'CLIENT_SETTING_CHANGED'; const UPDATE_IS_NIGHT = 'UPDATE_IS_NIGHT'; // User const AUTHENTICATION_STARTED = 'AUTHENTICATION_STARTED'; const AUTHENTICATION_SUCCESS = 'AUTHENTICATION_SUCCESS'; const AUTHENTICATION_FAILURE = 'AUTHENTICATION_FAILURE'; const USER_EMAIL_DECLINE = 'USER_EMAIL_DECLINE'; const USER_EMAIL_NEW_STARTED = 'USER_EMAIL_NEW_STARTED'; const USER_EMAIL_NEW_SUCCESS = 'USER_EMAIL_NEW_SUCCESS'; const USER_EMAIL_NEW_EXISTS = 'USER_EMAIL_NEW_EXISTS'; const USER_EMAIL_NEW_FAILURE = 'USER_EMAIL_NEW_FAILURE'; const USER_EMAIL_VERIFY_SET = 'USER_EMAIL_VERIFY_SET'; const USER_EMAIL_VERIFY_STARTED = 'USER_EMAIL_VERIFY_STARTED'; const USER_EMAIL_VERIFY_SUCCESS = 'USER_EMAIL_VERIFY_SUCCESS'; const USER_EMAIL_VERIFY_FAILURE = 'USER_EMAIL_VERIFY_FAILURE'; const USER_EMAIL_VERIFY_RETRY = 'USER_EMAIL_VERIFY_RETRY'; const USER_PHONE_RESET = 'USER_PHONE_RESET'; const USER_PHONE_NEW_STARTED = 'USER_PHONE_NEW_STARTED'; const USER_PHONE_NEW_SUCCESS = 'USER_PHONE_NEW_SUCCESS'; const USER_PHONE_NEW_FAILURE = 'USER_PHONE_NEW_FAILURE'; const USER_PHONE_VERIFY_STARTED = 'USER_PHONE_VERIFY_STARTED'; const USER_PHONE_VERIFY_SUCCESS = 'USER_PHONE_VERIFY_SUCCESS'; const USER_PHONE_VERIFY_FAILURE = 'USER_PHONE_VERIFY_FAILURE'; const USER_IDENTITY_VERIFY_STARTED = 'USER_IDENTITY_VERIFY_STARTED'; const USER_IDENTITY_VERIFY_SUCCESS = 'USER_IDENTITY_VERIFY_SUCCESS'; const USER_IDENTITY_VERIFY_FAILURE = 'USER_IDENTITY_VERIFY_FAILURE'; const USER_FETCH_STARTED = 'USER_FETCH_STARTED'; const USER_FETCH_SUCCESS = 'USER_FETCH_SUCCESS'; const USER_FETCH_FAILURE = 'USER_FETCH_FAILURE'; const USER_INVITE_STATUS_FETCH_STARTED = 'USER_INVITE_STATUS_FETCH_STARTED'; const USER_INVITE_STATUS_FETCH_SUCCESS = 'USER_INVITE_STATUS_FETCH_SUCCESS'; const USER_INVITE_STATUS_FETCH_FAILURE = 'USER_INVITE_STATUS_FETCH_FAILURE'; const USER_INVITE_NEW_STARTED = 'USER_INVITE_NEW_STARTED'; const USER_INVITE_NEW_SUCCESS = 'USER_INVITE_NEW_SUCCESS'; const USER_INVITE_NEW_FAILURE = 'USER_INVITE_NEW_FAILURE'; const FETCH_ACCESS_TOKEN_SUCCESS = 'FETCH_ACCESS_TOKEN_SUCCESS'; // Rewards const FETCH_REWARDS_STARTED = 'FETCH_REWARDS_STARTED'; const FETCH_REWARDS_COMPLETED = 'FETCH_REWARDS_COMPLETED'; const CLAIM_REWARD_STARTED = 'CLAIM_REWARD_STARTED'; const CLAIM_REWARD_SUCCESS = 'CLAIM_REWARD_SUCCESS'; const CLAIM_REWARD_FAILURE = 'CLAIM_REWARD_FAILURE'; const CLAIM_REWARD_CLEAR_ERROR = 'CLAIM_REWARD_CLEAR_ERROR'; const FETCH_REWARD_CONTENT_COMPLETED = 'FETCH_REWARD_CONTENT_COMPLETED'; // Language const DOWNLOAD_LANGUAGE_SUCCEEDED = 'DOWNLOAD_LANGUAGE_SUCCEEDED'; const DOWNLOAD_LANGUAGE_FAILED = 'DOWNLOAD_LANGUAGE_FAILED'; // Subscriptions const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE'; const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE'; const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS'; const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST'; const SET_SUBSCRIPTION_NOTIFICATION = 'SET_SUBSCRIPTION_NOTIFICATION'; const SET_SUBSCRIPTION_NOTIFICATIONS = 'SET_SUBSCRIPTION_NOTIFICATIONS'; const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED'; const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED'; const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE'; // Publishing const CLEAR_PUBLISH = 'CLEAR_PUBLISH'; const UPDATE_PUBLISH_FORM = 'UPDATE_PUBLISH_FORM'; const PUBLISH_START = 'PUBLISH_START'; const PUBLISH_SUCCESS = 'PUBLISH_SUCCESS'; const PUBLISH_FAIL = 'PUBLISH_FAIL'; const CLEAR_PUBLISH_ERROR = 'CLEAR_PUBLISH_ERROR'; const REMOVE_PENDING_PUBLISH = 'REMOVE_PENDING_PUBLISH'; const DO_PREPARE_EDIT = 'DO_PREPARE_EDIT'; // Notifications const CREATE_NOTIFICATION = 'CREATE_NOTIFICATION'; const EDIT_NOTIFICATION = 'EDIT_NOTIFICATION'; const DELETE_NOTIFICATION = 'DELETE_NOTIFICATION'; const DISMISS_NOTIFICATION = 'DISMISS_NOTIFICATION'; const CREATE_TOAST = 'CREATE_TOAST'; const DISMISS_TOAST = 'DISMISS_TOAST'; const CREATE_ERROR = 'CREATE_ERROR'; const DISMISS_ERROR = 'DISMISS_ERROR'; const FETCH_DATE = 'FETCH_DATE'; // Cost info const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED'; const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED'; const FETCH_COST_INFO_FAILED = 'FETCH_COST_INFO_FAILED'; // Tags const TOGGLE_TAG_FOLLOW = 'TOGGLE_TAG_FOLLOW'; const TAG_ADD = 'TAG_ADD'; const TAG_DELETE = 'TAG_DELETE'; var action_types = /*#__PURE__*/Object.freeze({ WINDOW_FOCUSED: WINDOW_FOCUSED, DAEMON_READY: DAEMON_READY, DAEMON_VERSION_MATCH: DAEMON_VERSION_MATCH, DAEMON_VERSION_MISMATCH: DAEMON_VERSION_MISMATCH, VOLUME_CHANGED: VOLUME_CHANGED, CHANGE_AFTER_AUTH_PATH: CHANGE_AFTER_AUTH_PATH, WINDOW_SCROLLED: WINDOW_SCROLLED, HISTORY_NAVIGATE: HISTORY_NAVIGATE, UPGRADE_CANCELLED: UPGRADE_CANCELLED, DOWNLOAD_UPGRADE: DOWNLOAD_UPGRADE, UPGRADE_DOWNLOAD_STARTED: UPGRADE_DOWNLOAD_STARTED, UPGRADE_DOWNLOAD_COMPLETED: UPGRADE_DOWNLOAD_COMPLETED, UPGRADE_DOWNLOAD_PROGRESSED: UPGRADE_DOWNLOAD_PROGRESSED, CHECK_UPGRADE_AVAILABLE: CHECK_UPGRADE_AVAILABLE, CHECK_UPGRADE_START: CHECK_UPGRADE_START, CHECK_UPGRADE_SUCCESS: CHECK_UPGRADE_SUCCESS, CHECK_UPGRADE_FAIL: CHECK_UPGRADE_FAIL, CHECK_UPGRADE_SUBSCRIBE: CHECK_UPGRADE_SUBSCRIBE, UPDATE_VERSION: UPDATE_VERSION, UPDATE_REMOTE_VERSION: UPDATE_REMOTE_VERSION, SKIP_UPGRADE: SKIP_UPGRADE, START_UPGRADE: START_UPGRADE, AUTO_UPDATE_DECLINED: AUTO_UPDATE_DECLINED, AUTO_UPDATE_DOWNLOADED: AUTO_UPDATE_DOWNLOADED, CLEAR_UPGRADE_TIMER: CLEAR_UPGRADE_TIMER, GET_NEW_ADDRESS_STARTED: GET_NEW_ADDRESS_STARTED, GET_NEW_ADDRESS_COMPLETED: GET_NEW_ADDRESS_COMPLETED, FETCH_TRANSACTIONS_STARTED: FETCH_TRANSACTIONS_STARTED, FETCH_TRANSACTIONS_COMPLETED: FETCH_TRANSACTIONS_COMPLETED, FETCH_SUPPORTS_STARTED: FETCH_SUPPORTS_STARTED, FETCH_SUPPORTS_COMPLETED: FETCH_SUPPORTS_COMPLETED, ABANDON_SUPPORT_STARTED: ABANDON_SUPPORT_STARTED, ABANDON_SUPPORT_COMPLETED: ABANDON_SUPPORT_COMPLETED, UPDATE_BALANCE: UPDATE_BALANCE, UPDATE_TOTAL_BALANCE: UPDATE_TOTAL_BALANCE, CHECK_ADDRESS_IS_MINE_STARTED: CHECK_ADDRESS_IS_MINE_STARTED, CHECK_ADDRESS_IS_MINE_COMPLETED: CHECK_ADDRESS_IS_MINE_COMPLETED, SEND_TRANSACTION_STARTED: SEND_TRANSACTION_STARTED, SEND_TRANSACTION_COMPLETED: SEND_TRANSACTION_COMPLETED, SEND_TRANSACTION_FAILED: SEND_TRANSACTION_FAILED, SUPPORT_TRANSACTION_STARTED: SUPPORT_TRANSACTION_STARTED, SUPPORT_TRANSACTION_COMPLETED: SUPPORT_TRANSACTION_COMPLETED, SUPPORT_TRANSACTION_FAILED: SUPPORT_TRANSACTION_FAILED, WALLET_ENCRYPT_START: WALLET_ENCRYPT_START, WALLET_ENCRYPT_COMPLETED: WALLET_ENCRYPT_COMPLETED, WALLET_ENCRYPT_FAILED: WALLET_ENCRYPT_FAILED, WALLET_UNLOCK_START: WALLET_UNLOCK_START, WALLET_UNLOCK_COMPLETED: WALLET_UNLOCK_COMPLETED, WALLET_UNLOCK_FAILED: WALLET_UNLOCK_FAILED, WALLET_DECRYPT_START: WALLET_DECRYPT_START, WALLET_DECRYPT_COMPLETED: WALLET_DECRYPT_COMPLETED, WALLET_DECRYPT_FAILED: WALLET_DECRYPT_FAILED, WALLET_LOCK_START: WALLET_LOCK_START, WALLET_LOCK_COMPLETED: WALLET_LOCK_COMPLETED, WALLET_LOCK_FAILED: WALLET_LOCK_FAILED, WALLET_STATUS_START: WALLET_STATUS_START, WALLET_STATUS_COMPLETED: WALLET_STATUS_COMPLETED, SET_TRANSACTION_LIST_FILTER: SET_TRANSACTION_LIST_FILTER, UPDATE_CURRENT_HEIGHT: UPDATE_CURRENT_HEIGHT, SET_DRAFT_TRANSACTION_AMOUNT: SET_DRAFT_TRANSACTION_AMOUNT, SET_DRAFT_TRANSACTION_ADDRESS: SET_DRAFT_TRANSACTION_ADDRESS, RESOLVE_URIS_STARTED: RESOLVE_URIS_STARTED, RESOLVE_URIS_COMPLETED: RESOLVE_URIS_COMPLETED, FETCH_CHANNEL_CLAIMS_STARTED: FETCH_CHANNEL_CLAIMS_STARTED, FETCH_CHANNEL_CLAIMS_COMPLETED: FETCH_CHANNEL_CLAIMS_COMPLETED, FETCH_CLAIM_LIST_MINE_STARTED: FETCH_CLAIM_LIST_MINE_STARTED, FETCH_CLAIM_LIST_MINE_COMPLETED: FETCH_CLAIM_LIST_MINE_COMPLETED, ABANDON_CLAIM_STARTED: ABANDON_CLAIM_STARTED, ABANDON_CLAIM_SUCCEEDED: ABANDON_CLAIM_SUCCEEDED, FETCH_CHANNEL_LIST_STARTED: FETCH_CHANNEL_LIST_STARTED, FETCH_CHANNEL_LIST_COMPLETED: FETCH_CHANNEL_LIST_COMPLETED, CREATE_CHANNEL_STARTED: CREATE_CHANNEL_STARTED, CREATE_CHANNEL_COMPLETED: CREATE_CHANNEL_COMPLETED, CREATE_CHANNEL_FAILED: CREATE_CHANNEL_FAILED, PUBLISH_STARTED: PUBLISH_STARTED, PUBLISH_COMPLETED: PUBLISH_COMPLETED, PUBLISH_FAILED: PUBLISH_FAILED, SET_PLAYING_URI: SET_PLAYING_URI, SET_CONTENT_POSITION: SET_CONTENT_POSITION, SET_CONTENT_LAST_VIEWED: SET_CONTENT_LAST_VIEWED, CLEAR_CONTENT_HISTORY_URI: CLEAR_CONTENT_HISTORY_URI, CLEAR_CONTENT_HISTORY_ALL: CLEAR_CONTENT_HISTORY_ALL, CLAIM_SEARCH_STARTED: CLAIM_SEARCH_STARTED, CLAIM_SEARCH_COMPLETED: CLAIM_SEARCH_COMPLETED, CLAIM_SEARCH_FAILED: CLAIM_SEARCH_FAILED, COMMENT_LIST_STARTED: COMMENT_LIST_STARTED, COMMENT_LIST_COMPLETED: COMMENT_LIST_COMPLETED, COMMENT_LIST_FAILED: COMMENT_LIST_FAILED, COMMENT_CREATE_STARTED: COMMENT_CREATE_STARTED, COMMENT_CREATE_COMPLETED: COMMENT_CREATE_COMPLETED, COMMENT_CREATE_FAILED: COMMENT_CREATE_FAILED, FILE_LIST_STARTED: FILE_LIST_STARTED, FILE_LIST_SUCCEEDED: FILE_LIST_SUCCEEDED, FETCH_FILE_INFO_STARTED: FETCH_FILE_INFO_STARTED, FETCH_FILE_INFO_COMPLETED: FETCH_FILE_INFO_COMPLETED, LOADING_VIDEO_STARTED: LOADING_VIDEO_STARTED, LOADING_VIDEO_COMPLETED: LOADING_VIDEO_COMPLETED, LOADING_VIDEO_FAILED: LOADING_VIDEO_FAILED, DOWNLOADING_STARTED: DOWNLOADING_STARTED, DOWNLOADING_PROGRESSED: DOWNLOADING_PROGRESSED, DOWNLOADING_COMPLETED: DOWNLOADING_COMPLETED, DOWNLOADING_CANCELED: DOWNLOADING_CANCELED, PLAY_VIDEO_STARTED: PLAY_VIDEO_STARTED, FETCH_AVAILABILITY_STARTED: FETCH_AVAILABILITY_STARTED, FETCH_AVAILABILITY_COMPLETED: FETCH_AVAILABILITY_COMPLETED, FILE_DELETE: FILE_DELETE, SET_FILE_LIST_SORT: SET_FILE_LIST_SORT, PURCHASE_URI_STARTED: PURCHASE_URI_STARTED, PURCHASE_URI_COMPLETED: PURCHASE_URI_COMPLETED, PURCHASE_URI_FAILED: PURCHASE_URI_FAILED, DELETE_PURCHASED_URI: DELETE_PURCHASED_URI, LOADING_FILE_STARTED: LOADING_FILE_STARTED, LOADING_FILE_COMPLETED: LOADING_FILE_COMPLETED, LOADING_FILE_FAILED: LOADING_FILE_FAILED, SEARCH_START: SEARCH_START, SEARCH_SUCCESS: SEARCH_SUCCESS, SEARCH_FAIL: SEARCH_FAIL, UPDATE_SEARCH_QUERY: UPDATE_SEARCH_QUERY, UPDATE_SEARCH_OPTIONS: UPDATE_SEARCH_OPTIONS, UPDATE_SEARCH_SUGGESTIONS: UPDATE_SEARCH_SUGGESTIONS, SEARCH_FOCUS: SEARCH_FOCUS, SEARCH_BLUR: SEARCH_BLUR, DAEMON_SETTINGS_RECEIVED: DAEMON_SETTINGS_RECEIVED, CLIENT_SETTING_CHANGED: CLIENT_SETTING_CHANGED, UPDATE_IS_NIGHT: UPDATE_IS_NIGHT, AUTHENTICATION_STARTED: AUTHENTICATION_STARTED, AUTHENTICATION_SUCCESS: AUTHENTICATION_SUCCESS, AUTHENTICATION_FAILURE: AUTHENTICATION_FAILURE, USER_EMAIL_DECLINE: USER_EMAIL_DECLINE, USER_EMAIL_NEW_STARTED: USER_EMAIL_NEW_STARTED, USER_EMAIL_NEW_SUCCESS: USER_EMAIL_NEW_SUCCESS, USER_EMAIL_NEW_EXISTS: USER_EMAIL_NEW_EXISTS, USER_EMAIL_NEW_FAILURE: USER_EMAIL_NEW_FAILURE, USER_EMAIL_VERIFY_SET: USER_EMAIL_VERIFY_SET, USER_EMAIL_VERIFY_STARTED: USER_EMAIL_VERIFY_STARTED, USER_EMAIL_VERIFY_SUCCESS: USER_EMAIL_VERIFY_SUCCESS, USER_EMAIL_VERIFY_FAILURE: USER_EMAIL_VERIFY_FAILURE, USER_EMAIL_VERIFY_RETRY: USER_EMAIL_VERIFY_RETRY, USER_PHONE_RESET: USER_PHONE_RESET, USER_PHONE_NEW_STARTED: USER_PHONE_NEW_STARTED, USER_PHONE_NEW_SUCCESS: USER_PHONE_NEW_SUCCESS, USER_PHONE_NEW_FAILURE: USER_PHONE_NEW_FAILURE, USER_PHONE_VERIFY_STARTED: USER_PHONE_VERIFY_STARTED, USER_PHONE_VERIFY_SUCCESS: USER_PHONE_VERIFY_SUCCESS, USER_PHONE_VERIFY_FAILURE: USER_PHONE_VERIFY_FAILURE, USER_IDENTITY_VERIFY_STARTED: USER_IDENTITY_VERIFY_STARTED, USER_IDENTITY_VERIFY_SUCCESS: USER_IDENTITY_VERIFY_SUCCESS, USER_IDENTITY_VERIFY_FAILURE: USER_IDENTITY_VERIFY_FAILURE, USER_FETCH_STARTED: USER_FETCH_STARTED, USER_FETCH_SUCCESS: USER_FETCH_SUCCESS, USER_FETCH_FAILURE: USER_FETCH_FAILURE, USER_INVITE_STATUS_FETCH_STARTED: USER_INVITE_STATUS_FETCH_STARTED, USER_INVITE_STATUS_FETCH_SUCCESS: USER_INVITE_STATUS_FETCH_SUCCESS, USER_INVITE_STATUS_FETCH_FAILURE: USER_INVITE_STATUS_FETCH_FAILURE, USER_INVITE_NEW_STARTED: USER_INVITE_NEW_STARTED, USER_INVITE_NEW_SUCCESS: USER_INVITE_NEW_SUCCESS, USER_INVITE_NEW_FAILURE: USER_INVITE_NEW_FAILURE, FETCH_ACCESS_TOKEN_SUCCESS: FETCH_ACCESS_TOKEN_SUCCESS, FETCH_REWARDS_STARTED: FETCH_REWARDS_STARTED, FETCH_REWARDS_COMPLETED: FETCH_REWARDS_COMPLETED, CLAIM_REWARD_STARTED: CLAIM_REWARD_STARTED, CLAIM_REWARD_SUCCESS: CLAIM_REWARD_SUCCESS, CLAIM_REWARD_FAILURE: CLAIM_REWARD_FAILURE, CLAIM_REWARD_CLEAR_ERROR: CLAIM_REWARD_CLEAR_ERROR, FETCH_REWARD_CONTENT_COMPLETED: FETCH_REWARD_CONTENT_COMPLETED, DOWNLOAD_LANGUAGE_SUCCEEDED: DOWNLOAD_LANGUAGE_SUCCEEDED, DOWNLOAD_LANGUAGE_FAILED: DOWNLOAD_LANGUAGE_FAILED, CHANNEL_SUBSCRIBE: CHANNEL_SUBSCRIBE, CHANNEL_UNSUBSCRIBE: CHANNEL_UNSUBSCRIBE, HAS_FETCHED_SUBSCRIPTIONS: HAS_FETCHED_SUBSCRIPTIONS, SET_SUBSCRIPTION_LATEST: SET_SUBSCRIPTION_LATEST, SET_SUBSCRIPTION_NOTIFICATION: SET_SUBSCRIPTION_NOTIFICATION, SET_SUBSCRIPTION_NOTIFICATIONS: SET_SUBSCRIPTION_NOTIFICATIONS, CHECK_SUBSCRIPTION_STARTED: CHECK_SUBSCRIPTION_STARTED, CHECK_SUBSCRIPTION_COMPLETED: CHECK_SUBSCRIPTION_COMPLETED, CHECK_SUBSCRIPTIONS_SUBSCRIBE: CHECK_SUBSCRIPTIONS_SUBSCRIBE, CLEAR_PUBLISH: CLEAR_PUBLISH, UPDATE_PUBLISH_FORM: UPDATE_PUBLISH_FORM, PUBLISH_START: PUBLISH_START, PUBLISH_SUCCESS: PUBLISH_SUCCESS, PUBLISH_FAIL: PUBLISH_FAIL, CLEAR_PUBLISH_ERROR: CLEAR_PUBLISH_ERROR, REMOVE_PENDING_PUBLISH: REMOVE_PENDING_PUBLISH, DO_PREPARE_EDIT: DO_PREPARE_EDIT, CREATE_NOTIFICATION: CREATE_NOTIFICATION, EDIT_NOTIFICATION: EDIT_NOTIFICATION, DELETE_NOTIFICATION: DELETE_NOTIFICATION, DISMISS_NOTIFICATION: DISMISS_NOTIFICATION, CREATE_TOAST: CREATE_TOAST, DISMISS_TOAST: DISMISS_TOAST, CREATE_ERROR: CREATE_ERROR, DISMISS_ERROR: DISMISS_ERROR, FETCH_DATE: FETCH_DATE, FETCH_COST_INFO_STARTED: FETCH_COST_INFO_STARTED, FETCH_COST_INFO_COMPLETED: FETCH_COST_INFO_COMPLETED, FETCH_COST_INFO_FAILED: FETCH_COST_INFO_FAILED, TOGGLE_TAG_FOLLOW: TOGGLE_TAG_FOLLOW, TAG_ADD: TAG_ADD, TAG_DELETE: TAG_DELETE }); const CC_LICENSES = [{ value: 'Creative Commons Attribution 4.0 International', url: 'https://creativecommons.org/licenses/by/4.0/legalcode' }, { value: 'Creative Commons Attribution-ShareAlike 4.0 International', url: 'https://creativecommons.org/licenses/by-sa/4.0/legalcode' }, { value: 'Creative Commons Attribution-NoDerivatives 4.0 International', url: 'https://creativecommons.org/licenses/by-nd/4.0/legalcode' }, { value: 'Creative Commons Attribution-NonCommercial 4.0 International', url: 'https://creativecommons.org/licenses/by-nc/4.0/legalcode' }, { value: 'Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International', url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode' }, { value: 'Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International', url: 'https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode' }]; const NONE = 'None'; const PUBLIC_DOMAIN = 'Public Domain'; const OTHER = 'other'; const COPYRIGHT = 'copyright'; var licenses = /*#__PURE__*/Object.freeze({ CC_LICENSES: CC_LICENSES, NONE: NONE, PUBLIC_DOMAIN: PUBLIC_DOMAIN, OTHER: OTHER, COPYRIGHT: COPYRIGHT }); const AUTH = 'auth'; const BACKUP = 'backup'; const CHANNEL = 'channel'; const DISCOVER = 'discover'; const FILE = 'file'; const DOWNLOADED = 'downloaded'; const PUBLISHED = 'published'; const GET_CREDITS = 'getcredits'; const HELP = 'help'; const INVITE = 'invite'; const PUBLISH = 'publish'; const REPORT = 'report'; const REWARDS = 'rewards'; const SEARCH = 'search'; const SEND_CREDITS = 'send'; const SETTINGS = 'settings'; const SHOW = 'show'; const SUBSCRIPTIONS = 'subscriptions'; const TRANSACTION_HISTORY = 'history'; const HISTORY = 'user_history'; const WALLET = 'wallet'; var pages = /*#__PURE__*/Object.freeze({ AUTH: AUTH, BACKUP: BACKUP, CHANNEL: CHANNEL, DISCOVER: DISCOVER, FILE: FILE, DOWNLOADED: DOWNLOADED, PUBLISHED: PUBLISHED, GET_CREDITS: GET_CREDITS, HELP: HELP, INVITE: INVITE, PUBLISH: PUBLISH, REPORT: REPORT, REWARDS: REWARDS, SEARCH: SEARCH, SEND_CREDITS: SEND_CREDITS, SETTINGS: SETTINGS, SHOW: SHOW, SUBSCRIPTIONS: SUBSCRIPTIONS, TRANSACTION_HISTORY: TRANSACTION_HISTORY, HISTORY: HISTORY, WALLET: WALLET }); /* hardcoded names still exist for these in reducers/settings.js - only discovered when debugging */ /* Many SETTINGS are stored in the localStorage by their name - be careful about changing the value of a SETTINGS constant, as doing so can invalidate existing SETTINGS */ const CREDIT_REQUIRED_ACKNOWLEDGED = 'credit_required_acknowledged'; const NEW_USER_ACKNOWLEDGED = 'welcome_acknowledged'; const EMAIL_COLLECTION_ACKNOWLEDGED = 'email_collection_acknowledged'; const LANGUAGE = 'language'; const SHOW_NSFW = 'showNsfw'; const SHOW_UNAVAILABLE = 'showUnavailable'; const INSTANT_PURCHASE_ENABLED = 'instantPurchaseEnabled'; const INSTANT_PURCHASE_MAX = 'instantPurchaseMax'; const THEME = 'theme'; const THEMES = 'themes'; const AUTOMATIC_DARK_MODE_ENABLED = 'automaticDarkModeEnabled'; // mobile settings const BACKGROUND_PLAY_ENABLED = 'backgroundPlayEnabled'; const FOREGROUND_NOTIFICATION_ENABLED = 'foregroundNotificationEnabled'; const KEEP_DAEMON_RUNNING = 'keepDaemonRunning'; var settings = /*#__PURE__*/Object.freeze({ CREDIT_REQUIRED_ACKNOWLEDGED: CREDIT_REQUIRED_ACKNOWLEDGED, NEW_USER_ACKNOWLEDGED: NEW_USER_ACKNOWLEDGED, EMAIL_COLLECTION_ACKNOWLEDGED: EMAIL_COLLECTION_ACKNOWLEDGED, LANGUAGE: LANGUAGE, SHOW_NSFW: SHOW_NSFW, SHOW_UNAVAILABLE: SHOW_UNAVAILABLE, INSTANT_PURCHASE_ENABLED: INSTANT_PURCHASE_ENABLED, INSTANT_PURCHASE_MAX: INSTANT_PURCHASE_MAX, THEME: THEME, THEMES: THEMES, AUTOMATIC_DARK_MODE_ENABLED: AUTOMATIC_DARK_MODE_ENABLED, BACKGROUND_PLAY_ENABLED: BACKGROUND_PLAY_ENABLED, FOREGROUND_NOTIFICATION_ENABLED: FOREGROUND_NOTIFICATION_ENABLED, KEEP_DAEMON_RUNNING: KEEP_DAEMON_RUNNING }); const DATE_NEW = 'dateNew'; const DATE_OLD = 'dateOld'; const TITLE = 'title'; const FILENAME = 'filename'; var sort_options = /*#__PURE__*/Object.freeze({ DATE_NEW: DATE_NEW, DATE_OLD: DATE_OLD, TITLE: TITLE, FILENAME: FILENAME }); const API_DOWN = 'apiDown'; const READY = 'ready'; const IN_PROGRESS = 'inProgress'; const COMPLETE = 'complete'; const MANUAL = 'manual'; var thumbnail_upload_statuses = /*#__PURE__*/Object.freeze({ API_DOWN: API_DOWN, READY: READY, IN_PROGRESS: IN_PROGRESS, COMPLETE: COMPLETE, MANUAL: MANUAL }); // eslint-disable-next-line import/prefer-default-export const ALL = 'all'; const SPEND = 'spend'; const RECEIVE = 'receive'; const PUBLISH$1 = 'publish'; const CHANNEL$1 = 'channel'; const TIP = 'tip'; const SUPPORT = 'support'; const UPDATE = 'update'; const ABANDON = 'abandon'; var transaction_types = /*#__PURE__*/Object.freeze({ ALL: ALL, SPEND: SPEND, RECEIVE: RECEIVE, PUBLISH: PUBLISH$1, CHANNEL: CHANNEL$1, TIP: TIP, SUPPORT: SUPPORT, UPDATE: UPDATE, ABANDON: ABANDON }); const SEARCH_TYPES = { FILE: 'file', CHANNEL: 'channel', SEARCH: 'search' }; const SEARCH_OPTIONS = { RESULT_COUNT: 'size', CLAIM_TYPE: 'claimType', INCLUDE_FILES: 'file', INCLUDE_CHANNELS: 'channel', INCLUDE_FILES_AND_CHANNELS: 'file,channel', MEDIA_AUDIO: 'audio', MEDIA_VIDEO: 'video', MEDIA_TEXT: 'text', MEDIA_IMAGE: 'image', MEDIA_APPLICATION: 'application' }; // const CHECK_DAEMON_STARTED_TRY_NUMBER = 200; // // Basic LBRY sdk connection config // Offers a proxy to call LBRY sdk methods // const Lbry = { isConnected: false, connectPromise: null, daemonConnectionString: 'http://localhost:5279', apiRequestHeaders: { 'Content-Type': 'application/json-rpc' }, // Allow overriding daemon connection string (e.g. to `/api/proxy` for lbryweb) setDaemonConnectionString: value => { Lbry.daemonConnectionString = value; }, setApiHeader: (key, value) => { Lbry.apiRequestHeaders = Object.assign(Lbry.apiRequestHeaders, { [key]: value }); }, unsetApiHeader: key => { Object.keys(Lbry.apiRequestHeaders).includes(key) && delete Lbry.apiRequestHeaders['key']; }, // Allow overriding Lbry methods overrides: {}, setOverride: (methodName, newMethod) => { Lbry.overrides[methodName] = newMethod; }, // Returns a human readable media type based on the content type or extension of a file that is returned by the sdk getMediaType: (contentType, extname) => { if (extname) { const formats = [[/^(mp4|m4v|webm|flv|f4v|ogv)$/i, 'video'], [/^(mp3|m4a|aac|wav|flac|ogg|opus)$/i, 'audio'], [/^(html|htm|xml|pdf|odf|doc|docx|md|markdown|txt|epub|org)$/i, 'document'], [/^(stl|obj|fbx|gcode)$/i, '3D-file']]; const res = formats.reduce((ret, testpair) => { switch (testpair[0].test(ret)) { case true: return testpair[1]; default: return ret; } }, extname); return res === extname ? 'unknown' : res; } else if (contentType) { // $FlowFixMe return (/^[^/]+/.exec(contentType)[0] ); } return 'unknown'; }, // // Lbry SDK Methods // https://lbry.tech/api/sdk // status: (params = {}) => daemonCallWithResult('status', params), stop: () => daemonCallWithResult('stop', {}), version: () => daemonCallWithResult('version', {}), // Claim fetching and manipulation resolve: params => daemonCallWithResult('resolve', params), get: params => daemonCallWithResult('get', params), publish: params => daemonCallWithResult('publish', params), claim_search: params => daemonCallWithResult('claim_search', params), claim_list: params => daemonCallWithResult('claim_list', params), channel_create: params => daemonCallWithResult('channel_create', params), channel_list: params => daemonCallWithResult('channel_list', params), stream_abandon: params => daemonCallWithResult('stream_abandon', params), channel_abandon: params => daemonCallWithResult('channel_abandon', params), support_create: params => daemonCallWithResult('support_create', params), // File fetching and manipulation file_list: (params = {}) => daemonCallWithResult('file_list', params), file_delete: (params = {}) => daemonCallWithResult('file_delete', params), file_set_status: (params = {}) => daemonCallWithResult('file_set_status', params), blob_delete: (params = {}) => daemonCallWithResult('blob_delete', params), blob_list: (params = {}) => daemonCallWithResult('blob_list', params), // Wallet utilities account_balance: (params = {}) => daemonCallWithResult('account_balance', params), account_decrypt: () => daemonCallWithResult('account_decrypt', {}), account_encrypt: (params = {}) => daemonCallWithResult('account_encrypt', params), account_unlock: (params = {}) => daemonCallWithResult('account_unlock', params), account_list: (params = {}) => daemonCallWithResult('account_list', params), account_send: (params = {}) => daemonCallWithResult('account_send', params), account_set: (params = {}) => daemonCallWithResult('account_set', params), address_is_mine: (params = {}) => daemonCallWithResult('address_is_mine', params), address_unused: (params = {}) => daemonCallWithResult('address_unused', params), transaction_list: (params = {}) => daemonCallWithResult('transaction_list', params), utxo_release: (params = {}) => daemonCallWithResult('utxo_release', params), support_abandon: (params = {}) => daemonCallWithResult('support_abandon', params), sync_hash: (params = {}) => daemonCallWithResult('sync_hash', params), sync_apply: (params = {}) => daemonCallWithResult('sync_apply', params), // Comments comment_list: (params = {}) => daemonCallWithResult('comment_list', params), comment_create: (params = {}) => daemonCallWithResult('comment_create', params), // Connect to the sdk connect: () => { if (Lbry.connectPromise === null) { Lbry.connectPromise = new Promise((resolve, reject) => { let tryNum = 0; // Check every half second to see if the daemon is accepting connections function checkDaemonStarted() { tryNum += 1; Lbry.status().then(resolve).catch(() => { if (tryNum <= CHECK_DAEMON_STARTED_TRY_NUMBER) { setTimeout(checkDaemonStarted, tryNum < 50 ? 400 : 1000); } else { reject(new Error('Unable to connect to LBRY')); } }); } checkDaemonStarted(); }); } // Flow thinks this could be empty, but it will always reuturn a promise // $FlowFixMe return Lbry.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); }); } function apiCall(method, params, resolve, reject) { const counter = new Date().getTime(); const options = { method: 'POST', headers: Lbry.apiRequestHeaders, body: JSON.stringify({ jsonrpc: '2.0', method, params, id: counter }) }; return fetch(Lbry.daemonConnectionString, 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 daemonCallWithResult(name, params = {}) { return new Promise((resolve, reject) => { apiCall(name, params, result => { resolve(result); }, reject); }); } // This is only for a fallback // If there is a Lbry method that is being called by an app, it should be added to /flow-typed/Lbry.js const lbryProxy = new Proxy(Lbry, { get(target, name) { if (name in target) { return target[name]; } return (params = {}) => new Promise((resolve, reject) => { apiCall(name, params, resolve, reject); }); } }); // const DEFAULT_SEARCH_RESULT_FROM = 0; const DEFAULT_SEARCH_SIZE = 20; function parseQueryParams(queryString) { if (queryString === '') return {}; const parts = queryString.split('?').pop().split('&').map(p => p.split('=')); const params = {}; parts.forEach(array => { const [first, second] = array; params[first] = second; }); return params; } function toQueryString(params) { if (!params) return ''; const parts = []; Object.keys(params).forEach(key => { if (Object.prototype.hasOwnProperty.call(params, key) && params[key]) { parts.push(`${key}=${params[key]}`); } }); return parts.join('&'); } const getSearchQueryString = (query, options = {}, includeUserOptions = false) => { const encodedQuery = encodeURIComponent(query); const queryParams = [`s=${encodedQuery}`, `size=${options.size || DEFAULT_SEARCH_SIZE}`, `from=${options.from || DEFAULT_SEARCH_RESULT_FROM}`]; if (includeUserOptions) { const claimType = options[SEARCH_OPTIONS.CLAIM_TYPE]; queryParams.push(`claimType=${claimType}`); // If they are only searching for channels, strip out the media info if (!claimType.includes(SEARCH_OPTIONS.INCLUDE_CHANNELS)) { queryParams.push(`mediaType=${[SEARCH_OPTIONS.MEDIA_FILE, SEARCH_OPTIONS.MEDIA_AUDIO, SEARCH_OPTIONS.MEDIA_VIDEO, SEARCH_OPTIONS.MEDIA_TEXT, SEARCH_OPTIONS.MEDIA_IMAGE, SEARCH_OPTIONS.MEDIA_APPLICATION].reduce((acc, currentOption) => options[currentOption] ? `${acc}${currentOption},` : acc, '')}`); } } return queryParams.join('&'); }; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const channelNameMinLength = 1; const claimIdMaxLength = 40; // see https://spec.lbry.com/#urls const regexInvalidURI = exports.regexInvalidURI = /[=&#:$@%?\u{0000}-\u{0008}\u{000b}-\u{000c}\u{000e}-\u{001F}\u{D800}-\u{DFFF}\u{FFFE}-\u{FFFF}]/gu; const regexAddress = /^(b|r)(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/; /** * Parses a LBRY name into its component parts. Throws errors with user-friendly * messages for invalid names. * * N.B. that "name" indicates the value in the name position of the URI. For * claims for channel content, this will actually be the channel name, and * the content name is in the path (e.g. lbry://@channel/content) * * In most situations, you'll want to use the contentName and channelName keys * and ignore the name key. * * Returns a dictionary with keys: * - name (string): The value in the "name" position in the URI. Note that this * could be either content name or channel name; see above. * - path (string, if persent) * - claimSequence (int, if present) * - bidPosition (int, if present) * - claimId (string, if present) * - isChannel (boolean) * - contentName (string): For anon claims, the name; for channel claims, the path * - channelName (string, if present): Channel name without @ */ function parseURI(URI, requireProto = false) { // Break into components. Empty sub-matches are converted to null const componentsRegex = new RegExp('^((?:lbry://)?)' + // protocol '([^:$#/]*)' + // claim name (stops at the first separator or end) '([:$#]?)([^/]*)' + // modifier separator, modifier (stops at the first path separator or end) '(/?)(.*)' // path separator, path ); const [proto, claimName, modSep, modVal, pathSep, path] = componentsRegex.exec(URI).slice(1).map(match => match || null); let contentName; // Validate protocol if (requireProto && !proto) { throw new Error(__('LBRY URIs must include a protocol prefix (lbry://).')); } // Validate and process name if (!claimName) { throw new Error(__('URI does not include name.')); } const isChannel = claimName.startsWith('@'); const channelName = isChannel ? claimName.slice(1) : claimName; if (isChannel) { if (!channelName) { throw new Error(__('No channel name after @.')); } if (channelName.length < channelNameMinLength) { throw new Error(__(`Channel names must be at least %s characters.`, channelNameMinLength)); } contentName = path; } const nameBadChars = (channelName || claimName).match(regexInvalidURI); if (nameBadChars) { throw new Error(__(`Invalid character %s in name: %s.`, nameBadChars.length === 1 ? '' : 's', nameBadChars.join(', '))); } // Validate and process modifier (claim ID, bid position or claim sequence) let claimId; let claimSequence; let bidPosition; if (modSep) { if (!modVal) { throw new Error(__(`No modifier provided after separator %s.`, modSep)); } if (modSep === '#') { claimId = modVal; } else if (modSep === ':') { claimSequence = modVal; } else if (modSep === '$') { bidPosition = modVal; } } if (claimId && (claimId.length > claimIdMaxLength || !claimId.match(/^[0-9a-f]+$/))) { throw new Error(__(`Invalid claim ID %s.`, claimId)); } if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) { throw new Error(__('Claim sequence must be a number.')); } if (bidPosition && !bidPosition.match(/^-?[1-9][0-9]*$/)) { throw new Error(__('Bid position must be a number.')); } // Validate and process path if (path) { if (!isChannel) { throw new Error(__('Only channel URIs may have a path.')); } const pathBadChars = path.match(regexInvalidURI); if (pathBadChars) { throw new Error(__(`Invalid character in path: %s`, pathBadChars.join(', '))); } contentName = path; } else if (pathSep) { throw new Error(__('No path provided after /')); } return _extends({ claimName, path, isChannel }, contentName ? { contentName } : {}, channelName ? { channelName } : {}, claimSequence ? { claimSequence: parseInt(claimSequence, 10) } : {}, bidPosition ? { bidPosition: parseInt(bidPosition, 10) } : {}, claimId ? { claimId } : {}, path ? { path } : {}); } /** * Takes an object in the same format returned by parse() and builds a URI. * * The channelName key will accept names with or without the @ prefix. */ function buildURI(URIObj, includeProto = true, protoDefault = 'lbry://') { const { claimId, claimSequence, bidPosition, contentName, channelName } = URIObj; let { claimName, path } = URIObj; if (channelName) { const channelNameFormatted = channelName.startsWith('@') ? channelName : `@${channelName}`; if (!claimName) { claimName = channelNameFormatted; } else if (claimName !== channelNameFormatted) { throw new Error(__('Received a channel content URI, but claim name and channelName do not match. "name" represents the value in the name position of the URI (lbry://name...), which for channel content will be the channel name. In most cases, to construct a channel URI you should just pass channelName and contentName.')); } } if (contentName) { if (!claimName) { claimName = contentName; } else if (!path) { path = contentName; } if (path && path !== contentName) { throw new Error(__('Path and contentName do not match. Only one is required; most likely you wanted contentName.')); } } return (includeProto ? protoDefault : '') + claimName + (claimId ? `#${claimId}` : '') + (claimSequence ? `:${claimSequence}` : '') + (bidPosition ? `${bidPosition}` : '') + (path ? `/${path}` : ''); } /* Takes a parseable LBRY URI and converts it to standard, canonical format */ function normalizeURI(URI) { const { claimName, path, bidPosition, claimSequence, claimId } = parseURI(URI); return buildURI({ claimName, path, claimSequence, bidPosition, claimId }); } function isURIValid(URI) { let parts; try { parts = parseURI(normalizeURI(URI)); } catch (error) { return false; } return parts && parts.claimName; } function isNameValid(claimName) { return !regexInvalidURI.test(claimName); } function isURIClaimable(URI) { let parts; try { parts = parseURI(normalizeURI(URI)); } catch (error) { return false; } return parts && parts.claimName && !parts.claimId && !parts.bidPosition && !parts.claimSequence && !parts.isChannel && !parts.path; } function convertToShareLink(URI) { const { claimName, path, bidPosition, claimSequence, claimId } = parseURI(URI); return buildURI({ claimName, path, claimSequence, bidPosition, claimId }, true, 'https://open.lbry.com/'); } var _extends$1 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const selectState = state => state.search; const selectSearchValue = reselect.createSelector(selectState, state => state.searchQuery); const selectSearchOptions = reselect.createSelector(selectState, state => state.options); const selectSuggestions = reselect.createSelector(selectState, state => state.suggestions); const selectIsSearching = reselect.createSelector(selectState, state => state.searching); const selectSearchUrisByQuery = reselect.createSelector(selectState, state => state.urisByQuery); const makeSelectSearchUris = query => // replace statement below is kind of ugly, and repeated in doSearch action reselect.createSelector(selectSearchUrisByQuery, byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]); const selectSearchBarFocused = reselect.createSelector(selectState, state => state.focused); const selectSearchSuggestions = reselect.createSelector(selectSearchValue, selectSuggestions, (query, suggestions) => { if (!query) { return []; } const queryIsPrefix = query === 'lbry:' || query === 'lbry:/' || query === 'lbry://' || query === 'lbry://@'; if (queryIsPrefix) { // If it is a prefix, wait until something else comes to figure out what to do return []; } else if (query.startsWith('lbry://')) { // If it starts with a prefix, don't show any autocomplete results // They are probably typing/pasting in a lbry uri return [{ value: query, type: query[7] === '@' ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE }]; } let searchSuggestions = []; try { const uri = normalizeURI(query); const { claimName, isChannel } = parseURI(uri); searchSuggestions.push({ value: claimName, type: SEARCH_TYPES.SEARCH }, { value: uri, shorthand: isChannel ? claimName.slice(1) : claimName, type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE }); } catch (e) { searchSuggestions.push({ value: query, type: SEARCH_TYPES.SEARCH }); } const apiSuggestions = suggestions[query] || []; if (apiSuggestions.length) { searchSuggestions = searchSuggestions.concat(apiSuggestions.filter(suggestion => suggestion !== query).map(suggestion => { // determine if it's a channel try { const uri = normalizeURI(suggestion); const { claimName, isChannel } = parseURI(uri); return { value: uri, shorthand: isChannel ? claimName.slice(1) : claimName, type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE }; } catch (e) { // search result includes some character that isn't valid in claim names return { value: suggestion, type: SEARCH_TYPES.SEARCH }; } })); } return searchSuggestions; }); // Creates a query string based on the state in the search reducer // Can be overrided by passing in custom sizes/from values for other areas pagination const makeSelectQueryWithOptions = (customQuery, customSize, customFrom, isBackgroundSearch = false // If it's a background search, don't use the users settings ) => reselect.createSelector(selectSearchValue, selectSearchOptions, (query, options) => { const size = customSize || options[SEARCH_OPTIONS.RESULT_COUNT]; const queryString = getSearchQueryString(customQuery || query, _extends$1({}, options, { size, from: customFrom }), !isBackgroundSearch); return queryString; }); // function doToast(params) { if (!params) { throw Error("'params' object is required to create a toast notification"); } return { type: CREATE_TOAST, data: { id: uuid(), params } }; } function doDismissToast() { return { type: DISMISS_TOAST }; } function doError(error) { return { type: CREATE_ERROR, data: { error } }; } function doDismissError() { return { type: DISMISS_ERROR }; } var _extends$2 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; // const naughtyTags = ['porn', 'nsfw', 'mature', 'xxx'].reduce((acc, tag) => _extends$2({}, acc, { [tag]: true }), {}); const isClaimNsfw = claim => { if (!claim) { throw new Error('No claim passed to isClaimNsfw()'); } if (!claim.value) { return false; } const tags = claim.value.tags || []; for (let i = 0; i < tags.length; i += 1) { const tag = tags[i].toLowerCase(); if (naughtyTags[tag]) { return true; } } return false; }; // const selectState$1 = state => state.claims || {}; const selectClaimsById = reselect.createSelector(selectState$1, state => state.byId || {}); const selectCurrentChannelPage = reselect.createSelector(selectState$1, state => state.currentChannelPage || 1); const selectClaimsByUri = reselect.createSelector(selectState$1, selectClaimsById, (state, byId) => { const byUri = state.claimsByUri || {}; const claims = {}; Object.keys(byUri).forEach(uri => { const claimId = byUri[uri]; // NOTE returning a null claim allows us to differentiate between an // undefined (never fetched claim) and one which just doesn't exist. Not // the cleanest solution but couldn't think of anything better right now if (claimId === null) { claims[uri] = null; } else { claims[uri] = byId[claimId]; } }); return claims; }); const selectAllClaimsByChannel = reselect.createSelector(selectState$1, state => state.claimsByChannel || {}); const selectPendingById = reselect.createSelector(selectState$1, state => state.pendingById || {}); const selectPendingClaims = reselect.createSelector(selectState$1, state => Object.values(state.pendingById || [])); const makeSelectClaimIsPending = uri => reselect.createSelector(selectPendingById, pendingById => { const { claimId } = parseURI(uri); return Boolean(pendingById[claimId]); }); const makeSelectPendingByUri = uri => reselect.createSelector(selectPendingById, pendingById => { const { claimId } = parseURI(uri); return pendingById[claimId]; }); const makeSelectClaimForUri = uri => reselect.createSelector(selectClaimsByUri, selectPendingById, (byUri, pendingById) => { // Check if a claim is pending first // It won't be in claimsByUri because resolving it will return nothing const { claimId } = parseURI(uri); const pendingClaim = pendingById[claimId]; if (pendingClaim) { return pendingClaim; } return byUri && byUri[normalizeURI(uri)]; }); const selectMyClaimsRaw = reselect.createSelector(selectState$1, state => state.myClaims); const selectAbandoningIds = reselect.createSelector(selectState$1, state => Object.keys(state.abandoningById || {})); const selectMyActiveClaims = reselect.createSelector(selectMyClaimsRaw, selectAbandoningIds, (claims, abandoningIds) => new Set(claims && claims.map(claim => claim.claim_id).filter(claimId => Object.keys(abandoningIds).indexOf(claimId) === -1))); const makeSelectClaimIsMine = rawUri => { const uri = normalizeURI(rawUri); return reselect.createSelector(selectClaimsByUri, selectMyActiveClaims, (claims, myClaims) => claims && claims[uri] && claims[uri].claim_id && myClaims.has(claims[uri].claim_id)); }; const selectAllFetchingChannelClaims = reselect.createSelector(selectState$1, state => state.fetchingChannelClaims || {}); const makeSelectFetchingChannelClaims = uri => reselect.createSelector(selectAllFetchingChannelClaims, fetching => fetching && fetching[uri]); const makeSelectClaimsInChannelForPage = (uri, page) => reselect.createSelector(selectClaimsById, selectAllClaimsByChannel, (byId, allClaims) => { const byChannel = allClaims[uri] || {}; const claimIds = byChannel[page || 1]; if (!claimIds) return claimIds; return claimIds.map(claimId => byId[claimId]); }); const makeSelectClaimsInChannelForCurrentPageState = uri => reselect.createSelector(selectClaimsById, selectAllClaimsByChannel, selectCurrentChannelPage, (byId, allClaims, page) => { const byChannel = allClaims[uri] || {}; const claimIds = byChannel[page || 1]; if (!claimIds) return claimIds; return claimIds.map(claimId => byId[claimId]); }); const makeSelectMetadataForUri = uri => reselect.createSelector(makeSelectClaimForUri(uri), claim => { const metadata = claim && claim.value; return metadata || (claim === undefined ? undefined : null); }); const makeSelectMetadataItemForUri = (uri, key) => reselect.createSelector(makeSelectMetadataForUri(uri), metadata => { return metadata ? metadata[key] : undefined; }); const makeSelectTitleForUri = uri => reselect.createSelector(makeSelectMetadataForUri(uri), metadata => metadata && metadata.title); const makeSelectDateForUri = uri => reselect.createSelector(makeSelectClaimForUri(uri), claim => { const timestamp = claim && claim.value && (claim.value.release_time ? claim.value.release_time * 1000 : claim.meta.creation_timestamp * 1000); if (!timestamp) { return undefined; } const dateObj = new Date(timestamp); return dateObj; }); const makeSelectContentTypeForUri = uri => reselect.createSelector(makeSelectClaimForUri(uri), claim => { const source = claim && claim.value && claim.value.source; return source ? source.media_type : undefined; }); const makeSelectThumbnailForUri = uri => reselect.createSelector(makeSelectClaimForUri(uri), claim => { const thumbnail = claim && claim.value && claim.value.thumbnail; return thumbnail ? thumbnail.url : undefined; }); const makeSelectCoverForUri = uri => reselect.createSelector(makeSelectClaimForUri(uri), claim => { const cover = claim && claim.value && claim.value.cover; return cover ? cover.url : undefined; }); const selectIsFetchingClaimListMine = reselect.createSelector(selectState$1, state => state.isFetchingClaimListMine); const selectMyClaims = reselect.createSelector(selectMyActiveClaims, selectClaimsById, selectAbandoningIds, selectPendingClaims, (myClaimIds, byId, abandoningIds, pendingClaims) => { const claims = []; myClaimIds.forEach(id => { const claim = byId[id]; if (claim && abandoningIds.indexOf(id) === -1) claims.push(claim); }); return [...claims, ...pendingClaims]; }); const selectMyClaimsWithoutChannels = reselect.createSelector(selectMyClaims, myClaims => myClaims.filter(claim => !claim.name.match(/^@/)).sort((a, b) => a.timestamp - b.timestamp)); const selectMyClaimUrisWithoutChannels = reselect.createSelector(selectMyClaimsWithoutChannels, myClaims => myClaims.sort((a, b) => b.timestamp - a.timestamp).map(claim => `lbry://${claim.name}#${claim.claim_id}`)); const selectAllMyClaimsByOutpoint = reselect.createSelector(selectMyClaimsRaw, claims => new Set(claims && claims.length ? claims.map(claim => `${claim.txid}:${claim.nout}`) : null)); const selectMyClaimsOutpoints = reselect.createSelector(selectMyClaims, myClaims => { const outpoints = []; myClaims.forEach(claim => outpoints.push(`${claim.txid}:${claim.nout}`)); return outpoints; }); const selectFetchingMyChannels = reselect.createSelector(selectState$1, state => state.fetchingMyChannels); const selectMyChannelClaims = reselect.createSelector(selectState$1, selectClaimsById, (state, byId) => { const ids = state.myChannelClaims || []; const claims = []; ids.forEach(id => { if (byId[id]) { // I'm not sure why this check is necessary, but it ought to be a quick fix for https://github.com/lbryio/lbry-desktop/issues/544 claims.push(byId[id]); } }); return claims; }); const selectResolvingUris = reselect.createSelector(selectState$1, state => state.resolvingUris || []); const makeSelectIsUriResolving = uri => reselect.createSelector(selectResolvingUris, resolvingUris => resolvingUris && resolvingUris.indexOf(uri) !== -1); const selectPlayingUri = reselect.createSelector(selectState$1, state => state.playingUri); const selectChannelClaimCounts = reselect.createSelector(selectState$1, state => state.channelClaimCounts || {}); const makeSelectTotalItemsForChannel = uri => reselect.createSelector(selectChannelClaimCounts, byUri => byUri && byUri[uri]); const makeSelectTotalPagesForChannel = (uri, pageSize = 10) => reselect.createSelector(selectChannelClaimCounts, byUri => byUri && byUri[uri] && Math.ceil(byUri[uri] / pageSize)); const makeSelectNsfwCountFromUris = uris => reselect.createSelector(selectClaimsByUri, claims => uris.reduce((acc, uri) => { const claim = claims[uri]; if (claim && isClaimNsfw(claim)) { return acc + 1; } return acc; }, 0)); const makeSelectNsfwCountForChannel = uri => reselect.createSelector(selectClaimsById, selectAllClaimsByChannel, selectCurrentChannelPage, (byId, allClaims, page) => { const byChannel = allClaims[uri] || {}; const claimIds = byChannel[page || 1]; if (!claimIds) return 0; return claimIds.reduce((acc, claimId) => { const claim = byId[claimId]; if (isClaimNsfw(claim)) { return acc + 1; } return acc; }, 0); }); const makeSelectClaimIsNsfw = uri => reselect.createSelector(makeSelectClaimForUri(uri), // Eventually these will come from some list of tags that are considered adult // Or possibly come from users settings of what tags they want to hide // For now, there is just a hard coded list of tags inside `isClaimNsfw` // selectNaughtyTags(), claim => { if (!claim) { return false; } return isClaimNsfw(claim); }); const makeSelectRecommendedContentForUri = uri => reselect.createSelector(makeSelectClaimForUri(uri), selectSearchUrisByQuery, (claim, searchUrisByQuery) => { const atVanityURI = !uri.includes('#'); let recommendedContent; if (claim) { // If we are at a vanity uri, build the full uri so we can properly filter const currentUri = atVanityURI ? buildURI({ claimId: claim.claim_id, claimName: claim.name }) : uri; const { title } = claim.value; const searchQuery = getSearchQueryString(title.replace(/\//, ' ')); let searchUris = searchUrisByQuery[searchQuery]; if (searchUris) { searchUris = searchUris.filter(searchUri => searchUri !== currentUri); recommendedContent = searchUris; } } return recommendedContent; }); const makeSelectFirstRecommendedFileForUri = uri => reselect.createSelector(makeSelectRecommendedContentForUri(uri), recommendedContent => recommendedContent ? recommendedContent[0] : null); // Returns the associated channel uri for a given claim uri // accepts a regular claim uri lbry://something // returns the channel uri that created this claim lbry://@channel const makeSelectChannelForClaimUri = (uri, includePrefix = false) => reselect.createSelector(makeSelectClaimForUri(uri), claim => { if (!claim || !claim.signing_channel) { return null; } const { claim_id: claimId, name } = claim.signing_channel; let channel = `${name}#${claimId}`; return includePrefix ? `lbry://${channel}` : channel; }); const makeSelectTagsForUri = uri => reselect.createSelector(makeSelectMetadataForUri(uri), metadata => { return metadata && metadata.tags || []; }); const selectFetchingClaimSearch = reselect.createSelector(selectState$1, state => state.fetchingClaimSearch); const selectLastClaimSearchUris = reselect.createSelector(selectState$1, state => state.lastClaimSearchUris); const selectState$2 = state => state.wallet || {}; const selectWalletState = selectState$2; const selectWalletIsEncrypted = reselect.createSelector(selectState$2, state => state.walletIsEncrypted); const selectWalletEncryptPending = reselect.createSelector(selectState$2, state => state.walletEncryptPending); const selectWalletEncryptSucceeded = reselect.createSelector(selectState$2, state => state.walletEncryptSucceded); const selectWalletEncryptResult = reselect.createSelector(selectState$2, state => state.walletEncryptResult); const selectWalletDecryptPending = reselect.createSelector(selectState$2, state => state.walletDecryptPending); const selectWalletDecryptSucceeded = reselect.createSelector(selectState$2, state => state.walletDecryptSucceded); const selectWalletDecryptResult = reselect.createSelector(selectState$2, state => state.walletDecryptResult); const selectWalletUnlockPending = reselect.createSelector(selectState$2, state => state.walletUnlockPending); const selectWalletUnlockSucceeded = reselect.createSelector(selectState$2, state => state.walletUnlockSucceded); const selectWalletUnlockResult = reselect.createSelector(selectState$2, state => state.walletUnlockResult); const selectWalletLockPending = reselect.createSelector(selectState$2, state => state.walletLockPending); const selectWalletLockSucceeded = reselect.createSelector(selectState$2, state => state.walletLockSucceded); const selectWalletLockResult = reselect.createSelector(selectState$2, state => state.walletLockResult); const selectBalance = reselect.createSelector(selectState$2, state => state.balance); const selectTotalBalance = reselect.createSelector(selectState$2, state => state.totalBalance); const selectTransactionsById = reselect.createSelector(selectState$2, state => state.transactions || {}); const selectSupportsByOutpoint = reselect.createSelector(selectState$2, state => state.supports || {}); const selectTransactionItems = reselect.createSelector(selectTransactionsById, byId => { const items = []; Object.keys(byId).forEach(txid => { const tx = byId[txid]; // ignore dust/fees // it is fee only txn if all infos are also empty if (Math.abs(tx.value) === Math.abs(tx.fee) && tx.claim_info.length === 0 && tx.support_info.length === 0 && tx.update_info.length === 0 && tx.abandon_info.length === 0) { return; } const append = []; append.push(...tx.claim_info.map(item => Object.assign({}, tx, item, { type: item.claim_name[0] === '@' ? CHANNEL$1 : PUBLISH$1 }))); append.push(...tx.support_info.map(item => Object.assign({}, tx, item, { type: !item.is_tip ? SUPPORT : TIP }))); append.push(...tx.update_info.map(item => Object.assign({}, tx, item, { type: UPDATE }))); append.push(...tx.abandon_info.map(item => Object.assign({}, tx, item, { type: ABANDON }))); if (!append.length) { append.push(Object.assign({}, tx, { type: tx.value < 0 ? SPEND : RECEIVE })); } items.push(...append.map(item => { // value on transaction, amount on outpoint // amount is always positive, but should match sign of value const balanceDelta = parseFloat(item.balance_delta); const value = parseFloat(item.value); const amount = balanceDelta || value; const fee = parseFloat(tx.fee); return { txid, timestamp: tx.timestamp, date: tx.timestamp ? new Date(Number(tx.timestamp) * 1000) : null, amount, fee, claim_id: item.claim_id, claim_name: item.claim_name, type: item.type || SPEND, nout: item.nout, confirmations: tx.confirmations }; })); }); return items.sort((tx1, tx2) => { if (!tx1.timestamp && !tx2.timestamp) { return 0; } else if (!tx1.timestamp && tx2.timestamp) { return -1; } else if (tx1.timestamp && !tx2.timestamp) { return 1; } return tx2.timestamp - tx1.timestamp; }); }); const selectRecentTransactions = reselect.createSelector(selectTransactionItems, transactions => { const threshold = new Date(); threshold.setDate(threshold.getDate() - 7); return transactions.filter(transaction => { if (!transaction.date) { return true; // pending transaction } return transaction.date > threshold; }); }); const selectHasTransactions = reselect.createSelector(selectTransactionItems, transactions => transactions && transactions.length > 0); const selectIsFetchingTransactions = reselect.createSelector(selectState$2, state => state.fetchingTransactions); const selectIsSendingSupport = reselect.createSelector(selectState$2, state => state.sendingSupport); const selectReceiveAddress = reselect.createSelector(selectState$2, state => state.receiveAddress); const selectGettingNewAddress = reselect.createSelector(selectState$2, state => state.gettingNewAddress); const selectDraftTransaction = reselect.createSelector(selectState$2, state => state.draftTransaction || {}); const selectDraftTransactionAmount = reselect.createSelector(selectDraftTransaction, draft => draft.amount); const selectDraftTransactionAddress = reselect.createSelector(selectDraftTransaction, draft => draft.address); const selectDraftTransactionError = reselect.createSelector(selectDraftTransaction, draft => draft.error); const selectBlocks = reselect.createSelector(selectState$2, state => state.blocks); const selectCurrentHeight = reselect.createSelector(selectState$2, state => state.latestBlock); const selectTransactionListFilter = reselect.createSelector(selectState$2, state => state.transactionListFilter || ''); function formatCredits(amount, precision) { if (Number.isNaN(parseFloat(amount))) return '0'; return parseFloat(amount).toFixed(precision || 1).replace(/\.?0+$/, ''); } function formatFullPrice(amount, precision = 1) { let formated = ''; const quantity = amount.toString().split('.'); const fraction = quantity[1]; if (fraction) { const decimals = fraction.split(''); const first = decimals.filter(number => number !== '0')[0]; const index = decimals.indexOf(first); // Set format fraction formated = `.${fraction.substring(0, index + precision)}`; } return parseFloat(quantity[0] + formated); } function creditsToString(amount) { const creditString = parseFloat(amount).toFixed(8); return creditString; } function doUpdateBalance() { return (dispatch, getState) => { const { wallet: { balance: balanceInStore } } = getState(); lbryProxy.account_balance().then(balanceAsString => { const balance = parseFloat(balanceAsString); if (balanceInStore !== balance) { dispatch({ type: UPDATE_BALANCE, data: { balance } }); } }); }; } function doUpdateTotalBalance() { return (dispatch, getState) => { const { wallet: { totalBalance: totalBalanceInStore } } = getState(); lbryProxy.account_list().then(accountList => { const { lbc_mainnet: accounts } = accountList; const totalSatoshis = accounts.length === 1 ? accounts[0].satoshis : accounts.reduce((a, b) => a.satoshis + b.satoshis); const totalBalance = (Number.isNaN(totalSatoshis) ? 0 : totalSatoshis) / Math.pow(10, 8); if (totalBalanceInStore !== totalBalance) { dispatch({ type: UPDATE_TOTAL_BALANCE, data: { totalBalance } }); } }); }; } function doBalanceSubscribe() { return dispatch => { dispatch(doUpdateBalance()); setInterval(() => dispatch(doUpdateBalance()), 5000); }; } function doTotalBalanceSubscribe() { return dispatch => { dispatch(doUpdateTotalBalance()); setInterval(() => dispatch(doUpdateTotalBalance()), 5000); }; } function doFetchTransactions() { return dispatch => { dispatch(doFetchSupports()); dispatch({ type: FETCH_TRANSACTIONS_STARTED }); lbryProxy.utxo_release().then(() => lbryProxy.transaction_list()).then(results => { dispatch({ type: FETCH_TRANSACTIONS_COMPLETED, data: { transactions: results } }); }); }; } function doFetchSupports() { return dispatch => { dispatch({ type: FETCH_SUPPORTS_STARTED }); lbryProxy.support_list().then(results => { dispatch({ type: FETCH_SUPPORTS_COMPLETED, data: { supports: results } }); }); }; } function doGetNewAddress() { return dispatch => { dispatch({ type: GET_NEW_ADDRESS_STARTED }); lbryProxy.address_unused().then(address => { dispatch({ type: GET_NEW_ADDRESS_COMPLETED, data: { address } }); }); }; } function doCheckAddressIsMine(address) { return dispatch => { dispatch({ type: CHECK_ADDRESS_IS_MINE_STARTED }); lbryProxy.address_is_mine({ address }).then(isMine => { if (!isMine) dispatch(doGetNewAddress()); dispatch({ type: CHECK_ADDRESS_IS_MINE_COMPLETED }); }); }; } function doSendDraftTransaction(address, amount) { return (dispatch, getState) => { const state = getState(); const balance = selectBalance(state); if (balance - amount <= 0) { dispatch(doToast({ title: 'Insufficient credits', message: 'Insufficient credits' })); return; } dispatch({ type: SEND_TRANSACTION_STARTED }); const successCallback = response => { if (response.txid) { dispatch({ type: SEND_TRANSACTION_COMPLETED }); dispatch(doToast({ message: `You sent ${amount} LBC`, linkText: 'History', linkTarget: '/wallet' })); } else { dispatch({ type: SEND_TRANSACTION_FAILED, data: { error: response } }); dispatch(doToast({ message: 'Transaction failed', isError: true })); } }; const errorCallback = error => { dispatch({ type: SEND_TRANSACTION_FAILED, data: { error: error.message } }); dispatch(doToast({ message: 'Transaction failed', isError: true })); }; lbryProxy.account_send({ addresses: [address], amount: creditsToString(amount) }).then(successCallback, errorCallback); }; } function doSetDraftTransactionAmount(amount) { return { type: SET_DRAFT_TRANSACTION_AMOUNT, data: { amount } }; } function doSetDraftTransactionAddress(address) { return { type: SET_DRAFT_TRANSACTION_ADDRESS, data: { address } }; } function doSendTip(amount, claimId, uri, successCallback, errorCallback) { return (dispatch, getState) => { const state = getState(); const balance = selectBalance(state); if (balance - amount <= 0) { dispatch(doToast({ message: 'Insufficient credits', isError: true })); return; } const success = () => { dispatch(doToast({ message: __(`You sent ${amount} LBC as a tip, Mahalo!`), linkText: __('History'), linkTarget: __('/wallet') })); dispatch({ type: SUPPORT_TRANSACTION_COMPLETED }); if (successCallback) { successCallback(); } }; const error = err => { dispatch(doToast({ message: __(`There was an error sending support funds.`), isError: true })); dispatch({ type: SUPPORT_TRANSACTION_FAILED, data: { error: err } }); if (errorCallback) { errorCallback(); } }; dispatch({ type: SUPPORT_TRANSACTION_STARTED }); lbryProxy.support_create({ claim_id: claimId, amount: creditsToString(amount), tip: true }).then(success, error); }; } function doWalletEncrypt(newPassword) { return dispatch => { dispatch({ type: WALLET_ENCRYPT_START }); lbryProxy.account_encrypt({ new_password: newPassword }).then(result => { if (result === true) { dispatch({ type: WALLET_ENCRYPT_COMPLETED, result }); } else { dispatch({ type: WALLET_ENCRYPT_FAILED, result }); } }); }; } function doWalletUnlock(password) { return dispatch => { dispatch({ type: WALLET_UNLOCK_START }); lbryProxy.account_unlock({ password }).then(result => { if (result === true) { dispatch({ type: WALLET_UNLOCK_COMPLETED, result }); } else { dispatch({ type: WALLET_UNLOCK_FAILED, result }); } }); }; } function doWalletDecrypt() { return dispatch => { dispatch({ type: WALLET_DECRYPT_START }); lbryProxy.account_decrypt().then(result => { if (result === true) { dispatch({ type: WALLET_DECRYPT_COMPLETED, result }); } else { dispatch({ type: WALLET_DECRYPT_FAILED, result }); } }); }; } function doWalletStatus() { return dispatch => { dispatch({ type: WALLET_STATUS_START }); lbryProxy.status().then(status => { if (status && status.wallet) { dispatch({ type: WALLET_STATUS_COMPLETED, result: status.wallet.is_encrypted }); } }); }; } function doSetTransactionListFilter(filterOption) { return { type: SET_TRANSACTION_LIST_FILTER, data: filterOption }; } function doUpdateBlockHeight() { return dispatch => lbryProxy.status().then(status => { if (status.wallet) { dispatch({ type: UPDATE_CURRENT_HEIGHT, data: status.wallet.blocks }); } }); } // https://github.com/reactjs/redux/issues/911 function batchActions(...actions) { return { type: 'BATCH_ACTIONS', actions }; } var _extends$3 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function doResolveUris(uris, returnCachedClaims = false) { return (dispatch, getState) => { const normalizedUris = uris.map(normalizeURI); const state = getState(); const resolvingUris = selectResolvingUris(state); const claimsByUri = selectClaimsByUri(state); const urisToResolve = normalizedUris.filter(uri => { if (resolvingUris.includes(uri)) { return false; } return returnCachedClaims ? !claimsByUri[uri] : true; }); if (urisToResolve.length === 0) { return; } dispatch({ type: RESOLVE_URIS_STARTED, data: { uris: normalizedUris } }); const resolveInfo = {}; lbryProxy.resolve({ urls: urisToResolve }).then(result => { Object.entries(result).forEach(([uri, uriResolveInfo]) => { const fallbackResolveInfo = { stream: null, claimsInChannel: null, channel: null }; // Flow has terrible Object.entries support // https://github.com/facebook/flow/issues/2221 if (uriResolveInfo) { if (uriResolveInfo.error) { resolveInfo[uri] = _extends$3({}, fallbackResolveInfo); } else { let result = {}; if (uriResolveInfo.value_type === 'channel') { result.channel = uriResolveInfo; // $FlowFixMe result.claimsInChannel = uriResolveInfo.meta.claims_in_channel; } else { result.stream = uriResolveInfo; if (uriResolveInfo.signing_channel) { result.channel = uriResolveInfo.signing_channel; result.claimsInChannel = uriResolveInfo.signing_channel.meta && uriResolveInfo.signing_channel.meta.claims_in_channel || 0; } } // $FlowFixMe resolveInfo[uri] = result; } } }); dispatch({ type: RESOLVE_URIS_COMPLETED, data: { resolveInfo } }); }); }; } function doResolveUri(uri) { return doResolveUris([uri]); } function doFetchClaimListMine() { return dispatch => { dispatch({ type: FETCH_CLAIM_LIST_MINE_STARTED }); lbryProxy.claim_list().then(claims => { dispatch({ type: FETCH_CLAIM_LIST_MINE_COMPLETED, data: { claims } }); }); }; } function doAbandonClaim(txid, nout) { const outpoint = `${txid}:${nout}`; return (dispatch, getState) => { const state = getState(); const myClaims = selectMyClaimsRaw(state); const mySupports = selectSupportsByOutpoint(state); // A user could be trying to abandon a support or one of their claims const claimToAbandon = myClaims.find(claim => claim.txid === txid && claim.nout === nout); const supportToAbandon = mySupports[outpoint]; if (!claimToAbandon && !supportToAbandon) { console.error('No associated support or claim with txid: ', txid); return; } const data = claimToAbandon ? { claimId: claimToAbandon.claim_id } : { outpoint: `${supportToAbandon.txid}:${supportToAbandon.nout}` }; const isClaim = !!claimToAbandon; const startedActionType = isClaim ? ABANDON_CLAIM_STARTED : ABANDON_SUPPORT_STARTED; const completedActionType = isClaim ? ABANDON_CLAIM_STARTED : ABANDON_SUPPORT_COMPLETED; dispatch({ type: startedActionType, data }); const errorCallback = () => { dispatch(doToast({ message: isClaim ? 'Error abandoning your claim' : 'Error unlocking your tip', isError: true })); }; const successCallback = () => { dispatch({ type: completedActionType, data }); dispatch(doToast({ message: isClaim ? 'Successfully abandoned your claim' : 'Successfully unlocked your tip!' })); // After abandoning, call claim_list to show the claim as abandoned // Also fetch transactions to show the new abandon transaction dispatch(doFetchClaimListMine()); dispatch(doFetchTransactions()); }; const abandonParams = { txid, nout, blocking: true }; let method; if (supportToAbandon) { method = 'support_abandon'; } else if (claimToAbandon) { const { name: claimName } = claimToAbandon; method = claimName.startsWith('@') ? 'channel_abandon' : 'stream_abandon'; } if (!method) { console.error('No "method" chosen for claim or support abandon'); return; } lbryProxy[method](abandonParams).then(successCallback, errorCallback); }; } function doFetchClaimsByChannel(uri, page = 1) { return dispatch => { dispatch({ type: FETCH_CHANNEL_CLAIMS_STARTED, data: { uri, page } }); lbryProxy.claim_search({ channel: uri, valid_channel_signature: true, page: page || 1, order_by: ['release_time'] }).then(result => { const { items: claimsInChannel, page: returnedPage } = result; dispatch({ type: FETCH_CHANNEL_CLAIMS_COMPLETED, data: { uri, claims: claimsInChannel || [], page: returnedPage || undefined } }); }); }; } function doCreateChannel(name, amount) { return dispatch => { dispatch({ type: CREATE_CHANNEL_STARTED }); return lbryProxy.channel_create({ name, bid: creditsToString(amount) }) // outputs[0] is the certificate // outputs[1] is the change from the tx, not in the app currently .then(result => { const channelClaim = result.outputs[0]; dispatch({ type: CREATE_CHANNEL_COMPLETED, data: { channelClaim } }); }).catch(error => { dispatch({ type: CREATE_CHANNEL_FAILED, data: error }); }); }; } function doFetchChannelListMine() { return dispatch => { dispatch({ type: FETCH_CHANNEL_LIST_STARTED }); const callback = channels => { dispatch({ type: FETCH_CHANNEL_LIST_COMPLETED, data: { claims: channels } }); }; lbryProxy.channel_list().then(callback); }; } function doClaimSearch(amount = 20, options = {}) { return dispatch => { dispatch({ type: CLAIM_SEARCH_STARTED }); const success = data => { const resolveInfo = {}; const uris = []; data.items.forEach(stream => { resolveInfo[stream.permanent_url] = { stream }; uris.push(stream.permanent_url); }); dispatch({ type: CLAIM_SEARCH_COMPLETED, data: { resolveInfo, uris, append: options.page && options.page !== 1 } }); }; const failure = err => { dispatch({ type: CLAIM_SEARCH_FAILED, error: err }); }; lbryProxy.claim_search(_extends$3({ page_size: amount }, options)).then(success, failure); }; } const selectState$3 = state => state.fileInfo || {}; const selectFileInfosByOutpoint = reselect.createSelector(selectState$3, state => state.byOutpoint || {}); const selectIsFetchingFileList = reselect.createSelector(selectState$3, state => state.isFetchingFileList); const selectIsFetchingFileListDownloadedOrPublished = reselect.createSelector(selectIsFetchingFileList, selectIsFetchingClaimListMine, (isFetchingFileList, isFetchingClaimListMine) => isFetchingFileList || isFetchingClaimListMine); const makeSelectFileInfoForUri = uri => reselect.createSelector(selectClaimsByUri, selectFileInfosByOutpoint, (claims, byOutpoint) => { const claim = claims[uri]; const outpoint = claim ? `${claim.txid}:${claim.nout}` : undefined; return outpoint ? byOutpoint[outpoint] : undefined; }); const selectDownloadingByOutpoint = reselect.createSelector(selectState$3, state => state.downloadingByOutpoint || {}); const makeSelectDownloadingForUri = uri => reselect.createSelector(selectDownloadingByOutpoint, makeSelectFileInfoForUri(uri), (byOutpoint, fileInfo) => { if (!fileInfo) return false; return byOutpoint[fileInfo.outpoint]; }); const selectUrisLoading = reselect.createSelector(selectState$3, state => state.urisLoading || {}); const makeSelectLoadingForUri = uri => reselect.createSelector(selectUrisLoading, byUri => byUri && byUri[uri]); const selectFileInfosDownloaded = reselect.createSelector(selectFileInfosByOutpoint, selectMyClaims, (byOutpoint, myClaims) => Object.values(byOutpoint).filter(fileInfo => { const myClaimIds = myClaims.map(claim => claim.claim_id); return fileInfo && myClaimIds.indexOf(fileInfo.claim_id) === -1 && (fileInfo.completed || fileInfo.written_bytes); })); // export const selectFileInfoForUri = (state, props) => { // const claims = selectClaimsByUri(state), // claim = claims[props.uri], // fileInfos = selectAllFileInfos(state), // outpoint = claim ? `${claim.txid}:${claim.nout}` : undefined; // return outpoint && fileInfos ? fileInfos[outpoint] : undefined; // }; const selectDownloadingFileInfos = reselect.createSelector(selectDownloadingByOutpoint, selectFileInfosByOutpoint, (downloadingByOutpoint, fileInfosByOutpoint) => { const outpoints = Object.keys(downloadingByOutpoint); const fileInfos = []; outpoints.forEach(outpoint => { const fileInfo = fileInfosByOutpoint[outpoint]; if (fileInfo) fileInfos.push(fileInfo); }); return fileInfos; }); const selectTotalDownloadProgress = reselect.createSelector(selectDownloadingFileInfos, fileInfos => { const progress = []; fileInfos.forEach(fileInfo => { progress.push(fileInfo.written_bytes / fileInfo.total_bytes * 100); }); const totalProgress = progress.reduce((a, b) => a + b, 0); if (fileInfos.length > 0) return totalProgress / fileInfos.length / 100.0; return -1; }); const selectSearchDownloadUris = query => reselect.createSelector(selectFileInfosDownloaded, selectClaimsById, (fileInfos, claimsById) => { if (!query || !fileInfos.length) { return null; } const queryParts = query.toLowerCase().split(' '); const searchQueryDictionary = {}; queryParts.forEach(subQuery => { searchQueryDictionary[subQuery] = subQuery; }); const arrayContainsQueryPart = array => { for (let i = 0; i < array.length; i += 1) { const subQuery = array[i]; if (searchQueryDictionary[subQuery]) { return true; } } return false; }; const downloadResultsFromQuery = []; fileInfos.forEach(fileInfo => { const { channel_name: channelName, claim_name: claimName, metadata } = fileInfo; const { author, description, title } = metadata; if (channelName) { const lowerCaseChannel = channelName.toLowerCase(); const strippedOutChannelName = lowerCaseChannel.slice(1); // trim off the @ if (searchQueryDictionary[channelName] || searchQueryDictionary[strippedOutChannelName]) { downloadResultsFromQuery.push(fileInfo); return; } } const nameParts = claimName.toLowerCase().split('-'); if (arrayContainsQueryPart(nameParts)) { downloadResultsFromQuery.push(fileInfo); return; } if (title) { const titleParts = title.toLowerCase().split(' '); if (arrayContainsQueryPart(titleParts)) { downloadResultsFromQuery.push(fileInfo); return; } } if (author) { const authorParts = author.toLowerCase().split(' '); if (arrayContainsQueryPart(authorParts)) { downloadResultsFromQuery.push(fileInfo); return; } } if (description) { const descriptionParts = description.toLowerCase().split(' '); if (arrayContainsQueryPart(descriptionParts)) { downloadResultsFromQuery.push(fileInfo); } } }); return downloadResultsFromQuery.length ? downloadResultsFromQuery.map(fileInfo => { const { channel_name: channelName, claim_id: claimId, claim_name: claimName } = fileInfo; const uriParams = {}; if (channelName) { const claim = claimsById[claimId]; if (claim && claim.signing_channel) { uriParams.claimId = claim.signing_channel.claim_id; } else { uriParams.claimId = claimId; } uriParams.channelName = channelName; uriParams.contentName = claimName; } else { uriParams.claimId = claimId; uriParams.claimName = claimName; } const uri = buildURI(uriParams); return uri; }) : null; }); const selectFileListPublishedSort = reselect.createSelector(selectState$3, state => state.fileListPublishedSort); const selectFileListDownloadedSort = reselect.createSelector(selectState$3, state => state.fileListDownloadedSort); const selectDownloadedUris = reselect.createSelector(selectFileInfosDownloaded, // We should use permament_url but it doesn't exist in file_list info => info.slice().reverse().map(claim => console.log(claim) || `lbry://${claim.claim_name}#${claim.claim_id}`)); // const selectState$4 = state => state.file || {}; const selectPurchaseUriErrorMessage = reselect.createSelector(selectState$4, state => state.purchaseUriErrorMessage); const selectFailedPurchaseUris = reselect.createSelector(selectState$4, state => state.failedPurchaseUris); const selectPurchasedUris = reselect.createSelector(selectState$4, state => state.purchasedUris); const selectPurchasedStreamingUrls = reselect.createSelector(selectState$4, state => state.purchasedStreamingUrls); const selectLastPurchasedUri = reselect.createSelector(selectState$4, state => state.purchasedUris.length > 0 ? state.purchasedUris[state.purchasedUris.length - 1] : null); const makeSelectStreamingUrlForUri = uri => reselect.createSelector(selectPurchasedStreamingUrls, streamingUrls => streamingUrls && streamingUrls[uri]); // function doFileGet(uri, saveFile = true) { return dispatch => { dispatch({ type: LOADING_FILE_STARTED, data: { uri } }); // set save_file argument to True to save the file (old behaviour) lbryProxy.get({ uri, save_file: saveFile }).then(streamInfo => { const timeout = streamInfo === null || typeof streamInfo !== 'object'; if (timeout) { dispatch({ type: LOADING_FILE_FAILED, data: { uri } }); dispatch({ type: PURCHASE_URI_FAILED, data: { uri } }); dispatch(doToast({ message: `File timeout for uri ${uri}`, isError: true })); } else { // purchase was completed successfully const { streaming_url: streamingUrl } = streamInfo; dispatch({ type: PURCHASE_URI_COMPLETED, data: { uri, streamingUrl: !saveFile && streamingUrl ? streamingUrl : null } }); } }).catch(() => { dispatch({ type: LOADING_FILE_FAILED, data: { uri } }); dispatch({ type: PURCHASE_URI_FAILED, data: { uri } }); dispatch(doToast({ message: `Failed to download ${uri}, please try again. If this problem persists, visit https://lbry.com/faq/support for support.`, isError: true })); }); }; } function doPurchaseUri(uri, costInfo, saveFile = true) { return (dispatch, getState) => { dispatch({ type: PURCHASE_URI_STARTED, data: { uri } }); const state = getState(); const balance = selectBalance(state); const fileInfo = makeSelectFileInfoForUri(uri)(state); const downloadingByOutpoint = selectDownloadingByOutpoint(state); const alreadyDownloading = fileInfo && !!downloadingByOutpoint[fileInfo.outpoint]; const alreadyStreaming = makeSelectStreamingUrlForUri(uri)(state); if (alreadyDownloading || alreadyStreaming) { dispatch({ type: PURCHASE_URI_FAILED, data: { uri, error: `Already fetching uri: ${uri}` } }); return; } const { cost } = costInfo; if (parseFloat(cost) > balance) { dispatch({ type: PURCHASE_URI_FAILED, data: { uri, error: 'Insufficient credits' } }); return; } dispatch(doFileGet(uri, saveFile)); }; } function doDeletePurchasedUri(uri) { return { type: DELETE_PURCHASED_URI, data: { uri } }; } function doFetchFileInfo(uri) { return (dispatch, getState) => { const state = getState(); const claim = selectClaimsByUri(state)[uri]; const outpoint = claim ? `${claim.txid}:${claim.nout}` : null; const alreadyFetching = !!selectUrisLoading(state)[uri]; if (!alreadyFetching) { dispatch({ type: FETCH_FILE_INFO_STARTED, data: { outpoint } }); lbryProxy.file_list({ outpoint, full_status: true }).then(fileInfos => { dispatch({ type: FETCH_FILE_INFO_COMPLETED, data: { outpoint, fileInfo: fileInfos && fileInfos.length ? fileInfos[0] : null } }); }); } }; } function doFileList() { return (dispatch, getState) => { const state = getState(); const isFetching = selectIsFetchingFileList(state); if (!isFetching) { dispatch({ type: FILE_LIST_STARTED }); lbryProxy.file_list().then(fileInfos => { dispatch({ type: FILE_LIST_SUCCEEDED, data: { fileInfos } }); }); } }; } function doFetchFileInfosAndPublishedClaims() { return (dispatch, getState) => { const state = getState(); const isFetchingClaimListMine = selectIsFetchingClaimListMine(state); const isFetchingFileInfo = selectIsFetchingFileList(state); if (!isFetchingClaimListMine) dispatch(doFetchClaimListMine()); if (!isFetchingFileInfo) dispatch(doFileList()); }; } function doSetFileListSort(page, value) { return { type: SET_FILE_LIST_SORT, data: { page, value } }; } // const formatLbryUriForWeb = uri => { const { claimName, claimId } = parseURI(uri); let webUrl = `/${claimName}`; if (claimId) { webUrl += `/${claimId}`; } return webUrl; }; var _extends$4 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const doResetThumbnailStatus = () => dispatch => { dispatch({ type: UPDATE_PUBLISH_FORM, data: { thumbnailPath: '' } }); return fetch('https://spee.ch/api/config/site/publishing').then(res => res.json()).then(status => { if (status.disabled) { throw Error(); } return dispatch({ type: UPDATE_PUBLISH_FORM, data: { uploadThumbnailStatus: READY, thumbnail: '' } }); }).catch(() => dispatch({ type: UPDATE_PUBLISH_FORM, data: { uploadThumbnailStatus: API_DOWN, thumbnail: '' } })); }; const doClearPublish = () => dispatch => { dispatch({ type: CLEAR_PUBLISH }); return dispatch(doResetThumbnailStatus()); }; const doUpdatePublishForm = publishFormValue => dispatch => dispatch({ type: UPDATE_PUBLISH_FORM, data: _extends$4({}, publishFormValue) }); const doUploadThumbnail = (filePath, thumbnailBuffer, fsAdapter) => dispatch => { let thumbnail, fileExt, fileName, fileType; const makeid = () => { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < 24; i += 1) text += possible.charAt(Math.floor(Math.random() * 62)); return text; }; const uploadError = (error = '') => { dispatch(batchActions({ type: UPDATE_PUBLISH_FORM, data: { uploadThumbnailStatus: READY, thumbnail: '', nsfw: false } }, doError(error))); }; dispatch({ type: UPDATE_PUBLISH_FORM, data: { uploadThumbnailStatus: IN_PROGRESS } }); if (fsAdapter && fsAdapter.readFile && filePath) { fsAdapter.readFile(filePath, 'base64').then(base64Image => { fileExt = 'png'; fileName = 'thumbnail.png'; fileType = 'image/png'; const data = new FormData(); const name = makeid(); data.append('name', name); data.append('file', { uri: 'file://' + filePath, type: fileType, name: fileName }); return fetch('https://spee.ch/api/claim/publish', { method: 'POST', body: data }).then(response => response.json()).then(json => json.success ? dispatch({ type: UPDATE_PUBLISH_FORM, data: { uploadThumbnailStatus: COMPLETE, thumbnail: `${json.data.url}${fileExt}` } }) : uploadError(json.message)).catch(err => uploadError(err.message)); }); } else { if (filePath) { thumbnail = fs.readFileSync(filePath); fileExt = path.extname(filePath); fileName = path.basename(filePath); fileType = `image/${fileExt.slice(1)}`; } else if (thumbnailBuffer) { thumbnail = thumbnailBuffer; fileExt = '.png'; fileName = 'thumbnail.png'; fileType = 'image/png'; } else { return null; } const data = new FormData(); const name = makeid(); const file = new File([thumbnail], fileName, { type: fileType }); data.append('name', name); data.append('file', file); return fetch('https://spee.ch/api/claim/publish', { method: 'POST', body: data }).then(response => response.json()).then(json => json.success ? dispatch({ type: UPDATE_PUBLISH_FORM, data: { uploadThumbnailStatus: COMPLETE, thumbnail: `${json.data.url}${fileExt}` } }) : uploadError(json.message)).catch(err => uploadError(err.message)); } }; const doPrepareEdit = (claim, uri, fileInfo) => dispatch => { const { name, amount, channel_name: channelName, value } = claim; const { author, description, // use same values as default state // fee will be undefined for free content fee = { amount: 0, currency: 'LBC' }, languages, license, license_url: licenseUrl, thumbnail, title } = value; const publishData = { name, channel: channelName, bid: amount, contentIsFree: !fee.amount, author, description, fee: { amount: fee.amount, currency: fee.currency }, languages, thumbnail: thumbnail ? thumbnail.url : null, title, uri, uploadThumbnailStatus: thumbnail ? MANUAL : undefined, licenseUrl, nsfw: isClaimNsfw(claim) }; // Make sure custom liscence's are mapped properly // If the license isn't one of the standard licenses, map the custom license and description/url if (!CC_LICENSES.some(({ value }) => value === license)) { if (!license || license === NONE || license === PUBLIC_DOMAIN) { publishData.licenseType = license; } else if (license && !licenseUrl && license !== NONE) { publishData.licenseType = COPYRIGHT; } else { publishData.licenseType = OTHER; } publishData.otherLicenseDescription = license; } else { publishData.licenseType = license; } if (fileInfo && fileInfo.download_path) { try { fs.accessSync(fileInfo.download_path, fs.constants.R_OK); publishData.filePath = fileInfo.download_path; } catch (e) { console.error(e.name, e.message); } } dispatch({ type: DO_PREPARE_EDIT, data: publishData }); }; const doPublish = params => (dispatch, getState) => { dispatch({ type: PUBLISH_START }); const state = getState(); const myChannels = selectMyChannelClaims(state); const myClaims = selectMyClaimsWithoutChannels(state); const { name, bid, filePath, description, language, license, licenseUrl, thumbnail, channel, title, contentIsFree, fee, uri, nsfw, claim } = params; // get the claim id from the channel name, we will use that instead const namedChannelClaim = myChannels.find(myChannel => myChannel.name === channel); const channelId = namedChannelClaim ? namedChannelClaim.claim_id : ''; const publishPayload = { name, bid: creditsToString(bid), title, license, languages: [language], description, tags: claim && claim.value.tags || [], locations: claim && claim.value.locations }; // Temporary solution to keep the same publish flow with the new tags api // Eventually we will allow users to enter their own tags on publish // `nsfw` will probably be removed if (licenseUrl) { publishPayload.license_url = licenseUrl; } if (thumbnail) { publishPayload.thumbnail_url = thumbnail; } if (claim && claim.value.release_time) { publishPayload.release_time = Number(claim.value.release_time); } if (nsfw) { if (!publishPayload.tags.includes('mature')) { publishPayload.tags.push('mature'); } } else { const indexToRemove = publishPayload.tags.indexOf('mature'); if (indexToRemove > -1) { publishPayload.tags.splice(indexToRemove, 1); } } if (channelId) { publishPayload.channel_id = channelId; } if (!contentIsFree && fee && fee.currency && Number(fee.amount) > 0) { publishPayload.fee_currency = fee.currency; publishPayload.fee_amount = creditsToString(fee.amount); } // Only pass file on new uploads, not metadata only edits. // The sdk will figure it out if (filePath) publishPayload.file_path = filePath; const success = successResponse => { //analytics.apiLogPublish(); const pendingClaim = successResponse.outputs[0]; const actions = []; actions.push({ type: PUBLISH_SUCCESS }); //actions.push(doOpenModal(MODALS.PUBLISH, { uri })); // We have to fake a temp claim until the new pending one is returned by claim_list_mine // We can't rely on claim_list_mine because there might be some delay before the new claims are returned // Doing this allows us to show the pending claim immediately, it will get overwritten by the real one const isMatch = claim => claim.claim_id === pendingClaim.claim_id; const isEdit = myClaims.some(isMatch); const myNewClaims = isEdit ? myClaims.map(claim => isMatch(claim) ? pendingClaim : claim) : myClaims.concat(pendingClaim); actions.push({ type: FETCH_CLAIM_LIST_MINE_COMPLETED, data: { claims: myNewClaims } }); dispatch(batchActions(...actions)); }; const failure = error => { dispatch({ type: PUBLISH_FAIL }); dispatch(doError(error.message)); }; return lbryProxy.publish(publishPayload).then(success, failure); }; // Calls claim_list_mine until any pending publishes are confirmed const doCheckPendingPublishes = () => (dispatch, getState) => { const state = getState(); const pendingById = selectPendingById(state); if (!Object.keys(pendingById).length) { return; } let publishCheckInterval; const checkFileList = () => { lbryProxy.claim_list().then(claims => { claims.forEach(claim => { // If it's confirmed, check if it was pending previously if (claim.confirmations > 0 && pendingById[claim.claim_id]) { delete pendingById[claim.claim_id]; // If it's confirmed, check if we should notify the user if (selectosNotificationsEnabled(getState())) { const notif = new window.Notification('LBRY Publish Complete', { body: `${claim.value.title} has been published to lbry://${claim.name}. Click here to view it`, silent: false }); notif.onclick = () => { dispatch(push(formatLbryUriForWeb(claim.permanent_url))); }; } } }); dispatch({ type: FETCH_CLAIM_LIST_MINE_COMPLETED, data: { claims } }); if (!Object.keys(pendingById).length) { clearInterval(publishCheckInterval); } }); }; publishCheckInterval = setInterval(() => { checkFileList(); }, 30000); }; // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. function debouce(func, wait, immediate) { let timeout; return function () { const context = this; const args = arguments; const later = () => { timeout = null; if (!immediate) func.apply(context, args); }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; } // function handleFetchResponse(response) { return response.status === 200 ? Promise.resolve(response.json()) : Promise.reject(new Error(response.statusText)); } // const DEBOUNCED_SEARCH_SUGGESTION_MS = 300; // We can't use env's because they aren't passed into node_modules let CONNECTION_STRING = 'https://lighthouse.lbry.com/'; const setSearchApi = endpoint => { CONNECTION_STRING = endpoint.replace(/\/*$/, '/'); // exactly one slash at the end; }; const getSearchSuggestions = value => (dispatch, getState) => { const query = value.trim(); // strip out any basic stuff for more accurate search results let searchValue = query.replace(/lbry:\/\//g, '').replace(/-/g, ' '); if (searchValue.includes('#')) { // This should probably be more robust, but I think it's fine for now // Remove everything after # to get rid of the claim id searchValue = searchValue.substring(0, searchValue.indexOf('#')); } const suggestions = selectSuggestions(getState()); if (suggestions[searchValue]) { return; } fetch(`${CONNECTION_STRING}autocomplete?s=${searchValue}`).then(handleFetchResponse).then(apiSuggestions => { dispatch({ type: UPDATE_SEARCH_SUGGESTIONS, data: { query: searchValue, suggestions: apiSuggestions } }); }).catch(() => { // If the fetch fails, do nothing // Basic search suggestions are already populated at this point }); }; const throttledSearchSuggestions = debouce((dispatch, query) => { dispatch(getSearchSuggestions(query)); }, DEBOUNCED_SEARCH_SUGGESTION_MS); const doUpdateSearchQuery = (query, shouldSkipSuggestions) => dispatch => { dispatch({ type: UPDATE_SEARCH_QUERY, data: { query } }); // Don't fetch new suggestions if the user just added a space if (!query.endsWith(' ') || !shouldSkipSuggestions) { throttledSearchSuggestions(dispatch, query); } }; const doSearch = (rawQuery, // pass in a query if you don't want to search for what's in the search bar size, // only pass in if you don't want to use the users setting (ex: related content) from, isBackgroundSearch = false) => (dispatch, getState) => { const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' '); if (!query) { dispatch({ type: SEARCH_FAIL }); return; } const state = getState(); const queryWithOptions = makeSelectQueryWithOptions(query, size, from, isBackgroundSearch)(state); // If we have already searched for something, we don't need to do anything const urisForQuery = makeSelectSearchUris(queryWithOptions)(state); if (urisForQuery && !!urisForQuery.length) { return; } dispatch({ type: SEARCH_START }); // If the user is on the file page with a pre-populated uri and they select // the search option without typing anything, searchQuery will be empty // We need to populate it so the input is filled on the search page // isBackgroundSearch means the search is happening in the background, don't update the search query if (!state.search.searchQuery && !isBackgroundSearch) { dispatch(doUpdateSearchQuery(query)); } fetch(`${CONNECTION_STRING}search?${queryWithOptions}`).then(handleFetchResponse).then(data => { const uris = []; const actions = []; data.forEach(result => { if (result.name) { const uri = buildURI({ claimName: result.name, claimId: result.claimId }); actions.push(doResolveUri(uri)); uris.push(uri); } }); actions.push({ type: SEARCH_SUCCESS, data: { query: queryWithOptions, uris } }); dispatch(batchActions(...actions)); }).catch(e => { dispatch({ type: SEARCH_FAIL }); }); }; const doFocusSearchInput = () => dispatch => dispatch({ type: SEARCH_FOCUS }); const doBlurSearchInput = () => dispatch => dispatch({ type: SEARCH_BLUR }); const doUpdateSearchOptions = newOptions => (dispatch, getState) => { const state = getState(); const searchValue = selectSearchValue(state); dispatch({ type: UPDATE_SEARCH_OPTIONS, data: newOptions }); if (searchValue) { // After updating, perform a search with the new options dispatch(doSearch(searchValue)); } }; function savePosition(claimId, outpoint, position) { return dispatch => { dispatch({ type: SET_CONTENT_POSITION, data: { claimId, outpoint, position } }); }; } // const doToggleTagFollow = name => ({ type: TOGGLE_TAG_FOLLOW, data: { name } }); const doAddTag = name => ({ type: TAG_ADD, data: { name } }); const doDeleteTag = name => ({ type: TAG_DELETE, data: { name } }); // function doCommentList(uri) { return (dispatch, getState) => { const state = getState(); const claim = selectClaimsByUri(state)[uri]; const claimId = claim ? claim.claim_id : null; dispatch({ type: COMMENT_LIST_STARTED }); lbryProxy.comment_list({ claim_id: claimId }).then(results => { dispatch({ type: COMMENT_LIST_COMPLETED, data: { comments: results, claimId: claimId, uri: uri } }); }).catch(error => { console.log(error); dispatch({ type: COMMENT_LIST_FAILED, data: error }); }); }; } function doCommentCreate(comment = '', claim_id = '', channel, parent_id) { return (dispatch, getState) => { const state = getState(); dispatch({ type: COMMENT_CREATE_STARTED }); const myChannels = selectMyChannelClaims(state); const namedChannelClaim = myChannels.find(myChannel => myChannel.name === channel); const channel_id = namedChannelClaim ? namedChannelClaim.claim_id : null; return lbryProxy.comment_create({ comment, claim_id, channel_id }).then(result => { dispatch({ type: COMMENT_CREATE_COMPLETED, data: { comment: result, claimId: claim_id } }); }).catch(error => { dispatch({ type: COMMENT_CREATE_FAILED, data: error }); dispatch(doToast({ message: 'Oops, someone broke comments.', isError: true })); }); }; } var _extends$5 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const reducers = {}; const defaultState = { byId: {}, claimsByUri: {}, claimsByChannel: {}, channelClaimCounts: {}, fetchingChannelClaims: {}, resolvingUris: [], // This should not be a Set // Storing sets in reducers can cause issues myChannelClaims: new Set(), fetchingMyChannels: false, abandoningById: {}, pendingById: {}, fetchingClaimSearch: false, lastClaimSearchUris: [] }; function handleClaimAction(state, action) { const { resolveInfo } = action.data; const byUri = Object.assign({}, state.claimsByUri); const byId = Object.assign({}, state.byId); const channelClaimCounts = Object.assign({}, state.channelClaimCounts); Object.entries(resolveInfo).forEach(([uri, resolveResponse]) => { // $FlowFixMe if (resolveResponse.claimsInChannel) { // $FlowFixMe channelClaimCounts[uri] = resolveResponse.claimsInChannel; } }); // $FlowFixMe Object.entries(resolveInfo).forEach(([uri, { channel, stream }]) => { if (stream) { byId[stream.claim_id] = stream; byUri[uri] = stream.claim_id; } if (channel) { byId[channel.claim_id] = channel; byUri[stream ? channel.permanent_url : uri] = channel.claim_id; } if (!stream && !channel) { byUri[uri] = null; } }); return Object.assign({}, state, { byId, claimsByUri: byUri, channelClaimCounts, resolvingUris: (state.resolvingUris || []).filter(uri => !resolveInfo[uri]) }); } reducers[RESOLVE_URIS_COMPLETED] = (state, action) => { return _extends$5({}, handleClaimAction(state, action)); }; reducers[FETCH_CLAIM_LIST_MINE_STARTED] = state => Object.assign({}, state, { isFetchingClaimListMine: true }); reducers[FETCH_CLAIM_LIST_MINE_COMPLETED] = (state, action) => { const { claims } = action.data; const byId = Object.assign({}, state.byId); const byUri = Object.assign({}, state.claimsByUri); const pendingById = Object.assign({}, state.pendingById); claims.forEach(claim => { const uri = buildURI({ claimName: claim.name, claimId: claim.claim_id }); if (claim.type && claim.type.match(/claim|update/)) { if (claim.confirmations < 1) { pendingById[claim.claim_id] = claim; delete byId[claim.claim_id]; delete byUri[claim.claim_id]; } else { byId[claim.claim_id] = claim; byUri[uri] = claim.claim_id; } } }); // Remove old pending publishes Object.values(pendingById) // $FlowFixMe .filter(pendingClaim => byId[pendingClaim.claim_id]).forEach(pendingClaim => { // $FlowFixMe delete pendingById[pendingClaim.claim_id]; }); return Object.assign({}, state, { isFetchingClaimListMine: false, myClaims: claims, byId, claimsByUri: byUri, pendingById }); }; reducers[FETCH_CHANNEL_LIST_STARTED] = state => Object.assign({}, state, { fetchingMyChannels: true }); reducers[FETCH_CHANNEL_LIST_COMPLETED] = (state, action) => { const { claims } = action.data; const myChannelClaims = new Set(state.myChannelClaims); const byId = Object.assign({}, state.byId); claims.forEach(claim => { myChannelClaims.add(claim.claim_id); byId[claim.claim_id] = claim; }); return Object.assign({}, state, { byId, fetchingMyChannels: false, myChannelClaims }); }; reducers[FETCH_CHANNEL_CLAIMS_STARTED] = (state, action) => { const { uri, page } = action.data; const fetchingChannelClaims = Object.assign({}, state.fetchingChannelClaims); fetchingChannelClaims[uri] = page; return Object.assign({}, state, { fetchingChannelClaims, currentChannelPage: page }); }; reducers[FETCH_CHANNEL_CLAIMS_COMPLETED] = (state, action) => { const { uri, claims, page } = action.data; const claimsByChannel = Object.assign({}, state.claimsByChannel); const byChannel = Object.assign({}, claimsByChannel[uri]); const allClaimIds = new Set(byChannel.all); const currentPageClaimIds = []; const byId = Object.assign({}, state.byId); const fetchingChannelClaims = Object.assign({}, state.fetchingChannelClaims); const claimsByUri = Object.assign({}, state.claimsByUri); if (claims !== undefined) { claims.forEach(claim => { allClaimIds.add(claim.claim_id); currentPageClaimIds.push(claim.claim_id); byId[claim.claim_id] = claim; claimsByUri[`lbry://${claim.name}#${claim.claim_id}`] = claim.claim_id; }); } byChannel.all = allClaimIds; byChannel[page] = currentPageClaimIds; claimsByChannel[uri] = byChannel; delete fetchingChannelClaims[uri]; return Object.assign({}, state, { claimsByChannel, byId, fetchingChannelClaims, claimsByUri, currentChannelPage: page }); }; reducers[ABANDON_CLAIM_STARTED] = (state, action) => { const { claimId } = action.data; const abandoningById = Object.assign({}, state.abandoningById); abandoningById[claimId] = true; return Object.assign({}, state, { abandoningById }); }; reducers[ABANDON_CLAIM_SUCCEEDED] = (state, action) => { const { claimId } = action.data; const byId = Object.assign({}, state.byId); const claimsByUri = Object.assign({}, state.claimsByUri); Object.keys(claimsByUri).forEach(uri => { if (claimsByUri[uri] === claimId) { delete claimsByUri[uri]; } }); delete byId[claimId]; return Object.assign({}, state, { byId, claimsByUri }); }; reducers[CREATE_CHANNEL_COMPLETED] = (state, action) => { const channelClaim = action.data.channelClaim; const byId = Object.assign({}, state.byId); const myChannelClaims = new Set(state.myChannelClaims); byId[channelClaim.claim_id] = channelClaim; myChannelClaims.add(channelClaim.claim_id); return Object.assign({}, state, { byId, myChannelClaims }); }; reducers[RESOLVE_URIS_STARTED] = (state, action) => { const { uris } = action.data; const oldResolving = state.resolvingUris || []; const newResolving = oldResolving.slice(); uris.forEach(uri => { if (!newResolving.includes(uri)) { newResolving.push(uri); } }); return Object.assign({}, state, { resolvingUris: newResolving }); }; reducers[CLAIM_SEARCH_STARTED] = state => { return Object.assign({}, state, { fetchingClaimSearch: true }); }; reducers[CLAIM_SEARCH_COMPLETED] = (state, action) => { const { lastClaimSearchUris } = state; let newClaimSearchUris = []; if (action.data.append) { newClaimSearchUris = lastClaimSearchUris.concat(action.data.uris); } else { newClaimSearchUris = action.data.uris; } return _extends$5({}, handleClaimAction(state, action), { fetchingClaimSearch: false, lastClaimSearchUris: newClaimSearchUris }); }; reducers[CLAIM_SEARCH_FAILED] = state => { return Object.assign({}, state, { fetchingClaimSearch: false }); }; function claimsReducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); return state; } // 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 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; }; var _extends$6 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const defaultState$1 = { byId: {}, commentsByUri: {}, isLoading: false }; const commentReducer = handleActions({ [COMMENT_CREATE_STARTED]: (state, action) => _extends$6({}, state, { isLoading: true }), [COMMENT_CREATE_FAILED]: (state, action) => _extends$6({}, state, { isLoading: false }), [COMMENT_CREATE_COMPLETED]: (state, action) => { const { comment, claimId } = action.data; const byId = Object.assign({}, state.byId); const comments = byId[claimId]; const newComments = comments.slice(); newComments.unshift(comment); byId[claimId] = newComments; return _extends$6({}, state, { byId }); }, [COMMENT_LIST_STARTED]: state => _extends$6({}, state, { isLoading: true }), [COMMENT_LIST_COMPLETED]: (state, action) => { const { comments, claimId, uri } = action.data; const byId = Object.assign({}, state.byId); const commentsByUri = Object.assign({}, state.commentsByUri); if (comments['items']) { byId[claimId] = comments['items']; commentsByUri[uri] = claimId; } return _extends$6({}, state, { byId, commentsByUri, isLoading: false }); }, [COMMENT_LIST_FAILED]: (state, action) => _extends$6({}, state, { isLoading: false }) }, defaultState$1); var _extends$7 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const reducers$1 = {}; const defaultState$2 = { positions: {} }; reducers$1[SET_CONTENT_POSITION] = (state, action) => { const { claimId, outpoint, position } = action.data; return _extends$7({}, state, { positions: _extends$7({}, state.positions, { [claimId]: _extends$7({}, state.positions[claimId], { [outpoint]: position }) }) }); }; function contentReducer(state = defaultState$2, action) { const handler = reducers$1[action.type]; if (handler) return handler(state, action); return state; } var _extends$8 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const reducers$2 = {}; const defaultState$3 = { fileListPublishedSort: DATE_NEW, fileListDownloadedSort: DATE_NEW }; reducers$2[FILE_LIST_STARTED] = state => Object.assign({}, state, { isFetchingFileList: true }); reducers$2[FILE_LIST_SUCCEEDED] = (state, action) => { const { fileInfos } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); fileInfos.forEach(fileInfo => { const { outpoint } = fileInfo; if (outpoint) newByOutpoint[fileInfo.outpoint] = fileInfo; }); return Object.assign({}, state, { isFetchingFileList: false, byOutpoint: newByOutpoint, pendingByOutpoint }); }; reducers$2[FETCH_FILE_INFO_STARTED] = (state, action) => { const { outpoint } = action.data; const newFetching = Object.assign({}, state.fetching); newFetching[outpoint] = true; return Object.assign({}, state, { fetching: newFetching }); }; reducers$2[FETCH_FILE_INFO_COMPLETED] = (state, action) => { const { fileInfo, outpoint } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); const newFetching = Object.assign({}, state.fetching); newByOutpoint[outpoint] = fileInfo; delete newFetching[outpoint]; return Object.assign({}, state, { byOutpoint: newByOutpoint, fetching: newFetching }); }; reducers$2[DOWNLOADING_STARTED] = (state, action) => { const { uri, outpoint, fileInfo } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); const newDownloading = Object.assign({}, state.downloadingByOutpoint); const newLoading = Object.assign({}, state.urisLoading); newDownloading[outpoint] = true; newByOutpoint[outpoint] = fileInfo; delete newLoading[uri]; return Object.assign({}, state, { downloadingByOutpoint: newDownloading, urisLoading: newLoading, byOutpoint: newByOutpoint }); }; reducers$2[DOWNLOADING_PROGRESSED] = (state, action) => { const { outpoint, fileInfo } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); const newDownloading = Object.assign({}, state.downloadingByOutpoint); newByOutpoint[outpoint] = fileInfo; newDownloading[outpoint] = true; return Object.assign({}, state, { byOutpoint: newByOutpoint, downloadingByOutpoint: newDownloading }); }; reducers$2[DOWNLOADING_CANCELED] = (state, action) => { const { outpoint } = action.data; const newDownloading = Object.assign({}, state.downloadingByOutpoint); delete newDownloading[outpoint]; return Object.assign({}, state, { downloadingByOutpoint: newDownloading }); }; reducers$2[DOWNLOADING_COMPLETED] = (state, action) => { const { outpoint, fileInfo } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); const newDownloading = Object.assign({}, state.downloadingByOutpoint); newByOutpoint[outpoint] = fileInfo; delete newDownloading[outpoint]; return Object.assign({}, state, { byOutpoint: newByOutpoint, downloadingByOutpoint: newDownloading }); }; reducers$2[FILE_DELETE] = (state, action) => { const { outpoint } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); const downloadingByOutpoint = Object.assign({}, state.downloadingByOutpoint); delete newByOutpoint[outpoint]; delete downloadingByOutpoint[outpoint]; return Object.assign({}, state, { byOutpoint: newByOutpoint, downloadingByOutpoint }); }; reducers$2[LOADING_VIDEO_STARTED] = (state, action) => { const { uri } = action.data; const newLoading = Object.assign({}, state.urisLoading); newLoading[uri] = true; const newErrors = _extends$8({}, state.errors); if (uri in newErrors) delete newErrors[uri]; return Object.assign({}, state, { urisLoading: newLoading, errors: _extends$8({}, newErrors) }); }; reducers$2[LOADING_VIDEO_FAILED] = (state, action) => { const { uri } = action.data; const newLoading = Object.assign({}, state.urisLoading); delete newLoading[uri]; const newErrors = _extends$8({}, state.errors); newErrors[uri] = true; return Object.assign({}, state, { urisLoading: newLoading, errors: _extends$8({}, newErrors) }); }; reducers$2[SET_FILE_LIST_SORT] = (state, action) => { const pageSortStates = { [PUBLISHED]: 'fileListPublishedSort', [DOWNLOADED]: 'fileListDownloadedSort' }; const pageSortState = pageSortStates[action.data.page]; const { value } = action.data; return Object.assign({}, state, { [pageSortState]: value }); }; function fileInfoReducer(state = defaultState$3, action) { const handler = reducers$2[action.type]; if (handler) return handler(state, action); return state; } var _extends$9 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const reducers$3 = {}; const defaultState$4 = { failedPurchaseUris: [], purchasedUris: [], purchasedStreamingUrls: {}, purchaseUriErrorMessage: '' }; reducers$3[PURCHASE_URI_STARTED] = (state, action) => { const { uri } = action.data; const newFailedPurchaseUris = state.failedPurchaseUris.slice(); if (newFailedPurchaseUris.includes(uri)) { newFailedPurchaseUris.splice(newFailedPurchaseUris.indexOf(uri), 1); } return _extends$9({}, state, { failedPurchaseUris: newFailedPurchaseUris, purchaseUriErrorMessage: '' }); }; reducers$3[PURCHASE_URI_COMPLETED] = (state, action) => { const { uri, streamingUrl } = action.data; const newPurchasedUris = state.purchasedUris.slice(); const newFailedPurchaseUris = state.failedPurchaseUris.slice(); const newPurchasedStreamingUrls = Object.assign({}, state.purchasedStreamingUrls); if (!newPurchasedUris.includes(uri)) { newPurchasedUris.push(uri); } if (newFailedPurchaseUris.includes(uri)) { newFailedPurchaseUris.splice(newFailedPurchaseUris.indexOf(uri), 1); } if (streamingUrl) { newPurchasedStreamingUrls[uri] = streamingUrl; } return _extends$9({}, state, { failedPurchaseUris: newFailedPurchaseUris, purchasedUris: newPurchasedUris, purchasedStreamingUrls: newPurchasedStreamingUrls, purchaseUriErrorMessage: '' }); }; reducers$3[PURCHASE_URI_FAILED] = (state, action) => { const { uri, error } = action.data; const newFailedPurchaseUris = state.failedPurchaseUris.slice(); if (!newFailedPurchaseUris.includes(uri)) { newFailedPurchaseUris.push(uri); } return _extends$9({}, state, { failedPurchaseUris: newFailedPurchaseUris, purchaseUriErrorMessage: error }); }; reducers$3[DELETE_PURCHASED_URI] = (state, action) => { const { uri } = action.data; const newPurchasedUris = state.purchasedUris.slice(); if (newPurchasedUris.includes(uri)) { newPurchasedUris.splice(newPurchasedUris.indexOf(uri), 1); } return _extends$9({}, state, { purchasedUris: newPurchasedUris }); }; function fileReducer(state = defaultState$4, action) { const handler = reducers$3[action.type]; if (handler) return handler(state, action); return state; } var _extends$a = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const defaultState$5 = { notifications: [], toasts: [], errors: [] }; const notificationsReducer = handleActions({ // Toasts [CREATE_TOAST]: (state, action) => { const toast = action.data; const newToasts = state.toasts.slice(); newToasts.push(toast); return _extends$a({}, state, { toasts: newToasts }); }, [DISMISS_TOAST]: state => { const newToasts = state.toasts.slice(); newToasts.shift(); return _extends$a({}, state, { toasts: newToasts }); }, // Notifications [CREATE_NOTIFICATION]: (state, action) => { const notification = action.data; const newNotifications = state.notifications.slice(); newNotifications.push(notification); return _extends$a({}, state, { notifications: newNotifications }); }, // Used to mark notifications as read/dismissed [EDIT_NOTIFICATION]: (state, action) => { const { notification } = action.data; let notifications = state.notifications.slice(); notifications = notifications.map(pastNotification => pastNotification.id === notification.id ? notification : pastNotification); return _extends$a({}, state, { notifications }); }, [DELETE_NOTIFICATION]: (state, action) => { const { id } = action.data; let newNotifications = state.notifications.slice(); newNotifications = newNotifications.filter(notification => notification.id !== id); return _extends$a({}, state, { notifications: newNotifications }); }, // Errors [CREATE_ERROR]: (state, action) => { const error = action.data; const newErrors = state.errors.slice(); newErrors.push(error); return _extends$a({}, state, { errors: newErrors }); }, [DISMISS_ERROR]: state => { const newErrors = state.errors.slice(); newErrors.shift(); return _extends$a({}, state, { errors: newErrors }); } }, defaultState$5); var _extends$b = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } const defaultState$6 = { editingURI: undefined, filePath: undefined, contentIsFree: true, fee: { amount: 1, currency: 'LBC' }, title: '', thumbnail_url: '', thumbnailPath: '', uploadThumbnailStatus: API_DOWN, description: '', language: 'en', nsfw: false, channel: CHANNEL_ANONYMOUS, channelId: '', name: '', nameError: undefined, bid: 0.1, bidError: undefined, licenseType: 'None', otherLicenseDescription: 'All rights reserved', licenseUrl: '', publishing: false, publishSuccess: false, publishError: undefined }; const publishReducer = handleActions({ [UPDATE_PUBLISH_FORM]: (state, action) => { const { data } = action; return _extends$b({}, state, data); }, [CLEAR_PUBLISH]: () => _extends$b({}, defaultState$6), [PUBLISH_START]: state => _extends$b({}, state, { publishing: true, publishSuccess: false }), [PUBLISH_FAIL]: state => _extends$b({}, state, { publishing: false }), [PUBLISH_SUCCESS]: state => _extends$b({}, state, { publishing: false, publishSuccess: true }), [DO_PREPARE_EDIT]: (state, action) => { const publishData = _objectWithoutProperties(action.data, []); const { channel, name, uri } = publishData; // The short uri is what is presented to the user // The editingUri is the full uri with claim id const shortUri = buildURI({ channelName: channel, contentName: name }); return _extends$b({}, defaultState$6, publishData, { editingURI: uri, uri: shortUri }); } }, defaultState$6); var _extends$c = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const defaultState$7 = { isActive: false, // does the user have any typed text in the search input focused: false, // is the search input focused searchQuery: '', // needs to be an empty string for input focusing options: { [SEARCH_OPTIONS.RESULT_COUNT]: 30, [SEARCH_OPTIONS.CLAIM_TYPE]: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS, [SEARCH_OPTIONS.MEDIA_AUDIO]: true, [SEARCH_OPTIONS.MEDIA_VIDEO]: true, [SEARCH_OPTIONS.MEDIA_TEXT]: true, [SEARCH_OPTIONS.MEDIA_IMAGE]: true, [SEARCH_OPTIONS.MEDIA_APPLICATION]: true }, suggestions: {}, urisByQuery: {} }; const searchReducer = handleActions({ [SEARCH_START]: state => _extends$c({}, state, { searching: true }), [SEARCH_SUCCESS]: (state, action) => { const { query, uris } = action.data; return _extends$c({}, state, { searching: false, urisByQuery: Object.assign({}, state.urisByQuery, { [query]: uris }) }); }, [SEARCH_FAIL]: state => _extends$c({}, state, { searching: false }), [UPDATE_SEARCH_QUERY]: (state, action) => _extends$c({}, state, { searchQuery: action.data.query, isActive: true }), [UPDATE_SEARCH_SUGGESTIONS]: (state, action) => _extends$c({}, state, { suggestions: _extends$c({}, state.suggestions, { [action.data.query]: action.data.suggestions }) }), // sets isActive to false so the uri will be populated correctly if the // user is on a file page. The search query will still be present on any // other page [DISMISS_NOTIFICATION]: state => _extends$c({}, state, { isActive: false }), [SEARCH_FOCUS]: state => _extends$c({}, state, { focused: true }), [SEARCH_BLUR]: state => _extends$c({}, state, { focused: false }), [UPDATE_SEARCH_OPTIONS]: (state, action) => { const { options: oldOptions } = state; const newOptions = action.data; const options = _extends$c({}, oldOptions, newOptions); return _extends$c({}, state, { options }); } }, defaultState$7); var _extends$d = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const tagsReducerBuilder = defaultState => handleActions({ [TOGGLE_TAG_FOLLOW]: (state, action) => { const { followedTags } = state; const { name } = action.data; let newFollowedTags = followedTags.slice(); if (newFollowedTags.includes(name)) { newFollowedTags = newFollowedTags.filter(tag => tag !== name); } else { newFollowedTags.push(name); } return _extends$d({}, state, { followedTags: newFollowedTags }); }, [TAG_ADD]: (state, action) => { const { knownTags } = state; const { name } = action.data; let newKnownTags = _extends$d({}, knownTags); newKnownTags[name] = { name }; return _extends$d({}, state, { knownTags: newKnownTags }); }, [TAG_DELETE]: (state, action) => { const { knownTags, followedTags } = state; const { name } = action.data; let newKnownTags = _extends$d({}, knownTags); delete newKnownTags[name]; const newFollowedTags = followedTags.filter(tag => tag !== name); return _extends$d({}, state, { knownTags: newKnownTags, followedTags: newFollowedTags }); } }, defaultState); var _extends$e = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const buildDraftTransaction = () => ({ amount: undefined, address: undefined }); // TODO: Split into common success and failure types // See details in https://github.com/lbryio/lbry/issues/1307 const defaultState$8 = { balance: undefined, totalBalance: undefined, latestBlock: undefined, transactions: {}, fetchingTransactions: false, supports: {}, fetchingSupports: false, abandoningSupportsByOutpoint: {}, gettingNewAddress: false, draftTransaction: buildDraftTransaction(), sendingSupport: false, walletIsEncrypted: false, walletEncryptPending: false, walletEncryptSucceded: null, walletEncryptResult: null, walletDecryptPending: false, walletDecryptSucceded: null, walletDecryptResult: null, walletUnlockPending: false, walletUnlockSucceded: null, walletUnlockResult: null, walletLockPending: false, walletLockSucceded: null, walletLockResult: null, transactionListFilter: 'all' }; const walletReducer = handleActions({ [FETCH_TRANSACTIONS_STARTED]: state => _extends$e({}, state, { fetchingTransactions: true }), [FETCH_TRANSACTIONS_COMPLETED]: (state, action) => { const byId = _extends$e({}, state.transactions); const { transactions } = action.data; transactions.forEach(transaction => { byId[transaction.txid] = transaction; }); return _extends$e({}, state, { transactions: byId, fetchingTransactions: false }); }, [FETCH_SUPPORTS_STARTED]: state => _extends$e({}, state, { fetchingSupports: true }), [FETCH_SUPPORTS_COMPLETED]: (state, action) => { const byOutpoint = state.supports; const { supports } = action.data; supports.forEach(transaction => { const { txid, nout } = transaction; byOutpoint[`${txid}:${nout}`] = transaction; }); return _extends$e({}, state, { supports: byOutpoint, fetchingSupports: false }); }, [ABANDON_SUPPORT_STARTED]: (state, action) => { const { outpoint } = action.data; const currentlyAbandoning = state.abandoningSupportsByOutpoint; currentlyAbandoning[outpoint] = true; return _extends$e({}, state, { abandoningSupportsByOutpoint: currentlyAbandoning }); }, [ABANDON_SUPPORT_COMPLETED]: (state, action) => { const { outpoint } = action.data; const byOutpoint = state.supports; const currentlyAbandoning = state.abandoningSupportsByOutpoint; delete currentlyAbandoning[outpoint]; delete byOutpoint[outpoint]; return _extends$e({}, state, { supports: byOutpoint, abandoningSupportsById: currentlyAbandoning }); }, [GET_NEW_ADDRESS_STARTED]: state => _extends$e({}, state, { gettingNewAddress: true }), [GET_NEW_ADDRESS_COMPLETED]: (state, action) => { const { address } = action.data; return _extends$e({}, state, { gettingNewAddress: false, receiveAddress: address }); }, [UPDATE_BALANCE]: (state, action) => _extends$e({}, state, { balance: action.data.balance }), [UPDATE_TOTAL_BALANCE]: (state, action) => _extends$e({}, state, { totalBalance: action.data.totalBalance }), [CHECK_ADDRESS_IS_MINE_STARTED]: state => _extends$e({}, state, { checkingAddressOwnership: true }), [CHECK_ADDRESS_IS_MINE_COMPLETED]: state => _extends$e({}, state, { checkingAddressOwnership: false }), [SET_DRAFT_TRANSACTION_AMOUNT]: (state, action) => { const oldDraft = state.draftTransaction; const newDraft = _extends$e({}, oldDraft, { amount: parseFloat(action.data.amount) }); return _extends$e({}, state, { draftTransaction: newDraft }); }, [SET_DRAFT_TRANSACTION_ADDRESS]: (state, action) => { const oldDraft = state.draftTransaction; const newDraft = _extends$e({}, oldDraft, { address: action.data.address }); return _extends$e({}, state, { draftTransaction: newDraft }); }, [SEND_TRANSACTION_STARTED]: state => { const newDraftTransaction = _extends$e({}, state.draftTransaction, { sending: true }); return _extends$e({}, state, { draftTransaction: newDraftTransaction }); }, [SEND_TRANSACTION_COMPLETED]: state => Object.assign({}, state, { draftTransaction: buildDraftTransaction() }), [SEND_TRANSACTION_FAILED]: (state, action) => { const newDraftTransaction = Object.assign({}, state.draftTransaction, { sending: false, error: action.data.error }); return _extends$e({}, state, { draftTransaction: newDraftTransaction }); }, [SUPPORT_TRANSACTION_STARTED]: state => _extends$e({}, state, { sendingSupport: true }), [SUPPORT_TRANSACTION_COMPLETED]: state => _extends$e({}, state, { sendingSupport: false }), [SUPPORT_TRANSACTION_FAILED]: (state, action) => _extends$e({}, state, { error: action.data.error, sendingSupport: false }), [WALLET_STATUS_COMPLETED]: (state, action) => _extends$e({}, state, { walletIsEncrypted: action.result }), [WALLET_ENCRYPT_START]: state => _extends$e({}, state, { walletEncryptPending: true, walletEncryptSucceded: null, walletEncryptResult: null }), [WALLET_ENCRYPT_COMPLETED]: (state, action) => _extends$e({}, state, { walletEncryptPending: false, walletEncryptSucceded: true, walletEncryptResult: action.result }), [WALLET_ENCRYPT_FAILED]: (state, action) => _extends$e({}, state, { walletEncryptPending: false, walletEncryptSucceded: false, walletEncryptResult: action.result }), [WALLET_DECRYPT_START]: state => _extends$e({}, state, { walletDecryptPending: true, walletDecryptSucceded: null, walletDecryptResult: null }), [WALLET_DECRYPT_COMPLETED]: (state, action) => _extends$e({}, state, { walletDecryptPending: false, walletDecryptSucceded: true, walletDecryptResult: action.result }), [WALLET_DECRYPT_FAILED]: (state, action) => _extends$e({}, state, { walletDecryptPending: false, walletDecryptSucceded: false, walletDecryptResult: action.result }), [WALLET_UNLOCK_START]: state => _extends$e({}, state, { walletUnlockPending: true, walletUnlockSucceded: null, walletUnlockResult: null }), [WALLET_UNLOCK_COMPLETED]: (state, action) => _extends$e({}, state, { walletUnlockPending: false, walletUnlockSucceded: true, walletUnlockResult: action.result }), [WALLET_UNLOCK_FAILED]: (state, action) => _extends$e({}, state, { walletUnlockPending: false, walletUnlockSucceded: false, walletUnlockResult: action.result }), [WALLET_LOCK_START]: state => _extends$e({}, state, { walletLockPending: false, walletLockSucceded: null, walletLockResult: null }), [WALLET_LOCK_COMPLETED]: (state, action) => _extends$e({}, state, { walletLockPending: false, walletLockSucceded: true, walletLockResult: action.result }), [WALLET_LOCK_FAILED]: (state, action) => _extends$e({}, state, { walletLockPending: false, walletLockSucceded: false, walletLockResult: action.result }), [SET_TRANSACTION_LIST_FILTER]: (state, action) => _extends$e({}, state, { transactionListFilter: action.data }), [UPDATE_CURRENT_HEIGHT]: (state, action) => _extends$e({}, state, { latestBlock: action.data }) }, defaultState$8); const selectState$5 = state => state.content || {}; const makeSelectContentPositionForUri = uri => reselect.createSelector(selectState$5, makeSelectClaimForUri(uri), (state, claim) => { if (!claim) { return null; } const outpoint = `${claim.txid}:${claim.nout}`; const id = claim.claim_id; return state.positions[id] ? state.positions[id][outpoint] : null; }); var _extends$f = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const selectState$6 = state => state.notifications || {}; const selectToast = reselect.createSelector(selectState$6, state => { if (state.toasts.length) { const { id, params } = state.toasts[0]; return _extends$f({ id }, params); } return null; }); const selectError = reselect.createSelector(selectState$6, state => { if (state.errors.length) { const { error } = state.errors[0]; return { error }; } return null; }); // const selectState$7 = state => state.comments || {}; const selectCommentsById = reselect.createSelector(selectState$7, state => state.byId || {}); const selectCommentsByUri = reselect.createSelector(selectState$7, state => { const byUri = state.commentsByUri || {}; const comments = {}; Object.keys(byUri).forEach(uri => { const claimId = byUri[uri]; if (claimId === null) { comments[uri] = null; } else { comments[uri] = claimId; } }); return comments; }); const makeSelectCommentsForUri = uri => reselect.createSelector(selectCommentsById, selectCommentsByUri, (byId, byUri) => { const claimId = byUri[uri]; return byId && byId[claimId]; }); function _objectWithoutProperties$1(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } const selectState$8 = state => state.publish || {}; const selectPublishFormValues = reselect.createSelector(selectState$8, state => { const formValues = _objectWithoutProperties$1(state, ['pendingPublish']); return formValues; }); const selectIsStillEditing = reselect.createSelector(selectPublishFormValues, publishState => { const { editingURI, uri } = publishState; if (!editingURI || !uri) { return false; } const { isChannel: currentIsChannel, claimName: currentClaimName, contentName: currentContentName } = parseURI(uri); const { isChannel: editIsChannel, claimName: editClaimName, contentName: editContentName } = parseURI(editingURI); // Depending on the previous/current use of a channel, we need to compare different things // ex: going from a channel to anonymous, the new uri won't return contentName, so we need to use claimName const currentName = currentIsChannel ? currentContentName : currentClaimName; const editName = editIsChannel ? editContentName : editClaimName; return currentName === editName; }); const selectMyClaimForUri = reselect.createSelector(selectPublishFormValues, selectIsStillEditing, selectClaimsById, selectMyClaimsWithoutChannels, ({ editingURI, uri }, isStillEditing, claimsById, myClaims) => { const { contentName, claimName } = parseURI(uri); const { claimId: editClaimId } = parseURI(editingURI); // If isStillEditing // They clicked "edit" from the file page // They haven't changed the channel/name after clicking edit // Get the claim so they can edit without re-uploading a new file return isStillEditing ? claimsById[editClaimId] : myClaims.find(claim => !contentName ? claim.name === claimName : claim.name === contentName || claim.name === claimName); }); const selectIsResolvingPublishUris = reselect.createSelector(selectState$8, selectResolvingUris, ({ uri, name }, resolvingUris) => { if (uri) { const isResolvingUri = resolvingUris.includes(uri); const { isChannel } = parseURI(uri); let isResolvingShortUri; if (isChannel) { const shortUri = buildURI({ contentName: name }); isResolvingShortUri = resolvingUris.includes(shortUri); } return isResolvingUri || isResolvingShortUri; } return false; }); const selectTakeOverAmount = reselect.createSelector(selectState$8, selectMyClaimForUri, selectClaimsByUri, ({ name }, myClaimForUri, claimsByUri) => { // We only care about the winning claim for the short uri const shortUri = buildURI({ contentName: name }); const claimForShortUri = claimsByUri[shortUri]; if (!myClaimForUri && claimForShortUri) { return claimForShortUri.effective_amount; } else if (myClaimForUri && claimForShortUri) { // https://github.com/lbryio/lbry/issues/1476 // We should check the current effective_amount on my claim to see how much additional lbc // is needed to win the claim. Currently this is not possible during a takeover. // With this, we could say something like, "You have x lbc in support, if you bid y additional LBC you will control the claim" // For now just ignore supports. We will just show the winning claim's bid amount return claimForShortUri.effective_amount || claimForShortUri.amount; } return null; }); // const selectState$9 = state => state.tags || {}; const selectKnownTagsByName = reselect.createSelector(selectState$9, state => state.knownTags); const selectFollowedTagsList = reselect.createSelector(selectState$9, state => state.followedTags); const selectFollowedTags = reselect.createSelector(selectFollowedTagsList, followedTags => followedTags.map(tag => ({ name: tag })).sort((a, b) => a.name.localeCompare(b.name))); const selectUnfollowedTags = reselect.createSelector(selectKnownTagsByName, selectFollowedTagsList, (tagsByName, followedTags) => { const followedTagsSet = new Set(followedTags); let tagsToReturn = []; Object.keys(tagsByName).forEach(key => { if (!followedTagsSet.has(key)) { const { name } = tagsByName[key]; tagsToReturn.push({ name }); } }); return tagsToReturn; }); exports.ACTIONS = action_types; exports.CLAIM_VALUES = claim; exports.LICENSES = licenses; exports.Lbry = lbryProxy; exports.PAGES = pages; exports.SEARCH_OPTIONS = SEARCH_OPTIONS; exports.SEARCH_TYPES = SEARCH_TYPES; exports.SETTINGS = settings; exports.SORT_OPTIONS = sort_options; exports.THUMBNAIL_STATUSES = thumbnail_upload_statuses; exports.TRANSACTIONS = transaction_types; exports.batchActions = batchActions; exports.buildURI = buildURI; exports.claimsReducer = claimsReducer; exports.commentReducer = commentReducer; exports.contentReducer = contentReducer; exports.convertToShareLink = convertToShareLink; exports.creditsToString = creditsToString; exports.doAbandonClaim = doAbandonClaim; exports.doAddTag = doAddTag; exports.doBalanceSubscribe = doBalanceSubscribe; exports.doBlurSearchInput = doBlurSearchInput; exports.doCheckAddressIsMine = doCheckAddressIsMine; exports.doCheckPendingPublishes = doCheckPendingPublishes; exports.doClaimSearch = doClaimSearch; exports.doClearPublish = doClearPublish; exports.doCommentCreate = doCommentCreate; exports.doCommentList = doCommentList; exports.doCreateChannel = doCreateChannel; exports.doDeletePurchasedUri = doDeletePurchasedUri; exports.doDeleteTag = doDeleteTag; exports.doDismissError = doDismissError; exports.doDismissToast = doDismissToast; exports.doError = doError; exports.doFetchChannelListMine = doFetchChannelListMine; exports.doFetchClaimListMine = doFetchClaimListMine; exports.doFetchClaimsByChannel = doFetchClaimsByChannel; exports.doFetchFileInfo = doFetchFileInfo; exports.doFetchFileInfosAndPublishedClaims = doFetchFileInfosAndPublishedClaims; exports.doFetchTransactions = doFetchTransactions; exports.doFileGet = doFileGet; exports.doFileList = doFileList; exports.doFocusSearchInput = doFocusSearchInput; exports.doGetNewAddress = doGetNewAddress; exports.doPrepareEdit = doPrepareEdit; exports.doPublish = doPublish; exports.doPurchaseUri = doPurchaseUri; exports.doResetThumbnailStatus = doResetThumbnailStatus; exports.doResolveUri = doResolveUri; exports.doResolveUris = doResolveUris; exports.doSearch = doSearch; exports.doSendDraftTransaction = doSendDraftTransaction; exports.doSendTip = doSendTip; exports.doSetDraftTransactionAddress = doSetDraftTransactionAddress; exports.doSetDraftTransactionAmount = doSetDraftTransactionAmount; exports.doSetFileListSort = doSetFileListSort; exports.doSetTransactionListFilter = doSetTransactionListFilter; exports.doToast = doToast; exports.doToggleTagFollow = doToggleTagFollow; exports.doTotalBalanceSubscribe = doTotalBalanceSubscribe; exports.doUpdateBalance = doUpdateBalance; exports.doUpdateBlockHeight = doUpdateBlockHeight; exports.doUpdatePublishForm = doUpdatePublishForm; exports.doUpdateSearchOptions = doUpdateSearchOptions; exports.doUpdateSearchQuery = doUpdateSearchQuery; exports.doUpdateTotalBalance = doUpdateTotalBalance; exports.doUploadThumbnail = doUploadThumbnail; exports.doWalletDecrypt = doWalletDecrypt; exports.doWalletEncrypt = doWalletEncrypt; exports.doWalletStatus = doWalletStatus; exports.doWalletUnlock = doWalletUnlock; exports.fileInfoReducer = fileInfoReducer; exports.fileReducer = fileReducer; exports.formatCredits = formatCredits; exports.formatFullPrice = formatFullPrice; exports.isClaimNsfw = isClaimNsfw; exports.isNameValid = isNameValid; exports.isURIClaimable = isURIClaimable; exports.isURIValid = isURIValid; exports.makeSelectChannelForClaimUri = makeSelectChannelForClaimUri; exports.makeSelectClaimForUri = makeSelectClaimForUri; exports.makeSelectClaimIsMine = makeSelectClaimIsMine; exports.makeSelectClaimIsNsfw = makeSelectClaimIsNsfw; exports.makeSelectClaimIsPending = makeSelectClaimIsPending; exports.makeSelectClaimsInChannelForCurrentPageState = makeSelectClaimsInChannelForCurrentPageState; exports.makeSelectClaimsInChannelForPage = makeSelectClaimsInChannelForPage; exports.makeSelectCommentsForUri = makeSelectCommentsForUri; exports.makeSelectContentPositionForUri = makeSelectContentPositionForUri; exports.makeSelectContentTypeForUri = makeSelectContentTypeForUri; exports.makeSelectCoverForUri = makeSelectCoverForUri; exports.makeSelectDateForUri = makeSelectDateForUri; exports.makeSelectDownloadingForUri = makeSelectDownloadingForUri; exports.makeSelectFetchingChannelClaims = makeSelectFetchingChannelClaims; exports.makeSelectFileInfoForUri = makeSelectFileInfoForUri; exports.makeSelectFirstRecommendedFileForUri = makeSelectFirstRecommendedFileForUri; exports.makeSelectIsUriResolving = makeSelectIsUriResolving; exports.makeSelectLoadingForUri = makeSelectLoadingForUri; exports.makeSelectMetadataForUri = makeSelectMetadataForUri; exports.makeSelectMetadataItemForUri = makeSelectMetadataItemForUri; exports.makeSelectNsfwCountForChannel = makeSelectNsfwCountForChannel; exports.makeSelectNsfwCountFromUris = makeSelectNsfwCountFromUris; exports.makeSelectPendingByUri = makeSelectPendingByUri; exports.makeSelectQueryWithOptions = makeSelectQueryWithOptions; exports.makeSelectRecommendedContentForUri = makeSelectRecommendedContentForUri; exports.makeSelectSearchUris = makeSelectSearchUris; exports.makeSelectStreamingUrlForUri = makeSelectStreamingUrlForUri; exports.makeSelectTagsForUri = makeSelectTagsForUri; exports.makeSelectThumbnailForUri = makeSelectThumbnailForUri; exports.makeSelectTitleForUri = makeSelectTitleForUri; exports.makeSelectTotalItemsForChannel = makeSelectTotalItemsForChannel; exports.makeSelectTotalPagesForChannel = makeSelectTotalPagesForChannel; exports.normalizeURI = normalizeURI; exports.notificationsReducer = notificationsReducer; exports.parseQueryParams = parseQueryParams; exports.parseURI = parseURI; exports.publishReducer = publishReducer; exports.regexAddress = regexAddress; exports.regexInvalidURI = regexInvalidURI; exports.savePosition = savePosition; exports.searchReducer = searchReducer; exports.selectAbandoningIds = selectAbandoningIds; exports.selectAllClaimsByChannel = selectAllClaimsByChannel; exports.selectAllFetchingChannelClaims = selectAllFetchingChannelClaims; exports.selectAllMyClaimsByOutpoint = selectAllMyClaimsByOutpoint; exports.selectBalance = selectBalance; exports.selectBlocks = selectBlocks; exports.selectChannelClaimCounts = selectChannelClaimCounts; exports.selectClaimsById = selectClaimsById; exports.selectClaimsByUri = selectClaimsByUri; exports.selectCurrentChannelPage = selectCurrentChannelPage; exports.selectDownloadedUris = selectDownloadedUris; exports.selectDownloadingByOutpoint = selectDownloadingByOutpoint; exports.selectDownloadingFileInfos = selectDownloadingFileInfos; exports.selectDraftTransaction = selectDraftTransaction; exports.selectDraftTransactionAddress = selectDraftTransactionAddress; exports.selectDraftTransactionAmount = selectDraftTransactionAmount; exports.selectDraftTransactionError = selectDraftTransactionError; exports.selectError = selectError; exports.selectFailedPurchaseUris = selectFailedPurchaseUris; exports.selectFetchingClaimSearch = selectFetchingClaimSearch; exports.selectFetchingMyChannels = selectFetchingMyChannels; exports.selectFileInfosByOutpoint = selectFileInfosByOutpoint; exports.selectFileInfosDownloaded = selectFileInfosDownloaded; exports.selectFileListDownloadedSort = selectFileListDownloadedSort; exports.selectFileListPublishedSort = selectFileListPublishedSort; exports.selectFollowedTags = selectFollowedTags; exports.selectGettingNewAddress = selectGettingNewAddress; exports.selectHasTransactions = selectHasTransactions; exports.selectIsFetchingClaimListMine = selectIsFetchingClaimListMine; exports.selectIsFetchingFileList = selectIsFetchingFileList; exports.selectIsFetchingFileListDownloadedOrPublished = selectIsFetchingFileListDownloadedOrPublished; exports.selectIsFetchingTransactions = selectIsFetchingTransactions; exports.selectIsResolvingPublishUris = selectIsResolvingPublishUris; exports.selectIsSearching = selectIsSearching; exports.selectIsSendingSupport = selectIsSendingSupport; exports.selectIsStillEditing = selectIsStillEditing; exports.selectLastClaimSearchUris = selectLastClaimSearchUris; exports.selectLastPurchasedUri = selectLastPurchasedUri; exports.selectMyActiveClaims = selectMyActiveClaims; exports.selectMyChannelClaims = selectMyChannelClaims; exports.selectMyClaimForUri = selectMyClaimForUri; exports.selectMyClaimUrisWithoutChannels = selectMyClaimUrisWithoutChannels; exports.selectMyClaims = selectMyClaims; exports.selectMyClaimsOutpoints = selectMyClaimsOutpoints; exports.selectMyClaimsRaw = selectMyClaimsRaw; exports.selectMyClaimsWithoutChannels = selectMyClaimsWithoutChannels; exports.selectPendingById = selectPendingById; exports.selectPendingClaims = selectPendingClaims; exports.selectPlayingUri = selectPlayingUri; exports.selectPublishFormValues = selectPublishFormValues; exports.selectPurchaseUriErrorMessage = selectPurchaseUriErrorMessage; exports.selectPurchasedStreamingUrls = selectPurchasedStreamingUrls; exports.selectPurchasedUris = selectPurchasedUris; exports.selectReceiveAddress = selectReceiveAddress; exports.selectRecentTransactions = selectRecentTransactions; exports.selectResolvingUris = selectResolvingUris; exports.selectSearchBarFocused = selectSearchBarFocused; exports.selectSearchDownloadUris = selectSearchDownloadUris; exports.selectSearchOptions = selectSearchOptions; exports.selectSearchState = selectState; exports.selectSearchSuggestions = selectSearchSuggestions; exports.selectSearchUrisByQuery = selectSearchUrisByQuery; exports.selectSearchValue = selectSearchValue; exports.selectSupportsByOutpoint = selectSupportsByOutpoint; exports.selectTakeOverAmount = selectTakeOverAmount; exports.selectToast = selectToast; exports.selectTotalBalance = selectTotalBalance; exports.selectTotalDownloadProgress = selectTotalDownloadProgress; exports.selectTransactionItems = selectTransactionItems; exports.selectTransactionListFilter = selectTransactionListFilter; exports.selectTransactionsById = selectTransactionsById; exports.selectUnfollowedTags = selectUnfollowedTags; exports.selectUrisLoading = selectUrisLoading; exports.selectWalletDecryptPending = selectWalletDecryptPending; exports.selectWalletDecryptResult = selectWalletDecryptResult; exports.selectWalletDecryptSucceeded = selectWalletDecryptSucceeded; exports.selectWalletEncryptPending = selectWalletEncryptPending; exports.selectWalletEncryptResult = selectWalletEncryptResult; exports.selectWalletEncryptSucceeded = selectWalletEncryptSucceeded; exports.selectWalletIsEncrypted = selectWalletIsEncrypted; exports.selectWalletState = selectWalletState; exports.selectWalletUnlockPending = selectWalletUnlockPending; exports.selectWalletUnlockResult = selectWalletUnlockResult; exports.selectWalletUnlockSucceeded = selectWalletUnlockSucceeded; exports.setSearchApi = setSearchApi; exports.tagsReducerBuilder = tagsReducerBuilder; exports.toQueryString = toQueryString; exports.walletReducer = walletReducer;