Compare commits
94 commits
master
...
anthony-fi
Author | SHA1 | Date | |
---|---|---|---|
|
11a6c78928 | ||
|
d5e582050a | ||
|
a77e59cb53 | ||
|
4b0a06cef7 | ||
|
00d28fe26e | ||
|
5dd5826b33 | ||
|
cbedc4b933 | ||
|
6b39fc1bbb | ||
|
f8f9b86cb4 | ||
|
1a5fb5fa51 | ||
|
c24153c6ca | ||
|
762bddb158 | ||
|
2922f0f2dc | ||
|
3849683a59 | ||
|
247ee757d1 | ||
|
03f69eff86 | ||
|
08adb805e9 | ||
|
3788ef58ec | ||
|
310fc81bd9 | ||
|
9569b6c7a5 | ||
|
0febd32c71 | ||
|
a90c516c71 | ||
|
dad7264636 | ||
|
27f346d8f1 | ||
|
e2176d0566 | ||
|
17121b2066 | ||
|
1b43c54725 | ||
|
398388de10 | ||
|
b8c763f749 | ||
|
b4f62e78de | ||
|
8c4224f1ce | ||
|
b7685a151d | ||
|
e14ec9b83e | ||
|
23525b0baa | ||
|
d62f63aff8 | ||
|
dcd00c2308 | ||
|
c782f73f30 | ||
|
6ff9a51058 | ||
|
9041e5e38d | ||
|
ce1621f7ed | ||
|
da63991972 | ||
|
b6ad4ae974 | ||
|
5d8fc40051 | ||
|
4b0318cd38 | ||
|
0c2c21b67e | ||
|
9bbd72d179 | ||
|
249b73f8c6 | ||
|
aabfc41ce9 | ||
|
5bcf89394e | ||
|
35072c0400 | ||
|
296febcffa | ||
|
cfdfdce2fe | ||
|
2a7f89d6b5 | ||
|
30023422b8 | ||
|
702297e722 | ||
|
07102c4988 | ||
|
3b442531ef | ||
|
f6e60abbf5 | ||
|
dab1ca1cb7 | ||
|
bba3a17977 | ||
|
4a22814c75 | ||
|
91be939c19 | ||
|
0b0f2848da | ||
|
055d437865 | ||
|
d1493d5fb3 | ||
|
b86a56f75b | ||
|
8498554f23 | ||
|
2505d67a7d | ||
|
03ea298236 | ||
|
a3302b1be8 | ||
|
58db9576b9 | ||
|
a9b9c3ccf0 | ||
|
ea516f88dc | ||
|
5f55a3f128 | ||
|
53063931ab | ||
|
6727e2766b | ||
|
c10fc675db | ||
|
fa889112c5 | ||
|
0e96f8d468 | ||
|
d5ad63c6e9 | ||
|
cd8f90c82d | ||
|
24eb2ef8ec | ||
|
37ddc395ea | ||
|
02a8099514 | ||
|
9dbee19961 | ||
|
b49fed4cf5 | ||
|
2b5d32c313 | ||
|
7c518aa712 | ||
|
6f3c43c95f | ||
|
a168dbcc01 | ||
|
3087f7c367 | ||
|
3f969ae20d | ||
|
19797c747e | ||
|
caadd889ce |
522 changed files with 15724 additions and 3020 deletions
|
@ -16,6 +16,7 @@ COMMENT_SERVER_NAME=Odysee
|
|||
SEARCH_SERVER_API=https://lighthouse.odysee.com/search
|
||||
SOCKETY_SERVER_API=wss://sockety.odysee.com/ws
|
||||
THUMBNAIL_CDN_URL=https://thumbnails.odysee.com/optimize/
|
||||
THUMBNAIL_CARDS_CDN_URL=https://cards.odysee.com/
|
||||
THUMBNAIL_HEIGHT=220
|
||||
THUMBNAIL_WIDTH=390
|
||||
THUMBNAIL_QUALITY=85
|
||||
|
@ -25,8 +26,6 @@ WELCOME_VERSION=1.0
|
|||
# STRIPE_PUBLIC_KEY='pk_test_NoL1JWL7i1ipfhVId5KfDZgo'
|
||||
|
||||
# Analytics
|
||||
MATOMO_URL=https://analytics.lbry.com/
|
||||
MATOMO_ID=4
|
||||
|
||||
# OG
|
||||
OG_TITLE_SUFFIX=| lbry.tv
|
||||
|
@ -116,3 +115,13 @@ ENABLE_UI_NOTIFICATIONS=false
|
|||
#MODELS_ENABLED=true
|
||||
|
||||
BRANDED_SITE=odysee
|
||||
|
||||
#FIREBASE
|
||||
FIREBASE_API_KEY=AIzaSyAgc-4QORyglpYZ3qH9E5pDauEDOJXgM3A
|
||||
FIREBASE_AUTH_DOMAIN=lbry-mobile.firebaseapp.com
|
||||
FIREBASE_PROJECT_ID=lbry-mobile
|
||||
FIREBASE_STORAGE_BUCKET=lbry-mobile.appspot.com
|
||||
FIREBASE_MESSAGING_SENDER_ID=638894153788
|
||||
FIREBASE_APP_ID=1:638894153788:web:35b295b15297201bd2e339
|
||||
FIREBASE_MEASUREMENT_ID=G-2MPJGFEEXC
|
||||
FIREBASE_VAPID_KEY=BFayEBpwMTU9GQQpXgitIJkfx-SD8-ltrFb3wLTZWgA27MfBhG4948pe0eERl432NzPrMKsbkXnA7ap_vLPgLYk
|
11
.flowconfig
11
.flowconfig
|
@ -7,13 +7,6 @@
|
|||
[include]
|
||||
|
||||
[libs]
|
||||
./flow-typed
|
||||
node_modules/lbry-redux/flow-typed/
|
||||
node_modules/lbryinc/flow-typed/
|
||||
|
||||
[untyped]
|
||||
.*/node_modules/lbry-redux
|
||||
.*/node_modules/lbryinc
|
||||
|
||||
[lints]
|
||||
|
||||
|
@ -31,7 +24,7 @@ module.name_mapper='^modal\(.*\)$' -> '<PROJECT_ROOT>/ui/modal\1'
|
|||
module.name_mapper='^app\(.*\)$' -> '<PROJECT_ROOT>/ui/app\1'
|
||||
module.name_mapper='^native\(.*\)$' -> '<PROJECT_ROOT>/ui/native\1'
|
||||
module.name_mapper='^analytics\(.*\)$' -> '<PROJECT_ROOT>/ui/analytics\1'
|
||||
module.name_mapper='^recsys\(.*\)$' -> '<PROJECT_ROOT>/ui/recsys\1'
|
||||
module.name_mapper='^recsys\(.*\)$' -> '<PROJECT_ROOT>/extras/recsys\1'
|
||||
module.name_mapper='^rewards\(.*\)$' -> '<PROJECT_ROOT>/ui/rewards\1'
|
||||
module.name_mapper='^i18n\(.*\)$' -> '<PROJECT_ROOT>/ui/i18n\1'
|
||||
module.name_mapper='^effects\(.*\)$' -> '<PROJECT_ROOT>/ui/effects\1'
|
||||
|
@ -42,6 +35,8 @@ module.name_mapper='^web\/effects\(.*\)$' -> '<PROJECT_ROOT>/web/effects\1'
|
|||
module.name_mapper='^web\/page\(.*\)$' -> '<PROJECT_ROOT>/web/page\1'
|
||||
module.name_mapper='^homepage\(.*\)$' -> '<PROJECT_ROOT>/ui/util/homepage\1'
|
||||
module.name_mapper='^scss\/component\(.*\)$' -> '<PROJECT_ROOT>/ui/scss/component/\1'
|
||||
module.name_mapper='^\$web\(.*\)$' -> '<PROJECT_ROOT>/web\1'
|
||||
module.name_mapper='^\$ui\(.*\)$' -> '<PROJECT_ROOT>/ui\1'
|
||||
|
||||
esproposal.optional_chaining=enable
|
||||
|
||||
|
|
2
.github/workflows/node.js.yml
vendored
2
.github/workflows/node.js.yml
vendored
|
@ -41,8 +41,6 @@ jobs:
|
|||
yarn compile:web
|
||||
env:
|
||||
# UI
|
||||
MATOMO_URL: https://analytics.lbry.com/
|
||||
MATOMO_ID: 4
|
||||
WELCOME_VERSION: 1.0
|
||||
DOMAIN: odysee.com
|
||||
URL: https://odysee.com
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -36,3 +36,4 @@ package-lock.json
|
|||
.env.ody
|
||||
.env.desktop
|
||||
.env.lbrytv
|
||||
analyzeResults*.html
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
{
|
||||
"linters": {
|
||||
"ui/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
|
||||
"extras/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
|
||||
"web/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
|
||||
"ui/**/*.{js,jsx}": ["eslint", "flow focus-check --color always", "git add"],
|
||||
"extras/**/*.{js,jsx}": ["eslint", "flow focus-check --color always", "git add"],
|
||||
"web/**/*.{js,jsx}": ["eslint", "git add"]
|
||||
},
|
||||
"ignore": ["node_modules", "web/dist/**/*", "dist/**/*", "package-lock.json"]
|
||||
|
|
|
@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Stream Key is now hidden _community pr!_ ([#7127](https://github.com/lbryio/lbry-desktop/pull/7127))
|
||||
- Fix playlist preview thumbnail ([#7178](https://github.com/lbryio/lbry-desktop/pull/7178)
|
||||
- Fixed “Your Account” popup on mobile ([#7172](https://github.com/lbryio/lbry-desktop/pull/7172))
|
||||
- Fix disable-support for comments ([#7245](https://github.com/lbryio/lbry-desktop/pull/7245))
|
||||
|
||||
## [0.51.2] - [2021-08-20]
|
||||
|
||||
|
@ -734,7 +735,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
### Added
|
||||
|
||||
- Channels page above Publishes which lists all your channels ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925))
|
||||
- YouTube channel claiming and transfer ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925)). See our [YouTube FAQ](https://lbry.com/faq/youtube) for more information.
|
||||
- YouTube channel claiming and transfer ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925)). See our [YouTube FAQ](https://odysee.com/@OdyseeHelp:b/youtube-sync:b) for more information.
|
||||
- New user sign in flow now includes automatic redeeming of 1 LBC and channel creation ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925))
|
||||
- Ability to save wallet encryption password ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925))
|
||||
- Sync your balance (only for users with new wallets) and preferences (subscriptions and tags) between devices ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925)). See our [FAQ for more information](https://lbry.com/faq/account-sync)
|
||||
|
@ -889,7 +890,7 @@ This release includes a breaking change that will reset many of your settings. T
|
|||
|
||||
### Added
|
||||
|
||||
- New app design for better [content discovery](https://lbry.com/faq/trending) with infinite scroll ([#2477](https://github.com/lbryio/lbry-desktop/pull/2477))
|
||||
- New app design for better [content discovery](https://odysee.com/@OdyseeHelp:b/OdyseeBasics:c) with infinite scroll ([#2477](https://github.com/lbryio/lbry-desktop/pull/2477))
|
||||
- First implementation of comments ([#2510](https://github.com/lbryio/lbry-desktop/pull/2510))
|
||||
- Ability to edit channels with new metadata and tags ([#2584](https://github.com/lbryio/lbry-desktop/pull/2584))
|
||||
- Tagging content on publish page ([#2593](https://github.com/lbryio/lbry-desktop/pull/2593))
|
||||
|
|
14
config.js
14
config.js
|
@ -3,8 +3,6 @@
|
|||
require('dotenv-defaults').config({ silent: false });
|
||||
|
||||
const config = {
|
||||
MATOMO_URL: process.env.MATOMO_URL,
|
||||
MATOMO_ID: process.env.MATOMO_ID,
|
||||
WEBPACK_WEB_PORT: process.env.WEBPACK_WEB_PORT,
|
||||
WEBPACK_ELECTRON_PORT: process.env.WEBPACK_ELECTRON_PORT,
|
||||
WEB_SERVER_PORT: process.env.WEB_SERVER_PORT,
|
||||
|
@ -22,6 +20,7 @@ const config = {
|
|||
SHARE_DOMAIN_URL: process.env.SHARE_DOMAIN_URL,
|
||||
URL: process.env.URL,
|
||||
THUMBNAIL_CDN_URL: process.env.THUMBNAIL_CDN_URL,
|
||||
THUMBNAIL_CARDS_CDN_URL: process.env.THUMBNAIL_CARDS_CDN_URL,
|
||||
THUMBNAIL_HEIGHT: process.env.THUMBNAIL_HEIGHT,
|
||||
THUMBNAIL_WIDTH: process.env.THUMBNAIL_WIDTH,
|
||||
THUMBNAIL_QUALITY: process.env.THUMBNAIL_QUALITY,
|
||||
|
@ -76,6 +75,17 @@ const config = {
|
|||
SHOW_TAGS_INTRO: process.env.SHOW_TAGS_INTRO === 'true',
|
||||
LIGHTHOUSE_DEFAULT_TYPES: process.env.LIGHTHOUSE_DEFAULT_TYPES,
|
||||
BRANDED_SITE: process.env.BRANDED_SITE,
|
||||
|
||||
// FIREBASE SDK
|
||||
FIREBASE_API_KEY: process.env.FIREBASE_API_KEY,
|
||||
FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN,
|
||||
FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID,
|
||||
FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET,
|
||||
FIREBASE_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID,
|
||||
FIREBASE_APP_ID: process.env.FIREBASE_APP_ID,
|
||||
FIREBASE_MEASUREMENT_ID: process.env.FIREBASE_MEASUREMENT_ID,
|
||||
FIREBASE_VAPID_KEY: process.env.FIREBASE_VAPID_KEY,
|
||||
|
||||
};
|
||||
|
||||
config.URL_DEV = `http://localhost:${config.WEBPACK_WEB_PORT}`;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { spawn, execSync } from 'child_process';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import Lbry from 'lbry';
|
||||
|
||||
export default class Daemon {
|
||||
static lbrynetPath =
|
||||
|
|
|
@ -6,7 +6,7 @@ import SemVer from 'semver';
|
|||
import https from 'https';
|
||||
import { app, dialog, ipcMain, session, shell } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import Lbry from 'lbry';
|
||||
import LbryFirstInstance from './LbryFirstInstance';
|
||||
import Daemon from './Daemon';
|
||||
import isDev from 'electron-is-dev';
|
||||
|
|
|
@ -8,7 +8,7 @@ if (typeof global.fetch === 'object') {
|
|||
global.fetch = global.fetch.default;
|
||||
}
|
||||
|
||||
const { Lbry } = require('lbry-redux');
|
||||
const Lbry = require('lbry');
|
||||
|
||||
delete global.window;
|
||||
|
||||
|
|
184
extras/lbry-first/lbry-first.js
Normal file
184
extras/lbry-first/lbry-first.js
Normal file
|
@ -0,0 +1,184 @@
|
|||
// @flow
|
||||
/*
|
||||
LBRY FIRST does not work due to api changes
|
||||
*/
|
||||
import 'proxy-polyfill';
|
||||
|
||||
const CHECK_LBRYFIRST_STARTED_TRY_NUMBER = 200;
|
||||
//
|
||||
// Basic LBRYFIRST connection config
|
||||
// Offers a proxy to call LBRYFIRST methods
|
||||
|
||||
//
|
||||
const LbryFirst: LbryFirstTypes = {
|
||||
isConnected: false,
|
||||
connectPromise: null,
|
||||
lbryFirstConnectionString: 'http://localhost:1337/rpc',
|
||||
apiRequestHeaders: { 'Content-Type': 'application/json' },
|
||||
|
||||
// Allow overriding lbryFirst connection string (e.g. to `/api/proxy` for lbryweb)
|
||||
setLbryFirstConnectionString: (value: string) => {
|
||||
LbryFirst.lbryFirstConnectionString = value;
|
||||
},
|
||||
|
||||
setApiHeader: (key: string, value: string) => {
|
||||
LbryFirst.apiRequestHeaders = Object.assign(LbryFirst.apiRequestHeaders, { [key]: value });
|
||||
},
|
||||
|
||||
unsetApiHeader: key => {
|
||||
Object.keys(LbryFirst.apiRequestHeaders).includes(key) &&
|
||||
delete LbryFirst.apiRequestHeaders['key'];
|
||||
},
|
||||
// Allow overriding Lbry methods
|
||||
overrides: {},
|
||||
setOverride: (methodName, newMethod) => {
|
||||
LbryFirst.overrides[methodName] = newMethod;
|
||||
},
|
||||
getApiRequestHeaders: () => LbryFirst.apiRequestHeaders,
|
||||
|
||||
// LbryFirst Methods
|
||||
status: (params = {}) => lbryFirstCallWithResult('status', params),
|
||||
stop: () => lbryFirstCallWithResult('stop', {}),
|
||||
version: () => lbryFirstCallWithResult('version', {}),
|
||||
|
||||
// Upload to youtube
|
||||
upload: (params: { title: string, description: string, file_path: ?string } = {}) => {
|
||||
// Only upload when originally publishing for now
|
||||
if (!params.file_path) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const uploadParams: {
|
||||
Title: string,
|
||||
Description: string,
|
||||
FilePath: string,
|
||||
Category: string,
|
||||
Keywords: string,
|
||||
} = {
|
||||
Title: params.title,
|
||||
Description: params.description,
|
||||
FilePath: params.file_path,
|
||||
Category: '',
|
||||
Keywords: '',
|
||||
};
|
||||
|
||||
return lbryFirstCallWithResult('youtube.Upload', uploadParams);
|
||||
},
|
||||
|
||||
hasYTAuth: (token: string) => {
|
||||
const hasYTAuthParams = {};
|
||||
hasYTAuthParams.AuthToken = token;
|
||||
return lbryFirstCallWithResult('youtube.HasAuth', hasYTAuthParams);
|
||||
},
|
||||
|
||||
ytSignup: () => {
|
||||
const emptyParams = {};
|
||||
return lbryFirstCallWithResult('youtube.Signup', emptyParams);
|
||||
},
|
||||
|
||||
remove: () => {
|
||||
const emptyParams = {};
|
||||
return lbryFirstCallWithResult('youtube.Remove', emptyParams);
|
||||
},
|
||||
|
||||
// Connect to lbry-first
|
||||
connect: () => {
|
||||
if (LbryFirst.connectPromise === null) {
|
||||
LbryFirst.connectPromise = new Promise((resolve, reject) => {
|
||||
let tryNum = 0;
|
||||
// Check every half second to see if the lbryFirst is accepting connections
|
||||
function checkLbryFirstStarted() {
|
||||
tryNum += 1;
|
||||
LbryFirst.status()
|
||||
.then(resolve)
|
||||
.catch(() => {
|
||||
if (tryNum <= CHECK_LBRYFIRST_STARTED_TRY_NUMBER) {
|
||||
setTimeout(checkLbryFirstStarted, tryNum < 50 ? 400 : 1000);
|
||||
} else {
|
||||
reject(new Error('Unable to connect to LBRY'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkLbryFirstStarted();
|
||||
});
|
||||
}
|
||||
|
||||
// Flow thinks this could be empty, but it will always return a promise
|
||||
// $FlowFixMe
|
||||
return LbryFirst.connectPromise;
|
||||
},
|
||||
};
|
||||
|
||||
function checkAndParse(response) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response.json();
|
||||
}
|
||||
return response.json().then(json => {
|
||||
let error;
|
||||
if (json.error) {
|
||||
const errorMessage = typeof json.error === 'object' ? json.error.message : json.error;
|
||||
error = new Error(errorMessage);
|
||||
} else {
|
||||
error = new Error('Protocol error with unknown response signature');
|
||||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
export function apiCall(method: string, params: ?{}, resolve: Function, reject: Function) {
|
||||
const counter = new Date().getTime();
|
||||
const paramsArray = [params];
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: LbryFirst.apiRequestHeaders,
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method,
|
||||
params: paramsArray,
|
||||
id: counter,
|
||||
}),
|
||||
};
|
||||
|
||||
return fetch(LbryFirst.lbryFirstConnectionString, options)
|
||||
.then(checkAndParse)
|
||||
.then(response => {
|
||||
const error = response.error || (response.result && response.result.error);
|
||||
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
return resolve(response.result);
|
||||
})
|
||||
.catch(reject);
|
||||
}
|
||||
|
||||
function lbryFirstCallWithResult(name: string, params: ?{} = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiCall(
|
||||
name,
|
||||
params,
|
||||
result => {
|
||||
resolve(result);
|
||||
},
|
||||
reject
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// This is only for a fallback
|
||||
// If there is a LbryFirst method that is being called by an app, it should be added to /flow-typed/LbryFirst.js
|
||||
const lbryFirstProxy = new Proxy(LbryFirst, {
|
||||
get(target: LbryFirstTypes, name: string) {
|
||||
if (name in target) {
|
||||
return target[name];
|
||||
}
|
||||
|
||||
return (params = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
apiCall(name, params, resolve, reject);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default lbryFirstProxy;
|
92
extras/lbryinc/constants/action_types.js
Normal file
92
extras/lbryinc/constants/action_types.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Claims
|
||||
export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED';
|
||||
export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED';
|
||||
export const FETCH_TRENDING_CONTENT_STARTED = 'FETCH_TRENDING_CONTENT_STARTED';
|
||||
export const FETCH_TRENDING_CONTENT_COMPLETED = 'FETCH_TRENDING_CONTENT_COMPLETED';
|
||||
export const RESOLVE_URIS_STARTED = 'RESOLVE_URIS_STARTED';
|
||||
export const RESOLVE_URIS_COMPLETED = 'RESOLVE_URIS_COMPLETED';
|
||||
export const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED';
|
||||
export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED';
|
||||
export const FETCH_CHANNEL_CLAIM_COUNT_STARTED = 'FETCH_CHANNEL_CLAIM_COUNT_STARTED';
|
||||
export const FETCH_CHANNEL_CLAIM_COUNT_COMPLETED = 'FETCH_CHANNEL_CLAIM_COUNT_COMPLETED';
|
||||
export const FETCH_CLAIM_LIST_MINE_STARTED = 'FETCH_CLAIM_LIST_MINE_STARTED';
|
||||
export const FETCH_CLAIM_LIST_MINE_COMPLETED = 'FETCH_CLAIM_LIST_MINE_COMPLETED';
|
||||
export const ABANDON_CLAIM_STARTED = 'ABANDON_CLAIM_STARTED';
|
||||
export const ABANDON_CLAIM_SUCCEEDED = 'ABANDON_CLAIM_SUCCEEDED';
|
||||
export const FETCH_CHANNEL_LIST_STARTED = 'FETCH_CHANNEL_LIST_STARTED';
|
||||
export const FETCH_CHANNEL_LIST_COMPLETED = 'FETCH_CHANNEL_LIST_COMPLETED';
|
||||
export const CREATE_CHANNEL_STARTED = 'CREATE_CHANNEL_STARTED';
|
||||
export const CREATE_CHANNEL_COMPLETED = 'CREATE_CHANNEL_COMPLETED';
|
||||
export const SET_PLAYING_URI = 'SET_PLAYING_URI';
|
||||
export const SET_CONTENT_POSITION = 'SET_CONTENT_POSITION';
|
||||
export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED';
|
||||
export const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI';
|
||||
export const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL';
|
||||
|
||||
// Subscriptions
|
||||
export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
|
||||
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
|
||||
export const CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS = 'CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS';
|
||||
export const CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS = 'CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS';
|
||||
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
|
||||
export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST';
|
||||
export const UPDATE_SUBSCRIPTION_UNREADS = 'UPDATE_SUBSCRIPTION_UNREADS';
|
||||
export const REMOVE_SUBSCRIPTION_UNREADS = 'REMOVE_SUBSCRIPTION_UNREADS';
|
||||
export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
|
||||
export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
|
||||
export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
|
||||
export const FETCH_SUBSCRIPTIONS_START = 'FETCH_SUBSCRIPTIONS_START';
|
||||
export const FETCH_SUBSCRIPTIONS_FAIL = 'FETCH_SUBSCRIPTIONS_FAIL';
|
||||
export const FETCH_SUBSCRIPTIONS_SUCCESS = 'FETCH_SUBSCRIPTIONS_SUCCESS';
|
||||
export const SET_VIEW_MODE = 'SET_VIEW_MODE';
|
||||
export const GET_SUGGESTED_SUBSCRIPTIONS_START = 'GET_SUGGESTED_SUBSCRIPTIONS_START';
|
||||
export const GET_SUGGESTED_SUBSCRIPTIONS_SUCCESS = 'GET_SUGGESTED_SUBSCRIPTIONS_SUCCESS';
|
||||
export const GET_SUGGESTED_SUBSCRIPTIONS_FAIL = 'GET_SUGGESTED_SUBSCRIPTIONS_FAIL';
|
||||
export const SUBSCRIPTION_FIRST_RUN_COMPLETED = 'SUBSCRIPTION_FIRST_RUN_COMPLETED';
|
||||
export const VIEW_SUGGESTED_SUBSCRIPTIONS = 'VIEW_SUGGESTED_SUBSCRIPTIONS';
|
||||
|
||||
// Blacklist
|
||||
export const FETCH_BLACK_LISTED_CONTENT_STARTED = 'FETCH_BLACK_LISTED_CONTENT_STARTED';
|
||||
export const FETCH_BLACK_LISTED_CONTENT_COMPLETED = 'FETCH_BLACK_LISTED_CONTENT_COMPLETED';
|
||||
export const FETCH_BLACK_LISTED_CONTENT_FAILED = 'FETCH_BLACK_LISTED_CONTENT_FAILED';
|
||||
export const BLACK_LISTED_CONTENT_SUBSCRIBE = 'BLACK_LISTED_CONTENT_SUBSCRIBE';
|
||||
|
||||
// Filtered list
|
||||
export const FETCH_FILTERED_CONTENT_STARTED = 'FETCH_FILTERED_CONTENT_STARTED';
|
||||
export const FETCH_FILTERED_CONTENT_COMPLETED = 'FETCH_FILTERED_CONTENT_COMPLETED';
|
||||
export const FETCH_FILTERED_CONTENT_FAILED = 'FETCH_FILTERED_CONTENT_FAILED';
|
||||
export const FILTERED_CONTENT_SUBSCRIBE = 'FILTERED_CONTENT_SUBSCRIBE';
|
||||
|
||||
// Cost Info
|
||||
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED';
|
||||
export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED';
|
||||
|
||||
// Stats
|
||||
export const FETCH_VIEW_COUNT_STARTED = 'FETCH_VIEW_COUNT_STARTED';
|
||||
export const FETCH_VIEW_COUNT_FAILED = 'FETCH_VIEW_COUNT_FAILED';
|
||||
export const FETCH_VIEW_COUNT_COMPLETED = 'FETCH_VIEW_COUNT_COMPLETED';
|
||||
export const FETCH_SUB_COUNT_STARTED = 'FETCH_SUB_COUNT_STARTED';
|
||||
export const FETCH_SUB_COUNT_FAILED = 'FETCH_SUB_COUNT_FAILED';
|
||||
export const FETCH_SUB_COUNT_COMPLETED = 'FETCH_SUB_COUNT_COMPLETED';
|
||||
|
||||
// Cross-device Sync
|
||||
export const GET_SYNC_STARTED = 'GET_SYNC_STARTED';
|
||||
export const GET_SYNC_COMPLETED = 'GET_SYNC_COMPLETED';
|
||||
export const GET_SYNC_FAILED = 'GET_SYNC_FAILED';
|
||||
export const SET_SYNC_STARTED = 'SET_SYNC_STARTED';
|
||||
export const SET_SYNC_FAILED = 'SET_SYNC_FAILED';
|
||||
export const SET_SYNC_COMPLETED = 'SET_SYNC_COMPLETED';
|
||||
export const SET_DEFAULT_ACCOUNT = 'SET_DEFAULT_ACCOUNT';
|
||||
export const SYNC_APPLY_STARTED = 'SYNC_APPLY_STARTED';
|
||||
export const SYNC_APPLY_COMPLETED = 'SYNC_APPLY_COMPLETED';
|
||||
export const SYNC_APPLY_FAILED = 'SYNC_APPLY_FAILED';
|
||||
export const SYNC_APPLY_BAD_PASSWORD = 'SYNC_APPLY_BAD_PASSWORD';
|
||||
export const SYNC_RESET = 'SYNC_RESET';
|
||||
|
||||
// Lbry.tv
|
||||
export const UPDATE_UPLOAD_PROGRESS = 'UPDATE_UPLOAD_PROGRESS';
|
||||
|
||||
// User
|
||||
export const GENERATE_AUTH_TOKEN_FAILURE = 'GENERATE_AUTH_TOKEN_FAILURE';
|
||||
export const GENERATE_AUTH_TOKEN_STARTED = 'GENERATE_AUTH_TOKEN_STARTED';
|
||||
export const GENERATE_AUTH_TOKEN_SUCCESS = 'GENERATE_AUTH_TOKEN_SUCCESS';
|
5
extras/lbryinc/constants/claim.js
Normal file
5
extras/lbryinc/constants/claim.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export const MINIMUM_PUBLISH_BID = 0.00000001;
|
||||
|
||||
export const CHANNEL_ANONYMOUS = 'anonymous';
|
||||
export const CHANNEL_NEW = 'new';
|
||||
export const PAGE_SIZE = 20;
|
4
extras/lbryinc/constants/errors.js
Normal file
4
extras/lbryinc/constants/errors.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const ALREADY_CLAIMED =
|
||||
'once the invite reward has been claimed the referrer cannot be changed';
|
||||
export const REFERRER_NOT_FOUND =
|
||||
'A lbry.tv account could not be found for the referrer you provided.';
|
11
extras/lbryinc/constants/youtube.js
Normal file
11
extras/lbryinc/constants/youtube.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export const YOUTUBE_SYNC_NOT_TRANSFERRED = 'not_transferred';
|
||||
export const YOUTUBE_SYNC_PENDING = 'pending';
|
||||
export const YOUTUBE_SYNC_PENDING_EMAIL = 'pendingemail';
|
||||
export const YOUTUBE_SYNC_PENDING_TRANSFER = 'pending_transfer';
|
||||
export const YOUTUBE_SYNC_COMPLETED_TRANSFER = 'completed_transfer';
|
||||
export const YOUTUBE_SYNC_QUEUED = 'queued';
|
||||
export const YOUTUBE_SYNC_SYNCING = 'syncing';
|
||||
export const YOUTUBE_SYNC_SYNCED = 'synced';
|
||||
export const YOUTUBE_SYNC_FAILED = 'failed';
|
||||
export const YOUTUBE_SYNC_PENDINGUPGRADE = 'pendingupgrade';
|
||||
export const YOUTUBE_SYNC_ABANDONDED = 'abandoned';
|
73
extras/lbryinc/index.js
Normal file
73
extras/lbryinc/index.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
import * as LBRYINC_ACTIONS from 'constants/action_types';
|
||||
import * as YOUTUBE_STATUSES from 'constants/youtube';
|
||||
import * as ERRORS from 'constants/errors';
|
||||
import Lbryio from './lbryio';
|
||||
|
||||
export { Lbryio };
|
||||
|
||||
// constants
|
||||
export { LBRYINC_ACTIONS, YOUTUBE_STATUSES, ERRORS };
|
||||
|
||||
// utils
|
||||
export { doTransifexUpload } from 'util/transifex-upload';
|
||||
|
||||
// actions
|
||||
export { doGenerateAuthToken } from './redux/actions/auth';
|
||||
export { doFetchCostInfoForUri } from './redux/actions/cost_info';
|
||||
export { doBlackListedOutpointsSubscribe } from './redux/actions/blacklist';
|
||||
export { doFilteredOutpointsSubscribe } from './redux/actions/filtered';
|
||||
// export { doFetchFeaturedUris, doFetchTrendingUris } from './redux/actions/homepage';
|
||||
export { doFetchViewCount, doFetchSubCount } from './redux/actions/stats';
|
||||
export {
|
||||
doCheckSync,
|
||||
doGetSync,
|
||||
doSetSync,
|
||||
doSetDefaultAccount,
|
||||
doSyncApply,
|
||||
doResetSync,
|
||||
doSyncEncryptAndDecrypt,
|
||||
} from 'redux/actions/sync';
|
||||
export { doUpdateUploadProgress } from './redux/actions/web';
|
||||
|
||||
// reducers
|
||||
export { authReducer } from './redux/reducers/auth';
|
||||
export { costInfoReducer } from './redux/reducers/cost_info';
|
||||
export { blacklistReducer } from './redux/reducers/blacklist';
|
||||
export { filteredReducer } from './redux/reducers/filtered';
|
||||
// export { homepageReducer } from './redux/reducers/homepage';
|
||||
export { statsReducer } from './redux/reducers/stats';
|
||||
export { syncReducer } from './redux/reducers/sync';
|
||||
export { webReducer } from './redux/reducers/web';
|
||||
|
||||
// selectors
|
||||
export { selectAuthToken, selectIsAuthenticating } from './redux/selectors/auth';
|
||||
export {
|
||||
makeSelectFetchingCostInfoForUri,
|
||||
makeSelectCostInfoForUri,
|
||||
selectAllCostInfoByUri,
|
||||
selectFetchingCostInfo,
|
||||
} from './redux/selectors/cost_info';
|
||||
export { selectBlackListedOutpoints, selectBlacklistedOutpointMap } from './redux/selectors/blacklist';
|
||||
export { selectFilteredOutpoints, selectFilteredOutpointMap } from './redux/selectors/filtered';
|
||||
// export {
|
||||
// selectFeaturedUris,
|
||||
// selectFetchingFeaturedUris,
|
||||
// selectTrendingUris,
|
||||
// selectFetchingTrendingUris,
|
||||
// } from './redux/selectors/homepage';
|
||||
export { selectViewCount, makeSelectViewCountForUri, makeSelectSubCountForUri } from './redux/selectors/stats';
|
||||
export { selectBanStateForUri } from './redux/selectors/ban';
|
||||
export {
|
||||
selectHasSyncedWallet,
|
||||
selectSyncData,
|
||||
selectSyncHash,
|
||||
selectSetSyncErrorMessage,
|
||||
selectGetSyncErrorMessage,
|
||||
selectGetSyncIsPending,
|
||||
selectSetSyncIsPending,
|
||||
selectSyncApplyIsPending,
|
||||
selectHashChanged,
|
||||
selectSyncApplyErrorMessage,
|
||||
selectSyncApplyPasswordError,
|
||||
} from './redux/selectors/sync';
|
||||
export { selectCurrentUploads, selectUploadCount } from './redux/selectors/web';
|
259
extras/lbryinc/lbryio.js
Normal file
259
extras/lbryinc/lbryio.js
Normal file
|
@ -0,0 +1,259 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import Lbry from 'lbry';
|
||||
import querystring from 'querystring';
|
||||
import analytics from 'analytics';
|
||||
|
||||
const Lbryio = {
|
||||
enabled: true,
|
||||
authenticationPromise: null,
|
||||
exchangePromise: null,
|
||||
exchangeLastFetched: null,
|
||||
CONNECTION_STRING: 'https://api.lbry.com/',
|
||||
};
|
||||
|
||||
const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
|
||||
const INTERNAL_APIS_DOWN = 'internal_apis_down';
|
||||
|
||||
// We can't use env's because they aren't passed into node_modules
|
||||
Lbryio.setLocalApi = (endpoint) => {
|
||||
Lbryio.CONNECTION_STRING = endpoint.replace(/\/*$/, '/'); // exactly one slash at the end;
|
||||
};
|
||||
|
||||
Lbryio.call = (resource, action, params = {}, method = 'get') => {
|
||||
if (!Lbryio.enabled) {
|
||||
return Promise.reject(new Error(__('LBRY internal API is disabled')));
|
||||
}
|
||||
|
||||
if (!(method === 'get' || method === 'post')) {
|
||||
return Promise.reject(new Error(__('Invalid method')));
|
||||
}
|
||||
|
||||
function checkAndParse(response) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (response.status === 500) {
|
||||
return Promise.reject(INTERNAL_APIS_DOWN);
|
||||
}
|
||||
|
||||
if (response) {
|
||||
return response.json().then((json) => {
|
||||
let error;
|
||||
if (json.error) {
|
||||
error = new Error(json.error);
|
||||
} else {
|
||||
error = new Error('Unknown API error signature');
|
||||
}
|
||||
error.response = response; // This is primarily a hack used in actions/user.js
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function makeRequest(url, options) {
|
||||
return fetch(url, options).then(checkAndParse);
|
||||
}
|
||||
|
||||
return Lbryio.getAuthToken().then((token) => {
|
||||
const fullParams = { auth_token: token, ...params };
|
||||
Object.keys(fullParams).forEach((key) => {
|
||||
const value = fullParams[key];
|
||||
if (typeof value === 'object') {
|
||||
fullParams[key] = JSON.stringify(value);
|
||||
}
|
||||
});
|
||||
|
||||
const qs = querystring.stringify(fullParams);
|
||||
let url = `${Lbryio.CONNECTION_STRING}${resource}/${action}?${qs}`;
|
||||
|
||||
let options = {
|
||||
method: 'GET',
|
||||
};
|
||||
|
||||
if (method === 'post') {
|
||||
options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: qs,
|
||||
};
|
||||
url = `${Lbryio.CONNECTION_STRING}${resource}/${action}`;
|
||||
}
|
||||
|
||||
return makeRequest(url, options).then((response) => {
|
||||
sendCallAnalytics(resource, action, params);
|
||||
return response.data;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Lbryio.authToken = null;
|
||||
|
||||
Lbryio.getAuthToken = () =>
|
||||
new Promise((resolve) => {
|
||||
if (Lbryio.authToken) {
|
||||
resolve(Lbryio.authToken);
|
||||
} else if (Lbryio.overrides.getAuthToken) {
|
||||
Lbryio.overrides.getAuthToken().then((token) => {
|
||||
resolve(token);
|
||||
});
|
||||
} else if (typeof window !== 'undefined') {
|
||||
const { store } = window;
|
||||
if (store) {
|
||||
const state = store.getState();
|
||||
const token = state.auth ? state.auth.authToken : null;
|
||||
Lbryio.authToken = token;
|
||||
resolve(token);
|
||||
}
|
||||
|
||||
resolve(null);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
|
||||
Lbryio.getCurrentUser = () => Lbryio.call('user', 'me');
|
||||
|
||||
Lbryio.authenticate = (domain, language) => {
|
||||
if (!Lbryio.enabled) {
|
||||
const params = {
|
||||
id: 1,
|
||||
primary_email: 'disabled@lbry.io',
|
||||
has_verified_email: true,
|
||||
is_identity_verified: true,
|
||||
is_reward_approved: false,
|
||||
language: language || 'en',
|
||||
};
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolve(params);
|
||||
});
|
||||
}
|
||||
|
||||
if (Lbryio.authenticationPromise === null) {
|
||||
Lbryio.authenticationPromise = new Promise((resolve, reject) => {
|
||||
Lbryio.getAuthToken()
|
||||
.then((token) => {
|
||||
if (!token || token.length > 60) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check that token works
|
||||
return Lbryio.getCurrentUser()
|
||||
.then((user) => user)
|
||||
.catch((error) => {
|
||||
if (error === INTERNAL_APIS_DOWN) {
|
||||
throw new Error('Internal APIS down');
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
})
|
||||
.then((user) => {
|
||||
if (user) {
|
||||
return user;
|
||||
}
|
||||
|
||||
return Lbry.status()
|
||||
.then(
|
||||
(status) =>
|
||||
new Promise((res, rej) => {
|
||||
const appId =
|
||||
domain && domain !== 'lbry.tv'
|
||||
? (domain.replace(/[.]/gi, '') + status.installation_id).slice(0, 66)
|
||||
: status.installation_id;
|
||||
Lbryio.call(
|
||||
'user',
|
||||
'new',
|
||||
{
|
||||
auth_token: '',
|
||||
language: language || 'en',
|
||||
app_id: appId,
|
||||
},
|
||||
'post'
|
||||
)
|
||||
.then((response) => {
|
||||
if (!response.auth_token) {
|
||||
throw new Error('auth_token was not set in the response');
|
||||
}
|
||||
|
||||
const { store } = window;
|
||||
if (Lbryio.overrides.setAuthToken) {
|
||||
Lbryio.overrides.setAuthToken(response.auth_token);
|
||||
}
|
||||
|
||||
if (store) {
|
||||
store.dispatch({
|
||||
type: ACTIONS.GENERATE_AUTH_TOKEN_SUCCESS,
|
||||
data: { authToken: response.auth_token },
|
||||
});
|
||||
}
|
||||
Lbryio.authToken = response.auth_token;
|
||||
return res(response);
|
||||
})
|
||||
.catch((error) => rej(error));
|
||||
})
|
||||
)
|
||||
.then((newUser) => {
|
||||
if (!newUser) {
|
||||
return Lbryio.getCurrentUser();
|
||||
}
|
||||
return newUser;
|
||||
});
|
||||
})
|
||||
.then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
return Lbryio.authenticationPromise;
|
||||
};
|
||||
|
||||
Lbryio.getStripeToken = () =>
|
||||
Lbryio.CONNECTION_STRING.startsWith('http://localhost:')
|
||||
? 'pk_test_NoL1JWL7i1ipfhVId5KfDZgo'
|
||||
: 'pk_live_e8M4dRNnCCbmpZzduEUZBgJO';
|
||||
|
||||
Lbryio.getExchangeRates = () => {
|
||||
if (!Lbryio.exchangeLastFetched || Date.now() - Lbryio.exchangeLastFetched > EXCHANGE_RATE_TIMEOUT) {
|
||||
Lbryio.exchangePromise = new Promise((resolve, reject) => {
|
||||
Lbryio.call('lbc', 'exchange_rate', {}, 'get', true)
|
||||
.then(({ lbc_usd: LBC_USD, lbc_btc: LBC_BTC, btc_usd: BTC_USD }) => {
|
||||
const rates = { LBC_USD, LBC_BTC, BTC_USD };
|
||||
resolve(rates);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
Lbryio.exchangeLastFetched = Date.now();
|
||||
}
|
||||
return Lbryio.exchangePromise;
|
||||
};
|
||||
|
||||
// Allow overriding lbryio methods
|
||||
// The desktop app will need to use it for getAuthToken because we use electron's ipcRenderer
|
||||
Lbryio.overrides = {};
|
||||
Lbryio.setOverride = (methodName, newMethod) => {
|
||||
Lbryio.overrides[methodName] = newMethod;
|
||||
};
|
||||
|
||||
function sendCallAnalytics(resource, action, params) {
|
||||
switch (resource) {
|
||||
case 'customer':
|
||||
if (action === 'tip') {
|
||||
analytics.reportEvent('spend_virtual_currency', {
|
||||
// https://developers.google.com/analytics/devguides/collection/ga4/reference/events#spend_virtual_currency
|
||||
value: params.amount,
|
||||
virtual_currency_name: params.currency.toLowerCase(),
|
||||
item_name: 'tip',
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export default Lbryio;
|
38
extras/lbryinc/redux/actions/auth.js
Normal file
38
extras/lbryinc/redux/actions/auth.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
|
||||
export function doGenerateAuthToken(installationId) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.GENERATE_AUTH_TOKEN_STARTED,
|
||||
});
|
||||
|
||||
Lbryio.call(
|
||||
'user',
|
||||
'new',
|
||||
{
|
||||
auth_token: '',
|
||||
language: 'en',
|
||||
app_id: installationId,
|
||||
},
|
||||
'post'
|
||||
)
|
||||
.then(response => {
|
||||
if (!response.auth_token) {
|
||||
dispatch({
|
||||
type: ACTIONS.GENERATE_AUTH_TOKEN_FAILURE,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: ACTIONS.GENERATE_AUTH_TOKEN_SUCCESS,
|
||||
data: { authToken: response.auth_token },
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch({
|
||||
type: ACTIONS.GENERATE_AUTH_TOKEN_FAILURE,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
52
extras/lbryinc/redux/actions/blacklist.js
Normal file
52
extras/lbryinc/redux/actions/blacklist.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { Lbryio } from 'lbryinc';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const CHECK_BLACK_LISTED_CONTENT_INTERVAL = 60 * 60 * 1000;
|
||||
|
||||
export function doFetchBlackListedOutpoints() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_STARTED,
|
||||
});
|
||||
|
||||
const success = ({ outpoints }) => {
|
||||
const splitOutpoints = [];
|
||||
if (outpoints) {
|
||||
outpoints.forEach((outpoint, index) => {
|
||||
const [txid, nout] = outpoint.split(':');
|
||||
|
||||
splitOutpoints[index] = { txid, nout: Number.parseInt(nout, 10) };
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
outpoints: splitOutpoints,
|
||||
success: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const failure = ({ message: error }) => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_FAILED,
|
||||
data: {
|
||||
error,
|
||||
success: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Lbryio.call('file', 'list_blocked', {
|
||||
auth_token: '',
|
||||
}).then(success, failure);
|
||||
};
|
||||
}
|
||||
|
||||
export function doBlackListedOutpointsSubscribe() {
|
||||
return dispatch => {
|
||||
dispatch(doFetchBlackListedOutpoints());
|
||||
setInterval(() => dispatch(doFetchBlackListedOutpoints()), CHECK_BLACK_LISTED_CONTENT_INTERVAL);
|
||||
};
|
||||
}
|
35
extras/lbryinc/redux/actions/cost_info.js
Normal file
35
extras/lbryinc/redux/actions/cost_info.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import { selectClaimsByUri } from 'redux/selectors/claims';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function doFetchCostInfoForUri(uri) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const claim = selectClaimsByUri(state)[uri];
|
||||
|
||||
if (!claim) return;
|
||||
|
||||
function resolve(costInfo) {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_COST_INFO_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
costInfo,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const fee = claim.value ? claim.value.fee : undefined;
|
||||
|
||||
if (fee === undefined) {
|
||||
resolve({ cost: 0, includesData: true });
|
||||
} else if (fee.currency === 'LBC') {
|
||||
resolve({ cost: fee.amount, includesData: true });
|
||||
} else {
|
||||
Lbryio.getExchangeRates().then(({ LBC_USD }) => {
|
||||
resolve({ cost: fee.amount / LBC_USD, includesData: true });
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
47
extras/lbryinc/redux/actions/filtered.js
Normal file
47
extras/lbryinc/redux/actions/filtered.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { Lbryio } from 'lbryinc';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const CHECK_FILTERED_CONTENT_INTERVAL = 60 * 60 * 1000;
|
||||
|
||||
export function doFetchFilteredOutpoints() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_FILTERED_CONTENT_STARTED,
|
||||
});
|
||||
|
||||
const success = ({ outpoints }) => {
|
||||
let formattedOutpoints = [];
|
||||
if (outpoints) {
|
||||
formattedOutpoints = outpoints.map(outpoint => {
|
||||
const [txid, nout] = outpoint.split(':');
|
||||
return { txid, nout: Number.parseInt(nout, 10) };
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_FILTERED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
outpoints: formattedOutpoints,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const failure = ({ error }) => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_FILTERED_CONTENT_FAILED,
|
||||
data: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Lbryio.call('file', 'list_filtered', { auth_token: '' }).then(success, failure);
|
||||
};
|
||||
}
|
||||
|
||||
export function doFilteredOutpointsSubscribe() {
|
||||
return dispatch => {
|
||||
dispatch(doFetchFilteredOutpoints());
|
||||
setInterval(() => dispatch(doFetchFilteredOutpoints()), CHECK_FILTERED_CONTENT_INTERVAL);
|
||||
};
|
||||
}
|
79
extras/lbryinc/redux/actions/homepage.js
Normal file
79
extras/lbryinc/redux/actions/homepage.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { Lbryio } from 'lbryinc';
|
||||
import { batchActions } from 'util/batch-actions';
|
||||
import { doResolveUris } from 'util/lbryURI';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
export function doFetchFeaturedUris(offloadResolve = false) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_FEATURED_CONTENT_STARTED,
|
||||
});
|
||||
|
||||
const success = ({ Uris }) => {
|
||||
let urisToResolve = [];
|
||||
Object.keys(Uris).forEach(category => {
|
||||
urisToResolve = [...urisToResolve, ...Uris[category]];
|
||||
});
|
||||
|
||||
const actions = [
|
||||
{
|
||||
type: ACTIONS.FETCH_FEATURED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
uris: Uris,
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
if (urisToResolve.length && !offloadResolve) {
|
||||
actions.push(doResolveUris(urisToResolve));
|
||||
}
|
||||
|
||||
dispatch(batchActions(...actions));
|
||||
};
|
||||
|
||||
const failure = () => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_FEATURED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
uris: {},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Lbryio.call('file', 'list_homepage').then(success, failure);
|
||||
};
|
||||
}
|
||||
|
||||
export function doFetchTrendingUris() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_TRENDING_CONTENT_STARTED,
|
||||
});
|
||||
|
||||
const success = data => {
|
||||
const urisToResolve = data.map(uri => uri.url);
|
||||
const actions = [
|
||||
doResolveUris(urisToResolve),
|
||||
{
|
||||
type: ACTIONS.FETCH_TRENDING_CONTENT_COMPLETED,
|
||||
data: {
|
||||
uris: data,
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
dispatch(batchActions(...actions));
|
||||
};
|
||||
|
||||
const failure = () => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_TRENDING_CONTENT_COMPLETED,
|
||||
data: {
|
||||
uris: [],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Lbryio.call('file', 'list_trending').then(success, failure);
|
||||
};
|
||||
}
|
68
extras/lbryinc/redux/actions/stats.js
Normal file
68
extras/lbryinc/redux/actions/stats.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
// @flow
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const FETCH_SUB_COUNT_MIN_INTERVAL_MS = 5 * 60 * 1000;
|
||||
const FETCH_SUB_COUNT_IDLE_FIRE_MS = 100;
|
||||
|
||||
export const doFetchViewCount = (claimIdCsv: string) => (dispatch: Dispatch) => {
|
||||
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_STARTED });
|
||||
|
||||
return Lbryio.call('file', 'view_count', { claim_id: claimIdCsv })
|
||||
.then((result: Array<number>) => {
|
||||
const viewCounts = result;
|
||||
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_COMPLETED, data: { claimIdCsv, viewCounts } });
|
||||
})
|
||||
.catch((error) => {
|
||||
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_FAILED, data: error });
|
||||
});
|
||||
};
|
||||
|
||||
const executeFetchSubCount = (claimIdCsv: string) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const subCountLastFetchedById = state.stats.subCountLastFetchedById;
|
||||
const now = Date.now();
|
||||
|
||||
const claimIds = claimIdCsv.split(',').filter((id) => {
|
||||
const prev = subCountLastFetchedById[id];
|
||||
return !prev || now - prev > FETCH_SUB_COUNT_MIN_INTERVAL_MS;
|
||||
});
|
||||
|
||||
if (claimIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ type: ACTIONS.FETCH_SUB_COUNT_STARTED });
|
||||
|
||||
return Lbryio.call('subscription', 'sub_count', { claim_id: claimIds.join(',') })
|
||||
.then((result: Array<number>) => {
|
||||
const subCounts = result;
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_SUB_COUNT_COMPLETED,
|
||||
data: { claimIdCsv, subCounts, fetchDate: now },
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
dispatch({ type: ACTIONS.FETCH_SUB_COUNT_FAILED, data: error });
|
||||
});
|
||||
};
|
||||
|
||||
let fetchSubCountTimer;
|
||||
let fetchSubCountQueue = '';
|
||||
|
||||
export const doFetchSubCount = (claimIdCsv: string) => (dispatch: Dispatch) => {
|
||||
if (fetchSubCountTimer) {
|
||||
clearTimeout(fetchSubCountTimer);
|
||||
}
|
||||
|
||||
if (fetchSubCountQueue && !fetchSubCountQueue.endsWith(',')) {
|
||||
fetchSubCountQueue += ',';
|
||||
}
|
||||
|
||||
fetchSubCountQueue += claimIdCsv;
|
||||
|
||||
fetchSubCountTimer = setTimeout(() => {
|
||||
dispatch(executeFetchSubCount(fetchSubCountQueue));
|
||||
fetchSubCountQueue = '';
|
||||
}, FETCH_SUB_COUNT_IDLE_FIRE_MS);
|
||||
};
|
289
extras/lbryinc/redux/actions/sync.js
Normal file
289
extras/lbryinc/redux/actions/sync.js
Normal file
|
@ -0,0 +1,289 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import Lbry from 'lbry';
|
||||
import { doWalletEncrypt, doWalletDecrypt } from 'redux/actions/wallet';
|
||||
|
||||
const NO_WALLET_ERROR = 'no wallet found for this user';
|
||||
|
||||
export function doSetDefaultAccount(success, failure) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_DEFAULT_ACCOUNT,
|
||||
});
|
||||
|
||||
Lbry.account_list()
|
||||
.then(accountList => {
|
||||
const { lbc_mainnet: accounts } = accountList;
|
||||
let defaultId;
|
||||
for (let i = 0; i < accounts.length; ++i) {
|
||||
if (accounts[i].satoshis > 0) {
|
||||
defaultId = accounts[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// In a case where there's no balance on either account
|
||||
// assume the second (which is created after sync) as default
|
||||
if (!defaultId && accounts.length > 1) {
|
||||
defaultId = accounts[1].id;
|
||||
}
|
||||
|
||||
// Set the default account
|
||||
if (defaultId) {
|
||||
Lbry.account_set({ account_id: defaultId, default: true })
|
||||
.then(() => {
|
||||
if (success) {
|
||||
success();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
if (failure) {
|
||||
failure(err);
|
||||
}
|
||||
});
|
||||
} else if (failure) {
|
||||
// no default account to set
|
||||
failure('Could not set a default account'); // fail
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
if (failure) {
|
||||
failure(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doSetSync(oldHash, newHash, data) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_SYNC_STARTED,
|
||||
});
|
||||
|
||||
return Lbryio.call('sync', 'set', { old_hash: oldHash, new_hash: newHash, data }, 'post')
|
||||
.then(response => {
|
||||
if (!response.hash) {
|
||||
throw Error('No hash returned for sync/set.');
|
||||
}
|
||||
|
||||
return dispatch({
|
||||
type: ACTIONS.SET_SYNC_COMPLETED,
|
||||
data: { syncHash: response.hash },
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_SYNC_FAILED,
|
||||
data: { error },
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doGetSync(passedPassword, callback) {
|
||||
const password = passedPassword === null || passedPassword === undefined ? '' : passedPassword;
|
||||
|
||||
function handleCallback(error, hasNewData) {
|
||||
if (callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new Error('Second argument passed to "doGetSync" must be a function');
|
||||
}
|
||||
|
||||
callback(error, hasNewData);
|
||||
}
|
||||
}
|
||||
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_STARTED,
|
||||
});
|
||||
|
||||
const data = {};
|
||||
|
||||
Lbry.wallet_status()
|
||||
.then(status => {
|
||||
if (status.is_locked) {
|
||||
return Lbry.wallet_unlock({ password });
|
||||
}
|
||||
|
||||
// Wallet is already unlocked
|
||||
return true;
|
||||
})
|
||||
.then(isUnlocked => {
|
||||
if (isUnlocked) {
|
||||
return Lbry.sync_hash();
|
||||
}
|
||||
data.unlockFailed = true;
|
||||
throw new Error();
|
||||
})
|
||||
.then(hash => Lbryio.call('sync', 'get', { hash }, 'post'))
|
||||
.then(response => {
|
||||
const syncHash = response.hash;
|
||||
data.syncHash = syncHash;
|
||||
data.syncData = response.data;
|
||||
data.changed = response.changed;
|
||||
data.hasSyncedWallet = true;
|
||||
|
||||
if (response.changed) {
|
||||
return Lbry.sync_apply({ password, data: response.data, blocking: true });
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (!response) {
|
||||
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
|
||||
handleCallback(null, data.changed);
|
||||
return;
|
||||
}
|
||||
|
||||
const { hash: walletHash, data: walletData } = response;
|
||||
|
||||
if (walletHash !== data.syncHash) {
|
||||
// different local hash, need to synchronise
|
||||
dispatch(doSetSync(data.syncHash, walletHash, walletData));
|
||||
}
|
||||
|
||||
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
|
||||
handleCallback(null, data.changed);
|
||||
})
|
||||
.catch(syncAttemptError => {
|
||||
if (data.unlockFailed) {
|
||||
dispatch({ type: ACTIONS.GET_SYNC_FAILED, data: { error: syncAttemptError } });
|
||||
|
||||
if (password !== '') {
|
||||
dispatch({ type: ACTIONS.SYNC_APPLY_BAD_PASSWORD });
|
||||
}
|
||||
|
||||
handleCallback(syncAttemptError);
|
||||
} else if (data.hasSyncedWallet) {
|
||||
const error =
|
||||
(syncAttemptError && syncAttemptError.message) || 'Error getting synced wallet';
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_FAILED,
|
||||
data: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
// Temp solution until we have a bad password error code
|
||||
// Don't fail on blank passwords so we don't show a "password error" message
|
||||
// before users have ever entered a password
|
||||
if (password !== '') {
|
||||
dispatch({ type: ACTIONS.SYNC_APPLY_BAD_PASSWORD });
|
||||
}
|
||||
|
||||
handleCallback(error);
|
||||
} else {
|
||||
// user doesn't have a synced wallet
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_COMPLETED,
|
||||
data: { hasSyncedWallet: false, syncHash: null },
|
||||
});
|
||||
|
||||
// call sync_apply to get data to sync
|
||||
// first time sync. use any string for old hash
|
||||
if (syncAttemptError.message === NO_WALLET_ERROR) {
|
||||
Lbry.sync_apply({ password })
|
||||
.then(({ hash: walletHash, data: syncApplyData }) => {
|
||||
dispatch(doSetSync('', walletHash, syncApplyData, password));
|
||||
handleCallback();
|
||||
})
|
||||
.catch(syncApplyError => {
|
||||
handleCallback(syncApplyError);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doSyncApply(syncHash, syncData, password) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.SYNC_APPLY_STARTED,
|
||||
});
|
||||
|
||||
Lbry.sync_apply({ password, data: syncData })
|
||||
.then(({ hash: walletHash, data: walletData }) => {
|
||||
dispatch({
|
||||
type: ACTIONS.SYNC_APPLY_COMPLETED,
|
||||
});
|
||||
|
||||
if (walletHash !== syncHash) {
|
||||
// different local hash, need to synchronise
|
||||
dispatch(doSetSync(syncHash, walletHash, walletData));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch({
|
||||
type: ACTIONS.SYNC_APPLY_FAILED,
|
||||
data: {
|
||||
error:
|
||||
'Invalid password specified. Please enter the password for your previously synchronised wallet.',
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doCheckSync() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_STARTED,
|
||||
});
|
||||
|
||||
Lbry.sync_hash().then(hash => {
|
||||
Lbryio.call('sync', 'get', { hash }, 'post')
|
||||
.then(response => {
|
||||
const data = {
|
||||
hasSyncedWallet: true,
|
||||
syncHash: response.hash,
|
||||
syncData: response.data,
|
||||
hashChanged: response.changed,
|
||||
};
|
||||
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
|
||||
})
|
||||
.catch(() => {
|
||||
// user doesn't have a synced wallet
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_COMPLETED,
|
||||
data: { hasSyncedWallet: false, syncHash: null },
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doResetSync() {
|
||||
return dispatch =>
|
||||
new Promise(resolve => {
|
||||
dispatch({ type: ACTIONS.SYNC_RESET });
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
export function doSyncEncryptAndDecrypt(oldPassword, newPassword, encrypt) {
|
||||
return dispatch => {
|
||||
const data = {};
|
||||
return Lbry.sync_hash()
|
||||
.then(hash => Lbryio.call('sync', 'get', { hash }, 'post'))
|
||||
.then(syncGetResponse => {
|
||||
data.oldHash = syncGetResponse.hash;
|
||||
|
||||
return Lbry.sync_apply({ password: oldPassword, data: syncGetResponse.data });
|
||||
})
|
||||
.then(() => {
|
||||
if (encrypt) {
|
||||
dispatch(doWalletEncrypt(newPassword));
|
||||
} else {
|
||||
dispatch(doWalletDecrypt());
|
||||
}
|
||||
})
|
||||
.then(() => Lbry.sync_apply({ password: newPassword }))
|
||||
.then(syncApplyResponse => {
|
||||
if (syncApplyResponse.hash !== data.oldHash) {
|
||||
return dispatch(doSetSync(data.oldHash, syncApplyResponse.hash, syncApplyResponse.data));
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
};
|
||||
}
|
12
extras/lbryinc/redux/actions/web.js
Normal file
12
extras/lbryinc/redux/actions/web.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
export const doUpdateUploadProgress = (
|
||||
progress: string,
|
||||
params: { [key: string]: any },
|
||||
xhr: any
|
||||
) => (dispatch: Dispatch) =>
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_UPLOAD_PROGRESS,
|
||||
data: { progress, params, xhr },
|
||||
});
|
29
extras/lbryinc/redux/reducers/auth.js
Normal file
29
extras/lbryinc/redux/reducers/auth.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const reducers = {};
|
||||
const defaultState = {
|
||||
authenticating: false,
|
||||
};
|
||||
|
||||
reducers[ACTIONS.GENERATE_AUTH_TOKEN_FAILURE] = state =>
|
||||
Object.assign({}, state, {
|
||||
authToken: null,
|
||||
authenticating: false,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.GENERATE_AUTH_TOKEN_STARTED] = state =>
|
||||
Object.assign({}, state, {
|
||||
authenticating: true,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.GENERATE_AUTH_TOKEN_SUCCESS] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
authToken: action.data.authToken,
|
||||
authenticating: false,
|
||||
});
|
||||
|
||||
export function authReducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
return state;
|
||||
}
|
37
extras/lbryinc/redux/reducers/blacklist.js
Normal file
37
extras/lbryinc/redux/reducers/blacklist.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
|
||||
const defaultState = {
|
||||
fetchingBlackListedOutpoints: false,
|
||||
fetchingBlackListedOutpointsSucceed: undefined,
|
||||
blackListedOutpoints: undefined,
|
||||
};
|
||||
|
||||
export const blacklistReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.FETCH_BLACK_LISTED_CONTENT_STARTED]: state => ({
|
||||
...state,
|
||||
fetchingBlackListedOutpoints: true,
|
||||
}),
|
||||
[ACTIONS.FETCH_BLACK_LISTED_CONTENT_COMPLETED]: (state, action) => {
|
||||
const { outpoints, success } = action.data;
|
||||
return {
|
||||
...state,
|
||||
fetchingBlackListedOutpoints: false,
|
||||
fetchingBlackListedOutpointsSucceed: success,
|
||||
blackListedOutpoints: outpoints,
|
||||
};
|
||||
},
|
||||
[ACTIONS.FETCH_BLACK_LISTED_CONTENT_FAILED]: (state, action) => {
|
||||
const { error, success } = action.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingBlackListedOutpoints: false,
|
||||
fetchingBlackListedOutpointsSucceed: success,
|
||||
fetchingBlackListedOutpointsError: error,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
38
extras/lbryinc/redux/reducers/cost_info.js
Normal file
38
extras/lbryinc/redux/reducers/cost_info.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { handleActions } from 'util/redux-utils';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const defaultState = {
|
||||
fetching: {},
|
||||
byUri: {},
|
||||
};
|
||||
|
||||
export const costInfoReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.FETCH_COST_INFO_STARTED]: (state, action) => {
|
||||
const { uri } = action.data;
|
||||
const newFetching = Object.assign({}, state.fetching);
|
||||
newFetching[uri] = true;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetching: newFetching,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.FETCH_COST_INFO_COMPLETED]: (state, action) => {
|
||||
const { uri, costInfo } = action.data;
|
||||
const newByUri = Object.assign({}, state.byUri);
|
||||
const newFetching = Object.assign({}, state.fetching);
|
||||
|
||||
newByUri[uri] = costInfo;
|
||||
delete newFetching[uri];
|
||||
|
||||
return {
|
||||
...state,
|
||||
byUri: newByUri,
|
||||
fetching: newFetching,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
34
extras/lbryinc/redux/reducers/filtered.js
Normal file
34
extras/lbryinc/redux/reducers/filtered.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
|
||||
const defaultState = {
|
||||
loading: false,
|
||||
filteredOutpoints: undefined,
|
||||
};
|
||||
|
||||
export const filteredReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.FETCH_FILTERED_CONTENT_STARTED]: state => ({
|
||||
...state,
|
||||
loading: true,
|
||||
}),
|
||||
[ACTIONS.FETCH_FILTERED_CONTENT_COMPLETED]: (state, action) => {
|
||||
const { outpoints } = action.data;
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
filteredOutpoints: outpoints,
|
||||
};
|
||||
},
|
||||
[ACTIONS.FETCH_FILTERED_CONTENT_FAILED]: (state, action) => {
|
||||
const { error } = action.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
fetchingFilteredOutpointsError: error,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
48
extras/lbryinc/redux/reducers/homepage.js
Normal file
48
extras/lbryinc/redux/reducers/homepage.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { handleActions } from 'util/redux-utils';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const defaultState = {
|
||||
fetchingFeaturedContent: false,
|
||||
fetchingFeaturedContentFailed: false,
|
||||
featuredUris: undefined,
|
||||
fetchingTrendingContent: false,
|
||||
fetchingTrendingContentFailed: false,
|
||||
trendingUris: undefined,
|
||||
};
|
||||
|
||||
export const homepageReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.FETCH_FEATURED_CONTENT_STARTED]: state => ({
|
||||
...state,
|
||||
fetchingFeaturedContent: true,
|
||||
}),
|
||||
|
||||
[ACTIONS.FETCH_FEATURED_CONTENT_COMPLETED]: (state, action) => {
|
||||
const { uris, success } = action.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingFeaturedContent: false,
|
||||
fetchingFeaturedContentFailed: !success,
|
||||
featuredUris: uris,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.FETCH_TRENDING_CONTENT_STARTED]: state => ({
|
||||
...state,
|
||||
fetchingTrendingContent: true,
|
||||
}),
|
||||
|
||||
[ACTIONS.FETCH_TRENDING_CONTENT_COMPLETED]: (state, action) => {
|
||||
const { uris, success } = action.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingTrendingContent: false,
|
||||
fetchingTrendingContentFailed: !success,
|
||||
trendingUris: uris,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
81
extras/lbryinc/redux/reducers/stats.js
Normal file
81
extras/lbryinc/redux/reducers/stats.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { handleActions } from 'util/redux-utils';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const defaultState = {
|
||||
fetchingViewCount: false,
|
||||
viewCountError: undefined,
|
||||
viewCountById: {},
|
||||
fetchingSubCount: false,
|
||||
subCountError: undefined,
|
||||
subCountById: {},
|
||||
subCountLastFetchedById: {},
|
||||
};
|
||||
|
||||
export const statsReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.FETCH_VIEW_COUNT_STARTED]: (state) => ({ ...state, fetchingViewCount: true }),
|
||||
|
||||
[ACTIONS.FETCH_VIEW_COUNT_FAILED]: (state, action) => ({
|
||||
...state,
|
||||
viewCountError: action.data,
|
||||
}),
|
||||
|
||||
[ACTIONS.FETCH_VIEW_COUNT_COMPLETED]: (state, action) => {
|
||||
const { claimIdCsv, viewCounts } = action.data;
|
||||
|
||||
const viewCountById = Object.assign({}, state.viewCountById);
|
||||
const claimIds = claimIdCsv.split(',');
|
||||
|
||||
if (claimIds.length === viewCounts.length) {
|
||||
claimIds.forEach((claimId, index) => {
|
||||
viewCountById[claimId] = viewCounts[index];
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingViewCount: false,
|
||||
viewCountById,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.FETCH_SUB_COUNT_STARTED]: (state) => ({ ...state, fetchingSubCount: true }),
|
||||
|
||||
[ACTIONS.FETCH_SUB_COUNT_FAILED]: (state, action) => ({
|
||||
...state,
|
||||
subCountError: action.data,
|
||||
}),
|
||||
|
||||
[ACTIONS.FETCH_SUB_COUNT_COMPLETED]: (state, action) => {
|
||||
const { claimIdCsv, subCounts, fetchDate } = action.data;
|
||||
|
||||
const subCountById = Object.assign({}, state.subCountById);
|
||||
const subCountLastFetchedById = Object.assign({}, state.subCountLastFetchedById);
|
||||
const claimIds = claimIdCsv.split(',');
|
||||
let dataChanged = false;
|
||||
|
||||
if (claimIds.length === subCounts.length) {
|
||||
claimIds.forEach((claimId, index) => {
|
||||
if (subCountById[claimId] !== subCounts[index]) {
|
||||
subCountById[claimId] = subCounts[index];
|
||||
dataChanged = true;
|
||||
}
|
||||
subCountLastFetchedById[claimId] = fetchDate;
|
||||
});
|
||||
}
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
fetchingSubCount: false,
|
||||
subCountLastFetchedById,
|
||||
};
|
||||
|
||||
if (dataChanged) {
|
||||
newState.subCountById = subCountById;
|
||||
}
|
||||
|
||||
return newState;
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
89
extras/lbryinc/redux/reducers/sync.js
Normal file
89
extras/lbryinc/redux/reducers/sync.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const reducers = {};
|
||||
const defaultState = {
|
||||
hasSyncedWallet: false,
|
||||
syncHash: null,
|
||||
syncData: null,
|
||||
setSyncErrorMessage: null,
|
||||
getSyncErrorMessage: null,
|
||||
syncApplyErrorMessage: '',
|
||||
syncApplyIsPending: false,
|
||||
syncApplyPasswordError: false,
|
||||
getSyncIsPending: false,
|
||||
setSyncIsPending: false,
|
||||
hashChanged: false,
|
||||
};
|
||||
|
||||
reducers[ACTIONS.GET_SYNC_STARTED] = state =>
|
||||
Object.assign({}, state, {
|
||||
getSyncIsPending: true,
|
||||
getSyncErrorMessage: null,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.GET_SYNC_COMPLETED] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
syncHash: action.data.syncHash,
|
||||
syncData: action.data.syncData,
|
||||
hasSyncedWallet: action.data.hasSyncedWallet,
|
||||
getSyncIsPending: false,
|
||||
hashChanged: action.data.hashChanged,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.GET_SYNC_FAILED] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
getSyncIsPending: false,
|
||||
getSyncErrorMessage: action.data.error,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SET_SYNC_STARTED] = state =>
|
||||
Object.assign({}, state, {
|
||||
setSyncIsPending: true,
|
||||
setSyncErrorMessage: null,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SET_SYNC_FAILED] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
setSyncIsPending: false,
|
||||
setSyncErrorMessage: action.data.error,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SET_SYNC_COMPLETED] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
setSyncIsPending: false,
|
||||
setSyncErrorMessage: null,
|
||||
hasSyncedWallet: true, // sync was successful, so the user has a synced wallet at this point
|
||||
syncHash: action.data.syncHash,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SYNC_APPLY_STARTED] = state =>
|
||||
Object.assign({}, state, {
|
||||
syncApplyPasswordError: false,
|
||||
syncApplyIsPending: true,
|
||||
syncApplyErrorMessage: '',
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SYNC_APPLY_COMPLETED] = state =>
|
||||
Object.assign({}, state, {
|
||||
syncApplyIsPending: false,
|
||||
syncApplyErrorMessage: '',
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SYNC_APPLY_FAILED] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
syncApplyIsPending: false,
|
||||
syncApplyErrorMessage: action.data.error,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SYNC_APPLY_BAD_PASSWORD] = state =>
|
||||
Object.assign({}, state, {
|
||||
syncApplyPasswordError: true,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SYNC_RESET] = () => defaultState;
|
||||
|
||||
export function syncReducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
return state;
|
||||
}
|
62
extras/lbryinc/redux/reducers/web.js
Normal file
62
extras/lbryinc/redux/reducers/web.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
/*
|
||||
test mock:
|
||||
currentUploads: {
|
||||
'test#upload': {
|
||||
progress: 50,
|
||||
params: {
|
||||
name: 'steve',
|
||||
thumbnail_url: 'https://dev2.spee.ch/4/KMNtoSZ009fawGz59VG8PrID.jpeg',
|
||||
},
|
||||
},
|
||||
},
|
||||
*/
|
||||
|
||||
export type Params = {
|
||||
channel?: string,
|
||||
name: string,
|
||||
thumbnail_url: ?string,
|
||||
title: ?string,
|
||||
};
|
||||
|
||||
export type UploadItem = {
|
||||
progess: string,
|
||||
params: Params,
|
||||
xhr?: any,
|
||||
};
|
||||
|
||||
export type TvState = {
|
||||
currentUploads: { [key: string]: UploadItem },
|
||||
};
|
||||
|
||||
const reducers = {};
|
||||
|
||||
const defaultState: TvState = {
|
||||
currentUploads: {},
|
||||
};
|
||||
|
||||
reducers[ACTIONS.UPDATE_UPLOAD_PROGRESS] = (state: TvState, action) => {
|
||||
const { progress, params, xhr } = action.data;
|
||||
const key = params.channel ? `${params.name}#${params.channel}` : `${params.name}#anonymous`;
|
||||
let currentUploads;
|
||||
if (!progress) {
|
||||
currentUploads = Object.assign({}, state.currentUploads);
|
||||
Object.keys(currentUploads).forEach(k => {
|
||||
if (k === key) {
|
||||
delete currentUploads[key];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
currentUploads = Object.assign({}, state.currentUploads);
|
||||
currentUploads[key] = { progress, params, xhr };
|
||||
}
|
||||
return { ...state, currentUploads };
|
||||
};
|
||||
|
||||
export function webReducer(state: TvState = defaultState, action: any) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
return state;
|
||||
}
|
4
extras/lbryinc/redux/selectors/auth.js
Normal file
4
extras/lbryinc/redux/selectors/auth.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
const selectState = (state) => state.auth || {};
|
||||
|
||||
export const selectAuthToken = (state) => selectState(state).authToken;
|
||||
export const selectIsAuthenticating = (state) => selectState(state).authenticating;
|
96
extras/lbryinc/redux/selectors/ban.js
Normal file
96
extras/lbryinc/redux/selectors/ban.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
// @flow
|
||||
|
||||
// TODO: This should be in 'redux/selectors/claim.js'. Temporarily putting it
|
||||
// here to get past importing issues with 'lbryinc', which the real fix might
|
||||
// involve moving it from 'extras' to 'ui' (big change).
|
||||
|
||||
import { createCachedSelector } from 're-reselect';
|
||||
import { selectClaimForUri } from 'redux/selectors/claims';
|
||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||
import { selectModerationBlockList } from 'redux/selectors/comments';
|
||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||
import { isURIEqual } from 'util/lbryURI';
|
||||
|
||||
const selectClaimExistsForUri = (state, uri) => {
|
||||
return Boolean(selectClaimForUri(state, uri));
|
||||
};
|
||||
|
||||
const selectTxidForUri = (state, uri) => {
|
||||
const claim = selectClaimForUri(state, uri);
|
||||
const signingChannel = claim && claim.signing_channel;
|
||||
return signingChannel ? signingChannel.txid : claim ? claim.txid : undefined;
|
||||
};
|
||||
|
||||
const selectNoutForUri = (state, uri) => {
|
||||
const claim = selectClaimForUri(state, uri);
|
||||
const signingChannel = claim && claim.signing_channel;
|
||||
return signingChannel ? signingChannel.nout : claim ? claim.nout : undefined;
|
||||
};
|
||||
|
||||
const selectPermanentUrlForUri = (state, uri) => {
|
||||
const claim = selectClaimForUri(state, uri);
|
||||
const signingChannel = claim && claim.signing_channel;
|
||||
return signingChannel ? signingChannel.permanent_url : claim ? claim.permanent_url : undefined;
|
||||
};
|
||||
|
||||
export const selectBanStateForUri = createCachedSelector(
|
||||
// Break apart 'selectClaimForUri' into 4 cheaper selectors that return
|
||||
// primitives for values that we care about. The Claim object itself is easily
|
||||
// invalidated due to constantly-changing fields like 'confirmation'.
|
||||
selectClaimExistsForUri,
|
||||
selectTxidForUri,
|
||||
selectNoutForUri,
|
||||
selectPermanentUrlForUri,
|
||||
selectBlackListedOutpoints,
|
||||
selectFilteredOutpoints,
|
||||
selectMutedChannels,
|
||||
selectModerationBlockList,
|
||||
(
|
||||
claimExists,
|
||||
txid,
|
||||
nout,
|
||||
permanentUrl,
|
||||
blackListedOutpoints,
|
||||
filteredOutpoints,
|
||||
mutedChannelUris,
|
||||
personalBlocklist
|
||||
) => {
|
||||
const banState = {};
|
||||
|
||||
if (!claimExists) {
|
||||
return banState;
|
||||
}
|
||||
|
||||
// This will be replaced once blocking is done at the wallet server level.
|
||||
if (blackListedOutpoints) {
|
||||
if (blackListedOutpoints.some((outpoint) => outpoint.txid === txid && outpoint.nout === nout)) {
|
||||
banState['blacklisted'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We're checking to see if the stream outpoint or signing channel outpoint
|
||||
// is in the filter list.
|
||||
if (filteredOutpoints) {
|
||||
if (filteredOutpoints.some((outpoint) => outpoint.txid === txid && outpoint.nout === nout)) {
|
||||
banState['filtered'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// block stream claims
|
||||
// block channel claims if we can't control for them in claim search
|
||||
if (mutedChannelUris.length) {
|
||||
if (mutedChannelUris.some((blockedUri) => isURIEqual(blockedUri, permanentUrl))) {
|
||||
banState['muted'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Commentron blocklist
|
||||
if (personalBlocklist.length) {
|
||||
if (personalBlocklist.some((blockedUri) => isURIEqual(blockedUri, permanentUrl))) {
|
||||
banState['blocked'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return banState;
|
||||
}
|
||||
)((state, uri) => String(uri));
|
15
extras/lbryinc/redux/selectors/blacklist.js
Normal file
15
extras/lbryinc/redux/selectors/blacklist.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
export const selectState = (state) => state.blacklist || {};
|
||||
|
||||
export const selectBlackListedOutpoints = (state) => selectState(state).blackListedOutpoints;
|
||||
|
||||
export const selectBlacklistedOutpointMap = createSelector(selectBlackListedOutpoints, (outpoints) =>
|
||||
outpoints
|
||||
? outpoints.reduce((acc, val) => {
|
||||
const outpoint = `${val.txid}:${val.nout}`;
|
||||
acc[outpoint] = 1;
|
||||
return acc;
|
||||
}, {})
|
||||
: {}
|
||||
);
|
13
extras/lbryinc/redux/selectors/cost_info.js
Normal file
13
extras/lbryinc/redux/selectors/cost_info.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
export const selectState = state => state.costInfo || {};
|
||||
|
||||
export const selectAllCostInfoByUri = createSelector(selectState, state => state.byUri || {});
|
||||
|
||||
export const makeSelectCostInfoForUri = uri =>
|
||||
createSelector(selectAllCostInfoByUri, costInfos => costInfos && costInfos[uri]);
|
||||
|
||||
export const selectFetchingCostInfo = createSelector(selectState, state => state.fetching || {});
|
||||
|
||||
export const makeSelectFetchingCostInfoForUri = uri =>
|
||||
createSelector(selectFetchingCostInfo, fetchingByUri => fetchingByUri && fetchingByUri[uri]);
|
15
extras/lbryinc/redux/selectors/filtered.js
Normal file
15
extras/lbryinc/redux/selectors/filtered.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
export const selectState = (state) => state.filtered || {};
|
||||
|
||||
export const selectFilteredOutpoints = (state) => selectState(state).filteredOutpoints;
|
||||
|
||||
export const selectFilteredOutpointMap = createSelector(selectFilteredOutpoints, (outpoints) =>
|
||||
outpoints
|
||||
? outpoints.reduce((acc, val) => {
|
||||
const outpoint = `${val.txid}:${val.nout}`;
|
||||
acc[outpoint] = 1;
|
||||
return acc;
|
||||
}, {})
|
||||
: {}
|
||||
);
|
6
extras/lbryinc/redux/selectors/homepage.js
Normal file
6
extras/lbryinc/redux/selectors/homepage.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
const selectState = (state) => state.homepage || {};
|
||||
|
||||
export const selectFeaturedUris = (state) => selectState(state).featuredUris;
|
||||
export const selectFetchingFeaturedUris = (state) => selectState(state).fetchingFeaturedContent;
|
||||
export const selectTrendingUris = (state) => selectState(state).trendingUris;
|
||||
export const selectFetchingTrendingUris = (state) => selectState(state).fetchingTrendingContent;
|
20
extras/lbryinc/redux/selectors/stats.js
Normal file
20
extras/lbryinc/redux/selectors/stats.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
|
||||
const selectState = state => state.stats || {};
|
||||
export const selectViewCount = createSelector(selectState, state => state.viewCountById);
|
||||
export const selectSubCount = createSelector(selectState, state => state.subCountById);
|
||||
|
||||
export const makeSelectViewCountForUri = uri =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
selectViewCount,
|
||||
(claim, viewCountById) => (claim ? viewCountById[claim.claim_id] || 0 : 0)
|
||||
);
|
||||
|
||||
export const makeSelectSubCountForUri = uri =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
selectSubCount,
|
||||
(claim, subCountById) => (claim ? subCountById[claim.claim_id] || 0 : 0)
|
||||
);
|
13
extras/lbryinc/redux/selectors/sync.js
Normal file
13
extras/lbryinc/redux/selectors/sync.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
const selectState = (state) => state.sync || {};
|
||||
|
||||
export const selectHasSyncedWallet = (state) => selectState(state).hasSyncedWallet;
|
||||
export const selectSyncHash = (state) => selectState(state).syncHash;
|
||||
export const selectSyncData = (state) => selectState(state).syncData;
|
||||
export const selectSetSyncErrorMessage = (state) => selectState(state).setSyncErrorMessage;
|
||||
export const selectGetSyncErrorMessage = (state) => selectState(state).getSyncErrorMessage;
|
||||
export const selectGetSyncIsPending = (state) => selectState(state).getSyncIsPending;
|
||||
export const selectSetSyncIsPending = (state) => selectState(state).setSyncIsPending;
|
||||
export const selectHashChanged = (state) => selectState(state).hashChanged;
|
||||
export const selectSyncApplyIsPending = (state) => selectState(state).syncApplyIsPending;
|
||||
export const selectSyncApplyErrorMessage = (state) => selectState(state).syncApplyErrorMessage;
|
||||
export const selectSyncApplyPasswordError = (state) => selectState(state).syncApplyPasswordError;
|
10
extras/lbryinc/redux/selectors/web.js
Normal file
10
extras/lbryinc/redux/selectors/web.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
const selectState = (state) => state.web || {};
|
||||
|
||||
export const selectCurrentUploads = (state) => selectState(state).currentUploads;
|
||||
|
||||
export const selectUploadCount = createSelector(
|
||||
selectCurrentUploads,
|
||||
(currentUploads) => currentUploads && Object.keys(currentUploads).length
|
||||
);
|
17
extras/lbryinc/util/redux-utils-delete-me.js
Normal file
17
extras/lbryinc/util/redux-utils-delete-me.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
// util for creating reducers
|
||||
// based off of redux-actions
|
||||
// https://redux-actions.js.org/docs/api/handleAction.html#handleactions
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const handleActions = (actionMap, defaultState) => (state = defaultState, action) => {
|
||||
const handler = actionMap[action.type];
|
||||
|
||||
if (handler) {
|
||||
const newState = handler(state, action);
|
||||
return Object.assign({}, state, newState);
|
||||
}
|
||||
|
||||
// just return the original state if no handler
|
||||
// returning a copy here breaks redux-persist
|
||||
return state;
|
||||
};
|
10
extras/lbryinc/util/swap-json.js
Normal file
10
extras/lbryinc/util/swap-json.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
export function swapKeyAndValue(dict) {
|
||||
const ret = {};
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key in dict) {
|
||||
if (dict.hasOwnProperty(key)) {
|
||||
ret[dict[key]] = key;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
78
extras/lbryinc/util/transifex-upload.js
Normal file
78
extras/lbryinc/util/transifex-upload.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
const apiBaseUrl = 'https://www.transifex.com/api/2/project';
|
||||
const resource = 'app-strings';
|
||||
|
||||
export function doTransifexUpload(contents, project, token, success, fail) {
|
||||
const url = `${apiBaseUrl}/${project}/resources/`;
|
||||
const updateUrl = `${apiBaseUrl}/${project}/resource/${resource}/content/`;
|
||||
const headers = {
|
||||
Authorization: `Basic ${Buffer.from(`api:${token}`).toString('base64')}`,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
const req = {
|
||||
accept_translations: true,
|
||||
i18n_type: 'KEYVALUEJSON',
|
||||
name: resource,
|
||||
slug: resource,
|
||||
content: contents,
|
||||
};
|
||||
|
||||
function handleResponse(text) {
|
||||
let json;
|
||||
try {
|
||||
// transifex api returns Python dicts for some reason.
|
||||
// Any way to get the api to return valid JSON?
|
||||
json = JSON.parse(text);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (success) {
|
||||
success(json || text);
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(err) {
|
||||
if (fail) {
|
||||
fail(err.message ? err.message : 'Could not upload strings resource to Transifex');
|
||||
}
|
||||
}
|
||||
|
||||
// check if the resource exists
|
||||
fetch(updateUrl, { headers })
|
||||
.then(response => response.json())
|
||||
.then(() => {
|
||||
// perform an update
|
||||
fetch(updateUrl, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify({ content: contents }),
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status !== 200 && response.status !== 201) {
|
||||
throw new Error('failed to update transifex');
|
||||
}
|
||||
|
||||
return response.text();
|
||||
})
|
||||
.then(handleResponse)
|
||||
.catch(handleError);
|
||||
})
|
||||
.catch(() => {
|
||||
// resource doesn't exist, create a fresh resource
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(req),
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status !== 200 && response.status !== 201) {
|
||||
throw new Error('failed to upload to transifex');
|
||||
}
|
||||
|
||||
return response.text();
|
||||
})
|
||||
.then(handleResponse)
|
||||
.catch(handleError);
|
||||
});
|
||||
}
|
3
extras/recsys/index.js
Normal file
3
extras/recsys/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Recsys from './recsys';
|
||||
|
||||
export default Recsys;
|
|
@ -1,10 +1,12 @@
|
|||
import { selectUser } from 'redux/selectors/user';
|
||||
import { makeSelectRecommendedRecsysIdForClaimId } from 'redux/selectors/search';
|
||||
import { v4 as Uuidv4 } from 'uuid';
|
||||
import { parseURI, SETTINGS, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { parseURI } from 'util/lbryURI';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { selectPlayingUri, selectPrimaryUri } from 'redux/selectors/content';
|
||||
import { makeSelectClientSetting, selectDaemonSettings } from 'redux/selectors/settings';
|
||||
import { history } from './store';
|
||||
import { history } from 'ui/store';
|
||||
|
||||
const recsysEndpoint = 'https://clickstream.odysee.com/log/video/view';
|
||||
const recsysId = 'lighthouse-v0';
|
||||
|
@ -71,7 +73,7 @@ const recsys = {
|
|||
* Called from recommendedContent component
|
||||
*/
|
||||
onRecsLoaded: function (claimId, uris) {
|
||||
if (window.store) {
|
||||
if (window && window.store) {
|
||||
const state = window.store.getState();
|
||||
if (!recsys.entries[claimId]) {
|
||||
recsys.createRecsysEntry(claimId);
|
||||
|
@ -90,9 +92,10 @@ const recsys = {
|
|||
* @param: parentUuid: string (optional)
|
||||
*/
|
||||
createRecsysEntry: function (claimId, parentUuid) {
|
||||
if (window.store && claimId) {
|
||||
if (window && window.store && claimId) {
|
||||
const state = window.store.getState();
|
||||
const { id: userId } = selectUser(state);
|
||||
const user = selectUser(state);
|
||||
const userId = user ? user.id : null;
|
||||
if (parentUuid) {
|
||||
// Make a stub entry that will be filled out on page load
|
||||
recsys.entries[claimId] = {
|
||||
|
@ -170,7 +173,7 @@ const recsys = {
|
|||
* if so, send the Entry.
|
||||
*/
|
||||
onPlayerDispose: function (claimId, isEmbedded) {
|
||||
if (window.store) {
|
||||
if (window && window.store) {
|
||||
const state = window.store.getState();
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const primaryUri = selectPrimaryUri(state);
|
||||
|
@ -193,7 +196,7 @@ const recsys = {
|
|||
// * more events until player is disposed. Don't send unless floatingPlayer playingUri
|
||||
// */
|
||||
// onLeaveFilePage: function (primaryUri) {
|
||||
// if (window.store) {
|
||||
// if (window && window.store) {
|
||||
// const state = window.store.getState();
|
||||
// const claim = makeSelectClaimForUri(primaryUri)(state);
|
||||
// const claimId = claim ? claim.claim_id : null;
|
||||
|
@ -219,7 +222,7 @@ const recsys = {
|
|||
* Send all claimIds that aren't currently playing.
|
||||
*/
|
||||
onNavigate: function () {
|
||||
if (window.store) {
|
||||
if (window && window.store) {
|
||||
const state = window.store.getState();
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const actualPlayingUri = playingUri && playingUri.uri;
|
10
flow-typed/Blocklist.js
vendored
Normal file
10
flow-typed/Blocklist.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare type BlocklistState = {
|
||||
blockedChannels: Array<string>
|
||||
};
|
||||
|
||||
declare type BlocklistAction = {
|
||||
type: string,
|
||||
data: {
|
||||
uri: string,
|
||||
},
|
||||
};
|
214
flow-typed/Claim.js
vendored
Normal file
214
flow-typed/Claim.js
vendored
Normal file
|
@ -0,0 +1,214 @@
|
|||
// @flow
|
||||
|
||||
declare type Claim = StreamClaim | ChannelClaim | CollectionClaim;
|
||||
|
||||
declare type ChannelClaim = GenericClaim & {
|
||||
value: ChannelMetadata,
|
||||
};
|
||||
|
||||
declare type CollectionClaim = GenericClaim & {
|
||||
value: CollectionMetadata,
|
||||
};
|
||||
|
||||
declare type StreamClaim = GenericClaim & {
|
||||
value: StreamMetadata,
|
||||
};
|
||||
|
||||
declare type GenericClaim = {
|
||||
address: string, // address associated with tx
|
||||
amount: string, // bid amount at time of tx
|
||||
canonical_url: string, // URL with short id, includes channel with short id
|
||||
claim_id: string, // unique claim identifier
|
||||
claim_sequence: number, // not being used currently
|
||||
claim_op: 'create' | 'update',
|
||||
confirmations: number,
|
||||
decoded_claim: boolean, // Not available currently https://github.com/lbryio/lbry/issues/2044
|
||||
timestamp?: number, // date of last transaction
|
||||
height: number, // block height the tx was confirmed
|
||||
is_channel_signature_valid?: boolean,
|
||||
is_my_output: boolean,
|
||||
name: string,
|
||||
normalized_name: string, // `name` normalized via unicode NFD spec,
|
||||
nout: number, // index number for an output of a tx
|
||||
permanent_url: string, // name + claim_id
|
||||
short_url: string, // permanent_url with short id, no channel
|
||||
txid: string, // unique tx id
|
||||
type: 'claim' | 'update' | 'support',
|
||||
value_type: 'stream' | 'channel' | 'collection',
|
||||
signing_channel?: ChannelClaim,
|
||||
reposted_claim?: GenericClaim,
|
||||
repost_channel_url?: string,
|
||||
repost_url?: string,
|
||||
repost_bid_amount?: string,
|
||||
purchase_receipt?: PurchaseReceipt,
|
||||
meta: {
|
||||
activation_height: number,
|
||||
claims_in_channel?: number,
|
||||
creation_height: number,
|
||||
creation_timestamp: number,
|
||||
effective_amount: string,
|
||||
expiration_height: number,
|
||||
is_controlling: boolean,
|
||||
support_amount: string,
|
||||
reposted: number,
|
||||
trending_global: number,
|
||||
trending_group: number,
|
||||
trending_local: number,
|
||||
trending_mixed: number,
|
||||
},
|
||||
};
|
||||
|
||||
declare type GenericMetadata = {
|
||||
title?: string,
|
||||
description?: string,
|
||||
thumbnail?: {
|
||||
url?: string,
|
||||
},
|
||||
languages?: Array<string>,
|
||||
tags?: Array<string>,
|
||||
locations?: Array<Location>,
|
||||
};
|
||||
|
||||
declare type ChannelMetadata = GenericMetadata & {
|
||||
public_key: string,
|
||||
public_key_id: string,
|
||||
cover_url?: string,
|
||||
email?: string,
|
||||
website_url?: string,
|
||||
featured?: Array<string>,
|
||||
};
|
||||
|
||||
declare type CollectionMetadata = GenericMetadata & {
|
||||
claims: Array<string>,
|
||||
}
|
||||
|
||||
declare type StreamMetadata = GenericMetadata & {
|
||||
license?: string, // License "title" ex: Creative Commons, Custom copyright
|
||||
license_url?: string, // Link to full license
|
||||
release_time?: number, // linux timestamp
|
||||
author?: string,
|
||||
|
||||
source: {
|
||||
sd_hash: string,
|
||||
media_type?: string,
|
||||
hash?: string,
|
||||
name?: string, // file name
|
||||
size?: number, // size of file in bytes
|
||||
},
|
||||
|
||||
// Only exists if a stream has a fee
|
||||
fee?: Fee,
|
||||
|
||||
stream_type: 'video' | 'audio' | 'image' | 'software',
|
||||
// Below correspond to `stream_type`
|
||||
video?: {
|
||||
duration: number,
|
||||
height: number,
|
||||
width: number,
|
||||
},
|
||||
audio?: {
|
||||
duration: number,
|
||||
},
|
||||
image?: {
|
||||
height: number,
|
||||
width: number,
|
||||
},
|
||||
software?: {
|
||||
os: string,
|
||||
},
|
||||
};
|
||||
|
||||
declare type Location = {
|
||||
latitude?: number,
|
||||
longitude?: number,
|
||||
country?: string,
|
||||
state?: string,
|
||||
city?: string,
|
||||
code?: string,
|
||||
};
|
||||
|
||||
declare type Fee = {
|
||||
amount: string,
|
||||
currency: string,
|
||||
address: string,
|
||||
};
|
||||
|
||||
declare type PurchaseReceipt = {
|
||||
address: string,
|
||||
amount: string,
|
||||
claim_id: string,
|
||||
confirmations: number,
|
||||
height: number,
|
||||
nout: number,
|
||||
timestamp: number,
|
||||
txid: string,
|
||||
type: 'purchase',
|
||||
};
|
||||
|
||||
declare type ClaimActionResolveInfo = {
|
||||
[string]: {
|
||||
stream: ?StreamClaim,
|
||||
channel: ?ChannelClaim,
|
||||
claimsInChannel: ?number,
|
||||
collection: ?CollectionClaim,
|
||||
},
|
||||
}
|
||||
|
||||
declare type ChannelUpdateParams = {
|
||||
claim_id: string,
|
||||
bid?: string,
|
||||
title?: string,
|
||||
cover_url?: string,
|
||||
thumbnail_url?: string,
|
||||
description?: string,
|
||||
website_url?: string,
|
||||
email?: string,
|
||||
tags?: Array<string>,
|
||||
replace?: boolean,
|
||||
languages?: Array<string>,
|
||||
locations?: Array<string>,
|
||||
blocking?: boolean,
|
||||
}
|
||||
|
||||
declare type ChannelPublishParams = {
|
||||
name: string,
|
||||
bid: string,
|
||||
blocking?: true,
|
||||
title?: string,
|
||||
cover_url?: string,
|
||||
thumbnail_url?: string,
|
||||
description?: string,
|
||||
website_url?: string,
|
||||
email?: string,
|
||||
tags?: Array<string>,
|
||||
languages?: Array<string>,
|
||||
}
|
||||
|
||||
declare type CollectionUpdateParams = {
|
||||
claim_id: string,
|
||||
claim_ids?: Array<string>,
|
||||
bid?: string,
|
||||
title?: string,
|
||||
cover_url?: string,
|
||||
thumbnail_url?: string,
|
||||
description?: string,
|
||||
website_url?: string,
|
||||
email?: string,
|
||||
tags?: Array<string>,
|
||||
replace?: boolean,
|
||||
languages?: Array<string>,
|
||||
locations?: Array<string>,
|
||||
blocking?: boolean,
|
||||
}
|
||||
|
||||
declare type CollectionPublishParams = {
|
||||
name: string,
|
||||
bid: string,
|
||||
claim_ids: Array<string>,
|
||||
blocking?: true,
|
||||
title?: string,
|
||||
thumbnail_url?: string,
|
||||
description?: string,
|
||||
tags?: Array<string>,
|
||||
languages?: Array<string>,
|
||||
}
|
29
flow-typed/CoinSwap.js
vendored
Normal file
29
flow-typed/CoinSwap.js
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
declare type CoinSwapInfo = {
|
||||
chargeCode: string,
|
||||
coins: Array<string>,
|
||||
sendAddresses: { [string]: string},
|
||||
sendAmounts: { [string]: any },
|
||||
lbcAmount: number,
|
||||
status?: {
|
||||
status: string,
|
||||
receiptCurrency: string,
|
||||
receiptTxid: string,
|
||||
lbcTxid: string,
|
||||
},
|
||||
}
|
||||
|
||||
declare type CoinSwapState = {
|
||||
coinSwaps: Array<CoinSwapInfo>,
|
||||
};
|
||||
|
||||
declare type CoinSwapAddAction = {
|
||||
type: string,
|
||||
data: CoinSwapInfo,
|
||||
};
|
||||
|
||||
declare type CoinSwapRemoveAction = {
|
||||
type: string,
|
||||
data: {
|
||||
chargeCode: string,
|
||||
},
|
||||
};
|
34
flow-typed/Collections.js
vendored
Normal file
34
flow-typed/Collections.js
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
declare type Collection = {
|
||||
id: string,
|
||||
items: Array<?string>,
|
||||
name: string,
|
||||
type: string,
|
||||
updatedAt: number,
|
||||
totalItems?: number,
|
||||
sourceId?: string, // if copied, claimId of original collection
|
||||
};
|
||||
|
||||
declare type CollectionState = {
|
||||
unpublished: CollectionGroup,
|
||||
resolved: CollectionGroup,
|
||||
pending: CollectionGroup,
|
||||
edited: CollectionGroup,
|
||||
builtin: CollectionGroup,
|
||||
saved: Array<string>,
|
||||
isResolvingCollectionById: { [string]: boolean },
|
||||
error?: string | null,
|
||||
};
|
||||
|
||||
declare type CollectionGroup = {
|
||||
[string]: Collection,
|
||||
}
|
||||
|
||||
declare type CollectionEditParams = {
|
||||
claims?: Array<Claim>,
|
||||
remove?: boolean,
|
||||
claimIds?: Array<string>,
|
||||
replace?: boolean,
|
||||
order?: { from: number, to: number },
|
||||
type?: string,
|
||||
name?: string,
|
||||
}
|
3
flow-typed/Comment.js
vendored
3
flow-typed/Comment.js
vendored
|
@ -45,6 +45,7 @@ declare type CommentsState = {
|
|||
isLoading: boolean,
|
||||
isLoadingById: boolean,
|
||||
isLoadingByParentId: { [string]: boolean },
|
||||
isCommenting: boolean,
|
||||
myComments: ?Set<string>,
|
||||
isFetchingReacts: boolean,
|
||||
myReactsByCommentId: ?{ [string]: Array<string> }, // {"CommentId:MyChannelId": ["like", "dislike", ...]}
|
||||
|
@ -177,7 +178,7 @@ declare type CommentCreateParams = {
|
|||
claim_id: string,
|
||||
parent_id?: string,
|
||||
signature: string,
|
||||
signing_ts: number,
|
||||
signing_ts: string,
|
||||
support_tx_id?: string,
|
||||
};
|
||||
|
||||
|
|
369
flow-typed/Lbry.js
vendored
Normal file
369
flow-typed/Lbry.js
vendored
Normal file
|
@ -0,0 +1,369 @@
|
|||
// @flow
|
||||
declare type StatusResponse = {
|
||||
blob_manager: {
|
||||
finished_blobs: number,
|
||||
},
|
||||
blockchain_headers: {
|
||||
download_progress: number,
|
||||
downloading_headers: boolean,
|
||||
},
|
||||
dht: {
|
||||
node_id: string,
|
||||
peers_in_routing_table: number,
|
||||
},
|
||||
hash_announcer: {
|
||||
announce_queue_size: number,
|
||||
},
|
||||
installation_id: string,
|
||||
is_running: boolean,
|
||||
skipped_components: Array<string>,
|
||||
startup_status: {
|
||||
blob_manager: boolean,
|
||||
blockchain_headers: boolean,
|
||||
database: boolean,
|
||||
dht: boolean,
|
||||
exchange_rate_manager: boolean,
|
||||
hash_announcer: boolean,
|
||||
peer_protocol_server: boolean,
|
||||
stream_manager: boolean,
|
||||
upnp: boolean,
|
||||
wallet: boolean,
|
||||
},
|
||||
stream_manager: {
|
||||
managed_files: number,
|
||||
},
|
||||
upnp: {
|
||||
aioupnp_version: string,
|
||||
dht_redirect_set: boolean,
|
||||
external_ip: string,
|
||||
gateway: string,
|
||||
peer_redirect_set: boolean,
|
||||
redirects: {},
|
||||
},
|
||||
wallet: ?{
|
||||
connected: string,
|
||||
best_blockhash: string,
|
||||
blocks: number,
|
||||
blocks_behind: number,
|
||||
is_encrypted: boolean,
|
||||
is_locked: boolean,
|
||||
headers_synchronization_progress: number,
|
||||
available_servers: number,
|
||||
},
|
||||
};
|
||||
|
||||
declare type VersionResponse = {
|
||||
build: string,
|
||||
lbrynet_version: string,
|
||||
os_release: string,
|
||||
os_system: string,
|
||||
platform: string,
|
||||
processor: string,
|
||||
python_version: string,
|
||||
};
|
||||
|
||||
declare type BalanceResponse = {
|
||||
available: string,
|
||||
reserved: string,
|
||||
reserved_subtotals: ?{
|
||||
claims: string,
|
||||
supports: string,
|
||||
tips: string,
|
||||
},
|
||||
total: string,
|
||||
};
|
||||
|
||||
declare type ResolveResponse = {
|
||||
// Keys are the url(s) passed to resolve
|
||||
[string]: { error?: {}, stream?: StreamClaim, channel?: ChannelClaim, collection?: CollectionClaim, claimsInChannel?: number },
|
||||
};
|
||||
|
||||
declare type GetResponse = FileListItem & { error?: string };
|
||||
|
||||
declare type GenericTxResponse = {
|
||||
height: number,
|
||||
hex: string,
|
||||
inputs: Array<{}>,
|
||||
outputs: Array<{}>,
|
||||
total_fee: string,
|
||||
total_input: string,
|
||||
total_output: string,
|
||||
txid: string,
|
||||
};
|
||||
|
||||
declare type PublishResponse = GenericTxResponse & {
|
||||
// Only first value in outputs is a claim
|
||||
// That's the only value we care about
|
||||
outputs: Array<Claim>,
|
||||
};
|
||||
|
||||
declare type ClaimSearchResponse = {
|
||||
items: Array<Claim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type ClaimListResponse = {
|
||||
items: Array<ChannelClaim | Claim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type ChannelCreateResponse = GenericTxResponse & {
|
||||
outputs: Array<ChannelClaim>,
|
||||
};
|
||||
|
||||
declare type ChannelUpdateResponse = GenericTxResponse & {
|
||||
outputs: Array<ChannelClaim>,
|
||||
};
|
||||
|
||||
declare type CommentCreateResponse = Comment;
|
||||
declare type CommentUpdateResponse = Comment;
|
||||
|
||||
declare type MyReactions = {
|
||||
// Keys are the commentId
|
||||
[string]: Array<string>,
|
||||
};
|
||||
|
||||
declare type OthersReactions = {
|
||||
// Keys are the commentId
|
||||
[string]: {
|
||||
// Keys are the reaction_type, e.g. 'like'
|
||||
[string]: number,
|
||||
},
|
||||
};
|
||||
|
||||
declare type CommentReactListResponse = {
|
||||
my_reactions: Array<MyReactions>,
|
||||
others_reactions: Array<OthersReactions>,
|
||||
};
|
||||
|
||||
declare type CommentHideResponse = {
|
||||
// keyed by the CommentIds entered
|
||||
[string]: { hidden: boolean },
|
||||
};
|
||||
|
||||
declare type CommentPinResponse = {
|
||||
// keyed by the CommentIds entered
|
||||
items: Comment,
|
||||
};
|
||||
|
||||
declare type CommentAbandonResponse = {
|
||||
// keyed by the CommentId given
|
||||
abandoned: boolean,
|
||||
};
|
||||
|
||||
declare type ChannelListResponse = {
|
||||
items: Array<ChannelClaim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type ChannelSignResponse = {
|
||||
signature: string,
|
||||
signing_ts: string,
|
||||
};
|
||||
|
||||
declare type CollectionCreateResponse = {
|
||||
outputs: Array<Claim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
}
|
||||
|
||||
declare type CollectionListResponse = {
|
||||
items: Array<Claim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type CollectionResolveResponse = {
|
||||
items: Array<Claim>,
|
||||
total_items: number,
|
||||
};
|
||||
|
||||
declare type CollectionResolveOptions = {
|
||||
claim_id: string,
|
||||
};
|
||||
|
||||
declare type CollectionListOptions = {
|
||||
page: number,
|
||||
page_size: number,
|
||||
resolve?: boolean,
|
||||
};
|
||||
|
||||
declare type FileListResponse = {
|
||||
items: Array<FileListItem>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type TxListResponse = {
|
||||
items: Array<Transaction>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type SupportListResponse = {
|
||||
items: Array<Support>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type BlobListResponse = { items: Array<string> };
|
||||
|
||||
declare type WalletListResponse = Array<{
|
||||
id: string,
|
||||
name: string,
|
||||
}>;
|
||||
|
||||
declare type WalletStatusResponse = {
|
||||
is_encrypted: boolean,
|
||||
is_locked: boolean,
|
||||
is_syncing: boolean,
|
||||
};
|
||||
|
||||
declare type SyncApplyResponse = {
|
||||
hash: string,
|
||||
data: string,
|
||||
};
|
||||
|
||||
declare type SupportAbandonResponse = GenericTxResponse;
|
||||
|
||||
declare type StreamListResponse = {
|
||||
items: Array<StreamClaim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type StreamRepostOptions = {
|
||||
name: string,
|
||||
bid: string,
|
||||
claim_id: string,
|
||||
channel_id?: string,
|
||||
};
|
||||
|
||||
declare type StreamRepostResponse = GenericTxResponse;
|
||||
|
||||
declare type PurchaseListResponse = {
|
||||
items: Array<PurchaseReceipt & { claim: StreamClaim }>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type PurchaseListOptions = {
|
||||
page: number,
|
||||
page_size: number,
|
||||
resolve: boolean,
|
||||
claim_id?: string,
|
||||
channel_id?: string,
|
||||
};
|
||||
|
||||
//
|
||||
// Types used in the generic Lbry object that is exported
|
||||
//
|
||||
declare type LbryTypes = {
|
||||
isConnected: boolean,
|
||||
connectPromise: any, // null |
|
||||
connect: () => any, // void | Promise<any> ?
|
||||
daemonConnectionString: string,
|
||||
alternateConnectionString: string,
|
||||
methodsUsingAlternateConnectionString: Array<string>,
|
||||
apiRequestHeaders: { [key: string]: string },
|
||||
setDaemonConnectionString: string => void,
|
||||
setApiHeader: (string, string) => void,
|
||||
unsetApiHeader: string => void,
|
||||
overrides: { [string]: ?Function },
|
||||
setOverride: (string, Function) => void,
|
||||
// getMediaType: (?string, ?string) => string,
|
||||
|
||||
// Lbry Methods
|
||||
stop: () => Promise<string>,
|
||||
status: () => Promise<StatusResponse>,
|
||||
version: () => Promise<VersionResponse>,
|
||||
resolve: (params: {}) => Promise<ResolveResponse>,
|
||||
get: (params: {}) => Promise<GetResponse>,
|
||||
publish: (params: {}) => Promise<PublishResponse>,
|
||||
|
||||
claim_search: (params: {}) => Promise<ClaimSearchResponse>,
|
||||
claim_list: (params: {}) => Promise<ClaimListResponse>,
|
||||
channel_create: (params: {}) => Promise<ChannelCreateResponse>,
|
||||
channel_update: (params: {}) => Promise<ChannelUpdateResponse>,
|
||||
channel_import: (params: {}) => Promise<string>,
|
||||
channel_list: (params: {}) => Promise<ChannelListResponse>,
|
||||
channel_sign: (params: {}) => Promise<ChannelSignResponse>,
|
||||
stream_abandon: (params: {}) => Promise<GenericTxResponse>,
|
||||
stream_list: (params: {}) => Promise<StreamListResponse>,
|
||||
channel_abandon: (params: {}) => Promise<GenericTxResponse>,
|
||||
support_create: (params: {}) => Promise<GenericTxResponse>,
|
||||
support_list: (params: {}) => Promise<SupportListResponse>,
|
||||
support_abandon: (params: {}) => Promise<SupportAbandonResponse>,
|
||||
stream_repost: (params: StreamRepostOptions) => Promise<StreamRepostResponse>,
|
||||
purchase_list: (params: PurchaseListOptions) => Promise<PurchaseListResponse>,
|
||||
collection_resolve: (params: CollectionResolveOptions) => Promise<CollectionResolveResponse>,
|
||||
collection_list: (params: CollectionListOptions) => Promise<CollectionListResponse>,
|
||||
collection_create: (params: {}) => Promise<CollectionCreateResponse>,
|
||||
collection_update: (params: {}) => Promise<CollectionCreateResponse>,
|
||||
|
||||
// File fetching and manipulation
|
||||
file_list: (params: {}) => Promise<FileListResponse>,
|
||||
file_delete: (params: {}) => Promise<boolean>,
|
||||
blob_delete: (params: {}) => Promise<string>,
|
||||
blob_list: (params: {}) => Promise<BlobListResponse>,
|
||||
file_set_status: (params: {}) => Promise<any>,
|
||||
file_reflect: (params: {}) => Promise<any>,
|
||||
|
||||
// Preferences
|
||||
preference_get: (params?: {}) => Promise<any>,
|
||||
preference_set: (params: {}) => Promise<any>,
|
||||
|
||||
// Commenting
|
||||
comment_update: (params: {}) => Promise<CommentUpdateResponse>,
|
||||
comment_hide: (params: {}) => Promise<CommentHideResponse>,
|
||||
comment_abandon: (params: {}) => Promise<CommentAbandonResponse>,
|
||||
comment_list: (params: {}) => Promise<any>,
|
||||
comment_create: (params: {}) => Promise<any>,
|
||||
|
||||
// Wallet utilities
|
||||
wallet_balance: (params: {}) => Promise<BalanceResponse>,
|
||||
wallet_decrypt: (prams: {}) => Promise<boolean>,
|
||||
wallet_encrypt: (params: {}) => Promise<boolean>,
|
||||
wallet_unlock: (params: {}) => Promise<boolean>,
|
||||
wallet_list: (params: {}) => Promise<WalletListResponse>,
|
||||
wallet_send: (params: {}) => Promise<GenericTxResponse>,
|
||||
wallet_status: (params?: {}) => Promise<WalletStatusResponse>,
|
||||
address_is_mine: (params: {}) => Promise<boolean>,
|
||||
address_unused: (params: {}) => Promise<string>, // New address
|
||||
address_list: (params: {}) => Promise<string>,
|
||||
transaction_list: (params: {}) => Promise<TxListResponse>,
|
||||
txo_list: (params: {}) => Promise<any>,
|
||||
account_set: (params: {}) => Promise<any>,
|
||||
account_list: (params?: {}) => Promise<any>,
|
||||
|
||||
// Sync
|
||||
sync_hash: (params?: {}) => Promise<string>,
|
||||
sync_apply: (params: {}) => Promise<SyncApplyResponse>,
|
||||
// syncGet
|
||||
|
||||
// The app shouldn't need to do this
|
||||
utxo_release: () => Promise<any>,
|
||||
};
|
99
flow-typed/LbryFirst.js
vendored
Normal file
99
flow-typed/LbryFirst.js
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
// @flow
|
||||
declare type LbryFirstStatusResponse = {
|
||||
Version: string,
|
||||
Message: string,
|
||||
Running: boolean,
|
||||
Commit: string,
|
||||
};
|
||||
|
||||
declare type LbryFirstVersionResponse = {
|
||||
build: string,
|
||||
lbrynet_version: string,
|
||||
os_release: string,
|
||||
os_system: string,
|
||||
platform: string,
|
||||
processor: string,
|
||||
python_version: string,
|
||||
};
|
||||
/* SAMPLE UPLOAD RESPONSE (FULL)
|
||||
"Video": {
|
||||
"etag": "\"Dn5xIderbhAnUk5TAW0qkFFir0M/xlGLrlTox7VFTRcR8F77RbKtaU4\"",
|
||||
"id": "8InjtdvVmwE",
|
||||
"kind": "youtube#video",
|
||||
"snippet": {
|
||||
"categoryId": "22",
|
||||
"channelId": "UCXiVsGTU88fJjheB2rqF0rA",
|
||||
"channelTitle": "Mark Beamer",
|
||||
"liveBroadcastContent": "none",
|
||||
"localized": {
|
||||
"title": "my title"
|
||||
},
|
||||
"publishedAt": "2020-05-05T04:17:53.000Z",
|
||||
"thumbnails": {
|
||||
"default": {
|
||||
"height": 90,
|
||||
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/default.jpg?sqp=CMTQw_UF&rs=AOn4CLB6dlhZMSMrazDlWRsitPgCsn8fVw",
|
||||
"width": 120
|
||||
},
|
||||
"high": {
|
||||
"height": 360,
|
||||
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/hqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLB-Je_7l6qvASRAR_bSGWZHaXaJWQ",
|
||||
"width": 480
|
||||
},
|
||||
"medium": {
|
||||
"height": 180,
|
||||
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/mqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLCvSnDLqVznRNMKuvJ_0misY_chPQ",
|
||||
"width": 320
|
||||
}
|
||||
},
|
||||
"title": "my title"
|
||||
},
|
||||
"status": {
|
||||
"embeddable": true,
|
||||
"license": "youtube",
|
||||
"privacyStatus": "private",
|
||||
"publicStatsViewable": true,
|
||||
"uploadStatus": "uploaded"
|
||||
}
|
||||
}
|
||||
*/
|
||||
declare type UploadResponse = {
|
||||
Video: {
|
||||
id: string,
|
||||
snippet: {
|
||||
channelId: string,
|
||||
},
|
||||
status: {
|
||||
uploadStatus: string,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
declare type HasYTAuthResponse = {
|
||||
HashAuth: boolean,
|
||||
};
|
||||
|
||||
declare type YTSignupResponse = {};
|
||||
|
||||
//
|
||||
// Types used in the generic LbryFirst object that is exported
|
||||
//
|
||||
declare type LbryFirstTypes = {
|
||||
isConnected: boolean,
|
||||
connectPromise: ?Promise<any>,
|
||||
connect: () => void,
|
||||
lbryFirstConnectionString: string,
|
||||
apiRequestHeaders: { [key: string]: string },
|
||||
setApiHeader: (string, string) => void,
|
||||
unsetApiHeader: string => void,
|
||||
overrides: { [string]: ?Function },
|
||||
setOverride: (string, Function) => void,
|
||||
|
||||
// LbryFirst Methods
|
||||
stop: () => Promise<string>,
|
||||
status: () => Promise<StatusResponse>,
|
||||
version: () => Promise<VersionResponse>,
|
||||
upload: any => Promise<?UploadResponse>,
|
||||
hasYTAuth: string => Promise<HasYTAuthResponse>,
|
||||
ytSignup: () => Promise<YTSignupResponse>,
|
||||
};
|
5
flow-typed/Reflector.js
vendored
Normal file
5
flow-typed/Reflector.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
declare type ReflectingUpdate = {
|
||||
fileListItem: FileListItem,
|
||||
progress: number | boolean,
|
||||
stalled: boolean,
|
||||
};
|
21
flow-typed/Tags.js
vendored
Normal file
21
flow-typed/Tags.js
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
declare type TagState = {
|
||||
followedTags: FollowedTags,
|
||||
knownTags: KnownTags,
|
||||
};
|
||||
|
||||
declare type Tag = {
|
||||
name: string,
|
||||
};
|
||||
|
||||
declare type KnownTags = {
|
||||
[string]: Tag,
|
||||
};
|
||||
|
||||
declare type FollowedTags = Array<string>;
|
||||
|
||||
declare type TagAction = {
|
||||
type: string,
|
||||
data: {
|
||||
name: string,
|
||||
},
|
||||
};
|
28
flow-typed/Transaction.js
vendored
Normal file
28
flow-typed/Transaction.js
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
// @flow
|
||||
declare type Transaction = {
|
||||
amount: number,
|
||||
claim_id: string,
|
||||
claim_name: string,
|
||||
fee: number,
|
||||
nout: number,
|
||||
txid: string,
|
||||
type: string,
|
||||
date: Date,
|
||||
};
|
||||
|
||||
declare type Support = {
|
||||
address: string,
|
||||
amount: string,
|
||||
claim_id: string,
|
||||
confirmations: number,
|
||||
height: string,
|
||||
is_change: string,
|
||||
is_mine: string,
|
||||
name: string,
|
||||
normalized_name: string,
|
||||
nout: string,
|
||||
permanent_url: string,
|
||||
timestamp: number,
|
||||
txid: string,
|
||||
type: string,
|
||||
};
|
27
flow-typed/Txo.js
vendored
Normal file
27
flow-typed/Txo.js
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
declare type Txo = {
|
||||
amount: number,
|
||||
claim_id: string,
|
||||
normalized_name: string,
|
||||
nout: number,
|
||||
txid: string,
|
||||
type: string,
|
||||
value_type: string,
|
||||
timestamp: number,
|
||||
is_my_output: boolean,
|
||||
is_my_input: boolean,
|
||||
is_spent: boolean,
|
||||
signing_channel?: {
|
||||
channel_id: string,
|
||||
},
|
||||
};
|
||||
|
||||
declare type TxoListParams = {
|
||||
page: number,
|
||||
page_size: number,
|
||||
type: string,
|
||||
is_my_input?: boolean,
|
||||
is_my_output?: boolean,
|
||||
is_not_my_input?: boolean,
|
||||
is_not_my_output?: boolean,
|
||||
is_spent?: boolean,
|
||||
};
|
2
flow-typed/i18n.js
vendored
Normal file
2
flow-typed/i18n.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
// @flow
|
||||
declare function __(a: string, b?: {}): string;
|
21
flow-typed/lbryURI.js
vendored
Normal file
21
flow-typed/lbryURI.js
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// @flow
|
||||
declare type LbryUrlObj = {
|
||||
// Path and channel will always exist when calling parseURI
|
||||
// But they may not exist when code calls buildURI
|
||||
isChannel?: boolean,
|
||||
path?: string,
|
||||
streamName?: string,
|
||||
streamClaimId?: string,
|
||||
channelName?: string,
|
||||
channelClaimId?: string,
|
||||
primaryClaimSequence?: number,
|
||||
secondaryClaimSequence?: number,
|
||||
primaryBidPosition?: number,
|
||||
secondaryBidPosition?: number,
|
||||
startTime?: number,
|
||||
|
||||
// Below are considered deprecated and should not be used due to unreliableness with claim.canonical_url
|
||||
claimName?: string,
|
||||
claimId?: string,
|
||||
contentName?: string,
|
||||
};
|
93
flow-typed/notification.js
vendored
93
flow-typed/notification.js
vendored
|
@ -1,4 +1,97 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
/*
|
||||
Toasts:
|
||||
- First-in, first-out queue
|
||||
- Simple messages that are shown in response to user interactions
|
||||
- Never saved
|
||||
- If they are the result of errors, use the isError flag when creating
|
||||
- For errors that should interrupt user behavior, use Error
|
||||
*/
|
||||
declare type ToastParams = {
|
||||
message: string,
|
||||
title?: string,
|
||||
linkText?: string,
|
||||
linkTarget?: string,
|
||||
isError?: boolean,
|
||||
};
|
||||
|
||||
declare type Toast = {
|
||||
id: string,
|
||||
params: ToastParams,
|
||||
};
|
||||
|
||||
declare type DoToast = {
|
||||
type: ACTIONS.CREATE_TOAST,
|
||||
data: Toast,
|
||||
};
|
||||
|
||||
/*
|
||||
Notifications:
|
||||
- List of notifications based on user interactions/app notifications
|
||||
- Always saved, but can be manually deleted
|
||||
- Can happen in the background, or because of user interaction (ex: publish confirmed)
|
||||
*/
|
||||
declare type Notification = {
|
||||
id: string, // Unique id
|
||||
dateCreated: number,
|
||||
isRead: boolean, // Used to display "new" notifications that a user hasn't seen yet
|
||||
source?: string, // The type/area an notification is from. Used for sorting (ex: publishes, transactions)
|
||||
// We may want to use priority/isDismissed in the future to specify how urgent a notification is
|
||||
// and if the user should see it immediately
|
||||
// isDissmied: boolean,
|
||||
// priority?: number
|
||||
};
|
||||
|
||||
declare type DoNotification = {
|
||||
type: ACTIONS.CREATE_NOTIFICATION,
|
||||
data: Notification,
|
||||
};
|
||||
|
||||
declare type DoEditNotification = {
|
||||
type: ACTIONS.EDIT_NOTIFICATION,
|
||||
data: {
|
||||
notification: Notification,
|
||||
},
|
||||
};
|
||||
|
||||
declare type DoDeleteNotification = {
|
||||
type: ACTIONS.DELETE_NOTIFICATION,
|
||||
data: {
|
||||
id: string, // The id to delete
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
Errors:
|
||||
- First-in, first-out queue
|
||||
- Errors that should interupt user behavior
|
||||
- For errors that can be shown without interrupting a user, use Toast with the isError flag
|
||||
*/
|
||||
declare type ErrorNotification = {
|
||||
title: string,
|
||||
text: string,
|
||||
};
|
||||
|
||||
declare type DoError = {
|
||||
type: ACTIONS.CREATE_ERROR,
|
||||
data: ErrorNotification,
|
||||
};
|
||||
|
||||
declare type DoDismissError = {
|
||||
type: ACTIONS.DISMISS_ERROR,
|
||||
};
|
||||
|
||||
/*
|
||||
NotificationState
|
||||
*/
|
||||
declare type NotificationState = {
|
||||
notifications: Array<Notification>,
|
||||
errors: Array<ErrorNotification>,
|
||||
toasts: Array<Toast>,
|
||||
};
|
||||
|
||||
declare type WebNotification = {
|
||||
active_at: string,
|
||||
created_at: string,
|
||||
|
|
2
flow-typed/publish.js
vendored
2
flow-typed/publish.js
vendored
|
@ -25,7 +25,7 @@ declare type UpdatePublishFormData = {
|
|||
licenseType?: string,
|
||||
uri?: string,
|
||||
nsfw: boolean,
|
||||
isMarkdownPost: boolean,
|
||||
isMarkdownPost?: boolean,
|
||||
};
|
||||
|
||||
declare type PublishParams = {
|
||||
|
|
3
flow-typed/redux.js
vendored
3
flow-typed/redux.js
vendored
|
@ -1,3 +1,6 @@
|
|||
// @flow
|
||||
|
||||
/* eslint-disable no-use-before-define */
|
||||
declare type GetState = () => any;
|
||||
declare type Dispatch = any;
|
||||
/* eslint-enable */
|
||||
|
|
15
package.json
15
package.json
|
@ -20,7 +20,7 @@
|
|||
},
|
||||
"main": "./dist/electron/main.js",
|
||||
"scripts": {
|
||||
"analyze": "source-map-explorer --only-mapped dist/electron/webpack/ui*.js --html dist/sourceMap.html",
|
||||
"analyze": "yarn compile && source-map-explorer --no-border-checks web/dist/public/*.js --html analyzeResults.html",
|
||||
"compile:electron": "node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.electron.config.js",
|
||||
"compile:web": "yarn copyenv && cd web && node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.config.js",
|
||||
"compile": "cross-env NODE_ENV=production yarn compile:electron && cross-env NODE_ENV=production yarn compile:web",
|
||||
|
@ -38,17 +38,18 @@
|
|||
"build:dir": "yarn build -- --dir -c.compression=store -c.mac.identity=null",
|
||||
"crossenv": "./node_modules/cross-env/dist/bin/cross-env",
|
||||
"flow": "flow",
|
||||
"lint": "eslint 'ui/**/*.{js,jsx}' && eslint 'web/**/*.{js,jsx}' && eslint 'electron/**/*.js' && flow",
|
||||
"lint-fix": "eslint --fix --quiet 'ui/**/*.{js,jsx}' && eslint --fix --quiet 'web/**/*.{js,jsx}' && eslint --fix --quiet 'electron/**/*.js'",
|
||||
"lint": "eslint 'ui/**/*.{js,jsx}' && eslint 'extras/**/*.{js,jsx}' && eslint 'web/**/*.{js,jsx}' && eslint 'electron/**/*.js' && flow",
|
||||
"lint-fix": "eslint --fix --quiet 'ui/**/*.{js,jsx}' && eslint --fix --quiet 'extras/**/*.{js,jsx}' && eslint --fix --quiet 'web/**/*.{js,jsx}' && eslint --fix --quiet 'electron/**/*.js'",
|
||||
"format": "prettier 'src/**/*.{js,jsx,scss,json}' --write",
|
||||
"flow-defs": "flow-typed install",
|
||||
"precommit": "lint-staged",
|
||||
"preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc",
|
||||
"preinstall": "",
|
||||
"postinstall": "cd web && yarn && cd .. && if-env NODE_ENV=production && yarn postinstall:warning || if-env APP_ENV=web && echo 'Done installing deps' || yarn postinstall:electron",
|
||||
"postinstall:electron": "electron-builder install-app-deps && node ./build/downloadDaemon.js && node ./build/downloadLBRYFirst.js",
|
||||
"postinstall:warning": "echo '\n\nWARNING\n\nNot all node modules were installed because NODE_ENV is set to \"production\".\nThis should only be set after installing dependencies with \"yarn\". The app will not work.\n\n'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ungap/from-entries": "^0.2.1",
|
||||
"auto-launch": "^5.0.5",
|
||||
"electron-dl": "^1.11.0",
|
||||
"electron-log": "^2.2.12",
|
||||
|
@ -59,6 +60,8 @@
|
|||
"if-env": "^1.0.4",
|
||||
"match-sorter": "^6.3.0",
|
||||
"parse-duration": "^1.0.0",
|
||||
"proxy-polyfill": "0.1.6",
|
||||
"re-reselect": "^4.0.0",
|
||||
"react-datetime-picker": "^3.2.1",
|
||||
"react-plastic": "^1.1.1",
|
||||
"react-top-loading-bar": "^2.0.1",
|
||||
|
@ -68,6 +71,7 @@
|
|||
"tempy": "^0.6.0",
|
||||
"videojs-contrib-ads": "^6.9.0",
|
||||
"videojs-ima": "^1.11.0",
|
||||
"videojs-ima-player": "^0.5.6",
|
||||
"videojs-logo": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -84,7 +88,6 @@
|
|||
"@babel/preset-flow": "^7.12.1",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/register": "^7.0.0",
|
||||
"@datapunt/matomo-tracker-js": "^0.1.4",
|
||||
"@exponent/electron-cookies": "^2.0.0",
|
||||
"@hot-loader/react-dom": "^16.13",
|
||||
"@reach/auto-id": "^0.13.0",
|
||||
|
@ -157,8 +160,6 @@
|
|||
"imagesloaded": "^4.1.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#0f930c4a7bfc7f164e6b3c6044050c1bc73f6ab8",
|
||||
"lbryinc": "lbryio/lbryinc#0b4e41ef90d6347819dd3453f2f9398a5c1b4f36",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
"lodash-es": "^4.17.14",
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
"About --[tab title in Channel Page]--": "About",
|
||||
"About --[link title in Sidebar or Footer]--": "About",
|
||||
"Community Guidelines": "Community Guidelines",
|
||||
"FAQ and Support": "FAQ and Support",
|
||||
"Share Channel": "Share Channel",
|
||||
"Go to page:": "Go to page:",
|
||||
"Enter a URL for your thumbnail.": "Enter a URL for your thumbnail.",
|
||||
|
@ -468,11 +469,8 @@
|
|||
"New --[clears Publish Form]--": "New",
|
||||
"Loading": "Loading",
|
||||
"This file is in your library.": "This file is in your library.",
|
||||
"'claimName', 'channelName', and 'streamName' are all empty. One must be present to build a url.": "'claimName', 'channelName', and 'streamName' are all empty. One must be present to build a url.",
|
||||
"Invalid claim ID %s.": "Invalid claim ID %s.",
|
||||
"'claimId' should no longer be used. Use 'streamClaimId' or 'channelClaimId' instead": "'claimId' should no longer be used. Use 'streamClaimId' or 'channelClaimId' instead",
|
||||
"View Tag": "View Tag",
|
||||
"'claimName' should no longer be used. Use 'streamClaimName' or 'channelClaimName' instead": "'claimName' should no longer be used. Use 'streamClaimName' or 'channelClaimName' instead",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Thai": "Thai",
|
||||
"Arabic": "Arabic",
|
||||
|
@ -485,7 +483,6 @@
|
|||
"Greek": "Greek",
|
||||
"Hide": "Hide",
|
||||
"Persian": "Persian",
|
||||
"'contentName' should no longer be used. Use 'streamName' instead": "'contentName' should no longer be used. Use 'streamName' instead",
|
||||
"Sorry, we can't preview this file.": "Sorry, we can't preview this file.",
|
||||
"View File": "View File",
|
||||
"Close": "Close",
|
||||
|
@ -2163,6 +2160,8 @@
|
|||
"Bank Accounts": "Bank Accounts",
|
||||
"Connect a bank account to receive tips and compensation in your local currency.": "Connect a bank account to receive tips and compensation in your local currency.",
|
||||
"Payment Methods": "Payment Methods",
|
||||
"Total Received Tips": "Total Received Tips",
|
||||
"Withdrawn": "Withdrawn",
|
||||
"Incoming": "Incoming",
|
||||
"Outgoing": "Outgoing",
|
||||
"Credits --[transactions tab]--": "Credits",
|
||||
|
@ -2178,11 +2177,29 @@
|
|||
"Card Last 4": "Card Last 4",
|
||||
"Search blocked channel name": "Search blocked channel name",
|
||||
"Discuss": "Discuss",
|
||||
"Validating...": "Validating...",
|
||||
"[Removed]": "[Removed]",
|
||||
"lbry.tv has been retired. You have been magically transported to Odysee.com. %more%": "lbry.tv has been retired. You have been magically transported to Odysee.com. %more%",
|
||||
"Show more livestreams": "Show more livestreams",
|
||||
"Creator": "Creator",
|
||||
"From comments": "From comments",
|
||||
"From search": "From search",
|
||||
"Manage tags": "Manage tags",
|
||||
"Notification Delivery": "Notification Delivery",
|
||||
"Choose how you'd like to receive your Odysee notifications.": "Choose how you'd like to receive your Odysee notifications.",
|
||||
"Desktop Notifications": "Desktop Notifications",
|
||||
"Browser Notifications": "Browser Notifications",
|
||||
"Receive push notifications in this browser, even when you're not on odysee.com": "Receive push notifications in this browser, even when you're not on odysee.com",
|
||||
"Email Notification Topics": "Email Notification Topics",
|
||||
"Choose which topics you’d like to be emailed about.": "Choose which topics you’d like to be emailed about.",
|
||||
"Email Notifications": "Email Notifications",
|
||||
"Receive notifications to the email address: %email%": "Receive notifications to the email address: %email%",
|
||||
"Realtime push notifications straight to your browser.": "Realtime push notifications straight to your browser.",
|
||||
"Don't miss another notification again.": "Don't miss another notification again.",
|
||||
"Enable Push Notifications": "Enable Push Notifications",
|
||||
"Dismiss": "Dismiss",
|
||||
"Heads up: browser notifications are currently blocked in this browser.": "Heads up: browser notifications are currently blocked in this browser.",
|
||||
"To enable push notifications please configure your browser to allow notifications on odysee.com.": "To enable push notifications please configure your browser to allow notifications on odysee.com.",
|
||||
"There was an error enabling browser notifications. Please make sure your browser settings allow you to subscribe to notifications.": "There was an error enabling browser notifications. Please make sure your browser settings allow you to subscribe to notifications.",
|
||||
"--end--": "--end--"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BB8DNPB73F"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('consent', 'default', {
|
||||
'ad_storage': 'denied',
|
||||
'analytics_storage': 'denied',
|
||||
});
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-BB8DNPB73F');
|
||||
</script>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
|
|
265
ui/analytics.js
265
ui/analytics.js
|
@ -1,15 +1,38 @@
|
|||
// @flow
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import MatomoTracker from '@datapunt/matomo-tracker-js';
|
||||
import { history } from './store';
|
||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||
import { SDK_API_PATH } from './index';
|
||||
// @if TARGET='app'
|
||||
import Native from 'native';
|
||||
import ElectronCookies from '@exponent/electron-cookies';
|
||||
import { generateInitialUrl } from 'util/url';
|
||||
// @endif
|
||||
import { MATOMO_ID, MATOMO_URL } from 'config';
|
||||
|
||||
// --- GA ---
|
||||
// - Events: 500 max (cannot be deleted).
|
||||
// - Dimensions: 25 max (cannot be deleted, but can be "archived"). Usually
|
||||
// tied to an event parameter for reporting purposes.
|
||||
//
|
||||
// Given the limitations above, we need to plan ahead before adding new Events
|
||||
// and Parameters.
|
||||
//
|
||||
// Events:
|
||||
// - Find a Recommended Event that is closest to what you need.
|
||||
// https://support.google.com/analytics/answer/9267735?hl=en
|
||||
// - If doesn't exist, use a Custom Event.
|
||||
//
|
||||
// Parameters:
|
||||
// - Custom parameters don't appear in automated reports until they are tied to
|
||||
// a Dimension.
|
||||
// - Add your entry to GA_DIMENSIONS below -- tt allows us to keep track so that
|
||||
// we don't exceed the limit. Re-use existing parameters if possible.
|
||||
// - Register the Dimension in GA Console to make it visible in reports.
|
||||
|
||||
export const GA_DIMENSIONS = {
|
||||
TYPE: 'type',
|
||||
ACTION: 'action',
|
||||
VALUE: 'value',
|
||||
START_TIME_MS: 'start_time_ms',
|
||||
DURATION_MS: 'duration_ms',
|
||||
END_TIME_MS: 'end_time_ms',
|
||||
};
|
||||
|
||||
// import getConnectionSpeed from 'util/detect-user-bandwidth';
|
||||
|
||||
// let userDownloadBandwidthInBitsPerSecond;
|
||||
|
@ -24,31 +47,23 @@ const isProduction = process.env.NODE_ENV === 'production';
|
|||
const devInternalApis = process.env.LBRY_API_URL && process.env.LBRY_API_URL.includes('dev');
|
||||
|
||||
export const SHARE_INTERNAL = 'shareInternal';
|
||||
const SHARE_THIRD_PARTY = 'shareThirdParty';
|
||||
|
||||
const WATCHMAN_BACKEND_ENDPOINT = 'https://watchman.na-backend.odysee.com/reports/playback';
|
||||
const SEND_DATA_TO_WATCHMAN_INTERVAL = 10; // in seconds
|
||||
|
||||
// @if TARGET='app'
|
||||
if (isProduction) {
|
||||
ElectronCookies.enable({
|
||||
origin: 'https://lbry.tv',
|
||||
});
|
||||
}
|
||||
// @endif
|
||||
|
||||
type Analytics = {
|
||||
appStartTime: number,
|
||||
eventStartTime: any,
|
||||
error: (string) => Promise<any>,
|
||||
sentryError: ({} | string, {}) => Promise<any>,
|
||||
pageView: (string, ?string) => void,
|
||||
setUser: (Object) => void,
|
||||
toggleInternal: (boolean, ?boolean) => void,
|
||||
apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>,
|
||||
apiLogPublish: (ChannelClaim | StreamClaim) => void,
|
||||
apiSyncTags: ({}) => void,
|
||||
tagFollowEvent: (string, boolean, ?string) => void,
|
||||
playerLoadedEvent: (?boolean) => void,
|
||||
playerStartedEvent: (?boolean) => void,
|
||||
playerLoadedEvent: (string, ?boolean) => void,
|
||||
playerVideoStartedEvent: (?boolean) => void,
|
||||
videoStartEvent: (string, number, string, number, string, any, number) => void,
|
||||
videoIsPlaying: (boolean, any) => void,
|
||||
videoBufferEvent: (
|
||||
|
@ -64,15 +79,16 @@ type Analytics = {
|
|||
}
|
||||
) => Promise<any>,
|
||||
adsFetchedEvent: () => void,
|
||||
adsReceivedEvent: (any) => void,
|
||||
adsErrorEvent: (any) => void,
|
||||
emailProvidedEvent: () => void,
|
||||
emailVerifiedEvent: () => void,
|
||||
rewardEligibleEvent: () => void,
|
||||
startupEvent: () => void,
|
||||
initAppStartTime: (startTime: number) => void,
|
||||
startupEvent: (time: number) => void,
|
||||
eventStarted: (name: string, time: number, id?: string) => void,
|
||||
eventCompleted: (name: string, time: number, id?: string) => void,
|
||||
purchaseEvent: (number) => void,
|
||||
readyEvent: (number) => void,
|
||||
openUrlEvent: (string) => void,
|
||||
reportEvent: (string, any) => void,
|
||||
};
|
||||
|
||||
type LogPublishParams = {
|
||||
|
@ -84,10 +100,8 @@ type LogPublishParams = {
|
|||
|
||||
let internalAnalyticsEnabled: boolean = IS_WEB || false;
|
||||
// let thirdPartyAnalyticsEnabled: boolean = IS_WEB || false;
|
||||
// @if TARGET='app'
|
||||
if (window.localStorage.getItem(SHARE_INTERNAL) === 'true') internalAnalyticsEnabled = true;
|
||||
// if (window.localStorage.getItem(SHARE_THIRD_PARTY) === 'true') thirdPartyAnalyticsEnabled = true;
|
||||
// @endif
|
||||
|
||||
const isGaAllowed = internalAnalyticsEnabled && isProduction;
|
||||
|
||||
/**
|
||||
* Determine the mobile device type viewing the data
|
||||
|
@ -208,15 +222,14 @@ async function sendWatchmanData(body) {
|
|||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (err) {
|
||||
console.log('ERROR FROM WATCHMAN BACKEND');
|
||||
console.log(err);
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
const analytics: Analytics = {
|
||||
appStartTime: 0,
|
||||
eventStartTime: {},
|
||||
|
||||
// receive buffer events from tracking plugin and save buffer amounts and times for backend call
|
||||
videoBufferEvent: async (claim, data) => {
|
||||
amountOfBufferEvents = amountOfBufferEvents + 1;
|
||||
|
@ -255,7 +268,7 @@ const analytics: Analytics = {
|
|||
startWatchmanIntervalIfNotRunning();
|
||||
}
|
||||
},
|
||||
videoStartEvent: (claimId, duration, poweredBy, passedUserId, canonicalUrl, passedPlayer, videoBitrate) => {
|
||||
videoStartEvent: (claimId, timeToStartVideo, poweredBy, passedUserId, canonicalUrl, passedPlayer, videoBitrate) => {
|
||||
// populate values for watchman when video starts
|
||||
userId = passedUserId;
|
||||
claimUrl = canonicalUrl;
|
||||
|
@ -265,8 +278,7 @@ const analytics: Analytics = {
|
|||
videoPlayer = passedPlayer;
|
||||
bitrateAsBitsPerSecond = videoBitrate;
|
||||
|
||||
sendPromMetric('time_to_start', duration);
|
||||
sendMatomoEvent('Media', 'TimeToStart', claimId, duration);
|
||||
sendPromMetric('time_to_start', timeToStartVideo);
|
||||
},
|
||||
error: (message) => {
|
||||
return new Promise((resolve) => {
|
||||
|
@ -292,45 +304,17 @@ const analytics: Analytics = {
|
|||
}
|
||||
});
|
||||
},
|
||||
pageView: (path, search) => {
|
||||
if (internalAnalyticsEnabled) {
|
||||
const params: { href: string, customDimensions?: Array<{ id: number, value: ?string }> } = { href: `${path}` };
|
||||
const dimensions = [];
|
||||
const searchParams = search && new URLSearchParams(search);
|
||||
|
||||
if (searchParams && searchParams.get('src')) {
|
||||
dimensions.push({ id: 1, value: searchParams.get('src') });
|
||||
}
|
||||
if (dimensions.length) {
|
||||
params['customDimensions'] = dimensions;
|
||||
}
|
||||
MatomoInstance.trackPageView(params);
|
||||
}
|
||||
},
|
||||
setUser: (userId) => {
|
||||
if (internalAnalyticsEnabled && userId) {
|
||||
window._paq.push(['setUserId', String(userId)]);
|
||||
// @if TARGET='app'
|
||||
Native.getAppVersionInfo().then(({ localVersion }) => {
|
||||
sendMatomoEvent('Version', 'Desktop-Version', localVersion);
|
||||
});
|
||||
// @endif
|
||||
if (isGaAllowed && userId && window.gtag) {
|
||||
window.gtag('set', { user_id: userId });
|
||||
}
|
||||
},
|
||||
toggleInternal: (enabled: boolean): void => {
|
||||
// Always collect analytics on lbry.tv
|
||||
// @if TARGET='app'
|
||||
internalAnalyticsEnabled = enabled;
|
||||
window.localStorage.setItem(SHARE_INTERNAL, enabled);
|
||||
// @endif
|
||||
// Always collect analytics on Odysee for now.
|
||||
},
|
||||
|
||||
toggleThirdParty: (enabled: boolean): void => {
|
||||
// Always collect analytics on lbry.tv
|
||||
// @if TARGET='app'
|
||||
// thirdPartyAnalyticsEnabled = enabled;
|
||||
window.localStorage.setItem(SHARE_THIRD_PARTY, enabled);
|
||||
// @endif
|
||||
// Always collect analytics on Odysee for now.
|
||||
},
|
||||
|
||||
apiLogView: (uri, outpoint, claimId, timeToStart) => {
|
||||
|
@ -387,56 +371,108 @@ const analytics: Analytics = {
|
|||
}
|
||||
},
|
||||
adsFetchedEvent: () => {
|
||||
sendMatomoEvent('Media', 'AdsFetched');
|
||||
sendGaEvent('ad_fetched');
|
||||
},
|
||||
adsReceivedEvent: (response) => {
|
||||
sendMatomoEvent('Media', 'AdsReceived', JSON.stringify(response));
|
||||
playerLoadedEvent: (renderMode, embedded) => {
|
||||
const RENDER_MODE_TO_EVENT = (renderMode) => {
|
||||
switch (renderMode) {
|
||||
case RENDER_MODES.VIDEO:
|
||||
return 'loaded_video';
|
||||
case RENDER_MODES.AUDIO:
|
||||
return 'loaded_audio';
|
||||
case RENDER_MODES.MARKDOWN:
|
||||
return 'loaded_markdown';
|
||||
case RENDER_MODES.IMAGE:
|
||||
return 'loaded_image';
|
||||
default:
|
||||
return 'loaded_misc';
|
||||
}
|
||||
};
|
||||
|
||||
sendGaEvent('player', {
|
||||
[GA_DIMENSIONS.ACTION]: RENDER_MODE_TO_EVENT(renderMode),
|
||||
[GA_DIMENSIONS.TYPE]: embedded ? 'embedded' : 'onsite',
|
||||
});
|
||||
},
|
||||
adsErrorEvent: (response) => {
|
||||
sendMatomoEvent('Media', 'AdsError', JSON.stringify(response));
|
||||
},
|
||||
playerLoadedEvent: (embedded) => {
|
||||
sendMatomoEvent('Player', 'Loaded', embedded ? 'embedded' : 'onsite');
|
||||
},
|
||||
playerStartedEvent: (embedded) => {
|
||||
sendMatomoEvent('Player', 'Started', embedded ? 'embedded' : 'onsite');
|
||||
playerVideoStartedEvent: (embedded) => {
|
||||
sendGaEvent('player', {
|
||||
[GA_DIMENSIONS.ACTION]: 'started_video',
|
||||
[GA_DIMENSIONS.TYPE]: embedded ? 'embedded' : 'onsite',
|
||||
});
|
||||
},
|
||||
tagFollowEvent: (tag, following) => {
|
||||
sendMatomoEvent('Tag', following ? 'Tag-Follow' : 'Tag-Unfollow', tag);
|
||||
},
|
||||
channelBlockEvent: (uri, blocked, location) => {
|
||||
sendMatomoEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri);
|
||||
sendGaEvent('tags', {
|
||||
[GA_DIMENSIONS.ACTION]: following ? 'follow' : 'unfollow',
|
||||
[GA_DIMENSIONS.VALUE]: tag,
|
||||
});
|
||||
},
|
||||
emailProvidedEvent: () => {
|
||||
sendMatomoEvent('Engagement', 'Email-Provided');
|
||||
sendGaEvent('engagement', {
|
||||
[GA_DIMENSIONS.TYPE]: 'email_provided',
|
||||
});
|
||||
},
|
||||
emailVerifiedEvent: () => {
|
||||
sendMatomoEvent('Engagement', 'Email-Verified');
|
||||
sendGaEvent('engagement', {
|
||||
[GA_DIMENSIONS.TYPE]: 'email_verified',
|
||||
});
|
||||
},
|
||||
rewardEligibleEvent: () => {
|
||||
sendMatomoEvent('Engagement', 'Reward-Eligible');
|
||||
sendGaEvent('engagement', {
|
||||
[GA_DIMENSIONS.TYPE]: 'reward_eligible',
|
||||
});
|
||||
},
|
||||
openUrlEvent: (url: string) => {
|
||||
sendMatomoEvent('Engagement', 'Open-Url', url);
|
||||
sendGaEvent('engagement', {
|
||||
[GA_DIMENSIONS.TYPE]: 'open_url',
|
||||
url,
|
||||
});
|
||||
},
|
||||
trendingAlgorithmEvent: (trendingAlgorithm: string) => {
|
||||
sendMatomoEvent('Engagement', 'Trending-Algorithm', trendingAlgorithm);
|
||||
sendGaEvent('engagement', {
|
||||
[GA_DIMENSIONS.TYPE]: 'trending_algorithm',
|
||||
trending_algorithm: trendingAlgorithm,
|
||||
});
|
||||
},
|
||||
startupEvent: () => {
|
||||
sendMatomoEvent('Startup', 'Startup');
|
||||
initAppStartTime: (startTime: number) => {
|
||||
analytics.appStartTime = startTime;
|
||||
},
|
||||
readyEvent: (timeToReady: number) => {
|
||||
sendMatomoEvent('Startup', 'App-Ready', 'Time', timeToReady);
|
||||
startupEvent: (time: number) => {
|
||||
if (analytics.appStartTime !== 0) {
|
||||
sendGaEvent('diag_app_ready', {
|
||||
[GA_DIMENSIONS.DURATION_MS]: time - analytics.appStartTime,
|
||||
});
|
||||
}
|
||||
},
|
||||
eventStarted: (name: string, time: number, id?: string) => {
|
||||
const key = id || name;
|
||||
analytics.eventStartTime[key] = time;
|
||||
},
|
||||
eventCompleted: (name: string, time: number, id?: string) => {
|
||||
const key = id || name;
|
||||
if (analytics.eventStartTime[key]) {
|
||||
sendGaEvent(name, {
|
||||
[GA_DIMENSIONS.START_TIME_MS]: analytics.eventStartTime[key] - analytics.appStartTime,
|
||||
[GA_DIMENSIONS.DURATION_MS]: time - analytics.eventStartTime[key],
|
||||
[GA_DIMENSIONS.END_TIME_MS]: time - analytics.appStartTime,
|
||||
});
|
||||
|
||||
delete analytics.eventStartTime[key];
|
||||
}
|
||||
},
|
||||
purchaseEvent: (purchaseInt: number) => {
|
||||
sendMatomoEvent('Purchase', 'Purchase-Complete', 'someLabel', purchaseInt);
|
||||
sendGaEvent('purchase', {
|
||||
// https://developers.google.com/analytics/devguides/collection/ga4/reference/events#purchase
|
||||
[GA_DIMENSIONS.VALUE]: purchaseInt,
|
||||
});
|
||||
},
|
||||
reportEvent: (event: string, params?: { [string]: string | number }) => {
|
||||
sendGaEvent(event, params);
|
||||
},
|
||||
};
|
||||
|
||||
function sendMatomoEvent(category, action, name, value) {
|
||||
if (internalAnalyticsEnabled) {
|
||||
const event = { category, action, name, value };
|
||||
MatomoInstance.trackEvent(event);
|
||||
function sendGaEvent(event: string, params?: { [string]: string | number }) {
|
||||
if (isGaAllowed && window.gtag) {
|
||||
window.gtag('event', event, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,35 +485,12 @@ function sendPromMetric(name: string, value?: number) {
|
|||
}
|
||||
}
|
||||
|
||||
const MatomoInstance = new MatomoTracker({
|
||||
urlBase: MATOMO_URL,
|
||||
siteId: MATOMO_ID, // optional, default value: `1`
|
||||
// heartBeat: { // optional, enabled by default
|
||||
// active: true, // optional, default value: true
|
||||
// seconds: 10 // optional, default value: `15
|
||||
// },
|
||||
// linkTracking: false // optional, default value: true
|
||||
});
|
||||
|
||||
// Manually call the first page view
|
||||
// React Router doesn't include this on `history.listen`
|
||||
// @if TARGET='web'
|
||||
analytics.pageView(window.location.pathname + window.location.search, window.location.search);
|
||||
// @endif
|
||||
|
||||
// @if TARGET='app'
|
||||
analytics.pageView(
|
||||
window.location.pathname.split('.html')[1] + window.location.search || generateInitialUrl(window.location.hash)
|
||||
);
|
||||
// @endif;
|
||||
|
||||
// Listen for url changes and report
|
||||
// This will include search queries
|
||||
history.listen((location) => {
|
||||
const { pathname, search } = location;
|
||||
|
||||
const page = `${pathname}${search}`;
|
||||
analytics.pageView(page, search);
|
||||
// Activate
|
||||
if (isGaAllowed && window.gtag) {
|
||||
window.gtag('consent', 'update', {
|
||||
ad_storage: 'granted',
|
||||
analytics_storage: 'granted',
|
||||
});
|
||||
}
|
||||
|
||||
export default analytics;
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
import { connect } from 'react-redux';
|
||||
import IframeReact from './view';
|
||||
|
||||
const select = state => ({});
|
||||
|
||||
const perform = () => ({});
|
||||
|
||||
export default connect(select, perform)(IframeReact);
|
||||
export default IframeReact;
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { connect } from 'react-redux';
|
||||
import AbandonedChannelPreview from './view';
|
||||
|
||||
const select = (state, props) => ({});
|
||||
|
||||
export default connect(select)(AbandonedChannelPreview);
|
||||
export default AbandonedChannelPreview;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import { parseURI } from 'lbry-redux';
|
||||
import { parseURI } from 'util/lbryURI';
|
||||
import ChannelBlockButton from 'component/channelBlockButton';
|
||||
import ChannelMuteButton from 'component/channelMuteButton';
|
||||
import SubscribeButton from 'component/subscribeButton';
|
||||
|
|
|
@ -5,13 +5,9 @@ import { selectGetSyncErrorMessage, selectSyncFatalError } from 'redux/selectors
|
|||
import { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user';
|
||||
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
||||
import {
|
||||
doFetchChannelListMine,
|
||||
doFetchCollectionListMine,
|
||||
SETTINGS,
|
||||
selectMyChannelUrls,
|
||||
doResolveUris,
|
||||
} from 'lbry-redux';
|
||||
import { doFetchChannelListMine, doFetchCollectionListMine, doResolveUris } from 'redux/actions/claims';
|
||||
import { selectMyChannelUrls } from 'redux/selectors/claims';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import {
|
||||
makeSelectClientSetting,
|
||||
|
@ -24,6 +20,7 @@ import {
|
|||
selectAutoUpdateDownloaded,
|
||||
selectModal,
|
||||
selectActiveChannelClaim,
|
||||
selectIsReloadRequired,
|
||||
} from 'redux/selectors/app';
|
||||
import { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings';
|
||||
import { doSyncLoop } from 'redux/actions/sync';
|
||||
|
@ -46,6 +43,7 @@ const select = (state) => ({
|
|||
languages: selectLoadedLanguages(state),
|
||||
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
|
||||
isUpgradeAvailable: selectIsUpgradeAvailable(state),
|
||||
isReloadRequired: selectIsReloadRequired(state),
|
||||
syncError: selectGetSyncErrorMessage(state),
|
||||
uploadCount: selectUploadCount(state),
|
||||
rewards: selectUnclaimedRewards(state),
|
||||
|
|
|
@ -4,8 +4,9 @@ import React, { useEffect, useRef, useState, useLayoutEffect } from 'react';
|
|||
import { lazyImport } from 'util/lazyImport';
|
||||
import classnames from 'classnames';
|
||||
import analytics from 'analytics';
|
||||
import { buildURI, parseURI } from 'lbry-redux';
|
||||
import { buildURI, parseURI } from 'util/lbryURI';
|
||||
import { SIMPLE_SITE } from 'config';
|
||||
import Nag from 'component/common/nag';
|
||||
import Router from 'component/router/index';
|
||||
import ReactModal from 'react-modal';
|
||||
import { openContextMenu } from 'util/context-menu';
|
||||
|
@ -17,10 +18,12 @@ import REWARDS from 'rewards';
|
|||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import Spinner from 'component/spinner';
|
||||
import LANGUAGES from 'constants/languages';
|
||||
|
||||
// @if TARGET='app'
|
||||
import useZoom from 'effects/use-zoom';
|
||||
import useHistoryNav from 'effects/use-history-nav';
|
||||
// @endif
|
||||
|
||||
// @if TARGET='web'
|
||||
import {
|
||||
useDegradedPerformance,
|
||||
|
@ -30,11 +33,11 @@ import {
|
|||
STATUS_DOWN,
|
||||
} from 'web/effects/use-degraded-performance';
|
||||
// @endif
|
||||
|
||||
import LANGUAGE_MIGRATIONS from 'constants/language-migrations';
|
||||
|
||||
const FileDrop = lazyImport(() => import('component/fileDrop' /* webpackChunkName: "secondary" */));
|
||||
const ModalRouter = lazyImport(() => import('modal/modalRouter' /* webpackChunkName: "secondary" */));
|
||||
const Nag = lazyImport(() => import('component/common/nag' /* webpackChunkName: "secondary" */));
|
||||
const NagContinueFirstRun = lazyImport(() =>
|
||||
import('component/nagContinueFirstRun' /* webpackChunkName: "secondary" */)
|
||||
);
|
||||
|
@ -67,6 +70,8 @@ export const IS_MAC = navigator.userAgent.indexOf('Mac OS X') !== -1;
|
|||
const MOUSE_BACK_BTN = 3;
|
||||
const MOUSE_FORWARD_BTN = 4;
|
||||
|
||||
const imaLibraryPath = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js';
|
||||
|
||||
type Props = {
|
||||
language: string,
|
||||
languages: Array<string>,
|
||||
|
@ -88,6 +93,7 @@ type Props = {
|
|||
onSignedIn: () => void,
|
||||
setLanguage: (string) => void,
|
||||
isUpgradeAvailable: boolean,
|
||||
isReloadRequired: boolean,
|
||||
autoUpdateDownloaded: boolean,
|
||||
updatePreferences: () => Promise<any>,
|
||||
getWalletSyncPref: () => Promise<any>,
|
||||
|
@ -122,6 +128,7 @@ function App(props: Props) {
|
|||
signIn,
|
||||
autoUpdateDownloaded,
|
||||
isUpgradeAvailable,
|
||||
isReloadRequired,
|
||||
requestDownloadUpgrade,
|
||||
uploadCount,
|
||||
history,
|
||||
|
@ -325,21 +332,19 @@ function App(props: Props) {
|
|||
}
|
||||
}, [previousRewardApproved, isRewardApproved]);
|
||||
|
||||
// Load IMA3 SDK for aniview: DISABLED FOR NOW
|
||||
// Load IMA3 SDK for aniview
|
||||
// @if TARGET='web'
|
||||
// useEffect(() => {
|
||||
// if (ENABLE_PREROLL_ADS) {
|
||||
// const script = document.createElement('script');
|
||||
// script.src = `https://imasdk.googleapis.com/js/sdkloader/ima3.js`;
|
||||
// script.async = true;
|
||||
// // $FlowFixMe
|
||||
// document.body.appendChild(script);
|
||||
// return () => {
|
||||
// // $FlowFixMe
|
||||
// document.body.removeChild(script);
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
useEffect(() => {
|
||||
const script = document.createElement('script');
|
||||
script.src = imaLibraryPath;
|
||||
script.async = true;
|
||||
// $FlowFixMe
|
||||
document.body.appendChild(script);
|
||||
return () => {
|
||||
// $FlowFixMe
|
||||
document.body.removeChild(script);
|
||||
};
|
||||
}, []);
|
||||
// @endif
|
||||
|
||||
// @if TARGET='app'
|
||||
|
@ -493,6 +498,14 @@ function App(props: Props) {
|
|||
{user === null && <NagNoUser />}
|
||||
{/* @endif */}
|
||||
</React.Suspense>
|
||||
|
||||
{isReloadRequired && (
|
||||
<Nag
|
||||
message={__('A new version of Odysee is available.')}
|
||||
actionText={__('Refresh')}
|
||||
onClick={() => window.location.reload()}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { withRouter } from 'react-router';
|
||||
import AutoplayCountdown from './view';
|
||||
import { selectModal } from 'redux/selectors/app';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectMetadataItemForUri, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectMetadataItemForUri, makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import ChannelAbout from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimIdForUri } from 'lbry-redux';
|
||||
import { makeSelectClaimIdForUri } from 'redux/selectors/claims';
|
||||
import {
|
||||
doCommentModUnBlock,
|
||||
doCommentModBlock,
|
||||
|
|
|
@ -6,9 +6,9 @@ import {
|
|||
makeSelectClaimIsMine,
|
||||
makeSelectTotalPagesInChannelSearch,
|
||||
makeSelectClaimForUri,
|
||||
doResolveUris,
|
||||
SETTINGS,
|
||||
} from 'lbry-redux';
|
||||
} from 'redux/selectors/claims';
|
||||
import { doResolveUris } from 'redux/actions/claims';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||
import { withRouter } from 'react-router';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
|
|
|
@ -125,11 +125,11 @@ function ChannelContent(props: Props) {
|
|||
<section className="card card--section">
|
||||
<p>
|
||||
{__(
|
||||
'In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this channel from our applications.'
|
||||
'In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this channel from our applications. Content may also be blocked due to DMCA Red Flag rules which are obvious copyright violations we come across, are discussed in public channels, or reported to us.'
|
||||
)}
|
||||
</p>
|
||||
<div className="section__actions">
|
||||
<Button button="link" href="https://lbry.com/faq/dmca" label={__('Read More')} />
|
||||
<Button button="link" href="https://odysee.com/@OdyseeHelp:b/copyright:f" label={__('Read More')} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
|||
import { withRouter } from 'react-router';
|
||||
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
|
||||
import ChannelDiscussion from './view';
|
||||
import { makeSelectTagInClaimOrChannelForUri } from 'lbry-redux';
|
||||
import { makeSelectTagInClaimOrChannelForUri } from 'redux/selectors/claims';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { search } = props.location;
|
||||
|
|
|
@ -4,17 +4,15 @@ import {
|
|||
makeSelectThumbnailForUri,
|
||||
makeSelectCoverForUri,
|
||||
makeSelectMetadataItemForUri,
|
||||
doUpdateChannel,
|
||||
doCreateChannel,
|
||||
makeSelectAmountForUri,
|
||||
makeSelectClaimForUri,
|
||||
selectUpdateChannelError,
|
||||
selectUpdatingChannel,
|
||||
selectCreateChannelError,
|
||||
selectCreatingChannel,
|
||||
selectBalance,
|
||||
doClearChannelErrors,
|
||||
} from 'lbry-redux';
|
||||
} from 'redux/selectors/claims';
|
||||
import { selectBalance } from 'redux/selectors/wallet';
|
||||
import { doUpdateChannel, doCreateChannel, doClearChannelErrors } from 'redux/actions/claims';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { doUpdateBlockListForPublishedChannel } from 'redux/actions/comments';
|
||||
import { doClaimInitialRewards } from 'redux/actions/rewards';
|
||||
|
|
|
@ -9,7 +9,7 @@ import TagsSearch from 'component/tagsSearch';
|
|||
import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field';
|
||||
import ErrorText from 'component/common/error-text';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import { isNameValid, parseURI } from 'lbry-redux';
|
||||
import { isNameValid, parseURI } from 'util/lbryURI';
|
||||
import ClaimAbandonButton from 'component/claimAbandonButton';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR, ESTIMATED_FEE } from 'constants/claim';
|
||||
|
@ -253,7 +253,7 @@ function ChannelForm(props: Props) {
|
|||
let nameError;
|
||||
if (!name && name !== undefined) {
|
||||
nameError = __('A name is required for your url');
|
||||
} else if (!isNameValid(name, false)) {
|
||||
} else if (!isNameValid(name)) {
|
||||
nameError = INVALID_NAME_ERROR;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, makeSelectIsUriResolving } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri, makeSelectIsUriResolving } from 'redux/selectors/claims';
|
||||
import ChannelMentionSuggestion from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
|
@ -2,13 +2,14 @@ import { connect } from 'react-redux';
|
|||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import { withRouter } from 'react-router';
|
||||
import { doResolveUris, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectTopLevelCommentsForUri } from 'redux/selectors/comments';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { doResolveUris } from 'redux/actions/claims';
|
||||
import { selectTopLevelCommentsForUri } from 'redux/selectors/comments';
|
||||
import ChannelMentionSuggestions from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
const subscriptionUris = selectSubscriptions(state).map(({ uri }) => uri);
|
||||
const topLevelComments = makeSelectTopLevelCommentsForUri(props.uri)(state);
|
||||
const topLevelComments = selectTopLevelCommentsForUri(state, props.uri);
|
||||
|
||||
const commentorUris = [];
|
||||
// Avoid repeated commentors
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList } from '@reach/combobox';
|
||||
import { Form } from 'component/common/form';
|
||||
import { parseURI, regexInvalidURI } from 'lbry-redux';
|
||||
import { parseURI, regexInvalidURI } from 'util/lbryURI';
|
||||
import { SEARCH_OPTIONS } from 'constants/search';
|
||||
import * as KEYCODES from 'constants/keycodes';
|
||||
import ChannelMentionSuggestion from 'component/channelMentionSuggestion';
|
||||
|
@ -69,8 +69,9 @@ export default function ChannelMentionSuggestions(props: Props) {
|
|||
const allShownCanonical = [canonicalCreator, ...canonicalSubscriptions, ...canonicalCommentors];
|
||||
const possibleMatches = allShownUris.filter((uri) => {
|
||||
try {
|
||||
// yuck a try catch in a filter?
|
||||
const { channelName } = parseURI(uri);
|
||||
return channelName.toLowerCase().includes(termToMatch);
|
||||
return channelName && channelName.toLowerCase().includes(termToMatch);
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectIsUriResolving, doResolveUri } from 'lbry-redux';
|
||||
import { makeSelectIsUriResolving } from 'redux/selectors/claims';
|
||||
import { doResolveUri } from 'redux/actions/claims';
|
||||
import { makeSelectWinningUriForQuery } from 'redux/selectors/search';
|
||||
import ChannelMentionTopSuggestion from './view';
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectMyChannelClaims } from 'lbry-redux';
|
||||
import { selectMyChannelClaims } from 'redux/selectors/claims';
|
||||
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
|
||||
import { doSetActiveChannel, doSetIncognito } from 'redux/actions/app';
|
||||
import ChannelSelector from './view';
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
makeSelectClaimForUri,
|
||||
makeSelectStakedLevelForChannelUri,
|
||||
makeSelectTotalStakedAmountForChannelUri,
|
||||
} from 'lbry-redux';
|
||||
} from 'redux/selectors/claims';
|
||||
import ChannelStakedIndicator from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectThumbnailForUri, doResolveUri, makeSelectClaimForUri, makeSelectIsUriResolving } from 'lbry-redux';
|
||||
import { makeSelectThumbnailForUri, makeSelectClaimForUri, makeSelectIsUriResolving } from 'redux/selectors/claims';
|
||||
import { doResolveUri } from 'redux/actions/claims';
|
||||
import ChannelThumbnail from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { parseURI } from 'lbry-redux';
|
||||
import { parseURI } from 'util/lbryURI';
|
||||
import classnames from 'classnames';
|
||||
import Gerbil from './gerbil.png';
|
||||
import FreezeframeWrapper from 'component/fileThumbnail/FreezeframeWrapper';
|
||||
|
@ -10,7 +10,7 @@ import { AVATAR_DEFAULT } from 'config';
|
|||
|
||||
type Props = {
|
||||
thumbnail: ?string,
|
||||
uri: ?string,
|
||||
uri: string,
|
||||
className?: string,
|
||||
thumbnailPreview: ?string,
|
||||
obscure?: boolean,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, makeSelectTitleForUri } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri, makeSelectTitleForUri } from 'redux/selectors/claims';
|
||||
import ChannelTitle from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
|||
import { doOpenModal } from 'redux/actions/app';
|
||||
import ClaimAbandonButton from './view';
|
||||
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: props.uri && makeSelectClaimForUri(props.uri)(state),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectChannelForClaimUri } from 'lbry-redux';
|
||||
import { makeSelectChannelForClaimUri } from 'redux/selectors/claims';
|
||||
import ClaimAuthor from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
|
@ -2,12 +2,12 @@ import { connect } from 'react-redux';
|
|||
import ClaimCollectionAdd from './view';
|
||||
import { withRouter } from 'react-router';
|
||||
import {
|
||||
makeSelectClaimForUri,
|
||||
doLocalCollectionCreate,
|
||||
selectBuiltinCollections,
|
||||
selectMyPublishedCollections,
|
||||
selectMyUnpublishedCollections,
|
||||
} from 'lbry-redux';
|
||||
} from 'redux/selectors/collections';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { doLocalCollectionCreate } from 'redux/actions/collections';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import CollectionAddButton from './view';
|
||||
import { makeSelectClaimForUri, makeSelectClaimUrlInCollection } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { makeSelectClaimUrlInCollection } from 'redux/selectors/collections';
|
||||
|
||||
const select = (state, props) => {
|
||||
const claim = makeSelectClaimForUri(props.uri)(state);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import ClaimEffectiveAmount from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectInsufficientCreditsForUri } from 'redux/selectors/content';
|
||||
import { makeSelectClaimWasPurchased } from 'lbry-redux';
|
||||
import { makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
||||
import ClaimInsufficientCredits from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doResolveUri, makeSelectClaimForUri, makeSelectIsUriResolving } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri, makeSelectIsUriResolving } from 'redux/selectors/claims';
|
||||
import { doResolveUri } from 'redux/actions/claims';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import { punctuationMarks } from 'util/remark-lbry';
|
||||
import { selectBlackListedOutpoints } from 'lbryinc';
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import ClaimList from './view';
|
||||
import { SETTINGS } from 'lbry-redux';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
|
||||
const select = (state) => ({
|
||||
|
|
|
@ -94,11 +94,21 @@ export default function ClaimList(props: Props) {
|
|||
setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW);
|
||||
}
|
||||
|
||||
function handleClaimClicked(e, claim, index) {
|
||||
const handleClaimClicked = React.useCallback(
|
||||
(e, claim, index) => {
|
||||
if (onClick) {
|
||||
onClick(e, claim, index);
|
||||
}
|
||||
}
|
||||
},
|
||||
[onClick]
|
||||
);
|
||||
|
||||
const customShouldHide = React.useCallback((claim: StreamClaim) => {
|
||||
// Hack to hide spee.ch thumbnail publishes
|
||||
// If it meets these requirements, it was probably uploaded here:
|
||||
// https://github.com/lbryio/lbry-redux/blob/master/src/redux/actions/publish.js#L74-L79
|
||||
return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch';
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = debounce((e) => {
|
||||
|
@ -195,13 +205,8 @@ export default function ClaimList(props: Props) {
|
|||
showHiddenByUser={showHiddenByUser}
|
||||
collectionId={collectionId}
|
||||
showNoSourceClaims={showNoSourceClaims}
|
||||
customShouldHide={(claim: StreamClaim) => {
|
||||
// Hack to hide spee.ch thumbnail publishes
|
||||
// If it meets these requirements, it was probably uploaded here:
|
||||
// https://github.com/lbryio/lbry-redux/blob/master/src/redux/actions/publish.js#L74-L79
|
||||
return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch';
|
||||
}}
|
||||
onClick={(e, claim, index) => handleClaimClicked(e, claim, index)}
|
||||
customShouldHide={customShouldHide}
|
||||
onClick={handleClaimClicked}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doClaimSearch,
|
||||
selectClaimsByUri,
|
||||
selectClaimSearchByQuery,
|
||||
selectClaimSearchByQueryLastPageReached,
|
||||
selectFetchingClaimSearch,
|
||||
SETTINGS,
|
||||
} from 'lbry-redux';
|
||||
} from 'redux/selectors/claims';
|
||||
import { doClaimSearch } from 'redux/actions/claims';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { selectFollowedTags } from 'redux/selectors/tags';
|
||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
||||
|
|
|
@ -5,7 +5,9 @@ import * as CS from 'constants/claim_search';
|
|||
import React from 'react';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import { withRouter } from 'react-router';
|
||||
import { createNormalizedClaimSearchKey, MATURE_TAGS, splitBySeparator } from 'lbry-redux';
|
||||
import { MATURE_TAGS } from 'constants/tags';
|
||||
import { createNormalizedClaimSearchKey } from 'util/claim';
|
||||
import { splitBySeparator } from 'util/lbryURI';
|
||||
import Button from 'component/button';
|
||||
import moment from 'moment';
|
||||
import ClaimList from 'component/claimList';
|
||||
|
@ -450,7 +452,9 @@ function ClaimListDiscover(props: Props) {
|
|||
<p>
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
contact_support: <Button button="link" label={__('contact support')} href="https://odysee.com/@OdyseeHelp:b?view=about" />,
|
||||
contact_support: (
|
||||
<Button button="link" label={__('contact support')} href="https://odysee.com/@OdyseeHelp:b?view=about" />
|
||||
),
|
||||
}}
|
||||
>
|
||||
If you continue to have issues, please %contact_support%.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue