Initial commit

This commit is contained in:
Akinwale Ariwodola 2018-01-11 12:54:20 +01:00
commit cf9b0cfdaf
18 changed files with 2291 additions and 0 deletions

30
.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
/node_modules
/LBRY-darwin-x64
/dist
/src/main/dist
/src/main/locales
/src/main/node_modules
/src/renderer/dist
/build/venv
/build/daemon.ver
/lbry-app-venv
/lbry-app
/lbry-venv
/static/daemon/lbrynet*
/static/locales
/daemon/build
/daemon/venv
/daemon/requirements.txt
/.idea
*.pyc
*.iml
.#*
build/daemon.zip
.vimrc
package-lock.json
.DS_Store

1085
build/index.js Normal file

File diff suppressed because it is too large Load diff

111
package.json Normal file
View file

@ -0,0 +1,111 @@
{
"name": "lbry-redux",
"version": "0.0.1",
"description": "Common shared components for desktop and mobile.",
"homepage": "https://lbry.io/",
"bugs": {
"url": "https://github.com/lbryio/lbry-redux/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/lbryio/lbry-redux"
},
"author": {
"name": "LBRY Inc.",
"email": "hello@lbry.io"
},
"main": "build/index.js",
"scripts": {
"build": "webpack",
"lint": "eslint 'src/**/*.{js,jsx}' --fix",
"format": "prettier 'src/**/*.{js,jsx,scss,json}' --write"
},
"keywords": [
"lbry"
],
"dependencies": {
"amplitude-js": "^4.0.0",
"bluebird": "^3.5.1",
"classnames": "^2.2.5",
"electron-dl": "^1.6.0",
"formik": "^0.10.4",
"from2": "^2.3.0",
"install": "^0.10.2",
"jayson": "^2.0.2",
"jshashes": "^1.0.7",
"keytar": "^4.0.3",
"localforage": "^1.5.0",
"npm": "^5.5.1",
"qrcode.react": "^0.7.2",
"rc-progress": "^2.0.6",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-markdown": "^2.5.0",
"react-modal": "^3.1.7",
"react-paginate": "^5.0.0",
"react-redux": "^5.0.3",
"react-simplemde-editor": "^3.6.11",
"redux": "^3.6.0",
"redux-action-buffer": "^1.1.0",
"redux-logger": "^3.0.1",
"redux-persist": "^4.8.0",
"redux-persist-transform-compress": "^4.2.0",
"redux-persist-transform-filter": "0.0.10",
"redux-thunk": "^2.2.0",
"render-media": "^2.10.0",
"reselect": "^3.0.0",
"semver": "^5.3.0",
"shapeshift.io": "^1.3.1",
"source-map-support": "^0.5.0",
"tree-kill": "^1.1.0",
"y18n": "^4.0.0"
},
"devDependencies": {
"babel-eslint": "^8.0.3",
"babel-plugin-module-resolver": "^3.0.0",
"babel-plugin-react-require": "^3.0.0",
"babel-polyfill": "^6.20.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.18.0",
"devtron": "^1.4.0",
"electron": "^1.7.9",
"electron-builder": "^19.49.0",
"electron-devtools-installer": "^2.2.1",
"electron-webpack": "^1.11.0",
"eslint": "^4.13.1",
"eslint-config-airbnb": "^16.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-import-resolver-webpack": "^0.8.3",
"eslint-plugin-flowtype": "^2.40.1",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-prettier": "^2.4.0",
"eslint-plugin-react": "^7.5.1",
"flow-babel-webpack-plugin": "^1.1.0",
"flow-bin": "^0.61.0",
"flow-typed": "^2.2.3",
"husky": "^0.14.3",
"i18n-extract": "^0.5.1",
"json-loader": "^0.5.4",
"lint-staged": "^6.0.0",
"node-loader": "^0.6.0",
"node-sass": "^4.7.2",
"prettier": "^1.4.2",
"sass-loader": "^6.0.6",
"webpack": "^3.10.0",
"webpack-build-notifier": "^0.1.18"
},
"resolutions": {
"webpack/webpack-sources": "1.0.1"
},
"engines": {
"node": ">=6",
"yarn": "^1.3"
},
"license": "MIT",
"lbrySettings": {
"lbrynetDaemonVersion": "0.18.0",
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-daemon-vDAEMONVER-OSNAME.zip"
}
}

View file

@ -0,0 +1,164 @@
export const OPEN_MODAL = 'OPEN_MODAL';
export const CLOSE_MODAL = 'CLOSE_MODAL';
export const SHOW_SNACKBAR = 'SHOW_SNACKBAR';
export const REMOVE_SNACKBAR_SNACK = 'REMOVE_SNACKBAR_SNACK';
export const WINDOW_FOCUSED = 'WINDOW_FOCUSED';
export const DAEMON_READY = 'DAEMON_READY';
export const DAEMON_VERSION_MATCH = 'DAEMON_VERSION_MATCH';
export const DAEMON_VERSION_MISMATCH = 'DAEMON_VERSION_MISMATCH';
export const VOLUME_CHANGED = 'VOLUME_CHANGED';
// Navigation
export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH';
export const WINDOW_SCROLLED = 'WINDOW_SCROLLED';
export const HISTORY_NAVIGATE = 'HISTORY_NAVIGATE';
// Upgrades
export const UPGRADE_CANCELLED = 'UPGRADE_CANCELLED';
export const DOWNLOAD_UPGRADE = 'DOWNLOAD_UPGRADE';
export const UPGRADE_DOWNLOAD_STARTED = 'UPGRADE_DOWNLOAD_STARTED';
export const UPGRADE_DOWNLOAD_COMPLETED = 'UPGRADE_DOWNLOAD_COMPLETED';
export const UPGRADE_DOWNLOAD_PROGRESSED = 'UPGRADE_DOWNLOAD_PROGRESSED';
export const CHECK_UPGRADE_AVAILABLE = 'CHECK_UPGRADE_AVAILABLE';
export const CHECK_UPGRADE_START = 'CHECK_UPGRADE_START';
export const CHECK_UPGRADE_SUCCESS = 'CHECK_UPGRADE_SUCCESS';
export const CHECK_UPGRADE_FAIL = 'CHECK_UPGRADE_FAIL';
export const CHECK_UPGRADE_SUBSCRIBE = 'CHECK_UPGRADE_SUBSCRIBE';
export const UPDATE_VERSION = 'UPDATE_VERSION';
export const UPDATE_REMOTE_VERSION = 'UPDATE_REMOTE_VERSION';
export const SKIP_UPGRADE = 'SKIP_UPGRADE';
export const START_UPGRADE = 'START_UPGRADE';
// Wallet
export const GET_NEW_ADDRESS_STARTED = 'GET_NEW_ADDRESS_STARTED';
export const GET_NEW_ADDRESS_COMPLETED = 'GET_NEW_ADDRESS_COMPLETED';
export const FETCH_TRANSACTIONS_STARTED = 'FETCH_TRANSACTIONS_STARTED';
export const FETCH_TRANSACTIONS_COMPLETED = 'FETCH_TRANSACTIONS_COMPLETED';
export const UPDATE_BALANCE = 'UPDATE_BALANCE';
export const CHECK_ADDRESS_IS_MINE_STARTED = 'CHECK_ADDRESS_IS_MINE_STARTED';
export const CHECK_ADDRESS_IS_MINE_COMPLETED = 'CHECK_ADDRESS_IS_MINE_COMPLETED';
export const SET_DRAFT_TRANSACTION_AMOUNT = 'SET_DRAFT_TRANSACTION_AMOUNT';
export const SET_DRAFT_TRANSACTION_ADDRESS = 'SET_DRAFT_TRANSACTION_ADDRESS';
export const SEND_TRANSACTION_STARTED = 'SEND_TRANSACTION_STARTED';
export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED';
export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED';
export const FETCH_BLOCK_SUCCESS = 'FETCH_BLOCK_SUCCESS';
export const SUPPORT_TRANSACTION_STARTED = 'SUPPORT_TRANSACTION_STARTED';
export const SUPPORT_TRANSACTION_COMPLETED = 'SUPPORT_TRANSACTION_COMPLETED';
export const SUPPORT_TRANSACTION_FAILED = 'SUPPORT_TRANSACTION_FAILED';
// Claims
export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED';
export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED';
export const RESOLVE_URIS_STARTED = 'RESOLVE_URIS_STARTED';
export const RESOLVE_URIS_COMPLETED = 'RESOLVE_URIS_COMPLETED';
export const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED';
export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED';
export const FETCH_CHANNEL_CLAIM_COUNT_STARTED = 'FETCH_CHANNEL_CLAIM_COUNT_STARTED';
export const FETCH_CHANNEL_CLAIM_COUNT_COMPLETED = 'FETCH_CHANNEL_CLAIM_COUNT_COMPLETED';
export const FETCH_CLAIM_LIST_MINE_STARTED = 'FETCH_CLAIM_LIST_MINE_STARTED';
export const FETCH_CLAIM_LIST_MINE_COMPLETED = 'FETCH_CLAIM_LIST_MINE_COMPLETED';
export const ABANDON_CLAIM_STARTED = 'ABANDON_CLAIM_STARTED';
export const ABANDON_CLAIM_SUCCEEDED = 'ABANDON_CLAIM_SUCCEEDED';
export const FETCH_CHANNEL_LIST_MINE_STARTED = 'FETCH_CHANNEL_LIST_MINE_STARTED';
export const FETCH_CHANNEL_LIST_MINE_COMPLETED = 'FETCH_CHANNEL_LIST_MINE_COMPLETED';
export const CREATE_CHANNEL_STARTED = 'CREATE_CHANNEL_STARTED';
export const CREATE_CHANNEL_COMPLETED = 'CREATE_CHANNEL_COMPLETED';
export const PUBLISH_STARTED = 'PUBLISH_STARTED';
export const PUBLISH_COMPLETED = 'PUBLISH_COMPLETED';
export const PUBLISH_FAILED = 'PUBLISH_FAILED';
export const SET_PLAYING_URI = 'PLAY_URI';
// Files
export const FILE_LIST_STARTED = 'FILE_LIST_STARTED';
export const FILE_LIST_SUCCEEDED = 'FILE_LIST_SUCCEEDED';
export const FETCH_FILE_INFO_STARTED = 'FETCH_FILE_INFO_STARTED';
export const FETCH_FILE_INFO_COMPLETED = 'FETCH_FILE_INFO_COMPLETED';
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED';
export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED';
export const LOADING_VIDEO_STARTED = 'LOADING_VIDEO_STARTED';
export const LOADING_VIDEO_COMPLETED = 'LOADING_VIDEO_COMPLETED';
export const LOADING_VIDEO_FAILED = 'LOADING_VIDEO_FAILED';
export const DOWNLOADING_STARTED = 'DOWNLOADING_STARTED';
export const DOWNLOADING_PROGRESSED = 'DOWNLOADING_PROGRESSED';
export const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED';
export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED';
export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED';
export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED';
export const FILE_DELETE = 'FILE_DELETE';
// Search
export const SEARCH_STARTED = 'SEARCH_STARTED';
export const SEARCH_COMPLETED = 'SEARCH_COMPLETED';
export const SEARCH_CANCELLED = 'SEARCH_CANCELLED';
// Settings
export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED';
export const CLIENT_SETTING_CHANGED = 'CLIENT_SETTING_CHANGED';
// User
export const AUTHENTICATION_STARTED = 'AUTHENTICATION_STARTED';
export const AUTHENTICATION_SUCCESS = 'AUTHENTICATION_SUCCESS';
export const AUTHENTICATION_FAILURE = 'AUTHENTICATION_FAILURE';
export const USER_EMAIL_DECLINE = 'USER_EMAIL_DECLINE';
export const USER_EMAIL_NEW_STARTED = 'USER_EMAIL_NEW_STARTED';
export const USER_EMAIL_NEW_SUCCESS = 'USER_EMAIL_NEW_SUCCESS';
export const USER_EMAIL_NEW_EXISTS = 'USER_EMAIL_NEW_EXISTS';
export const USER_EMAIL_NEW_FAILURE = 'USER_EMAIL_NEW_FAILURE';
export const USER_EMAIL_VERIFY_STARTED = 'USER_EMAIL_VERIFY_STARTED';
export const USER_EMAIL_VERIFY_SUCCESS = 'USER_EMAIL_VERIFY_SUCCESS';
export const USER_EMAIL_VERIFY_FAILURE = 'USER_EMAIL_VERIFY_FAILURE';
export const USER_IDENTITY_VERIFY_STARTED = 'USER_IDENTITY_VERIFY_STARTED';
export const USER_IDENTITY_VERIFY_SUCCESS = 'USER_IDENTITY_VERIFY_SUCCESS';
export const USER_IDENTITY_VERIFY_FAILURE = 'USER_IDENTITY_VERIFY_FAILURE';
export const USER_FETCH_STARTED = 'USER_FETCH_STARTED';
export const USER_FETCH_SUCCESS = 'USER_FETCH_SUCCESS';
export const USER_FETCH_FAILURE = 'USER_FETCH_FAILURE';
export const USER_INVITE_STATUS_FETCH_STARTED = 'USER_INVITE_STATUS_FETCH_STARTED';
export const USER_INVITE_STATUS_FETCH_SUCCESS = 'USER_INVITE_STATUS_FETCH_SUCCESS';
export const USER_INVITE_STATUS_FETCH_FAILURE = 'USER_INVITE_STATUS_FETCH_FAILURE';
export const USER_INVITE_NEW_STARTED = 'USER_INVITE_NEW_STARTED';
export const USER_INVITE_NEW_SUCCESS = 'USER_INVITE_NEW_SUCCESS';
export const USER_INVITE_NEW_FAILURE = 'USER_INVITE_NEW_FAILURE';
export const FETCH_ACCESS_TOKEN_SUCCESS = 'FETCH_ACCESS_TOKEN_SUCCESS';
// Rewards
export const FETCH_REWARDS_STARTED = 'FETCH_REWARDS_STARTED';
export const FETCH_REWARDS_COMPLETED = 'FETCH_REWARDS_COMPLETED';
export const CLAIM_REWARD_STARTED = 'CLAIM_REWARD_STARTED';
export const CLAIM_REWARD_SUCCESS = 'CLAIM_REWARD_SUCCESS';
export const CLAIM_REWARD_FAILURE = 'CLAIM_REWARD_FAILURE';
export const CLAIM_REWARD_CLEAR_ERROR = 'CLAIM_REWARD_CLEAR_ERROR';
export const FETCH_REWARD_CONTENT_COMPLETED = 'FETCH_REWARD_CONTENT_COMPLETED';
// Language
export const DOWNLOAD_LANGUAGE_SUCCEEDED = 'DOWNLOAD_LANGUAGE_SUCCEEDED';
export const DOWNLOAD_LANGUAGE_FAILED = 'DOWNLOAD_LANGUAGE_FAILED';
// ShapeShift
export const GET_SUPPORTED_COINS_START = 'GET_SUPPORTED_COINS_START';
export const GET_SUPPORTED_COINS_SUCCESS = 'GET_SUPPORTED_COINS_SUCCESS';
export const GET_SUPPORTED_COINS_FAIL = 'GET_SUPPORTED_COINS_FAIL';
export const GET_COIN_STATS_START = 'GET_COIN_STATS_START';
export const GET_COIN_STATS_SUCCESS = 'GET_COIN_STATS_SUCCESS';
export const GET_COIN_STATS_FAIL = 'GET_COIN_STATS_FAIL';
export const PREPARE_SHAPE_SHIFT_START = 'PREPARE_SHAPE_SHIFT_START';
export const PREPARE_SHAPE_SHIFT_SUCCESS = 'PREPARE_SHAPE_SHIFT_SUCCESS';
export const PREPARE_SHAPE_SHIFT_FAIL = 'PREPARE_SHAPE_SHIFT_FAIL';
export const GET_ACTIVE_SHIFT_START = 'GET_ACTIVE_SHIFT_START';
export const GET_ACTIVE_SHIFT_SUCCESS = 'GET_ACTIVE_SHIFT_SUCCESS';
export const GET_ACTIVE_SHIFT_FAIL = 'GET_ACTIVE_SHIFT_FAIL';
export const CLEAR_SHAPE_SHIFT = 'CLEAR_SHAPE_SHIFT';
// Subscriptions
export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
// Video controls
export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';
// Media controls
export const MEDIA_PLAY = 'MEDIA_PLAY';
export const MEDIA_PAUSE = 'MEDIA_PAUSE';
export const MEDIA_POSITION = 'MEDIA_POSITION';

5
src/constants/icons.js Normal file
View file

@ -0,0 +1,5 @@
export const FEATURED = 'rocket';
export const LOCAL = 'folder';
export const FILE = 'file';
export const HISTORY = 'history';
export const HELP_CIRCLE = 'question-circle';

187
src/constants/languages.js Normal file
View file

@ -0,0 +1,187 @@
const LANGUAGES = {
aa: ['Afar', 'Afar'],
ab: ['Abkhazian', 'Аҧсуа'],
af: ['Afrikaans', 'Afrikaans'],
ak: ['Akan', 'Akana'],
am: ['Amharic', 'አማርኛ'],
an: ['Aragonese', 'Aragonés'],
ar: ['Arabic', 'العربية'],
as: ['Assamese', 'অসমীয়া'],
av: ['Avar', 'Авар'],
ay: ['Aymara', 'Aymar'],
az: ['Azerbaijani', 'Azərbaycanca / آذربايجان'],
ba: ['Bashkir', 'Башҡорт'],
be: ['Belarusian', 'Беларуская'],
bg: ['Bulgarian', 'Български'],
bh: ['Bihari', 'भोजपुरी'],
bi: ['Bislama', 'Bislama'],
bm: ['Bambara', 'Bamanankan'],
bn: ['Bengali', 'বাংলা'],
bo: ['Tibetan', 'བོད་ཡིག / Bod skad'],
br: ['Breton', 'Brezhoneg'],
bs: ['Bosnian', 'Bosanski'],
ca: ['Catalan', 'Català'],
ce: ['Chechen', 'Нохчийн'],
ch: ['Chamorro', 'Chamoru'],
co: ['Corsican', 'Corsu'],
cr: ['Cree', 'Nehiyaw'],
cs: ['Czech', 'Česky'],
cu: ['Old Church Slavonic / Old Bulgarian', 'словѣньскъ / slověnĭskŭ'],
cv: ['Chuvash', 'Чăваш'],
cy: ['Welsh', 'Cymraeg'],
da: ['Danish', 'Dansk'],
de: ['German', 'Deutsch'],
dv: ['Divehi', 'ދިވެހިބަސް'],
dz: ['Dzongkha', 'ཇོང་ཁ'],
ee: ['Ewe', 'Ɛʋɛ'],
el: ['Greek', 'Ελληνικά'],
en: ['English', 'English'],
eo: ['Esperanto', 'Esperanto'],
es: ['Spanish', 'Español'],
et: ['Estonian', 'Eesti'],
eu: ['Basque', 'Euskara'],
fa: ['Persian', 'فارسی'],
ff: ['Peul', 'Fulfulde'],
fi: ['Finnish', 'Suomi'],
fj: ['Fijian', 'Na Vosa Vakaviti'],
fo: ['Faroese', 'Føroyskt'],
fr: ['French', 'Français'],
fy: ['West Frisian', 'Frysk'],
ga: ['Irish', 'Gaeilge'],
gd: ['Scottish Gaelic', 'Gàidhlig'],
gl: ['Galician', 'Galego'],
gn: ['Guarani', "Avañe'ẽ"],
gu: ['Gujarati', 'ગુજરાતી'],
gv: ['Manx', 'Gaelg'],
ha: ['Hausa', 'هَوُسَ'],
he: ['Hebrew', 'עברית'],
hi: ['Hindi', 'हिन्दी'],
ho: ['Hiri Motu', 'Hiri Motu'],
hr: ['Croatian', 'Hrvatski'],
ht: ['Haitian', 'Krèyol ayisyen'],
hu: ['Hungarian', 'Magyar'],
hy: ['Armenian', 'Հայերեն'],
hz: ['Herero', 'Otsiherero'],
ia: ['Interlingua', 'Interlingua'],
id: ['Indonesian', 'Bahasa Indonesia'],
ie: ['Interlingue', 'Interlingue'],
ig: ['Igbo', 'Igbo'],
ii: ['Sichuan Yi', 'ꆇꉙ / 四川彝语'],
ik: ['Inupiak', 'Iñupiak'],
io: ['Ido', 'Ido'],
is: ['Icelandic', 'Íslenska'],
it: ['Italian', 'Italiano'],
iu: ['Inuktitut', 'ᐃᓄᒃᑎᑐᑦ'],
ja: ['Japanese', '日本語'],
jv: ['Javanese', 'Basa Jawa'],
ka: ['Georgian', 'ქართული'],
kg: ['Kongo', 'KiKongo'],
ki: ['Kikuyu', 'Gĩkũyũ'],
kj: ['Kuanyama', 'Kuanyama'],
kk: ['Kazakh', 'Қазақша'],
kl: ['Greenlandic', 'Kalaallisut'],
km: ['Cambodian', 'ភាសាខ្មែរ'],
kn: ['Kannada', 'ಕನ್ನಡ'],
ko: ['Korean', '한국어'],
kr: ['Kanuri', 'Kanuri'],
ks: ['Kashmiri', 'कश्मीरी / كشميري'],
ku: ['Kurdish', 'Kurdî / كوردی'],
kv: ['Komi', 'Коми'],
kw: ['Cornish', 'Kernewek'],
ky: ['Kirghiz', 'Kırgızca / Кыргызча'],
la: ['Latin', 'Latina'],
lb: ['Luxembourgish', 'Lëtzebuergesch'],
lg: ['Ganda', 'Luganda'],
li: ['Limburgian', 'Limburgs'],
ln: ['Lingala', 'Lingála'],
lo: ['Laotian', 'ລາວ / Pha xa lao'],
lt: ['Lithuanian', 'Lietuvių'],
lv: ['Latvian', 'Latviešu'],
mg: ['Malagasy', 'Malagasy'],
mh: ['Marshallese', 'Kajin Majel / Ebon'],
mi: ['Maori', 'Māori'],
mk: ['Macedonian', 'Македонски'],
ml: ['Malayalam', 'മലയാളം'],
mn: ['Mongolian', 'Монгол'],
mo: ['Moldovan', 'Moldovenească'],
mr: ['Marathi', 'मराठी'],
ms: ['Malay', 'Bahasa Melayu'],
mt: ['Maltese', 'bil-Malti'],
my: ['Burmese', 'Myanmasa'],
na: ['Nauruan', 'Dorerin Naoero'],
nd: ['North Ndebele', 'Sindebele'],
ne: ['Nepali', 'नेपाली'],
ng: ['Ndonga', 'Oshiwambo'],
nl: ['Dutch', 'Nederlands'],
nn: ['Norwegian Nynorsk', 'Norsk (nynorsk)'],
no: ['Norwegian', 'Norsk (bokmål / riksmål)'],
nr: ['South Ndebele', 'isiNdebele'],
nv: ['Navajo', 'Diné bizaad'],
ny: ['Chichewa', 'Chi-Chewa'],
oc: ['Occitan', 'Occitan'],
oj: ['Ojibwa', 'ᐊᓂᔑᓈᐯᒧᐎᓐ / Anishinaabemowin'],
om: ['Oromo', 'Oromoo'],
or: ['Oriya', 'ଓଡ଼ିଆ'],
os: ['Ossetian / Ossetic', 'Иронау'],
pa: ['Panjabi / Punjabi', 'ਪੰਜਾਬੀ / पंजाबी / پنجابي'],
pi: ['Pali', 'Pāli / पाऴि'],
pl: ['Polish', 'Polski'],
ps: ['Pashto', 'پښتو'],
pt: ['Portuguese', 'Português'],
qu: ['Quechua', 'Runa Simi'],
rm: ['Raeto Romance', 'Rumantsch'],
rn: ['Kirundi', 'Kirundi'],
ro: ['Romanian', 'Română'],
ru: ['Russian', 'Русский'],
rw: ['Rwandi', 'Kinyarwandi'],
sa: ['Sanskrit', 'संस्कृतम्'],
sc: ['Sardinian', 'Sardu'],
sd: ['Sindhi', 'सिनधि'],
se: ['Northern Sami', 'Sámegiella'],
sg: ['Sango', 'Sängö'],
sh: ['Serbo-Croatian', 'Srpskohrvatski / Српскохрватски'],
si: ['Sinhalese', 'සිංහල'],
sk: ['Slovak', 'Slovenčina'],
sl: ['Slovenian', 'Slovenščina'],
sm: ['Samoan', 'Gagana Samoa'],
sn: ['Shona', 'chiShona'],
so: ['Somalia', 'Soomaaliga'],
sq: ['Albanian', 'Shqip'],
sr: ['Serbian', 'Српски'],
ss: ['Swati', 'SiSwati'],
st: ['Southern Sotho', 'Sesotho'],
su: ['Sundanese', 'Basa Sunda'],
sv: ['Swedish', 'Svenska'],
sw: ['Swahili', 'Kiswahili'],
ta: ['Tamil', 'தமிழ்'],
te: ['Telugu', 'తెలుగు'],
tg: ['Tajik', 'Тоҷикӣ'],
th: ['Thai', 'ไทย / Phasa Thai'],
ti: ['Tigrinya', 'ትግርኛ'],
tk: ['Turkmen', 'Туркмен / تركمن'],
tl: ['Tagalog / Filipino', 'Tagalog'],
tn: ['Tswana', 'Setswana'],
to: ['Tonga', 'Lea Faka-Tonga'],
tr: ['Turkish', 'Türkçe'],
ts: ['Tsonga', 'Xitsonga'],
tt: ['Tatar', 'Tatarça'],
tw: ['Twi', 'Twi'],
ty: ['Tahitian', 'Reo Mā`ohi'],
ug: ['Uyghur', 'Uyƣurqə / ئۇيغۇرچە'],
uk: ['Ukrainian', 'Українська'],
ur: ['Urdu', 'اردو'],
uz: ['Uzbek', 'Ўзбек'],
ve: ['Venda', 'Tshivenḓa'],
vi: ['Vietnamese', 'Tiếng Việt'],
vo: ['Volapük', 'Volapük'],
wa: ['Walloon', 'Walon'],
wo: ['Wolof', 'Wollof'],
xh: ['Xhosa', 'isiXhosa'],
yi: ['Yiddish', 'ייִדיש'],
yo: ['Yoruba', 'Yorùbá'],
za: ['Zhuang', 'Cuengh / Tôô / 壮语'],
zh: ['Chinese', '中文'],
zu: ['Zulu', 'isiZulu'],
};
export default LANGUAGES;

View file

@ -0,0 +1,15 @@
export const CONFIRM_FILE_REMOVE = 'confirmFileRemove';
export const INCOMPATIBLE_DAEMON = 'incompatibleDaemon';
export const FILE_TIMEOUT = 'file_timeout';
export const DOWNLOADING = 'downloading';
export const ERROR = 'error';
export const INSUFFICIENT_CREDITS = 'insufficient_credits';
export const UPGRADE = 'upgrade';
export const WELCOME = 'welcome';
export const EMAIL_COLLECTION = 'email_collection';
export const FIRST_REWARD = 'first_reward';
export const AUTHENTICATION_FAILURE = 'auth_failure';
export const TRANSACTION_FAILED = 'transaction_failed';
export const REWARD_APPROVAL_REQUIRED = 'reward_approval_required';
export const AFFIRM_PURCHASE = 'affirm_purchase';
export const CONFIRM_CLAIM_REVOKE = 'confirmClaimRevoke';

13
src/constants/settings.js Normal file
View file

@ -0,0 +1,13 @@
/* 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 */
export const CREDIT_REQUIRED_ACKNOWLEDGED = 'credit_required_acknowledged';
export const NEW_USER_ACKNOWLEDGED = 'welcome_acknowledged';
export const EMAIL_COLLECTION_ACKNOWLEDGED = 'email_collection_acknowledged';
export const LANGUAGE = 'language';
export const SHOW_NSFW = 'showNsfw';
export const SHOW_UNAVAILABLE = 'showUnavailable';
export const INSTANT_PURCHASE_ENABLED = 'instantPurchaseEnabled';
export const INSTANT_PURCHASE_MAX = 'instantPurchaseMax';
export const THEME = 'theme';
export const THEMES = 'themes';

View file

@ -0,0 +1,3 @@
export const NO_DEPOSITS = 'no_deposits';
export const RECEIVED = 'received';
export const COMPLETE = 'complete';

View file

@ -0,0 +1,2 @@
// eslint-disable-next-line import/prefer-default-export
export const TIP = 'tip';

2
src/index.js Normal file
View file

@ -0,0 +1,2 @@
export { lbry } from './lbry';
export { lbryio } from './lbryio';

86
src/jsonrpc.js Normal file
View file

@ -0,0 +1,86 @@
const jsonrpc = {};
jsonrpc.call = (
connectionString,
method,
params,
callback,
errorCallback,
connectFailedCallback
) => {
function checkAndParse(response) {
if (response.status >= 200 && response.status < 300) {
return response.json();
}
return response.json().then(json => {
let error;
if (json.error) {
error = new Error(json.error);
} else {
error = new Error('Protocol error with unknown response signature');
}
return Promise.reject(error);
});
}
const counter = parseInt(sessionStorage.getItem('JSONRPCCounter') || 0, 10);
const url = connectionString;
const options = {
method: 'POST',
body: JSON.stringify({
jsonrpc: '2.0',
method,
params,
id: counter,
}),
};
sessionStorage.setItem('JSONRPCCounter', counter + 1);
return fetch(url, options)
.then(checkAndParse)
.then(response => {
const error = response.error || (response.result && response.result.error);
if (!error && typeof callback === 'function') {
return callback(response.result);
}
if (error && typeof errorCallback === 'function') {
return errorCallback(error);
}
const errorEvent = new CustomEvent('unhandledError', {
detail: {
connectionString,
method,
params,
code: error.code,
message: error.message || error,
data: error.data,
},
});
document.dispatchEvent(errorEvent);
return Promise.resolve();
})
.catch(error => {
if (connectFailedCallback) {
return connectFailedCallback(error);
}
const errorEvent = new CustomEvent('unhandledError', {
detail: {
connectionString,
method,
params,
code: error.response && error.response.status,
message: __('Connection to API server failed'),
},
});
document.dispatchEvent(errorEvent);
return Promise.resolve();
});
};
export default jsonrpc;

289
src/lbry.js Normal file
View file

@ -0,0 +1,289 @@
import jsonrpc from 'jsonrpc';
const CHECK_DAEMON_STARTED_TRY_NUMBER = 200;
const Lbry = {
isConnected: false,
daemonConnectionString: 'http://localhost:5279',
pendingPublishTimeout: 20 * 60 * 1000,
};
function apiCall(method, params, resolve, reject) {
return jsonrpc.call(Lbry.daemonConnectionString, method, params, resolve, reject, reject);
}
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);
});
},
});
function getLocal(key, fallback = undefined) {
const itemRaw = localStorage.getItem(key);
return itemRaw === null ? fallback : JSON.parse(itemRaw);
}
function setLocal(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
/**
* Records a publish attempt in local storage. Returns a dictionary with all the data needed to
* needed to make a dummy claim or file info object.
*/
let pendingId = 0;
function savePendingPublish({ name, channelName }) {
pendingId += 1;
const pendingPublishes = getLocal('pendingPublishes') || [];
const newPendingPublish = {
name,
channelName,
claim_id: `pending-${pendingId}`,
txid: `pending-${pendingId}`,
nout: 0,
outpoint: `pending-${pendingId}:0`,
time: Date.now(),
};
setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]);
return newPendingPublish;
}
/**
* If there is a pending publish with the given name or outpoint, remove it.
* A channel name may also be provided along with name.
*/
function removePendingPublishIfNeeded({ name, channelName, outpoint }) {
function pubMatches(pub) {
return (
pub.outpoint === outpoint ||
(pub.name === name && (!channelName || pub.channel_name === channelName))
);
}
setLocal('pendingPublishes', Lbry.getPendingPublishes().filter(pub => !pubMatches(pub)));
}
/**
* Gets the current list of pending publish attempts. Filters out any that have timed out and
* removes them from the list.
*/
Lbry.getPendingPublishes = () => {
const pendingPublishes = getLocal('pendingPublishes') || [];
const newPendingPublishes = pendingPublishes.filter(
pub => Date.now() - pub.time <= Lbry.pendingPublishTimeout
);
setLocal('pendingPublishes', newPendingPublishes);
return newPendingPublishes;
};
/**
* Gets a pending publish attempt by its name or (fake) outpoint. A channel name can also be
* provided along withe the name. If no pending publish is found, returns null.
*/
function getPendingPublish({ name, channelName, outpoint }) {
const pendingPublishes = Lbry.getPendingPublishes();
return (
pendingPublishes.find(
pub =>
pub.outpoint === outpoint ||
(pub.name === name && (!channelName || pub.channel_name === channelName))
) || null
);
}
function pendingPublishToDummyClaim({ channelName, name, outpoint, claimId, txid, nout }) {
return { name, outpoint, claimId, txid, nout, channelName };
}
function pendingPublishToDummyFileInfo({ name, outpoint, claimId }) {
return { name, outpoint, claimId, metadata: null };
}
// core
Lbry.connectPromise = null;
Lbry.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;
lbryProxy
.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();
});
}
return Lbry.connectPromise;
};
/**
* Publishes a file. The optional fileListedCallback is called when the file becomes available in
* lbry.file_list() during the publish process.
*
* This currently includes a work-around to cache the file in local storage so that the pending
* publish can appear in the UI immediately.
*/
Lbry.publishDeprecated = (params, fileListedCallback, publishedCallback, errorCallback) => {
// Give a short grace period in case publish() returns right away or (more likely) gives an error
const returnPendingTimeout = setTimeout(
() => {
const { name, channel_name: channelName } = params;
if (publishedCallback || fileListedCallback) {
savePendingPublish({
name,
channelName,
});
publishedCallback(true);
}
},
2000,
{ once: true }
);
lbryProxy.publish(params).then(
result => {
if (returnPendingTimeout) clearTimeout(returnPendingTimeout);
publishedCallback(result);
},
err => {
if (returnPendingTimeout) clearTimeout(returnPendingTimeout);
errorCallback(err);
}
);
};
Lbry.imagePath = file => `${staticResourcesPath}/img/${file}`;
Lbry.getMediaType = (contentType, fileName) => {
if (contentType) {
return /^[^/]+/.exec(contentType)[0];
} else if (fileName) {
const dotIndex = fileName.lastIndexOf('.');
if (dotIndex === -1) {
return 'unknown';
}
const ext = fileName.substr(dotIndex + 1);
if (/^mp4|m4v|webm|flv|f4v|ogv$/i.test(ext)) {
return 'video';
} else if (/^mp3|m4a|aac|wav|flac|ogg|opus$/i.test(ext)) {
return 'audio';
} else if (/^html|htm|xml|pdf|odf|doc|docx|md|markdown|txt|epub|org$/i.test(ext)) {
return 'document';
}
return 'unknown';
}
return 'unknown';
};
Lbry.getAppVersionInfo = () =>
new Promise(resolve => {
/*ipcRenderer.once('version-info-received', (event, versionInfo) => {
resolve(versionInfo);
});
ipcRenderer.send('version-info-requested');*/
});
/**
* Wrappers for API methods to simulate missing or future behavior. Unlike the old-style stubs,
* these are designed to be transparent wrappers around the corresponding API methods.
*/
/**
* Returns results from the file_list API method, plus dummy entries for pending publishes.
* (If a real publish with the same name is found, the pending publish will be ignored and removed.)
*/
Lbry.file_list = (params = {}) =>
new Promise((resolve, reject) => {
const { name, channel_name: channelName, outpoint } = params;
/**
* If we're searching by outpoint, check first to see if there's a matching pending publish.
* Pending publishes use their own faux outpoints that are always unique, so we don't need
* to check if there's a real file.
*/
if (outpoint) {
const pendingPublish = getPendingPublish({ outpoint });
if (pendingPublish) {
resolve([pendingPublishToDummyFileInfo(pendingPublish)]);
return;
}
}
apiCall(
'file_list',
params,
fileInfos => {
removePendingPublishIfNeeded({ name, channelName, outpoint });
// if a naked file_list call, append the pending file infos
if (!name && !channelName && !outpoint) {
const dummyFileInfos = Lbry.getPendingPublishes().map(pendingPublishToDummyFileInfo);
resolve([...fileInfos, ...dummyFileInfos]);
} else {
resolve(fileInfos);
}
},
reject
);
});
Lbry.claim_list_mine = (params = {}) =>
new Promise((resolve, reject) => {
apiCall(
'claim_list_mine',
params,
claims => {
claims.forEach(({ name, channel_name: channelName, txid, nout }) => {
removePendingPublishIfNeeded({
name,
channelName,
outpoint: `${txid}:${nout}`,
});
});
const dummyClaims = Lbry.getPendingPublishes().map(pendingPublishToDummyClaim);
resolve([...claims, ...dummyClaims]);
},
reject
);
});
Lbry.resolve = (params = {}) =>
new Promise((resolve, reject) => {
apiCall(
'resolve',
params,
data => {
if ('uri' in params) {
// If only a single URI was requested, don't nest the results in an object
resolve(data && data[params.uri] ? data[params.uri] : {});
} else {
resolve(data || {});
}
},
reject
);
});
export default lbryProxy;

176
src/lbryio.js Normal file
View file

@ -0,0 +1,176 @@
import Lbry from 'lbry';
import querystring from 'querystring';
const Lbryio = {
enabled: true,
authenticationPromise: null,
exchangePromise: null,
exchangeLastFetched: null,
};
const CONNECTION_STRING = process.env.LBRY_APP_API_URL
? process.env.LBRY_APP_API_URL.replace(/\/*$/, '/') // exactly one slash at the end
: 'https://api.lbry.io/';
const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
Lbryio.getExchangeRates = () => {
if (
!Lbryio.exchangeLastFetched ||
Date.now() - Lbryio.exchangeLastFetched > EXCHANGE_RATE_TIMEOUT
) {
Lbryio.exchangePromise = new Promise((resolve, reject) => {
Lbryio.call('lbc', 'exchange_rate', {}, 'get', true)
.then(({ lbc_usd: LBC_USD, lbc_btc: LBC_BTC, btc_usd: BTC_USD }) => {
const rates = { LBC_USD, LBC_BTC, BTC_USD };
resolve(rates);
})
.catch(reject);
});
Lbryio.exchangeLastFetched = Date.now();
}
return Lbryio.exchangePromise;
};
Lbryio.call = (resource, action, params = {}, method = 'get') => {
if (!Lbryio.enabled) {
console.log(__('Internal API disabled'));
return Promise.reject(new Error(__('LBRY internal API is disabled')));
}
if (!(method === 'get' || method === 'post')) {
return Promise.reject(new Error(__('Invalid method')));
}
function checkAndParse(response) {
if (response.status >= 200 && response.status < 300) {
return response.json();
}
return response.json().then(json => {
let error;
if (json.error) {
error = new Error(json.error);
} else {
error = new Error('Unknown API error signature');
}
error.response = response; // This is primarily a hack used in actions/user.js
return Promise.reject(error);
});
}
function makeRequest(url, options) {
return fetch(url, options).then(checkAndParse);
}
return Lbryio.getAuthToken().then(token => {
const fullParams = { auth_token: token, ...params };
const qs = querystring.stringify(fullParams);
let url = `${CONNECTION_STRING}${resource}/${action}?${qs}`;
let options = {
method: 'GET',
};
if (method === 'post') {
options = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: qs,
};
url = `${CONNECTION_STRING}${resource}/${action}`;
}
return makeRequest(url, options).then(response => response.data);
});
};
Lbryio.authToken = null;
Lbryio.getAuthToken = () =>
new Promise(resolve => {
if (Lbryio.authToken) {
resolve(Lbryio.authToken);
} else {
/*ipcRenderer.once('auth-token-response', (event, token) => {
Lbryio.authToken = token;
return resolve(token);
});
ipcRenderer.send('get-auth-token');*/
}
});
Lbryio.setAuthToken = token => {
Lbryio.authToken = token ? token.toString().trim() : null;
//ipcRenderer.send('set-auth-token', token);
};
Lbryio.getCurrentUser = () => Lbryio.call('user', 'me');
Lbryio.authenticate = () => {
if (!Lbryio.enabled) {
return new Promise(resolve => {
resolve({
id: 1,
language: 'en',
primary_email: 'disabled@lbry.io',
has_verified_email: true,
is_identity_verified: true,
is_reward_approved: false,
});
});
}
if (Lbryio.authenticationPromise === null) {
Lbryio.authenticationPromise = new Promise((resolve, reject) => {
Lbryio.getAuthToken()
.then(token => {
if (!token || token.length > 60) {
return false;
}
// check that token works
return Lbryio.getCurrentUser()
.then(() => true)
.catch(() => false);
})
.then(isTokenValid => {
if (isTokenValid) {
return reject;
}
return Lbry.status()
.then(status =>
Lbryio.call(
'user',
'new',
{
auth_token: '',
language: 'en',
app_id: status.installation_id,
},
'post'
)
)
.then(response => {
if (!response.auth_token) {
throw new Error(__('auth_token is missing from response'));
}
return Lbryio.setAuthToken(response.auth_token);
});
})
.then(Lbryio.getCurrentUser)
.then(resolve, reject);
});
}
return Lbryio.authenticationPromise;
};
Lbryio.getStripeToken = () =>
CONNECTION_STRING.startsWith('http://localhost:')
? 'pk_test_NoL1JWL7i1ipfhVId5KfDZgo'
: 'pk_live_e8M4dRNnCCbmpZzduEUZBgJO';
export default Lbryio;

View file

@ -0,0 +1,38 @@
import * as ACTIONS from 'constants/action_types';
import Lbryio from 'lbryio';
import { selectClaimsByUri } from 'redux/selectors/claims';
// eslint-disable-next-line import/prefer-default-export
export function doFetchCostInfoForUri(uri) {
return (dispatch, getState) => {
const state = getState();
const claim = null; //selectClaimsByUri(state)[uri];
if (!claim) return;
function resolve(costInfo) {
dispatch({
type: ACTIONS.FETCH_COST_INFO_COMPLETED,
data: {
uri,
costInfo,
},
});
}
const fee =
claim.value && claim.value.stream && claim.value.stream.metadata
? claim.value.stream.metadata.fee
: undefined;
if (fee === undefined) {
resolve({ cost: 0, includesData: true });
} else if (fee.currency === 'LBC') {
resolve({ cost: fee.amount, includesData: true });
} else {
Lbryio.getExchangeRates().then(({ LBC_USD }) => {
resolve({ cost: fee.amount / LBC_USD, includesData: true });
});
}
};
}

View file

@ -0,0 +1,34 @@
import * as ACTIONS from 'constants/action_types';
const reducers = {};
const defaultState = {};
reducers[ACTIONS.FETCH_COST_INFO_STARTED] = (state, action) => {
const { uri } = action.data;
const newFetching = Object.assign({}, state.fetching);
newFetching[uri] = true;
return Object.assign({}, state, {
fetching: newFetching,
});
};
reducers[ACTIONS.FETCH_COST_INFO_COMPLETED] = (state, action) => {
const { uri, costInfo } = action.data;
const newByUri = Object.assign({}, state.byUri);
const newFetching = Object.assign({}, state.fetching);
newByUri[uri] = costInfo;
delete newFetching[uri];
return Object.assign({}, state, {
byUri: newByUri,
fetching: newFetching,
});
};
export default function reducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);
return state;
}

View file

@ -0,0 +1,20 @@
import { createSelector } from 'reselect';
import { selectCurrentParams } from 'redux/selectors/navigation';
export const selectState = state => state.costInfo || {};
export const selectAllCostInfoByUri = createSelector(selectState, state => state.byUri || {});
export const makeSelectCostInfoForUri = uri =>
createSelector(selectAllCostInfoByUri, costInfos => costInfos && costInfos[uri]);
export const selectCostForCurrentPageUri = createSelector(
selectAllCostInfoByUri,
selectCurrentParams,
(costInfo, params) => (params.uri && costInfo[params.uri] ? costInfo[params.uri].cost : undefined)
);
export const selectFetchingCostInfo = createSelector(selectState, state => state.fetching || {});
export const makeSelectFetchingCostInfoForUri = uri =>
createSelector(selectFetchingCostInfo, fetchingByUri => fetchingByUri && fetchingByUri[uri]);

31
webpack.config.js Normal file
View file

@ -0,0 +1,31 @@
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'index.js',
libraryTarget: 'commonjs2'
},
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, 'src'),
exclude: /(node_modules|bower_components|build)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react', 'stage-2']
}
}
}
]
},
resolve: {
modules: [path.resolve(__dirname, 'src/'), 'node_modules', __dirname],
extensions: ['.js', '.jsx', '.scss'],
},
externals: {
'react': 'commonjs react'
}
};