Commit 1701bc74 authored by Jason Salyers's avatar Jason Salyers
Browse files

[JES] Change options for managing API endpoints. Users can now choose a...

[JES] Change options for managing API endpoints. Users can now choose a preferred, add their own, remove ones they don't want, and these changes automatically get set in hive-js as well for failover options
parent d1c43c22
......@@ -11,17 +11,41 @@ import Dropzone from 'react-dropzone';
import MuteList from 'app/components/elements/MuteList';
import { isLoggedIn } from 'app/utils/UserUtil';
import * as api from '@hiveio/hive-js';
import Cookies from 'universal-cookie';
//TODO?: Maybe move this to a config file somewhere?
const KNOWN_API_NODES = [
'api.hive.blog',
'anyx.io',
'hived.hive-engine.com',
'api.followbtcnews.com',
'rpc.esteem.app',
'api.openhive.network',
'api.pharesim.me',
'hive.roelandp.nl',
'hived.privex.io',
'techcoderx.com',
'hive.3speak.online',
'rpc.ausbit.dev',
'api.hivekings.com',
];
class Settings extends React.Component {
constructor(props) {
super(props);
let cookies = new Cookies();
this.state = {
errorMessage: '',
successMessage: '',
progress: {},
expand_advanced: false,
cookies: cookies,
endpoint_error_message: '',
original_api_endpoints: '', //safety precaution for the moment
};
this.initForm(props);
this.onNsfwPrefChange = this.onNsfwPrefChange.bind(this);
this.resetEndpointOptions = this.resetEndpointOptions.bind(this);
}
componentWillMount() {
......@@ -38,6 +62,28 @@ class Settings extends React.Component {
}
}
componentDidMount() {
//Create the cookies if they don't already exist
let endpoints = [];
if (!this.state.cookies.get('user_preferred_api_endpoint')) {
let default_endpoint = 'https://api.hive.blog';
this.state.cookies.set(
'user_preferred_api_endpoint',
default_endpoint
);
}
if (!this.state.cookies.get('user_api_endpoints')) {
endpoints = api.config.get('alternative_api_endpoints');
for (var node of KNOWN_API_NODES) endpoints.push('https://' + node);
this.state.cookies.set('user_api_endpoints', endpoints);
} else endpoints = this.state.cookies.get('user_api_endpoints');
let preferred = this.getPreferredApiEndpoint();
api.api.setOptions({ url: preferred });
this.synchronizeLists();
}
initForm(props) {
reactForm({
instance: this,
......@@ -264,53 +310,172 @@ class Settings extends React.Component {
};
getPreferredApiEndpoint = () => {
let preferred_api_endpoint = $STM_Config.steemd_connection_client;
if (
typeof window !== 'undefined' &&
localStorage.getItem('user_preferred_api_endpoint')
) {
preferred_api_endpoint = localStorage.getItem(
let preferred_api_endpoint = 'https://api.hive.blog';
if (this.state.cookies.get('user_preferred_api_endpoint'))
preferred_api_endpoint = this.state.cookies.get(
'user_preferred_api_endpoint'
);
}
return preferred_api_endpoint;
};
resetEndpointOptions = () => {
this.state.cookies.set(
'user_preferred_api_endpoint',
'https://api.hive.blog'
);
this.state.cookies.set('user_api_endpoints', []);
let preferred_api_endpoint = 'https://api.hive.blog';
let alternative_api_endpoints = api.config.get(
'alternative_api_endpoints'
);
alternative_api_endpoints.length = 0;
for (var node of KNOWN_API_NODES)
alternative_api_endpoints.push('https://' + node);
api.api.setOptions({ url: preferred_api_endpoint });
let cookies = this.state.cookies;
cookies.set('user_preferred_api_endpoint', preferred_api_endpoint);
cookies.set('user_api_endpoints', alternative_api_endpoints);
this.setState({ cookies: cookies, endpoint_error_message: '' });
};
synchronizeLists = () => {
let preferred = this.getPreferredApiEndpoint();
let alternative_api_endpoints = api.config.get(
'alternative_api_endpoints'
);
let user_endpoints = this.state.cookies.get('user_api_endpoints');
api.api.setOptions({ url: preferred });
alternative_api_endpoints.length = 0;
for (var user_endpoint of user_endpoints)
alternative_api_endpoints.push(user_endpoint);
};
setPreferredApiEndpoint = event => {
let cookies = this.state.cookies;
cookies.set('user_preferred_api_endpoint', event.target.value);
this.setState({ cookies: cookies, endpoint_error_message: '' }); //doing it this way to force a re-render, otherwise the option doesn't look updated even though it is
api.api.setOptions({ url: event.target.value });
};
generateAPIEndpointOptions = () => {
const endpoints = api.config.get('alternative_api_endpoints');
let user_endpoints = this.state.cookies.get('user_api_endpoints');
if (endpoints === null || endpoints === undefined) {
return null;
}
if (user_endpoints === null || user_endpoints === undefined) return;
const preferred_api_endpoint = this.getPreferredApiEndpoint();
const entries = [];
for (let ei = 0; ei < endpoints.length; ei += 1) {
const endpoint = endpoints[ei];
//this one is always present even if the api config call fails
if (endpoint !== preferred_api_endpoint) {
const entry = (
<option key={endpoint} value={endpoint}>
{endpoint}
</option>
);
entries.push(entry);
}
for (let ei = 0; ei < user_endpoints.length; ei += 1) {
const endpoint = user_endpoints[ei];
let entry = (
<tr key={endpoint + 'key'}>
<td>{endpoint}</td>
<td>
<input
type="radio"
value={endpoint}
checked={endpoint === preferred_api_endpoint}
onChange={e => this.setPreferredApiEndpoint(e)}
/>
</td>
<td style={{ fontSize: '20px' }}>
<button
onClick={e => {
this.removeAPIEndpoint(endpoint);
}}
>
{'\u2612'}
</button>
</td>
</tr>
);
entries.push(entry);
}
return entries;
};
handlePreferredAPIEndpointChange = event => {
if (typeof window !== 'undefined') {
localStorage.setItem(
'user_preferred_api_endpoint',
event.target.value
);
api.api.setOptions({ url: event.target.value });
toggleShowAdvancedSettings = event => {
this.setState({ expand_advanced: !this.state.expand_advanced });
};
addAPIEndpoint = value => {
this.setState({ endpoint_error_message: '' });
let validated = /^https?:\/\//.test(value);
if (!validated) {
this.setState({
endpoint_error_message:
'This appears to be a bad URL, please check it and try again',
});
return;
}
let cookies = this.state.cookies;
let endpoints = cookies.get('user_api_endpoints');
if (endpoints === null || endpoints === undefined) {
this.setState({
endpoint_error_message: 'Unable to get endpoints from cookie',
});
return;
}
for (var endpoint of endpoints) {
if (endpoint === value) {
this.setState({
endpoint_error_message:
'This server is already in the list',
});
return;
}
}
endpoints.push(value);
cookies.set('user_api_endpoints', endpoints);
this.setState({ cookies: cookies }, () => {
this.synchronizeLists();
});
};
removeAPIEndpoint = value => {
this.setState({ endpoint_error_message: '' });
//don't remove the active endpoint
//don't remove it if it is the only endpoint in the list
let active_endpoint = this.getPreferredApiEndpoint();
if (value === active_endpoint) {
this.setState({
endpoint_error_message:
"Can't remove the current preferred endpoint. Please select a new preffered endpoint first",
});
return;
}
let cookies = this.state.cookies;
let endpoints = cookies.get('user_api_endpoints');
if (endpoints === null || endpoints === undefined) {
this.setState({
endpoint_error_message: 'Unable to get endpoints from cookie',
});
return;
}
if (endpoints.length == 1) {
this.setState({
endpoint_error_message:
'You must have at least 1 valid endpoint in your list',
});
return;
}
let new_endpoints = [];
for (var endpoint of endpoints) {
if (endpoint !== value) {
new_endpoints.push(endpoint);
}
}
cookies.set('user_api_endpoints', new_endpoints);
this.setState({ cookies: cookies }, () => {
this.synchronizeLists();
});
};
render() {
......@@ -345,7 +510,7 @@ class Settings extends React.Component {
account_is_witness,
} = this.state;
const preferred_api_endpoint = this.getPreferredApiEndpoint();
const endpoint_options = this.generateAPIEndpointOptions();
return (
<div className="Settings">
......@@ -696,30 +861,6 @@ class Settings extends React.Component {
</select>
</label>
</div>
<div className="form__field column small-12 medium-6 large-4">
<label>
{tt(
'settings_jsx.choose_preferred_api_endpoint'
)}
<select
defaultValue={
preferred_api_endpoint
}
onChange={
this
.handlePreferredAPIEndpointChange
}
>
<option
value={preferred_api_endpoint}
>
{preferred_api_endpoint}
</option>
{this.generateAPIEndpointOptions()}
</select>
</label>
</div>
<div className="form__field column small-12 medium-6 large-4">
<label>
{tt(
......@@ -750,6 +891,96 @@ class Settings extends React.Component {
</div>
</div>
)}
<br />
<div className="row">
<div className="large-12 columns">
<h4 onClick={this.toggleShowAdvancedSettings}>
{tt('settings_jsx.advanced') + ' '}{' '}
{this.state.expand_advanced ? '\u25B2' : '\u25BC'}
</h4>
{this.state.expand_advanced && (
<div>
<b>{tt('settings_jsx.api_endpoint_options')}</b>
<table style={{ width: '60%' }}>
<thead />
<tbody>
<tr>
<td style={{ width: '50%' }}>
<b>
{tt(
'settings_jsx.endpoint'
)}
</b>
</td>
<td style={{ width: '25%' }}>
<b>
{tt(
'settings_jsx.preferred'
)}
</b>
</td>
<td style={{ width: '25%' }}>
<b>
{tt('settings_jsx.remove')}
</b>
</td>
</tr>
{endpoint_options}
</tbody>
</table>
<h4>
<b>{tt('settings_jsx.add_api_endpoint')}</b>
</h4>
<table style={{ width: '60%' }}>
<thead />
<tbody>
<tr>
<td style={{ width: '40%' }}>
<input
type="text"
ref={el =>
(this.new_endpoint = el)
}
/>
</td>
<td
style={{
width: '20%',
fontSize: '30px',
}}
>
<button
onClick={e =>
this.addAPIEndpoint(
this.new_endpoint
.value
)
}
>
{'\u2713'}
</button>
</td>
<td>
<div className="error">
{
this.state
.endpoint_error_message
}
</div>
</td>
</tr>
</tbody>
</table>
<span
className="button"
onClick={this.resetEndpointOptions}
>
{tt('settings_jsx.reset_endpoints')}
</span>
</div>
)}
</div>
</div>
{ignores &&
ignores.size > 0 && (
<div className="row">
......
......@@ -743,7 +743,14 @@
"choose_preferred_api_endpoint": "Choose Your Preferred API Node",
"default_beneficiaries": "Referral System",
"default_beneficiaries_enabled": "Use Default Beneficiaries",
"default_beneficiaries_disabled": "Opt-Out Referral System"
"default_beneficiaries_disabled": "Opt-Out Referral System",
"advanced": "Advanced",
"api_endpoint_options": "API Endpoint Options",
"endpoint": "Endpoint",
"preferred": "Preferred?",
"remove": "Remove",
"add_api_endpoint": "Add API Endpoint",
"reset_endpoints": "Reset Endpoints"
},
"transfer_jsx": {
"amount_is_in_form": "Amount is in the form 99999.999",
......
......@@ -625,7 +625,13 @@
"profile_about": "Sobre",
"profile_location": "Localización",
"profile_website": "Página Web",
"choose_preferred_api_endpoint": "Elija su nodo de API preferido"
"advanced": "Avanzado",
"api_endpoint_options": "Opciones de punto final de API",
"endpoint": "Punto final",
"preferred": "Privilegiado?",
"remove": "Eliminar",
"add_api_endpoint": "Agregar punto final de API",
"reset_endpoints": "Restablecer puntos finales"
},
"transfer_jsx": {
"amount_is_in_form": "La cantidad está en el formato 99999.999",
......
......@@ -653,7 +653,13 @@
"profile_about": "A propos",
"profile_location": "Lieu",
"profile_website": "Site internet",
"choose_preferred_api_endpoint": "Choisissez votre nœud d'API préféré"
"advanced": "Avancée",
"api_endpoint_options": "Options de point de terminaison API",
"endpoint": "Point final",
"preferred": "Préféré?",
"remove": "Retirer",
"add_api_endpoint": "Ajouter un point de terminaison d'API",
"reset_endpoints": "Réinitialiser les points de terminaison"
},
"transfer_jsx": {
"amount_is_in_form": "Montant dans le format 99999.999",
......
......@@ -632,7 +632,13 @@
"profile_about": "About",
"profile_location": "Località",
"profile_website": "Sito",
"choose_preferred_api_endpoint": "Scegli il tuo nodo API preferito"
"advanced": "Avanzate",
"api_endpoint_options": "Opzioni endpoint API",
"endpoint": "Endpoint",
"preferred": "Preferito?",
"remove": "Rimuovere",
"add_api_endpoint": "Aggiungi endpoint API",
"reset_endpoints": "Reimposta endpoint"
},
"transfer_jsx": {
"amount_is_in_form": "La quantita deve essere della forma 99999.999",
......
......@@ -630,7 +630,13 @@
"profile_about": "概要",
"profile_location": "場所",
"profile_website": "ウェブサイト",
"choose_preferred_api_endpoint": "優先APIノードを選択してください"
"advanced": "高度な",
"api_endpoint_options": "APIエンドポイントオプション",
"endpoint": "終点",
"preferred": "優先?",
"remove": "削除する",
"add_api_endpoint": "APIエンドポイントを追加",
"reset_endpoints": "エンドポイントをリセット"
},
"transfer_jsx": {
"amount_is_in_form": "金額は99999.999の形式です",
......
......@@ -627,7 +627,13 @@
"profile_about": "한 줄 소개",
"profile_location": "지역",
"profile_website": "웹사이트",
"choose_preferred_api_endpoint": "기본 API 노드 선택"
"advanced": "많은",
"api_endpoint_options": "API 엔드 포인트 옵션",
"endpoint": "엔드 포인트",
"preferred": "선호하는?",
"remove": "없애다",
"add_api_endpoint": "API 엔드 포인트 추가",
"reset_endpoints": "엔드 포인트 재설정"
},
"transfer_jsx": {
"amount_is_in_form": "Amount is in the form 99999.999",
......
......@@ -345,6 +345,11 @@
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/cookie@^0.3.3":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803"
integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==
"@types/inline-style-prefixer@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/inline-style-prefixer/-/inline-style-prefixer-3.0.1.tgz#8541e636b029124b747952e9a28848286d2b5bf6"
......@@ -358,6 +363,11 @@
version "9.4.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.0.tgz#b85a0bcf1e1cc84eb4901b7e96966aedc6f078d1"
"@types/object-assign@^4.0.30":
version "4.0.30"
resolved "https://registry.yarnpkg.com/@types/object-assign/-/object-assign-4.0.30.tgz#8949371d5a99f4381ee0f1df0a9b7a187e07e652"
integrity sha1-iUk3HVqZ9Dge4PHfCpt6GH4H5lI=
"@types/react@^16.0.18":
version "16.0.36"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.36.tgz#ceb5639013bdb92a94147883052e69bb2c22c69b"
......@@ -2590,6 +2600,11 @@ cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
cookie@^0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
cookies@~0.7.0:
version "0.7.1"
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.7.1.tgz#7c8a615f5481c61ab9f16c833731bcb8f663b99b"
......@@ -9542,6 +9557,16 @@ unique-string@^1.0.0:
dependencies:
crypto-random-string "^1.0.0"
universal-cookie@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/universal-cookie/-/universal-cookie-4.0.3.tgz#c2fa59127260e6ad21ef3e0cdd66ad453cbc41f6"
integrity sha512-YbEHRs7bYOBTIWedTR9koVEe2mXrq+xdjTJZcoKJK/pQaE6ni28ak2AKXFpevb+X6w3iU5SXzWDiJkmpDRb9qw==
dependencies:
"@types/cookie" "^0.3.3"
"@types/object-assign" "^4.0.30"
cookie "^0.4.0"
object-assign "^4.1.1"
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment