import * as React from 'react'; import { Component, View } from 'reactxp'; import { links } from '../../config.json'; import { NoCreditError, NoInternetError } from '../../main/errors'; import { ITunnelEndpoint, parseSocketAddress } from '../../shared/daemon-rpc-types'; import { pgettext } from '../../shared/gettext'; import * as AppButton from './AppButton'; import styles from './ConnectStyles'; import { Brand, HeaderBarStyle, SettingsBarButton } from './HeaderBar'; import ImageView from './ImageView'; import { Container, Header, Layout } from './Layout'; import Map, { MarkerStyle, ZoomLevel } from './Map'; import NotificationArea from './NotificationArea'; import TunnelControl, { IRelayInAddress, IRelayOutAddress } from './TunnelControl'; import AccountExpiry from '../lib/account-expiry'; import { IConnectionReduxState } from '../redux/connection/reducers'; import { IVersionReduxState } from '../redux/version/reducers'; interface IProps { connection: IConnectionReduxState; version: IVersionReduxState; accountExpiry?: AccountExpiry; selectedRelayName: string; connectionInfoOpen: boolean; blockWhenDisconnected: boolean; onSettings: () => void; onSelectLocation: () => void; onConnect: () => void; onDisconnect: () => void; onExternalLink: (url: string) => void; onToggleConnectionInfo: (value: boolean) => void; } type MarkerOrSpinner = 'marker' | 'spinner'; export default class Connect extends Component { public render() { const error = this.checkForErrors(); const child = error ? this.renderError(error) : this.renderMap(); return (
{child}
); } public renderError(error: Error) { let title = ''; let message = ''; if (error instanceof NoCreditError) { title = pgettext('connect-view', 'Out of time'); message = pgettext( 'connect-view', 'Buy more time, so you can continue using the internet securely', ); } if (error instanceof NoInternetError) { title = pgettext('connect-view', 'Offline'); message = pgettext( 'connect-view', 'Your internet connection will be secured when you get back online', ); } const { isBlocked } = this.props.connection; return ( {title} {message} {error instanceof NoCreditError ? ( Buy more time ) : null} ); } public renderMap() { const status = this.props.connection.status; const relayOutAddress: IRelayOutAddress = { ipv4: this.props.connection.ip, }; const relayInAddress: IRelayInAddress | undefined = (status.state === 'connecting' || status.state === 'connected') && status.details ? this.tunnelEndpointToRelayInAddress(status.details) : undefined; return ( {/* show spinner when connecting */} {this.showMarkerOrSpinner() === 'spinner' ? ( ) : null} ); } private handleBuyMorePress = () => { this.props.onExternalLink(links.purchase); }; private headerBarStyle(): HeaderBarStyle { const { status } = this.props.connection; switch (status.state) { case 'disconnected': return HeaderBarStyle.error; case 'connecting': case 'connected': return HeaderBarStyle.success; case 'blocked': switch (status.details.reason) { case 'set_firewall_policy_error': return HeaderBarStyle.error; default: return HeaderBarStyle.success; } case 'disconnecting': switch (status.details) { case 'block': case 'reconnect': return HeaderBarStyle.success; case 'nothing': return HeaderBarStyle.error; default: throw new Error(`Invalid action after disconnection: ${status.details}`); } } } private checkForErrors(): Error | undefined { // Offline? if (!this.props.connection.isOnline) { return new NoInternetError(); } // No credit? if (this.props.accountExpiry && this.props.accountExpiry.hasExpired()) { return new NoCreditError(); } return undefined; } private getMapProps(): Map['props'] { const { longitude, latitude, status: { state }, } = this.props.connection; // when the user location is known if (typeof longitude === 'number' && typeof latitude === 'number') { return { center: [longitude, latitude], // do not show the marker when connecting or reconnecting showMarker: this.showMarkerOrSpinner() === 'marker', markerStyle: this.getMarkerStyle(), // zoom in when connected zoomLevel: state === 'connected' ? ZoomLevel.low : ZoomLevel.medium, // a magic offset to align marker with spinner offset: [0, 123], }; } else { return { center: [0, 0], showMarker: false, markerStyle: MarkerStyle.unsecure, // show the world when user location is not known zoomLevel: ZoomLevel.high, // remove the offset since the marker is hidden offset: [0, 0], }; } } private getMarkerStyle(): MarkerStyle { const { status } = this.props.connection; switch (status.state) { case 'connecting': case 'connected': return MarkerStyle.secure; case 'blocked': switch (status.details.reason) { case 'set_firewall_policy_error': return MarkerStyle.unsecure; default: return MarkerStyle.secure; } case 'disconnected': return MarkerStyle.unsecure; case 'disconnecting': switch (status.details) { case 'block': case 'reconnect': return MarkerStyle.secure; case 'nothing': return MarkerStyle.unsecure; default: throw new Error(`Invalid action after disconnection: ${status.details}`); } } } private showMarkerOrSpinner(): MarkerOrSpinner { const status = this.props.connection.status; return status.state === 'connecting' || (status.state === 'disconnecting' && status.details === 'reconnect') ? 'spinner' : 'marker'; } private tunnelEndpointToRelayInAddress(tunnelEndpoint: ITunnelEndpoint): IRelayInAddress { const socketAddr = parseSocketAddress(tunnelEndpoint.address); return { ip: socketAddr.host, port: socketAddr.port, protocol: tunnelEndpoint.protocol, }; } }