'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 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'; // 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, 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 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 }); /* 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 }); // eslint-disable-next-line import/prefer-default-export const ALL = 'all'; const SPEND = 'spend'; const RECEIVE = 'receive'; const PUBLISH = 'publish'; const CHANNEL = '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, CHANNEL: CHANNEL, TIP: TIP, SUPPORT: SUPPORT, UPDATE: UPDATE, ABANDON: ABANDON }); 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 AUTH = 'auth'; const BACKUP = 'backup'; const CHANNEL$1 = '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$1 = '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$1, DISCOVER: DISCOVER, FILE: FILE, DOWNLOADED: DOWNLOADED, PUBLISHED: PUBLISHED, GET_CREDITS: GET_CREDITS, HELP: HELP, INVITE: INVITE, PUBLISH: PUBLISH$1, REPORT: REPORT, REWARDS: REWARDS, SEARCH: SEARCH, SEND_CREDITS: SEND_CREDITS, SETTINGS: SETTINGS, SHOW: SHOW, SUBSCRIPTIONS: SUBSCRIPTIONS, TRANSACTION_HISTORY: TRANSACTION_HISTORY, HISTORY: HISTORY, WALLET: WALLET }); 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), // 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(/^@/))); const selectMyClaimUrisWithoutChannels = reselect.createSelector(selectMyClaimsWithoutChannels, myClaims => myClaims.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 : PUBLISH }))); 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; <<<<<<< HEAD result.claimsInChannel = uriResolveInfo.signing_channel.meta && uriResolveInfo.signing_channel.meta.claims_in_channel || 0; ======= result.claimsInChannel = uriResolveInfo.meta && uriResolveInfo.meta.claims_in_channel || 0; >>>>>>> add tags } } // $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, <<<<<<< HEAD valid_channel_signatures: true, ======= is_controlling: true, >>>>>>> add tags 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 } }); }; 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.map(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 } }; } // 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 => { 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(() => { 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 } }); 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 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$4({}, 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) => { return _extends$4({}, handleClaimAction(state, action), { fetchingClaimSearch: false, lastClaimSearchUris: action.data.uris }); }; 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; } 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$1 = {}; const defaultState$1 = { failedPurchaseUris: [], purchasedUris: [], purchasedStreamingUrls: {}, purchaseUriErrorMessage: '' }; reducers$1[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$5({}, state, { failedPurchaseUris: newFailedPurchaseUris, purchaseUriErrorMessage: '' }); }; reducers$1[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$5({}, state, { failedPurchaseUris: newFailedPurchaseUris, purchasedUris: newPurchasedUris, purchasedStreamingUrls: newPurchasedStreamingUrls, purchaseUriErrorMessage: '' }); }; reducers$1[PURCHASE_URI_FAILED] = (state, action) => { const { uri, error } = action.data; const newFailedPurchaseUris = state.failedPurchaseUris.slice(); if (!newFailedPurchaseUris.includes(uri)) { newFailedPurchaseUris.push(uri); } return _extends$5({}, state, { failedPurchaseUris: newFailedPurchaseUris, purchaseUriErrorMessage: error }); }; reducers$1[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$5({}, state, { purchasedUris: newPurchasedUris }); }; function fileReducer(state = defaultState$1, action) { const handler = reducers$1[action.type]; if (handler) return handler(state, action); 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 reducers$2 = {}; const defaultState$2 = { 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$6({}, state.errors); if (uri in newErrors) delete newErrors[uri]; return Object.assign({}, state, { urisLoading: newLoading, errors: _extends$6({}, newErrors) }); }; reducers$2[LOADING_VIDEO_FAILED] = (state, action) => { const { uri } = action.data; const newLoading = Object.assign({}, state.urisLoading); delete newLoading[uri]; const newErrors = _extends$6({}, state.errors); newErrors[uri] = true; return Object.assign({}, state, { urisLoading: newLoading, errors: _extends$6({}, 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$2, action) { const handler = reducers$2[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$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 defaultState$3 = { 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$7({}, state, { toasts: newToasts }); }, [DISMISS_TOAST]: state => { const newToasts = state.toasts.slice(); newToasts.shift(); return _extends$7({}, state, { toasts: newToasts }); }, // Notifications [CREATE_NOTIFICATION]: (state, action) => { const notification = action.data; const newNotifications = state.notifications.slice(); newNotifications.push(notification); return _extends$7({}, 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$7({}, state, { notifications }); }, [DELETE_NOTIFICATION]: (state, action) => { const { id } = action.data; let newNotifications = state.notifications.slice(); newNotifications = newNotifications.filter(notification => notification.id !== id); return _extends$7({}, state, { notifications: newNotifications }); }, // Errors [CREATE_ERROR]: (state, action) => { const error = action.data; const newErrors = state.errors.slice(); newErrors.push(error); return _extends$7({}, state, { errors: newErrors }); }, [DISMISS_ERROR]: state => { const newErrors = state.errors.slice(); newErrors.shift(); return _extends$7({}, state, { errors: newErrors }); } }, defaultState$3); 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 defaultState$4 = { 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$8({}, state, { searching: true }), [SEARCH_SUCCESS]: (state, action) => { const { query, uris } = action.data; return _extends$8({}, state, { searching: false, urisByQuery: Object.assign({}, state.urisByQuery, { [query]: uris }) }); }, [SEARCH_FAIL]: state => _extends$8({}, state, { searching: false }), [UPDATE_SEARCH_QUERY]: (state, action) => _extends$8({}, state, { searchQuery: action.data.query, isActive: true }), [UPDATE_SEARCH_SUGGESTIONS]: (state, action) => _extends$8({}, state, { suggestions: _extends$8({}, 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$8({}, state, { isActive: false }), [SEARCH_FOCUS]: state => _extends$8({}, state, { focused: true }), [SEARCH_BLUR]: state => _extends$8({}, state, { focused: false }), [UPDATE_SEARCH_OPTIONS]: (state, action) => { const { options: oldOptions } = state; const newOptions = action.data; const options = _extends$8({}, oldOptions, newOptions); return _extends$8({}, state, { options }); } }, defaultState$4); 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 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$5 = { 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$9({}, state, { fetchingTransactions: true }), [FETCH_TRANSACTIONS_COMPLETED]: (state, action) => { const byId = _extends$9({}, state.transactions); const { transactions } = action.data; transactions.forEach(transaction => { byId[transaction.txid] = transaction; }); return _extends$9({}, state, { transactions: byId, fetchingTransactions: false }); }, [FETCH_SUPPORTS_STARTED]: state => _extends$9({}, 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$9({}, state, { supports: byOutpoint, fetchingSupports: false }); }, [ABANDON_SUPPORT_STARTED]: (state, action) => { const { outpoint } = action.data; const currentlyAbandoning = state.abandoningSupportsByOutpoint; currentlyAbandoning[outpoint] = true; return _extends$9({}, 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$9({}, state, { supports: byOutpoint, abandoningSupportsById: currentlyAbandoning }); }, [GET_NEW_ADDRESS_STARTED]: state => _extends$9({}, state, { gettingNewAddress: true }), [GET_NEW_ADDRESS_COMPLETED]: (state, action) => { const { address } = action.data; return _extends$9({}, state, { gettingNewAddress: false, receiveAddress: address }); }, [UPDATE_BALANCE]: (state, action) => _extends$9({}, state, { balance: action.data.balance }), [UPDATE_TOTAL_BALANCE]: (state, action) => _extends$9({}, state, { totalBalance: action.data.totalBalance }), [CHECK_ADDRESS_IS_MINE_STARTED]: state => _extends$9({}, state, { checkingAddressOwnership: true }), [CHECK_ADDRESS_IS_MINE_COMPLETED]: state => _extends$9({}, state, { checkingAddressOwnership: false }), [SET_DRAFT_TRANSACTION_AMOUNT]: (state, action) => { const oldDraft = state.draftTransaction; const newDraft = _extends$9({}, oldDraft, { amount: parseFloat(action.data.amount) }); return _extends$9({}, state, { draftTransaction: newDraft }); }, [SET_DRAFT_TRANSACTION_ADDRESS]: (state, action) => { const oldDraft = state.draftTransaction; const newDraft = _extends$9({}, oldDraft, { address: action.data.address }); return _extends$9({}, state, { draftTransaction: newDraft }); }, [SEND_TRANSACTION_STARTED]: state => { const newDraftTransaction = _extends$9({}, state.draftTransaction, { sending: true }); return _extends$9({}, 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$9({}, state, { draftTransaction: newDraftTransaction }); }, [SUPPORT_TRANSACTION_STARTED]: state => _extends$9({}, state, { sendingSupport: true }), [SUPPORT_TRANSACTION_COMPLETED]: state => _extends$9({}, state, { sendingSupport: false }), [SUPPORT_TRANSACTION_FAILED]: (state, action) => _extends$9({}, state, { error: action.data.error, sendingSupport: false }), [WALLET_STATUS_COMPLETED]: (state, action) => _extends$9({}, state, { walletIsEncrypted: action.result }), [WALLET_ENCRYPT_START]: state => _extends$9({}, state, { walletEncryptPending: true, walletEncryptSucceded: null, walletEncryptResult: null }), [WALLET_ENCRYPT_COMPLETED]: (state, action) => _extends$9({}, state, { walletEncryptPending: false, walletEncryptSucceded: true, walletEncryptResult: action.result }), [WALLET_ENCRYPT_FAILED]: (state, action) => _extends$9({}, state, { walletEncryptPending: false, walletEncryptSucceded: false, walletEncryptResult: action.result }), [WALLET_DECRYPT_START]: state => _extends$9({}, state, { walletDecryptPending: true, walletDecryptSucceded: null, walletDecryptResult: null }), [WALLET_DECRYPT_COMPLETED]: (state, action) => _extends$9({}, state, { walletDecryptPending: false, walletDecryptSucceded: true, walletDecryptResult: action.result }), [WALLET_DECRYPT_FAILED]: (state, action) => _extends$9({}, state, { walletDecryptPending: false, walletDecryptSucceded: false, walletDecryptResult: action.result }), [WALLET_UNLOCK_START]: state => _extends$9({}, state, { walletUnlockPending: true, walletUnlockSucceded: null, walletUnlockResult: null }), [WALLET_UNLOCK_COMPLETED]: (state, action) => _extends$9({}, state, { walletUnlockPending: false, walletUnlockSucceded: true, walletUnlockResult: action.result }), [WALLET_UNLOCK_FAILED]: (state, action) => _extends$9({}, state, { walletUnlockPending: false, walletUnlockSucceded: false, walletUnlockResult: action.result }), [WALLET_LOCK_START]: state => _extends$9({}, state, { walletLockPending: false, walletLockSucceded: null, walletLockResult: null }), [WALLET_LOCK_COMPLETED]: (state, action) => _extends$9({}, state, { walletLockPending: false, walletLockSucceded: true, walletLockResult: action.result }), [WALLET_LOCK_FAILED]: (state, action) => _extends$9({}, state, { walletLockPending: false, walletLockSucceded: false, walletLockResult: action.result }), [SET_TRANSACTION_LIST_FILTER]: (state, action) => _extends$9({}, state, { transactionListFilter: action.data }), [UPDATE_CURRENT_HEIGHT]: (state, action) => _extends$9({}, state, { latestBlock: action.data }) }, defaultState$5); 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 reducers$3 = {}; const defaultState$6 = { positions: {} }; reducers$3[SET_CONTENT_POSITION] = (state, action) => { const { claimId, outpoint, position } = action.data; return _extends$a({}, state, { positions: _extends$a({}, state.positions, { [claimId]: _extends$a({}, state.positions[claimId], { [outpoint]: position }) }) }); }; function contentReducer(state = defaultState$6, action) { const handler = reducers$3[action.type]; if (handler) return handler(state, action); return state; } 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; }; 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$b({}, state, { followedTags: newFollowedTags }); }, [TAG_ADD]: (state, action) => { const { knownTags } = state; const { name } = action.data; let newKnownTags = _extends$b({}, knownTags); newKnownTags[name] = { name }; return _extends$b({}, state, { knownTags: newKnownTags }); }, [TAG_DELETE]: (state, action) => { const { knownTags, followedTags } = state; const { name } = action.data; let newKnownTags = _extends$b({}, knownTags); delete newKnownTags[name]; const newFollowedTags = followedTags.filter(tag => tag !== name); return _extends$b({}, state, { knownTags: newKnownTags, followedTags: newFollowedTags }); } }, defaultState); 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$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 selectState$6 = state => state.notifications || {}; const selectToast = reselect.createSelector(selectState$6, state => { if (state.toasts.length) { const { id, params } = state.toasts[0]; return _extends$c({ 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.tags || {}; const selectKnownTagsByName = reselect.createSelector(selectState$7, state => state.knownTags); const selectFollowedTagsList = reselect.createSelector(selectState$7, 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.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.contentReducer = contentReducer; exports.convertToShareLink = convertToShareLink; exports.creditsToString = creditsToString; exports.doAbandonClaim = doAbandonClaim; exports.doAddTag = doAddTag; exports.doBalanceSubscribe = doBalanceSubscribe; exports.doBlurSearchInput = doBlurSearchInput; exports.doCheckAddressIsMine = doCheckAddressIsMine; exports.doClaimSearch = doClaimSearch; 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.doPurchaseUri = doPurchaseUri; 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.doUpdateSearchOptions = doUpdateSearchOptions; exports.doUpdateSearchQuery = doUpdateSearchQuery; exports.doUpdateTotalBalance = doUpdateTotalBalance; 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.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.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.selectIsSearching = selectIsSearching; exports.selectIsSendingSupport = selectIsSendingSupport; exports.selectLastClaimSearchUris = selectLastClaimSearchUris; exports.selectLastPurchasedUri = selectLastPurchasedUri; exports.selectMyActiveClaims = selectMyActiveClaims; exports.selectMyChannelClaims = selectMyChannelClaims; 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.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.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;